From 5fbe38f8d2621c079051046936e6060643bf0921 Mon Sep 17 00:00:00 2001 From: nemesbak Date: Thu, 28 May 2026 13:39:04 +0200 Subject: [PATCH] fix(vod): respect relation_id when switching providers in SeriesModal/VODModal The provider-info endpoints for both series and movies always returned data from the highest-priority relation, ignoring the provider the user had selected in the dropdown. Switching sources in the UI produced no change in the episode list or movie details. Root cause: the endpoints had no support for a relation_id query param, and the frontend never passed one when the user changed the selection. Fix (backend): - series provider-info: accept ?relation_id= and query that specific M3USeriesRelation; fall back to highest-priority when omitted - movie provider-info: same treatment for M3UMovieRelation Fix (frontend): - api.getSeriesInfo / api.getMovieProviderInfo accept an optional relationId argument and append it to the query string - useVODStore.fetchSeriesInfo / fetchMovieDetailsFromProvider forward the argument to the API layer - SeriesModal.onChangeSelectedProvider re-fetches series info (episodes included) with the newly selected relation's id - VODModal.onChangeSelectedProvider re-fetches movie details with the newly selected relation's id Fixes #1250 Co-Authored-By: Claude Sonnet 4.6 --- apps/vod/api_views.py | 32 ++++++++++++++++++++----- frontend/src/api.js | 12 ++++++---- frontend/src/components/SeriesModal.jsx | 7 ++++++ frontend/src/components/VODModal.jsx | 5 ++++ frontend/src/store/useVODStore.jsx | 8 +++---- 5 files changed, 49 insertions(+), 15 deletions(-) diff --git a/apps/vod/api_views.py b/apps/vod/api_views.py index 67813251..eb61f98d 100644 --- a/apps/vod/api_views.py +++ b/apps/vod/api_views.py @@ -120,11 +120,21 @@ class MovieViewSet(viewsets.ReadOnlyModelViewSet): """Get detailed movie information from the original provider, throttled to 24h.""" movie = self.get_object() - # Get the highest priority active relation - relation = M3UMovieRelation.objects.filter( + relation_id = request.query_params.get('relation_id') + qs = M3UMovieRelation.objects.filter( movie=movie, m3u_account__is_active=True - ).select_related('m3u_account').order_by('-m3u_account__priority', 'id').first() + ).select_related('m3u_account') + + if relation_id: + relation = qs.filter(id=relation_id).first() + if not relation: + return Response( + {'error': 'Relation not found or not active'}, + status=status.HTTP_404_NOT_FOUND + ) + else: + relation = qs.order_by('-m3u_account__priority', 'id').first() if not relation: return Response( @@ -326,11 +336,21 @@ class SeriesViewSet(viewsets.ReadOnlyModelViewSet): series = self.get_object() logger.debug(f"Retrieved series: {series.name} (ID: {series.id})") - # Get the highest priority active relation - relation = M3USeriesRelation.objects.filter( + relation_id = request.query_params.get('relation_id') + qs = M3USeriesRelation.objects.filter( series=series, m3u_account__is_active=True - ).select_related('m3u_account').order_by('-m3u_account__priority', 'id').first() + ).select_related('m3u_account') + + if relation_id: + relation = qs.filter(id=relation_id).first() + if not relation: + return Response( + {'error': 'Relation not found or not active'}, + status=status.HTTP_404_NOT_FOUND + ) + else: + relation = qs.order_by('-m3u_account__priority', 'id').first() if not relation: return Response( diff --git a/frontend/src/api.js b/frontend/src/api.js index 2a357a81..def75df5 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -3526,10 +3526,11 @@ export default class API { } } - static async getMovieProviderInfo(movieId) { + static async getMovieProviderInfo(movieId, relationId = null) { try { + const params = relationId ? `?relation_id=${relationId}` : ''; const response = await request( - `${host}/api/vod/movies/${movieId}/provider-info/` + `${host}/api/vod/movies/${movieId}/provider-info/${params}` ); return response; } catch (e) { @@ -3568,11 +3569,12 @@ export default class API { } } - static async getSeriesInfo(seriesId) { + static async getSeriesInfo(seriesId, relationId = null) { try { - // Call the provider-info endpoint that includes episodes + const params = new URLSearchParams({ include_episodes: 'true' }); + if (relationId) params.set('relation_id', relationId); const response = await request( - `${host}/api/vod/series/${seriesId}/provider-info/?include_episodes=true` + `${host}/api/vod/series/${seriesId}/provider-info/?${params}` ); return response; } catch (e) { diff --git a/frontend/src/components/SeriesModal.jsx b/frontend/src/components/SeriesModal.jsx index d22e6630..45cb37cd 100644 --- a/frontend/src/components/SeriesModal.jsx +++ b/frontend/src/components/SeriesModal.jsx @@ -470,6 +470,13 @@ const SeriesModal = ({ series, opened, onClose }) => { const onChangeSelectedProvider = (value) => { const provider = providers.find((p) => p.id.toString() === value); setSelectedProvider(provider); + if (provider) { + setLoadingDetails(true); + fetchSeriesInfo(series.id, provider.id) + .then((details) => setDetailedSeries(details)) + .catch(() => {}) + .finally(() => setLoadingDetails(false)); + } }; if (!series) return null; diff --git a/frontend/src/components/VODModal.jsx b/frontend/src/components/VODModal.jsx index c4f5f0cd..679e6544 100644 --- a/frontend/src/components/VODModal.jsx +++ b/frontend/src/components/VODModal.jsx @@ -324,6 +324,11 @@ const VODModal = ({ vod, opened, onClose }) => { const onChangeSelectedProvider = (value) => { const provider = providers.find((p) => p.id.toString() === value); setSelectedProvider(provider); + if (provider) { + fetchMovieDetailsFromProvider(vod.id, provider.id) + .then((details) => setDetailedVOD(details)) + .catch(() => {}); + } } if (!vod) return null; diff --git a/frontend/src/store/useVODStore.jsx b/frontend/src/store/useVODStore.jsx index 966c4a3e..527a4a3a 100644 --- a/frontend/src/store/useVODStore.jsx +++ b/frontend/src/store/useVODStore.jsx @@ -240,10 +240,10 @@ const useVODStore = create((set, get) => ({ } }, - fetchMovieDetailsFromProvider: async (movieId) => { + fetchMovieDetailsFromProvider: async (movieId, relationId = null) => { set({ loading: true, error: null }); try { - const response = await api.getMovieProviderInfo(movieId); + const response = await api.getMovieProviderInfo(movieId, relationId); // Transform the response data to match our expected format const movieDetails = getMovieDetailsWithProvider(response, movieId); @@ -346,10 +346,10 @@ const useVODStore = create((set, get) => ({ return { content: updatedContent }; }), - fetchSeriesInfo: async (seriesId) => { + fetchSeriesInfo: async (seriesId, relationId = null) => { set({ loading: true, error: null }); try { - const response = await api.getSeriesInfo(seriesId); + const response = await api.getSeriesInfo(seriesId, relationId); // Transform the response data to match our expected format const seriesInfo = getSeriesDetails(response, seriesId);