parent
f9ad81ddac
commit
2ce435fb5d
|
@ -13,7 +13,7 @@ class DateFieldFilter(filters.FieldListFilter):
|
||||||
input_type = "date"
|
input_type = "date"
|
||||||
|
|
||||||
def __init__(self, field, request, params, model, model_admin, field_path):
|
def __init__(self, field, request, params, model, model_admin, field_path):
|
||||||
self.field_generic = "%s__" % field_path
|
self.field_generic = f"{field_path}__"
|
||||||
self.date_params = {
|
self.date_params = {
|
||||||
k: v for k, v in params.items() if k.startswith(self.field_generic)
|
k: v for k, v in params.items() if k.startswith(self.field_generic)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,41 +0,0 @@
|
||||||
class UnrelatedInlineMixin:
|
|
||||||
"""Inline class that can be included in an admin change view whose model is
|
|
||||||
not directly related to inline's model."""
|
|
||||||
|
|
||||||
view_model = None
|
|
||||||
parent_model = None
|
|
||||||
parent_fk = ""
|
|
||||||
|
|
||||||
def __init__(self, parent_model, admin_site):
|
|
||||||
self.view_model = parent_model
|
|
||||||
super().__init__(self.parent_model, admin_site)
|
|
||||||
|
|
||||||
def get_parent(self, view_obj):
|
|
||||||
"""Get formset's instance from `obj` of AdminSite's change form."""
|
|
||||||
field = self.parent_model._meta.get_field(self.parent_fk).remote_field
|
|
||||||
return getattr(view_obj, field.name, None)
|
|
||||||
|
|
||||||
def save_parent(self, parent, view_obj):
|
|
||||||
"""Save formset's instance."""
|
|
||||||
setattr(parent, self.parent_fk, view_obj)
|
|
||||||
parent.save()
|
|
||||||
return parent
|
|
||||||
|
|
||||||
def get_formset(self, request, obj):
|
|
||||||
ParentFormSet = super().get_formset(request, obj)
|
|
||||||
inline = self
|
|
||||||
|
|
||||||
class FormSet(ParentFormSet):
|
|
||||||
view_obj = None
|
|
||||||
|
|
||||||
def __init__(self, *args, instance=None, **kwargs):
|
|
||||||
self.view_obj = instance
|
|
||||||
instance = inline.get_parent(instance)
|
|
||||||
self.instance = instance
|
|
||||||
super().__init__(*args, instance=instance, **kwargs)
|
|
||||||
|
|
||||||
def save(self):
|
|
||||||
inline.save_parent(self.instance, self.view_obj)
|
|
||||||
return super().save()
|
|
||||||
|
|
||||||
return FormSet
|
|
|
@ -80,7 +80,7 @@ class LogArchiver:
|
||||||
def load_file(self, path):
|
def load_file(self, path):
|
||||||
with gzip.open(path, "rb") as archive:
|
with gzip.open(path, "rb") as archive:
|
||||||
data = archive.read()
|
data = archive.read()
|
||||||
logs = yaml.load(data)
|
logs = yaml.safe_load(data)
|
||||||
|
|
||||||
# we need to preload diffusions, sounds and tracks
|
# we need to preload diffusions, sounds and tracks
|
||||||
rels = {
|
rels = {
|
||||||
|
|
|
@ -84,7 +84,6 @@ class SoundStats:
|
||||||
self.stats = [SoxStats(self.path)]
|
self.stats = [SoxStats(self.path)]
|
||||||
position = 0
|
position = 0
|
||||||
length = self.stats[0].get("length")
|
length = self.stats[0].get("length")
|
||||||
print(self.stats, "-----")
|
|
||||||
if not self.sample_length:
|
if not self.sample_length:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import pytz
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.utils import timezone as tz
|
from django.utils import timezone as tz
|
||||||
|
|
||||||
|
@ -11,38 +12,36 @@ __all__ = ("AircoxMiddleware",)
|
||||||
class AircoxMiddleware(object):
|
class AircoxMiddleware(object):
|
||||||
"""Middleware used to get default info for the given website.
|
"""Middleware used to get default info for the given website.
|
||||||
|
|
||||||
Theses
|
It provide following request attributes:
|
||||||
|
- ``station``: current Station
|
||||||
|
|
||||||
This middleware must be set after the middleware
|
This middleware must be set after the middleware
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
timezone_session_key = "aircox.timezone"
|
||||||
|
|
||||||
def __init__(self, get_response):
|
def __init__(self, get_response):
|
||||||
self.get_response = get_response
|
self.get_response = get_response
|
||||||
|
|
||||||
def get_station(self, request):
|
def get_station(self, request):
|
||||||
"""Return station for the provided request."""
|
"""Return station for the provided request."""
|
||||||
expr = Q(default=True) | Q(hosts__contains=request.get_host())
|
host = request.get_host()
|
||||||
# case = Case(When(hosts__contains=request.get_host(), then=Value(0)),
|
expr = Q(default=True) | Q(hosts=host) | Q(hosts__contains=host + "\n")
|
||||||
# When(default=True, then=Value(32)))
|
|
||||||
return Station.objects.filter(expr).order_by("default").first()
|
return Station.objects.filter(expr).order_by("default").first()
|
||||||
# .annotate(resolve_priority=case) \
|
|
||||||
# .order_by('resolve_priority').first()
|
|
||||||
|
|
||||||
def init_timezone(self, request):
|
def init_timezone(self, request):
|
||||||
# note: later we can use http://freegeoip.net/ on user side if
|
# note: later we can use http://freegeoip.net/ on user side if
|
||||||
# required
|
# required
|
||||||
timezone = None
|
timezone = None
|
||||||
try:
|
try:
|
||||||
timezone = request.session.get("aircox.timezone")
|
timezone = request.session.get(self.timezone_session_key)
|
||||||
if timezone:
|
if timezone:
|
||||||
timezone = pytz.timezone(timezone)
|
timezone = ZoneInfo(timezone)
|
||||||
|
tz.activate(timezone)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if not timezone:
|
|
||||||
timezone = tz.get_current_timezone()
|
|
||||||
tz.activate(timezone)
|
|
||||||
|
|
||||||
def __call__(self, request):
|
def __call__(self, request):
|
||||||
self.init_timezone(request)
|
self.init_timezone(request)
|
||||||
request.station = self.get_station(request)
|
request.station = self.get_station(request)
|
||||||
|
|
|
@ -39,8 +39,10 @@ class DiffusionQuerySet(RerunQuerySet):
|
||||||
def date(self, date=None, order=True):
|
def date(self, date=None, order=True):
|
||||||
"""Diffusions occuring date."""
|
"""Diffusions occuring date."""
|
||||||
date = date or datetime.date.today()
|
date = date or datetime.date.today()
|
||||||
start = tz.datetime.combine(date, datetime.time())
|
start = tz.make_aware(tz.datetime.combine(date, datetime.time()))
|
||||||
end = tz.datetime.combine(date, datetime.time(23, 59, 59, 999))
|
end = tz.make_aware(
|
||||||
|
tz.datetime.combine(date, datetime.time(23, 59, 59, 999))
|
||||||
|
)
|
||||||
# start = tz.get_current_timezone().localize(start)
|
# start = tz.get_current_timezone().localize(start)
|
||||||
# end = tz.get_current_timezone().localize(end)
|
# end = tz.get_current_timezone().localize(end)
|
||||||
qs = self.filter(start__range=(start, end))
|
qs = self.filter(start__range=(start, end))
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import calendar
|
import calendar
|
||||||
|
import zoneinfo
|
||||||
|
|
||||||
import pytz
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils import timezone as tz
|
from django.utils import timezone as tz
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
|
@ -49,9 +49,9 @@ class Schedule(Rerun):
|
||||||
)
|
)
|
||||||
timezone = models.CharField(
|
timezone = models.CharField(
|
||||||
_("timezone"),
|
_("timezone"),
|
||||||
default=lambda: tz.get_current_timezone().zone,
|
default=lambda: tz.get_current_timezone().key,
|
||||||
max_length=100,
|
max_length=100,
|
||||||
choices=[(x, x) for x in pytz.all_timezones],
|
choices=[(x, x) for x in zoneinfo.available_timezones()],
|
||||||
help_text=_("timezone used for the date"),
|
help_text=_("timezone used for the date"),
|
||||||
)
|
)
|
||||||
duration = models.TimeField(
|
duration = models.TimeField(
|
||||||
|
@ -82,9 +82,7 @@ class Schedule(Rerun):
|
||||||
@cached_property
|
@cached_property
|
||||||
def tz(self):
|
def tz(self):
|
||||||
"""Pytz timezone of the schedule."""
|
"""Pytz timezone of the schedule."""
|
||||||
import pytz
|
return zoneinfo.ZoneInfo(self.timezone)
|
||||||
|
|
||||||
return pytz.timezone(self.timezone)
|
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def start(self):
|
def start(self):
|
||||||
|
@ -110,7 +108,7 @@ class Schedule(Rerun):
|
||||||
"""Return a datetime set to schedule's time for the provided date,
|
"""Return a datetime set to schedule's time for the provided date,
|
||||||
handling timezone (based on schedule's timezone)."""
|
handling timezone (based on schedule's timezone)."""
|
||||||
date = tz.datetime.combine(date, self.time)
|
date = tz.datetime.combine(date, self.time)
|
||||||
return self.tz.normalize(self.tz.localize(date))
|
return date.replace(tzinfo=self.tz)
|
||||||
|
|
||||||
def dates_of_month(self, date):
|
def dates_of_month(self, date):
|
||||||
"""Return normalized diffusion dates of provided date's month."""
|
"""Return normalized diffusion dates of provided date's month."""
|
||||||
|
|
|
@ -53,8 +53,10 @@ def page_post_save(sender, instance, created, *args, **kwargs):
|
||||||
def program_post_save(sender, instance, created, *args, **kwargs):
|
def program_post_save(sender, instance, created, *args, **kwargs):
|
||||||
"""Clean-up later diffusions when a program becomes inactive."""
|
"""Clean-up later diffusions when a program becomes inactive."""
|
||||||
if not instance.active:
|
if not instance.active:
|
||||||
Diffusion.object.program(instance).after(tz.now()).delete()
|
Diffusion.objects.program(instance).after(tz.now()).delete()
|
||||||
Episode.object.parent(instance).filter(diffusion__isnull=True).delete()
|
Episode.objects.parent(instance).filter(
|
||||||
|
diffusion__isnull=True
|
||||||
|
).delete()
|
||||||
|
|
||||||
cover = getattr(instance, "__initial_cover", None)
|
cover = getattr(instance, "__initial_cover", None)
|
||||||
if cover is None and instance.cover is not None:
|
if cover is None and instance.cover is not None:
|
||||||
|
|
|
@ -60,7 +60,7 @@ class Station(models.Model):
|
||||||
max_length=512,
|
max_length=512,
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text=_("specify one url per line"),
|
help_text=_("specify one domain per line, without 'http://' prefix"),
|
||||||
)
|
)
|
||||||
audio_streams = models.TextField(
|
audio_streams = models.TextField(
|
||||||
_("audio streams"),
|
_("audio streams"),
|
||||||
|
|
|
@ -121,7 +121,7 @@ class SpoofMixin:
|
||||||
if funcs is not None:
|
if funcs is not None:
|
||||||
self.funcs = funcs
|
self.funcs = funcs
|
||||||
|
|
||||||
def get_trace(self, name, args=False, kw=False):
|
def get_trace(self, name="__call__", args=False, kw=False):
|
||||||
"""Get a function call parameters.
|
"""Get a function call parameters.
|
||||||
|
|
||||||
:param str name: function name
|
:param str name: function name
|
||||||
|
@ -137,7 +137,7 @@ class SpoofMixin:
|
||||||
raise ValueError(f"{name} called multiple times.")
|
raise ValueError(f"{name} called multiple times.")
|
||||||
return self._get_trace(trace, args=args, kw=kw)
|
return self._get_trace(trace, args=args, kw=kw)
|
||||||
|
|
||||||
def get_traces(self, name, args=False, kw=False):
|
def get_traces(self, name="__call__", args=False, kw=False):
|
||||||
"""Get a tuple of all call parameters.
|
"""Get a tuple of all call parameters.
|
||||||
|
|
||||||
Parameters are the same as `get()`.
|
Parameters are the same as `get()`.
|
||||||
|
|
62
aircox/tests/admin/test_filters.py
Normal file
62
aircox/tests/admin/test_filters.py
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
from datetime import date, timedelta
|
||||||
|
|
||||||
|
from django.contrib.admin import filters as d_filters
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from ..conftest import req_factory
|
||||||
|
from aircox import models
|
||||||
|
from aircox.admin import filters
|
||||||
|
|
||||||
|
|
||||||
|
class FakeFilter(d_filters.FieldListFilter):
|
||||||
|
def __init__(self, field, request, params, model, model_admin, field_path):
|
||||||
|
self.field = field
|
||||||
|
self.request = request
|
||||||
|
self.params = params
|
||||||
|
self.model = model
|
||||||
|
self.model_admin = model_admin
|
||||||
|
self.field_path = field_path
|
||||||
|
|
||||||
|
|
||||||
|
class DateFieldFilter(filters.DateFieldFilter, FakeFilter):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
today = date.today()
|
||||||
|
tomorrow = date.today() + timedelta(days=1)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def req():
|
||||||
|
return req_factory.get("/test", {"pub_date__gte": today})
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def date_filter(req):
|
||||||
|
return DateFieldFilter(
|
||||||
|
models.Page._meta.get_field("pub_date"),
|
||||||
|
req,
|
||||||
|
{"pub_date__lte": tomorrow, "other_param": 13},
|
||||||
|
models.Page,
|
||||||
|
None,
|
||||||
|
"pub_date",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestDateFieldFilter:
|
||||||
|
def test___init__(self, date_filter):
|
||||||
|
assert date_filter.date_params == {"pub_date__lte": tomorrow}
|
||||||
|
|
||||||
|
date_filter.links = [
|
||||||
|
(str(link[0]), *list(link[1:])) for link in date_filter.links
|
||||||
|
]
|
||||||
|
assert date_filter.links == [
|
||||||
|
(str(_("None")), "pub_date__isnull", None, "1"),
|
||||||
|
(str(_("Exact")), "pub_date__date", date_filter.input_type),
|
||||||
|
(str(_("Since")), "pub_date__gte", date_filter.input_type),
|
||||||
|
(str(_("Until")), "pub_date__lte", date_filter.input_type),
|
||||||
|
]
|
||||||
|
assert date_filter.query_attrs == {
|
||||||
|
"pub_date__gte": today.strftime("%Y-%m-%d")
|
||||||
|
}
|
|
@ -2,6 +2,10 @@ from datetime import time, timedelta
|
||||||
import itertools
|
import itertools
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.test import RequestFactory
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from model_bakery import baker
|
from model_bakery import baker
|
||||||
|
|
||||||
|
@ -9,6 +13,21 @@ from aircox import models
|
||||||
from aircox.test import Interface
|
from aircox.test import Interface
|
||||||
|
|
||||||
|
|
||||||
|
req_factory = RequestFactory()
|
||||||
|
"""Request Factory used among different tests."""
|
||||||
|
|
||||||
|
|
||||||
|
settings.ALLOWED_HOSTS = list(settings.ALLOWED_HOSTS) + [
|
||||||
|
"sub.server.org",
|
||||||
|
"server.org",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def staff_user():
|
||||||
|
return baker.make(User, is_active=True, is_staff=True)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def logger():
|
def logger():
|
||||||
logger = Interface(
|
logger = Interface(
|
||||||
|
@ -18,8 +37,24 @@ def logger():
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def stations():
|
def station():
|
||||||
return baker.make(models.Station, _quantity=2)
|
return baker.make(
|
||||||
|
models.Station,
|
||||||
|
hosts="server.org",
|
||||||
|
audio_streams="stream 1\nstream 2",
|
||||||
|
default=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def sub_station():
|
||||||
|
"""Non-default station."""
|
||||||
|
return baker.make(models.Station, hosts="sub.server.org", default=False)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def stations(station, sub_station):
|
||||||
|
return [station, sub_station]
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
@ -28,7 +63,11 @@ def programs(stations):
|
||||||
itertools.chain(
|
itertools.chain(
|
||||||
*(
|
*(
|
||||||
baker.make(
|
baker.make(
|
||||||
models.Program, station=station, cover=None, _quantity=2
|
models.Program,
|
||||||
|
station=station,
|
||||||
|
cover=None,
|
||||||
|
status=models.Program.STATUS_PUBLISHED,
|
||||||
|
_quantity=2,
|
||||||
)
|
)
|
||||||
for station in stations
|
for station in stations
|
||||||
)
|
)
|
||||||
|
|
|
@ -24,7 +24,7 @@ class TestSchedule:
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_tz(self, schedules):
|
def test_tz(self, schedules):
|
||||||
for schedule in schedules:
|
for schedule in schedules:
|
||||||
assert schedule.timezone == schedule.tz.zone
|
assert schedule.timezone == schedule.tz.key
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_start(self, schedules):
|
def test_start(self, schedules):
|
||||||
|
@ -45,7 +45,7 @@ class TestSchedule:
|
||||||
def test_normalize(self, schedules):
|
def test_normalize(self, schedules):
|
||||||
for schedule in schedules:
|
for schedule in schedules:
|
||||||
dt = datetime.combine(schedule.date, schedule.time)
|
dt = datetime.combine(schedule.date, schedule.time)
|
||||||
assert schedule.normalize(dt).tzinfo.zone == schedule.timezone
|
assert schedule.normalize(dt).tzinfo.key == schedule.timezone
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_dates_of_month_ponctual(self):
|
def test_dates_of_month_ponctual(self):
|
||||||
|
@ -117,7 +117,7 @@ class TestSchedule:
|
||||||
assert dt.month == at.month
|
assert dt.month == at.month
|
||||||
assert dt.weekday() == schedule.date.weekday()
|
assert dt.weekday() == schedule.date.weekday()
|
||||||
assert dt.time() == schedule.time
|
assert dt.time() == schedule.time
|
||||||
assert dt.tzinfo.zone == schedule.timezone
|
assert dt.tzinfo.key == schedule.timezone
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_diffusions_of_month(self, sched_initials):
|
def test_diffusions_of_month(self, sched_initials):
|
||||||
|
|
45
aircox/tests/test_admin_site.py
Normal file
45
aircox/tests/test_admin_site.py
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
from django.urls import path, reverse
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from aircox import admin_site, urls as _urls
|
||||||
|
from .conftest import req_factory
|
||||||
|
|
||||||
|
|
||||||
|
# Just for code quality: urls module is required because we need some
|
||||||
|
# url resolvers to be registered in order to run tests.
|
||||||
|
_urls
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def site():
|
||||||
|
return admin_site.AdminSite()
|
||||||
|
|
||||||
|
|
||||||
|
class TestAdminSite:
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_each_context(self, site, staff_user):
|
||||||
|
req = req_factory.get("admin/test")
|
||||||
|
req.user = staff_user
|
||||||
|
context = site.each_context(req)
|
||||||
|
assert "programs" in context
|
||||||
|
assert "diffusions" in context
|
||||||
|
assert "comments" in context
|
||||||
|
|
||||||
|
def test_get_urls(self, site):
|
||||||
|
extra_url = path("test/path", lambda *_, **kw: _)
|
||||||
|
site.extra_urls.append(extra_url)
|
||||||
|
urls = site.get_urls()
|
||||||
|
assert extra_url in urls
|
||||||
|
|
||||||
|
def test_get_tools(self, site):
|
||||||
|
tools = site.get_tools()
|
||||||
|
tools = dict(tools)
|
||||||
|
assert tools == {
|
||||||
|
_("Statistics"): reverse("admin:tools-stats"),
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_route_view(self, site):
|
||||||
|
# TODO
|
||||||
|
pass
|
54
aircox/tests/test_converters.py
Normal file
54
aircox/tests/test_converters.py
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
from datetime import date
|
||||||
|
from django.utils.safestring import SafeString
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from aircox import converters
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def page_path_conv():
|
||||||
|
return converters.PagePathConverter()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def week_conv():
|
||||||
|
return converters.WeekConverter()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def date_conv():
|
||||||
|
return converters.DateConverter()
|
||||||
|
|
||||||
|
|
||||||
|
class TestPagePathConverter:
|
||||||
|
def test_to_python(self, page_path_conv):
|
||||||
|
val = "path_value"
|
||||||
|
result = page_path_conv.to_python(val)
|
||||||
|
assert result == "/" + val + "/"
|
||||||
|
|
||||||
|
def test_to_url(self, page_path_conv):
|
||||||
|
val = "/val"
|
||||||
|
result = page_path_conv.to_url(val)
|
||||||
|
assert isinstance(result, SafeString)
|
||||||
|
assert result == val[1:] + "/"
|
||||||
|
|
||||||
|
|
||||||
|
class TestWeekConverter:
|
||||||
|
def test_to_python(self, week_conv):
|
||||||
|
val = "2023/02"
|
||||||
|
assert week_conv.to_python(val) == date(2023, 1, 9)
|
||||||
|
|
||||||
|
def test_to_url(self, week_conv):
|
||||||
|
val = date(2023, 1, 10)
|
||||||
|
assert week_conv.to_url(val) == "2023/02"
|
||||||
|
|
||||||
|
|
||||||
|
class TestDateConverter:
|
||||||
|
def test_to_python(self, date_conv):
|
||||||
|
val = "2023/02/05"
|
||||||
|
assert date_conv.to_python(val) == date(2023, 2, 5)
|
||||||
|
|
||||||
|
def test_to_url(self, date_conv):
|
||||||
|
val = date(2023, 5, 10)
|
||||||
|
assert date_conv.to_url(val) == "2023/05/10"
|
|
@ -4,7 +4,6 @@ import django.utils.timezone as tz
|
||||||
__all__ = (
|
__all__ = (
|
||||||
"Redirect",
|
"Redirect",
|
||||||
"redirect",
|
"redirect",
|
||||||
"date_range",
|
|
||||||
"cast_date",
|
"cast_date",
|
||||||
"date_or_default",
|
"date_or_default",
|
||||||
"to_timedelta",
|
"to_timedelta",
|
||||||
|
@ -29,31 +28,17 @@ def redirect(url):
|
||||||
|
|
||||||
|
|
||||||
def str_to_date(value, sep="/"):
|
def str_to_date(value, sep="/"):
|
||||||
"""Return a date from the provided `value` string, formated as "yyyy/mm/dd"
|
"""Return a date from the provided `value` string, formated as
|
||||||
(or "dd/mm/yyyy" if `reverse` is True).
|
"yyyy/mm/dd".
|
||||||
|
|
||||||
Raises ValueError for incorrect value format.
|
:raises: ValueError for incorrect value format.
|
||||||
"""
|
"""
|
||||||
value = value.split(sep)[:3]
|
value = value.split(sep)[:3]
|
||||||
if len(value) < 3:
|
if len(value) < 3:
|
||||||
return ValueError("incorrect date format")
|
raise ValueError("incorrect date format")
|
||||||
return datetime.date(int(value[0]), int(value[1]), int(value[2]))
|
return datetime.date(int(value[0]), int(value[1]), int(value[2]))
|
||||||
|
|
||||||
|
|
||||||
def date_range(date, delta=None, **delta_kwargs):
|
|
||||||
"""Return a range of provided date such as `[date-delta, date+delta]`.
|
|
||||||
|
|
||||||
:param date: the reference date
|
|
||||||
:param delta: timedelta
|
|
||||||
:param **delta_kwargs: timedelta init arguments
|
|
||||||
|
|
||||||
Return a datetime range for a given day, as:
|
|
||||||
```(date, 0:0:0:0; date, 23:59:59:999)```.
|
|
||||||
"""
|
|
||||||
delta = tz.timedelta(**delta_kwargs) if delta is None else delta
|
|
||||||
return [date - delta, date + delta]
|
|
||||||
|
|
||||||
|
|
||||||
def cast_date(date, into=datetime.date):
|
def cast_date(date, into=datetime.date):
|
||||||
"""Cast a given date into the provided class' instance.
|
"""Cast a given date into the provided class' instance.
|
||||||
|
|
||||||
|
|
|
@ -48,9 +48,7 @@ class BaseView(TemplateResponseMixin, ContextMixin):
|
||||||
kwargs["sidebar_list_url"] = self.get_sidebar_url()
|
kwargs["sidebar_list_url"] = self.get_sidebar_url()
|
||||||
|
|
||||||
if "audio_streams" not in kwargs:
|
if "audio_streams" not in kwargs:
|
||||||
streams = self.station.audio_streams
|
kwargs["audio_streams"] = self.station.streams
|
||||||
streams = streams and streams.split("\n")
|
|
||||||
kwargs["audio_streams"] = streams
|
|
||||||
|
|
||||||
if "model" not in kwargs:
|
if "model" not in kwargs:
|
||||||
model = (
|
model = (
|
||||||
|
@ -63,7 +61,7 @@ class BaseView(TemplateResponseMixin, ContextMixin):
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
# FIXME: rename to sth like [Base]?StationAPIView
|
# FIXME: rename to sth like [Base]?StationAPIView/Mixin
|
||||||
class BaseAPIView:
|
class BaseAPIView:
|
||||||
@property
|
@property
|
||||||
def station(self):
|
def station(self):
|
||||||
|
|
|
@ -91,6 +91,8 @@ class AttachedToMixin:
|
||||||
return super().get_page()
|
return super().get_page()
|
||||||
|
|
||||||
|
|
||||||
|
# FIXME: django-filter provides filter mixin, but I don't remember why this is
|
||||||
|
# used.
|
||||||
class FiltersMixin:
|
class FiltersMixin:
|
||||||
"""Mixin integrating Django filters' filter set."""
|
"""Mixin integrating Django filters' filter set."""
|
||||||
|
|
||||||
|
|
|
@ -6,11 +6,11 @@ to:
|
||||||
- cancels Diffusions that have an archive but could not have been played;
|
- cancels Diffusions that have an archive but could not have been played;
|
||||||
- run Liquidsoap
|
- run Liquidsoap
|
||||||
"""
|
"""
|
||||||
|
from datetime import timezone
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from argparse import RawTextHelpFormatter
|
from argparse import RawTextHelpFormatter
|
||||||
|
|
||||||
import pytz
|
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from django.utils import timezone as tz
|
from django.utils import timezone as tz
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ from aircox_streamer.controllers import Monitor, Streamer
|
||||||
|
|
||||||
|
|
||||||
# force using UTC
|
# force using UTC
|
||||||
tz.activate(pytz.UTC)
|
tz.activate(timezone.UTC)
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
import pytz
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
sys.path.insert(1, os.path.dirname(os.path.realpath(__file__)))
|
sys.path.insert(1, os.path.dirname(os.path.realpath(__file__)))
|
||||||
|
@ -65,7 +65,7 @@ USE_I18N = True
|
||||||
USE_L10N = True
|
USE_L10N = True
|
||||||
USE_TZ = True
|
USE_TZ = True
|
||||||
|
|
||||||
timezone.activate(pytz.timezone(TIME_ZONE))
|
timezone.activate(ZoneInfo(TIME_ZONE))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import locale
|
import locale
|
||||||
|
|
|
@ -10,6 +10,7 @@ For Django settings see:
|
||||||
https://docs.djangoproject.com/en/3.1/topics/settings/
|
https://docs.djangoproject.com/en/3.1/topics/settings/
|
||||||
https://docs.djangoproject.com/en/3.1/ref/settings/
|
https://docs.djangoproject.com/en/3.1/ref/settings/
|
||||||
"""
|
"""
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
from .prod import *
|
from .prod import *
|
||||||
|
|
||||||
# FOR dev: from .dev import *
|
# FOR dev: from .dev import *
|
||||||
|
@ -20,7 +21,7 @@ LANGUAGE_CODE = "fr-BE"
|
||||||
LC_LOCALE = "fr_BE.UTF-8"
|
LC_LOCALE = "fr_BE.UTF-8"
|
||||||
TIME_ZONE = "Europe/Brussels"
|
TIME_ZONE = "Europe/Brussels"
|
||||||
|
|
||||||
timezone.activate(pytz.timezone(TIME_ZONE))
|
timezone.activate(ZoneInfo(TIME_ZONE))
|
||||||
|
|
||||||
# Secret key: you MUST put a consistent secret key. You can generate one
|
# Secret key: you MUST put a consistent secret key. You can generate one
|
||||||
# at https://djecrety.ir/
|
# at https://djecrety.ir/
|
||||||
|
|
6
notes.md
6
notes.md
|
@ -76,6 +76,6 @@ cms:
|
||||||
|
|
||||||
# For the next version:
|
# For the next version:
|
||||||
## Refactorisation
|
## Refactorisation
|
||||||
Move:
|
- move into `aircox_streamer`: `Log`, `Port`
|
||||||
- into `aircox_streamer`: `Log`, `Port`
|
- move into `aircox_cms`: `Page`, `NavItem`, `Category`, `StaticPage`, etc.
|
||||||
- into `aircox_cms`: `Page`, `NavItem`, `Category`, `StaticPage`, etc.
|
- use TextChoice and IntegerChoices in models fields enums
|
||||||
|
|
Loading…
Reference in New Issue
Block a user