From dfdcf78344ade11c313fcfe79278c17ba9186c3a Mon Sep 17 00:00:00 2001 From: bkfox Date: Sat, 30 May 2020 14:50:07 +0200 Subject: [PATCH] schedule & diffusions check/update + cleanup Schedule methods --- aircox/admin/episode.py | 10 ++-- aircox/admin/program.py | 16 +++++- aircox/locale/fr/LC_MESSAGES/django.po | 2 +- aircox/management/commands/diffusions.py | 23 --------- aircox/models/episode.py | 6 ++- aircox/models/program.py | 63 +++--------------------- aircox/models/signals.py | 30 +++++------ scripts/cron | 4 +- 8 files changed, 46 insertions(+), 108 deletions(-) diff --git a/aircox/admin/episode.py b/aircox/admin/episode.py index 526768e..cd688a4 100644 --- a/aircox/admin/episode.py +++ b/aircox/admin/episode.py @@ -1,7 +1,7 @@ from copy import copy -from django import forms from django.contrib import admin +from django.forms import ModelForm from django.utils.translation import gettext as _ from ..models import Episode, Diffusion @@ -11,7 +11,8 @@ from .sound import SoundInline, TrackInline class DiffusionBaseAdmin: - fields = ['type', 'start', 'end'] + fields = ('type', 'start', 'end', 'schedule') + readonly_fields = ('schedule',) def get_readonly_fields(self, request, obj=None): fields = super().get_readonly_fields(request, obj) @@ -35,7 +36,8 @@ class DiffusionAdmin(DiffusionBaseAdmin, admin.ModelAdmin): list_editable = ('type',) ordering = ('-start', 'id') - fields = ['type', 'start', 'end', 'initial', 'program'] + fields = ('type', 'start', 'end', 'initial', 'program', 'schedule') + readonly_fields = ('schedule',) class DiffusionInline(DiffusionBaseAdmin, admin.TabularInline): @@ -47,7 +49,7 @@ class DiffusionInline(DiffusionBaseAdmin, admin.TabularInline): return request.user.has_perm('aircox_program.scheduling') -class EpisodeAdminForm(forms.ModelForm): +class EpisodeAdminForm(ModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields['parent'].required = True diff --git a/aircox/admin/program.py b/aircox/admin/program.py index 057dad3..6d9be1f 100644 --- a/aircox/admin/program.py +++ b/aircox/admin/program.py @@ -1,20 +1,33 @@ from copy import copy from django.contrib import admin +from django.forms import ModelForm from django.utils.translation import gettext_lazy as _ from ..models import Program, Schedule, Stream from .page import PageAdmin +# In order to simplify schedule_post_save algorithm, an existing schedule can't +# update the following fields: "frequency", "date" +class ScheduleInlineForm(ModelForm): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + if self.initial: + self.fields['date'].disabled = True + self.fields['frequency'].disabled = True + + class ScheduleInline(admin.TabularInline): model = Schedule + form = ScheduleInlineForm + readonly_fields = ('timezone',) extra = 1 class StreamInline(admin.TabularInline): - fields = ['delay', 'begin', 'end'] model = Stream + fields = ['delay', 'begin', 'end'] extra = 1 @@ -71,4 +84,3 @@ class StreamAdmin(admin.ModelAdmin): list_display = ('id', 'program', 'delay', 'begin', 'end') - diff --git a/aircox/locale/fr/LC_MESSAGES/django.po b/aircox/locale/fr/LC_MESSAGES/django.po index be0a6bf..5799c1b 100644 --- a/aircox/locale/fr/LC_MESSAGES/django.po +++ b/aircox/locale/fr/LC_MESSAGES/django.po @@ -203,7 +203,7 @@ msgstr "corbeille" #: models/page.py:73 msgid "status" -msgstr "status" +msgstr "statut" #: models/page.py:77 msgid "cover" diff --git a/aircox/management/commands/diffusions.py b/aircox/management/commands/diffusions.py index 11cd393..b5a2cea 100755 --- a/aircox/management/commands/diffusions.py +++ b/aircox/management/commands/diffusions.py @@ -52,23 +52,6 @@ class Actions: logger.info('[clean] %d diffusions will be removed', qs.count()) qs.delete() - def check(self): - # TODO: redo - qs = Diffusion.objects.filter(type=Diffusion.TYPE_UNCONFIRMED, - start__gt=self.date) - items = [] - for diffusion in qs: - schedules = Schedule.objects.filter(program=diffusion.program) - for schedule in schedules: - if schedule.match(diffusion.start): - break - else: - items.append(diffusion.id) - - logger.info('[check] %d diffusions will be removed', len(items)) - if items: - Diffusion.objects.filter(id__in=items).delete() - class Command(BaseCommand): help = __doc__ @@ -88,12 +71,6 @@ class Command(BaseCommand): '-l', '--clean', action='store_true', help='remove unconfirmed diffusions older than the given month' ) - group.add_argument( - '-c', '--check', action='store_true', - help='check unconfirmed later diffusions from the given ' - 'date agains\'t schedule. If no schedule is found, remove ' - 'it.' - ) group = parser.add_argument_group('date') group.add_argument( diff --git a/aircox/models/episode.py b/aircox/models/episode.py index d225e76..166c7da 100644 --- a/aircox/models/episode.py +++ b/aircox/models/episode.py @@ -9,7 +9,7 @@ from django.utils.functional import cached_property from aircox import settings, utils from .program import Program, ProgramChildQuerySet, \ - BaseRerun, BaseRerunQuerySet + BaseRerun, BaseRerunQuerySet, Schedule from .page import Page, PageQuerySet @@ -145,6 +145,10 @@ class Diffusion(BaseRerun): episode = models.ForeignKey( Episode, models.CASCADE, verbose_name=_('episode'), ) + schedule = models.ForeignKey( + Schedule, models.CASCADE, verbose_name=_('schedule'), + blank=True, null=True, + ) type = models.SmallIntegerField( verbose_name=_('type'), default=TYPE_ON_AIR, choices=TYPE_CHOICES, ) diff --git a/aircox/models/program.py b/aircox/models/program.py index 80fd5dd..f78afc5 100644 --- a/aircox/models/program.py +++ b/aircox/models/program.py @@ -229,7 +229,7 @@ class BaseRerun(models.Model): }) -# BIG FIXME: self.date is still used as datetime +# ? BIG FIXME: self.date is still used as datetime class Schedule(BaseRerun): """ A Schedule defines time slots of programs' diffusions. It can be an initial @@ -284,7 +284,6 @@ class Schedule(BaseRerun): }[x]) for x, y in Frequency.__members__.items()], ) - class Meta: verbose_name = _('Schedule') verbose_name_plural = _('Schedules') @@ -340,59 +339,10 @@ class Schedule(BaseRerun): return False - def match(self, date=None, check_time=True): - """ - Return True if the given date(time) matches the schedule. - """ - date = utils.date_or_default( - date, tz.datetime if check_time else datetime.date) - - if self.date.weekday() != date.weekday() or \ - not self.match_week(date): - return False - - # we check against a normalized version (norm_date will have - # schedule's date. - return date == self.normalize(date) if check_time else True - - def match_week(self, date=None): - """ - Return True if the given week number matches the schedule, False - otherwise. - If the schedule is ponctual, return None. - """ - - if self.frequency == Schedule.Frequency.ponctual: - return False - - # since we care only about the week, go to the same day of the week - date = utils.date_or_default(date, datetime.date) - date += tz.timedelta(days=self.date.weekday() - date.weekday()) - - # FIXME this case - - if self.frequency == Schedule.Frequency.one_on_two: - # cf notes in date_of_month - diff = date - utils.cast_date(self.date, datetime.date) - - return not (diff.days % 14) - - first_of_month = date.replace(day=1) - week = date.isocalendar()[1] - first_of_month.isocalendar()[1] - - # weeks of month - - if week == 4: - # fifth week: return if for every week - - return self.frequency == self.Frequency.every - - return (self.frequency & (0b0001 << week) > 0) - def normalize(self, date): """ - Return a new datetime with schedule time. Timezone is handled - using `schedule.timezone`. + Return a datetime set to schedule's time for the provided date, + handling timezone (based on schedule's timezone). """ date = tz.datetime.combine(date, self.time) return self.tz.normalize(self.tz.localize(date)) @@ -412,11 +362,9 @@ class Schedule(BaseRerun): date_wday = date.weekday() # end of month before the wanted weekday: move one week back - if date_wday < sched_wday: date -= tz.timedelta(days=7) date += tz.timedelta(days=sched_wday - date_wday) - return [self.normalize(date)] # move to the first day of the month that matches the schedule's weekday @@ -466,7 +414,8 @@ class Schedule(BaseRerun): # remove dates corresponding to existing diffusions saved = set(Diffusion.objects.filter(start__in=dates.keys(), - program=self.program) + program=self.program, + schedule=self) .values_list('start', flat=True)) # make diffs @@ -487,7 +436,7 @@ class Schedule(BaseRerun): initial = diffusions[initial] diffusions[date] = Diffusion( - episode=episode, type=Diffusion.TYPE_ON_AIR, + episode=episode, schedule=self, type=Diffusion.TYPE_ON_AIR, initial=initial, start=date, end=date+duration ) return episodes.values(), diffusions.values() diff --git a/aircox/models/signals.py b/aircox/models/signals.py index 490445e..c2ccd57 100755 --- a/aircox/models/signals.py +++ b/aircox/models/signals.py @@ -1,6 +1,7 @@ import pytz from django.contrib.auth.models import User, Group, Permission +from django.db import transaction from django.db.models import F, signals from django.dispatch import receiver from django.utils import timezone as tz @@ -62,7 +63,6 @@ def schedule_pre_save(sender, instance, *args, **kwargs): instance._initial = Schedule.objects.get(pk=instance.pk) -# TODO @receiver(signals.post_save, sender=Schedule) def schedule_post_save(sender, instance, created, *args, **kwargs): """ @@ -76,27 +76,21 @@ def schedule_post_save(sender, instance, created, *args, **kwargs): today = tz.datetime.today() delta = instance.normalize(today) - initial.normalize(today) - - qs = Diffusion.objects.program(instance.program).after(tz.now()) - pks = [d.pk for d in qs if initial.match(d.date)] - qs.filter(pk__in=pks).update( - start=F('start') + delta, - end=F('start') + delta + utils.to_timedelta(instance.duration) - ) + duration = utils.to_timedelta(instance.duration) + with transaction.atomic(): + qs = Diffusion.objects.filter(schedule=instance).after(tz.now()) + for diffusion in qs: + diffusion.start = diffusion.start + delta + diffusion.end = diffusion.start + duration + diffusion.save() @receiver(signals.pre_delete, sender=Schedule) def schedule_pre_delete(sender, instance, *args, **kwargs): - """ - Delete later corresponding diffusion to a changed schedule. - """ - if not instance.program.sync: - return - - qs = Diffusion.objects.program(instance.program).after(tz.now()) - pks = [d.pk for d in qs if instance.match(d.date)] - qs.filter(pk__in=pks).delete() - + """ Delete later corresponding diffusion to a changed schedule. """ + Diffusion.objects.filter(schedule=instance).after(tz.now()).delete() + Episode.objects.filter(diffusion__isnull=True, content__isnull=True, + sound__isnull=True).delete() @receiver(signals.post_delete, sender=Diffusion) def diffusion_post_delete(sender, instance, *args, **kwargs): diff --git a/scripts/cron b/scripts/cron index dd307b5..1609fcb 100755 --- a/scripts/cron +++ b/scripts/cron @@ -3,13 +3,13 @@ # aircox daily tasks: # - diffusions monitoring for the current month cd /srv/apps/aircox/ -scripts/launch_in_venv ./manage.py diffusions --update --clean --check +scripts/launch_in_venv ./manage.py diffusions --update --clean # - diffusions monitoring for the next month scripts/launch_in_venv ./manage.py diffusions --update --next-month cd - # - archiver monitoring for the next month -scripts/launch_in_venv ./manage.py archiver -a 90 +scripts/launch_in_venv ./manage.py archiver cd -