write schedule tests

This commit is contained in:
bkfox 2023-04-02 20:31:35 +02:00
parent 6974c617a5
commit 826bb149bc
6 changed files with 141 additions and 84 deletions

View File

@ -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")

View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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