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=<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 <noreply@anthropic.com>
This commit is contained in:
nemesbak 2026-05-28 13:39:04 +02:00
parent 5ea194059b
commit 5fbe38f8d2
5 changed files with 49 additions and 15 deletions

View file

@ -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(

View file

@ -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) {

View file

@ -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;

View file

@ -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;

View file

@ -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);