schedule & diffusions check/update + cleanup Schedule methods

This commit is contained in:
bkfox 2020-05-30 14:50:07 +02:00
parent 687238752c
commit dfdcf78344
8 changed files with 46 additions and 108 deletions

View File

@ -1,7 +1,7 @@
from copy import copy from copy import copy
from django import forms
from django.contrib import admin from django.contrib import admin
from django.forms import ModelForm
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from ..models import Episode, Diffusion from ..models import Episode, Diffusion
@ -11,7 +11,8 @@ from .sound import SoundInline, TrackInline
class DiffusionBaseAdmin: class DiffusionBaseAdmin:
fields = ['type', 'start', 'end'] fields = ('type', 'start', 'end', 'schedule')
readonly_fields = ('schedule',)
def get_readonly_fields(self, request, obj=None): def get_readonly_fields(self, request, obj=None):
fields = super().get_readonly_fields(request, obj) fields = super().get_readonly_fields(request, obj)
@ -35,7 +36,8 @@ class DiffusionAdmin(DiffusionBaseAdmin, admin.ModelAdmin):
list_editable = ('type',) list_editable = ('type',)
ordering = ('-start', 'id') ordering = ('-start', 'id')
fields = ['type', 'start', 'end', 'initial', 'program'] fields = ('type', 'start', 'end', 'initial', 'program', 'schedule')
readonly_fields = ('schedule',)
class DiffusionInline(DiffusionBaseAdmin, admin.TabularInline): class DiffusionInline(DiffusionBaseAdmin, admin.TabularInline):
@ -47,7 +49,7 @@ class DiffusionInline(DiffusionBaseAdmin, admin.TabularInline):
return request.user.has_perm('aircox_program.scheduling') return request.user.has_perm('aircox_program.scheduling')
class EpisodeAdminForm(forms.ModelForm): class EpisodeAdminForm(ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.fields['parent'].required = True self.fields['parent'].required = True

View File

@ -1,20 +1,33 @@
from copy import copy from copy import copy
from django.contrib import admin from django.contrib import admin
from django.forms import ModelForm
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from ..models import Program, Schedule, Stream from ..models import Program, Schedule, Stream
from .page import PageAdmin 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): class ScheduleInline(admin.TabularInline):
model = Schedule model = Schedule
form = ScheduleInlineForm
readonly_fields = ('timezone',)
extra = 1 extra = 1
class StreamInline(admin.TabularInline): class StreamInline(admin.TabularInline):
fields = ['delay', 'begin', 'end']
model = Stream model = Stream
fields = ['delay', 'begin', 'end']
extra = 1 extra = 1
@ -71,4 +84,3 @@ class StreamAdmin(admin.ModelAdmin):
list_display = ('id', 'program', 'delay', 'begin', 'end') list_display = ('id', 'program', 'delay', 'begin', 'end')

View File

@ -203,7 +203,7 @@ msgstr "corbeille"
#: models/page.py:73 #: models/page.py:73
msgid "status" msgid "status"
msgstr "status" msgstr "statut"
#: models/page.py:77 #: models/page.py:77
msgid "cover" msgid "cover"

View File

@ -52,23 +52,6 @@ class Actions:
logger.info('[clean] %d diffusions will be removed', qs.count()) logger.info('[clean] %d diffusions will be removed', qs.count())
qs.delete() 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): class Command(BaseCommand):
help = __doc__ help = __doc__
@ -88,12 +71,6 @@ class Command(BaseCommand):
'-l', '--clean', action='store_true', '-l', '--clean', action='store_true',
help='remove unconfirmed diffusions older than the given month' 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 = parser.add_argument_group('date')
group.add_argument( group.add_argument(

View File

@ -9,7 +9,7 @@ from django.utils.functional import cached_property
from aircox import settings, utils from aircox import settings, utils
from .program import Program, ProgramChildQuerySet, \ from .program import Program, ProgramChildQuerySet, \
BaseRerun, BaseRerunQuerySet BaseRerun, BaseRerunQuerySet, Schedule
from .page import Page, PageQuerySet from .page import Page, PageQuerySet
@ -145,6 +145,10 @@ class Diffusion(BaseRerun):
episode = models.ForeignKey( episode = models.ForeignKey(
Episode, models.CASCADE, verbose_name=_('episode'), Episode, models.CASCADE, verbose_name=_('episode'),
) )
schedule = models.ForeignKey(
Schedule, models.CASCADE, verbose_name=_('schedule'),
blank=True, null=True,
)
type = models.SmallIntegerField( type = models.SmallIntegerField(
verbose_name=_('type'), default=TYPE_ON_AIR, choices=TYPE_CHOICES, verbose_name=_('type'), default=TYPE_ON_AIR, choices=TYPE_CHOICES,
) )

View File

@ -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): class Schedule(BaseRerun):
""" """
A Schedule defines time slots of programs' diffusions. It can be an initial 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()], }[x]) for x, y in Frequency.__members__.items()],
) )
class Meta: class Meta:
verbose_name = _('Schedule') verbose_name = _('Schedule')
verbose_name_plural = _('Schedules') verbose_name_plural = _('Schedules')
@ -340,59 +339,10 @@ class Schedule(BaseRerun):
return False 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): def normalize(self, date):
""" """
Return a new datetime with schedule time. Timezone is handled Return a datetime set to schedule's time for the provided date,
using `schedule.timezone`. handling timezone (based on schedule's timezone).
""" """
date = tz.datetime.combine(date, self.time) date = tz.datetime.combine(date, self.time)
return self.tz.normalize(self.tz.localize(date)) return self.tz.normalize(self.tz.localize(date))
@ -412,11 +362,9 @@ class Schedule(BaseRerun):
date_wday = date.weekday() date_wday = date.weekday()
# end of month before the wanted weekday: move one week back # end of month before the wanted weekday: move one week back
if date_wday < sched_wday: if date_wday < sched_wday:
date -= tz.timedelta(days=7) date -= tz.timedelta(days=7)
date += tz.timedelta(days=sched_wday - date_wday) date += tz.timedelta(days=sched_wday - date_wday)
return [self.normalize(date)] return [self.normalize(date)]
# move to the first day of the month that matches the schedule's weekday # 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 # remove dates corresponding to existing diffusions
saved = set(Diffusion.objects.filter(start__in=dates.keys(), saved = set(Diffusion.objects.filter(start__in=dates.keys(),
program=self.program) program=self.program,
schedule=self)
.values_list('start', flat=True)) .values_list('start', flat=True))
# make diffs # make diffs
@ -487,7 +436,7 @@ class Schedule(BaseRerun):
initial = diffusions[initial] initial = diffusions[initial]
diffusions[date] = Diffusion( 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 initial=initial, start=date, end=date+duration
) )
return episodes.values(), diffusions.values() return episodes.values(), diffusions.values()

View File

@ -1,6 +1,7 @@
import pytz import pytz
from django.contrib.auth.models import User, Group, Permission from django.contrib.auth.models import User, Group, Permission
from django.db import transaction
from django.db.models import F, signals from django.db.models import F, signals
from django.dispatch import receiver from django.dispatch import receiver
from django.utils import timezone as tz 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) instance._initial = Schedule.objects.get(pk=instance.pk)
# TODO
@receiver(signals.post_save, sender=Schedule) @receiver(signals.post_save, sender=Schedule)
def schedule_post_save(sender, instance, created, *args, **kwargs): 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() today = tz.datetime.today()
delta = instance.normalize(today) - initial.normalize(today) delta = instance.normalize(today) - initial.normalize(today)
duration = utils.to_timedelta(instance.duration)
qs = Diffusion.objects.program(instance.program).after(tz.now()) with transaction.atomic():
pks = [d.pk for d in qs if initial.match(d.date)] qs = Diffusion.objects.filter(schedule=instance).after(tz.now())
qs.filter(pk__in=pks).update( for diffusion in qs:
start=F('start') + delta, diffusion.start = diffusion.start + delta
end=F('start') + delta + utils.to_timedelta(instance.duration) diffusion.end = diffusion.start + duration
) diffusion.save()
@receiver(signals.pre_delete, sender=Schedule) @receiver(signals.pre_delete, sender=Schedule)
def schedule_pre_delete(sender, instance, *args, **kwargs): def schedule_pre_delete(sender, instance, *args, **kwargs):
""" """ Delete later corresponding diffusion to a changed schedule. """
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,
if not instance.program.sync: sound__isnull=True).delete()
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()
@receiver(signals.post_delete, sender=Diffusion) @receiver(signals.post_delete, sender=Diffusion)
def diffusion_post_delete(sender, instance, *args, **kwargs): def diffusion_post_delete(sender, instance, *args, **kwargs):

View File

@ -3,13 +3,13 @@
# aircox daily tasks: # aircox daily tasks:
# - diffusions monitoring for the current month # - diffusions monitoring for the current month
cd /srv/apps/aircox/ 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 # - diffusions monitoring for the next month
scripts/launch_in_venv ./manage.py diffusions --update --next-month scripts/launch_in_venv ./manage.py diffusions --update --next-month
cd - cd -
# - archiver monitoring for the next month # - archiver monitoring for the next month
scripts/launch_in_venv ./manage.py archiver -a 90 scripts/launch_in_venv ./manage.py archiver
cd - cd -