parent
695e4d7c5d
commit
cd19c26e82
|
@ -16,7 +16,7 @@ repos:
|
||||||
rev: 6.0.0
|
rev: 6.0.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: flake8
|
- id: flake8
|
||||||
exclude: instance/settings/
|
exclude: ^instance/settings/|migrations/
|
||||||
- repo: https://github.com/PyCQA/docformatter.git
|
- repo: https://github.com/PyCQA/docformatter.git
|
||||||
rev: v1.5.1
|
rev: v1.5.1
|
||||||
hooks:
|
hooks:
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
from . import filters
|
from . import filters
|
||||||
from .article import ArticleAdmin
|
from .article import ArticleAdmin
|
||||||
from .episode import DiffusionAdmin, EpisodeAdmin
|
from .diffusion import DiffusionAdmin
|
||||||
|
from .episode import EpisodeAdmin
|
||||||
from .log import LogAdmin
|
from .log import LogAdmin
|
||||||
from .page import PageAdmin, StaticPageAdmin
|
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 .sound import SoundAdmin, TrackAdmin
|
||||||
from .station import StationAdmin
|
from .station import StationAdmin
|
||||||
|
|
||||||
|
|
48
aircox/admin/diffusion.py
Normal file
48
aircox/admin/diffusion.py
Normal file
|
@ -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")
|
|
@ -1,52 +1,11 @@
|
||||||
from adminsortable2.admin import SortableAdminBase
|
from adminsortable2.admin import SortableAdminBase
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.forms import ModelForm
|
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 .page import PageAdmin
|
||||||
from .sound import SoundInline, TrackInline
|
from .sound import SoundInline, TrackInline
|
||||||
|
from .diffusion import 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")
|
|
||||||
|
|
||||||
|
|
||||||
class EpisodeAdminForm(ModelForm):
|
class EpisodeAdminForm(ModelForm):
|
||||||
|
|
|
@ -1,26 +1,12 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.forms import ModelForm
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from ..models import Program, Schedule, Stream
|
from aircox.models import Program, Schedule, Stream
|
||||||
from .page import PageAdmin
|
from .page import PageAdmin
|
||||||
|
from .schedule import ScheduleInline
|
||||||
|
|
||||||
|
|
||||||
# In order to simplify schedule_post_save algorithm, an existing schedule can't
|
__all__ = ("ProgramAdmin", "StreamInline", "StreamAdmin")
|
||||||
# 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
|
|
||||||
|
|
||||||
|
|
||||||
class StreamInline(admin.TabularInline):
|
class StreamInline(admin.TabularInline):
|
||||||
|
@ -58,36 +44,6 @@ class ProgramAdmin(PageAdmin):
|
||||||
return fields
|
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)
|
@admin.register(Stream)
|
||||||
class StreamAdmin(admin.ModelAdmin):
|
class StreamAdmin(admin.ModelAdmin):
|
||||||
list_display = ("id", "program", "delay", "begin", "end")
|
list_display = ("id", "program", "delay", "begin", "end")
|
||||||
|
|
55
aircox/admin/schedule.py
Normal file
55
aircox/admin/schedule.py
Normal file
|
@ -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 []
|
1722
aircox/migrations/0001_initial.py
Normal file
1722
aircox/migrations/0001_initial.py
Normal file
File diff suppressed because it is too large
Load Diff
17
aircox/migrations/0002_auto_20200526_1516.py
Normal file
17
aircox/migrations/0002_auto_20200526_1516.py
Normal file
|
@ -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",
|
||||||
|
),
|
||||||
|
]
|
146
aircox/migrations/0003_auto_20200530_1116.py
Normal file
146
aircox/migrations/0003_auto_20200530_1116.py
Normal file
|
@ -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",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
59
aircox/migrations/0004_auto_20200921_2356.py
Normal file
59
aircox/migrations/0004_auto_20200921_2356.py
Normal file
|
@ -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"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
839
aircox/migrations/0005_auto_20220318_1205.py
Normal file
839
aircox/migrations/0005_auto_20220318_1205.py
Normal file
|
@ -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",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
23
aircox/migrations/0006_alter_sound_file.py
Normal file
23
aircox/migrations/0006_alter_sound_file.py
Normal file
|
@ -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",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
19
aircox/migrations/0009_track_year.py
Normal file
19
aircox/migrations/0009_track_year.py
Normal file
|
@ -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"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
19
aircox/migrations/0010_alter_track_album.py
Normal file
19
aircox/migrations/0010_alter_track_album.py
Normal file
|
@ -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"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
48
aircox/migrations/0011_usersettings.py
Normal file
48
aircox/migrations/0011_usersettings.py
Normal file
|
@ -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",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
|
@ -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",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
0
aircox/migrations/__init__.py
Normal file
0
aircox/migrations/__init__.py
Normal file
|
@ -1,17 +1,11 @@
|
||||||
from . import signals
|
from . import signals
|
||||||
from .article import Article
|
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 .log import Log, LogArchiver, LogQuerySet
|
||||||
from .page import Category, Comment, NavItem, Page, PageQuerySet, StaticPage
|
from .page import Category, Comment, NavItem, Page, PageQuerySet, StaticPage
|
||||||
from .program import (
|
from .program import Program, ProgramChildQuerySet, ProgramQuerySet, Stream
|
||||||
BaseRerun,
|
from .schedule import Schedule
|
||||||
BaseRerunQuerySet,
|
|
||||||
Program,
|
|
||||||
ProgramChildQuerySet,
|
|
||||||
ProgramQuerySet,
|
|
||||||
Schedule,
|
|
||||||
Stream,
|
|
||||||
)
|
|
||||||
from .sound import Sound, SoundQuerySet, Track
|
from .sound import Sound, SoundQuerySet, Track
|
||||||
from .station import Port, Station, StationQuerySet
|
from .station import Port, Station, StationQuerySet
|
||||||
from .user_settings import UserSettings
|
from .user_settings import UserSettings
|
||||||
|
@ -36,8 +30,6 @@ __all__ = (
|
||||||
"Stream",
|
"Stream",
|
||||||
"Schedule",
|
"Schedule",
|
||||||
"ProgramChildQuerySet",
|
"ProgramChildQuerySet",
|
||||||
"BaseRerun",
|
|
||||||
"BaseRerunQuerySet",
|
|
||||||
"Sound",
|
"Sound",
|
||||||
"SoundQuerySet",
|
"SoundQuerySet",
|
||||||
"Track",
|
"Track",
|
||||||
|
|
282
aircox/models/diffusion.py
Normal file
282
aircox/models/diffusion.py
Normal file
|
@ -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),
|
||||||
|
}
|
|
@ -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.functional import cached_property
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from easy_thumbnails.files import get_thumbnailer
|
from easy_thumbnails.files import get_thumbnailer
|
||||||
|
|
||||||
from aircox.conf import settings
|
from aircox.conf import settings
|
||||||
from aircox import utils
|
|
||||||
|
|
||||||
from .page import Page
|
from .page import Page
|
||||||
from .program import (
|
from .program import ProgramChildQuerySet
|
||||||
BaseRerun,
|
|
||||||
BaseRerunQuerySet,
|
|
||||||
ProgramChildQuerySet,
|
|
||||||
Schedule,
|
|
||||||
)
|
|
||||||
|
|
||||||
__all__ = ("Episode", "Diffusion", "DiffusionQuerySet")
|
__all__ = ("Episode",)
|
||||||
|
|
||||||
|
|
||||||
class Episode(Page):
|
class Episode(Page):
|
||||||
|
@ -90,269 +79,3 @@ class Episode(Page):
|
||||||
return super().get_init_kwargs_from(
|
return super().get_init_kwargs_from(
|
||||||
page, title=title, program=page, **kwargs
|
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),
|
|
||||||
}
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ from django.utils.translation import gettext_lazy as _
|
||||||
from aircox.conf import settings
|
from aircox.conf import settings
|
||||||
|
|
||||||
__all__ = ("Settings", "settings")
|
__all__ = ("Settings", "settings")
|
||||||
from .episode import Diffusion
|
from .diffusion import Diffusion
|
||||||
from .sound import Sound, Track
|
from .sound import Sound, Track
|
||||||
from .station import Station
|
from .station import Station
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,13 @@
|
||||||
import calendar
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
from collections import OrderedDict
|
|
||||||
from enum import IntEnum
|
|
||||||
|
|
||||||
import pytz
|
|
||||||
from django.conf import settings as conf
|
from django.conf import settings as conf
|
||||||
from django.core.exceptions import ValidationError
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import F
|
from django.db.models import F
|
||||||
from django.db.models.functions import Concat, Substr
|
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 django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from aircox import utils
|
|
||||||
from aircox.conf import settings
|
from aircox.conf import settings
|
||||||
|
|
||||||
from .page import Page, PageQuerySet
|
from .page import Page, PageQuerySet
|
||||||
|
@ -26,12 +18,9 @@ logger = logging.getLogger("aircox")
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
"Program",
|
"Program",
|
||||||
|
"ProgramChildQuerySet",
|
||||||
"ProgramQuerySet",
|
"ProgramQuerySet",
|
||||||
"Stream",
|
"Stream",
|
||||||
"Schedule",
|
|
||||||
"ProgramChildQuerySet",
|
|
||||||
"BaseRerun",
|
|
||||||
"BaseRerunQuerySet",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -167,352 +156,6 @@ class ProgramChildQuerySet(PageQuerySet):
|
||||||
return self.parent(program, id)
|
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):
|
class Stream(models.Model):
|
||||||
"""When there are no program scheduled, it is possible to play sounds in
|
"""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
|
order to avoid blanks. A Stream is a Program that plays this role, and
|
||||||
|
|
106
aircox/models/rerun.py
Normal file
106
aircox/models/rerun.py
Normal file
|
@ -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)
|
217
aircox/models/schedule.py
Normal file
217
aircox/models/schedule.py
Normal file
|
@ -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()
|
|
@ -6,9 +6,11 @@ from django.utils import timezone as tz
|
||||||
|
|
||||||
from aircox import utils
|
from aircox import utils
|
||||||
from aircox.conf import settings
|
from aircox.conf import settings
|
||||||
from .episode import Episode, Diffusion
|
from .diffusion import Diffusion
|
||||||
|
from .episode import Episode
|
||||||
from .page import Page
|
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
|
# Add a default group to a user when it is created. It also assigns a list
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
<section>
|
<section>
|
||||||
<h4 class="title is-4">{% translate "Diffusions" %}</h4>
|
<h4 class="title is-4">{% translate "Diffusions" %}</h4>
|
||||||
{% for schedule in program.schedule_set.all %}
|
{% for schedule in program.schedule_set.all %}
|
||||||
{{ schedule.get_frequency_verbose }}
|
{{ schedule.get_frequency_display }}
|
||||||
{% with schedule.start|date:"H:i" as start %}
|
{% with schedule.start|date:"H:i" as start %}
|
||||||
{% with schedule.end|date:"H:i" as end %}
|
{% with schedule.end|date:"H:i" as end %}
|
||||||
<time datetime="{{ start }}">{{ start }}</time>
|
<time datetime="{{ start }}">{{ start }}</time>
|
||||||
|
|
72
aircox/tests/conftest.py
Normal file
72
aircox/tests/conftest.py
Normal file
|
@ -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
|
||||||
|
]
|
19
aircox/tests/models/test_diffusion.py
Normal file
19
aircox/tests/models/test_diffusion.py
Normal file
|
@ -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
|
117
aircox/tests/models/test_rerun.py
Normal file
117
aircox/tests/models/test_rerun.py
Normal file
|
@ -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
|
135
aircox/tests/models/test_schedule.py
Normal file
135
aircox/tests/models/test_schedule.py
Normal file
|
@ -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
|
||||||
|
)
|
|
@ -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
|
|
|
@ -1,7 +1,8 @@
|
||||||
from . import admin
|
from . import admin
|
||||||
from .article import ArticleDetailView, ArticleListView
|
from .article import ArticleDetailView, ArticleListView
|
||||||
from .base import BaseAPIView, BaseView
|
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 .home import HomeView
|
||||||
from .log import LogListAPIView, LogListView
|
from .log import LogListAPIView, LogListView
|
||||||
from .page import (
|
from .page import (
|
||||||
|
|
30
aircox/views/diffusion.py
Normal file
30
aircox/views/diffusion.py
Normal file
|
@ -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)
|
|
@ -1,18 +1,11 @@
|
||||||
import datetime
|
|
||||||
|
|
||||||
from django.views.generic import ListView
|
|
||||||
|
|
||||||
from ..filters import EpisodeFilters
|
from ..filters import EpisodeFilters
|
||||||
from ..models import Diffusion, Episode, Program, StaticPage
|
from ..models import Episode, Program, StaticPage
|
||||||
from .base import BaseView
|
|
||||||
from .mixins import AttachedToMixin, GetDateMixin
|
|
||||||
from .page import PageListView
|
from .page import PageListView
|
||||||
from .program import ProgramPageDetailView
|
from .program import ProgramPageDetailView
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
"EpisodeDetailView",
|
"EpisodeDetailView",
|
||||||
"EpisodeListView",
|
"EpisodeListView",
|
||||||
"DiffusionListView",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,24 +25,3 @@ class EpisodeListView(PageListView):
|
||||||
has_headline = True
|
has_headline = True
|
||||||
parent_model = Program
|
parent_model = Program
|
||||||
attach_to_value = StaticPage.ATTACH_TO_EPISODES
|
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)
|
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
pytest~=7.2
|
pytest~=7.2
|
||||||
pytest-django~=4.5
|
pytest-django~=4.5
|
||||||
|
model_bakery~=1.10
|
||||||
|
|
Loading…
Reference in New Issue
Block a user