From cd19c26e82c589cd35932073d32b18f57d8b8297 Mon Sep 17 00:00:00 2001 From: Thomas Kairos Date: Sun, 2 Apr 2023 20:37:47 +0200 Subject: [PATCH] #93: reorganise Rerun, Diffusion, Schedule module (#95) #93 Co-authored-by: bkfox Reviewed-on: https://git.radiocampus.be/rc/aircox/pulls/95 --- .pre-commit-config.yaml | 2 +- aircox/admin/__init__.py | 6 +- aircox/admin/diffusion.py | 48 + aircox/admin/episode.py | 45 +- aircox/admin/program.py | 50 +- aircox/admin/schedule.py | 55 + aircox/migrations/0001_initial.py | 1722 +++++++++++++++++ aircox/migrations/0002_auto_20200526_1516.py | 17 + aircox/migrations/0003_auto_20200530_1116.py | 146 ++ aircox/migrations/0004_auto_20200921_2356.py | 59 + aircox/migrations/0005_auto_20220318_1205.py | 839 ++++++++ aircox/migrations/0006_alter_sound_file.py | 23 + ...wnloadable_alter_page_pub_date_and_more.py | 710 +++++++ ..._diffusion_options_track_album_and_more.py | 48 + aircox/migrations/0009_track_year.py | 19 + aircox/migrations/0010_alter_track_album.py | 19 + aircox/migrations/0011_usersettings.py | 48 + ..._alter_sound_file_alter_station_default.py | 33 + aircox/migrations/__init__.py | 0 aircox/models/__init__.py | 16 +- aircox/models/diffusion.py | 282 +++ aircox/models/episode.py | 281 +-- aircox/models/log.py | 2 +- aircox/models/program.py | 359 +--- aircox/models/rerun.py | 106 + aircox/models/schedule.py | 217 +++ aircox/models/signals.py | 6 +- aircox/templates/aircox/program_detail.html | 2 +- aircox/tests/conftest.py | 72 + aircox/tests/models/test_diffusion.py | 19 + aircox/tests/models/test_rerun.py | 117 ++ aircox/tests/models/test_schedule.py | 135 ++ aircox/tests/old.py | 66 - aircox/views/__init__.py | 3 +- aircox/views/diffusion.py | 30 + aircox/views/episode.py | 30 +- requirements_tests.txt | 1 + 37 files changed, 4791 insertions(+), 842 deletions(-) create mode 100644 aircox/admin/diffusion.py create mode 100644 aircox/admin/schedule.py create mode 100644 aircox/migrations/0001_initial.py create mode 100644 aircox/migrations/0002_auto_20200526_1516.py create mode 100644 aircox/migrations/0003_auto_20200530_1116.py create mode 100644 aircox/migrations/0004_auto_20200921_2356.py create mode 100644 aircox/migrations/0005_auto_20220318_1205.py create mode 100644 aircox/migrations/0006_alter_sound_file.py create mode 100644 aircox/migrations/0007_sound_is_downloadable_alter_page_pub_date_and_more.py create mode 100644 aircox/migrations/0008_alter_diffusion_options_track_album_and_more.py create mode 100644 aircox/migrations/0009_track_year.py create mode 100644 aircox/migrations/0010_alter_track_album.py create mode 100644 aircox/migrations/0011_usersettings.py create mode 100644 aircox/migrations/0012_alter_sound_file_alter_station_default.py create mode 100644 aircox/migrations/__init__.py create mode 100644 aircox/models/diffusion.py create mode 100644 aircox/models/rerun.py create mode 100644 aircox/models/schedule.py create mode 100644 aircox/tests/conftest.py create mode 100644 aircox/tests/models/test_diffusion.py create mode 100644 aircox/tests/models/test_rerun.py create mode 100644 aircox/tests/models/test_schedule.py delete mode 100755 aircox/tests/old.py create mode 100644 aircox/views/diffusion.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 68db793..fe80aeb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: rev: 6.0.0 hooks: - id: flake8 - exclude: instance/settings/ + exclude: ^instance/settings/|migrations/ - repo: https://github.com/PyCQA/docformatter.git rev: v1.5.1 hooks: diff --git a/aircox/admin/__init__.py b/aircox/admin/__init__.py index 9f8cf76..2d86ee8 100644 --- a/aircox/admin/__init__.py +++ b/aircox/admin/__init__.py @@ -1,9 +1,11 @@ from . import filters from .article import ArticleAdmin -from .episode import DiffusionAdmin, EpisodeAdmin +from .diffusion import DiffusionAdmin +from .episode import EpisodeAdmin from .log import LogAdmin from .page import PageAdmin, StaticPageAdmin -from .program import ProgramAdmin, ScheduleAdmin, StreamAdmin +from .program import ProgramAdmin, StreamAdmin +from .schedule import ScheduleAdmin from .sound import SoundAdmin, TrackAdmin from .station import StationAdmin diff --git a/aircox/admin/diffusion.py b/aircox/admin/diffusion.py new file mode 100644 index 0000000..2e2fee5 --- /dev/null +++ b/aircox/admin/diffusion.py @@ -0,0 +1,48 @@ +from django.contrib import admin +from django.utils.translation import gettext as _ + +from aircox.models import Diffusion + + +__all__ = ("DiffusionBaseAdmin", "DiffusionAdmin", "DiffusionInline") + + +class DiffusionBaseAdmin: + fields = ("type", "start", "end", "schedule") + readonly_fields = ("schedule",) + + def get_readonly_fields(self, request, obj=None): + fields = super().get_readonly_fields(request, obj) + if not request.user.has_perm("aircox_program.scheduling"): + fields = fields + ("program", "start", "end") + return [field for field in fields if field in self.fields] + + +@admin.register(Diffusion) +class DiffusionAdmin(DiffusionBaseAdmin, admin.ModelAdmin): + def start_date(self, obj): + return obj.local_start.strftime("%Y/%m/%d %H:%M") + + start_date.short_description = _("start") + + def end_date(self, obj): + return obj.local_end.strftime("%H:%M") + + end_date.short_description = _("end") + + list_display = ("episode", "start_date", "end_date", "type", "initial") + list_filter = ("type", "start", "program") + list_editable = ("type",) + ordering = ("-start", "id") + + fields = ("type", "start", "end", "initial", "program", "schedule") + readonly_fields = ("schedule",) + + +class DiffusionInline(DiffusionBaseAdmin, admin.TabularInline): + model = Diffusion + fk_name = "episode" + extra = 0 + + def has_add_permission(self, request, obj): + return request.user.has_perm("aircox_program.scheduling") diff --git a/aircox/admin/episode.py b/aircox/admin/episode.py index 2472b68..9061b14 100644 --- a/aircox/admin/episode.py +++ b/aircox/admin/episode.py @@ -1,52 +1,11 @@ from adminsortable2.admin import SortableAdminBase from django.contrib import admin from django.forms import ModelForm -from django.utils.translation import gettext as _ -from ..models import Diffusion, Episode +from aircox.models import Episode from .page import PageAdmin from .sound import SoundInline, TrackInline - - -class DiffusionBaseAdmin: - fields = ("type", "start", "end", "schedule") - readonly_fields = ("schedule",) - - def get_readonly_fields(self, request, obj=None): - fields = super().get_readonly_fields(request, obj) - if not request.user.has_perm("aircox_program.scheduling"): - fields = fields + ("program", "start", "end") - return [field for field in fields if field in self.fields] - - -@admin.register(Diffusion) -class DiffusionAdmin(DiffusionBaseAdmin, admin.ModelAdmin): - def start_date(self, obj): - return obj.local_start.strftime("%Y/%m/%d %H:%M") - - start_date.short_description = _("start") - - def end_date(self, obj): - return obj.local_end.strftime("%H:%M") - - end_date.short_description = _("end") - - list_display = ("episode", "start_date", "end_date", "type", "initial") - list_filter = ("type", "start", "program") - list_editable = ("type",) - ordering = ("-start", "id") - - fields = ("type", "start", "end", "initial", "program", "schedule") - readonly_fields = ("schedule",) - - -class DiffusionInline(DiffusionBaseAdmin, admin.TabularInline): - model = Diffusion - fk_name = "episode" - extra = 0 - - def has_add_permission(self, request, obj): - return request.user.has_perm("aircox_program.scheduling") +from .diffusion import DiffusionInline class EpisodeAdminForm(ModelForm): diff --git a/aircox/admin/program.py b/aircox/admin/program.py index db29e32..72873bf 100644 --- a/aircox/admin/program.py +++ b/aircox/admin/program.py @@ -1,26 +1,12 @@ 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 aircox.models import Program, Schedule, Stream from .page import PageAdmin +from .schedule import ScheduleInline -# 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 +__all__ = ("ProgramAdmin", "StreamInline", "StreamAdmin") class StreamInline(admin.TabularInline): @@ -58,36 +44,6 @@ class ProgramAdmin(PageAdmin): return fields -@admin.register(Schedule) -class ScheduleAdmin(admin.ModelAdmin): - def program_title(self, obj): - return obj.program.title - - program_title.short_description = _("Program") - - def freq(self, obj): - return obj.get_frequency_verbose() - - freq.short_description = _("Day") - - list_filter = ["frequency", "program"] - list_display = [ - "program_title", - "freq", - "time", - "timezone", - "duration", - "initial", - ] - list_editable = ["time", "duration", "initial"] - - def get_readonly_fields(self, request, obj=None): - if obj: - return ["program", "date", "frequency"] - else: - return [] - - @admin.register(Stream) class StreamAdmin(admin.ModelAdmin): list_display = ("id", "program", "delay", "begin", "end") diff --git a/aircox/admin/schedule.py b/aircox/admin/schedule.py new file mode 100644 index 0000000..214e9d0 --- /dev/null +++ b/aircox/admin/schedule.py @@ -0,0 +1,55 @@ +from django.contrib import admin +from django.forms import ModelForm +from django.utils.translation import gettext_lazy as _ + +from aircox.models import Schedule + + +__all__ = ("ScheduleInlineForm", "ScheduleInline", "ScheduleAdmin") + + +# 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 + + +@admin.register(Schedule) +class ScheduleAdmin(admin.ModelAdmin): + def program_title(self, obj): + return obj.program.title + + program_title.short_description = _("Program") + + def freq(self, obj): + return obj.get_frequency_display() + + freq.short_description = _("Day") + + list_filter = ["frequency", "program"] + list_display = [ + "program_title", + "freq", + "time", + "timezone", + "duration", + "initial", + ] + list_editable = ["time", "duration", "initial"] + + def get_readonly_fields(self, request, obj=None): + if obj: + return ["program", "date", "frequency"] + else: + return [] diff --git a/aircox/migrations/0001_initial.py b/aircox/migrations/0001_initial.py new file mode 100644 index 0000000..35d509d --- /dev/null +++ b/aircox/migrations/0001_initial.py @@ -0,0 +1,1722 @@ +# Generated by Django 3.0.6 on 2020-05-26 12:57 + +import ckeditor.fields +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone +import filer.fields.image +import taggit.managers + + +class Migration(migrations.Migration): + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.FILER_IMAGE_MODEL), + ("taggit", "0003_taggeditem_add_unique_index"), + ] + + operations = [ + migrations.CreateModel( + name="Category", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "title", + models.CharField(max_length=64, verbose_name="title"), + ), + ("slug", models.SlugField(max_length=64, verbose_name="slug")), + ], + options={ + "verbose_name": "Category", + "verbose_name_plural": "Categories", + }, + ), + migrations.CreateModel( + name="Diffusion", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "type", + models.SmallIntegerField( + choices=[ + (0, "on air"), + (1, "not confirmed"), + (2, "cancelled"), + ], + default=0, + verbose_name="type", + ), + ), + ("start", models.DateTimeField(verbose_name="start")), + ("end", models.DateTimeField(verbose_name="end")), + ( + "initial", + models.ForeignKey( + blank=True, + help_text="mark as rerun of this %(model_name)", + limit_choices_to={"initial__isnull": True}, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="rerun_set", + to="aircox.Diffusion", + verbose_name="initial schedule", + ), + ), + ], + options={ + "verbose_name": "Diffusion", + "verbose_name_plural": "Diffusions", + "permissions": ( + ("programming", "edit the diffusion's planification"), + ), + }, + ), + migrations.CreateModel( + name="Page", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("title", models.CharField(max_length=100)), + ( + "slug", + models.SlugField( + blank=True, + max_length=120, + unique=True, + verbose_name="slug", + ), + ), + ( + "status", + models.PositiveSmallIntegerField( + choices=[ + (0, "draft"), + (16, "published"), + (32, "trash"), + ], + default=0, + verbose_name="status", + ), + ), + ( + "content", + ckeditor.fields.RichTextField( + blank=True, null=True, verbose_name="content" + ), + ), + ("pub_date", models.DateTimeField(blank=True, null=True)), + ( + "featured", + models.BooleanField( + default=False, verbose_name="featured" + ), + ), + ( + "allow_comments", + models.BooleanField( + default=True, verbose_name="allow comments" + ), + ), + ( + "category", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="aircox.Category", + verbose_name="category", + ), + ), + ( + "cover", + filer.fields.image.FilerImageField( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.FILER_IMAGE_MODEL, + verbose_name="cover", + ), + ), + ( + "parent", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="child_set", + to="aircox.Page", + ), + ), + ], + options={ + "abstract": False, + }, + ), + migrations.CreateModel( + name="Sound", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=64, verbose_name="name")), + ( + "type", + models.SmallIntegerField( + choices=[ + (0, "other"), + (1, "archive"), + (2, "excerpt"), + (3, "removed"), + ], + verbose_name="type", + ), + ), + ( + "position", + models.PositiveSmallIntegerField( + default=0, + help_text="position in the playlist", + verbose_name="order", + ), + ), + ( + "path", + models.FilePathField( + blank=True, + match="(\\.ogg|\\.flac|\\.wav|\\.mp3|\\.opus)$", + max_length=255, + null=True, + path="/media/data/courants/projets/aircox/static/media/programs", + recursive=True, + unique=True, + verbose_name="file", + ), + ), + ( + "embed", + models.TextField( + blank=True, + help_text="HTML code to embed a sound from an external plateform", + null=True, + verbose_name="embed", + ), + ), + ( + "duration", + models.TimeField( + blank=True, + help_text="duration of the sound", + null=True, + verbose_name="duration", + ), + ), + ( + "mtime", + models.DateTimeField( + blank=True, + help_text="last modification date and time", + null=True, + verbose_name="modification time", + ), + ), + ( + "is_good_quality", + models.BooleanField( + blank=True, + help_text="sound meets quality requirements", + null=True, + verbose_name="good quality", + ), + ), + ( + "is_public", + models.BooleanField( + default=False, + help_text="if it can be podcasted from the server", + verbose_name="public", + ), + ), + ], + options={ + "verbose_name": "Sound", + "verbose_name_plural": "Sounds", + }, + ), + migrations.CreateModel( + name="Article", + fields=[ + ( + "page_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="aircox.Page", + ), + ), + ( + "is_static", + models.BooleanField( + default=False, + help_text="Should this article be considered as a page instead of a blog article", + verbose_name="is static", + ), + ), + ], + options={ + "verbose_name": "Article", + "verbose_name_plural": "Articles", + }, + bases=("aircox.page",), + ), + migrations.CreateModel( + name="Episode", + fields=[ + ( + "page_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="aircox.Page", + ), + ), + ], + options={ + "verbose_name": "Episode", + "verbose_name_plural": "Episodes", + }, + bases=("aircox.page",), + ), + migrations.CreateModel( + name="Program", + fields=[ + ( + "page_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="aircox.Page", + ), + ), + ( + "active", + models.BooleanField( + default=True, + help_text="if not checked this program is no longer active", + verbose_name="active", + ), + ), + ( + "sync", + models.BooleanField( + default=True, + help_text="update later diffusions according to schedule changes", + verbose_name="syncronise", + ), + ), + ], + options={ + "abstract": False, + }, + bases=("aircox.page",), + ), + migrations.CreateModel( + name="Track", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "position", + models.PositiveSmallIntegerField( + default=0, + help_text="position in the playlist", + verbose_name="order", + ), + ), + ( + "timestamp", + models.PositiveSmallIntegerField( + blank=True, + help_text="position in seconds", + null=True, + verbose_name="timestamp", + ), + ), + ( + "title", + models.CharField(max_length=128, verbose_name="title"), + ), + ( + "artist", + models.CharField(max_length=128, verbose_name="artist"), + ), + ( + "info", + models.CharField( + blank=True, + help_text="additional informations about this track, such as the version, if is it a remix, features, etc.", + max_length=128, + null=True, + verbose_name="information", + ), + ), + ( + "sound", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="aircox.Sound", + verbose_name="sound", + ), + ), + ( + "tags", + taggit.managers.TaggableManager( + blank=True, + help_text="A comma-separated list of tags.", + through="taggit.TaggedItem", + to="taggit.Tag", + verbose_name="tags", + ), + ), + ( + "episode", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="aircox.Episode", + verbose_name="episode", + ), + ), + ], + options={ + "verbose_name": "Track", + "verbose_name_plural": "Tracks", + "ordering": ("position",), + }, + ), + migrations.CreateModel( + name="Station", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=64, verbose_name="name")), + ( + "slug", + models.SlugField( + max_length=64, unique=True, verbose_name="slug" + ), + ), + ( + "path", + models.CharField( + blank=True, + help_text="path to the working directory", + max_length=256, + verbose_name="path", + ), + ), + ( + "default", + models.BooleanField( + default=True, + help_text="use this station as the main one.", + verbose_name="default station", + ), + ), + ( + "active", + models.BooleanField( + default=True, + help_text="whether this station is still active or not.", + verbose_name="active", + ), + ), + ( + "hosts", + models.TextField( + blank=True, + help_text="specify one url per line", + max_length=512, + null=True, + verbose_name="website's urls", + ), + ), + ( + "audio_streams", + models.TextField( + blank=True, + help_text="Audio streams urls used by station's player. One url a line.", + max_length=2048, + null=True, + verbose_name="audio streams", + ), + ), + ( + "default_cover", + filer.fields.image.FilerImageField( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="+", + to=settings.FILER_IMAGE_MODEL, + verbose_name="Default pages cover", + ), + ), + ( + "logo", + filer.fields.image.FilerImageField( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.FILER_IMAGE_MODEL, + verbose_name="Logo", + ), + ), + ], + ), + migrations.CreateModel( + name="StaticPage", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("title", models.CharField(max_length=100)), + ( + "slug", + models.SlugField( + blank=True, + max_length=120, + unique=True, + verbose_name="slug", + ), + ), + ( + "status", + models.PositiveSmallIntegerField( + choices=[ + (0, "draft"), + (16, "published"), + (32, "trash"), + ], + default=0, + verbose_name="status", + ), + ), + ( + "content", + ckeditor.fields.RichTextField( + blank=True, null=True, verbose_name="content" + ), + ), + ( + "view", + models.SmallIntegerField( + blank=True, + choices=[ + (0, "Home Page"), + (1, "Schedule Page"), + (2, "Log Page"), + (3, "Programs list"), + (4, "Episodes list"), + (5, "Articles list"), + ], + help_text="display this page content to related element", + null=True, + verbose_name="attach to", + ), + ), + ( + "cover", + filer.fields.image.FilerImageField( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.FILER_IMAGE_MODEL, + verbose_name="cover", + ), + ), + ( + "parent", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="child_set", + to="aircox.StaticPage", + ), + ), + ], + options={ + "abstract": False, + }, + ), + migrations.CreateModel( + name="Port", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "direction", + models.SmallIntegerField( + choices=[(0, "input"), (1, "output")], + verbose_name="direction", + ), + ), + ( + "type", + models.SmallIntegerField( + choices=[ + (0, "jack"), + (1, "alsa"), + (2, "pulseaudio"), + (3, "icecast"), + (4, "http"), + (5, "https"), + (6, "file"), + ], + verbose_name="type", + ), + ), + ( + "active", + models.BooleanField( + default=True, + help_text="this port is active", + verbose_name="active", + ), + ), + ( + "settings", + models.TextField( + blank=True, + help_text="list of comma separated params available; this is put in the output config file as raw code; plugin related", + null=True, + verbose_name="port settings", + ), + ), + ( + "station", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="aircox.Station", + verbose_name="station", + ), + ), + ], + ), + migrations.CreateModel( + name="NavItem", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("menu", models.SlugField(max_length=24, verbose_name="menu")), + ( + "order", + models.PositiveSmallIntegerField(verbose_name="order"), + ), + ( + "text", + models.CharField(max_length=64, verbose_name="title"), + ), + ( + "url", + models.CharField( + blank=True, + max_length=256, + null=True, + verbose_name="url", + ), + ), + ( + "page", + models.ForeignKey( + blank=True, + limit_choices_to={"view__isnull": True}, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="aircox.StaticPage", + verbose_name="page", + ), + ), + ( + "station", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="aircox.Station", + verbose_name="station", + ), + ), + ], + options={ + "verbose_name": "Menu item", + "ordering": ("order", "pk"), + }, + ), + migrations.CreateModel( + name="Log", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "type", + models.SmallIntegerField( + choices=[ + (0, "stop"), + (1, "start"), + (2, "cancelled"), + (3, "on air"), + (4, "other"), + ], + verbose_name="type", + ), + ), + ( + "date", + models.DateTimeField( + db_index=True, + default=django.utils.timezone.now, + verbose_name="date", + ), + ), + ( + "source", + models.CharField( + blank=True, + help_text="identifier of the source related to this log", + max_length=64, + null=True, + verbose_name="source", + ), + ), + ( + "comment", + models.CharField( + blank=True, + max_length=512, + null=True, + verbose_name="comment", + ), + ), + ( + "diffusion", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="aircox.Diffusion", + verbose_name="Diffusion", + ), + ), + ( + "sound", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="aircox.Sound", + verbose_name="Sound", + ), + ), + ( + "station", + models.ForeignKey( + help_text="related station", + on_delete=django.db.models.deletion.CASCADE, + to="aircox.Station", + verbose_name="station", + ), + ), + ( + "track", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="aircox.Track", + verbose_name="Track", + ), + ), + ], + ), + migrations.CreateModel( + name="Comment", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "nickname", + models.CharField(max_length=32, verbose_name="nickname"), + ), + ( + "email", + models.EmailField(max_length=32, verbose_name="email"), + ), + ("date", models.DateTimeField(auto_now_add=True)), + ( + "content", + models.TextField(max_length=1024, verbose_name="content"), + ), + ( + "page", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="aircox.Page", + verbose_name="related page", + ), + ), + ], + ), + migrations.CreateModel( + name="Stream", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "delay", + models.TimeField( + blank=True, + help_text="minimal delay between two sound plays", + null=True, + verbose_name="delay", + ), + ), + ( + "begin", + models.TimeField( + blank=True, + help_text="used to define a time range this stream isplayed", + null=True, + verbose_name="begin", + ), + ), + ( + "end", + models.TimeField( + blank=True, + help_text="used to define a time range this stream isplayed", + null=True, + verbose_name="end", + ), + ), + ( + "program", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="aircox.Program", + verbose_name="related program", + ), + ), + ], + ), + migrations.AddField( + model_name="sound", + name="episode", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="aircox.Episode", + verbose_name="episode", + ), + ), + migrations.AddField( + model_name="sound", + name="program", + field=models.ForeignKey( + blank=True, + help_text="program related to it", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="aircox.Program", + verbose_name="program", + ), + ), + migrations.CreateModel( + name="Schedule", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "date", + models.DateField( + help_text="date of the first diffusion", + verbose_name="date", + ), + ), + ( + "time", + models.TimeField( + help_text="start time", verbose_name="time" + ), + ), + ( + "timezone", + models.CharField( + choices=[ + ("Africa/Abidjan", "Africa/Abidjan"), + ("Africa/Accra", "Africa/Accra"), + ("Africa/Addis_Ababa", "Africa/Addis_Ababa"), + ("Africa/Algiers", "Africa/Algiers"), + ("Africa/Asmara", "Africa/Asmara"), + ("Africa/Asmera", "Africa/Asmera"), + ("Africa/Bamako", "Africa/Bamako"), + ("Africa/Bangui", "Africa/Bangui"), + ("Africa/Banjul", "Africa/Banjul"), + ("Africa/Bissau", "Africa/Bissau"), + ("Africa/Blantyre", "Africa/Blantyre"), + ("Africa/Brazzaville", "Africa/Brazzaville"), + ("Africa/Bujumbura", "Africa/Bujumbura"), + ("Africa/Cairo", "Africa/Cairo"), + ("Africa/Casablanca", "Africa/Casablanca"), + ("Africa/Ceuta", "Africa/Ceuta"), + ("Africa/Conakry", "Africa/Conakry"), + ("Africa/Dakar", "Africa/Dakar"), + ("Africa/Dar_es_Salaam", "Africa/Dar_es_Salaam"), + ("Africa/Djibouti", "Africa/Djibouti"), + ("Africa/Douala", "Africa/Douala"), + ("Africa/El_Aaiun", "Africa/El_Aaiun"), + ("Africa/Freetown", "Africa/Freetown"), + ("Africa/Gaborone", "Africa/Gaborone"), + ("Africa/Harare", "Africa/Harare"), + ("Africa/Johannesburg", "Africa/Johannesburg"), + ("Africa/Juba", "Africa/Juba"), + ("Africa/Kampala", "Africa/Kampala"), + ("Africa/Khartoum", "Africa/Khartoum"), + ("Africa/Kigali", "Africa/Kigali"), + ("Africa/Kinshasa", "Africa/Kinshasa"), + ("Africa/Lagos", "Africa/Lagos"), + ("Africa/Libreville", "Africa/Libreville"), + ("Africa/Lome", "Africa/Lome"), + ("Africa/Luanda", "Africa/Luanda"), + ("Africa/Lubumbashi", "Africa/Lubumbashi"), + ("Africa/Lusaka", "Africa/Lusaka"), + ("Africa/Malabo", "Africa/Malabo"), + ("Africa/Maputo", "Africa/Maputo"), + ("Africa/Maseru", "Africa/Maseru"), + ("Africa/Mbabane", "Africa/Mbabane"), + ("Africa/Mogadishu", "Africa/Mogadishu"), + ("Africa/Monrovia", "Africa/Monrovia"), + ("Africa/Nairobi", "Africa/Nairobi"), + ("Africa/Ndjamena", "Africa/Ndjamena"), + ("Africa/Niamey", "Africa/Niamey"), + ("Africa/Nouakchott", "Africa/Nouakchott"), + ("Africa/Ouagadougou", "Africa/Ouagadougou"), + ("Africa/Porto-Novo", "Africa/Porto-Novo"), + ("Africa/Sao_Tome", "Africa/Sao_Tome"), + ("Africa/Timbuktu", "Africa/Timbuktu"), + ("Africa/Tripoli", "Africa/Tripoli"), + ("Africa/Tunis", "Africa/Tunis"), + ("Africa/Windhoek", "Africa/Windhoek"), + ("America/Adak", "America/Adak"), + ("America/Anchorage", "America/Anchorage"), + ("America/Anguilla", "America/Anguilla"), + ("America/Antigua", "America/Antigua"), + ("America/Araguaina", "America/Araguaina"), + ( + "America/Argentina/Buenos_Aires", + "America/Argentina/Buenos_Aires", + ), + ( + "America/Argentina/Catamarca", + "America/Argentina/Catamarca", + ), + ( + "America/Argentina/ComodRivadavia", + "America/Argentina/ComodRivadavia", + ), + ( + "America/Argentina/Cordoba", + "America/Argentina/Cordoba", + ), + ( + "America/Argentina/Jujuy", + "America/Argentina/Jujuy", + ), + ( + "America/Argentina/La_Rioja", + "America/Argentina/La_Rioja", + ), + ( + "America/Argentina/Mendoza", + "America/Argentina/Mendoza", + ), + ( + "America/Argentina/Rio_Gallegos", + "America/Argentina/Rio_Gallegos", + ), + ( + "America/Argentina/Salta", + "America/Argentina/Salta", + ), + ( + "America/Argentina/San_Juan", + "America/Argentina/San_Juan", + ), + ( + "America/Argentina/San_Luis", + "America/Argentina/San_Luis", + ), + ( + "America/Argentina/Tucuman", + "America/Argentina/Tucuman", + ), + ( + "America/Argentina/Ushuaia", + "America/Argentina/Ushuaia", + ), + ("America/Aruba", "America/Aruba"), + ("America/Asuncion", "America/Asuncion"), + ("America/Atikokan", "America/Atikokan"), + ("America/Atka", "America/Atka"), + ("America/Bahia", "America/Bahia"), + ( + "America/Bahia_Banderas", + "America/Bahia_Banderas", + ), + ("America/Barbados", "America/Barbados"), + ("America/Belem", "America/Belem"), + ("America/Belize", "America/Belize"), + ("America/Blanc-Sablon", "America/Blanc-Sablon"), + ("America/Boa_Vista", "America/Boa_Vista"), + ("America/Bogota", "America/Bogota"), + ("America/Boise", "America/Boise"), + ("America/Buenos_Aires", "America/Buenos_Aires"), + ("America/Cambridge_Bay", "America/Cambridge_Bay"), + ("America/Campo_Grande", "America/Campo_Grande"), + ("America/Cancun", "America/Cancun"), + ("America/Caracas", "America/Caracas"), + ("America/Catamarca", "America/Catamarca"), + ("America/Cayenne", "America/Cayenne"), + ("America/Cayman", "America/Cayman"), + ("America/Chicago", "America/Chicago"), + ("America/Chihuahua", "America/Chihuahua"), + ("America/Coral_Harbour", "America/Coral_Harbour"), + ("America/Cordoba", "America/Cordoba"), + ("America/Costa_Rica", "America/Costa_Rica"), + ("America/Creston", "America/Creston"), + ("America/Cuiaba", "America/Cuiaba"), + ("America/Curacao", "America/Curacao"), + ("America/Danmarkshavn", "America/Danmarkshavn"), + ("America/Dawson", "America/Dawson"), + ("America/Dawson_Creek", "America/Dawson_Creek"), + ("America/Denver", "America/Denver"), + ("America/Detroit", "America/Detroit"), + ("America/Dominica", "America/Dominica"), + ("America/Edmonton", "America/Edmonton"), + ("America/Eirunepe", "America/Eirunepe"), + ("America/El_Salvador", "America/El_Salvador"), + ("America/Ensenada", "America/Ensenada"), + ("America/Fort_Nelson", "America/Fort_Nelson"), + ("America/Fort_Wayne", "America/Fort_Wayne"), + ("America/Fortaleza", "America/Fortaleza"), + ("America/Glace_Bay", "America/Glace_Bay"), + ("America/Godthab", "America/Godthab"), + ("America/Goose_Bay", "America/Goose_Bay"), + ("America/Grand_Turk", "America/Grand_Turk"), + ("America/Grenada", "America/Grenada"), + ("America/Guadeloupe", "America/Guadeloupe"), + ("America/Guatemala", "America/Guatemala"), + ("America/Guayaquil", "America/Guayaquil"), + ("America/Guyana", "America/Guyana"), + ("America/Halifax", "America/Halifax"), + ("America/Havana", "America/Havana"), + ("America/Hermosillo", "America/Hermosillo"), + ( + "America/Indiana/Indianapolis", + "America/Indiana/Indianapolis", + ), + ("America/Indiana/Knox", "America/Indiana/Knox"), + ( + "America/Indiana/Marengo", + "America/Indiana/Marengo", + ), + ( + "America/Indiana/Petersburg", + "America/Indiana/Petersburg", + ), + ( + "America/Indiana/Tell_City", + "America/Indiana/Tell_City", + ), + ("America/Indiana/Vevay", "America/Indiana/Vevay"), + ( + "America/Indiana/Vincennes", + "America/Indiana/Vincennes", + ), + ( + "America/Indiana/Winamac", + "America/Indiana/Winamac", + ), + ("America/Indianapolis", "America/Indianapolis"), + ("America/Inuvik", "America/Inuvik"), + ("America/Iqaluit", "America/Iqaluit"), + ("America/Jamaica", "America/Jamaica"), + ("America/Jujuy", "America/Jujuy"), + ("America/Juneau", "America/Juneau"), + ( + "America/Kentucky/Louisville", + "America/Kentucky/Louisville", + ), + ( + "America/Kentucky/Monticello", + "America/Kentucky/Monticello", + ), + ("America/Knox_IN", "America/Knox_IN"), + ("America/Kralendijk", "America/Kralendijk"), + ("America/La_Paz", "America/La_Paz"), + ("America/Lima", "America/Lima"), + ("America/Los_Angeles", "America/Los_Angeles"), + ("America/Louisville", "America/Louisville"), + ("America/Lower_Princes", "America/Lower_Princes"), + ("America/Maceio", "America/Maceio"), + ("America/Managua", "America/Managua"), + ("America/Manaus", "America/Manaus"), + ("America/Marigot", "America/Marigot"), + ("America/Martinique", "America/Martinique"), + ("America/Matamoros", "America/Matamoros"), + ("America/Mazatlan", "America/Mazatlan"), + ("America/Mendoza", "America/Mendoza"), + ("America/Menominee", "America/Menominee"), + ("America/Merida", "America/Merida"), + ("America/Metlakatla", "America/Metlakatla"), + ("America/Mexico_City", "America/Mexico_City"), + ("America/Miquelon", "America/Miquelon"), + ("America/Moncton", "America/Moncton"), + ("America/Monterrey", "America/Monterrey"), + ("America/Montevideo", "America/Montevideo"), + ("America/Montreal", "America/Montreal"), + ("America/Montserrat", "America/Montserrat"), + ("America/Nassau", "America/Nassau"), + ("America/New_York", "America/New_York"), + ("America/Nipigon", "America/Nipigon"), + ("America/Nome", "America/Nome"), + ("America/Noronha", "America/Noronha"), + ( + "America/North_Dakota/Beulah", + "America/North_Dakota/Beulah", + ), + ( + "America/North_Dakota/Center", + "America/North_Dakota/Center", + ), + ( + "America/North_Dakota/New_Salem", + "America/North_Dakota/New_Salem", + ), + ("America/Nuuk", "America/Nuuk"), + ("America/Ojinaga", "America/Ojinaga"), + ("America/Panama", "America/Panama"), + ("America/Pangnirtung", "America/Pangnirtung"), + ("America/Paramaribo", "America/Paramaribo"), + ("America/Phoenix", "America/Phoenix"), + ( + "America/Port-au-Prince", + "America/Port-au-Prince", + ), + ("America/Port_of_Spain", "America/Port_of_Spain"), + ("America/Porto_Acre", "America/Porto_Acre"), + ("America/Porto_Velho", "America/Porto_Velho"), + ("America/Puerto_Rico", "America/Puerto_Rico"), + ("America/Punta_Arenas", "America/Punta_Arenas"), + ("America/Rainy_River", "America/Rainy_River"), + ("America/Rankin_Inlet", "America/Rankin_Inlet"), + ("America/Recife", "America/Recife"), + ("America/Regina", "America/Regina"), + ("America/Resolute", "America/Resolute"), + ("America/Rio_Branco", "America/Rio_Branco"), + ("America/Rosario", "America/Rosario"), + ("America/Santa_Isabel", "America/Santa_Isabel"), + ("America/Santarem", "America/Santarem"), + ("America/Santiago", "America/Santiago"), + ("America/Santo_Domingo", "America/Santo_Domingo"), + ("America/Sao_Paulo", "America/Sao_Paulo"), + ("America/Scoresbysund", "America/Scoresbysund"), + ("America/Shiprock", "America/Shiprock"), + ("America/Sitka", "America/Sitka"), + ("America/St_Barthelemy", "America/St_Barthelemy"), + ("America/St_Johns", "America/St_Johns"), + ("America/St_Kitts", "America/St_Kitts"), + ("America/St_Lucia", "America/St_Lucia"), + ("America/St_Thomas", "America/St_Thomas"), + ("America/St_Vincent", "America/St_Vincent"), + ("America/Swift_Current", "America/Swift_Current"), + ("America/Tegucigalpa", "America/Tegucigalpa"), + ("America/Thule", "America/Thule"), + ("America/Thunder_Bay", "America/Thunder_Bay"), + ("America/Tijuana", "America/Tijuana"), + ("America/Toronto", "America/Toronto"), + ("America/Tortola", "America/Tortola"), + ("America/Vancouver", "America/Vancouver"), + ("America/Virgin", "America/Virgin"), + ("America/Whitehorse", "America/Whitehorse"), + ("America/Winnipeg", "America/Winnipeg"), + ("America/Yakutat", "America/Yakutat"), + ("America/Yellowknife", "America/Yellowknife"), + ("Antarctica/Casey", "Antarctica/Casey"), + ("Antarctica/Davis", "Antarctica/Davis"), + ( + "Antarctica/DumontDUrville", + "Antarctica/DumontDUrville", + ), + ("Antarctica/Macquarie", "Antarctica/Macquarie"), + ("Antarctica/Mawson", "Antarctica/Mawson"), + ("Antarctica/McMurdo", "Antarctica/McMurdo"), + ("Antarctica/Palmer", "Antarctica/Palmer"), + ("Antarctica/Rothera", "Antarctica/Rothera"), + ("Antarctica/South_Pole", "Antarctica/South_Pole"), + ("Antarctica/Syowa", "Antarctica/Syowa"), + ("Antarctica/Troll", "Antarctica/Troll"), + ("Antarctica/Vostok", "Antarctica/Vostok"), + ("Arctic/Longyearbyen", "Arctic/Longyearbyen"), + ("Asia/Aden", "Asia/Aden"), + ("Asia/Almaty", "Asia/Almaty"), + ("Asia/Amman", "Asia/Amman"), + ("Asia/Anadyr", "Asia/Anadyr"), + ("Asia/Aqtau", "Asia/Aqtau"), + ("Asia/Aqtobe", "Asia/Aqtobe"), + ("Asia/Ashgabat", "Asia/Ashgabat"), + ("Asia/Ashkhabad", "Asia/Ashkhabad"), + ("Asia/Atyrau", "Asia/Atyrau"), + ("Asia/Baghdad", "Asia/Baghdad"), + ("Asia/Bahrain", "Asia/Bahrain"), + ("Asia/Baku", "Asia/Baku"), + ("Asia/Bangkok", "Asia/Bangkok"), + ("Asia/Barnaul", "Asia/Barnaul"), + ("Asia/Beirut", "Asia/Beirut"), + ("Asia/Bishkek", "Asia/Bishkek"), + ("Asia/Brunei", "Asia/Brunei"), + ("Asia/Calcutta", "Asia/Calcutta"), + ("Asia/Chita", "Asia/Chita"), + ("Asia/Choibalsan", "Asia/Choibalsan"), + ("Asia/Chongqing", "Asia/Chongqing"), + ("Asia/Chungking", "Asia/Chungking"), + ("Asia/Colombo", "Asia/Colombo"), + ("Asia/Dacca", "Asia/Dacca"), + ("Asia/Damascus", "Asia/Damascus"), + ("Asia/Dhaka", "Asia/Dhaka"), + ("Asia/Dili", "Asia/Dili"), + ("Asia/Dubai", "Asia/Dubai"), + ("Asia/Dushanbe", "Asia/Dushanbe"), + ("Asia/Famagusta", "Asia/Famagusta"), + ("Asia/Gaza", "Asia/Gaza"), + ("Asia/Harbin", "Asia/Harbin"), + ("Asia/Hebron", "Asia/Hebron"), + ("Asia/Ho_Chi_Minh", "Asia/Ho_Chi_Minh"), + ("Asia/Hong_Kong", "Asia/Hong_Kong"), + ("Asia/Hovd", "Asia/Hovd"), + ("Asia/Irkutsk", "Asia/Irkutsk"), + ("Asia/Istanbul", "Asia/Istanbul"), + ("Asia/Jakarta", "Asia/Jakarta"), + ("Asia/Jayapura", "Asia/Jayapura"), + ("Asia/Jerusalem", "Asia/Jerusalem"), + ("Asia/Kabul", "Asia/Kabul"), + ("Asia/Kamchatka", "Asia/Kamchatka"), + ("Asia/Karachi", "Asia/Karachi"), + ("Asia/Kashgar", "Asia/Kashgar"), + ("Asia/Kathmandu", "Asia/Kathmandu"), + ("Asia/Katmandu", "Asia/Katmandu"), + ("Asia/Khandyga", "Asia/Khandyga"), + ("Asia/Kolkata", "Asia/Kolkata"), + ("Asia/Krasnoyarsk", "Asia/Krasnoyarsk"), + ("Asia/Kuala_Lumpur", "Asia/Kuala_Lumpur"), + ("Asia/Kuching", "Asia/Kuching"), + ("Asia/Kuwait", "Asia/Kuwait"), + ("Asia/Macao", "Asia/Macao"), + ("Asia/Macau", "Asia/Macau"), + ("Asia/Magadan", "Asia/Magadan"), + ("Asia/Makassar", "Asia/Makassar"), + ("Asia/Manila", "Asia/Manila"), + ("Asia/Muscat", "Asia/Muscat"), + ("Asia/Nicosia", "Asia/Nicosia"), + ("Asia/Novokuznetsk", "Asia/Novokuznetsk"), + ("Asia/Novosibirsk", "Asia/Novosibirsk"), + ("Asia/Omsk", "Asia/Omsk"), + ("Asia/Oral", "Asia/Oral"), + ("Asia/Phnom_Penh", "Asia/Phnom_Penh"), + ("Asia/Pontianak", "Asia/Pontianak"), + ("Asia/Pyongyang", "Asia/Pyongyang"), + ("Asia/Qatar", "Asia/Qatar"), + ("Asia/Qostanay", "Asia/Qostanay"), + ("Asia/Qyzylorda", "Asia/Qyzylorda"), + ("Asia/Rangoon", "Asia/Rangoon"), + ("Asia/Riyadh", "Asia/Riyadh"), + ("Asia/Saigon", "Asia/Saigon"), + ("Asia/Sakhalin", "Asia/Sakhalin"), + ("Asia/Samarkand", "Asia/Samarkand"), + ("Asia/Seoul", "Asia/Seoul"), + ("Asia/Shanghai", "Asia/Shanghai"), + ("Asia/Singapore", "Asia/Singapore"), + ("Asia/Srednekolymsk", "Asia/Srednekolymsk"), + ("Asia/Taipei", "Asia/Taipei"), + ("Asia/Tashkent", "Asia/Tashkent"), + ("Asia/Tbilisi", "Asia/Tbilisi"), + ("Asia/Tehran", "Asia/Tehran"), + ("Asia/Tel_Aviv", "Asia/Tel_Aviv"), + ("Asia/Thimbu", "Asia/Thimbu"), + ("Asia/Thimphu", "Asia/Thimphu"), + ("Asia/Tokyo", "Asia/Tokyo"), + ("Asia/Tomsk", "Asia/Tomsk"), + ("Asia/Ujung_Pandang", "Asia/Ujung_Pandang"), + ("Asia/Ulaanbaatar", "Asia/Ulaanbaatar"), + ("Asia/Ulan_Bator", "Asia/Ulan_Bator"), + ("Asia/Urumqi", "Asia/Urumqi"), + ("Asia/Ust-Nera", "Asia/Ust-Nera"), + ("Asia/Vientiane", "Asia/Vientiane"), + ("Asia/Vladivostok", "Asia/Vladivostok"), + ("Asia/Yakutsk", "Asia/Yakutsk"), + ("Asia/Yangon", "Asia/Yangon"), + ("Asia/Yekaterinburg", "Asia/Yekaterinburg"), + ("Asia/Yerevan", "Asia/Yerevan"), + ("Atlantic/Azores", "Atlantic/Azores"), + ("Atlantic/Bermuda", "Atlantic/Bermuda"), + ("Atlantic/Canary", "Atlantic/Canary"), + ("Atlantic/Cape_Verde", "Atlantic/Cape_Verde"), + ("Atlantic/Faeroe", "Atlantic/Faeroe"), + ("Atlantic/Faroe", "Atlantic/Faroe"), + ("Atlantic/Jan_Mayen", "Atlantic/Jan_Mayen"), + ("Atlantic/Madeira", "Atlantic/Madeira"), + ("Atlantic/Reykjavik", "Atlantic/Reykjavik"), + ( + "Atlantic/South_Georgia", + "Atlantic/South_Georgia", + ), + ("Atlantic/St_Helena", "Atlantic/St_Helena"), + ("Atlantic/Stanley", "Atlantic/Stanley"), + ("Australia/ACT", "Australia/ACT"), + ("Australia/Adelaide", "Australia/Adelaide"), + ("Australia/Brisbane", "Australia/Brisbane"), + ("Australia/Broken_Hill", "Australia/Broken_Hill"), + ("Australia/Canberra", "Australia/Canberra"), + ("Australia/Currie", "Australia/Currie"), + ("Australia/Darwin", "Australia/Darwin"), + ("Australia/Eucla", "Australia/Eucla"), + ("Australia/Hobart", "Australia/Hobart"), + ("Australia/LHI", "Australia/LHI"), + ("Australia/Lindeman", "Australia/Lindeman"), + ("Australia/Lord_Howe", "Australia/Lord_Howe"), + ("Australia/Melbourne", "Australia/Melbourne"), + ("Australia/NSW", "Australia/NSW"), + ("Australia/North", "Australia/North"), + ("Australia/Perth", "Australia/Perth"), + ("Australia/Queensland", "Australia/Queensland"), + ("Australia/South", "Australia/South"), + ("Australia/Sydney", "Australia/Sydney"), + ("Australia/Tasmania", "Australia/Tasmania"), + ("Australia/Victoria", "Australia/Victoria"), + ("Australia/West", "Australia/West"), + ("Australia/Yancowinna", "Australia/Yancowinna"), + ("Brazil/Acre", "Brazil/Acre"), + ("Brazil/DeNoronha", "Brazil/DeNoronha"), + ("Brazil/East", "Brazil/East"), + ("Brazil/West", "Brazil/West"), + ("CET", "CET"), + ("CST6CDT", "CST6CDT"), + ("Canada/Atlantic", "Canada/Atlantic"), + ("Canada/Central", "Canada/Central"), + ("Canada/Eastern", "Canada/Eastern"), + ("Canada/Mountain", "Canada/Mountain"), + ("Canada/Newfoundland", "Canada/Newfoundland"), + ("Canada/Pacific", "Canada/Pacific"), + ("Canada/Saskatchewan", "Canada/Saskatchewan"), + ("Canada/Yukon", "Canada/Yukon"), + ("Chile/Continental", "Chile/Continental"), + ("Chile/EasterIsland", "Chile/EasterIsland"), + ("Cuba", "Cuba"), + ("EET", "EET"), + ("EST", "EST"), + ("EST5EDT", "EST5EDT"), + ("Egypt", "Egypt"), + ("Eire", "Eire"), + ("Etc/GMT", "Etc/GMT"), + ("Etc/GMT+0", "Etc/GMT+0"), + ("Etc/GMT+1", "Etc/GMT+1"), + ("Etc/GMT+10", "Etc/GMT+10"), + ("Etc/GMT+11", "Etc/GMT+11"), + ("Etc/GMT+12", "Etc/GMT+12"), + ("Etc/GMT+2", "Etc/GMT+2"), + ("Etc/GMT+3", "Etc/GMT+3"), + ("Etc/GMT+4", "Etc/GMT+4"), + ("Etc/GMT+5", "Etc/GMT+5"), + ("Etc/GMT+6", "Etc/GMT+6"), + ("Etc/GMT+7", "Etc/GMT+7"), + ("Etc/GMT+8", "Etc/GMT+8"), + ("Etc/GMT+9", "Etc/GMT+9"), + ("Etc/GMT-0", "Etc/GMT-0"), + ("Etc/GMT-1", "Etc/GMT-1"), + ("Etc/GMT-10", "Etc/GMT-10"), + ("Etc/GMT-11", "Etc/GMT-11"), + ("Etc/GMT-12", "Etc/GMT-12"), + ("Etc/GMT-13", "Etc/GMT-13"), + ("Etc/GMT-14", "Etc/GMT-14"), + ("Etc/GMT-2", "Etc/GMT-2"), + ("Etc/GMT-3", "Etc/GMT-3"), + ("Etc/GMT-4", "Etc/GMT-4"), + ("Etc/GMT-5", "Etc/GMT-5"), + ("Etc/GMT-6", "Etc/GMT-6"), + ("Etc/GMT-7", "Etc/GMT-7"), + ("Etc/GMT-8", "Etc/GMT-8"), + ("Etc/GMT-9", "Etc/GMT-9"), + ("Etc/GMT0", "Etc/GMT0"), + ("Etc/Greenwich", "Etc/Greenwich"), + ("Etc/UCT", "Etc/UCT"), + ("Etc/UTC", "Etc/UTC"), + ("Etc/Universal", "Etc/Universal"), + ("Etc/Zulu", "Etc/Zulu"), + ("Europe/Amsterdam", "Europe/Amsterdam"), + ("Europe/Andorra", "Europe/Andorra"), + ("Europe/Astrakhan", "Europe/Astrakhan"), + ("Europe/Athens", "Europe/Athens"), + ("Europe/Belfast", "Europe/Belfast"), + ("Europe/Belgrade", "Europe/Belgrade"), + ("Europe/Berlin", "Europe/Berlin"), + ("Europe/Bratislava", "Europe/Bratislava"), + ("Europe/Brussels", "Europe/Brussels"), + ("Europe/Bucharest", "Europe/Bucharest"), + ("Europe/Budapest", "Europe/Budapest"), + ("Europe/Busingen", "Europe/Busingen"), + ("Europe/Chisinau", "Europe/Chisinau"), + ("Europe/Copenhagen", "Europe/Copenhagen"), + ("Europe/Dublin", "Europe/Dublin"), + ("Europe/Gibraltar", "Europe/Gibraltar"), + ("Europe/Guernsey", "Europe/Guernsey"), + ("Europe/Helsinki", "Europe/Helsinki"), + ("Europe/Isle_of_Man", "Europe/Isle_of_Man"), + ("Europe/Istanbul", "Europe/Istanbul"), + ("Europe/Jersey", "Europe/Jersey"), + ("Europe/Kaliningrad", "Europe/Kaliningrad"), + ("Europe/Kiev", "Europe/Kiev"), + ("Europe/Kirov", "Europe/Kirov"), + ("Europe/Lisbon", "Europe/Lisbon"), + ("Europe/Ljubljana", "Europe/Ljubljana"), + ("Europe/London", "Europe/London"), + ("Europe/Luxembourg", "Europe/Luxembourg"), + ("Europe/Madrid", "Europe/Madrid"), + ("Europe/Malta", "Europe/Malta"), + ("Europe/Mariehamn", "Europe/Mariehamn"), + ("Europe/Minsk", "Europe/Minsk"), + ("Europe/Monaco", "Europe/Monaco"), + ("Europe/Moscow", "Europe/Moscow"), + ("Europe/Nicosia", "Europe/Nicosia"), + ("Europe/Oslo", "Europe/Oslo"), + ("Europe/Paris", "Europe/Paris"), + ("Europe/Podgorica", "Europe/Podgorica"), + ("Europe/Prague", "Europe/Prague"), + ("Europe/Riga", "Europe/Riga"), + ("Europe/Rome", "Europe/Rome"), + ("Europe/Samara", "Europe/Samara"), + ("Europe/San_Marino", "Europe/San_Marino"), + ("Europe/Sarajevo", "Europe/Sarajevo"), + ("Europe/Saratov", "Europe/Saratov"), + ("Europe/Simferopol", "Europe/Simferopol"), + ("Europe/Skopje", "Europe/Skopje"), + ("Europe/Sofia", "Europe/Sofia"), + ("Europe/Stockholm", "Europe/Stockholm"), + ("Europe/Tallinn", "Europe/Tallinn"), + ("Europe/Tirane", "Europe/Tirane"), + ("Europe/Tiraspol", "Europe/Tiraspol"), + ("Europe/Ulyanovsk", "Europe/Ulyanovsk"), + ("Europe/Uzhgorod", "Europe/Uzhgorod"), + ("Europe/Vaduz", "Europe/Vaduz"), + ("Europe/Vatican", "Europe/Vatican"), + ("Europe/Vienna", "Europe/Vienna"), + ("Europe/Vilnius", "Europe/Vilnius"), + ("Europe/Volgograd", "Europe/Volgograd"), + ("Europe/Warsaw", "Europe/Warsaw"), + ("Europe/Zagreb", "Europe/Zagreb"), + ("Europe/Zaporozhye", "Europe/Zaporozhye"), + ("Europe/Zurich", "Europe/Zurich"), + ("GB", "GB"), + ("GB-Eire", "GB-Eire"), + ("GMT", "GMT"), + ("GMT+0", "GMT+0"), + ("GMT-0", "GMT-0"), + ("GMT0", "GMT0"), + ("Greenwich", "Greenwich"), + ("HST", "HST"), + ("Hongkong", "Hongkong"), + ("Iceland", "Iceland"), + ("Indian/Antananarivo", "Indian/Antananarivo"), + ("Indian/Chagos", "Indian/Chagos"), + ("Indian/Christmas", "Indian/Christmas"), + ("Indian/Cocos", "Indian/Cocos"), + ("Indian/Comoro", "Indian/Comoro"), + ("Indian/Kerguelen", "Indian/Kerguelen"), + ("Indian/Mahe", "Indian/Mahe"), + ("Indian/Maldives", "Indian/Maldives"), + ("Indian/Mauritius", "Indian/Mauritius"), + ("Indian/Mayotte", "Indian/Mayotte"), + ("Indian/Reunion", "Indian/Reunion"), + ("Iran", "Iran"), + ("Israel", "Israel"), + ("Jamaica", "Jamaica"), + ("Japan", "Japan"), + ("Kwajalein", "Kwajalein"), + ("Libya", "Libya"), + ("MET", "MET"), + ("MST", "MST"), + ("MST7MDT", "MST7MDT"), + ("Mexico/BajaNorte", "Mexico/BajaNorte"), + ("Mexico/BajaSur", "Mexico/BajaSur"), + ("Mexico/General", "Mexico/General"), + ("NZ", "NZ"), + ("NZ-CHAT", "NZ-CHAT"), + ("Navajo", "Navajo"), + ("PRC", "PRC"), + ("PST8PDT", "PST8PDT"), + ("Pacific/Apia", "Pacific/Apia"), + ("Pacific/Auckland", "Pacific/Auckland"), + ("Pacific/Bougainville", "Pacific/Bougainville"), + ("Pacific/Chatham", "Pacific/Chatham"), + ("Pacific/Chuuk", "Pacific/Chuuk"), + ("Pacific/Easter", "Pacific/Easter"), + ("Pacific/Efate", "Pacific/Efate"), + ("Pacific/Enderbury", "Pacific/Enderbury"), + ("Pacific/Fakaofo", "Pacific/Fakaofo"), + ("Pacific/Fiji", "Pacific/Fiji"), + ("Pacific/Funafuti", "Pacific/Funafuti"), + ("Pacific/Galapagos", "Pacific/Galapagos"), + ("Pacific/Gambier", "Pacific/Gambier"), + ("Pacific/Guadalcanal", "Pacific/Guadalcanal"), + ("Pacific/Guam", "Pacific/Guam"), + ("Pacific/Honolulu", "Pacific/Honolulu"), + ("Pacific/Johnston", "Pacific/Johnston"), + ("Pacific/Kiritimati", "Pacific/Kiritimati"), + ("Pacific/Kosrae", "Pacific/Kosrae"), + ("Pacific/Kwajalein", "Pacific/Kwajalein"), + ("Pacific/Majuro", "Pacific/Majuro"), + ("Pacific/Marquesas", "Pacific/Marquesas"), + ("Pacific/Midway", "Pacific/Midway"), + ("Pacific/Nauru", "Pacific/Nauru"), + ("Pacific/Niue", "Pacific/Niue"), + ("Pacific/Norfolk", "Pacific/Norfolk"), + ("Pacific/Noumea", "Pacific/Noumea"), + ("Pacific/Pago_Pago", "Pacific/Pago_Pago"), + ("Pacific/Palau", "Pacific/Palau"), + ("Pacific/Pitcairn", "Pacific/Pitcairn"), + ("Pacific/Pohnpei", "Pacific/Pohnpei"), + ("Pacific/Ponape", "Pacific/Ponape"), + ("Pacific/Port_Moresby", "Pacific/Port_Moresby"), + ("Pacific/Rarotonga", "Pacific/Rarotonga"), + ("Pacific/Saipan", "Pacific/Saipan"), + ("Pacific/Samoa", "Pacific/Samoa"), + ("Pacific/Tahiti", "Pacific/Tahiti"), + ("Pacific/Tarawa", "Pacific/Tarawa"), + ("Pacific/Tongatapu", "Pacific/Tongatapu"), + ("Pacific/Truk", "Pacific/Truk"), + ("Pacific/Wake", "Pacific/Wake"), + ("Pacific/Wallis", "Pacific/Wallis"), + ("Pacific/Yap", "Pacific/Yap"), + ("Poland", "Poland"), + ("Portugal", "Portugal"), + ("ROC", "ROC"), + ("ROK", "ROK"), + ("Singapore", "Singapore"), + ("Turkey", "Turkey"), + ("UCT", "UCT"), + ("US/Alaska", "US/Alaska"), + ("US/Aleutian", "US/Aleutian"), + ("US/Arizona", "US/Arizona"), + ("US/Central", "US/Central"), + ("US/East-Indiana", "US/East-Indiana"), + ("US/Eastern", "US/Eastern"), + ("US/Hawaii", "US/Hawaii"), + ("US/Indiana-Starke", "US/Indiana-Starke"), + ("US/Michigan", "US/Michigan"), + ("US/Mountain", "US/Mountain"), + ("US/Pacific", "US/Pacific"), + ("US/Samoa", "US/Samoa"), + ("UTC", "UTC"), + ("Universal", "Universal"), + ("W-SU", "W-SU"), + ("WET", "WET"), + ("Zulu", "Zulu"), + ], + default=django.utils.timezone.get_current_timezone, + help_text="timezone used for the date", + max_length=100, + verbose_name="timezone", + ), + ), + ( + "duration", + models.TimeField( + help_text="regular duration", verbose_name="duration" + ), + ), + ( + "frequency", + models.SmallIntegerField( + choices=[ + (0, "ponctual"), + (1, "1st {day} of the month"), + (2, "2nd {day} of the month"), + (4, "3rd {day} of the month"), + (8, "4th {day} of the month"), + (16, "last {day} of the month"), + (5, "1st and 3rd {day}s of the month"), + (10, "2nd and 4th {day}s of the month"), + (31, "every {day}"), + (32, "one {day} on two"), + ], + verbose_name="frequency", + ), + ), + ( + "initial", + models.ForeignKey( + blank=True, + help_text="mark as rerun of this %(model_name)", + limit_choices_to={"initial__isnull": True}, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="rerun_set", + to="aircox.Schedule", + verbose_name="initial schedule", + ), + ), + ( + "program", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="aircox.Program", + verbose_name="related program", + ), + ), + ], + options={ + "verbose_name": "Schedule", + "verbose_name_plural": "Schedules", + }, + ), + migrations.AddField( + model_name="program", + name="station", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="aircox.Station", + verbose_name="station", + ), + ), + migrations.AddField( + model_name="diffusion", + name="episode", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="aircox.Episode", + verbose_name="episode", + ), + ), + migrations.AddField( + model_name="diffusion", + name="program", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="aircox.Program", + verbose_name="related program", + ), + ), + ] diff --git a/aircox/migrations/0002_auto_20200526_1516.py b/aircox/migrations/0002_auto_20200526_1516.py new file mode 100644 index 0000000..d197a70 --- /dev/null +++ b/aircox/migrations/0002_auto_20200526_1516.py @@ -0,0 +1,17 @@ +# Generated by Django 3.0.6 on 2020-05-26 13:16 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("aircox", "0001_initial"), + ] + + operations = [ + migrations.RenameField( + model_name="staticpage", + old_name="view", + new_name="attach_to", + ), + ] diff --git a/aircox/migrations/0003_auto_20200530_1116.py b/aircox/migrations/0003_auto_20200530_1116.py new file mode 100644 index 0000000..b4dcd8e --- /dev/null +++ b/aircox/migrations/0003_auto_20200530_1116.py @@ -0,0 +1,146 @@ +# Generated by Django 3.0.6 on 2020-05-30 11:16 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import filer.fields.image + + +class Migration(migrations.Migration): + dependencies = [ + migrations.swappable_dependency(settings.FILER_IMAGE_MODEL), + ("aircox", "0002_auto_20200526_1516"), + ] + + operations = [ + migrations.AlterModelOptions( + name="log", + options={"verbose_name": "Log", "verbose_name_plural": "Logs"}, + ), + migrations.AlterModelOptions( + name="page", + options={ + "verbose_name": "Publication", + "verbose_name_plural": "Publications", + }, + ), + migrations.AlterModelOptions( + name="program", + options={ + "verbose_name": "Program", + "verbose_name_plural": "Programs", + }, + ), + migrations.RemoveField( + model_name="article", + name="is_static", + ), + migrations.AddField( + model_name="diffusion", + name="schedule", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="aircox.Schedule", + verbose_name="schedule", + ), + ), + migrations.AlterField( + model_name="diffusion", + name="initial", + field=models.ForeignKey( + blank=True, + limit_choices_to={"initial__isnull": True}, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="rerun_set", + to="aircox.Diffusion", + verbose_name="rerun of", + ), + ), + migrations.AlterField( + model_name="navitem", + name="page", + field=models.ForeignKey( + blank=True, + limit_choices_to={"attach_to__isnull": True}, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="aircox.StaticPage", + verbose_name="page", + ), + ), + migrations.AlterField( + model_name="schedule", + name="frequency", + field=models.SmallIntegerField( + choices=[ + (0, "ponctual"), + (1, "1st {day} of the month"), + (2, "2nd {day} of the month"), + (4, "3rd {day} of the month"), + (8, "4th {day} of the month"), + (16, "last {day} of the month"), + (5, "1st and 3rd {day} of the month"), + (10, "2nd and 4th {day} of the month"), + (31, "every {day}"), + (32, "one {day} on two"), + ], + verbose_name="frequency", + ), + ), + migrations.AlterField( + model_name="schedule", + name="initial", + field=models.ForeignKey( + blank=True, + limit_choices_to={"initial__isnull": True}, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="rerun_set", + to="aircox.Schedule", + verbose_name="rerun of", + ), + ), + migrations.AlterField( + model_name="staticpage", + name="attach_to", + field=models.SmallIntegerField( + blank=True, + choices=[ + (0, "Home page"), + (1, "Diffusions page"), + (2, "Logs page"), + (3, "Programs list"), + (4, "Episodes list"), + (5, "Articles list"), + ], + help_text="display this page content to related element", + null=True, + verbose_name="attach to", + ), + ), + migrations.AlterField( + model_name="station", + name="default_cover", + field=filer.fields.image.FilerImageField( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="+", + to=settings.FILER_IMAGE_MODEL, + verbose_name="Default pages' cover", + ), + ), + migrations.AlterField( + model_name="track", + name="timestamp", + field=models.PositiveSmallIntegerField( + blank=True, + help_text="position (in seconds)", + null=True, + verbose_name="timestamp", + ), + ), + ] diff --git a/aircox/migrations/0004_auto_20200921_2356.py b/aircox/migrations/0004_auto_20200921_2356.py new file mode 100644 index 0000000..7bdf8b3 --- /dev/null +++ b/aircox/migrations/0004_auto_20200921_2356.py @@ -0,0 +1,59 @@ +# Generated by Django 3.1.1 on 2020-09-21 23:56 + +import ckeditor_uploader.fields +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + dependencies = [ + ("aircox", "0003_auto_20200530_1116"), + ] + + operations = [ + migrations.AlterModelOptions( + name="comment", + options={ + "verbose_name": "Comment", + "verbose_name_plural": "Comments", + }, + ), + migrations.AlterModelOptions( + name="navitem", + options={ + "ordering": ("order", "pk"), + "verbose_name": "Menu item", + "verbose_name_plural": "Menu items", + }, + ), + migrations.RemoveField( + model_name="sound", + name="embed", + ), + migrations.AlterField( + model_name="page", + name="content", + field=ckeditor_uploader.fields.RichTextUploadingField( + blank=True, null=True, verbose_name="content" + ), + ), + migrations.AlterField( + model_name="sound", + name="program", + field=models.ForeignKey( + default=1, + help_text="program related to it", + on_delete=django.db.models.deletion.CASCADE, + to="aircox.program", + verbose_name="program", + ), + preserve_default=False, + ), + migrations.AlterField( + model_name="staticpage", + name="content", + field=ckeditor_uploader.fields.RichTextUploadingField( + blank=True, null=True, verbose_name="content" + ), + ), + ] diff --git a/aircox/migrations/0005_auto_20220318_1205.py b/aircox/migrations/0005_auto_20220318_1205.py new file mode 100644 index 0000000..d728453 --- /dev/null +++ b/aircox/migrations/0005_auto_20220318_1205.py @@ -0,0 +1,839 @@ +# Generated by Django 3.2.12 on 2022-03-18 12:05 + +import aircox.models.sound +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + dependencies = [ + ("aircox", "0004_auto_20200921_2356"), + ] + + operations = [ + migrations.RemoveField( + model_name="sound", + name="path", + ), + migrations.AddField( + model_name="sound", + name="file", + field=models.FileField( + default="", + upload_to=aircox.models.sound.Sound._upload_to, + verbose_name="file", + ), + preserve_default=False, + ), + migrations.AlterField( + model_name="category", + name="id", + field=models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + migrations.AlterField( + model_name="comment", + name="id", + field=models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + migrations.AlterField( + model_name="diffusion", + name="end", + field=models.DateTimeField(db_index=True, verbose_name="end"), + ), + migrations.AlterField( + model_name="diffusion", + name="id", + field=models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + migrations.AlterField( + model_name="diffusion", + name="start", + field=models.DateTimeField(db_index=True, verbose_name="start"), + ), + migrations.AlterField( + model_name="log", + name="id", + field=models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + migrations.AlterField( + model_name="navitem", + name="id", + field=models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + migrations.AlterField( + model_name="navitem", + name="page", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="aircox.staticpage", + verbose_name="page", + ), + ), + migrations.AlterField( + model_name="page", + name="id", + field=models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + migrations.AlterField( + model_name="port", + name="id", + field=models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + migrations.AlterField( + model_name="schedule", + name="id", + field=models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + migrations.AlterField( + model_name="schedule", + name="timezone", + field=models.CharField( + choices=[ + ("Africa/Abidjan", "Africa/Abidjan"), + ("Africa/Accra", "Africa/Accra"), + ("Africa/Addis_Ababa", "Africa/Addis_Ababa"), + ("Africa/Algiers", "Africa/Algiers"), + ("Africa/Asmara", "Africa/Asmara"), + ("Africa/Asmera", "Africa/Asmera"), + ("Africa/Bamako", "Africa/Bamako"), + ("Africa/Bangui", "Africa/Bangui"), + ("Africa/Banjul", "Africa/Banjul"), + ("Africa/Bissau", "Africa/Bissau"), + ("Africa/Blantyre", "Africa/Blantyre"), + ("Africa/Brazzaville", "Africa/Brazzaville"), + ("Africa/Bujumbura", "Africa/Bujumbura"), + ("Africa/Cairo", "Africa/Cairo"), + ("Africa/Casablanca", "Africa/Casablanca"), + ("Africa/Ceuta", "Africa/Ceuta"), + ("Africa/Conakry", "Africa/Conakry"), + ("Africa/Dakar", "Africa/Dakar"), + ("Africa/Dar_es_Salaam", "Africa/Dar_es_Salaam"), + ("Africa/Djibouti", "Africa/Djibouti"), + ("Africa/Douala", "Africa/Douala"), + ("Africa/El_Aaiun", "Africa/El_Aaiun"), + ("Africa/Freetown", "Africa/Freetown"), + ("Africa/Gaborone", "Africa/Gaborone"), + ("Africa/Harare", "Africa/Harare"), + ("Africa/Johannesburg", "Africa/Johannesburg"), + ("Africa/Juba", "Africa/Juba"), + ("Africa/Kampala", "Africa/Kampala"), + ("Africa/Khartoum", "Africa/Khartoum"), + ("Africa/Kigali", "Africa/Kigali"), + ("Africa/Kinshasa", "Africa/Kinshasa"), + ("Africa/Lagos", "Africa/Lagos"), + ("Africa/Libreville", "Africa/Libreville"), + ("Africa/Lome", "Africa/Lome"), + ("Africa/Luanda", "Africa/Luanda"), + ("Africa/Lubumbashi", "Africa/Lubumbashi"), + ("Africa/Lusaka", "Africa/Lusaka"), + ("Africa/Malabo", "Africa/Malabo"), + ("Africa/Maputo", "Africa/Maputo"), + ("Africa/Maseru", "Africa/Maseru"), + ("Africa/Mbabane", "Africa/Mbabane"), + ("Africa/Mogadishu", "Africa/Mogadishu"), + ("Africa/Monrovia", "Africa/Monrovia"), + ("Africa/Nairobi", "Africa/Nairobi"), + ("Africa/Ndjamena", "Africa/Ndjamena"), + ("Africa/Niamey", "Africa/Niamey"), + ("Africa/Nouakchott", "Africa/Nouakchott"), + ("Africa/Ouagadougou", "Africa/Ouagadougou"), + ("Africa/Porto-Novo", "Africa/Porto-Novo"), + ("Africa/Sao_Tome", "Africa/Sao_Tome"), + ("Africa/Timbuktu", "Africa/Timbuktu"), + ("Africa/Tripoli", "Africa/Tripoli"), + ("Africa/Tunis", "Africa/Tunis"), + ("Africa/Windhoek", "Africa/Windhoek"), + ("America/Adak", "America/Adak"), + ("America/Anchorage", "America/Anchorage"), + ("America/Anguilla", "America/Anguilla"), + ("America/Antigua", "America/Antigua"), + ("America/Araguaina", "America/Araguaina"), + ( + "America/Argentina/Buenos_Aires", + "America/Argentina/Buenos_Aires", + ), + ( + "America/Argentina/Catamarca", + "America/Argentina/Catamarca", + ), + ( + "America/Argentina/ComodRivadavia", + "America/Argentina/ComodRivadavia", + ), + ("America/Argentina/Cordoba", "America/Argentina/Cordoba"), + ("America/Argentina/Jujuy", "America/Argentina/Jujuy"), + ( + "America/Argentina/La_Rioja", + "America/Argentina/La_Rioja", + ), + ("America/Argentina/Mendoza", "America/Argentina/Mendoza"), + ( + "America/Argentina/Rio_Gallegos", + "America/Argentina/Rio_Gallegos", + ), + ("America/Argentina/Salta", "America/Argentina/Salta"), + ( + "America/Argentina/San_Juan", + "America/Argentina/San_Juan", + ), + ( + "America/Argentina/San_Luis", + "America/Argentina/San_Luis", + ), + ("America/Argentina/Tucuman", "America/Argentina/Tucuman"), + ("America/Argentina/Ushuaia", "America/Argentina/Ushuaia"), + ("America/Aruba", "America/Aruba"), + ("America/Asuncion", "America/Asuncion"), + ("America/Atikokan", "America/Atikokan"), + ("America/Atka", "America/Atka"), + ("America/Bahia", "America/Bahia"), + ("America/Bahia_Banderas", "America/Bahia_Banderas"), + ("America/Barbados", "America/Barbados"), + ("America/Belem", "America/Belem"), + ("America/Belize", "America/Belize"), + ("America/Blanc-Sablon", "America/Blanc-Sablon"), + ("America/Boa_Vista", "America/Boa_Vista"), + ("America/Bogota", "America/Bogota"), + ("America/Boise", "America/Boise"), + ("America/Buenos_Aires", "America/Buenos_Aires"), + ("America/Cambridge_Bay", "America/Cambridge_Bay"), + ("America/Campo_Grande", "America/Campo_Grande"), + ("America/Cancun", "America/Cancun"), + ("America/Caracas", "America/Caracas"), + ("America/Catamarca", "America/Catamarca"), + ("America/Cayenne", "America/Cayenne"), + ("America/Cayman", "America/Cayman"), + ("America/Chicago", "America/Chicago"), + ("America/Chihuahua", "America/Chihuahua"), + ("America/Coral_Harbour", "America/Coral_Harbour"), + ("America/Cordoba", "America/Cordoba"), + ("America/Costa_Rica", "America/Costa_Rica"), + ("America/Creston", "America/Creston"), + ("America/Cuiaba", "America/Cuiaba"), + ("America/Curacao", "America/Curacao"), + ("America/Danmarkshavn", "America/Danmarkshavn"), + ("America/Dawson", "America/Dawson"), + ("America/Dawson_Creek", "America/Dawson_Creek"), + ("America/Denver", "America/Denver"), + ("America/Detroit", "America/Detroit"), + ("America/Dominica", "America/Dominica"), + ("America/Edmonton", "America/Edmonton"), + ("America/Eirunepe", "America/Eirunepe"), + ("America/El_Salvador", "America/El_Salvador"), + ("America/Ensenada", "America/Ensenada"), + ("America/Fort_Nelson", "America/Fort_Nelson"), + ("America/Fort_Wayne", "America/Fort_Wayne"), + ("America/Fortaleza", "America/Fortaleza"), + ("America/Glace_Bay", "America/Glace_Bay"), + ("America/Godthab", "America/Godthab"), + ("America/Goose_Bay", "America/Goose_Bay"), + ("America/Grand_Turk", "America/Grand_Turk"), + ("America/Grenada", "America/Grenada"), + ("America/Guadeloupe", "America/Guadeloupe"), + ("America/Guatemala", "America/Guatemala"), + ("America/Guayaquil", "America/Guayaquil"), + ("America/Guyana", "America/Guyana"), + ("America/Halifax", "America/Halifax"), + ("America/Havana", "America/Havana"), + ("America/Hermosillo", "America/Hermosillo"), + ( + "America/Indiana/Indianapolis", + "America/Indiana/Indianapolis", + ), + ("America/Indiana/Knox", "America/Indiana/Knox"), + ("America/Indiana/Marengo", "America/Indiana/Marengo"), + ( + "America/Indiana/Petersburg", + "America/Indiana/Petersburg", + ), + ("America/Indiana/Tell_City", "America/Indiana/Tell_City"), + ("America/Indiana/Vevay", "America/Indiana/Vevay"), + ("America/Indiana/Vincennes", "America/Indiana/Vincennes"), + ("America/Indiana/Winamac", "America/Indiana/Winamac"), + ("America/Indianapolis", "America/Indianapolis"), + ("America/Inuvik", "America/Inuvik"), + ("America/Iqaluit", "America/Iqaluit"), + ("America/Jamaica", "America/Jamaica"), + ("America/Jujuy", "America/Jujuy"), + ("America/Juneau", "America/Juneau"), + ( + "America/Kentucky/Louisville", + "America/Kentucky/Louisville", + ), + ( + "America/Kentucky/Monticello", + "America/Kentucky/Monticello", + ), + ("America/Knox_IN", "America/Knox_IN"), + ("America/Kralendijk", "America/Kralendijk"), + ("America/La_Paz", "America/La_Paz"), + ("America/Lima", "America/Lima"), + ("America/Los_Angeles", "America/Los_Angeles"), + ("America/Louisville", "America/Louisville"), + ("America/Lower_Princes", "America/Lower_Princes"), + ("America/Maceio", "America/Maceio"), + ("America/Managua", "America/Managua"), + ("America/Manaus", "America/Manaus"), + ("America/Marigot", "America/Marigot"), + ("America/Martinique", "America/Martinique"), + ("America/Matamoros", "America/Matamoros"), + ("America/Mazatlan", "America/Mazatlan"), + ("America/Mendoza", "America/Mendoza"), + ("America/Menominee", "America/Menominee"), + ("America/Merida", "America/Merida"), + ("America/Metlakatla", "America/Metlakatla"), + ("America/Mexico_City", "America/Mexico_City"), + ("America/Miquelon", "America/Miquelon"), + ("America/Moncton", "America/Moncton"), + ("America/Monterrey", "America/Monterrey"), + ("America/Montevideo", "America/Montevideo"), + ("America/Montreal", "America/Montreal"), + ("America/Montserrat", "America/Montserrat"), + ("America/Nassau", "America/Nassau"), + ("America/New_York", "America/New_York"), + ("America/Nipigon", "America/Nipigon"), + ("America/Nome", "America/Nome"), + ("America/Noronha", "America/Noronha"), + ( + "America/North_Dakota/Beulah", + "America/North_Dakota/Beulah", + ), + ( + "America/North_Dakota/Center", + "America/North_Dakota/Center", + ), + ( + "America/North_Dakota/New_Salem", + "America/North_Dakota/New_Salem", + ), + ("America/Nuuk", "America/Nuuk"), + ("America/Ojinaga", "America/Ojinaga"), + ("America/Panama", "America/Panama"), + ("America/Pangnirtung", "America/Pangnirtung"), + ("America/Paramaribo", "America/Paramaribo"), + ("America/Phoenix", "America/Phoenix"), + ("America/Port-au-Prince", "America/Port-au-Prince"), + ("America/Port_of_Spain", "America/Port_of_Spain"), + ("America/Porto_Acre", "America/Porto_Acre"), + ("America/Porto_Velho", "America/Porto_Velho"), + ("America/Puerto_Rico", "America/Puerto_Rico"), + ("America/Punta_Arenas", "America/Punta_Arenas"), + ("America/Rainy_River", "America/Rainy_River"), + ("America/Rankin_Inlet", "America/Rankin_Inlet"), + ("America/Recife", "America/Recife"), + ("America/Regina", "America/Regina"), + ("America/Resolute", "America/Resolute"), + ("America/Rio_Branco", "America/Rio_Branco"), + ("America/Rosario", "America/Rosario"), + ("America/Santa_Isabel", "America/Santa_Isabel"), + ("America/Santarem", "America/Santarem"), + ("America/Santiago", "America/Santiago"), + ("America/Santo_Domingo", "America/Santo_Domingo"), + ("America/Sao_Paulo", "America/Sao_Paulo"), + ("America/Scoresbysund", "America/Scoresbysund"), + ("America/Shiprock", "America/Shiprock"), + ("America/Sitka", "America/Sitka"), + ("America/St_Barthelemy", "America/St_Barthelemy"), + ("America/St_Johns", "America/St_Johns"), + ("America/St_Kitts", "America/St_Kitts"), + ("America/St_Lucia", "America/St_Lucia"), + ("America/St_Thomas", "America/St_Thomas"), + ("America/St_Vincent", "America/St_Vincent"), + ("America/Swift_Current", "America/Swift_Current"), + ("America/Tegucigalpa", "America/Tegucigalpa"), + ("America/Thule", "America/Thule"), + ("America/Thunder_Bay", "America/Thunder_Bay"), + ("America/Tijuana", "America/Tijuana"), + ("America/Toronto", "America/Toronto"), + ("America/Tortola", "America/Tortola"), + ("America/Vancouver", "America/Vancouver"), + ("America/Virgin", "America/Virgin"), + ("America/Whitehorse", "America/Whitehorse"), + ("America/Winnipeg", "America/Winnipeg"), + ("America/Yakutat", "America/Yakutat"), + ("America/Yellowknife", "America/Yellowknife"), + ("Antarctica/Casey", "Antarctica/Casey"), + ("Antarctica/Davis", "Antarctica/Davis"), + ("Antarctica/DumontDUrville", "Antarctica/DumontDUrville"), + ("Antarctica/Macquarie", "Antarctica/Macquarie"), + ("Antarctica/Mawson", "Antarctica/Mawson"), + ("Antarctica/McMurdo", "Antarctica/McMurdo"), + ("Antarctica/Palmer", "Antarctica/Palmer"), + ("Antarctica/Rothera", "Antarctica/Rothera"), + ("Antarctica/South_Pole", "Antarctica/South_Pole"), + ("Antarctica/Syowa", "Antarctica/Syowa"), + ("Antarctica/Troll", "Antarctica/Troll"), + ("Antarctica/Vostok", "Antarctica/Vostok"), + ("Arctic/Longyearbyen", "Arctic/Longyearbyen"), + ("Asia/Aden", "Asia/Aden"), + ("Asia/Almaty", "Asia/Almaty"), + ("Asia/Amman", "Asia/Amman"), + ("Asia/Anadyr", "Asia/Anadyr"), + ("Asia/Aqtau", "Asia/Aqtau"), + ("Asia/Aqtobe", "Asia/Aqtobe"), + ("Asia/Ashgabat", "Asia/Ashgabat"), + ("Asia/Ashkhabad", "Asia/Ashkhabad"), + ("Asia/Atyrau", "Asia/Atyrau"), + ("Asia/Baghdad", "Asia/Baghdad"), + ("Asia/Bahrain", "Asia/Bahrain"), + ("Asia/Baku", "Asia/Baku"), + ("Asia/Bangkok", "Asia/Bangkok"), + ("Asia/Barnaul", "Asia/Barnaul"), + ("Asia/Beirut", "Asia/Beirut"), + ("Asia/Bishkek", "Asia/Bishkek"), + ("Asia/Brunei", "Asia/Brunei"), + ("Asia/Calcutta", "Asia/Calcutta"), + ("Asia/Chita", "Asia/Chita"), + ("Asia/Choibalsan", "Asia/Choibalsan"), + ("Asia/Chongqing", "Asia/Chongqing"), + ("Asia/Chungking", "Asia/Chungking"), + ("Asia/Colombo", "Asia/Colombo"), + ("Asia/Dacca", "Asia/Dacca"), + ("Asia/Damascus", "Asia/Damascus"), + ("Asia/Dhaka", "Asia/Dhaka"), + ("Asia/Dili", "Asia/Dili"), + ("Asia/Dubai", "Asia/Dubai"), + ("Asia/Dushanbe", "Asia/Dushanbe"), + ("Asia/Famagusta", "Asia/Famagusta"), + ("Asia/Gaza", "Asia/Gaza"), + ("Asia/Harbin", "Asia/Harbin"), + ("Asia/Hebron", "Asia/Hebron"), + ("Asia/Ho_Chi_Minh", "Asia/Ho_Chi_Minh"), + ("Asia/Hong_Kong", "Asia/Hong_Kong"), + ("Asia/Hovd", "Asia/Hovd"), + ("Asia/Irkutsk", "Asia/Irkutsk"), + ("Asia/Istanbul", "Asia/Istanbul"), + ("Asia/Jakarta", "Asia/Jakarta"), + ("Asia/Jayapura", "Asia/Jayapura"), + ("Asia/Jerusalem", "Asia/Jerusalem"), + ("Asia/Kabul", "Asia/Kabul"), + ("Asia/Kamchatka", "Asia/Kamchatka"), + ("Asia/Karachi", "Asia/Karachi"), + ("Asia/Kashgar", "Asia/Kashgar"), + ("Asia/Kathmandu", "Asia/Kathmandu"), + ("Asia/Katmandu", "Asia/Katmandu"), + ("Asia/Khandyga", "Asia/Khandyga"), + ("Asia/Kolkata", "Asia/Kolkata"), + ("Asia/Krasnoyarsk", "Asia/Krasnoyarsk"), + ("Asia/Kuala_Lumpur", "Asia/Kuala_Lumpur"), + ("Asia/Kuching", "Asia/Kuching"), + ("Asia/Kuwait", "Asia/Kuwait"), + ("Asia/Macao", "Asia/Macao"), + ("Asia/Macau", "Asia/Macau"), + ("Asia/Magadan", "Asia/Magadan"), + ("Asia/Makassar", "Asia/Makassar"), + ("Asia/Manila", "Asia/Manila"), + ("Asia/Muscat", "Asia/Muscat"), + ("Asia/Nicosia", "Asia/Nicosia"), + ("Asia/Novokuznetsk", "Asia/Novokuznetsk"), + ("Asia/Novosibirsk", "Asia/Novosibirsk"), + ("Asia/Omsk", "Asia/Omsk"), + ("Asia/Oral", "Asia/Oral"), + ("Asia/Phnom_Penh", "Asia/Phnom_Penh"), + ("Asia/Pontianak", "Asia/Pontianak"), + ("Asia/Pyongyang", "Asia/Pyongyang"), + ("Asia/Qatar", "Asia/Qatar"), + ("Asia/Qostanay", "Asia/Qostanay"), + ("Asia/Qyzylorda", "Asia/Qyzylorda"), + ("Asia/Rangoon", "Asia/Rangoon"), + ("Asia/Riyadh", "Asia/Riyadh"), + ("Asia/Saigon", "Asia/Saigon"), + ("Asia/Sakhalin", "Asia/Sakhalin"), + ("Asia/Samarkand", "Asia/Samarkand"), + ("Asia/Seoul", "Asia/Seoul"), + ("Asia/Shanghai", "Asia/Shanghai"), + ("Asia/Singapore", "Asia/Singapore"), + ("Asia/Srednekolymsk", "Asia/Srednekolymsk"), + ("Asia/Taipei", "Asia/Taipei"), + ("Asia/Tashkent", "Asia/Tashkent"), + ("Asia/Tbilisi", "Asia/Tbilisi"), + ("Asia/Tehran", "Asia/Tehran"), + ("Asia/Tel_Aviv", "Asia/Tel_Aviv"), + ("Asia/Thimbu", "Asia/Thimbu"), + ("Asia/Thimphu", "Asia/Thimphu"), + ("Asia/Tokyo", "Asia/Tokyo"), + ("Asia/Tomsk", "Asia/Tomsk"), + ("Asia/Ujung_Pandang", "Asia/Ujung_Pandang"), + ("Asia/Ulaanbaatar", "Asia/Ulaanbaatar"), + ("Asia/Ulan_Bator", "Asia/Ulan_Bator"), + ("Asia/Urumqi", "Asia/Urumqi"), + ("Asia/Ust-Nera", "Asia/Ust-Nera"), + ("Asia/Vientiane", "Asia/Vientiane"), + ("Asia/Vladivostok", "Asia/Vladivostok"), + ("Asia/Yakutsk", "Asia/Yakutsk"), + ("Asia/Yangon", "Asia/Yangon"), + ("Asia/Yekaterinburg", "Asia/Yekaterinburg"), + ("Asia/Yerevan", "Asia/Yerevan"), + ("Atlantic/Azores", "Atlantic/Azores"), + ("Atlantic/Bermuda", "Atlantic/Bermuda"), + ("Atlantic/Canary", "Atlantic/Canary"), + ("Atlantic/Cape_Verde", "Atlantic/Cape_Verde"), + ("Atlantic/Faeroe", "Atlantic/Faeroe"), + ("Atlantic/Faroe", "Atlantic/Faroe"), + ("Atlantic/Jan_Mayen", "Atlantic/Jan_Mayen"), + ("Atlantic/Madeira", "Atlantic/Madeira"), + ("Atlantic/Reykjavik", "Atlantic/Reykjavik"), + ("Atlantic/South_Georgia", "Atlantic/South_Georgia"), + ("Atlantic/St_Helena", "Atlantic/St_Helena"), + ("Atlantic/Stanley", "Atlantic/Stanley"), + ("Australia/ACT", "Australia/ACT"), + ("Australia/Adelaide", "Australia/Adelaide"), + ("Australia/Brisbane", "Australia/Brisbane"), + ("Australia/Broken_Hill", "Australia/Broken_Hill"), + ("Australia/Canberra", "Australia/Canberra"), + ("Australia/Currie", "Australia/Currie"), + ("Australia/Darwin", "Australia/Darwin"), + ("Australia/Eucla", "Australia/Eucla"), + ("Australia/Hobart", "Australia/Hobart"), + ("Australia/LHI", "Australia/LHI"), + ("Australia/Lindeman", "Australia/Lindeman"), + ("Australia/Lord_Howe", "Australia/Lord_Howe"), + ("Australia/Melbourne", "Australia/Melbourne"), + ("Australia/NSW", "Australia/NSW"), + ("Australia/North", "Australia/North"), + ("Australia/Perth", "Australia/Perth"), + ("Australia/Queensland", "Australia/Queensland"), + ("Australia/South", "Australia/South"), + ("Australia/Sydney", "Australia/Sydney"), + ("Australia/Tasmania", "Australia/Tasmania"), + ("Australia/Victoria", "Australia/Victoria"), + ("Australia/West", "Australia/West"), + ("Australia/Yancowinna", "Australia/Yancowinna"), + ("Brazil/Acre", "Brazil/Acre"), + ("Brazil/DeNoronha", "Brazil/DeNoronha"), + ("Brazil/East", "Brazil/East"), + ("Brazil/West", "Brazil/West"), + ("CET", "CET"), + ("CST6CDT", "CST6CDT"), + ("Canada/Atlantic", "Canada/Atlantic"), + ("Canada/Central", "Canada/Central"), + ("Canada/Eastern", "Canada/Eastern"), + ("Canada/Mountain", "Canada/Mountain"), + ("Canada/Newfoundland", "Canada/Newfoundland"), + ("Canada/Pacific", "Canada/Pacific"), + ("Canada/Saskatchewan", "Canada/Saskatchewan"), + ("Canada/Yukon", "Canada/Yukon"), + ("Chile/Continental", "Chile/Continental"), + ("Chile/EasterIsland", "Chile/EasterIsland"), + ("Cuba", "Cuba"), + ("EET", "EET"), + ("EST", "EST"), + ("EST5EDT", "EST5EDT"), + ("Egypt", "Egypt"), + ("Eire", "Eire"), + ("Etc/GMT", "Etc/GMT"), + ("Etc/GMT+0", "Etc/GMT+0"), + ("Etc/GMT+1", "Etc/GMT+1"), + ("Etc/GMT+10", "Etc/GMT+10"), + ("Etc/GMT+11", "Etc/GMT+11"), + ("Etc/GMT+12", "Etc/GMT+12"), + ("Etc/GMT+2", "Etc/GMT+2"), + ("Etc/GMT+3", "Etc/GMT+3"), + ("Etc/GMT+4", "Etc/GMT+4"), + ("Etc/GMT+5", "Etc/GMT+5"), + ("Etc/GMT+6", "Etc/GMT+6"), + ("Etc/GMT+7", "Etc/GMT+7"), + ("Etc/GMT+8", "Etc/GMT+8"), + ("Etc/GMT+9", "Etc/GMT+9"), + ("Etc/GMT-0", "Etc/GMT-0"), + ("Etc/GMT-1", "Etc/GMT-1"), + ("Etc/GMT-10", "Etc/GMT-10"), + ("Etc/GMT-11", "Etc/GMT-11"), + ("Etc/GMT-12", "Etc/GMT-12"), + ("Etc/GMT-13", "Etc/GMT-13"), + ("Etc/GMT-14", "Etc/GMT-14"), + ("Etc/GMT-2", "Etc/GMT-2"), + ("Etc/GMT-3", "Etc/GMT-3"), + ("Etc/GMT-4", "Etc/GMT-4"), + ("Etc/GMT-5", "Etc/GMT-5"), + ("Etc/GMT-6", "Etc/GMT-6"), + ("Etc/GMT-7", "Etc/GMT-7"), + ("Etc/GMT-8", "Etc/GMT-8"), + ("Etc/GMT-9", "Etc/GMT-9"), + ("Etc/GMT0", "Etc/GMT0"), + ("Etc/Greenwich", "Etc/Greenwich"), + ("Etc/UCT", "Etc/UCT"), + ("Etc/UTC", "Etc/UTC"), + ("Etc/Universal", "Etc/Universal"), + ("Etc/Zulu", "Etc/Zulu"), + ("Europe/Amsterdam", "Europe/Amsterdam"), + ("Europe/Andorra", "Europe/Andorra"), + ("Europe/Astrakhan", "Europe/Astrakhan"), + ("Europe/Athens", "Europe/Athens"), + ("Europe/Belfast", "Europe/Belfast"), + ("Europe/Belgrade", "Europe/Belgrade"), + ("Europe/Berlin", "Europe/Berlin"), + ("Europe/Bratislava", "Europe/Bratislava"), + ("Europe/Brussels", "Europe/Brussels"), + ("Europe/Bucharest", "Europe/Bucharest"), + ("Europe/Budapest", "Europe/Budapest"), + ("Europe/Busingen", "Europe/Busingen"), + ("Europe/Chisinau", "Europe/Chisinau"), + ("Europe/Copenhagen", "Europe/Copenhagen"), + ("Europe/Dublin", "Europe/Dublin"), + ("Europe/Gibraltar", "Europe/Gibraltar"), + ("Europe/Guernsey", "Europe/Guernsey"), + ("Europe/Helsinki", "Europe/Helsinki"), + ("Europe/Isle_of_Man", "Europe/Isle_of_Man"), + ("Europe/Istanbul", "Europe/Istanbul"), + ("Europe/Jersey", "Europe/Jersey"), + ("Europe/Kaliningrad", "Europe/Kaliningrad"), + ("Europe/Kiev", "Europe/Kiev"), + ("Europe/Kirov", "Europe/Kirov"), + ("Europe/Lisbon", "Europe/Lisbon"), + ("Europe/Ljubljana", "Europe/Ljubljana"), + ("Europe/London", "Europe/London"), + ("Europe/Luxembourg", "Europe/Luxembourg"), + ("Europe/Madrid", "Europe/Madrid"), + ("Europe/Malta", "Europe/Malta"), + ("Europe/Mariehamn", "Europe/Mariehamn"), + ("Europe/Minsk", "Europe/Minsk"), + ("Europe/Monaco", "Europe/Monaco"), + ("Europe/Moscow", "Europe/Moscow"), + ("Europe/Nicosia", "Europe/Nicosia"), + ("Europe/Oslo", "Europe/Oslo"), + ("Europe/Paris", "Europe/Paris"), + ("Europe/Podgorica", "Europe/Podgorica"), + ("Europe/Prague", "Europe/Prague"), + ("Europe/Riga", "Europe/Riga"), + ("Europe/Rome", "Europe/Rome"), + ("Europe/Samara", "Europe/Samara"), + ("Europe/San_Marino", "Europe/San_Marino"), + ("Europe/Sarajevo", "Europe/Sarajevo"), + ("Europe/Saratov", "Europe/Saratov"), + ("Europe/Simferopol", "Europe/Simferopol"), + ("Europe/Skopje", "Europe/Skopje"), + ("Europe/Sofia", "Europe/Sofia"), + ("Europe/Stockholm", "Europe/Stockholm"), + ("Europe/Tallinn", "Europe/Tallinn"), + ("Europe/Tirane", "Europe/Tirane"), + ("Europe/Tiraspol", "Europe/Tiraspol"), + ("Europe/Ulyanovsk", "Europe/Ulyanovsk"), + ("Europe/Uzhgorod", "Europe/Uzhgorod"), + ("Europe/Vaduz", "Europe/Vaduz"), + ("Europe/Vatican", "Europe/Vatican"), + ("Europe/Vienna", "Europe/Vienna"), + ("Europe/Vilnius", "Europe/Vilnius"), + ("Europe/Volgograd", "Europe/Volgograd"), + ("Europe/Warsaw", "Europe/Warsaw"), + ("Europe/Zagreb", "Europe/Zagreb"), + ("Europe/Zaporozhye", "Europe/Zaporozhye"), + ("Europe/Zurich", "Europe/Zurich"), + ("GB", "GB"), + ("GB-Eire", "GB-Eire"), + ("GMT", "GMT"), + ("GMT+0", "GMT+0"), + ("GMT-0", "GMT-0"), + ("GMT0", "GMT0"), + ("Greenwich", "Greenwich"), + ("HST", "HST"), + ("Hongkong", "Hongkong"), + ("Iceland", "Iceland"), + ("Indian/Antananarivo", "Indian/Antananarivo"), + ("Indian/Chagos", "Indian/Chagos"), + ("Indian/Christmas", "Indian/Christmas"), + ("Indian/Cocos", "Indian/Cocos"), + ("Indian/Comoro", "Indian/Comoro"), + ("Indian/Kerguelen", "Indian/Kerguelen"), + ("Indian/Mahe", "Indian/Mahe"), + ("Indian/Maldives", "Indian/Maldives"), + ("Indian/Mauritius", "Indian/Mauritius"), + ("Indian/Mayotte", "Indian/Mayotte"), + ("Indian/Reunion", "Indian/Reunion"), + ("Iran", "Iran"), + ("Israel", "Israel"), + ("Jamaica", "Jamaica"), + ("Japan", "Japan"), + ("Kwajalein", "Kwajalein"), + ("Libya", "Libya"), + ("MET", "MET"), + ("MST", "MST"), + ("MST7MDT", "MST7MDT"), + ("Mexico/BajaNorte", "Mexico/BajaNorte"), + ("Mexico/BajaSur", "Mexico/BajaSur"), + ("Mexico/General", "Mexico/General"), + ("NZ", "NZ"), + ("NZ-CHAT", "NZ-CHAT"), + ("Navajo", "Navajo"), + ("PRC", "PRC"), + ("PST8PDT", "PST8PDT"), + ("Pacific/Apia", "Pacific/Apia"), + ("Pacific/Auckland", "Pacific/Auckland"), + ("Pacific/Bougainville", "Pacific/Bougainville"), + ("Pacific/Chatham", "Pacific/Chatham"), + ("Pacific/Chuuk", "Pacific/Chuuk"), + ("Pacific/Easter", "Pacific/Easter"), + ("Pacific/Efate", "Pacific/Efate"), + ("Pacific/Enderbury", "Pacific/Enderbury"), + ("Pacific/Fakaofo", "Pacific/Fakaofo"), + ("Pacific/Fiji", "Pacific/Fiji"), + ("Pacific/Funafuti", "Pacific/Funafuti"), + ("Pacific/Galapagos", "Pacific/Galapagos"), + ("Pacific/Gambier", "Pacific/Gambier"), + ("Pacific/Guadalcanal", "Pacific/Guadalcanal"), + ("Pacific/Guam", "Pacific/Guam"), + ("Pacific/Honolulu", "Pacific/Honolulu"), + ("Pacific/Johnston", "Pacific/Johnston"), + ("Pacific/Kanton", "Pacific/Kanton"), + ("Pacific/Kiritimati", "Pacific/Kiritimati"), + ("Pacific/Kosrae", "Pacific/Kosrae"), + ("Pacific/Kwajalein", "Pacific/Kwajalein"), + ("Pacific/Majuro", "Pacific/Majuro"), + ("Pacific/Marquesas", "Pacific/Marquesas"), + ("Pacific/Midway", "Pacific/Midway"), + ("Pacific/Nauru", "Pacific/Nauru"), + ("Pacific/Niue", "Pacific/Niue"), + ("Pacific/Norfolk", "Pacific/Norfolk"), + ("Pacific/Noumea", "Pacific/Noumea"), + ("Pacific/Pago_Pago", "Pacific/Pago_Pago"), + ("Pacific/Palau", "Pacific/Palau"), + ("Pacific/Pitcairn", "Pacific/Pitcairn"), + ("Pacific/Pohnpei", "Pacific/Pohnpei"), + ("Pacific/Ponape", "Pacific/Ponape"), + ("Pacific/Port_Moresby", "Pacific/Port_Moresby"), + ("Pacific/Rarotonga", "Pacific/Rarotonga"), + ("Pacific/Saipan", "Pacific/Saipan"), + ("Pacific/Samoa", "Pacific/Samoa"), + ("Pacific/Tahiti", "Pacific/Tahiti"), + ("Pacific/Tarawa", "Pacific/Tarawa"), + ("Pacific/Tongatapu", "Pacific/Tongatapu"), + ("Pacific/Truk", "Pacific/Truk"), + ("Pacific/Wake", "Pacific/Wake"), + ("Pacific/Wallis", "Pacific/Wallis"), + ("Pacific/Yap", "Pacific/Yap"), + ("Poland", "Poland"), + ("Portugal", "Portugal"), + ("ROC", "ROC"), + ("ROK", "ROK"), + ("Singapore", "Singapore"), + ("Turkey", "Turkey"), + ("UCT", "UCT"), + ("US/Alaska", "US/Alaska"), + ("US/Aleutian", "US/Aleutian"), + ("US/Arizona", "US/Arizona"), + ("US/Central", "US/Central"), + ("US/East-Indiana", "US/East-Indiana"), + ("US/Eastern", "US/Eastern"), + ("US/Hawaii", "US/Hawaii"), + ("US/Indiana-Starke", "US/Indiana-Starke"), + ("US/Michigan", "US/Michigan"), + ("US/Mountain", "US/Mountain"), + ("US/Pacific", "US/Pacific"), + ("US/Samoa", "US/Samoa"), + ("UTC", "UTC"), + ("Universal", "Universal"), + ("W-SU", "W-SU"), + ("WET", "WET"), + ("Zulu", "Zulu"), + ], + default=django.utils.timezone.get_current_timezone, + help_text="timezone used for the date", + max_length=100, + verbose_name="timezone", + ), + ), + migrations.AlterField( + model_name="sound", + name="id", + field=models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + migrations.AlterField( + model_name="sound", + name="program", + field=models.ForeignKey( + blank=True, + help_text="program related to it", + on_delete=django.db.models.deletion.CASCADE, + to="aircox.program", + verbose_name="program", + ), + ), + migrations.AlterField( + model_name="staticpage", + name="id", + field=models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + migrations.AlterField( + model_name="station", + name="id", + field=models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + migrations.AlterField( + model_name="stream", + name="id", + field=models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + migrations.AlterField( + model_name="track", + name="id", + field=models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ] diff --git a/aircox/migrations/0006_alter_sound_file.py b/aircox/migrations/0006_alter_sound_file.py new file mode 100644 index 0000000..8f4b44f --- /dev/null +++ b/aircox/migrations/0006_alter_sound_file.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.12 on 2022-03-26 15:21 + +import aircox.models.sound +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("aircox", "0005_auto_20220318_1205"), + ] + + operations = [ + migrations.AlterField( + model_name="sound", + name="file", + field=models.FileField( + db_index=True, + max_length=256, + upload_to=aircox.models.sound.Sound._upload_to, + verbose_name="file", + ), + ), + ] diff --git a/aircox/migrations/0007_sound_is_downloadable_alter_page_pub_date_and_more.py b/aircox/migrations/0007_sound_is_downloadable_alter_page_pub_date_and_more.py new file mode 100644 index 0000000..ec5b8dc --- /dev/null +++ b/aircox/migrations/0007_sound_is_downloadable_alter_page_pub_date_and_more.py @@ -0,0 +1,710 @@ +# Generated by Django 4.1 on 2022-10-06 13:47 + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + dependencies = [ + ("aircox", "0006_alter_sound_file"), + ] + + operations = [ + migrations.AddField( + model_name="sound", + name="is_downloadable", + field=models.BooleanField( + default=False, + help_text="whether it can be publicly downloaded by visitors (sound must be public)", + verbose_name="downloadable", + ), + ), + migrations.AlterField( + model_name="page", + name="pub_date", + field=models.DateTimeField( + blank=True, + db_index=True, + null=True, + verbose_name="publication date", + ), + ), + migrations.AlterField( + model_name="schedule", + name="timezone", + field=models.CharField( + choices=[ + ("Africa/Abidjan", "Africa/Abidjan"), + ("Africa/Accra", "Africa/Accra"), + ("Africa/Addis_Ababa", "Africa/Addis_Ababa"), + ("Africa/Algiers", "Africa/Algiers"), + ("Africa/Asmara", "Africa/Asmara"), + ("Africa/Asmera", "Africa/Asmera"), + ("Africa/Bamako", "Africa/Bamako"), + ("Africa/Bangui", "Africa/Bangui"), + ("Africa/Banjul", "Africa/Banjul"), + ("Africa/Bissau", "Africa/Bissau"), + ("Africa/Blantyre", "Africa/Blantyre"), + ("Africa/Brazzaville", "Africa/Brazzaville"), + ("Africa/Bujumbura", "Africa/Bujumbura"), + ("Africa/Cairo", "Africa/Cairo"), + ("Africa/Casablanca", "Africa/Casablanca"), + ("Africa/Ceuta", "Africa/Ceuta"), + ("Africa/Conakry", "Africa/Conakry"), + ("Africa/Dakar", "Africa/Dakar"), + ("Africa/Dar_es_Salaam", "Africa/Dar_es_Salaam"), + ("Africa/Djibouti", "Africa/Djibouti"), + ("Africa/Douala", "Africa/Douala"), + ("Africa/El_Aaiun", "Africa/El_Aaiun"), + ("Africa/Freetown", "Africa/Freetown"), + ("Africa/Gaborone", "Africa/Gaborone"), + ("Africa/Harare", "Africa/Harare"), + ("Africa/Johannesburg", "Africa/Johannesburg"), + ("Africa/Juba", "Africa/Juba"), + ("Africa/Kampala", "Africa/Kampala"), + ("Africa/Khartoum", "Africa/Khartoum"), + ("Africa/Kigali", "Africa/Kigali"), + ("Africa/Kinshasa", "Africa/Kinshasa"), + ("Africa/Lagos", "Africa/Lagos"), + ("Africa/Libreville", "Africa/Libreville"), + ("Africa/Lome", "Africa/Lome"), + ("Africa/Luanda", "Africa/Luanda"), + ("Africa/Lubumbashi", "Africa/Lubumbashi"), + ("Africa/Lusaka", "Africa/Lusaka"), + ("Africa/Malabo", "Africa/Malabo"), + ("Africa/Maputo", "Africa/Maputo"), + ("Africa/Maseru", "Africa/Maseru"), + ("Africa/Mbabane", "Africa/Mbabane"), + ("Africa/Mogadishu", "Africa/Mogadishu"), + ("Africa/Monrovia", "Africa/Monrovia"), + ("Africa/Nairobi", "Africa/Nairobi"), + ("Africa/Ndjamena", "Africa/Ndjamena"), + ("Africa/Niamey", "Africa/Niamey"), + ("Africa/Nouakchott", "Africa/Nouakchott"), + ("Africa/Ouagadougou", "Africa/Ouagadougou"), + ("Africa/Porto-Novo", "Africa/Porto-Novo"), + ("Africa/Sao_Tome", "Africa/Sao_Tome"), + ("Africa/Timbuktu", "Africa/Timbuktu"), + ("Africa/Tripoli", "Africa/Tripoli"), + ("Africa/Tunis", "Africa/Tunis"), + ("Africa/Windhoek", "Africa/Windhoek"), + ("America/Adak", "America/Adak"), + ("America/Anchorage", "America/Anchorage"), + ("America/Anguilla", "America/Anguilla"), + ("America/Antigua", "America/Antigua"), + ("America/Araguaina", "America/Araguaina"), + ( + "America/Argentina/Buenos_Aires", + "America/Argentina/Buenos_Aires", + ), + ( + "America/Argentina/Catamarca", + "America/Argentina/Catamarca", + ), + ( + "America/Argentina/ComodRivadavia", + "America/Argentina/ComodRivadavia", + ), + ("America/Argentina/Cordoba", "America/Argentina/Cordoba"), + ("America/Argentina/Jujuy", "America/Argentina/Jujuy"), + ( + "America/Argentina/La_Rioja", + "America/Argentina/La_Rioja", + ), + ("America/Argentina/Mendoza", "America/Argentina/Mendoza"), + ( + "America/Argentina/Rio_Gallegos", + "America/Argentina/Rio_Gallegos", + ), + ("America/Argentina/Salta", "America/Argentina/Salta"), + ( + "America/Argentina/San_Juan", + "America/Argentina/San_Juan", + ), + ( + "America/Argentina/San_Luis", + "America/Argentina/San_Luis", + ), + ("America/Argentina/Tucuman", "America/Argentina/Tucuman"), + ("America/Argentina/Ushuaia", "America/Argentina/Ushuaia"), + ("America/Aruba", "America/Aruba"), + ("America/Asuncion", "America/Asuncion"), + ("America/Atikokan", "America/Atikokan"), + ("America/Atka", "America/Atka"), + ("America/Bahia", "America/Bahia"), + ("America/Bahia_Banderas", "America/Bahia_Banderas"), + ("America/Barbados", "America/Barbados"), + ("America/Belem", "America/Belem"), + ("America/Belize", "America/Belize"), + ("America/Blanc-Sablon", "America/Blanc-Sablon"), + ("America/Boa_Vista", "America/Boa_Vista"), + ("America/Bogota", "America/Bogota"), + ("America/Boise", "America/Boise"), + ("America/Buenos_Aires", "America/Buenos_Aires"), + ("America/Cambridge_Bay", "America/Cambridge_Bay"), + ("America/Campo_Grande", "America/Campo_Grande"), + ("America/Cancun", "America/Cancun"), + ("America/Caracas", "America/Caracas"), + ("America/Catamarca", "America/Catamarca"), + ("America/Cayenne", "America/Cayenne"), + ("America/Cayman", "America/Cayman"), + ("America/Chicago", "America/Chicago"), + ("America/Chihuahua", "America/Chihuahua"), + ("America/Coral_Harbour", "America/Coral_Harbour"), + ("America/Cordoba", "America/Cordoba"), + ("America/Costa_Rica", "America/Costa_Rica"), + ("America/Creston", "America/Creston"), + ("America/Cuiaba", "America/Cuiaba"), + ("America/Curacao", "America/Curacao"), + ("America/Danmarkshavn", "America/Danmarkshavn"), + ("America/Dawson", "America/Dawson"), + ("America/Dawson_Creek", "America/Dawson_Creek"), + ("America/Denver", "America/Denver"), + ("America/Detroit", "America/Detroit"), + ("America/Dominica", "America/Dominica"), + ("America/Edmonton", "America/Edmonton"), + ("America/Eirunepe", "America/Eirunepe"), + ("America/El_Salvador", "America/El_Salvador"), + ("America/Ensenada", "America/Ensenada"), + ("America/Fort_Nelson", "America/Fort_Nelson"), + ("America/Fort_Wayne", "America/Fort_Wayne"), + ("America/Fortaleza", "America/Fortaleza"), + ("America/Glace_Bay", "America/Glace_Bay"), + ("America/Godthab", "America/Godthab"), + ("America/Goose_Bay", "America/Goose_Bay"), + ("America/Grand_Turk", "America/Grand_Turk"), + ("America/Grenada", "America/Grenada"), + ("America/Guadeloupe", "America/Guadeloupe"), + ("America/Guatemala", "America/Guatemala"), + ("America/Guayaquil", "America/Guayaquil"), + ("America/Guyana", "America/Guyana"), + ("America/Halifax", "America/Halifax"), + ("America/Havana", "America/Havana"), + ("America/Hermosillo", "America/Hermosillo"), + ( + "America/Indiana/Indianapolis", + "America/Indiana/Indianapolis", + ), + ("America/Indiana/Knox", "America/Indiana/Knox"), + ("America/Indiana/Marengo", "America/Indiana/Marengo"), + ( + "America/Indiana/Petersburg", + "America/Indiana/Petersburg", + ), + ("America/Indiana/Tell_City", "America/Indiana/Tell_City"), + ("America/Indiana/Vevay", "America/Indiana/Vevay"), + ("America/Indiana/Vincennes", "America/Indiana/Vincennes"), + ("America/Indiana/Winamac", "America/Indiana/Winamac"), + ("America/Indianapolis", "America/Indianapolis"), + ("America/Inuvik", "America/Inuvik"), + ("America/Iqaluit", "America/Iqaluit"), + ("America/Jamaica", "America/Jamaica"), + ("America/Jujuy", "America/Jujuy"), + ("America/Juneau", "America/Juneau"), + ( + "America/Kentucky/Louisville", + "America/Kentucky/Louisville", + ), + ( + "America/Kentucky/Monticello", + "America/Kentucky/Monticello", + ), + ("America/Knox_IN", "America/Knox_IN"), + ("America/Kralendijk", "America/Kralendijk"), + ("America/La_Paz", "America/La_Paz"), + ("America/Lima", "America/Lima"), + ("America/Los_Angeles", "America/Los_Angeles"), + ("America/Louisville", "America/Louisville"), + ("America/Lower_Princes", "America/Lower_Princes"), + ("America/Maceio", "America/Maceio"), + ("America/Managua", "America/Managua"), + ("America/Manaus", "America/Manaus"), + ("America/Marigot", "America/Marigot"), + ("America/Martinique", "America/Martinique"), + ("America/Matamoros", "America/Matamoros"), + ("America/Mazatlan", "America/Mazatlan"), + ("America/Mendoza", "America/Mendoza"), + ("America/Menominee", "America/Menominee"), + ("America/Merida", "America/Merida"), + ("America/Metlakatla", "America/Metlakatla"), + ("America/Mexico_City", "America/Mexico_City"), + ("America/Miquelon", "America/Miquelon"), + ("America/Moncton", "America/Moncton"), + ("America/Monterrey", "America/Monterrey"), + ("America/Montevideo", "America/Montevideo"), + ("America/Montreal", "America/Montreal"), + ("America/Montserrat", "America/Montserrat"), + ("America/Nassau", "America/Nassau"), + ("America/New_York", "America/New_York"), + ("America/Nipigon", "America/Nipigon"), + ("America/Nome", "America/Nome"), + ("America/Noronha", "America/Noronha"), + ( + "America/North_Dakota/Beulah", + "America/North_Dakota/Beulah", + ), + ( + "America/North_Dakota/Center", + "America/North_Dakota/Center", + ), + ( + "America/North_Dakota/New_Salem", + "America/North_Dakota/New_Salem", + ), + ("America/Nuuk", "America/Nuuk"), + ("America/Ojinaga", "America/Ojinaga"), + ("America/Panama", "America/Panama"), + ("America/Pangnirtung", "America/Pangnirtung"), + ("America/Paramaribo", "America/Paramaribo"), + ("America/Phoenix", "America/Phoenix"), + ("America/Port-au-Prince", "America/Port-au-Prince"), + ("America/Port_of_Spain", "America/Port_of_Spain"), + ("America/Porto_Acre", "America/Porto_Acre"), + ("America/Porto_Velho", "America/Porto_Velho"), + ("America/Puerto_Rico", "America/Puerto_Rico"), + ("America/Punta_Arenas", "America/Punta_Arenas"), + ("America/Rainy_River", "America/Rainy_River"), + ("America/Rankin_Inlet", "America/Rankin_Inlet"), + ("America/Recife", "America/Recife"), + ("America/Regina", "America/Regina"), + ("America/Resolute", "America/Resolute"), + ("America/Rio_Branco", "America/Rio_Branco"), + ("America/Rosario", "America/Rosario"), + ("America/Santa_Isabel", "America/Santa_Isabel"), + ("America/Santarem", "America/Santarem"), + ("America/Santiago", "America/Santiago"), + ("America/Santo_Domingo", "America/Santo_Domingo"), + ("America/Sao_Paulo", "America/Sao_Paulo"), + ("America/Scoresbysund", "America/Scoresbysund"), + ("America/Shiprock", "America/Shiprock"), + ("America/Sitka", "America/Sitka"), + ("America/St_Barthelemy", "America/St_Barthelemy"), + ("America/St_Johns", "America/St_Johns"), + ("America/St_Kitts", "America/St_Kitts"), + ("America/St_Lucia", "America/St_Lucia"), + ("America/St_Thomas", "America/St_Thomas"), + ("America/St_Vincent", "America/St_Vincent"), + ("America/Swift_Current", "America/Swift_Current"), + ("America/Tegucigalpa", "America/Tegucigalpa"), + ("America/Thule", "America/Thule"), + ("America/Thunder_Bay", "America/Thunder_Bay"), + ("America/Tijuana", "America/Tijuana"), + ("America/Toronto", "America/Toronto"), + ("America/Tortola", "America/Tortola"), + ("America/Vancouver", "America/Vancouver"), + ("America/Virgin", "America/Virgin"), + ("America/Whitehorse", "America/Whitehorse"), + ("America/Winnipeg", "America/Winnipeg"), + ("America/Yakutat", "America/Yakutat"), + ("America/Yellowknife", "America/Yellowknife"), + ("Antarctica/Casey", "Antarctica/Casey"), + ("Antarctica/Davis", "Antarctica/Davis"), + ("Antarctica/DumontDUrville", "Antarctica/DumontDUrville"), + ("Antarctica/Macquarie", "Antarctica/Macquarie"), + ("Antarctica/Mawson", "Antarctica/Mawson"), + ("Antarctica/McMurdo", "Antarctica/McMurdo"), + ("Antarctica/Palmer", "Antarctica/Palmer"), + ("Antarctica/Rothera", "Antarctica/Rothera"), + ("Antarctica/South_Pole", "Antarctica/South_Pole"), + ("Antarctica/Syowa", "Antarctica/Syowa"), + ("Antarctica/Troll", "Antarctica/Troll"), + ("Antarctica/Vostok", "Antarctica/Vostok"), + ("Arctic/Longyearbyen", "Arctic/Longyearbyen"), + ("Asia/Aden", "Asia/Aden"), + ("Asia/Almaty", "Asia/Almaty"), + ("Asia/Amman", "Asia/Amman"), + ("Asia/Anadyr", "Asia/Anadyr"), + ("Asia/Aqtau", "Asia/Aqtau"), + ("Asia/Aqtobe", "Asia/Aqtobe"), + ("Asia/Ashgabat", "Asia/Ashgabat"), + ("Asia/Ashkhabad", "Asia/Ashkhabad"), + ("Asia/Atyrau", "Asia/Atyrau"), + ("Asia/Baghdad", "Asia/Baghdad"), + ("Asia/Bahrain", "Asia/Bahrain"), + ("Asia/Baku", "Asia/Baku"), + ("Asia/Bangkok", "Asia/Bangkok"), + ("Asia/Barnaul", "Asia/Barnaul"), + ("Asia/Beirut", "Asia/Beirut"), + ("Asia/Bishkek", "Asia/Bishkek"), + ("Asia/Brunei", "Asia/Brunei"), + ("Asia/Calcutta", "Asia/Calcutta"), + ("Asia/Chita", "Asia/Chita"), + ("Asia/Choibalsan", "Asia/Choibalsan"), + ("Asia/Chongqing", "Asia/Chongqing"), + ("Asia/Chungking", "Asia/Chungking"), + ("Asia/Colombo", "Asia/Colombo"), + ("Asia/Dacca", "Asia/Dacca"), + ("Asia/Damascus", "Asia/Damascus"), + ("Asia/Dhaka", "Asia/Dhaka"), + ("Asia/Dili", "Asia/Dili"), + ("Asia/Dubai", "Asia/Dubai"), + ("Asia/Dushanbe", "Asia/Dushanbe"), + ("Asia/Famagusta", "Asia/Famagusta"), + ("Asia/Gaza", "Asia/Gaza"), + ("Asia/Harbin", "Asia/Harbin"), + ("Asia/Hebron", "Asia/Hebron"), + ("Asia/Ho_Chi_Minh", "Asia/Ho_Chi_Minh"), + ("Asia/Hong_Kong", "Asia/Hong_Kong"), + ("Asia/Hovd", "Asia/Hovd"), + ("Asia/Irkutsk", "Asia/Irkutsk"), + ("Asia/Istanbul", "Asia/Istanbul"), + ("Asia/Jakarta", "Asia/Jakarta"), + ("Asia/Jayapura", "Asia/Jayapura"), + ("Asia/Jerusalem", "Asia/Jerusalem"), + ("Asia/Kabul", "Asia/Kabul"), + ("Asia/Kamchatka", "Asia/Kamchatka"), + ("Asia/Karachi", "Asia/Karachi"), + ("Asia/Kashgar", "Asia/Kashgar"), + ("Asia/Kathmandu", "Asia/Kathmandu"), + ("Asia/Katmandu", "Asia/Katmandu"), + ("Asia/Khandyga", "Asia/Khandyga"), + ("Asia/Kolkata", "Asia/Kolkata"), + ("Asia/Krasnoyarsk", "Asia/Krasnoyarsk"), + ("Asia/Kuala_Lumpur", "Asia/Kuala_Lumpur"), + ("Asia/Kuching", "Asia/Kuching"), + ("Asia/Kuwait", "Asia/Kuwait"), + ("Asia/Macao", "Asia/Macao"), + ("Asia/Macau", "Asia/Macau"), + ("Asia/Magadan", "Asia/Magadan"), + ("Asia/Makassar", "Asia/Makassar"), + ("Asia/Manila", "Asia/Manila"), + ("Asia/Muscat", "Asia/Muscat"), + ("Asia/Nicosia", "Asia/Nicosia"), + ("Asia/Novokuznetsk", "Asia/Novokuznetsk"), + ("Asia/Novosibirsk", "Asia/Novosibirsk"), + ("Asia/Omsk", "Asia/Omsk"), + ("Asia/Oral", "Asia/Oral"), + ("Asia/Phnom_Penh", "Asia/Phnom_Penh"), + ("Asia/Pontianak", "Asia/Pontianak"), + ("Asia/Pyongyang", "Asia/Pyongyang"), + ("Asia/Qatar", "Asia/Qatar"), + ("Asia/Qostanay", "Asia/Qostanay"), + ("Asia/Qyzylorda", "Asia/Qyzylorda"), + ("Asia/Rangoon", "Asia/Rangoon"), + ("Asia/Riyadh", "Asia/Riyadh"), + ("Asia/Saigon", "Asia/Saigon"), + ("Asia/Sakhalin", "Asia/Sakhalin"), + ("Asia/Samarkand", "Asia/Samarkand"), + ("Asia/Seoul", "Asia/Seoul"), + ("Asia/Shanghai", "Asia/Shanghai"), + ("Asia/Singapore", "Asia/Singapore"), + ("Asia/Srednekolymsk", "Asia/Srednekolymsk"), + ("Asia/Taipei", "Asia/Taipei"), + ("Asia/Tashkent", "Asia/Tashkent"), + ("Asia/Tbilisi", "Asia/Tbilisi"), + ("Asia/Tehran", "Asia/Tehran"), + ("Asia/Tel_Aviv", "Asia/Tel_Aviv"), + ("Asia/Thimbu", "Asia/Thimbu"), + ("Asia/Thimphu", "Asia/Thimphu"), + ("Asia/Tokyo", "Asia/Tokyo"), + ("Asia/Tomsk", "Asia/Tomsk"), + ("Asia/Ujung_Pandang", "Asia/Ujung_Pandang"), + ("Asia/Ulaanbaatar", "Asia/Ulaanbaatar"), + ("Asia/Ulan_Bator", "Asia/Ulan_Bator"), + ("Asia/Urumqi", "Asia/Urumqi"), + ("Asia/Ust-Nera", "Asia/Ust-Nera"), + ("Asia/Vientiane", "Asia/Vientiane"), + ("Asia/Vladivostok", "Asia/Vladivostok"), + ("Asia/Yakutsk", "Asia/Yakutsk"), + ("Asia/Yangon", "Asia/Yangon"), + ("Asia/Yekaterinburg", "Asia/Yekaterinburg"), + ("Asia/Yerevan", "Asia/Yerevan"), + ("Atlantic/Azores", "Atlantic/Azores"), + ("Atlantic/Bermuda", "Atlantic/Bermuda"), + ("Atlantic/Canary", "Atlantic/Canary"), + ("Atlantic/Cape_Verde", "Atlantic/Cape_Verde"), + ("Atlantic/Faeroe", "Atlantic/Faeroe"), + ("Atlantic/Faroe", "Atlantic/Faroe"), + ("Atlantic/Jan_Mayen", "Atlantic/Jan_Mayen"), + ("Atlantic/Madeira", "Atlantic/Madeira"), + ("Atlantic/Reykjavik", "Atlantic/Reykjavik"), + ("Atlantic/South_Georgia", "Atlantic/South_Georgia"), + ("Atlantic/St_Helena", "Atlantic/St_Helena"), + ("Atlantic/Stanley", "Atlantic/Stanley"), + ("Australia/ACT", "Australia/ACT"), + ("Australia/Adelaide", "Australia/Adelaide"), + ("Australia/Brisbane", "Australia/Brisbane"), + ("Australia/Broken_Hill", "Australia/Broken_Hill"), + ("Australia/Canberra", "Australia/Canberra"), + ("Australia/Currie", "Australia/Currie"), + ("Australia/Darwin", "Australia/Darwin"), + ("Australia/Eucla", "Australia/Eucla"), + ("Australia/Hobart", "Australia/Hobart"), + ("Australia/LHI", "Australia/LHI"), + ("Australia/Lindeman", "Australia/Lindeman"), + ("Australia/Lord_Howe", "Australia/Lord_Howe"), + ("Australia/Melbourne", "Australia/Melbourne"), + ("Australia/NSW", "Australia/NSW"), + ("Australia/North", "Australia/North"), + ("Australia/Perth", "Australia/Perth"), + ("Australia/Queensland", "Australia/Queensland"), + ("Australia/South", "Australia/South"), + ("Australia/Sydney", "Australia/Sydney"), + ("Australia/Tasmania", "Australia/Tasmania"), + ("Australia/Victoria", "Australia/Victoria"), + ("Australia/West", "Australia/West"), + ("Australia/Yancowinna", "Australia/Yancowinna"), + ("Brazil/Acre", "Brazil/Acre"), + ("Brazil/DeNoronha", "Brazil/DeNoronha"), + ("Brazil/East", "Brazil/East"), + ("Brazil/West", "Brazil/West"), + ("CET", "CET"), + ("CST6CDT", "CST6CDT"), + ("Canada/Atlantic", "Canada/Atlantic"), + ("Canada/Central", "Canada/Central"), + ("Canada/Eastern", "Canada/Eastern"), + ("Canada/Mountain", "Canada/Mountain"), + ("Canada/Newfoundland", "Canada/Newfoundland"), + ("Canada/Pacific", "Canada/Pacific"), + ("Canada/Saskatchewan", "Canada/Saskatchewan"), + ("Canada/Yukon", "Canada/Yukon"), + ("Chile/Continental", "Chile/Continental"), + ("Chile/EasterIsland", "Chile/EasterIsland"), + ("Cuba", "Cuba"), + ("EET", "EET"), + ("EST", "EST"), + ("EST5EDT", "EST5EDT"), + ("Egypt", "Egypt"), + ("Eire", "Eire"), + ("Etc/GMT", "Etc/GMT"), + ("Etc/GMT+0", "Etc/GMT+0"), + ("Etc/GMT+1", "Etc/GMT+1"), + ("Etc/GMT+10", "Etc/GMT+10"), + ("Etc/GMT+11", "Etc/GMT+11"), + ("Etc/GMT+12", "Etc/GMT+12"), + ("Etc/GMT+2", "Etc/GMT+2"), + ("Etc/GMT+3", "Etc/GMT+3"), + ("Etc/GMT+4", "Etc/GMT+4"), + ("Etc/GMT+5", "Etc/GMT+5"), + ("Etc/GMT+6", "Etc/GMT+6"), + ("Etc/GMT+7", "Etc/GMT+7"), + ("Etc/GMT+8", "Etc/GMT+8"), + ("Etc/GMT+9", "Etc/GMT+9"), + ("Etc/GMT-0", "Etc/GMT-0"), + ("Etc/GMT-1", "Etc/GMT-1"), + ("Etc/GMT-10", "Etc/GMT-10"), + ("Etc/GMT-11", "Etc/GMT-11"), + ("Etc/GMT-12", "Etc/GMT-12"), + ("Etc/GMT-13", "Etc/GMT-13"), + ("Etc/GMT-14", "Etc/GMT-14"), + ("Etc/GMT-2", "Etc/GMT-2"), + ("Etc/GMT-3", "Etc/GMT-3"), + ("Etc/GMT-4", "Etc/GMT-4"), + ("Etc/GMT-5", "Etc/GMT-5"), + ("Etc/GMT-6", "Etc/GMT-6"), + ("Etc/GMT-7", "Etc/GMT-7"), + ("Etc/GMT-8", "Etc/GMT-8"), + ("Etc/GMT-9", "Etc/GMT-9"), + ("Etc/GMT0", "Etc/GMT0"), + ("Etc/Greenwich", "Etc/Greenwich"), + ("Etc/UCT", "Etc/UCT"), + ("Etc/UTC", "Etc/UTC"), + ("Etc/Universal", "Etc/Universal"), + ("Etc/Zulu", "Etc/Zulu"), + ("Europe/Amsterdam", "Europe/Amsterdam"), + ("Europe/Andorra", "Europe/Andorra"), + ("Europe/Astrakhan", "Europe/Astrakhan"), + ("Europe/Athens", "Europe/Athens"), + ("Europe/Belfast", "Europe/Belfast"), + ("Europe/Belgrade", "Europe/Belgrade"), + ("Europe/Berlin", "Europe/Berlin"), + ("Europe/Bratislava", "Europe/Bratislava"), + ("Europe/Brussels", "Europe/Brussels"), + ("Europe/Bucharest", "Europe/Bucharest"), + ("Europe/Budapest", "Europe/Budapest"), + ("Europe/Busingen", "Europe/Busingen"), + ("Europe/Chisinau", "Europe/Chisinau"), + ("Europe/Copenhagen", "Europe/Copenhagen"), + ("Europe/Dublin", "Europe/Dublin"), + ("Europe/Gibraltar", "Europe/Gibraltar"), + ("Europe/Guernsey", "Europe/Guernsey"), + ("Europe/Helsinki", "Europe/Helsinki"), + ("Europe/Isle_of_Man", "Europe/Isle_of_Man"), + ("Europe/Istanbul", "Europe/Istanbul"), + ("Europe/Jersey", "Europe/Jersey"), + ("Europe/Kaliningrad", "Europe/Kaliningrad"), + ("Europe/Kiev", "Europe/Kiev"), + ("Europe/Kirov", "Europe/Kirov"), + ("Europe/Kyiv", "Europe/Kyiv"), + ("Europe/Lisbon", "Europe/Lisbon"), + ("Europe/Ljubljana", "Europe/Ljubljana"), + ("Europe/London", "Europe/London"), + ("Europe/Luxembourg", "Europe/Luxembourg"), + ("Europe/Madrid", "Europe/Madrid"), + ("Europe/Malta", "Europe/Malta"), + ("Europe/Mariehamn", "Europe/Mariehamn"), + ("Europe/Minsk", "Europe/Minsk"), + ("Europe/Monaco", "Europe/Monaco"), + ("Europe/Moscow", "Europe/Moscow"), + ("Europe/Nicosia", "Europe/Nicosia"), + ("Europe/Oslo", "Europe/Oslo"), + ("Europe/Paris", "Europe/Paris"), + ("Europe/Podgorica", "Europe/Podgorica"), + ("Europe/Prague", "Europe/Prague"), + ("Europe/Riga", "Europe/Riga"), + ("Europe/Rome", "Europe/Rome"), + ("Europe/Samara", "Europe/Samara"), + ("Europe/San_Marino", "Europe/San_Marino"), + ("Europe/Sarajevo", "Europe/Sarajevo"), + ("Europe/Saratov", "Europe/Saratov"), + ("Europe/Simferopol", "Europe/Simferopol"), + ("Europe/Skopje", "Europe/Skopje"), + ("Europe/Sofia", "Europe/Sofia"), + ("Europe/Stockholm", "Europe/Stockholm"), + ("Europe/Tallinn", "Europe/Tallinn"), + ("Europe/Tirane", "Europe/Tirane"), + ("Europe/Tiraspol", "Europe/Tiraspol"), + ("Europe/Ulyanovsk", "Europe/Ulyanovsk"), + ("Europe/Uzhgorod", "Europe/Uzhgorod"), + ("Europe/Vaduz", "Europe/Vaduz"), + ("Europe/Vatican", "Europe/Vatican"), + ("Europe/Vienna", "Europe/Vienna"), + ("Europe/Vilnius", "Europe/Vilnius"), + ("Europe/Volgograd", "Europe/Volgograd"), + ("Europe/Warsaw", "Europe/Warsaw"), + ("Europe/Zagreb", "Europe/Zagreb"), + ("Europe/Zaporozhye", "Europe/Zaporozhye"), + ("Europe/Zurich", "Europe/Zurich"), + ("GB", "GB"), + ("GB-Eire", "GB-Eire"), + ("GMT", "GMT"), + ("GMT+0", "GMT+0"), + ("GMT-0", "GMT-0"), + ("GMT0", "GMT0"), + ("Greenwich", "Greenwich"), + ("HST", "HST"), + ("Hongkong", "Hongkong"), + ("Iceland", "Iceland"), + ("Indian/Antananarivo", "Indian/Antananarivo"), + ("Indian/Chagos", "Indian/Chagos"), + ("Indian/Christmas", "Indian/Christmas"), + ("Indian/Cocos", "Indian/Cocos"), + ("Indian/Comoro", "Indian/Comoro"), + ("Indian/Kerguelen", "Indian/Kerguelen"), + ("Indian/Mahe", "Indian/Mahe"), + ("Indian/Maldives", "Indian/Maldives"), + ("Indian/Mauritius", "Indian/Mauritius"), + ("Indian/Mayotte", "Indian/Mayotte"), + ("Indian/Reunion", "Indian/Reunion"), + ("Iran", "Iran"), + ("Israel", "Israel"), + ("Jamaica", "Jamaica"), + ("Japan", "Japan"), + ("Kwajalein", "Kwajalein"), + ("Libya", "Libya"), + ("MET", "MET"), + ("MST", "MST"), + ("MST7MDT", "MST7MDT"), + ("Mexico/BajaNorte", "Mexico/BajaNorte"), + ("Mexico/BajaSur", "Mexico/BajaSur"), + ("Mexico/General", "Mexico/General"), + ("NZ", "NZ"), + ("NZ-CHAT", "NZ-CHAT"), + ("Navajo", "Navajo"), + ("PRC", "PRC"), + ("PST8PDT", "PST8PDT"), + ("Pacific/Apia", "Pacific/Apia"), + ("Pacific/Auckland", "Pacific/Auckland"), + ("Pacific/Bougainville", "Pacific/Bougainville"), + ("Pacific/Chatham", "Pacific/Chatham"), + ("Pacific/Chuuk", "Pacific/Chuuk"), + ("Pacific/Easter", "Pacific/Easter"), + ("Pacific/Efate", "Pacific/Efate"), + ("Pacific/Enderbury", "Pacific/Enderbury"), + ("Pacific/Fakaofo", "Pacific/Fakaofo"), + ("Pacific/Fiji", "Pacific/Fiji"), + ("Pacific/Funafuti", "Pacific/Funafuti"), + ("Pacific/Galapagos", "Pacific/Galapagos"), + ("Pacific/Gambier", "Pacific/Gambier"), + ("Pacific/Guadalcanal", "Pacific/Guadalcanal"), + ("Pacific/Guam", "Pacific/Guam"), + ("Pacific/Honolulu", "Pacific/Honolulu"), + ("Pacific/Johnston", "Pacific/Johnston"), + ("Pacific/Kanton", "Pacific/Kanton"), + ("Pacific/Kiritimati", "Pacific/Kiritimati"), + ("Pacific/Kosrae", "Pacific/Kosrae"), + ("Pacific/Kwajalein", "Pacific/Kwajalein"), + ("Pacific/Majuro", "Pacific/Majuro"), + ("Pacific/Marquesas", "Pacific/Marquesas"), + ("Pacific/Midway", "Pacific/Midway"), + ("Pacific/Nauru", "Pacific/Nauru"), + ("Pacific/Niue", "Pacific/Niue"), + ("Pacific/Norfolk", "Pacific/Norfolk"), + ("Pacific/Noumea", "Pacific/Noumea"), + ("Pacific/Pago_Pago", "Pacific/Pago_Pago"), + ("Pacific/Palau", "Pacific/Palau"), + ("Pacific/Pitcairn", "Pacific/Pitcairn"), + ("Pacific/Pohnpei", "Pacific/Pohnpei"), + ("Pacific/Ponape", "Pacific/Ponape"), + ("Pacific/Port_Moresby", "Pacific/Port_Moresby"), + ("Pacific/Rarotonga", "Pacific/Rarotonga"), + ("Pacific/Saipan", "Pacific/Saipan"), + ("Pacific/Samoa", "Pacific/Samoa"), + ("Pacific/Tahiti", "Pacific/Tahiti"), + ("Pacific/Tarawa", "Pacific/Tarawa"), + ("Pacific/Tongatapu", "Pacific/Tongatapu"), + ("Pacific/Truk", "Pacific/Truk"), + ("Pacific/Wake", "Pacific/Wake"), + ("Pacific/Wallis", "Pacific/Wallis"), + ("Pacific/Yap", "Pacific/Yap"), + ("Poland", "Poland"), + ("Portugal", "Portugal"), + ("ROC", "ROC"), + ("ROK", "ROK"), + ("Singapore", "Singapore"), + ("Turkey", "Turkey"), + ("UCT", "UCT"), + ("US/Alaska", "US/Alaska"), + ("US/Aleutian", "US/Aleutian"), + ("US/Arizona", "US/Arizona"), + ("US/Central", "US/Central"), + ("US/East-Indiana", "US/East-Indiana"), + ("US/Eastern", "US/Eastern"), + ("US/Hawaii", "US/Hawaii"), + ("US/Indiana-Starke", "US/Indiana-Starke"), + ("US/Michigan", "US/Michigan"), + ("US/Mountain", "US/Mountain"), + ("US/Pacific", "US/Pacific"), + ("US/Samoa", "US/Samoa"), + ("UTC", "UTC"), + ("Universal", "Universal"), + ("W-SU", "W-SU"), + ("WET", "WET"), + ("Zulu", "Zulu"), + ], + default=django.utils.timezone.get_current_timezone, + help_text="timezone used for the date", + max_length=100, + verbose_name="timezone", + ), + ), + migrations.AlterField( + model_name="sound", + name="is_public", + field=models.BooleanField( + default=False, + help_text="whether it is publicly available as podcast", + verbose_name="public", + ), + ), + migrations.AlterField( + model_name="stream", + name="begin", + field=models.TimeField( + blank=True, + help_text="used to define a time range this stream is played", + null=True, + verbose_name="begin", + ), + ), + migrations.AlterField( + model_name="stream", + name="end", + field=models.TimeField( + blank=True, + help_text="used to define a time range this stream is played", + null=True, + verbose_name="end", + ), + ), + ] diff --git a/aircox/migrations/0008_alter_diffusion_options_track_album_and_more.py b/aircox/migrations/0008_alter_diffusion_options_track_album_and_more.py new file mode 100644 index 0000000..8eee35e --- /dev/null +++ b/aircox/migrations/0008_alter_diffusion_options_track_album_and_more.py @@ -0,0 +1,48 @@ +# Generated by Django 4.1 on 2022-12-09 13:46 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("aircox", "0007_sound_is_downloadable_alter_page_pub_date_and_more"), + ] + + operations = [ + migrations.AlterModelOptions( + name="diffusion", + options={ + "permissions": ( + ("programming", "edit the diffusions' planification"), + ), + "verbose_name": "Diffusion", + "verbose_name_plural": "Diffusions", + }, + ), + migrations.AddField( + model_name="track", + name="album", + field=models.CharField( + default="", max_length=128, verbose_name="album" + ), + ), + migrations.AlterField( + model_name="schedule", + name="frequency", + field=models.SmallIntegerField( + choices=[ + (0, "ponctual"), + (1, "1st {day} of the month"), + (2, "2nd {day} of the month"), + (4, "3rd {day} of the month"), + (8, "4th {day} of the month"), + (16, "last {day} of the month"), + (5, "1st and 3rd {day} of the month"), + (10, "2nd and 4th {day} of the month"), + (31, "{day}"), + (32, "one {day} on two"), + ], + verbose_name="frequency", + ), + ), + ] diff --git a/aircox/migrations/0009_track_year.py b/aircox/migrations/0009_track_year.py new file mode 100644 index 0000000..d1bd0c3 --- /dev/null +++ b/aircox/migrations/0009_track_year.py @@ -0,0 +1,19 @@ +# Generated by Django 4.1 on 2022-12-09 13:50 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("aircox", "0008_alter_diffusion_options_track_album_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="track", + name="year", + field=models.IntegerField( + blank=True, null=True, verbose_name="year" + ), + ), + ] diff --git a/aircox/migrations/0010_alter_track_album.py b/aircox/migrations/0010_alter_track_album.py new file mode 100644 index 0000000..956875c --- /dev/null +++ b/aircox/migrations/0010_alter_track_album.py @@ -0,0 +1,19 @@ +# Generated by Django 4.1 on 2022-12-09 18:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("aircox", "0009_track_year"), + ] + + operations = [ + migrations.AlterField( + model_name="track", + name="album", + field=models.CharField( + blank=True, max_length=128, null=True, verbose_name="album" + ), + ), + ] diff --git a/aircox/migrations/0011_usersettings.py b/aircox/migrations/0011_usersettings.py new file mode 100644 index 0000000..2c42c3b --- /dev/null +++ b/aircox/migrations/0011_usersettings.py @@ -0,0 +1,48 @@ +# Generated by Django 4.1 on 2022-12-11 12:24 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("aircox", "0010_alter_track_album"), + ] + + operations = [ + migrations.CreateModel( + name="UserSettings", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "playlist_editor_columns", + models.JSONField(verbose_name="Playlist Editor Columns"), + ), + ( + "playlist_editor_sep", + models.CharField( + max_length=16, verbose_name="Playlist Editor Separator" + ), + ), + ( + "user", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + related_name="aircox_settings", + to=settings.AUTH_USER_MODEL, + verbose_name="User", + ), + ), + ], + ), + ] diff --git a/aircox/migrations/0012_alter_sound_file_alter_station_default.py b/aircox/migrations/0012_alter_sound_file_alter_station_default.py new file mode 100644 index 0000000..e12cff6 --- /dev/null +++ b/aircox/migrations/0012_alter_sound_file_alter_station_default.py @@ -0,0 +1,33 @@ +# Generated by Django 4.1 on 2023-01-25 15:18 + +import aircox.models.sound +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("aircox", "0011_usersettings"), + ] + + operations = [ + migrations.AlterField( + model_name="sound", + name="file", + field=models.FileField( + db_index=True, + max_length=256, + unique=True, + upload_to=aircox.models.sound.Sound._upload_to, + verbose_name="file", + ), + ), + migrations.AlterField( + model_name="station", + name="default", + field=models.BooleanField( + default=False, + help_text="use this station as the main one.", + verbose_name="default station", + ), + ), + ] diff --git a/aircox/migrations/__init__.py b/aircox/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/aircox/models/__init__.py b/aircox/models/__init__.py index 4e1419f..d4034a5 100644 --- a/aircox/models/__init__.py +++ b/aircox/models/__init__.py @@ -1,17 +1,11 @@ from . import signals from .article import Article -from .episode import Diffusion, DiffusionQuerySet, Episode +from .diffusion import Diffusion, DiffusionQuerySet +from .episode import Episode from .log import Log, LogArchiver, LogQuerySet from .page import Category, Comment, NavItem, Page, PageQuerySet, StaticPage -from .program import ( - BaseRerun, - BaseRerunQuerySet, - Program, - ProgramChildQuerySet, - ProgramQuerySet, - Schedule, - Stream, -) +from .program import Program, ProgramChildQuerySet, ProgramQuerySet, Stream +from .schedule import Schedule from .sound import Sound, SoundQuerySet, Track from .station import Port, Station, StationQuerySet from .user_settings import UserSettings @@ -36,8 +30,6 @@ __all__ = ( "Stream", "Schedule", "ProgramChildQuerySet", - "BaseRerun", - "BaseRerunQuerySet", "Sound", "SoundQuerySet", "Track", diff --git a/aircox/models/diffusion.py b/aircox/models/diffusion.py new file mode 100644 index 0000000..2949885 --- /dev/null +++ b/aircox/models/diffusion.py @@ -0,0 +1,282 @@ +import datetime + +from django.db import models +from django.db.models import Q +from django.utils import timezone as tz +from django.utils.functional import cached_property +from django.utils.translation import gettext_lazy as _ + +from aircox import utils + +from .episode import Episode +from .schedule import Schedule +from .rerun import Rerun, RerunQuerySet + + +__all__ = ("Diffusion", "DiffusionQuerySet") + + +class DiffusionQuerySet(RerunQuerySet): + def episode(self, episode=None, id=None): + """Diffusions for this episode.""" + return ( + self.filter(episode=episode) + if id is None + else self.filter(episode__id=id) + ) + + def on_air(self): + """On air diffusions.""" + return self.filter(type=Diffusion.TYPE_ON_AIR) + + # TODO: rename to `datetime` + def now(self, now=None, order=True): + """Diffusions occuring now.""" + now = now or tz.now() + qs = self.filter(start__lte=now, end__gte=now).distinct() + return qs.order_by("start") if order else qs + + def date(self, date=None, order=True): + """Diffusions occuring date.""" + date = date or datetime.date.today() + start = tz.datetime.combine(date, datetime.time()) + end = tz.datetime.combine(date, datetime.time(23, 59, 59, 999)) + # start = tz.get_current_timezone().localize(start) + # end = tz.get_current_timezone().localize(end) + qs = self.filter(start__range=(start, end)) + return qs.order_by("start") if order else qs + + def at(self, date, order=True): + """Return diffusions at specified date or datetime.""" + return ( + self.now(date, order) + if isinstance(date, tz.datetime) + else self.date(date, order) + ) + + def after(self, date=None): + """Return a queryset of diffusions that happen after the given date + (default: today).""" + date = utils.date_or_default(date) + if isinstance(date, tz.datetime): + qs = self.filter(Q(start__gte=date) | Q(end__gte=date)) + else: + qs = self.filter(Q(start__date__gte=date) | Q(end__date__gte=date)) + return qs.order_by("start") + + def before(self, date=None): + """Return a queryset of diffusions that finish before the given date + (default: today).""" + date = utils.date_or_default(date) + if isinstance(date, tz.datetime): + qs = self.filter(start__lt=date) + else: + qs = self.filter(start__date__lt=date) + return qs.order_by("start") + + def range(self, start, end): + # FIXME can return dates that are out of range... + return self.after(start).before(end) + + +class Diffusion(Rerun): + """A Diffusion is an occurrence of a Program that is scheduled on the + station's timetable. It can be a rerun of a previous diffusion. In such a + case, use rerun's info instead of its own. + + A Diffusion without any rerun is named Episode (previously, a + Diffusion was different from an Episode, but in the end, an + episode only has a name, a linked program, and a list of sounds, so we + finally merge theme). + + A Diffusion can have different types: + - default: simple diffusion that is planified / did occurred + - unconfirmed: a generated diffusion that has not been confirmed and thus + is not yet planified + - cancel: the diffusion has been canceled + - stop: the diffusion has been manually stopped + """ + + objects = DiffusionQuerySet.as_manager() + + TYPE_ON_AIR = 0x00 + TYPE_UNCONFIRMED = 0x01 + TYPE_CANCEL = 0x02 + TYPE_CHOICES = ( + (TYPE_ON_AIR, _("on air")), + (TYPE_UNCONFIRMED, _("not confirmed")), + (TYPE_CANCEL, _("cancelled")), + ) + + 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, + ) + start = models.DateTimeField(_("start"), db_index=True) + end = models.DateTimeField(_("end"), db_index=True) + # port = models.ForeignKey( + # 'self', + # verbose_name = _('port'), + # blank = True, null = True, + # on_delete=models.SET_NULL, + # help_text = _('use this input port'), + # ) + + item_template_name = "aircox/widgets/diffusion_item.html" + + class Meta: + verbose_name = _("Diffusion") + verbose_name_plural = _("Diffusions") + permissions = ( + ("programming", _("edit the diffusions' planification")), + ) + + def __str__(self): + str_ = "{episode} - {date}".format( + episode=self.episode and self.episode.title, + date=self.local_start.strftime("%Y/%m/%d %H:%M%z"), + ) + if self.initial: + str_ += " ({})".format(_("rerun")) + return str_ + + def save(self, *args, **kwargs): + super().save(*args, **kwargs) + if self.is_initial and self.episode != self._initial["episode"]: + self.rerun_set.update(episode=self.episode, program=self.program) + + # def save(self, no_check=False, *args, **kwargs): + # if self.start != self._initial['start'] or \ + # self.end != self._initial['end']: + # self.check_conflicts() + + def save_rerun(self): + self.episode = self.initial.episode + super().save_rerun() + + def save_initial(self): + self.program = self.episode.program + + @property + def duration(self): + return self.end - self.start + + @property + def date(self): + """Return diffusion start as a date.""" + + return utils.cast_date(self.start) + + @cached_property + def local_start(self): + """Return a version of self.date that is localized to self.timezone; + This is needed since datetime are stored as UTC date and we want to get + it as local time.""" + + return tz.localtime(self.start, tz.get_current_timezone()) + + @property + def local_end(self): + """Return a version of self.date that is localized to self.timezone; + This is needed since datetime are stored as UTC date and we want to get + it as local time.""" + + return tz.localtime(self.end, tz.get_current_timezone()) + + @property + def is_now(self): + """True if diffusion is currently running.""" + now = tz.now() + return ( + self.type == self.TYPE_ON_AIR + and self.start <= now + and self.end >= now + ) + + @property + def is_live(self): + """True if Diffusion is live (False if there are sounds files).""" + return ( + self.type == self.TYPE_ON_AIR + and not self.episode.sound_set.archive().count() + ) + + def get_playlist(self, **types): + """Returns sounds as a playlist (list of *local* archive file path). + + The given arguments are passed to ``get_sounds``. + """ + from .sound import Sound + + return list( + self.get_sounds(**types) + .filter(path__isnull=False, type=Sound.TYPE_ARCHIVE) + .values_list("path", flat=True) + ) + + def get_sounds(self, **types): + """Return a queryset of sounds related to this diffusion, ordered by + type then path. + + **types: filter on the given sound types name, as `archive=True` + """ + from .sound import Sound + + sounds = (self.initial or self).sound_set.order_by("type", "path") + _in = [ + getattr(Sound.Type, name) for name, value in types.items() if value + ] + + return sounds.filter(type__in=_in) + + def is_date_in_range(self, date=None): + """Return true if the given date is in the diffusion's start-end + range.""" + date = date or tz.now() + + return self.start < date < self.end + + def get_conflicts(self): + """Return conflicting diffusions queryset.""" + + # conflicts=Diffusion.objects.filter( + # Q(start__lt=OuterRef('start'), end__gt=OuterRef('end')) | + # Q(start__gt=OuterRef('start'), start__lt=OuterRef('end')) + # ) + # diffs= Diffusion.objects.annotate(conflict_with=Exists(conflicts)) + # .filter(conflict_with=True) + return ( + Diffusion.objects.filter( + Q(start__lt=self.start, end__gt=self.start) + | Q(start__gt=self.start, start__lt=self.end) + ) + .exclude(pk=self.pk) + .distinct() + ) + + def check_conflicts(self): + conflicts = self.get_conflicts() + self.conflicts.set(conflicts) + + _initial = None + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._initial = { + "start": self.start, + "end": self.end, + "episode": getattr(self, "episode", None), + } diff --git a/aircox/models/episode.py b/aircox/models/episode.py index ab95c2d..bf15fe9 100644 --- a/aircox/models/episode.py +++ b/aircox/models/episode.py @@ -1,24 +1,13 @@ -import datetime - -from django.db import models -from django.db.models import Q -from django.utils import timezone as tz from django.utils.functional import cached_property from django.utils.translation import gettext_lazy as _ from easy_thumbnails.files import get_thumbnailer from aircox.conf import settings -from aircox import utils from .page import Page -from .program import ( - BaseRerun, - BaseRerunQuerySet, - ProgramChildQuerySet, - Schedule, -) +from .program import ProgramChildQuerySet -__all__ = ("Episode", "Diffusion", "DiffusionQuerySet") +__all__ = ("Episode",) class Episode(Page): @@ -90,269 +79,3 @@ class Episode(Page): return super().get_init_kwargs_from( page, title=title, program=page, **kwargs ) - - -class DiffusionQuerySet(BaseRerunQuerySet): - def episode(self, episode=None, id=None): - """Diffusions for this episode.""" - return ( - self.filter(episode=episode) - if id is None - else self.filter(episode__id=id) - ) - - def on_air(self): - """On air diffusions.""" - return self.filter(type=Diffusion.TYPE_ON_AIR) - - # TODO: rename to `datetime` - def now(self, now=None, order=True): - """Diffusions occuring now.""" - now = now or tz.now() - qs = self.filter(start__lte=now, end__gte=now).distinct() - return qs.order_by("start") if order else qs - - def date(self, date=None, order=True): - """Diffusions occuring date.""" - date = date or datetime.date.today() - start = tz.datetime.combine(date, datetime.time()) - end = tz.datetime.combine(date, datetime.time(23, 59, 59, 999)) - # start = tz.get_current_timezone().localize(start) - # end = tz.get_current_timezone().localize(end) - qs = self.filter(start__range=(start, end)) - return qs.order_by("start") if order else qs - - def at(self, date, order=True): - """Return diffusions at specified date or datetime.""" - return ( - self.now(date, order) - if isinstance(date, tz.datetime) - else self.date(date, order) - ) - - def after(self, date=None): - """Return a queryset of diffusions that happen after the given date - (default: today).""" - date = utils.date_or_default(date) - if isinstance(date, tz.datetime): - qs = self.filter(Q(start__gte=date) | Q(end__gte=date)) - else: - qs = self.filter(Q(start__date__gte=date) | Q(end__date__gte=date)) - return qs.order_by("start") - - def before(self, date=None): - """Return a queryset of diffusions that finish before the given date - (default: today).""" - date = utils.date_or_default(date) - if isinstance(date, tz.datetime): - qs = self.filter(start__lt=date) - else: - qs = self.filter(start__date__lt=date) - return qs.order_by("start") - - def range(self, start, end): - # FIXME can return dates that are out of range... - return self.after(start).before(end) - - -class Diffusion(BaseRerun): - """A Diffusion is an occurrence of a Program that is scheduled on the - station's timetable. It can be a rerun of a previous diffusion. In such a - case, use rerun's info instead of its own. - - A Diffusion without any rerun is named Episode (previously, a - Diffusion was different from an Episode, but in the end, an - episode only has a name, a linked program, and a list of sounds, so we - finally merge theme). - - A Diffusion can have different types: - - default: simple diffusion that is planified / did occurred - - unconfirmed: a generated diffusion that has not been confirmed and thus - is not yet planified - - cancel: the diffusion has been canceled - - stop: the diffusion has been manually stopped - """ - - objects = DiffusionQuerySet.as_manager() - - TYPE_ON_AIR = 0x00 - TYPE_UNCONFIRMED = 0x01 - TYPE_CANCEL = 0x02 - TYPE_CHOICES = ( - (TYPE_ON_AIR, _("on air")), - (TYPE_UNCONFIRMED, _("not confirmed")), - (TYPE_CANCEL, _("cancelled")), - ) - - 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, - ) - start = models.DateTimeField(_("start"), db_index=True) - end = models.DateTimeField(_("end"), db_index=True) - # port = models.ForeignKey( - # 'self', - # verbose_name = _('port'), - # blank = True, null = True, - # on_delete=models.SET_NULL, - # help_text = _('use this input port'), - # ) - - item_template_name = "aircox/widgets/diffusion_item.html" - - class Meta: - verbose_name = _("Diffusion") - verbose_name_plural = _("Diffusions") - permissions = ( - ("programming", _("edit the diffusions' planification")), - ) - - def __str__(self): - str_ = "{episode} - {date}".format( - episode=self.episode and self.episode.title, - date=self.local_start.strftime("%Y/%m/%d %H:%M%z"), - ) - if self.initial: - str_ += " ({})".format(_("rerun")) - return str_ - - def save(self, *args, **kwargs): - super().save(*args, **kwargs) - if self.is_initial and self.episode != self._initial["episode"]: - self.rerun_set.update(episode=self.episode, program=self.program) - - # def save(self, no_check=False, *args, **kwargs): - # if self.start != self._initial['start'] or \ - # self.end != self._initial['end']: - # self.check_conflicts() - - def save_rerun(self): - self.episode = self.initial.episode - self.program = self.episode.program - - def save_initial(self): - self.program = self.episode.program - - @property - def duration(self): - return self.end - self.start - - @property - def date(self): - """Return diffusion start as a date.""" - - return utils.cast_date(self.start) - - @cached_property - def local_start(self): - """Return a version of self.date that is localized to self.timezone; - This is needed since datetime are stored as UTC date and we want to get - it as local time.""" - - return tz.localtime(self.start, tz.get_current_timezone()) - - @property - def local_end(self): - """Return a version of self.date that is localized to self.timezone; - This is needed since datetime are stored as UTC date and we want to get - it as local time.""" - - return tz.localtime(self.end, tz.get_current_timezone()) - - @property - def is_now(self): - """True if diffusion is currently running.""" - now = tz.now() - return ( - self.type == self.TYPE_ON_AIR - and self.start <= now - and self.end >= now - ) - - @property - def is_live(self): - """True if Diffusion is live (False if there are sounds files).""" - return ( - self.type == self.TYPE_ON_AIR - and not self.episode.sound_set.archive().count() - ) - - def get_playlist(self, **types): - """Returns sounds as a playlist (list of *local* archive file path). - - The given arguments are passed to ``get_sounds``. - """ - from .sound import Sound - - return list( - self.get_sounds(**types) - .filter(path__isnull=False, type=Sound.TYPE_ARCHIVE) - .values_list("path", flat=True) - ) - - def get_sounds(self, **types): - """Return a queryset of sounds related to this diffusion, ordered by - type then path. - - **types: filter on the given sound types name, as `archive=True` - """ - from .sound import Sound - - sounds = (self.initial or self).sound_set.order_by("type", "path") - _in = [ - getattr(Sound.Type, name) for name, value in types.items() if value - ] - - return sounds.filter(type__in=_in) - - def is_date_in_range(self, date=None): - """Return true if the given date is in the diffusion's start-end - range.""" - date = date or tz.now() - - return self.start < date < self.end - - def get_conflicts(self): - """Return conflicting diffusions queryset.""" - - # conflicts=Diffusion.objects.filter( - # Q(start__lt=OuterRef('start'), end__gt=OuterRef('end')) | - # Q(start__gt=OuterRef('start'), start__lt=OuterRef('end')) - # ) - # diffs= Diffusion.objects.annotate(conflict_with=Exists(conflicts)) - # .filter(conflict_with=True) - return ( - Diffusion.objects.filter( - Q(start__lt=self.start, end__gt=self.start) - | Q(start__gt=self.start, start__lt=self.end) - ) - .exclude(pk=self.pk) - .distinct() - ) - - def check_conflicts(self): - conflicts = self.get_conflicts() - self.conflicts.set(conflicts) - - _initial = None - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self._initial = { - "start": self.start, - "end": self.end, - "episode": getattr(self, "episode", None), - } diff --git a/aircox/models/log.py b/aircox/models/log.py index 77e6220..b918a42 100644 --- a/aircox/models/log.py +++ b/aircox/models/log.py @@ -13,7 +13,7 @@ from django.utils.translation import gettext_lazy as _ from aircox.conf import settings __all__ = ("Settings", "settings") -from .episode import Diffusion +from .diffusion import Diffusion from .sound import Sound, Track from .station import Station diff --git a/aircox/models/program.py b/aircox/models/program.py index c6a89a5..c6562db 100644 --- a/aircox/models/program.py +++ b/aircox/models/program.py @@ -1,21 +1,13 @@ -import calendar import logging import os import shutil -from collections import OrderedDict -from enum import IntEnum -import pytz from django.conf import settings as conf -from django.core.exceptions import ValidationError from django.db import models from django.db.models import F from django.db.models.functions import Concat, Substr -from django.utils import timezone as tz -from django.utils.functional import cached_property from django.utils.translation import gettext_lazy as _ -from aircox import utils from aircox.conf import settings from .page import Page, PageQuerySet @@ -26,12 +18,9 @@ logger = logging.getLogger("aircox") __all__ = ( "Program", + "ProgramChildQuerySet", "ProgramQuerySet", "Stream", - "Schedule", - "ProgramChildQuerySet", - "BaseRerun", - "BaseRerunQuerySet", ) @@ -167,352 +156,6 @@ class ProgramChildQuerySet(PageQuerySet): return self.parent(program, id) -class BaseRerunQuerySet(models.QuerySet): - """Queryset for BaseRerun (sub)classes.""" - - def station(self, station=None, id=None): - return ( - self.filter(program__station=station) - if id is None - else self.filter(program__station__id=id) - ) - - def program(self, program=None, id=None): - return ( - self.filter(program=program) - if id is None - else self.filter(program__id=id) - ) - - def rerun(self): - return self.filter(initial__isnull=False) - - def initial(self): - return self.filter(initial__isnull=True) - - -class BaseRerun(models.Model): - """Abstract model offering rerun facilities. - - Assume `start` is a datetime field or attribute implemented by - subclass. - """ - - program = models.ForeignKey( - Program, - models.CASCADE, - db_index=True, - verbose_name=_("related program"), - ) - initial = models.ForeignKey( - "self", - models.SET_NULL, - related_name="rerun_set", - verbose_name=_("rerun of"), - limit_choices_to={"initial__isnull": True}, - blank=True, - null=True, - db_index=True, - ) - - objects = BaseRerunQuerySet.as_manager() - - class Meta: - abstract = True - - def save(self, *args, **kwargs): - if self.initial is not None: - self.initial = self.initial.get_initial() - if self.initial == self: - self.initial = None - - if self.is_rerun: - self.save_rerun() - else: - self.save_initial() - super().save(*args, **kwargs) - - def save_rerun(self): - pass - - def save_initial(self): - pass - - @property - def is_initial(self): - return self.initial is None - - @property - def is_rerun(self): - return self.initial is not None - - def get_initial(self): - """Return the initial schedule (self or initial)""" - return self if self.initial is None else self.initial.get_initial() - - def clean(self): - super().clean() - if self.initial is not None and self.initial.start >= self.start: - raise ValidationError( - {"initial": _("rerun must happen after original")} - ) - - -# ? 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 run or a rerun (in such case it is linked to - the related schedule). - """ - - # Frequency for schedules. Basically, it is a mask of bits where each bit - # is a week. Bits > rank 5 are used for special schedules. - # Important: the first week is always the first week where the weekday of - # the schedule is present. - # For ponctual programs, there is no need for a schedule, only a diffusion - class Frequency(IntEnum): - ponctual = 0b000000 - first = 0b000001 - second = 0b000010 - third = 0b000100 - fourth = 0b001000 - last = 0b010000 - first_and_third = 0b000101 - second_and_fourth = 0b001010 - every = 0b011111 - one_on_two = 0b100000 - - date = models.DateField( - _("date"), - help_text=_("date of the first diffusion"), - ) - time = models.TimeField( - _("time"), - help_text=_("start time"), - ) - timezone = models.CharField( - _("timezone"), - default=tz.get_current_timezone, - max_length=100, - choices=[(x, x) for x in pytz.all_timezones], - help_text=_("timezone used for the date"), - ) - duration = models.TimeField( - _("duration"), - help_text=_("regular duration"), - ) - frequency = models.SmallIntegerField( - _("frequency"), - choices=[ - ( - int(y), - { - "ponctual": _("ponctual"), - "first": _("1st {day} of the month"), - "second": _("2nd {day} of the month"), - "third": _("3rd {day} of the month"), - "fourth": _("4th {day} of the month"), - "last": _("last {day} of the month"), - "first_and_third": _("1st and 3rd {day} of the month"), - "second_and_fourth": _("2nd and 4th {day} of the month"), - "every": _("{day}"), - "one_on_two": _("one {day} on two"), - }[x], - ) - for x, y in Frequency.__members__.items() - ], - ) - - class Meta: - verbose_name = _("Schedule") - verbose_name_plural = _("Schedules") - - def __str__(self): - return "{} - {}, {}".format( - self.program.title, - self.get_frequency_verbose(), - self.time.strftime("%H:%M"), - ) - - def save_rerun(self, *args, **kwargs): - self.program = self.initial.program - self.duration = self.initial.duration - self.frequency = self.initial.frequency - - @cached_property - def tz(self): - """Pytz timezone of the schedule.""" - import pytz - - return pytz.timezone(self.timezone) - - @cached_property - def start(self): - """Datetime of the start (timezone unaware)""" - return tz.datetime.combine(self.date, self.time) - - @cached_property - def end(self): - """Datetime of the end.""" - return self.start + utils.to_timedelta(self.duration) - - def get_frequency_verbose(self): - """Return frequency formated for display.""" - from django.template.defaultfilters import date - - return ( - self.get_frequency_display() - .format(day=date(self.date, "l")) - .capitalize() - ) - - # initial cached data - __initial = None - - def changed(self, fields=["date", "duration", "frequency", "timezone"]): - initial = self._Schedule__initial - - if not initial: - return - - this = self.__dict__ - - for field in fields: - if initial.get(field) != this.get(field): - return True - - return False - - def normalize(self, date): - """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)) - - def dates_of_month(self, date): - """Return normalized diffusion dates of provided date's month.""" - if self.frequency == Schedule.Frequency.ponctual: - return [] - - sched_wday, freq = self.date.weekday(), self.frequency - date = date.replace(day=1) - - # last of the month - if freq == Schedule.Frequency.last: - date = date.replace( - day=calendar.monthrange(date.year, date.month)[1] - ) - 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. Check on SO#3284452 for the formula - date_wday, month = date.weekday(), date.month - date += tz.timedelta( - days=(7 if date_wday > sched_wday else 0) - date_wday + sched_wday - ) - - if freq == Schedule.Frequency.one_on_two: - # - adjust date with modulo 14 (= 2 weeks in days) - # - there are max 3 "weeks on two" per month - if (date - self.date).days % 14: - date += tz.timedelta(days=7) - dates = (date + tz.timedelta(days=14 * i) for i in range(0, 3)) - else: - dates = ( - date + tz.timedelta(days=7 * week) - for week in range(0, 5) - if freq & (0b1 << week) - ) - - return [self.normalize(date) for date in dates if date.month == month] - - def _exclude_existing_date(self, dates): - from .episode 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): - """Get episodes and diffusions for month of provided date, including - reruns. - - :returns: tuple([Episode], [Diffusion]) - """ - from .episode import Diffusion, Episode - - if ( - self.initial is not None - or self.frequency == Schedule.Frequency.ponctual - ): - return [], [] - - # dates for self and reruns as (date, initial) - reruns = [ - (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.update( - [ - (rerun.normalize(date.date() + delta), date) - for date in dates.keys() - for rerun, delta in reruns - ] - ) - - # remove dates corresponding to existing diffusions - saved = set( - Diffusion.objects.filter( - start__in=dates.keys(), program=self.program, schedule=self - ).values_list("start", flat=True) - ) - - # make diffs - duration = utils.to_timedelta(self.duration) - diffusions = {} - episodes = {} - - for date, initial in dates.items(): - if date in saved: - continue - - if initial is None: - episode = Episode.from_page(self.program, date=date) - episode.date = date - episodes[date] = episode - else: - episode = episodes[initial] - initial = diffusions[initial] - - diffusions[date] = Diffusion( - episode=episode, - schedule=self, - type=Diffusion.TYPE_ON_AIR, - initial=initial, - start=date, - end=date + duration, - ) - return episodes.values(), diffusions.values() - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - # TODO/FIXME: use validators? - if self.initial is not None and self.date > self.date: - raise ValueError("initial must be later") - - class Stream(models.Model): """When there are no program scheduled, it is possible to play sounds in order to avoid blanks. A Stream is a Program that plays this role, and diff --git a/aircox/models/rerun.py b/aircox/models/rerun.py new file mode 100644 index 0000000..59b7c20 --- /dev/null +++ b/aircox/models/rerun.py @@ -0,0 +1,106 @@ +from django.core.exceptions import ValidationError +from django.db import models +from django.utils.translation import gettext_lazy as _ + +from .program import Program + + +__all__ = ( + "Rerun", + "RerunQuerySet", +) + + +class RerunQuerySet(models.QuerySet): + """Queryset for Rerun (sub)classes.""" + + def station(self, station=None, id=None): + return ( + self.filter(program__station=station) + if id is None + else self.filter(program__station__id=id) + ) + + def program(self, program=None, id=None): + return ( + self.filter(program=program) + if id is None + else self.filter(program__id=id) + ) + + def rerun(self): + return self.filter(initial__isnull=False) + + def initial(self): + return self.filter(initial__isnull=True) + + +class Rerun(models.Model): + """Abstract model offering rerun facilities. + + Assume `start` is a datetime field or attribute implemented by + subclass. + """ + + program = models.ForeignKey( + Program, + models.CASCADE, + db_index=True, + verbose_name=_("related program"), + ) + initial = models.ForeignKey( + "self", + models.SET_NULL, + related_name="rerun_set", + verbose_name=_("rerun of"), + limit_choices_to={"initial__isnull": True}, + blank=True, + null=True, + db_index=True, + ) + + objects = RerunQuerySet.as_manager() + + class Meta: + abstract = True + + @property + def is_initial(self): + return self.initial is None + + @property + def is_rerun(self): + return self.initial is not None + + def get_initial(self): + """Return the initial schedule (self or initial)""" + return self if self.initial is None else self.initial.get_initial() + + def clean(self): + super().clean() + if ( + hasattr(self, "start") + and self.initial is not None + and self.initial.start >= self.start + ): + raise ValidationError( + {"initial": _("rerun must happen after original")} + ) + + def save_rerun(self): + self.program = self.initial.program + + def save_initial(self): + pass + + def save(self, *args, **kwargs): + if self.initial is not None: + self.initial = self.initial.get_initial() + if self.initial == self: + self.initial = None + + if self.is_rerun: + self.save_rerun() + else: + self.save_initial() + super().save(*args, **kwargs) diff --git a/aircox/models/schedule.py b/aircox/models/schedule.py new file mode 100644 index 0000000..e867682 --- /dev/null +++ b/aircox/models/schedule.py @@ -0,0 +1,217 @@ +import calendar + +import pytz +from django.db import models +from django.utils import timezone as tz +from django.utils.functional import cached_property +from django.utils.translation import gettext_lazy as _ + +from aircox import utils + +from .rerun import Rerun + + +__all__ = ("Schedule",) + + +# ? BIG FIXME: self.date is still used as datetime +class Schedule(Rerun): + """A Schedule defines time slots of programs' diffusions. + + It can be an initial run or a rerun (in such case it is linked to + the related schedule). + """ + + # Frequency for schedules. Basically, it is a mask of bits where each bit + # is a week. Bits > rank 5 are used for special schedules. + # Important: the first week is always the first week where the weekday of + # the schedule is present. + # For ponctual programs, there is no need for a schedule, only a diffusion + class Frequency(models.IntegerChoices): + ponctual = 0b000000, _("ponctual") + first = 0b000001, _("1st {day} of the month") + second = 0b000010, _("2nd {day} of the month") + third = 0b000100, _("3rd {day} of the month") + fourth = 0b001000, _("4th {day} of the month") + last = 0b010000, _("last {day} of the month") + first_and_third = 0b000101, _("1st and 3rd {day} of the month") + second_and_fourth = 0b001010, _("2nd and 4th {day} of the month") + every = 0b011111, _("{day}") + one_on_two = 0b100000, _("one {day} on two") + + date = models.DateField( + _("date"), + help_text=_("date of the first diffusion"), + ) + time = models.TimeField( + _("time"), + help_text=_("start time"), + ) + timezone = models.CharField( + _("timezone"), + default=lambda: tz.get_current_timezone().zone, + max_length=100, + choices=[(x, x) for x in pytz.all_timezones], + help_text=_("timezone used for the date"), + ) + duration = models.TimeField( + _("duration"), + help_text=_("regular duration"), + ) + frequency = models.SmallIntegerField( + _("frequency"), + choices=Frequency.choices, + ) + + class Meta: + verbose_name = _("Schedule") + verbose_name_plural = _("Schedules") + + def __str__(self): + return "{} - {}, {}".format( + self.program.title, + self.get_frequency_display(), + self.time.strftime("%H:%M"), + ) + + def save_rerun(self): + super().save_rerun() + self.duration = self.initial.duration + self.frequency = self.initial.frequency + + @cached_property + def tz(self): + """Pytz timezone of the schedule.""" + import pytz + + return pytz.timezone(self.timezone) + + @cached_property + def start(self): + """Datetime of the start (timezone unaware)""" + return tz.datetime.combine(self.date, self.time) + + @cached_property + def end(self): + """Datetime of the end.""" + return self.start + utils.to_timedelta(self.duration) + + def get_frequency_display(self): + """Return frequency formated for display.""" + from django.template.defaultfilters import date + + return ( + self._get_FIELD_display(self._meta.get_field("frequency")) + .format(day=date(self.date, "l")) + .capitalize() + ) + + def normalize(self, date): + """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)) + + def dates_of_month(self, date): + """Return normalized diffusion dates of provided date's month.""" + if self.frequency == Schedule.Frequency.ponctual: + return [] + + sched_wday, freq = self.date.weekday(), self.frequency + date = date.replace(day=1) + + # last of the month + if freq == Schedule.Frequency.last: + date = date.replace( + day=calendar.monthrange(date.year, date.month)[1] + ) + 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. Check on SO#3284452 for the formula + date_wday, month = date.weekday(), date.month + date += tz.timedelta( + days=(7 if date_wday > sched_wday else 0) - date_wday + sched_wday + ) + + if freq == Schedule.Frequency.one_on_two: + # - adjust date with modulo 14 (= 2 weeks in days) + # - there are max 3 "weeks on two" per month + if (date - self.date).days % 14: + date += tz.timedelta(days=7) + dates = (date + tz.timedelta(days=14 * i) for i in range(0, 3)) + else: + dates = ( + date + tz.timedelta(days=7 * week) + for week in range(0, 5) + if freq & (0b1 << week) + ) + + return [self.normalize(date) for date in dates if date.month == month] + + def diffusions_of_month(self, date): + """Get episodes and diffusions for month of provided date, including + reruns. + + :returns: tuple([Episode], [Diffusion]) + """ + from .diffusion import Diffusion + from .episode import Episode + + if ( + self.initial is not None + or self.frequency == Schedule.Frequency.ponctual + ): + return [], [] + + # dates for self and reruns as (date, initial) + reruns = [ + (rerun, rerun.date - self.date) for rerun in self.rerun_set.all() + ] + + dates = {date: None for date in self.dates_of_month(date)} + dates.update( + (rerun.normalize(date.date() + delta), date) + for date in list(dates.keys()) + for rerun, delta in reruns + ) + + # remove dates corresponding to existing diffusions + saved = set( + Diffusion.objects.filter( + start__in=dates.keys(), program=self.program, schedule=self + ).values_list("start", flat=True) + ) + + # make diffs + duration = utils.to_timedelta(self.duration) + diffusions = {} + episodes = {} + + for date, initial in dates.items(): + if date in saved: + continue + + if initial is None: + episode = Episode.from_page(self.program, date=date) + episode.date = date + episodes[date] = episode + else: + episode = episodes[initial] + initial = diffusions[initial] + + diffusions[date] = Diffusion( + 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 9a89e65..97cf952 100755 --- a/aircox/models/signals.py +++ b/aircox/models/signals.py @@ -6,9 +6,11 @@ from django.utils import timezone as tz from aircox import utils from aircox.conf import settings -from .episode import Episode, Diffusion +from .diffusion import Diffusion +from .episode import Episode from .page import Page -from .program import Program, Schedule +from .program import Program +from .schedule import Schedule # Add a default group to a user when it is created. It also assigns a list diff --git a/aircox/templates/aircox/program_detail.html b/aircox/templates/aircox/program_detail.html index 67f7083..267067b 100644 --- a/aircox/templates/aircox/program_detail.html +++ b/aircox/templates/aircox/program_detail.html @@ -43,7 +43,7 @@

{% translate "Diffusions" %}

{% 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.end|date:"H:i" as end %} diff --git a/aircox/tests/conftest.py b/aircox/tests/conftest.py new file mode 100644 index 0000000..2a29393 --- /dev/null +++ b/aircox/tests/conftest.py @@ -0,0 +1,72 @@ +from datetime import time, timedelta +import itertools + +import pytest +from model_bakery import baker + +from aircox import models + + +@pytest.fixture +def stations(): + return baker.make("aircox.station", _quantity=2) + + +@pytest.fixture +def programs(stations): + items = list( + itertools.chain( + *( + baker.make("aircox.program", station=station, _quantity=3) + for station in stations + ) + ) + ) + for item in items: + item.save() + return items + + +@pytest.fixture +def sched_initials(programs): + # use concrete class; timezone is provided in order to ensure DST + items = [ + baker.prepare( + "aircox.schedule", + program=program, + time=time(16, 00), + timezone="Europe/Brussels", + ) + for program in programs + ] + models.Schedule.objects.bulk_create(items) + return items + + +@pytest.fixture +def sched_reruns(sched_initials): + # use concrete class + items = [ + baker.prepare( + "aircox.schedule", + initial=initial, + program=initial.program, + date=initial.date, + time=(initial.start + timedelta(hours=1)).time(), + ) + for initial in sched_initials + ] + models.Schedule.objects.bulk_create(items) + return items + + +@pytest.fixture +def schedules(sched_initials, sched_reruns): + return sched_initials + sched_reruns + + +@pytest.fixture +def episodes(programs): + return [ + baker.make("aircox.episode", parent=program) for program in programs + ] diff --git a/aircox/tests/models/test_diffusion.py b/aircox/tests/models/test_diffusion.py new file mode 100644 index 0000000..26b177b --- /dev/null +++ b/aircox/tests/models/test_diffusion.py @@ -0,0 +1,19 @@ +import pytest + + +class TestDiffusionQuerySet: + @pytest.mark.django_db + def test_episode_by_obj(self, episodes): + pass + + @pytest.mark.django_db + def test_episode_by_id(self, episodes): + pass + + @pytest.mark.django_db + def test_on_air(self, episodes): + pass + + @pytest.mark.django_db + def test_now(self, episodes): + pass diff --git a/aircox/tests/models/test_rerun.py b/aircox/tests/models/test_rerun.py new file mode 100644 index 0000000..5cfb895 --- /dev/null +++ b/aircox/tests/models/test_rerun.py @@ -0,0 +1,117 @@ +from datetime import timedelta + +import pytest + +from django.core.exceptions import ValidationError + +# we use Schedule as concrete class (Rerun is abstract) +from aircox.models import Schedule + + +class TestRerunQuerySet: + @pytest.mark.django_db + def test_station_by_obj(self, stations, schedules): + for station in stations: + queryset = ( + Schedule.objects.station(station) + .distinct() + .values_list("program__station", flat=True) + ) + assert queryset.count() == 1 + assert queryset.first() == station.pk + + @pytest.mark.django_db + def test_station_by_id(self, stations, schedules): + for station in stations: + queryset = ( + Schedule.objects.station(id=station.pk) + .distinct() + .values_list("program__station", flat=True) + ) + assert queryset.count() == 1 + assert queryset.first() == station.pk + + @pytest.mark.django_db + def test_program_by_obj(self, programs, schedules): + for program in programs: + queryset = ( + Schedule.objects.program(program) + .distinct() + .values_list("program", flat=True) + ) + assert queryset.count() == 1 + assert queryset.first() == program.pk + + @pytest.mark.django_db + def test_program_by_id(self, programs, schedules): + for program in programs: + queryset = ( + Schedule.objects.program(id=program.pk) + .distinct() + .values_list("program", flat=True) + ) + assert queryset.count() == 1 + assert queryset.first() == program.pk + + @pytest.mark.django_db + def test_rerun(self, schedules): + queryset = Schedule.objects.rerun().values_list("initial", flat=True) + assert None not in queryset + + @pytest.mark.django_db + def test_initial(self, schedules): + queryset = ( + Schedule.objects.initial() + .distinct() + .values_list("initial", flat=True) + ) + assert queryset.count() == 1 + assert queryset.first() is None + + +class TestRerun: + @pytest.mark.django_db + def test_is_initial_true(self, sched_initials): + assert all(r.is_initial for r in sched_initials) + + @pytest.mark.django_db + def test_is_initial_false(self, sched_reruns): + assert all(not r.is_initial for r in sched_reruns) + + @pytest.mark.django_db + def test_is_rerun_true(self, sched_reruns): + assert all(r.is_rerun for r in sched_reruns) + + @pytest.mark.django_db + def test_is_rerun_false(self, sched_initials): + assert all(not r.is_rerun for r in sched_initials) + + @pytest.mark.django_db + def test_get_initial_of_initials(self, sched_initials): + assert all(r.get_initial() is r for r in sched_initials) + + @pytest.mark.django_db + def test_get_initial_of_reruns(self, sched_reruns): + assert all(r.get_initial() is r.initial for r in sched_reruns) + + @pytest.mark.django_db + def test_clean_success(self, sched_reruns): + for rerun in sched_reruns: + rerun.clean() + + @pytest.mark.django_db + def test_clean_fails(self, sched_reruns): + for rerun in sched_reruns: + rerun.time = (rerun.initial.start - timedelta(hours=2)).time() + with pytest.raises(ValidationError): + rerun.clean() + + @pytest.mark.django_db + def test_save_rerun(self, sched_reruns): + for rerun in sched_reruns: + rerun.program = None + rerun.save_rerun() + assert rerun.program == rerun.initial.program + + # TODO: save() + # save_initial is empty, thus not tested diff --git a/aircox/tests/models/test_schedule.py b/aircox/tests/models/test_schedule.py new file mode 100644 index 0000000..6d1a662 --- /dev/null +++ b/aircox/tests/models/test_schedule.py @@ -0,0 +1,135 @@ +from datetime import date, datetime, time, timedelta + +import pytest +from model_bakery import baker + +import calendar +from dateutil.relativedelta import relativedelta + +from aircox import utils +from aircox.models import Diffusion, Schedule + + +class TestSchedule: + @pytest.mark.django_db + def test_save_rerun(self, sched_reruns): + for schedule in sched_reruns: + schedule.duration = None + schedule.frequency = None + schedule.save_rerun() + assert schedule.program == schedule.initial.program + assert schedule.duration == schedule.initial.duration + assert schedule.frequency == schedule.initial.frequency + + @pytest.mark.django_db + def test_tz(self, schedules): + for schedule in schedules: + assert schedule.timezone == schedule.tz.zone + + @pytest.mark.django_db + def test_start(self, schedules): + for schedule in schedules: + assert schedule.start.date() == schedule.date + assert schedule.start.time() == schedule.time + + @pytest.mark.django_db + def test_end(self, schedules): + for schedule in schedules: + delta = utils.to_timedelta(schedule.duration) + assert schedule.end - schedule.start == delta + + # def test_get_frequency_display(self): + # pass + + @pytest.mark.django_db + def test_normalize(self, schedules): + for schedule in schedules: + dt = datetime.combine(schedule.date, schedule.time) + assert schedule.normalize(dt).tzinfo.zone == schedule.timezone + + @pytest.mark.django_db + def test_dates_of_month_ponctual(self): + 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.parametrize("months", range(0, 25, 2)) + @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.parametrize("months", range(0, 25, 2)) + @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 + def test_diffusions_of_month(self, sched_initials): + # TODO: test values of initial, rerun + for schedule in sched_initials: + at = schedule.date + timedelta(days=30) + dates = set(schedule.dates_of_month(at)) + episodes, diffusions = schedule.diffusions_of_month(at) + + assert all(r.date in dates for r in episodes) + assert all( + (not r.initial or r.date in dates) + and r.type == Diffusion.TYPE_ON_AIR + for r in diffusions + ) diff --git a/aircox/tests/old.py b/aircox/tests/old.py deleted file mode 100755 index fd8531d..0000000 --- a/aircox/tests/old.py +++ /dev/null @@ -1,66 +0,0 @@ -import calendar -import datetime -import logging - -from dateutil.relativedelta import relativedelta -from django.test import TestCase -from django.utils import timezone as tz - -from aircox.models import Schedule - -logger = logging.getLogger("aircox.test") -logger.setLevel("INFO") - - -class ScheduleCheck(TestCase): - def setUp(self): - self.schedules = [ - Schedule( - date=tz.now(), - duration=datetime.time(1, 30), - frequency=frequency, - ) - for frequency in Schedule.Frequency.__members__.values() - ] - - def test_frequencies(self): - for schedule in self.schedules: - logger.info( - "- test frequency %s" % schedule.get_frequency_display() - ) - date = schedule.date - count = 24 - while count: - logger.info( - "- month %(month)s/%(year)s" - % {"month": date.month, "year": date.year} - ) - count -= 1 - dates = schedule.dates_of_month(date) - if schedule.frequency == schedule.Frequency.one_on_two: - self.check_one_on_two(schedule, date, dates) - elif schedule.frequency == schedule.Frequency.last: - self.check_last(schedule, date, dates) - else: - pass - date += relativedelta(months=1) - - def check_one_on_two(self, schedule, date, dates): - for date in dates: - delta = date.date() - schedule.date.date() - self.assertEqual(delta.days % 14, 0) - - def check_last(self, schedule, date, dates): - month_info = calendar.monthrange(date.year, date.month) - date = datetime.date(date.year, date.month, month_info[1]) - - # end of month before the wanted weekday: move one week back - if date.weekday() < schedule.date.weekday(): - date -= datetime.timedelta(days=7) - - date -= datetime.timedelta(days=date.weekday()) - date += datetime.timedelta(days=schedule.date.weekday()) - self.assertEqual(date, dates[0].date()) - - def check_n_of_week(self, schedule, date, dates): - pass diff --git a/aircox/views/__init__.py b/aircox/views/__init__.py index 8991694..3f9dd15 100644 --- a/aircox/views/__init__.py +++ b/aircox/views/__init__.py @@ -1,7 +1,8 @@ from . import admin from .article import ArticleDetailView, ArticleListView from .base import BaseAPIView, BaseView -from .episode import DiffusionListView, EpisodeDetailView, EpisodeListView +from .diffusion import DiffusionListView +from .episode import EpisodeDetailView, EpisodeListView from .home import HomeView from .log import LogListAPIView, LogListView from .page import ( diff --git a/aircox/views/diffusion.py b/aircox/views/diffusion.py new file mode 100644 index 0000000..5b90efb --- /dev/null +++ b/aircox/views/diffusion.py @@ -0,0 +1,30 @@ +import datetime + +from django.views.generic import ListView + +from aircox.models import Diffusion, StaticPage +from .base import BaseView +from .mixins import AttachedToMixin, GetDateMixin + +__all__ = ("DiffusionListView",) + + +class DiffusionListView(GetDateMixin, AttachedToMixin, BaseView, ListView): + """View for timetables.""" + + model = Diffusion + has_filters = True + redirect_date_url = "diffusion-list" + attach_to_value = StaticPage.ATTACH_TO_DIFFUSIONS + + def get_date(self): + date = super().get_date() + return date if date is not None else datetime.date.today() + + def get_queryset(self): + return super().get_queryset().date(self.date).order_by("start") + + def get_context_data(self, **kwargs): + start = self.date - datetime.timedelta(days=self.date.weekday()) + dates = [start + datetime.timedelta(days=i) for i in range(0, 7)] + return super().get_context_data(date=self.date, dates=dates, **kwargs) diff --git a/aircox/views/episode.py b/aircox/views/episode.py index 7ebb52f..e53dd19 100644 --- a/aircox/views/episode.py +++ b/aircox/views/episode.py @@ -1,18 +1,11 @@ -import datetime - -from django.views.generic import ListView - from ..filters import EpisodeFilters -from ..models import Diffusion, Episode, Program, StaticPage -from .base import BaseView -from .mixins import AttachedToMixin, GetDateMixin +from ..models import Episode, Program, StaticPage from .page import PageListView from .program import ProgramPageDetailView __all__ = ( "EpisodeDetailView", "EpisodeListView", - "DiffusionListView", ) @@ -32,24 +25,3 @@ class EpisodeListView(PageListView): has_headline = True parent_model = Program attach_to_value = StaticPage.ATTACH_TO_EPISODES - - -class DiffusionListView(GetDateMixin, AttachedToMixin, BaseView, ListView): - """View for timetables.""" - - model = Diffusion - has_filters = True - redirect_date_url = "diffusion-list" - attach_to_value = StaticPage.ATTACH_TO_DIFFUSIONS - - def get_date(self): - date = super().get_date() - return date if date is not None else datetime.date.today() - - def get_queryset(self): - return super().get_queryset().date(self.date).order_by("start") - - def get_context_data(self, **kwargs): - start = self.date - datetime.timedelta(days=self.date.weekday()) - dates = [start + datetime.timedelta(days=i) for i in range(0, 7)] - return super().get_context_data(date=self.date, dates=dates, **kwargs) diff --git a/requirements_tests.txt b/requirements_tests.txt index d18e0aa..9007f2b 100644 --- a/requirements_tests.txt +++ b/requirements_tests.txt @@ -1,2 +1,3 @@ pytest~=7.2 pytest-django~=4.5 +model_bakery~=1.10