diff --git a/biscuit/apps/chronos/__init__.py b/biscuit/apps/chronos/__init__.py index 284a66af967d25ed09a77eb95c261e503d6f3974..59161f0c996b7c7ccb9f09d7e337a530a65c1fe8 100644 --- a/biscuit/apps/chronos/__init__.py +++ b/biscuit/apps/chronos/__init__.py @@ -1,8 +1,8 @@ import pkg_resources try: - __version__ = pkg_resources.get_distribution('BiscuIT-App-Chronos').version + __version__ = pkg_resources.get_distribution("BiscuIT-App-Chronos").version except Exception: - __version__ = 'unknown' + __version__ = "unknown" -default_app_config = 'biscuit.apps.chronos.apps.ChronosConfig' +default_app_config = "biscuit.apps.chronos.apps.ChronosConfig" diff --git a/biscuit/apps/chronos/apps.py b/biscuit/apps/chronos/apps.py index 301742481cdc8207bde82cc28d148661d41fd062..939cc6dd011786cb36291d773068b1c236e4a76c 100644 --- a/biscuit/apps/chronos/apps.py +++ b/biscuit/apps/chronos/apps.py @@ -2,5 +2,5 @@ from biscuit.core.util.apps import AppConfig class ChronosConfig(AppConfig): - name = 'biscuit.apps.chronos' - verbose_name = 'BiscuIT - Chronos (Timetables)' + name = "biscuit.apps.chronos" + verbose_name = "BiscuIT - Chronos (Timetables)" diff --git a/biscuit/apps/chronos/forms.py b/biscuit/apps/chronos/forms.py index 666eb0e6c12fcd064d37980ff333e91e1a4ec242..8ee42c3aff9219766bbd4d365c7ea2d174bd4090 100644 --- a/biscuit/apps/chronos/forms.py +++ b/biscuit/apps/chronos/forms.py @@ -1,31 +1,51 @@ from django import forms from django.db.models import Count from django.utils.translation import ugettext_lazy as _ + from django_select2.forms import ModelSelect2MultipleWidget, Select2Widget -from biscuit.core.models import Person, Group +from biscuit.core.models import Group, Person -from .models import Room, LessonSubstitution, Subject, LessonPeriod +from .models import LessonPeriod, LessonSubstitution, Room, Subject class SelectForm(forms.Form): group = forms.ModelChoiceField( - queryset=Group.objects.annotate(lessons_count=Count('lessons')).filter(lessons_count__gt=0), - label=_('Group'), required=False, widget=Select2Widget) + queryset=Group.objects.annotate(lessons_count=Count("lessons")).filter( + lessons_count__gt=0 + ), + label=_("Group"), + required=False, + widget=Select2Widget, + ) teacher = forms.ModelChoiceField( - queryset=Person.objects.annotate(lessons_count=Count( - 'lessons_as_teacher')).filter(lessons_count__gt=0), - label=_('Teacher'), required=False, widget=Select2Widget) + queryset=Person.objects.annotate( + lessons_count=Count("lessons_as_teacher") + ).filter(lessons_count__gt=0), + label=_("Teacher"), + required=False, + widget=Select2Widget, + ) room = forms.ModelChoiceField( - queryset=Room.objects.annotate(lessons_count=Count( - 'lesson_periods')).filter(lessons_count__gt=0), - label=_('Room'), required=False, widget=Select2Widget) + queryset=Room.objects.annotate(lessons_count=Count("lesson_periods")).filter( + lessons_count__gt=0 + ), + label=_("Room"), + required=False, + widget=Select2Widget, + ) class LessonSubstitutionForm(forms.ModelForm): class Meta: model = LessonSubstitution - fields = ['week', 'lesson_period', 'subject', 'teachers', 'room', 'cancelled'] + fields = ["week", "lesson_period", "subject", "teachers", "room", "cancelled"] widgets = { - 'teachers': ModelSelect2MultipleWidget(search_fields=['first_name__icontains', 'last_name__icontains', 'short_name__icontains']) + "teachers": ModelSelect2MultipleWidget( + search_fields=[ + "first_name__icontains", + "last_name__icontains", + "short_name__icontains", + ] + ) } diff --git a/biscuit/apps/chronos/menus.py b/biscuit/apps/chronos/menus.py index 11851fe6cc21a3c84404b76b99a3663745486cd5..38db3e98b469ce573a4138612e727b3f95e37c3c 100644 --- a/biscuit/apps/chronos/menus.py +++ b/biscuit/apps/chronos/menus.py @@ -1,29 +1,32 @@ from django.utils.translation import ugettext_lazy as _ MENUS = { - 'NAV_MENU_CORE': [ + "NAV_MENU_CORE": [ { - 'name': _('Timetables'), - 'url': '#', - 'root': True, - 'validators': ['menu_generator.validators.is_authenticated', 'biscuit.core.util.core_helpers.has_person'], - 'submenu': [ + "name": _("Timetables"), + "url": "#", + "root": True, + "validators": [ + "menu_generator.validators.is_authenticated", + "biscuit.core.util.core_helpers.has_person", + ], + "submenu": [ { - 'name': _('Timetable'), - 'url': 'timetable', - 'validators': ['menu_generator.validators.is_authenticated'] + "name": _("Timetable"), + "url": "timetable", + "validators": ["menu_generator.validators.is_authenticated"], }, { - 'name': _('Daily lessons'), - 'url': 'lessons_day', - 'validators': ['menu_generator.validators.is_authenticated'] + "name": _("Daily lessons"), + "url": "lessons_day", + "validators": ["menu_generator.validators.is_authenticated"], }, { - 'name': _('Substitutions'), - 'url': 'substitutions', - 'validators': ['menu_generator.validators.is_authenticated'] - } - ] + "name": _("Substitutions"), + "url": "substitutions", + "validators": ["menu_generator.validators.is_authenticated"], + }, + ], } ] } diff --git a/biscuit/apps/chronos/migrations/0001_initial.py b/biscuit/apps/chronos/migrations/0001_initial.py index f66c967080e791a1264bfcabe78fa362da00afd0..6ee00a6cbda2fe97d5cf47c96ee32ba2658d8c56 100644 --- a/biscuit/apps/chronos/migrations/0001_initial.py +++ b/biscuit/apps/chronos/migrations/0001_initial.py @@ -1,10 +1,11 @@ # Generated by Django 2.2.5 on 2019-09-03 18:30 -import biscuit.apps.chronos.util -import biscuit.core.util.core_helpers import django.core.validators -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models + +import biscuit.apps.chronos.util +import biscuit.core.util.core_helpers class Migration(migrations.Migration): @@ -12,123 +13,335 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('core', '0001_initial'), + ("core", "0001_initial"), ] operations = [ migrations.CreateModel( - name='Lesson', + name="Lesson", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('date_start', models.DateField(null=True, verbose_name='Effective start date of lesson')), - ('date_end', models.DateField(null=True, verbose_name='Effective end date of lesson')), - ('groups', models.ManyToManyField(related_name='lessons', to='core.Group')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "date_start", + models.DateField( + null=True, verbose_name="Effective start date of lesson" + ), + ), + ( + "date_end", + models.DateField( + null=True, verbose_name="Effective end date of lesson" + ), + ), + ( + "groups", + models.ManyToManyField(related_name="lessons", to="core.Group"), + ), ], - options={ - 'ordering': ['date_start'], - }, + options={"ordering": ["date_start"],}, ), migrations.CreateModel( - name='LessonPeriod', + name="LessonPeriod", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('lesson', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='lesson_periods', to='chronos.Lesson')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "lesson", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="lesson_periods", + to="chronos.Lesson", + ), + ), ], options={ - 'ordering': ['lesson__date_start', 'period__weekday', 'period__period'], + "ordering": ["lesson__date_start", "period__weekday", "period__period"], }, ), migrations.CreateModel( - name='TimePeriod', + name="TimePeriod", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('weekday', models.PositiveSmallIntegerField(choices=[(0, 'Sunday'), (1, 'Monday'), (2, 'Tuesday'), (3, 'Wednesday'), (4, 'Thursday'), (5, 'Friday'), (6, 'Saturday')], verbose_name='Week day')), - ('period', models.PositiveSmallIntegerField(verbose_name='Number of period')), - ('time_start', models.TimeField(verbose_name='Time the period starts')), - ('time_end', models.TimeField(verbose_name='Time the period ends')), - ('school', models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='core.School')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "weekday", + models.PositiveSmallIntegerField( + choices=[ + (0, "Sunday"), + (1, "Monday"), + (2, "Tuesday"), + (3, "Wednesday"), + (4, "Thursday"), + (5, "Friday"), + (6, "Saturday"), + ], + verbose_name="Week day", + ), + ), + ( + "period", + models.PositiveSmallIntegerField(verbose_name="Number of period"), + ), + ("time_start", models.TimeField(verbose_name="Time the period starts")), + ("time_end", models.TimeField(verbose_name="Time the period ends")), + ( + "school", + models.ForeignKey( + default=1, + on_delete=django.db.models.deletion.CASCADE, + to="core.School", + ), + ), ], options={ - 'ordering': ['weekday', 'period'], - 'unique_together': {('school', 'weekday', 'period')}, + "ordering": ["weekday", "period"], + "unique_together": {("school", "weekday", "period")}, }, ), migrations.CreateModel( - name='Subject', + name="Subject", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('abbrev', models.CharField(max_length=10, verbose_name='Abbreviation of subject in timetable')), - ('name', models.CharField(max_length=30, verbose_name='Long name of subject')), - ('colour_fg', models.CharField(blank=True, max_length=7, validators=[django.core.validators.RegexValidator('#[0-9A-F]{6}')], verbose_name='Foreground colour in timetable')), - ('colour_bg', models.CharField(blank=True, max_length=7, validators=[django.core.validators.RegexValidator('#[0-9A-F]{6}')], verbose_name='Background colour in timetable')), - ('school', models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='core.School')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "abbrev", + models.CharField( + max_length=10, + verbose_name="Abbreviation of subject in timetable", + ), + ), + ( + "name", + models.CharField( + max_length=30, verbose_name="Long name of subject" + ), + ), + ( + "colour_fg", + models.CharField( + blank=True, + max_length=7, + validators=[ + django.core.validators.RegexValidator("#[0-9A-F]{6}") + ], + verbose_name="Foreground colour in timetable", + ), + ), + ( + "colour_bg", + models.CharField( + blank=True, + max_length=7, + validators=[ + django.core.validators.RegexValidator("#[0-9A-F]{6}") + ], + verbose_name="Background colour in timetable", + ), + ), + ( + "school", + models.ForeignKey( + default=1, + on_delete=django.db.models.deletion.CASCADE, + to="core.School", + ), + ), ], options={ - 'ordering': ['name', 'abbrev'], - 'unique_together': {('school', 'abbrev'), ('school', 'name')}, + "ordering": ["name", "abbrev"], + "unique_together": {("school", "abbrev"), ("school", "name")}, }, ), migrations.CreateModel( - name='Room', + name="Room", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('short_name', models.CharField(max_length=10, verbose_name='Short name, e.g. room number')), - ('name', models.CharField(max_length=30, verbose_name='Long name')), - ('school', models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='core.School')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "short_name", + models.CharField( + max_length=10, verbose_name="Short name, e.g. room number" + ), + ), + ("name", models.CharField(max_length=30, verbose_name="Long name")), + ( + "school", + models.ForeignKey( + default=1, + on_delete=django.db.models.deletion.CASCADE, + to="core.School", + ), + ), ], options={ - 'ordering': ['name', 'short_name'], - 'unique_together': {('school', 'name'), ('school', 'short_name')}, + "ordering": ["name", "short_name"], + "unique_together": {("school", "name"), ("school", "short_name")}, }, ), migrations.CreateModel( - name='LessonSubstitution', + name="LessonSubstitution", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('week', models.IntegerField(default=biscuit.apps.chronos.util.CalendarWeek.current_week, verbose_name='Week')), - ('lesson_period', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='substitutions', to='chronos.LessonPeriod')), - ('room', models.ForeignKey(null=True, blank=True, on_delete=django.db.models.deletion.CASCADE, to='chronos.Room', verbose_name='Room')), - ('school', models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='core.School')), - ('subject', models.ForeignKey(null=True, blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='lesson_substitutions', to='chronos.Subject', verbose_name='Subject')), - ('teachers', models.ManyToManyField(blank=True, related_name='lesson_substitutions', to='core.Person')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "week", + models.IntegerField( + default=biscuit.apps.chronos.util.CalendarWeek.current_week, + verbose_name="Week", + ), + ), + ( + "lesson_period", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="substitutions", + to="chronos.LessonPeriod", + ), + ), + ( + "room", + models.ForeignKey( + null=True, + blank=True, + on_delete=django.db.models.deletion.CASCADE, + to="chronos.Room", + verbose_name="Room", + ), + ), + ( + "school", + models.ForeignKey( + default=1, + on_delete=django.db.models.deletion.CASCADE, + to="core.School", + ), + ), + ( + "subject", + models.ForeignKey( + null=True, + blank=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="lesson_substitutions", + to="chronos.Subject", + verbose_name="Subject", + ), + ), + ( + "teachers", + models.ManyToManyField( + blank=True, + related_name="lesson_substitutions", + to="core.Person", + ), + ), ], options={ - 'ordering': ['lesson_period__lesson__date_start', 'week', 'lesson_period__period__weekday', 'lesson_period__period__period'], + "ordering": [ + "lesson_period__lesson__date_start", + "week", + "lesson_period__period__weekday", + "lesson_period__period__period", + ], }, ), migrations.AddField( - model_name='lessonperiod', - name='period', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='lesson_periods', to='chronos.TimePeriod'), + model_name="lessonperiod", + name="period", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="lesson_periods", + to="chronos.TimePeriod", + ), ), migrations.AddField( - model_name='lessonperiod', - name='room', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='lesson_periods', to='chronos.Room'), + model_name="lessonperiod", + name="room", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="lesson_periods", + to="chronos.Room", + ), ), migrations.AddField( - model_name='lessonperiod', - name='school', - field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='core.School'), + model_name="lessonperiod", + name="school", + field=models.ForeignKey( + default=1, on_delete=django.db.models.deletion.CASCADE, to="core.School" + ), ), migrations.AddField( - model_name='lesson', - name='periods', - field=models.ManyToManyField(related_name='lessons', through='chronos.LessonPeriod', to='chronos.TimePeriod'), + model_name="lesson", + name="periods", + field=models.ManyToManyField( + related_name="lessons", + through="chronos.LessonPeriod", + to="chronos.TimePeriod", + ), ), migrations.AddField( - model_name='lesson', - name='school', - field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='core.School'), + model_name="lesson", + name="school", + field=models.ForeignKey( + default=1, on_delete=django.db.models.deletion.CASCADE, to="core.School" + ), ), migrations.AddField( - model_name='lesson', - name='subject', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='lessons', to='chronos.Subject'), + model_name="lesson", + name="subject", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="lessons", + to="chronos.Subject", + ), ), migrations.AddField( - model_name='lesson', - name='teachers', - field=models.ManyToManyField(related_name='lessons', to='core.Person'), + model_name="lesson", + name="teachers", + field=models.ManyToManyField(related_name="lessons", to="core.Person"), ), ] diff --git a/biscuit/apps/chronos/migrations/0002_db_indexes.py b/biscuit/apps/chronos/migrations/0002_db_indexes.py index 49be25b9145eb526bcfa3c6aec74415fc6d6034b..f6ee2fe1cf4fd021c8c1c00b75f6d349b3f3978c 100644 --- a/biscuit/apps/chronos/migrations/0002_db_indexes.py +++ b/biscuit/apps/chronos/migrations/0002_db_indexes.py @@ -6,25 +6,31 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('core', '0001_initial'), - ('chronos', '0001_initial'), + ("core", "0001_initial"), + ("chronos", "0001_initial"), ] operations = [ migrations.AlterUniqueTogether( - name='lessonsubstitution', - unique_together={('school', 'lesson_period', 'week')}, + name="lessonsubstitution", + unique_together={("school", "lesson_period", "week")}, ), migrations.AddIndex( - model_name='lesson', - index=models.Index(fields=['date_start', 'date_end'], name='chronos_les_date_st_5ecc62_idx'), + model_name="lesson", + index=models.Index( + fields=["date_start", "date_end"], name="chronos_les_date_st_5ecc62_idx" + ), ), migrations.AddIndex( - model_name='lessonperiod', - index=models.Index(fields=['lesson', 'period'], name='chronos_les_lesson__05250e_idx'), + model_name="lessonperiod", + index=models.Index( + fields=["lesson", "period"], name="chronos_les_lesson__05250e_idx" + ), ), migrations.AddIndex( - model_name='timeperiod', - index=models.Index(fields=['time_start', 'time_end'], name='chronos_tim_time_st_491e4c_idx'), + model_name="timeperiod", + index=models.Index( + fields=["time_start", "time_end"], name="chronos_tim_time_st_491e4c_idx" + ), ), ] diff --git a/biscuit/apps/chronos/migrations/0003_substitution_cancelled_or_subject.py b/biscuit/apps/chronos/migrations/0003_substitution_cancelled_or_subject.py index 7f53974affeec61b9164028cfa3b4447345aae5c..a7486e4702fadab910c7e59e230ffbffca20ea30 100644 --- a/biscuit/apps/chronos/migrations/0003_substitution_cancelled_or_subject.py +++ b/biscuit/apps/chronos/migrations/0003_substitution_cancelled_or_subject.py @@ -6,17 +6,22 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('chronos', '0002_db_indexes'), + ("chronos", "0002_db_indexes"), ] operations = [ migrations.AddField( - model_name='lessonsubstitution', - name='cancelled', + model_name="lessonsubstitution", + name="cancelled", field=models.BooleanField(default=False), ), migrations.AddConstraint( - model_name='lessonsubstitution', - constraint=models.CheckConstraint(check=models.Q(('cancelled', True), ('subject__isnull', False), _negated=True), name='either_substituted_or_cancelled'), + model_name="lessonsubstitution", + constraint=models.CheckConstraint( + check=models.Q( + ("cancelled", True), ("subject__isnull", False), _negated=True + ), + name="either_substituted_or_cancelled", + ), ), ] diff --git a/biscuit/apps/chronos/migrations/0004_room_name_not_unique.py b/biscuit/apps/chronos/migrations/0004_room_name_not_unique.py index 241ea2db5ff8d9d81ff49924543b4e9ec840a4d2..c862d32086b74b71c9a1715f53993a82623e3e48 100644 --- a/biscuit/apps/chronos/migrations/0004_room_name_not_unique.py +++ b/biscuit/apps/chronos/migrations/0004_room_name_not_unique.py @@ -6,12 +6,11 @@ from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('chronos', '0003_substitution_cancelled_or_subject'), + ("chronos", "0003_substitution_cancelled_or_subject"), ] operations = [ migrations.AlterUniqueTogether( - name='room', - unique_together={('school', 'short_name')}, + name="room", unique_together={("school", "short_name")}, ), ] diff --git a/biscuit/apps/chronos/models.py b/biscuit/apps/chronos/models.py index f3594aaf6fcbd051a517aa4c47d642b6521c0022..a380c6770c4930eda235058ae82173137e7abb48 100644 --- a/biscuit/apps/chronos/models.py +++ b/biscuit/apps/chronos/models.py @@ -22,10 +22,11 @@ class LessonPeriodManager(models.Manager): def get_queryset(self): """ Ensures all related lesson data is loaded as well. """ - return super().get_queryset().select_related( - 'lesson', 'lesson__subject', 'period', 'room' - ).prefetch_related( - 'lesson__groups', 'lesson__teachers', 'substitutions' + return ( + super() + .get_queryset() + .select_related("lesson", "lesson__subject", "period", "room") + .prefetch_related("lesson__groups", "lesson__teachers", "substitutions") ) @@ -41,8 +42,8 @@ class LessonPeriodQuerySet(models.QuerySet): """ Filter for all lessons within a calendar week. """ return self.within_dates( - wanted_week[0] + timedelta(days=1) * (models.F('period__weekday') - 1), - wanted_week[0] + timedelta(days=1) * (models.F('period__weekday') - 1) + wanted_week[0] + timedelta(days=1) * (models.F("period__weekday") - 1), + wanted_week[0] + timedelta(days=1) * (models.F("period__weekday") - 1), ).annotate_week(wanted_week) def on_day(self, day: date): @@ -50,9 +51,11 @@ class LessonPeriodQuerySet(models.QuerySet): week, weekday = week_weekday_from_date(day) - return self.within_dates(day, day).filter( - period__weekday=weekday - ).annotate_week(week) + return ( + self.within_dates(day, day) + .filter(period__weekday=weekday) + .annotate_week(week) + ) def at_time(self, when: Optional[datetime] = None): """ Filter for the lessons taking place at a certain point in time. """ @@ -65,32 +68,39 @@ class LessonPeriodQuerySet(models.QuerySet): lesson__date_end__gte=now.date(), period__weekday=now.isoweekday(), period__time_start__lte=now.time(), - period__time_end__gte=now.time() + period__time_end__gte=now.time(), ).annotate_week(week) def filter_participant(self, person: Union[Person, int]): """ Filter for all lessons a participant (student) attends. """ return self.filter( - Q(lesson__groups__members=person) | Q(lesson__groups__parent_groups__members=person)) + Q(lesson__groups__members=person) + | Q(lesson__groups__parent_groups__members=person) + ) def filter_group(self, group: Union[Group, int]): """ Filter for all lessons a group (class) regularly attends. """ return self.filter( - Q(lesson__groups=group) | Q(lesson__groups__parent_groups=group)) + Q(lesson__groups=group) | Q(lesson__groups__parent_groups=group) + ) def filter_teacher(self, teacher: Union[Person, int]): """ Filter for all lessons given by a certain teacher. """ return self.filter( - Q(substitutions__teachers=teacher, substitutions__week=models.F('_week')) | Q(lesson__teachers=teacher)) + Q(substitutions__teachers=teacher, substitutions__week=models.F("_week")) + | Q(lesson__teachers=teacher) + ) def filter_room(self, room: Union[Room, int]): """ Filter for all lessons taking part in a certain room. """ return self.filter( - Q(substitutions__room=room, substitutions__week=models.F('_week')) | Q(room=room)) + Q(substitutions__room=room, substitutions__week=models.F("_week")) + | Q(room=room) + ) def annotate_week(self, week: Union[CalendarWeek, int]): """ Annotate all lessons in the QuerySet with the number of the provided calendar week. """ @@ -100,9 +110,7 @@ class LessonPeriodQuerySet(models.QuerySet): else: week_num = week - return self.annotate( - _week=models.Value(week_num, models.IntegerField()) - ) + return self.annotate(_week=models.Value(week_num, models.IntegerField())) def next(self, reference: LessonPeriod, offset: Optional[int] = 1) -> LessonPeriod: """ Get another lesson in an ordered set of lessons. @@ -112,7 +120,7 @@ class LessonPeriodQuerySet(models.QuerySet): previous lesson can be selected. """ - index = list(self.values_list('id', flat=True)).index(reference.id) + index = list(self.values_list("id", flat=True)).index(reference.id) next_index = index + offset if next_index > self.count() - 1: @@ -132,35 +140,40 @@ class LessonPeriodQuerySet(models.QuerySet): All three fields are filtered, in order. """ - if query_data.get('group', None): - return self.filter_group(int(query_data['group'])) - if query_data.get('teacher', None): - return self.filter_teacher(int(query_data['teacher'])) - if query_data.get('room', None): - return self.filter_room(int(query_data['room'])) + if query_data.get("group", None): + return self.filter_group(int(query_data["group"])) + if query_data.get("teacher", None): + return self.filter_teacher(int(query_data["teacher"])) + if query_data.get("room", None): + return self.filter_room(int(query_data["room"])) class TimePeriod(models.Model): WEEKDAY_CHOICES = [ - (0, _('Sunday')), - (1, _('Monday')), - (2, _('Tuesday')), - (3, _('Wednesday')), - (4, _('Thursday')), - (5, _('Friday')), - (6, _('Saturday')) + (0, _("Sunday")), + (1, _("Monday")), + (2, _("Tuesday")), + (3, _("Wednesday")), + (4, _("Thursday")), + (5, _("Friday")), + (6, _("Saturday")), ] - weekday = models.PositiveSmallIntegerField(verbose_name=_( - 'Week day'), choices=WEEKDAY_CHOICES) - period = models.PositiveSmallIntegerField( - verbose_name=_('Number of period')) + weekday = models.PositiveSmallIntegerField( + verbose_name=_("Week day"), choices=WEEKDAY_CHOICES + ) + period = models.PositiveSmallIntegerField(verbose_name=_("Number of period")) - time_start = models.TimeField(verbose_name=_('Time the period starts')) - time_end = models.TimeField(verbose_name=_('Time the period ends')) + time_start = models.TimeField(verbose_name=_("Time the period starts")) + time_end = models.TimeField(verbose_name=_("Time the period ends")) def __str__(self) -> str: - return '%s, %d. period (%s - %s)' % (self.weekday, self.period, self.time_start, self.time_end) + return "%s, %d. period (%s - %s)" % ( + self.weekday, + self.period, + self.time_start, + self.time_end, + ) @classmethod def get_times_dict(cls) -> Dict[int, Tuple[datetime, datetime]]: @@ -175,113 +188,143 @@ class TimePeriod(models.Model): wanted_week = week else: year = date.today().year - week_number = week or getattr(self, '_week', None) or CalendarWeek().week + week_number = week or getattr(self, "_week", None) or CalendarWeek().week if week_number < self.school.current_term.date_start.isocalendar()[1]: year += 1 wanted_week = CalendarWeek(year=year, week=week_number) - return wanted_week[self.weekday-1] + return wanted_week[self.weekday - 1] class Meta: - unique_together = [['weekday', 'period']] - ordering = ['weekday', 'period'] - indexes = [models.Index(fields=['time_start', 'time_end'])] + unique_together = [["weekday", "period"]] + ordering = ["weekday", "period"] + indexes = [models.Index(fields=["time_start", "time_end"])] class Subject(models.Model): - abbrev = models.CharField(verbose_name=_( - 'Abbreviation of subject in timetable'), max_length=10, unique=True) - name = models.CharField(verbose_name=_( - 'Long name of subject'), max_length=30, unique=True) - - colour_fg = models.CharField(verbose_name=_('Foreground colour in timetable'), blank=True, validators=[ - validators.RegexValidator(r'#[0-9A-F]{6}')], max_length=7) - colour_bg = models.CharField(verbose_name=_('Background colour in timetable'), blank=True, validators=[ - validators.RegexValidator(r'#[0-9A-F]{6}')], max_length=7) + abbrev = models.CharField( + verbose_name=_("Abbreviation of subject in timetable"), + max_length=10, + unique=True, + ) + name = models.CharField( + verbose_name=_("Long name of subject"), max_length=30, unique=True + ) + + colour_fg = models.CharField( + verbose_name=_("Foreground colour in timetable"), + blank=True, + validators=[validators.RegexValidator(r"#[0-9A-F]{6}")], + max_length=7, + ) + colour_bg = models.CharField( + verbose_name=_("Background colour in timetable"), + blank=True, + validators=[validators.RegexValidator(r"#[0-9A-F]{6}")], + max_length=7, + ) def __str__(self) -> str: - return '%s - %s' % (self.abbrev, self.name) + return "%s - %s" % (self.abbrev, self.name) class Meta: - ordering = ['name', 'abbrev'] + ordering = ["name", "abbrev"] class Room(models.Model): - short_name = models.CharField(verbose_name=_( - 'Short name, e.g. room number'), max_length=10, unique=True) - name = models.CharField(verbose_name=_('Long name'), - max_length=30) + short_name = models.CharField( + verbose_name=_("Short name, e.g. room number"), max_length=10, unique=True + ) + name = models.CharField(verbose_name=_("Long name"), max_length=30) def __str__(self) -> str: - return '%s (%s)' % (self.name, self.short_name) + return "%s (%s)" % (self.name, self.short_name) class Meta: - ordering = ['name', 'short_name'] + ordering = ["name", "short_name"] class Lesson(models.Model): subject = models.ForeignKey( - 'Subject', on_delete=models.CASCADE, related_name='lessons') - teachers = models.ManyToManyField('core.Person', related_name='lessons_as_teacher') + "Subject", on_delete=models.CASCADE, related_name="lessons" + ) + teachers = models.ManyToManyField("core.Person", related_name="lessons_as_teacher") periods = models.ManyToManyField( - 'TimePeriod', related_name='lessons', through='LessonPeriod') - groups = models.ManyToManyField('core.Group', related_name='lessons') + "TimePeriod", related_name="lessons", through="LessonPeriod" + ) + groups = models.ManyToManyField("core.Group", related_name="lessons") - date_start = models.DateField(verbose_name=_( - 'Effective start date of lesson'), null=True) - date_end = models.DateField(verbose_name=_( - 'Effective end date of lesson'), null=True) + date_start = models.DateField( + verbose_name=_("Effective start date of lesson"), null=True + ) + date_end = models.DateField( + verbose_name=_("Effective end date of lesson"), null=True + ) @property - def teacher_names(self, sep: Optional[str] = ', ') -> str: + def teacher_names(self, sep: Optional[str] = ", ") -> str: return sep.join([teacher.full_name for teacher in self.teachers.all()]) @property - def group_names(self, sep: Optional[str] = ', ') -> str: + def group_names(self, sep: Optional[str] = ", ") -> str: return sep.join([group.short_name for group in self.groups.all()]) def get_calendar_week(self, week: int): year = self.date_start.year - if week < int(self.date_start.strftime('%V')): + if week < int(self.date_start.strftime("%V")): year += 1 return CalendarWeek(year=year, week=week) class Meta: - ordering = ['date_start'] - indexes = [models.Index(fields=['date_start', 'date_end'])] + ordering = ["date_start"] + indexes = [models.Index(fields=["date_start", "date_end"])] class LessonSubstitution(models.Model): - week = models.IntegerField(verbose_name=_('Week'), - default=CalendarWeek.current_week) + week = models.IntegerField( + verbose_name=_("Week"), default=CalendarWeek.current_week + ) - lesson_period = models.ForeignKey( - 'LessonPeriod', models.CASCADE, 'substitutions') + lesson_period = models.ForeignKey("LessonPeriod", models.CASCADE, "substitutions") subject = models.ForeignKey( - 'Subject', on_delete=models.CASCADE, - related_name='lesson_substitutions', null=True, blank=True, verbose_name=_('Subject')) - teachers = models.ManyToManyField('core.Person', - related_name='lesson_substitutions', blank=True) - room = models.ForeignKey('Room', models.CASCADE, null=True, blank=True, verbose_name=_('Room')) + "Subject", + on_delete=models.CASCADE, + related_name="lesson_substitutions", + null=True, + blank=True, + verbose_name=_("Subject"), + ) + teachers = models.ManyToManyField( + "core.Person", related_name="lesson_substitutions", blank=True + ) + room = models.ForeignKey( + "Room", models.CASCADE, null=True, blank=True, verbose_name=_("Room") + ) cancelled = models.BooleanField(default=False) def clean(self) -> None: if self.subject and self.cancelled: - raise ValidationError(_('Lessons can only be either substituted or cancelled.')) + raise ValidationError( + _("Lessons can only be either substituted or cancelled.") + ) class Meta: - unique_together = [['lesson_period', 'week']] - ordering = ['lesson_period__lesson__date_start', 'week', - 'lesson_period__period__weekday', 'lesson_period__period__period'] + unique_together = [["lesson_period", "week"]] + ordering = [ + "lesson_period__lesson__date_start", + "week", + "lesson_period__period__weekday", + "lesson_period__period__period", + ] constraints = [ models.CheckConstraint( check=~Q(cancelled=True, subject__isnull=False), - name='either_substituted_or_cancelled' + name="either_substituted_or_cancelled", ) ] @@ -289,13 +332,17 @@ class LessonSubstitution(models.Model): class LessonPeriod(models.Model, ExtensibleModel): objects = LessonPeriodManager.from_queryset(LessonPeriodQuerySet)() - lesson = models.ForeignKey('Lesson', models.CASCADE, related_name='lesson_periods') - period = models.ForeignKey('TimePeriod', models.CASCADE, related_name='lesson_periods') + lesson = models.ForeignKey("Lesson", models.CASCADE, related_name="lesson_periods") + period = models.ForeignKey( + "TimePeriod", models.CASCADE, related_name="lesson_periods" + ) - room = models.ForeignKey('Room', models.CASCADE, null=True, related_name='lesson_periods') + room = models.ForeignKey( + "Room", models.CASCADE, null=True, related_name="lesson_periods" + ) def get_substitution(self, week: Optional[int] = None) -> LessonSubstitution: - wanted_week = week or getattr(self, '_week', None) or CalendarWeek().week + wanted_week = week or getattr(self, "_week", None) or CalendarWeek().week # We iterate over all substitutions because this can make use of # prefetching when this model is loaded from outside, in contrast @@ -323,17 +370,20 @@ class LessonPeriod(models.Model, ExtensibleModel): else: return self.room - def get_teacher_names(self, sep: Optional[str] = ', ') -> str: + def get_teacher_names(self, sep: Optional[str] = ", ") -> str: return sep.join([teacher.full_name for teacher in self.get_teachers().all()]) def get_groups(self) -> models.query.QuerySet: return self.lesson.groups def __str__(self) -> str: - return '%s, %d., %s, %s' % (self.period.get_weekday_display(), self.period.period, - ', '.join(list(self.lesson.groups.values_list('short_name', flat=True))), - self.lesson.subject.name) + return "%s, %d., %s, %s" % ( + self.period.get_weekday_display(), + self.period.period, + ", ".join(list(self.lesson.groups.values_list("short_name", flat=True))), + self.lesson.subject.name, + ) class Meta: - ordering = ['lesson__date_start', 'period__weekday', 'period__period'] - indexes = [models.Index(fields=['lesson', 'period'])] + ordering = ["lesson__date_start", "period__weekday", "period__period"] + indexes = [models.Index(fields=["lesson", "period"])] diff --git a/biscuit/apps/chronos/tables.py b/biscuit/apps/chronos/tables.py index f05d6aa13cbae38d939b7b038a62132ab571a599..fd663e9cbf6476e4b78671a42857d145c6902113 100644 --- a/biscuit/apps/chronos/tables.py +++ b/biscuit/apps/chronos/tables.py @@ -3,43 +3,59 @@ from __future__ import annotations from typing import Optional from django.utils.translation import ugettext_lazy as _ + import django_tables2 as tables from django_tables2.utils import A from .models import LessonPeriod -def _css_class_from_lesson_state(record: Optional[LessonPeriod] = None, table: Optional[LessonsTable] = None) -> str: +def _css_class_from_lesson_state( + record: Optional[LessonPeriod] = None, table: Optional[LessonsTable] = None +) -> str: if record.get_substitution(record._week): if record.get_substitution(record._week).cancelled: - return 'table-danger' + return "table-danger" else: - return 'table-warning' + return "table-warning" else: - return '' + return "" class LessonsTable(tables.Table): class Meta: - attrs = {'class': 'table table-striped table-bordered table-hover table-responsive-xl'} - row_attrs = {'class': _css_class_from_lesson_state} - - period__period = tables.Column(accessor='period.period') - lesson__groups = tables.Column(accessor='lesson.group_names', verbose_name=_('Groups')) - lesson__teachers = tables.Column(accessor='lesson.teacher_names', verbose_name=_('Teachers')) - lesson__subject = tables.Column(accessor='lesson.subject') - room = tables.Column(accessor='room') + attrs = { + "class": "table table-striped table-bordered table-hover table-responsive-xl" + } + row_attrs = {"class": _css_class_from_lesson_state} + + period__period = tables.Column(accessor="period.period") + lesson__groups = tables.Column( + accessor="lesson.group_names", verbose_name=_("Groups") + ) + lesson__teachers = tables.Column( + accessor="lesson.teacher_names", verbose_name=_("Teachers") + ) + lesson__subject = tables.Column(accessor="lesson.subject") + room = tables.Column(accessor="room") edit_substitution = tables.LinkColumn( - 'edit_substitution', args=[A('id'), A('_week')], text=_('Substitution')) + "edit_substitution", args=[A("id"), A("_week")], text=_("Substitution") + ) class SubstitutionsTable(tables.Table): class Meta: - attrs = {'class': 'table table-striped table-bordered table-hover table-responsive-xl'} - - lesson_period = tables.Column(verbose_name=_('Lesson')) - lesson__groups = tables.Column(accessor='lesson_period.lesson.group_names', verbose_name=_('Groups')) - lesson__teachers = tables.Column(accessor='lesson_period.get_teacher_names', verbose_name=_('Teachers')) - lesson__subject = tables.Column(accessor='subject') - room = tables.Column(accessor='room') - cancelled = tables.BooleanColumn(accessor='cancelled', verbose_name=_('Cancelled')) + attrs = { + "class": "table table-striped table-bordered table-hover table-responsive-xl" + } + + lesson_period = tables.Column(verbose_name=_("Lesson")) + lesson__groups = tables.Column( + accessor="lesson_period.lesson.group_names", verbose_name=_("Groups") + ) + lesson__teachers = tables.Column( + accessor="lesson_period.get_teacher_names", verbose_name=_("Teachers") + ) + lesson__subject = tables.Column(accessor="subject") + room = tables.Column(accessor="room") + cancelled = tables.BooleanColumn(accessor="cancelled", verbose_name=_("Cancelled")) diff --git a/biscuit/apps/chronos/templatetags/week_helpers.py b/biscuit/apps/chronos/templatetags/week_helpers.py index 20e19d222ae6ae2a95326dfb20e9e1aab287fcc9..529d253ae7b9a66879598c794650820358aac29c 100644 --- a/biscuit/apps/chronos/templatetags/week_helpers.py +++ b/biscuit/apps/chronos/templatetags/week_helpers.py @@ -4,8 +4,7 @@ from typing import Optional, Union from django import template from django.db.models.query import QuerySet -from ..util import CalendarWeek, week_weekday_to_date, week_period_to_date - +from ..util import CalendarWeek, week_period_to_date, week_weekday_to_date register = template.Library() diff --git a/biscuit/apps/chronos/urls.py b/biscuit/apps/chronos/urls.py index f567ab33cefb5996f357ba1d768a63b8071e3d1c..c3c8157dd3bc9f28367d51c917ed1494adb88376 100644 --- a/biscuit/apps/chronos/urls.py +++ b/biscuit/apps/chronos/urls.py @@ -2,14 +2,25 @@ from django.urls import path from . import views - urlpatterns = [ - path('timetable', views.timetable, name='timetable'), - path('timetable/<int:year>/<int:week>', views.timetable, name='timetable_by_week'), - path('lessons', views.lessons_day, name='lessons_day'), - path('lessons/<when>', views.lessons_day, name='lessons_day_by_date'), - path('lessons/<int:id_>/<int:week>/substition', views.edit_substitution, name='edit_substitution'), - path('lessons/<int:id_>/<int:week>/substition/delete', views.delete_substitution, name='delete_substitution'), - path('substitutions', views.substitutions, name='substitutions'), - path('substitutions/<int:year>/<int:week>', views.substitutions, name='substitutions_by_week') + path("timetable", views.timetable, name="timetable"), + path("timetable/<int:year>/<int:week>", views.timetable, name="timetable_by_week"), + path("lessons", views.lessons_day, name="lessons_day"), + path("lessons/<when>", views.lessons_day, name="lessons_day_by_date"), + path( + "lessons/<int:id_>/<int:week>/substition", + views.edit_substitution, + name="edit_substitution", + ), + path( + "lessons/<int:id_>/<int:week>/substition/delete", + views.delete_substitution, + name="delete_substitution", + ), + path("substitutions", views.substitutions, name="substitutions"), + path( + "substitutions/<int:year>/<int:week>", + views.substitutions, + name="substitutions_by_week", + ), ] diff --git a/biscuit/apps/chronos/util.py b/biscuit/apps/chronos/util.py index ccbeaf144034d1a449f559995cfcf78cc48d2276..300838bc8d44b8b14a1f8313f26dfe63420ad1da 100644 --- a/biscuit/apps/chronos/util.py +++ b/biscuit/apps/chronos/util.py @@ -20,7 +20,7 @@ class CalendarWeek: def from_date(cls, when: date): """ Get the calendar week by a date object (the week this date is in). """ - return cls(year=when.year, week=int(when.strftime('%V'))) + return cls(year=when.year, week=int(when.strftime("%V"))) @classmethod def current_week(cls) -> int: @@ -33,7 +33,7 @@ class CalendarWeek: """ Get all calendar weeks within a date range. """ if start > end: - raise ValueError('End date must be after start date.') + raise ValueError("End date must be after start date.") current = start weeks = [] @@ -49,22 +49,30 @@ class CalendarWeek: if not self.year: self.year = today.year if not self.week: - self.week = int(today.strftime('%V')) + self.week = int(today.strftime("%V")) def __str__(self) -> str: - return '%s %d (%s %s %s)' % (_('Calendar Week'), self.week, self[0], _('to'), self[-1]) + return "%s %d (%s %s %s)" % ( + _("Calendar Week"), + self.week, + self[0], + _("to"), + self[-1], + ) def __len__(self) -> int: return 7 def __getitem__(self, n: int) -> date: if n < -7 or n > 6: - raise IndexError('Week day %d is out of range.' % n) + raise IndexError("Week day %d is out of range." % n) if n < 0: n += 7 - return datetime.strptime('%d-%d-%d' % (self.year, self.week, n + 1), '%G-%V-%u').date() + return datetime.strptime( + "%d-%d-%d" % (self.year, self.week, n + 1), "%G-%V-%u" + ).date() def __contains__(self, day: date) -> bool: return self.__class__.form_date(day) == self diff --git a/biscuit/apps/chronos/views.py b/biscuit/apps/chronos/views.py index f388891463742628250e87889d6c822c35a864f5..dce61cff3b1c7404dc51f8b9f36b86f16e7e7034 100644 --- a/biscuit/apps/chronos/views.py +++ b/biscuit/apps/chronos/views.py @@ -1,5 +1,5 @@ -from datetime import date, datetime, timedelta from collections import OrderedDict +from datetime import date, datetime, timedelta from typing import Optional from django.contrib.auth.decorators import login_required @@ -14,14 +14,16 @@ from django_tables2 import RequestConfig from biscuit.core.decorators import admin_required from biscuit.core.util import messages -from .forms import SelectForm, LessonSubstitutionForm -from .models import LessonPeriod, TimePeriod, LessonSubstitution -from .util import CalendarWeek +from .forms import LessonSubstitutionForm, SelectForm +from .models import LessonPeriod, LessonSubstitution, TimePeriod from .tables import LessonsTable, SubstitutionsTable +from .util import CalendarWeek @login_required -def timetable(request: HttpRequest, year: Optional[int] = None, week: Optional[int] = None) -> HttpResponse: +def timetable( + request: HttpRequest, year: Optional[int] = None, week: Optional[int] = None +) -> HttpResponse: context = {} if year and week: @@ -32,60 +34,77 @@ def timetable(request: HttpRequest, year: Optional[int] = None, week: Optional[i lesson_periods = LessonPeriod.objects.in_week(wanted_week) # Incrementally filter lesson periods by GET parameters - if request.GET.get('group', None) or request.GET.get('teacher', None) or request.GET.get('room', None): + if ( + request.GET.get("group", None) + or request.GET.get("teacher", None) + or request.GET.get("room", None) + ): lesson_periods = lesson_periods.filter_from_query(request.GET) else: # Redirect to a selected view if no filter provided if request.user.person: if request.user.person.primary_group: - return redirect(reverse('timetable') + '?group=%d' % request.user.person.primary_group.pk) + return redirect( + reverse("timetable") + + "?group=%d" % request.user.person.primary_group.pk + ) elif lesson_periods.filter(lesson__teachers=request.user.person).exists(): - return redirect(reverse('timetable') + '?teacher=%d' % request.user.person.pk) + return redirect( + reverse("timetable") + "?teacher=%d" % request.user.person.pk + ) # Regroup lesson periods per weekday per_day = {} for lesson_period in lesson_periods: - per_day.setdefault(lesson_period.period.weekday, - {})[lesson_period.period.period] = lesson_period + per_day.setdefault(lesson_period.period.weekday, {})[ + lesson_period.period.period + ] = lesson_period # Determine overall first and last day and period min_max = TimePeriod.objects.aggregate( - Min('period'), Max('period'), - Min('weekday'), Max('weekday')) + Min("period"), Max("period"), Min("weekday"), Max("weekday") + ) # Fill in empty lessons - for weekday_num in range(min_max.get('weekday__min', 0), - min_max.get('weekday__max', 6) + 1): + for weekday_num in range( + min_max.get("weekday__min", 0), min_max.get("weekday__max", 6) + 1 + ): # Fill in empty weekdays if weekday_num not in per_day.keys(): per_day[weekday_num] = {} # Fill in empty lessons on this workday - for period_num in range(min_max.get('period__min', 1), - min_max.get('period__max', 7) + 1): + for period_num in range( + min_max.get("period__min", 1), min_max.get("period__max", 7) + 1 + ): if period_num not in per_day[weekday_num].keys(): per_day[weekday_num][period_num] = None # Order this weekday by periods - per_day[weekday_num] = OrderedDict( - sorted(per_day[weekday_num].items())) + per_day[weekday_num] = OrderedDict(sorted(per_day[weekday_num].items())) # Add a form to filter the view select_form = SelectForm(request.GET or None) - context['current_head'] = _('Timetable') - context['lesson_periods'] = OrderedDict(sorted(per_day.items())) - context['periods'] = TimePeriod.get_times_dict() - context['weekdays'] = dict(TimePeriod.WEEKDAY_CHOICES) - context['week'] = wanted_week - context['select_form'] = select_form + context["current_head"] = _("Timetable") + context["lesson_periods"] = OrderedDict(sorted(per_day.items())) + context["periods"] = TimePeriod.get_times_dict() + context["weekdays"] = dict(TimePeriod.WEEKDAY_CHOICES) + context["week"] = wanted_week + context["select_form"] = select_form week_prev = wanted_week - 1 week_next = wanted_week + 1 - context['url_prev'] = '%s?%s' % (reverse('timetable_by_week', args=[week_prev.year, week_prev.week]), request.GET.urlencode()) - context['url_next'] = '%s?%s' % (reverse('timetable_by_week', args=[week_next.year, week_next.week]), request.GET.urlencode()) + context["url_prev"] = "%s?%s" % ( + reverse("timetable_by_week", args=[week_prev.year, week_prev.week]), + request.GET.urlencode(), + ) + context["url_next"] = "%s?%s" % ( + reverse("timetable_by_week", args=[week_next.year, week_next.week]), + request.GET.urlencode(), + ) - return render(request, 'chronos/tt_week.html', context) + return render(request, "chronos/tt_week.html", context) @login_required @@ -93,7 +112,7 @@ def lessons_day(request: HttpRequest, when: Optional[str] = None) -> HttpRespons context = {} if when: - day = datetime.strptime(when, '%Y-%m-%d').date() + day = datetime.strptime(when, "%Y-%m-%d").date() else: day = date.today() @@ -104,17 +123,21 @@ def lessons_day(request: HttpRequest, when: Optional[str] = None) -> HttpRespons lessons_table = LessonsTable(lesson_periods.all()) RequestConfig(request).configure(lessons_table) - context['current_head'] = _('Lessons %s') % (day) - context['lessons_table'] = lessons_table - context['day'] = day - context['lesson_periods'] = lesson_periods + context["current_head"] = _("Lessons %s") % (day) + context["lessons_table"] = lessons_table + context["day"] = day + context["lesson_periods"] = lesson_periods day_prev = day - timedelta(days=1) day_next = day + timedelta(days=1) - context['url_prev'] = reverse('lessons_day_by_date', args=[day_prev.strftime('%Y-%m-%d')]) - context['url_next'] = reverse('lessons_day_by_date', args=[day_next.strftime('%Y-%m-%d')]) + context["url_prev"] = reverse( + "lessons_day_by_date", args=[day_prev.strftime("%Y-%m-%d")] + ) + context["url_next"] = reverse( + "lessons_day_by_date", args=[day_next.strftime("%Y-%m-%d")] + ) - return render(request, 'chronos/lessons_day.html', context) + return render(request, "chronos/lessons_day.html", context) @admin_required @@ -125,26 +148,33 @@ def edit_substitution(request: HttpRequest, id_: int, week: int) -> HttpResponse wanted_week = lesson_period.lesson.get_calendar_week(week) lesson_substitution = LessonSubstitution.objects.filter( - week=wanted_week.week, lesson_period=lesson_period).first() + week=wanted_week.week, lesson_period=lesson_period + ).first() if lesson_substitution: edit_substitution_form = LessonSubstitutionForm( - request.POST or None, instance=lesson_substitution) + request.POST or None, instance=lesson_substitution + ) else: edit_substitution_form = LessonSubstitutionForm( - request.POST or None, initial={'week': wanted_week.week, 'lesson_period': lesson_period}) + request.POST or None, + initial={"week": wanted_week.week, "lesson_period": lesson_period}, + ) - context['substitution'] = lesson_substitution + context["substitution"] = lesson_substitution - if request.method == 'POST': + if request.method == "POST": if edit_substitution_form.is_valid(): edit_substitution_form.save(commit=True) - messages.success(request, _('The substitution has been saved.')) - return redirect('lessons_day_by_date', when=wanted_week[lesson_period.period.weekday - 1].strftime('%Y-%m-%d')) + messages.success(request, _("The substitution has been saved.")) + return redirect( + "lessons_day_by_date", + when=wanted_week[lesson_period.period.weekday - 1].strftime("%Y-%m-%d"), + ) - context['edit_substitution_form'] = edit_substitution_form + context["edit_substitution_form"] = edit_substitution_form - return render(request, 'chronos/edit_substitution.html', context) + return render(request, "chronos/edit_substitution.html", context) @admin_required @@ -158,11 +188,16 @@ def delete_substitution(request: HttpRequest, id_: int, week: int) -> HttpRespon week=wanted_week.week, lesson_period=lesson_period ).delete() - messages.success(request, _('The substitution has been deleted.')) - return redirect('lessons_day_by_date', when=wanted_week[lesson_period.period.weekday - 1].strftime('%Y-%m-%d')) + messages.success(request, _("The substitution has been deleted.")) + return redirect( + "lessons_day_by_date", + when=wanted_week[lesson_period.period.weekday - 1].strftime("%Y-%m-%d"), + ) -def substitutions(request: HttpRequest, year: Optional[int] = None, week: Optional[int] = None) -> HttpResponse: +def substitutions( + request: HttpRequest, year: Optional[int] = None, week: Optional[int] = None +) -> HttpResponse: context = {} if week: @@ -176,11 +211,15 @@ def substitutions(request: HttpRequest, year: Optional[int] = None, week: Option substitutions_table = SubstitutionsTable(substitutions) RequestConfig(request).configure(substitutions_table) - context['current_head'] = str(wanted_week) - context['substitutions_table'] = substitutions_table + context["current_head"] = str(wanted_week) + context["substitutions_table"] = substitutions_table week_prev = wanted_week - 1 week_next = wanted_week + 1 - context['url_prev'] = '%s' % (reverse('substitutions_by_week', args=[week_prev.year, week_prev.week])) - context['url_next'] = '%s' % (reverse('substitutions_by_week', args=[week_next.year, week_next.week])) - - return render(request, 'chronos/substitutions.html', context) + context["url_prev"] = "%s" % ( + reverse("substitutions_by_week", args=[week_prev.year, week_prev.week]) + ) + context["url_next"] = "%s" % ( + reverse("substitutions_by_week", args=[week_next.year, week_next.week]) + ) + + return render(request, "chronos/substitutions.html", context)