11# FORK: metadata-management and score endpoints mirroring web-only actions
22# (metadata_sync_views, score_views). URL wiring lives in fork_urls.py.
33import logging
4- from decimal import Decimal , InvalidOperation
54from http import HTTPStatus as HTTP # noqa: N814
65
76from django .apps import apps
87from drf_spectacular .utils import extend_schema
98from rest_framework import views as drf_views
109from rest_framework .response import Response
1110
12- from app import custom_metadata , history_cache
11+ from app import custom_metadata
1312from app import metadata_sync_views as web_metadata_views
1413from app .models import (
15- Episode ,
1614 Item ,
1715 MediaTypes ,
1816 MetadataProviderPreference ,
19- Season ,
2017 Sources ,
2118)
2219from app .services import metadata_resolution
2320
2421from .contract_serializers import DetailErrorSerializer
25- from .helpers import check_valid_type , resolve_episode_coordinate_for_request
22+ from .helpers import (
23+ apply_episode_score ,
24+ check_valid_type ,
25+ get_tracked_season ,
26+ resolve_episode_coordinate_for_request ,
27+ validate_episode_score ,
28+ )
2629from .schema import MEDIA_TYPE_PARAM , MEDIA_TYPE_TV_ONLY_PARAM
2730
2831logger = logging .getLogger (__name__ )
@@ -255,17 +258,7 @@ def patch(
255258 if coordinate_error :
256259 return coordinate_error
257260
258- season = (
259- Season .objects .filter (
260- item__media_id = media_id ,
261- item__source = source ,
262- item__season_number = season_number ,
263- item__episode_number = None ,
264- user = request .user ,
265- )
266- .order_by ("id" )
267- .first ()
268- )
261+ season = get_tracked_season (request .user , media_id , source , season_number )
269262 if season is None :
270263 return Response (
271264 {"detail" : "Season not found or not tracked." },
@@ -277,49 +270,16 @@ def patch(
277270 {"detail" : "'score' is required (number or null)." },
278271 status = HTTP .BAD_REQUEST ,
279272 )
280- raw_score = request .data .get ("score" )
281- score = None
282- if raw_score is not None :
283- try :
284- score = Decimal (str (raw_score ))
285- except (InvalidOperation , TypeError , ValueError ):
286- return Response (
287- {"detail" : "Invalid score." },
288- status = HTTP .BAD_REQUEST ,
289- )
290- if not (Decimal (0 ) <= score <= Decimal (10 )):
291- return Response (
292- {"detail" : "Score must be between 0 and 10." },
293- status = HTTP .BAD_REQUEST ,
294- )
273+ score , error = validate_episode_score (request .data .get ("score" ))
274+ if error :
275+ return error
295276
296- episodes = Episode .objects .filter (
297- related_season = season ,
298- item__episode_number = int (episode_number ),
299- )
300- if not episodes .exists ():
277+ if not apply_episode_score (season , episode_number , score ):
301278 return Response (
302279 {"detail" : "Episode not tracked." },
303280 status = HTTP .NOT_FOUND ,
304281 )
305282
306- episodes .update (score = score )
307-
308- # episodes.update() runs raw SQL and skips post_save, so invalidate
309- # the affected history days like the web view does.
310- day_keys = [
311- history_cache .history_day_key (end_date )
312- for end_date in episodes .values_list ("end_date" , flat = True )
313- ]
314- day_keys = [day_key for day_key in day_keys if day_key ]
315- if day_keys :
316- history_cache .invalidate_history_days (
317- request .user .id ,
318- day_keys = day_keys ,
319- logging_styles = ("sessions" , "repeats" ),
320- reason = "episode_score_change" ,
321- )
322-
323283 return Response (
324284 {"score" : str (score ) if score is not None else None },
325285 status = HTTP .OK ,
0 commit comments