schedule & diffusions check/update + cleanup Schedule methods
This commit is contained in:
parent
687238752c
commit
dfdcf78344
|
@ -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
|
||||||
|
|
|
@ -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')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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 -
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user