fix(epg): address second round of reviewer feedback

- Fix NameError in sd_lineups_search: add dispatcharr_version import
- Fix DELETE handler: persist changesRemaining from SD response to custom_properties
- Fix lockout reset time: calculate next midnight UTC (00:00Z) instead of
  rolling now+24h, matching SD's documented reset behavior
- Remove redundant explicit index from SDProgramMD5: unique_together already
  creates this index in Postgres
This commit is contained in:
mwhit 2026-05-19 23:48:11 +00:00
parent 6f316977ad
commit d8c0850fd8
3 changed files with 35 additions and 10 deletions

View file

@ -188,17 +188,22 @@ class EPGSourceViewSet(viewsets.ModelViewSet):
def _save_sd_lockout(self, source):
"""
Persist a hard lockout to EPGSource model fields when SD returns
4100 MAX_LINEUP_CHANGES_REACHED. Lockout clears automatically at next
midnight UTC per SD's documented reset schedule.
Persist a hard lockout to EPGSource custom_properties when SD returns
4100 MAX_LINEUP_CHANGES_REACHED. SD lineup change counters reset at
00:00Z (midnight UTC) per SD's documented behavior — error 4100 states
"lineup changes for today" and all SD rate counters reset at midnight UTC.
Lockout clears automatically when the next midnight UTC passes.
"""
from django.utils import timezone
from datetime import datetime, timedelta, timezone as dt_timezone
now = timezone.now()
# SD resets on a 24-hour rolling window from when the limit was hit.
# We use 24 hours from now rather than next midnight UTC to be safe.
reset_at = now + timedelta(hours=24)
# Calculate next midnight UTC — SD resets at 00:00Z not on a rolling window
tomorrow = (now + timedelta(days=1)).replace(
hour=0, minute=0, second=0, microsecond=0,
tzinfo=dt_timezone.utc
)
reset_at = tomorrow
cp = source.custom_properties or {}
cp['sd_changes_remaining'] = 0
@ -357,9 +362,14 @@ class EPGSourceViewSet(viewsets.ModelViewSet):
"changes_remaining": self._get_sd_changes_remaining(source),
})
resp.raise_for_status()
sd_data = resp.json()
# SD returns changesRemaining on deletes — persist it
changes_remaining = sd_data.get('changesRemaining')
if changes_remaining is not None:
self._save_sd_changes_remaining(source, changes_remaining)
logger.info(f"SD lineup deleted for source {source.id}: {lineup_id}")
return Response({
**resp.json(),
**sd_data,
"changes_remaining": self._get_sd_changes_remaining(source),
})
except http_requests.exceptions.RequestException as e:
@ -377,6 +387,7 @@ class EPGSourceViewSet(viewsets.ModelViewSet):
"""
import requests as http_requests
from apps.epg.tasks import SD_BASE_URL
from version import __version__ as dispatcharr_version
source = self.get_object()
if source.source_type != 'schedules_direct':

View file

@ -0,0 +1,17 @@
# Generated by Django 6.0.4 on 2026-05-19 23:46
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('epg', '0023_schedules_direct'),
]
operations = [
migrations.RemoveIndex(
model_name='sdprogrammd5',
name='epg_sdprogr_epg_sou_222ce1_idx',
),
]

View file

@ -208,9 +208,6 @@ class SDProgramMD5(models.Model):
class Meta:
unique_together = ('epg_source', 'program_id')
indexes = [
models.Index(fields=['epg_source', 'program_id']),
]
def __str__(self):
return f"SDProgramMD5: {self.program_id} ({self.epg_source.name})"