diff --git a/aleksis/apps/chronos/models.py b/aleksis/apps/chronos/models.py index 293b62ba8f886a0689d79121421de7e346bb9473..0f9c5f9e409b53f15bab8b1e4c4674e4076ab88b 100644 --- a/aleksis/apps/chronos/models.py +++ b/aleksis/apps/chronos/models.py @@ -1,13 +1,15 @@ from __future__ import annotations -from datetime import date, datetime, timedelta +from datetime import date, datetime, timedelta, time from typing import Dict, Optional, Tuple, Union from django.core import validators from django.core.exceptions import ValidationError from django.db import models -from django.db.models import F, Q +from django.db.models import F, Max, Min, Q +from django.db.models.functions import Coalesce from django.http.request import QueryDict +from django.utils.decorators import classproperty from django.utils.translation import ugettext_lazy as _ from calendarweek.django import CalendarWeek, i18n_day_names_lazy, i18n_day_abbrs_lazy @@ -244,6 +246,30 @@ class TimePeriod(models.Model): return wanted_week[self.weekday] + @classproperty + def period_min(cls) -> int: + return cls.objects.aggregate(period__min=Coalesce(Min("period"), 1)).get("period__min") + + @classproperty + def period_max(cls) -> int: + return cls.objects.aggregate(period__max=Coalesce(Max("period"), 7)).get("period__max") + + @classproperty + def time_min(cls) -> Optional[time]: + return cls.objects.aggregate(Min("time_start")).get("time_start__min") + + @classproperty + def time_max(cls) -> Optional[time]: + return cls.objects.aggregate(Max("time_start")).get("time_start__max") + + @classproperty + def weekday_min(cls) -> int: + return cls.objects.aggregate(weekday__min=Coalesce(Min("weekday"), 0)).get("weekday__min") + + @classproperty + def weekday_max(cls) -> int: + return cls.objects.aggregate(weekday__max=Coalesce(Max("weekday"), 6)).get("weekday__max") + class Meta: unique_together = [["weekday", "period"]] ordering = ["weekday", "period"] diff --git a/aleksis/apps/chronos/util/min_max.py b/aleksis/apps/chronos/util/min_max.py deleted file mode 100644 index 4d5dd421443f36b3b2f01ce0a2ef74b6504a0b5e..0000000000000000000000000000000000000000 --- a/aleksis/apps/chronos/util/min_max.py +++ /dev/null @@ -1,19 +0,0 @@ -from django.db.models import Min, Max - -from aleksis.apps.chronos.models import TimePeriod - -# Determine overall first and last day and period -min_max = TimePeriod.objects.aggregate( - Min("period"), Max("period"), Min("weekday"), Max("weekday"), Min("time_start"), Max("time_end") -) - -period_min = min_max.get("period__min", 1) -period_max = min_max.get("period__max", 7) - -time_min = min_max.get("time_start__min", None) -time_max = min_max.get("time_end__max", None) - -weekday_min_ = min_max.get("weekday__min", 0) -weekday_max = min_max.get("weekday__max", 6) - - diff --git a/aleksis/apps/chronos/util/prev_next.py b/aleksis/apps/chronos/util/prev_next.py index c0a844dc40e728a17c98e6289def5bd69c1dd9b9..4f954c3082eb69e5c18afb12896c975ed07135b0 100644 --- a/aleksis/apps/chronos/util/prev_next.py +++ b/aleksis/apps/chronos/util/prev_next.py @@ -5,7 +5,7 @@ from calendarweek import CalendarWeek from django.urls import reverse from django.utils import timezone -from aleksis.apps.chronos.util.min_max import weekday_min_, weekday_max, time_max +from ..models import TimePeriod def get_next_relevant_day(day: Optional[date] = None, time: Optional[time] = None, prev: bool = False) -> date: @@ -15,23 +15,23 @@ def get_next_relevant_day(day: Optional[date] = None, time: Optional[time] = Non day = timezone.now().date() if time is not None and not prev: - if time > time_max: + if time > TimePeriod.time_max: day += timedelta(days=1) cw = CalendarWeek.from_date(day) - if day.weekday() > weekday_max: + if day.weekday() > TimePeriod.weekday_max: if prev: - day = cw[weekday_max] + day = cw[TimePeriod.weekday_max] else: cw += 1 - day = cw[weekday_min_] - elif day.weekday() < weekday_min_: + day = cw[TimePeriod.weekday_min] + elif day.weekday() < TimePeriod.weekday_min: if prev: cw -= 1 - day = cw[weekday_max] + day = cw[TimePeriod.weekday_max] else: - day = cw[weekday_min_] + day = cw[TimePeriod.weekday_min] return day diff --git a/aleksis/apps/chronos/views.py b/aleksis/apps/chronos/views.py index cd3acad87d356a9debafe3e30302514473f31baa..44fe5e7f670267dbd4ae8c5093b6420ecf5fcb9b 100644 --- a/aleksis/apps/chronos/views.py +++ b/aleksis/apps/chronos/views.py @@ -153,13 +153,13 @@ def timetable( ] = [lesson_period] # Fill in empty lessons - for period_num in range(period_min, period_max + 1): + for period_num in range(TimePeriod.period_min, TimePeriod.period_max + 1): # Fill in empty weekdays if period_num not in per_period.keys(): per_period[period_num] = {} # Fill in empty lessons on this workday - for weekday_num in range(weekday_min_, weekday_max + 1): + for weekday_num in range(TimePeriod.weekday_min, TimePeriod.weekday_max + 1): if weekday_num not in per_period[period_num].keys(): per_period[period_num][weekday_num] = [] @@ -169,10 +169,10 @@ def timetable( context["lesson_periods"] = OrderedDict(sorted(per_period.items())) context["periods"] = TimePeriod.get_times_dict() context["weekdays"] = dict( - TimePeriod.WEEKDAY_CHOICES[weekday_min_ : weekday_max + 1] + TimePeriod.WEEKDAY_CHOICES[TimePeriod.weekday_min : TimePeriod.weekday_max + 1] ) context["weekdays_short"] = dict( - TimePeriod.WEEKDAY_CHOICES_SHORT[weekday_min_ : weekday_max + 1] + TimePeriod.WEEKDAY_CHOICES_SHORT[TimePeriod.weekday_min : TimePeriod.weekday_max + 1] ) context["weeks"] = get_weeks_for_year(year=wanted_week.year) context["week"] = wanted_week @@ -266,7 +266,8 @@ def edit_substitution(request: HttpRequest, id_: int, week: int) -> HttpResponse date = wanted_week[lesson_period.period.weekday] return redirect( - "lessons_day_by_date", year=date.year, month=date.month, day=date.day + "lessons_day_by_date", + year=date.year, month=date.month, day=date.day ) context["edit_substitution_form"] = edit_substitution_form diff --git a/poetry.lock b/poetry.lock index 28e6962c2c88ad1487dfccbf777f814cff0d214a..8a2acb4de4976064953cbf323e304874f39bf496 100644 --- a/poetry.lock +++ b/poetry.lock @@ -23,11 +23,13 @@ django-image-cropping = "^1.2" django-impersonate = "^1.4" django-ipware = "^2.1" django-js-reverse = "^0.9.1" +django-jsonstore = "^0.4.1" django-maintenance-mode = "^0.14.0" django-material = "^1.6.0" django-menu-generator = "^1.0.4" django-middleware-global-request = "^0.1.2" -django-pwa = "^1.0.6" +django-polymorphic = "^2.1.2" +django-pwa = "rev 67cf917a081df3116968f684ebb28e4c076b2b50" django-sass-processor = "^0.8" django-settings-context-processor = "^0.2" django-tables2 = "^2.1" @@ -44,7 +46,7 @@ requests = "^2.22" [package.dependencies.django-constance] extras = ["database"] -version = "rev 590fa02eb30e377da0eda5cc3a84254b839176a7" +version = "^2.6.0" [package.dependencies.django-phonenumber-field] extras = ["phonenumbers"] @@ -110,7 +112,7 @@ description = "Utilities for working with calendar weeks in Python and Django" name = "calendarweek" optional = false python-versions = ">=3.7,<4.0" -version = "0.4.4" +version = "0.4.5" [package.extras] django = ["Django (>=2.2,<4.0)"] @@ -167,6 +169,9 @@ optional = false python-versions = "*" version = "5.0.6" +[package.dependencies] +six = "*" + [[package]] category = "main" description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." @@ -235,7 +240,7 @@ description = "Django admin CKEditor integration." name = "django-ckeditor" optional = false python-versions = "*" -version = "5.8.0" +version = "5.9.0" [package.dependencies] django-js-asset = ">=1.2.2" @@ -246,7 +251,7 @@ description = "Django live settings with pluggable backends, including Redis." name = "django-constance" optional = false python-versions = "*" -version = "2.5.0" +version = "2.6.0" [package.dependencies] [package.dependencies.django-picklefield] @@ -257,10 +262,6 @@ version = "*" database = ["django-picklefield"] redis = ["redis"] -[package.source] -reference = "590fa02eb30e377da0eda5cc3a84254b839176a7" -type = "git" -url = "https://github.com/jazzband/django-constance" [[package]] category = "main" description = "A configurable set of panels that display various debug information about the current request/response." @@ -279,7 +280,7 @@ description = "Yet another Django audit log app, hopefully the simplest one." name = "django-easy-audit" optional = false python-versions = "*" -version = "1.2rc1" +version = "1.2.1rc1" [package.dependencies] beautifulsoup4 = "*" @@ -367,6 +368,18 @@ version = "0.9.1" [package.dependencies] Django = ">=1.5" +[[package]] +category = "main" +description = "Expose JSONField data as a virtual django model fields." +name = "django-jsonstore" +optional = false +python-versions = "*" +version = "0.4.1" + +[package.dependencies] +Django = ">=1.11" +six = "*" + [[package]] category = "main" description = "django-maintenance-mode shows a 503 error page when maintenance-mode is on." @@ -459,7 +472,7 @@ description = "Pickled object field for Django" name = "django-picklefield" optional = false python-versions = "*" -version = "2.0" +version = "2.1.1" [package.dependencies] Django = ">=1.11" @@ -467,6 +480,17 @@ Django = ">=1.11" [package.extras] tests = ["tox"] +[[package]] +category = "main" +description = "Seamless polymorphic inheritance for Django models" +name = "django-polymorphic" +optional = false +python-versions = "*" +version = "2.1.2" + +[package.dependencies] +Django = ">=1.11" + [[package]] category = "main" description = "A Django app to include a manifest.json and Service Worker instance to enable progressive web app behavior" @@ -478,6 +502,10 @@ version = "1.0.6" [package.dependencies] django = ">=1.8" +[package.source] +reference = "67cf917a081df3116968f684ebb28e4c076b2b50" +type = "git" +url = "https://github.com/Natureshadow/django-pwa" [[package]] category = "main" description = "Render a particular block from a template to a string." @@ -791,7 +819,7 @@ category = "main" description = "YAML parser and emitter for Python" name = "pyyaml" optional = false -python-versions = "*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" version = "5.3" [[package]] @@ -876,7 +904,7 @@ description = "Fast, Extensible Progress Meter" name = "tqdm" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*" -version = "4.41.1" +version = "4.42.0" [package.extras] dev = ["py-make (>=0.1.0)", "twine", "argopt", "pydoc-markdown"] @@ -887,7 +915,7 @@ description = "Twilio API client and TwiML generator" name = "twilio" optional = false python-versions = "*" -version = "6.35.2" +version = "6.35.3" [package.dependencies] PyJWT = ">=1.4.2" @@ -903,8 +931,8 @@ category = "main" description = "HTTP library with thread-safe connection pooling, file post, and more." name = "urllib3" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4" -version = "1.25.7" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +version = "1.25.8" [package.extras] brotli = ["brotlipy (>=0.6.0)"] @@ -943,8 +971,8 @@ beautifulsoup4 = [ {file = "beautifulsoup4-4.8.2.tar.gz", hash = "sha256:05fd825eb01c290877657a56df4c6e4c311b3965bda790c613a3d6fb01a5462a"}, ] calendarweek = [ - {file = "calendarweek-0.4.4-py3-none-any.whl", hash = "sha256:6510a42015558f140ed6677e79efbb45d8bf87ccded069db4026283eb639a256"}, - {file = "calendarweek-0.4.4.tar.gz", hash = "sha256:02f092ec54ebe162dc9f3614de6efbf3d7fb35115e8ca5d62e99d65c342f5732"}, + {file = "calendarweek-0.4.5-py3-none-any.whl", hash = "sha256:b35fcc087073969d017cede62a7295bcd714a1304bcb4c4e2b0f23acb0265fb1"}, + {file = "calendarweek-0.4.5.tar.gz", hash = "sha256:5b1788ca435022f9348fc81a718974e51dd85d080f9aa3dad717df70a1bc6e1f"}, ] certifi = [ {file = "certifi-2019.11.28-py2.py3-none-any.whl", hash = "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3"}, @@ -989,17 +1017,19 @@ django-bulk-update = [ {file = "django_bulk_update-2.2.0-py2.py3-none-any.whl", hash = "sha256:49a403392ae05ea872494d74fb3dfa3515f8df5c07cc277c3dc94724c0ee6985"}, ] django-ckeditor = [ - {file = "django-ckeditor-5.8.0.tar.gz", hash = "sha256:46fc9c7346ea36183dc0cea350f98704f8b04c4722b7fe4fb18baf8ae20423fb"}, - {file = "django_ckeditor-5.8.0-py2.py3-none-any.whl", hash = "sha256:a59bab13f4481318f8a048b1b0aef5c7da768a6352dcfb9ba0e77d91fbb9462a"}, + {file = "django-ckeditor-5.9.0.tar.gz", hash = "sha256:e4d112851a72c5bf8b586e1c674d34084cab16d28f2553ad15cc770d1e9639c7"}, + {file = "django_ckeditor-5.9.0-py2.py3-none-any.whl", hash = "sha256:71c3c7bb46b0cbfb9712ef64af0d2a406eab233f44ecd7c42c24bdfa39ae3bde"}, +] +django-constance = [ + {file = "django-constance-2.6.0.tar.gz", hash = "sha256:12d827f9d5552ee39884fb6fb356f231f32b1ab8958acc715e3d1a6ecf913653"}, ] -django-constance = [] django-debug-toolbar = [ {file = "django-debug-toolbar-2.1.tar.gz", hash = "sha256:24c157bc6c0e1648e0a6587511ecb1b007a00a354ce716950bff2de12693e7a8"}, {file = "django_debug_toolbar-2.1-py3-none-any.whl", hash = "sha256:77cfba1d6e91b9bc3d36dc7dc74a9bb80be351948db5f880f2562a0cbf20b6c5"}, ] django-easy-audit = [ - {file = "django-easy-audit-1.2rc1.tar.gz", hash = "sha256:80f82fa4006290dcd6589a345e75de1c780de49d38218050eedd9048c54b647d"}, - {file = "django_easy_audit-1.2rc1-py3-none-any.whl", hash = "sha256:fb9c5ec3e90f0900302448d3648acc11da6d6b3d35d13d77eab917ab8c813d77"}, + {file = "django-easy-audit-1.2.1rc1.tar.gz", hash = "sha256:a127264dbfef4aac17bfa74439487540ad41e47ff4c067d3d77bfad82fd23bc5"}, + {file = "django_easy_audit-1.2.1rc1-py3-none-any.whl", hash = "sha256:00e9a9bc063ad73120fe399f2e7bc216af5fc32186dc5eccad09e2c4cd10abc1"}, ] django-filter = [ {file = "django-filter-2.2.0.tar.gz", hash = "sha256:c3deb57f0dd7ff94d7dce52a047516822013e2b441bed472b722a317658cfd14"}, @@ -1030,6 +1060,9 @@ django-js-reverse = [ {file = "django-js-reverse-0.9.1.tar.gz", hash = "sha256:2a392d169f44e30b883c30dfcfd917a14167ce8fe196c99d2385b31c90d77aa0"}, {file = "django_js_reverse-0.9.1-py2.py3-none-any.whl", hash = "sha256:8134c2ab6307c945edfa90671ca65e85d6c1754d48566bdd6464be259cc80c30"}, ] +django-jsonstore = [ + {file = "django-jsonstore-0.4.1.tar.gz", hash = "sha256:d6e42152af3f924e4657c99e80144ba9a6410799256f6134b5a4e9fa4282ec10"}, +] django-maintenance-mode = [ {file = "django-maintenance-mode-0.14.0.tar.gz", hash = "sha256:f3fef1760fdcda5e0bf6c2966aadc77eea6f328580a9c751920daba927281a68"}, {file = "django_maintenance_mode-0.14.0-py2-none-any.whl", hash = "sha256:b4cc24a469ed10897826a28f05d64e6166a58d130e4940ac124ce198cd4cc778"}, @@ -1057,13 +1090,14 @@ django-phonenumber-field = [ {file = "django_phonenumber_field-3.0.1-py3-none-any.whl", hash = "sha256:1ab19f723928582fed412bd9844221fa4ff466276d8526b8b4a9913ee1487c5e"}, ] django-picklefield = [ - {file = "django-picklefield-2.0.tar.gz", hash = "sha256:f1733a8db1b6046c0d7d738e785f9875aa3c198215de11993463a9339aa4ea24"}, - {file = "django_picklefield-2.0-py2.py3-none-any.whl", hash = "sha256:9052f2dcf4882c683ce87b4356f29b4d014c0dad645b6906baf9f09571f52bc8"}, + {file = "django-picklefield-2.1.1.tar.gz", hash = "sha256:67a5e156343e3b032cac2f65565f0faa81635a99c7da74b0f07a0f5db467b646"}, + {file = "django_picklefield-2.1.1-py2.py3-none-any.whl", hash = "sha256:e03cb181b7161af38ad6b573af127e4fe9b7cc2c455b42c1ec43eaad525ade0a"}, ] -django-pwa = [ - {file = "django-pwa-1.0.6.tar.gz", hash = "sha256:b3f1ad0c5241fae4c7505423540de4db93077d7c88416ff6d2af545ffe209f34"}, - {file = "django_pwa-1.0.6-py3-none-any.whl", hash = "sha256:9306105fcb637ae16fea6527be4b147d45fd53db85efb1d4f61dfea6bf793e56"}, +django-polymorphic = [ + {file = "django-polymorphic-2.1.2.tar.gz", hash = "sha256:6e08a76c91066635ccb7ef3ebbe9a0ad149febae6b30be2579716ec16d3c6461"}, + {file = "django_polymorphic-2.1.2-py2.py3-none-any.whl", hash = "sha256:0a25058e95e5e99fe0beeabb8f4734effe242d7b5b77dca416fba9fd3062da6a"}, ] +django-pwa = [] django-render-block = [ {file = "django_render_block-0.6-py2.py3-none-any.whl", hash = "sha256:95c7dc9610378a10e0c4a10d8364ec7307210889afccd6a67a6aaa0fd599bd4d"}, ] @@ -1276,15 +1310,15 @@ toml = [ {file = "toml-0.10.0.tar.gz", hash = "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c"}, ] tqdm = [ - {file = "tqdm-4.41.1-py2.py3-none-any.whl", hash = "sha256:efab950cf7cc1e4d8ee50b2bb9c8e4a89f8307b49e0b2c9cfef3ec4ca26655eb"}, - {file = "tqdm-4.41.1.tar.gz", hash = "sha256:4789ccbb6fc122b5a6a85d512e4e41fc5acad77216533a6f2b8ce51e0f265c23"}, + {file = "tqdm-4.42.0-py2.py3-none-any.whl", hash = "sha256:01464d5950e9a07a8e463c2767883d9616c099c6502f6c7ef4e2e11d3065bd35"}, + {file = "tqdm-4.42.0.tar.gz", hash = "sha256:5865f5fef9d739864ff341ddaa69894173ebacedb1aaafcf014de56343d01d5c"}, ] twilio = [ - {file = "twilio-6.35.2.tar.gz", hash = "sha256:a086443642c0e1f13c8f8f087b426ca81ec883efbe496d8279180a49bb9287bc"}, + {file = "twilio-6.35.3.tar.gz", hash = "sha256:4474fa87fde5ea5526e296be782d1b06fc6722f9e4698a503c47b5c2f8b70ea9"}, ] urllib3 = [ - {file = "urllib3-1.25.7-py2.py3-none-any.whl", hash = "sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293"}, - {file = "urllib3-1.25.7.tar.gz", hash = "sha256:f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745"}, + {file = "urllib3-1.25.8-py2.py3-none-any.whl", hash = "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc"}, + {file = "urllib3-1.25.8.tar.gz", hash = "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"}, ] yubiotp = [ {file = "YubiOTP-0.2.2.post1-py2.py3-none-any.whl", hash = "sha256:7e281801b24678f4bda855ce8ab975a7688a912f5a6cb22b6c2b16263a93cbd2"},