#93: reorganise Rerun, Diffusion, Schedule module #95
|
@ -33,7 +33,7 @@ class ScheduleAdmin(admin.ModelAdmin):
|
||||||
program_title.short_description = _("Program")
|
program_title.short_description = _("Program")
|
||||||
|
|
||||||
def freq(self, obj):
|
def freq(self, obj):
|
||||||
return obj.get_frequency_verbose()
|
return obj.get_frequency_display()
|
||||||
|
|
||||||
freq.short_description = _("Day")
|
freq.short_description = _("Day")
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import calendar
|
import calendar
|
||||||
from collections import OrderedDict
|
|
||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
@ -28,7 +27,7 @@ class Schedule(Rerun):
|
||||||
# Important: the first week is always the first week where the weekday of
|
# Important: the first week is always the first week where the weekday of
|
||||||
# the schedule is present.
|
# the schedule is present.
|
||||||
# For ponctual programs, there is no need for a schedule, only a diffusion
|
# For ponctual programs, there is no need for a schedule, only a diffusion
|
||||||
class Frequency(models.IntegerChoice):
|
class Frequency(models.IntegerChoices):
|
||||||
ponctual = 0b000000, _("ponctual")
|
ponctual = 0b000000, _("ponctual")
|
||||||
first = 0b000001, _("1st {day} of the month")
|
first = 0b000001, _("1st {day} of the month")
|
||||||
second = 0b000010, _("2nd {day} of the month")
|
second = 0b000010, _("2nd {day} of the month")
|
||||||
|
@ -50,7 +49,7 @@ class Schedule(Rerun):
|
||||||
)
|
)
|
||||||
timezone = models.CharField(
|
timezone = models.CharField(
|
||||||
_("timezone"),
|
_("timezone"),
|
||||||
default=tz.get_current_timezone,
|
default=lambda: tz.get_current_timezone().zone,
|
||||||
max_length=100,
|
max_length=100,
|
||||||
choices=[(x, x) for x in pytz.all_timezones],
|
choices=[(x, x) for x in pytz.all_timezones],
|
||||||
help_text=_("timezone used for the date"),
|
help_text=_("timezone used for the date"),
|
||||||
|
@ -71,7 +70,7 @@ class Schedule(Rerun):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "{} - {}, {}".format(
|
return "{} - {}, {}".format(
|
||||||
self.program.title,
|
self.program.title,
|
||||||
self.get_frequency_verbose(),
|
self.get_frequency_display(),
|
||||||
self.time.strftime("%H:%M"),
|
self.time.strftime("%H:%M"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -97,12 +96,12 @@ class Schedule(Rerun):
|
||||||
"""Datetime of the end."""
|
"""Datetime of the end."""
|
||||||
return self.start + utils.to_timedelta(self.duration)
|
return self.start + utils.to_timedelta(self.duration)
|
||||||
|
|
||||||
def get_frequency_verbose(self):
|
def get_frequency_display(self):
|
||||||
"""Return frequency formated for display."""
|
"""Return frequency formated for display."""
|
||||||
from django.template.defaultfilters import date
|
from django.template.defaultfilters import date
|
||||||
|
|
||||||
return (
|
return (
|
||||||
self.get_frequency_display()
|
self._get_FIELD_display(self._meta.get_field("frequency"))
|
||||||
.format(day=date(self.date, "l"))
|
.format(day=date(self.date, "l"))
|
||||||
.capitalize()
|
.capitalize()
|
||||||
)
|
)
|
||||||
|
@ -156,16 +155,6 @@ class Schedule(Rerun):
|
||||||
|
|
||||||
return [self.normalize(date) for date in dates if date.month == month]
|
return [self.normalize(date) for date in dates if date.month == month]
|
||||||
|
|
||||||
def _exclude_existing_date(self, dates):
|
|
||||||
from .diffusion import Diffusion
|
|
||||||
|
|
||||||
saved = set(
|
|
||||||
Diffusion.objects.filter(start__in=dates).values_list(
|
|
||||||
"start", flat=True
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return [date for date in dates if date not in saved]
|
|
||||||
|
|
||||||
def diffusions_of_month(self, date):
|
def diffusions_of_month(self, date):
|
||||||
"""Get episodes and diffusions for month of provided date, including
|
"""Get episodes and diffusions for month of provided date, including
|
||||||
reruns.
|
reruns.
|
||||||
|
@ -186,13 +175,11 @@ class Schedule(Rerun):
|
||||||
(rerun, rerun.date - self.date) for rerun in self.rerun_set.all()
|
(rerun, rerun.date - self.date) for rerun in self.rerun_set.all()
|
||||||
]
|
]
|
||||||
|
|
||||||
dates = OrderedDict((date, None) for date in self.dates_of_month(date))
|
dates = {date: None for date in self.dates_of_month(date)}
|
||||||
dates.update(
|
dates.update(
|
||||||
[
|
|
||||||
(rerun.normalize(date.date() + delta), date)
|
(rerun.normalize(date.date() + delta), date)
|
||||||
for date in dates.keys()
|
for date in list(dates.keys())
|
||||||
for rerun, delta in reruns
|
for rerun, delta in reruns
|
||||||
]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# remove dates corresponding to existing diffusions
|
# remove dates corresponding to existing diffusions
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
<section>
|
<section>
|
||||||
<h4 class="title is-4">{% translate "Diffusions" %}</h4>
|
<h4 class="title is-4">{% translate "Diffusions" %}</h4>
|
||||||
{% for schedule in program.schedule_set.all %}
|
{% for schedule in program.schedule_set.all %}
|
||||||
{{ schedule.get_frequency_verbose }}
|
{{ schedule.get_frequency_display }}
|
||||||
{% with schedule.start|date:"H:i" as start %}
|
{% with schedule.start|date:"H:i" as start %}
|
||||||
{% with schedule.end|date:"H:i" as end %}
|
{% with schedule.end|date:"H:i" as end %}
|
||||||
<time datetime="{{ start }}">{{ start }}</time>
|
<time datetime="{{ start }}">{{ start }}</time>
|
||||||
|
|
|
@ -4,47 +4,60 @@ import itertools
|
||||||
import pytest
|
import pytest
|
||||||
from model_bakery import baker
|
from model_bakery import baker
|
||||||
|
|
||||||
from aircox.models import Diffusion
|
from aircox import models
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def stations():
|
def stations():
|
||||||
return baker.make("aircox.station", quantity=2)
|
return baker.make("aircox.station", _quantity=2)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def programs(stations):
|
def programs(stations):
|
||||||
return list(
|
items = list(
|
||||||
itertools.chain(
|
itertools.chain(
|
||||||
*(
|
*(
|
||||||
baker.make("aircox.program", quantity=3, station=station)
|
baker.make("aircox.program", station=station, _quantity=3)
|
||||||
for station in stations
|
for station in stations
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
for item in items:
|
||||||
|
item.save()
|
||||||
|
return items
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def sched_initials(programs):
|
def sched_initials(programs):
|
||||||
# use concrete class
|
# use concrete class; timezone is provided in order to ensure DST
|
||||||
return [
|
items = [
|
||||||
baker.make("aircox.schedule", program=program, time=time(16, 00))
|
baker.prepare(
|
||||||
|
"aircox.schedule",
|
||||||
|
program=program,
|
||||||
|
time=time(16, 00),
|
||||||
|
timezone="Europe/Brussels",
|
||||||
|
)
|
||||||
for program in programs
|
for program in programs
|
||||||
]
|
]
|
||||||
|
models.Schedule.objects.bulk_create(items)
|
||||||
|
return items
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def sched_reruns(initials):
|
def sched_reruns(sched_initials):
|
||||||
# use concrete class
|
# use concrete class
|
||||||
return [
|
items = [
|
||||||
baker.make(
|
baker.prepare(
|
||||||
"aircox.schedule",
|
"aircox.schedule",
|
||||||
initial=initial,
|
initial=initial,
|
||||||
|
program=initial.program,
|
||||||
date=initial.date,
|
date=initial.date,
|
||||||
time=(initial.start + timedelta(hours=1)).time(),
|
time=(initial.start + timedelta(hours=1)).time(),
|
||||||
)
|
)
|
||||||
for initial in initials
|
for initial in sched_initials
|
||||||
]
|
]
|
||||||
|
models.Schedule.objects.bulk_create(items)
|
||||||
|
return items
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
@ -54,10 +67,6 @@ def schedules(sched_initials, sched_reruns):
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def episodes(programs):
|
def episodes(programs):
|
||||||
items = []
|
return [
|
||||||
for program in programs:
|
baker.make("aircox.episode", parent=program) for program in programs
|
||||||
items += [
|
|
||||||
baker.make("aircox.episode", parent=program, type=type)
|
|
||||||
for type, _ in Diffusion.TYPE_CHOICES
|
|
||||||
]
|
]
|
||||||
return items
|
|
||||||
|
|
|
@ -10,60 +10,60 @@ from aircox.models import Schedule
|
||||||
|
|
||||||
class TestRerunQuerySet:
|
class TestRerunQuerySet:
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_station_by_obj(self, stations):
|
def test_station_by_obj(self, stations, schedules):
|
||||||
for station in stations:
|
for station in stations:
|
||||||
queryset = (
|
queryset = (
|
||||||
Schedule.objects.station(station)
|
Schedule.objects.station(station)
|
||||||
.distinct()
|
.distinct()
|
||||||
.value_list("station", flat=True)
|
.values_list("program__station", flat=True)
|
||||||
)
|
)
|
||||||
assert queryset.count() == 1
|
assert queryset.count() == 1
|
||||||
assert queryset.first() == station
|
assert queryset.first() == station.pk
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_station_by_id(self, stations):
|
def test_station_by_id(self, stations, schedules):
|
||||||
for station in stations:
|
for station in stations:
|
||||||
queryset = (
|
queryset = (
|
||||||
Schedule.objects.station(id=station.pk)
|
Schedule.objects.station(id=station.pk)
|
||||||
.distinct()
|
.distinct()
|
||||||
.value_list("station", flat=True)
|
.values_list("program__station", flat=True)
|
||||||
)
|
)
|
||||||
assert queryset.count() == 1
|
assert queryset.count() == 1
|
||||||
assert queryset.first() == station
|
assert queryset.first() == station.pk
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_program_by_obj(self, programs):
|
def test_program_by_obj(self, programs, schedules):
|
||||||
for program in programs:
|
for program in programs:
|
||||||
queryset = (
|
queryset = (
|
||||||
Schedule.objects.program(program)
|
Schedule.objects.program(program)
|
||||||
.distinct()
|
.distinct()
|
||||||
.value_list("program", flat=True)
|
.values_list("program", flat=True)
|
||||||
)
|
)
|
||||||
assert queryset.count() == 1
|
assert queryset.count() == 1
|
||||||
assert queryset.first() == program
|
assert queryset.first() == program.pk
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_program_by_id(self, programs):
|
def test_program_by_id(self, programs, schedules):
|
||||||
for program in programs:
|
for program in programs:
|
||||||
queryset = (
|
queryset = (
|
||||||
Schedule.objects.program(id=program.pk)
|
Schedule.objects.program(id=program.pk)
|
||||||
.distinct()
|
.distinct()
|
||||||
.value_list("program", flat=True)
|
.values_list("program", flat=True)
|
||||||
)
|
)
|
||||||
assert queryset.count() == 1
|
assert queryset.count() == 1
|
||||||
assert queryset.first() == program
|
assert queryset.first() == program.pk
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_rerun(self, sched_reruns):
|
def test_rerun(self, schedules):
|
||||||
queryset = Schedule.objects.rerun().value_list("initial", flat=True)
|
queryset = Schedule.objects.rerun().values_list("initial", flat=True)
|
||||||
assert None not in queryset
|
assert None not in queryset
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_initial(self, sched_initials):
|
def test_initial(self, schedules):
|
||||||
queryset = (
|
queryset = (
|
||||||
Schedule.objects.rerun()
|
Schedule.objects.initial()
|
||||||
.distinct()
|
.distinct()
|
||||||
.value_list("initial", flat=True)
|
.values_list("initial", flat=True)
|
||||||
)
|
)
|
||||||
assert queryset.count() == 1
|
assert queryset.count() == 1
|
||||||
assert queryset.first() is None
|
assert queryset.first() is None
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
|
from datetime import date, datetime, time, timedelta
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from datetime import datetime
|
from model_bakery import baker
|
||||||
|
|
||||||
|
import calendar
|
||||||
|
from dateutil.relativedelta import relativedelta
|
||||||
|
|
||||||
from aircox import utils
|
from aircox import utils
|
||||||
|
from aircox.models import Diffusion, Schedule
|
||||||
|
|
||||||
|
|
||||||
class TestSchedule:
|
class TestSchedule:
|
||||||
|
@ -32,43 +38,98 @@ class TestSchedule:
|
||||||
delta = utils.to_timedelta(schedule.duration)
|
delta = utils.to_timedelta(schedule.duration)
|
||||||
assert schedule.end - schedule.start == delta
|
assert schedule.end - schedule.start == delta
|
||||||
|
|
||||||
# def test_get_frequency_verbose(self):
|
# def test_get_frequency_display(self):
|
||||||
# pass
|
# pass
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_normalize(self, schedules):
|
def test_normalize(self, schedules):
|
||||||
for schedule in schedules:
|
for schedule in schedules:
|
||||||
dt = datetime.combine(schedule.date, schedule.time)
|
dt = datetime.combine(schedule.date, schedule.time)
|
||||||
assert schedule.normalize(dt).tzinfo.zone == schedule.timezone.zone
|
assert schedule.normalize(dt).tzinfo.zone == schedule.timezone
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_dates_of_month_ponctual(self):
|
def test_dates_of_month_ponctual(self):
|
||||||
pass
|
schedule = baker.prepare(
|
||||||
|
Schedule, frequency=Schedule.Frequency.ponctual
|
||||||
|
)
|
||||||
|
at = schedule.date + relativedelta(months=4)
|
||||||
|
assert schedule.dates_of_month(at) == []
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_dates_of_month_n_day_of_month(self):
|
@pytest.mark.parametrize("months", range(0, 25, 2))
|
||||||
pass
|
@pytest.mark.parametrize("hour", range(0, 24, 3))
|
||||||
|
def test_dates_of_month_last(self, months, hour):
|
||||||
|
schedule = baker.prepare(
|
||||||
|
Schedule, time=time(hour, 00), frequency=Schedule.Frequency.last
|
||||||
|
)
|
||||||
|
at = schedule.date + relativedelta(months=months)
|
||||||
|
datetimes = schedule.dates_of_month(at)
|
||||||
|
assert len(datetimes) == 1
|
||||||
|
|
||||||
|
dt = datetimes[0]
|
||||||
|
self._assert_date(schedule, at, dt)
|
||||||
|
|
||||||
|
month_info = calendar.monthrange(at.year, at.month)
|
||||||
|
at = date(at.year, at.month, month_info[1])
|
||||||
|
if at.weekday() < schedule.date.weekday():
|
||||||
|
at -= timedelta(days=7)
|
||||||
|
at += timedelta(days=schedule.date.weekday()) - timedelta(
|
||||||
|
days=at.weekday()
|
||||||
|
)
|
||||||
|
assert dt.date() == at
|
||||||
|
|
||||||
|
# since the same method is used for first, second, etc. frequencies
|
||||||
|
# we assume testing every is sufficient
|
||||||
|
@pytest.mark.django_db
|
||||||
|
@pytest.mark.parametrize("months", range(0, 25, 2))
|
||||||
|
@pytest.mark.parametrize("hour", range(0, 24, 3))
|
||||||
|
def test_dates_of_month_every(self, months, hour):
|
||||||
|
schedule = baker.prepare(
|
||||||
|
Schedule, time=time(hour, 00), frequency=Schedule.Frequency.every
|
||||||
|
)
|
||||||
|
at = schedule.date + relativedelta(months=months)
|
||||||
|
datetimes = schedule.dates_of_month(at)
|
||||||
|
last = None
|
||||||
|
for dt in datetimes:
|
||||||
|
self._assert_date(schedule, at, dt)
|
||||||
|
if last:
|
||||||
|
assert (dt - last).days == 7
|
||||||
|
last = dt
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_dates_of_month_first_and_third(self):
|
@pytest.mark.parametrize("months", range(0, 25, 2))
|
||||||
pass
|
@pytest.mark.parametrize("hour", range(0, 24, 3))
|
||||||
|
def test_dates_of_month_one_on_two(self, months, hour):
|
||||||
|
schedule = baker.prepare(
|
||||||
|
Schedule,
|
||||||
|
time=time(hour, 00),
|
||||||
|
frequency=Schedule.Frequency.one_on_two,
|
||||||
|
)
|
||||||
|
at = schedule.date + relativedelta(months=months)
|
||||||
|
datetimes = schedule.dates_of_month(at)
|
||||||
|
for dt in datetimes:
|
||||||
|
self._assert_date(schedule, at, dt)
|
||||||
|
delta = dt.date() - schedule.date
|
||||||
|
assert delta.days % 14 == 0
|
||||||
|
|
||||||
|
def _assert_date(self, schedule, at, dt):
|
||||||
|
assert dt.year == at.year
|
||||||
|
assert dt.month == at.month
|
||||||
|
assert dt.weekday() == schedule.date.weekday()
|
||||||
|
assert dt.time() == schedule.time
|
||||||
|
assert dt.tzinfo.zone == schedule.timezone
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_dates_of_month_second_and_fourth(self):
|
def test_diffusions_of_month(self, sched_initials):
|
||||||
pass
|
# TODO: test values of initial, rerun
|
||||||
|
for schedule in sched_initials:
|
||||||
|
at = schedule.start + timedelta(days=30)
|
||||||
|
dates = set(schedule.dates_of_month(at))
|
||||||
|
episodes, diffusions = schedule.diffusions_of_month(at)
|
||||||
|
|
||||||
@pytest.mark.django_db
|
assert all(r.date in dates for r in episodes)
|
||||||
def test_dates_of_month_every(self):
|
assert all(
|
||||||
pass
|
(not r.initial or r.date in dates)
|
||||||
|
and r.type == Diffusion.TYPE_ON_AIR
|
||||||
@pytest.mark.django_db
|
for r in diffusions
|
||||||
def test_dates_of_month_one_on_two(self):
|
)
|
||||||
pass
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
|
||||||
def test__exclude_existing_date(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
|
||||||
def test_diffusions_of_month(self):
|
|
||||||
pass
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user