diff --git a/aleksis/apps/chronos/admin.py b/aleksis/apps/chronos/admin.py index 56878a6c21c4f1c588de90d8c9eaec1ad3926d7d..8c99c4b5c23d10f027fae74e34da80ecfa6ea8a6 100644 --- a/aleksis/apps/chronos/admin.py +++ b/aleksis/apps/chronos/admin.py @@ -1,4 +1,4 @@ -#noqa +# noqa from django.contrib import admin from django.utils.html import format_html @@ -153,7 +153,7 @@ admin.site.register(Room, RoomAdmin) class SubjectAdmin(admin.ModelAdmin): def _colour(self, obj): - return colour_badge(obj.colour_fg, obj.colour_bg, obj.short_name, ) + return colour_badge(obj.colour_fg, obj.colour_bg, obj.short_name,) list_display = ("short_name", "name", "_colour") list_display_links = ("short_name", "name") diff --git a/aleksis/apps/chronos/forms.py b/aleksis/apps/chronos/forms.py index ab31b103aeccfd231787f82bae36f80eb8586191..0e676e2f72b16db074f7e9b3099fba72a9cce9c2 100644 --- a/aleksis/apps/chronos/forms.py +++ b/aleksis/apps/chronos/forms.py @@ -24,4 +24,6 @@ class LessonSubstitutionForm(forms.ModelForm): } -AnnouncementForm.add_node_to_layout(Fieldset(_("Options for timetables"), "show_in_timetables")) +AnnouncementForm.add_node_to_layout( + Fieldset(_("Options for timetables"), "show_in_timetables") +) diff --git a/aleksis/apps/chronos/managers.py b/aleksis/apps/chronos/managers.py index 1087f8e09348ea957e0046763940e27ff4249b9a..2f32a3e49c2d1d5ed7285e1fb01d63c128af1172 100644 --- a/aleksis/apps/chronos/managers.py +++ b/aleksis/apps/chronos/managers.py @@ -9,8 +9,12 @@ from django.db.models import F, Q, Count from django.http import QueryDict from aleksis.core.models import Person, Group +from aleksis.core.util.core_helpers import get_site_preferences + class TimetableType(Enum): + """Enum for different types of timetables.""" + GROUP = "group" TEACHER = "teacher" ROOM = "room" @@ -89,8 +93,10 @@ class LessonDataQuerySet(models.QuerySet, WeekQuerySetMixin): """ Filter for all lessons within a calendar week. """ return self.within_dates( - wanted_week[0] + timedelta(days=1) * (F(self._period_path + "period__weekday") - 1), - wanted_week[0] + timedelta(days=1) * (F(self._period_path + "period__weekday") - 1), + wanted_week[0] + + timedelta(days=1) * (F(self._period_path + "period__weekday") - 1), + wanted_week[0] + + timedelta(days=1) * (F(self._period_path + "period__weekday") - 1), ).annotate_week(wanted_week) def on_day(self, day: date): @@ -125,7 +131,9 @@ class LessonDataQuerySet(models.QuerySet, WeekQuerySetMixin): return self.filter( Q(**{self._period_path + "lesson__groups__members": person}) - | Q(**{self._period_path + "lesson__groups__parent_groups__members": person}) + | Q( + **{self._period_path + "lesson__groups__parent_groups__members": person} + ) ) def filter_group(self, group: Union[Group, int]): @@ -147,7 +155,12 @@ class LessonDataQuerySet(models.QuerySet, WeekQuerySetMixin): """ Filter for all lessons given by a certain teacher. """ qs1 = self.filter(**{self._period_path + "lesson__teachers": teacher}) - qs2 = self.filter(**{self._subst_path + "teachers": teacher, self._subst_path + "week": F("_week"), }) + qs2 = self.filter( + **{ + self._subst_path + "teachers": teacher, + self._subst_path + "week": F("_week"), + } + ) return qs1.union(qs2) @@ -155,11 +168,15 @@ class LessonDataQuerySet(models.QuerySet, WeekQuerySetMixin): """ Filter for all lessons taking part in a certain room. """ qs1 = self.filter(**{self._period_path + "room": room}) - qs2 = self.filter(**{self._subst_path + "room": room, self._subst_path + "week": F("_week"),}) + qs2 = self.filter( + **{self._subst_path + "room": room, self._subst_path + "week": F("_week"),} + ) return qs1.union(qs2) def group_by_periods(self, is_person: bool = False) -> dict: + """Group a QuerySet of objects with attribute period by period numbers and weekdays.""" + per_period = {} for obj in self: period = obj.period.period @@ -178,7 +195,11 @@ class LessonDataQuerySet(models.QuerySet, WeekQuerySetMixin): return per_period - def filter_from_type(self, type_: TimetableType, pk: int) -> Optional[models.QuerySet]: + def filter_from_type( + self, type_: TimetableType, pk: int + ) -> Optional[models.QuerySet]: + """Filter lesson data for a group, teacher or room by provided type.""" + if type_ == TimetableType.GROUP: return self.filter_group(pk) elif type_ == TimetableType.TEACHER: @@ -189,6 +210,8 @@ class LessonDataQuerySet(models.QuerySet, WeekQuerySetMixin): return None def filter_from_person(self, person: Person) -> Optional[models.QuerySet]: + """Filter lesson data for a person.""" + type_ = person.timetable_type if type_ == TimetableType.TEACHER: @@ -205,7 +228,11 @@ class LessonDataQuerySet(models.QuerySet, WeekQuerySetMixin): # If no student or teacher return None - def daily_lessons_for_person(self, person: Person, wanted_day: date) -> Optional[models.QuerySet]: + def daily_lessons_for_person( + self, person: Person, wanted_day: date + ) -> Optional[models.QuerySet]: + """Filter lesson data on a day by a person.""" + if person.timetable_type is None: return None @@ -213,7 +240,9 @@ class LessonDataQuerySet(models.QuerySet, WeekQuerySetMixin): return lesson_periods - def next(self, reference: "LessonPeriod", offset: Optional[int] = 1) -> "LessonPeriod": + def next( + self, reference: "LessonPeriod", offset: Optional[int] = 1 + ) -> "LessonPeriod": """ Get another lesson in an ordered set of lessons. By default, it returns the next lesson in the set. By passing the offset argument, @@ -234,17 +263,21 @@ class LessonDataQuerySet(models.QuerySet, WeekQuerySetMixin): class LessonPeriodQuerySet(LessonDataQuerySet): + """QuerySet with custom query methods for lesson periods.""" + _period_path = "" _subst_path = "substitutions__" class LessonSubstitutionQuerySet(LessonDataQuerySet): + """QuerySet with custom query methods for substitutions.""" + _period_path = "lesson_period__" _subst_path = "" def affected_lessons(self): """ Return all lessons which are affected by selected substitutions """ - from .models import Lesson # noaq + from .models import Lesson # noaq return Lesson.objects.filter(lesson_periods__substitutions__in=self) @@ -265,6 +298,11 @@ class LessonSubstitutionQuerySet(LessonDataQuerySet): class DateRangeQuerySet(models.QuerySet): + """QuerySet with custom query methods for models with date and period ranges. + + Filterable fields: date_start, date_end, period_from, period_to + """ + def within_dates(self, start: date, end: date): """ Filter for all events within a date range. """ @@ -286,28 +324,41 @@ class DateRangeQuerySet(models.QuerySet): now = when or datetime.now() return self.on_day(now.date()).filter( - period_from__time_start__lte=now.time(), - period_to__time_end__gte=now.time() + period_from__time_start__lte=now.time(), period_to__time_end__gte=now.time() ) class AbsenceQuerySet(DateRangeQuerySet): + """QuerySet with custom query methods for absences.""" + def absent_teachers(self): - return Person.objects.filter(absences__in=self).annotate(absences_count=Count("absences")) + return Person.objects.filter(absences__in=self).annotate( + absences_count=Count("absences") + ) def absent_groups(self): - return Group.objects.filter(absences__in=self).annotate(absences_count=Count("absences")) + return Group.objects.filter(absences__in=self).annotate( + absences_count=Count("absences") + ) def absent_rooms(self): - return Person.objects.filter(absences__in=self).annotate(absences_count=Count("absences")) + return Person.objects.filter(absences__in=self).annotate( + absences_count=Count("absences") + ) class HolidayQuerySet(DateRangeQuerySet): + """QuerySet with custom query methods for holidays.""" + pass class SupervisionQuerySet(models.QuerySet, WeekQuerySetMixin): + """QuerySet with custom query methods for supervisions.""" + def filter_by_weekday(self, weekday: int): + """Filter supervisions by weekday.""" + self.filter( Q(break_item__before_period__weekday=weekday) | Q(break_item__after_period__weekday=weekday) @@ -324,13 +375,16 @@ class SupervisionQuerySet(models.QuerySet, WeekQuerySetMixin): dates = [week[w] for w in range(0, 7)] - return self.filter(Q(substitutions__teacher=teacher, substitutions__date__in=dates) | Q(teacher=teacher)) + return self.filter( + Q(substitutions__teacher=teacher, substitutions__date__in=dates) + | Q(teacher=teacher) + ) return self class TimetableQuerySet(models.QuerySet): - """ Common filters + """Common query set methods for objects in timetables. Models need following fields: - groups @@ -370,7 +424,11 @@ class TimetableQuerySet(models.QuerySet): else: return self.filter(room=room) - def filter_from_type(self, type_: TimetableType, pk: int) -> Optional[models.QuerySet]: + def filter_from_type( + self, type_: TimetableType, pk: int + ) -> Optional[models.QuerySet]: + """Filter data for a group, teacher or room by provided type.""" + if type_ == TimetableType.GROUP: return self.filter_group(pk) elif type_ == TimetableType.TEACHER: @@ -381,6 +439,8 @@ class TimetableQuerySet(models.QuerySet): return None def filter_from_person(self, person: Person) -> Optional[models.QuerySet]: + """Filter data by person.""" + type_ = person.timetable_type if type_ == TimetableType.TEACHER: @@ -399,6 +459,8 @@ class TimetableQuerySet(models.QuerySet): class EventQuerySet(DateRangeQuerySet, TimetableQuerySet): + """QuerySet with custom query methods for events.""" + def annotate_day(self, day: date): """ Annotate all events in the QuerySet with the provided date. """ @@ -406,9 +468,13 @@ class EventQuerySet(DateRangeQuerySet, TimetableQuerySet): class ExtraLessonQuerySet(TimetableQuerySet): + """QuerySet with custom query methods for extra lessons.""" + _multiple_rooms = False def within_dates(self, start: date, end: date): + """Filter all extra lessons within a specific time range.""" + week_start = CalendarWeek.from_date(start) week_end = CalendarWeek.from_date(end) @@ -419,11 +485,18 @@ class ExtraLessonQuerySet(TimetableQuerySet): period__weekday__lte=end.weekday(), ) - def on_day(self, day:date): + def on_day(self, day: date): + """Filter all extra lessons on a day.""" + return self.within_dates(day, day) class GroupPropertiesMixin: + """Mixin for common group properties. + + Needed field: `groups` + """ + @property def group_names(self, sep: Optional[str] = ", ") -> str: return sep.join([group.short_name for group in self.groups.all()]) @@ -431,7 +504,11 @@ class GroupPropertiesMixin: @property def groups_to_show(self) -> models.QuerySet: groups = self.groups.all() - if groups.count() == 1 and groups[0].parent_groups.all() and get_site_preferences()["chronos__use_parent_groups"]: + if ( + groups.count() == 1 + and groups[0].parent_groups.all() + and get_site_preferences()["chronos__use_parent_groups"] + ): return groups[0].parent_groups.all() else: return groups @@ -442,6 +519,11 @@ class GroupPropertiesMixin: class TeacherPropertiesMixin: + """Mixin for common teacher properties. + + Needed field: `teacher` + """ + @property def teacher_names(self, sep: Optional[str] = ", ") -> str: return sep.join([teacher.full_name for teacher in self.teachers.all()]) diff --git a/aleksis/apps/chronos/menus.py b/aleksis/apps/chronos/menus.py index 592e6df90319d6165615e8ab3551101ce6e0fbbd..4a07e4e87d46782bd43782725e6a54b549c3393c 100644 --- a/aleksis/apps/chronos/menus.py +++ b/aleksis/apps/chronos/menus.py @@ -17,7 +17,10 @@ MENUS = { "url": "my_timetable", "icon": "person", "validators": [ - ("aleksis.core.util.predicates.permission_validator", "chronos.view_my_timetable"), + ( + "aleksis.core.util.predicates.permission_validator", + "chronos.view_my_timetable", + ), ], }, { @@ -25,7 +28,10 @@ MENUS = { "url": "all_timetables", "icon": "grid_on", "validators": [ - ("aleksis.core.util.predicates.permission_validator", "chronos.view_timetable_overview"), + ( + "aleksis.core.util.predicates.permission_validator", + "chronos.view_timetable_overview", + ), ], }, { @@ -33,7 +39,10 @@ MENUS = { "url": "lessons_day", "icon": "calendar_today", "validators": [ - ("aleksis.core.util.predicates.permission_validator", "chronos.view_lessons_day"), + ( + "aleksis.core.util.predicates.permission_validator", + "chronos.view_lessons_day", + ), ], }, { @@ -41,7 +50,10 @@ MENUS = { "url": "substitutions", "icon": "update", "validators": [ - ("aleksis.core.util.predicates.permission_validator", "chronos.view_substitutions"), + ( + "aleksis.core.util.predicates.permission_validator", + "chronos.view_substitutions", + ), ], }, ], diff --git a/aleksis/apps/chronos/migrations/0005_remove_school_related.py b/aleksis/apps/chronos/migrations/0005_remove_school_related.py index f2ebba33ac96bb24ffe4e866919ebf8cb3edfe2a..7d3ba123a3c1f91565b61957f29594e705007b1c 100644 --- a/aleksis/apps/chronos/migrations/0005_remove_school_related.py +++ b/aleksis/apps/chronos/migrations/0005_remove_school_related.py @@ -6,69 +6,53 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('core', '0008_rename_fields_notification_activity'), - ('chronos', '0004_room_name_not_unique'), + ("core", "0008_rename_fields_notification_activity"), + ("chronos", "0004_room_name_not_unique"), ] operations = [ - migrations.RemoveField( - model_name='lesson', - name='school', - ), - migrations.RemoveField( - model_name='lessonperiod', - name='school', - ), + migrations.RemoveField(model_name="lesson", name="school",), + migrations.RemoveField(model_name="lessonperiod", name="school",), migrations.AlterField( - model_name='lesson', - name='teachers', - field=models.ManyToManyField(related_name='lessons_as_teacher', to='core.Person'), + model_name="lesson", + name="teachers", + field=models.ManyToManyField( + related_name="lessons_as_teacher", to="core.Person" + ), ), migrations.AlterField( - model_name='room', - name='short_name', - field=models.CharField(max_length=10, unique=True, verbose_name='Short name, e.g. room number'), + model_name="room", + name="short_name", + field=models.CharField( + max_length=10, unique=True, verbose_name="Short name, e.g. room number" + ), ), migrations.AlterField( - model_name='subject', - name='abbrev', - field=models.CharField(max_length=10, unique=True, verbose_name='Abbreviation of subject in timetable'), + model_name="subject", + name="abbrev", + field=models.CharField( + max_length=10, + unique=True, + verbose_name="Abbreviation of subject in timetable", + ), ), migrations.AlterField( - model_name='subject', - name='name', - field=models.CharField(max_length=30, unique=True, verbose_name='Long name of subject'), - ), - migrations.AlterUniqueTogether( - name='lessonsubstitution', - unique_together={('lesson_period', 'week')}, - ), - migrations.AlterUniqueTogether( - name='room', - unique_together=set(), + model_name="subject", + name="name", + field=models.CharField( + max_length=30, unique=True, verbose_name="Long name of subject" + ), ), migrations.AlterUniqueTogether( - name='subject', - unique_together=set(), + name="lessonsubstitution", unique_together={("lesson_period", "week")}, ), + migrations.AlterUniqueTogether(name="room", unique_together=set(),), + migrations.AlterUniqueTogether(name="subject", unique_together=set(),), migrations.AlterUniqueTogether( - name='timeperiod', - unique_together={('weekday', 'period')}, - ), - migrations.RemoveField( - model_name='lessonsubstitution', - name='school', - ), - migrations.RemoveField( - model_name='room', - name='school', - ), - migrations.RemoveField( - model_name='subject', - name='school', - ), - migrations.RemoveField( - model_name='timeperiod', - name='school', + name="timeperiod", unique_together={("weekday", "period")}, ), + migrations.RemoveField(model_name="lessonsubstitution", name="school",), + migrations.RemoveField(model_name="room", name="school",), + migrations.RemoveField(model_name="subject", name="school",), + migrations.RemoveField(model_name="timeperiod", name="school",), ] diff --git a/aleksis/apps/chronos/migrations/0006_extended_data.py b/aleksis/apps/chronos/migrations/0006_extended_data.py index 71785e3f7b61e8445ce2a5425952a13be3227552..6cbea6402c3a9d5d0dedf97cb40fc11ebe9e5115 100644 --- a/aleksis/apps/chronos/migrations/0006_extended_data.py +++ b/aleksis/apps/chronos/migrations/0006_extended_data.py @@ -7,13 +7,15 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('chronos', '0005_remove_school_related'), + ("chronos", "0005_remove_school_related"), ] operations = [ migrations.AddField( - model_name='lessonperiod', - name='extended_data', - field=django.contrib.postgres.fields.jsonb.JSONField(default=dict, editable=False), + model_name="lessonperiod", + name="extended_data", + field=django.contrib.postgres.fields.jsonb.JSONField( + default=dict, editable=False + ), ), ] diff --git a/aleksis/apps/chronos/migrations/0007_advanced_models_from_untis.py b/aleksis/apps/chronos/migrations/0007_advanced_models_from_untis.py index 87b517a8beb8d3ee4dd097981ed53f95cc2b4ad4..ccefed73c6c2cb1be14b4aa9acf87f447da97b08 100644 --- a/aleksis/apps/chronos/migrations/0007_advanced_models_from_untis.py +++ b/aleksis/apps/chronos/migrations/0007_advanced_models_from_untis.py @@ -9,272 +9,545 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('chronos', '0006_extended_data'), + ("chronos", "0006_extended_data"), ] operations = [ migrations.CreateModel( - name='Absence', + name="Absence", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('extended_data', django.contrib.postgres.fields.jsonb.JSONField(default=dict, editable=False)), - ('date_start', models.DateField(null=True, verbose_name='Effective start date of absence')), - ('date_end', models.DateField(null=True, verbose_name='Effective end date of absence')), - ('comment', models.TextField(verbose_name='Comment', null=True, blank=True)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "extended_data", + django.contrib.postgres.fields.jsonb.JSONField( + default=dict, editable=False + ), + ), + ( + "date_start", + models.DateField( + null=True, verbose_name="Effective start date of absence" + ), + ), + ( + "date_end", + models.DateField( + null=True, verbose_name="Effective end date of absence" + ), + ), + ( + "comment", + models.TextField(verbose_name="Comment", null=True, blank=True), + ), ], options={ - 'verbose_name': 'Absence', - 'verbose_name_plural': 'Absences', - 'ordering': ['date_start'], + "verbose_name": "Absence", + "verbose_name_plural": "Absences", + "ordering": ["date_start"], }, ), migrations.CreateModel( - name='AbsenceReason', + name="AbsenceReason", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('extended_data', django.contrib.postgres.fields.jsonb.JSONField(default=dict, editable=False)), - ('title', models.CharField(max_length=50, verbose_name='Title')), - ('description', models.TextField(verbose_name='Description', null=True, blank=True)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "extended_data", + django.contrib.postgres.fields.jsonb.JSONField( + default=dict, editable=False + ), + ), + ("title", models.CharField(max_length=50, verbose_name="Title")), + ( + "description", + models.TextField(verbose_name="Description", null=True, blank=True), + ), ], options={ - 'verbose_name': 'Absence reason', - 'verbose_name_plural': 'Absence reasons', + "verbose_name": "Absence reason", + "verbose_name_plural": "Absence reasons", }, ), migrations.CreateModel( - name='Event', + name="Event", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('extended_data', django.contrib.postgres.fields.jsonb.JSONField(default=dict, editable=False)), - ('title', models.CharField(max_length=50, verbose_name='Title')), - ('date_start', models.DateField(null=True, verbose_name='Effective start date of event')), - ('date_end', models.DateField(null=True, verbose_name='Effective end date of event')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "extended_data", + django.contrib.postgres.fields.jsonb.JSONField( + default=dict, editable=False + ), + ), + ("title", models.CharField(max_length=50, verbose_name="Title")), + ( + "date_start", + models.DateField( + null=True, verbose_name="Effective start date of event" + ), + ), + ( + "date_end", + models.DateField( + null=True, verbose_name="Effective end date of event" + ), + ), ], options={ - 'verbose_name': 'Events', - 'verbose_name_plural': 'Events', - 'ordering': ['date_start'], + "verbose_name": "Events", + "verbose_name_plural": "Events", + "ordering": ["date_start"], }, ), migrations.CreateModel( - name='Exam', + name="Exam", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('extended_data', django.contrib.postgres.fields.jsonb.JSONField(default=dict, editable=False)), - ('date', models.DateField(null=True, verbose_name='Date of exam')), - ('title', models.CharField(max_length=50, verbose_name='Title')), - ('comment', models.TextField(verbose_name='Comment', null=True, blank=True)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "extended_data", + django.contrib.postgres.fields.jsonb.JSONField( + default=dict, editable=False + ), + ), + ("date", models.DateField(null=True, verbose_name="Date of exam")), + ("title", models.CharField(max_length=50, verbose_name="Title")), + ( + "comment", + models.TextField(verbose_name="Comment", null=True, blank=True), + ), ], options={ - 'verbose_name': 'Exam', - 'verbose_name_plural': 'Exams', - 'ordering': ['date'], + "verbose_name": "Exam", + "verbose_name_plural": "Exams", + "ordering": ["date"], }, ), migrations.CreateModel( - name='Holiday', + name="Holiday", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('extended_data', django.contrib.postgres.fields.jsonb.JSONField(default=dict, editable=False)), - ('title', models.CharField(max_length=50, verbose_name='Title of the holidays')), - ('date_start', models.DateField(null=True, verbose_name='Effective start date of holidays')), - ('date_end', models.DateField(null=True, verbose_name='Effective end date of holidays')), - ('comments', models.TextField(verbose_name='Comments', null=True, blank=True)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "extended_data", + django.contrib.postgres.fields.jsonb.JSONField( + default=dict, editable=False + ), + ), + ( + "title", + models.CharField( + max_length=50, verbose_name="Title of the holidays" + ), + ), + ( + "date_start", + models.DateField( + null=True, verbose_name="Effective start date of holidays" + ), + ), + ( + "date_end", + models.DateField( + null=True, verbose_name="Effective end date of holidays" + ), + ), + ( + "comments", + models.TextField(verbose_name="Comments", null=True, blank=True), + ), ], options={ - 'verbose_name': 'Holiday', - 'verbose_name_plural': 'Holidays', - 'ordering': ['date_start'], + "verbose_name": "Holiday", + "verbose_name_plural": "Holidays", + "ordering": ["date_start"], }, ), migrations.CreateModel( - name='SupervisionArea', + name="SupervisionArea", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('extended_data', django.contrib.postgres.fields.jsonb.JSONField(default=dict, editable=False)), - ('short_name', models.CharField(max_length=10, verbose_name='Short name')), - ('name', models.CharField(max_length=50, verbose_name='Long name')), - ('colour_fg', colorfield.fields.ColorField(default='#000000', max_length=18)), - ('colour_bg', colorfield.fields.ColorField(default='#FFFFFF', max_length=18)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "extended_data", + django.contrib.postgres.fields.jsonb.JSONField( + default=dict, editable=False + ), + ), + ( + "short_name", + models.CharField(max_length=10, verbose_name="Short name"), + ), + ("name", models.CharField(max_length=50, verbose_name="Long name")), + ( + "colour_fg", + colorfield.fields.ColorField(default="#000000", max_length=18), + ), + ( + "colour_bg", + colorfield.fields.ColorField(default="#FFFFFF", max_length=18), + ), ], options={ - 'verbose_name': 'Supervision areas', - 'verbose_name_plural': 'Supervision areas', - 'ordering': ['name'], + "verbose_name": "Supervision areas", + "verbose_name_plural": "Supervision areas", + "ordering": ["name"], }, ), migrations.CreateModel( - name='Break', + name="Break", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('extended_data', django.contrib.postgres.fields.jsonb.JSONField(default=dict, editable=False)), - ('short_name', models.CharField(max_length=10, verbose_name='Short name')), - ('name', models.CharField(max_length=50, verbose_name='Long name')), - ('weekday', models.PositiveSmallIntegerField( - choices=[(0, 'Montag'), (1, 'Dienstag'), (2, 'Mittwoch'), (3, 'Donnerstag'), (4, 'Freitag'), - (5, 'Samstag'), (6, 'Sonntag')], verbose_name='Week day')), - ('time_start', models.TimeField(verbose_name='Start time')), - ('time_end', models.TimeField(verbose_name='End time')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "extended_data", + django.contrib.postgres.fields.jsonb.JSONField( + default=dict, editable=False + ), + ), + ( + "short_name", + models.CharField(max_length=10, verbose_name="Short name"), + ), + ("name", models.CharField(max_length=50, verbose_name="Long name")), + ( + "weekday", + models.PositiveSmallIntegerField( + choices=[ + (0, "Montag"), + (1, "Dienstag"), + (2, "Mittwoch"), + (3, "Donnerstag"), + (4, "Freitag"), + (5, "Samstag"), + (6, "Sonntag"), + ], + verbose_name="Week day", + ), + ), + ("time_start", models.TimeField(verbose_name="Start time")), + ("time_end", models.TimeField(verbose_name="End time")), ], options={ - 'verbose_name': 'Break', - 'verbose_name_plural': 'Breaks', - 'ordering': ['weekday', 'time_start'], + "verbose_name": "Break", + "verbose_name_plural": "Breaks", + "ordering": ["weekday", "time_start"], }, ), migrations.CreateModel( - name='Supervision', + name="Supervision", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('extended_data', django.contrib.postgres.fields.jsonb.JSONField(default=dict, editable=False)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "extended_data", + django.contrib.postgres.fields.jsonb.JSONField( + default=dict, editable=False + ), + ), ], options={ - 'verbose_name': 'Supervision', - 'verbose_name_plural': 'Supervisions', - 'ordering': ['area', 'break_item'], + "verbose_name": "Supervision", + "verbose_name_plural": "Supervisions", + "ordering": ["area", "break_item"], }, ), migrations.CreateModel( - name='SupervisionSubstitution', + name="SupervisionSubstitution", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('extended_data', django.contrib.postgres.fields.jsonb.JSONField(default=dict, editable=False)), - ('date', models.DateField(verbose_name='Date')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "extended_data", + django.contrib.postgres.fields.jsonb.JSONField( + default=dict, editable=False + ), + ), + ("date", models.DateField(verbose_name="Date")), ], options={ - 'verbose_name': 'Supervision substitution', - 'verbose_name_plural': 'Supervision substitutions', - 'ordering': ['date', 'supervision'], + "verbose_name": "Supervision substitution", + "verbose_name_plural": "Supervision substitutions", + "ordering": ["date", "supervision"], }, ), migrations.AddIndex( - model_name='holiday', - index=models.Index(fields=['date_start', 'date_end'], name='chronos_hol_date_st_a47004_idx'), + model_name="holiday", + index=models.Index( + fields=["date_start", "date_end"], name="chronos_hol_date_st_a47004_idx" + ), ), migrations.AddField( - model_name='exam', - name='lesson', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='exams', to='chronos.Lesson'), + model_name="exam", + name="lesson", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="exams", + to="chronos.Lesson", + ), ), migrations.AddField( - model_name='exam', - name='period_from', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='chronos.TimePeriod', verbose_name='Effective start period of exam'), + model_name="exam", + name="period_from", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to="chronos.TimePeriod", + verbose_name="Effective start period of exam", + ), ), migrations.AddField( - model_name='exam', - name='period_to', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='chronos.TimePeriod', verbose_name='Effective end period of exam'), + model_name="exam", + name="period_to", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to="chronos.TimePeriod", + verbose_name="Effective end period of exam", + ), ), migrations.AddField( - model_name='event', - name='absence_reason', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='absence_reason', to='chronos.AbsenceReason', verbose_name='Absence reason'), + model_name="event", + name="absence_reason", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="absence_reason", + to="chronos.AbsenceReason", + verbose_name="Absence reason", + ), ), migrations.AddField( - model_name='event', - name='teachers', - field=models.ManyToManyField(related_name='events', to='core.Person', verbose_name='Teachers'), + model_name="event", + name="teachers", + field=models.ManyToManyField( + related_name="events", to="core.Person", verbose_name="Teachers" + ), ), migrations.AddField( - model_name='event', - name='period_from', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='chronos.TimePeriod', verbose_name='Effective start period of event'), + model_name="event", + name="period_from", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to="chronos.TimePeriod", + verbose_name="Effective start period of event", + ), ), migrations.AddField( - model_name='event', - name='period_to', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='chronos.TimePeriod', verbose_name='Effective end period of event'), + model_name="event", + name="period_to", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to="chronos.TimePeriod", + verbose_name="Effective end period of event", + ), ), migrations.AddField( - model_name='event', - name='rooms', - field=models.ManyToManyField(related_name='events', to='chronos.Room', verbose_name='Rooms'), + model_name="event", + name="rooms", + field=models.ManyToManyField( + related_name="events", to="chronos.Room", verbose_name="Rooms" + ), ), migrations.AddField( - model_name='absence', - name='period_from', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='chronos.TimePeriod', verbose_name='Effective start period of absence'), + model_name="absence", + name="period_from", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to="chronos.TimePeriod", + verbose_name="Effective start period of absence", + ), ), migrations.AddField( - model_name='absence', - name='period_to', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='chronos.TimePeriod', verbose_name='Effective end period of absence'), + model_name="absence", + name="period_to", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to="chronos.TimePeriod", + verbose_name="Effective end period of absence", + ), ), migrations.AddField( - model_name='absence', - name='person', - field=models.ManyToManyField(related_name='absences', to='core.Person'), + model_name="absence", + name="person", + field=models.ManyToManyField(related_name="absences", to="core.Person"), ), migrations.AddField( - model_name='absence', - name='reason', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='absences', to='chronos.AbsenceReason'), + model_name="absence", + name="reason", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="absences", + to="chronos.AbsenceReason", + ), ), migrations.AddIndex( - model_name='exam', - index=models.Index(fields=['date'], name='chronos_exa_date_5ba442_idx'), + model_name="exam", + index=models.Index(fields=["date"], name="chronos_exa_date_5ba442_idx"), ), migrations.AddIndex( - model_name='event', - index=models.Index(fields=['period_from', 'period_to', 'date_start', 'date_end'], name='chronos_eve_periodf_56eb18_idx'), + model_name="event", + index=models.Index( + fields=["period_from", "period_to", "date_start", "date_end"], + name="chronos_eve_periodf_56eb18_idx", + ), ), migrations.AddIndex( - model_name='absence', - index=models.Index(fields=['date_start', 'date_end'], name='chronos_abs_date_st_337ff5_idx'), + model_name="absence", + index=models.Index( + fields=["date_start", "date_end"], name="chronos_abs_date_st_337ff5_idx" + ), ), migrations.AddField( - model_name='lessonsubstitution', - name='cancelled_for_teachers', - field=models.BooleanField(default=False, verbose_name='Cancelled for teachers?'), + model_name="lessonsubstitution", + name="cancelled_for_teachers", + field=models.BooleanField( + default=False, verbose_name="Cancelled for teachers?" + ), ), migrations.AddField( - model_name='lessonsubstitution', - name='comment', - field=models.TextField(blank=True, null=True, verbose_name='Comment'), + model_name="lessonsubstitution", + name="comment", + field=models.TextField(blank=True, null=True, verbose_name="Comment"), ), migrations.AlterField( - model_name='lessonsubstitution', - name='cancelled', - field=models.BooleanField(default=False, verbose_name='Cancelled?'), + model_name="lessonsubstitution", + name="cancelled", + field=models.BooleanField(default=False, verbose_name="Cancelled?"), ), migrations.AddField( - model_name='event', - name='groups', - field=models.ManyToManyField(related_name='events', to='core.Group', verbose_name='Groups'), + model_name="event", + name="groups", + field=models.ManyToManyField( + related_name="events", to="core.Group", verbose_name="Groups" + ), ), migrations.AddField( - model_name='supervisionsubstitution', - name='supervision', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='substitutions', - to='chronos.Supervision', verbose_name='Supervision'), + model_name="supervisionsubstitution", + name="supervision", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="substitutions", + to="chronos.Supervision", + verbose_name="Supervision", + ), ), migrations.AddField( - model_name='supervisionsubstitution', - name='teacher', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, - related_name='substituted_supervisions', to='core.Person', verbose_name='Teacher'), + model_name="supervisionsubstitution", + name="teacher", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="substituted_supervisions", + to="core.Person", + verbose_name="Teacher", + ), ), migrations.AddField( - model_name='supervision', - name='area', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='supervisions', - to='chronos.SupervisionArea', verbose_name='Supervision area'), + model_name="supervision", + name="area", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="supervisions", + to="chronos.SupervisionArea", + verbose_name="Supervision area", + ), ), migrations.AddField( - model_name='supervision', - name='break_item', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='supervisions', - to='chronos.Break', verbose_name='Break'), + model_name="supervision", + name="break_item", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="supervisions", + to="chronos.Break", + verbose_name="Break", + ), ), migrations.AddField( - model_name='supervision', - name='teacher', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='supervisions', - to='core.Person', verbose_name='Teacher'), + model_name="supervision", + name="teacher", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="supervisions", + to="core.Person", + verbose_name="Teacher", + ), ), migrations.AddIndex( - model_name='break', - index=models.Index(fields=['weekday', 'time_start', 'time_end'], name='chronos_bre_weekday_165338_idx'), + model_name="break", + index=models.Index( + fields=["weekday", "time_start", "time_end"], + name="chronos_bre_weekday_165338_idx", + ), ), ] diff --git a/aleksis/apps/chronos/migrations/0008_break_period.py b/aleksis/apps/chronos/migrations/0008_break_period.py index 740fd91b9d598255b02d92f095b86d0b12b04f17..971432ab9282bd68a8a4159d8f9306073d5c9ab0 100644 --- a/aleksis/apps/chronos/migrations/0008_break_period.py +++ b/aleksis/apps/chronos/migrations/0008_break_period.py @@ -7,38 +7,45 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('chronos', '0007_advanced_models_from_untis'), + ("chronos", "0007_advanced_models_from_untis"), ] operations = [ migrations.RemoveIndex( - model_name='break', - name='chronos_bre_weekday_165338_idx', - ), - migrations.RemoveField( - model_name='break', - name='time_end', - ), - migrations.RemoveField( - model_name='break', - name='time_start', - ), - migrations.RemoveField( - model_name='break', - name='weekday', + model_name="break", name="chronos_bre_weekday_165338_idx", ), + migrations.RemoveField(model_name="break", name="time_end",), + migrations.RemoveField(model_name="break", name="time_start",), + migrations.RemoveField(model_name="break", name="weekday",), migrations.AddField( - model_name='break', - name='after_period', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='break_after', to='chronos.TimePeriod', verbose_name='Effective start of break'), + model_name="break", + name="after_period", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="break_after", + to="chronos.TimePeriod", + verbose_name="Effective start of break", + ), ), migrations.AddField( - model_name='break', - name='before_period', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='break_before', to='chronos.TimePeriod', verbose_name='Effective end of break'), + model_name="break", + name="before_period", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="break_before", + to="chronos.TimePeriod", + verbose_name="Effective end of break", + ), ), migrations.AddIndex( - model_name='break', - index=models.Index(fields=['after_period', 'before_period'], name='chronos_bre_after_p_0f28d3_idx'), + model_name="break", + index=models.Index( + fields=["after_period", "before_period"], + name="chronos_bre_after_p_0f28d3_idx", + ), ), ] diff --git a/aleksis/apps/chronos/migrations/0009_extended_data.py b/aleksis/apps/chronos/migrations/0009_extended_data.py index 6c8ff26421436d260ca295103a02e85e6960dd67..ed65071f8878776a9e767ab09a9c49e069c4a06a 100644 --- a/aleksis/apps/chronos/migrations/0009_extended_data.py +++ b/aleksis/apps/chronos/migrations/0009_extended_data.py @@ -7,33 +7,43 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('chronos', '0008_break_period'), + ("chronos", "0008_break_period"), ] operations = [ migrations.AddField( - model_name='lesson', - name='extended_data', - field=django.contrib.postgres.fields.jsonb.JSONField(default=dict, editable=False), + model_name="lesson", + name="extended_data", + field=django.contrib.postgres.fields.jsonb.JSONField( + default=dict, editable=False + ), ), migrations.AddField( - model_name='lessonsubstitution', - name='extended_data', - field=django.contrib.postgres.fields.jsonb.JSONField(default=dict, editable=False), + model_name="lessonsubstitution", + name="extended_data", + field=django.contrib.postgres.fields.jsonb.JSONField( + default=dict, editable=False + ), ), migrations.AddField( - model_name='room', - name='extended_data', - field=django.contrib.postgres.fields.jsonb.JSONField(default=dict, editable=False), + model_name="room", + name="extended_data", + field=django.contrib.postgres.fields.jsonb.JSONField( + default=dict, editable=False + ), ), migrations.AddField( - model_name='subject', - name='extended_data', - field=django.contrib.postgres.fields.jsonb.JSONField(default=dict, editable=False), + model_name="subject", + name="extended_data", + field=django.contrib.postgres.fields.jsonb.JSONField( + default=dict, editable=False + ), ), migrations.AddField( - model_name='timeperiod', - name='extended_data', - field=django.contrib.postgres.fields.jsonb.JSONField(default=dict, editable=False), + model_name="timeperiod", + name="extended_data", + field=django.contrib.postgres.fields.jsonb.JSONField( + default=dict, editable=False + ), ), ] diff --git a/aleksis/apps/chronos/migrations/0010_absence_reason_name.py b/aleksis/apps/chronos/migrations/0010_absence_reason_name.py index 7c57327a2e19c8ff2865d14fabcd65acd317146f..95f05e8b63035b6b8b1f9b5b8fcba620647efb01 100644 --- a/aleksis/apps/chronos/migrations/0010_absence_reason_name.py +++ b/aleksis/apps/chronos/migrations/0010_absence_reason_name.py @@ -7,27 +7,29 @@ from django.db.models import F class Migration(migrations.Migration): dependencies = [ - ('chronos', '0009_extended_data'), + ("chronos", "0009_extended_data"), ] operations = [ migrations.AddField( - model_name='absencereason', - name='name', - field=models.CharField(default=F("description"), blank=True, max_length=255, null=True, verbose_name='Name'), + model_name="absencereason", + name="name", + field=models.CharField( + default=F("description"), + blank=True, + max_length=255, + null=True, + verbose_name="Name", + ), ), migrations.AddField( - model_name='absencereason', - name='short_name', - field=models.CharField(default=F("title"), max_length=255, verbose_name='Short name'), + model_name="absencereason", + name="short_name", + field=models.CharField( + default=F("title"), max_length=255, verbose_name="Short name" + ), preserve_default=False, ), - migrations.RemoveField( - model_name='absencereason', - name='description', - ), - migrations.RemoveField( - model_name='absencereason', - name='title', - ), + migrations.RemoveField(model_name="absencereason", name="description",), + migrations.RemoveField(model_name="absencereason", name="title",), ] diff --git a/aleksis/apps/chronos/migrations/0011_absence_for_groups_and_rooms.py b/aleksis/apps/chronos/migrations/0011_absence_for_groups_and_rooms.py index 008a723af7c68f96dbff9f8b149fb3c36305028e..cf7df94cb62f676c1e25188e086bf9460483dcd3 100644 --- a/aleksis/apps/chronos/migrations/0011_absence_for_groups_and_rooms.py +++ b/aleksis/apps/chronos/migrations/0011_absence_for_groups_and_rooms.py @@ -7,27 +7,42 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('chronos', '0010_absence_reason_name'), + ("chronos", "0010_absence_reason_name"), ] operations = [ - migrations.RemoveField( - model_name='absence', - name='person', - ), + migrations.RemoveField(model_name="absence", name="person",), migrations.AddField( - model_name='absence', - name='group', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='absences', to='core.Group'), + model_name="absence", + name="group", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="absences", + to="core.Group", + ), ), migrations.AddField( - model_name='absence', - name='room', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='absences', to='chronos.Room'), + model_name="absence", + name="room", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="absences", + to="chronos.Room", + ), ), migrations.AddField( - model_name='absence', - name='teacher', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='absences', to='core.Person'), + model_name="absence", + name="teacher", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="absences", + to="core.Person", + ), ), ] diff --git a/aleksis/apps/chronos/migrations/0012_event_remove_absence_reason.py b/aleksis/apps/chronos/migrations/0012_event_remove_absence_reason.py index 4e6611963aab5f79179b0489ca4bd41d25bd4433..5bdaaee5e09b316ff0531c041a7ee79d392fe432 100644 --- a/aleksis/apps/chronos/migrations/0012_event_remove_absence_reason.py +++ b/aleksis/apps/chronos/migrations/0012_event_remove_absence_reason.py @@ -6,12 +6,9 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('chronos', '0011_absence_for_groups_and_rooms'), + ("chronos", "0011_absence_for_groups_and_rooms"), ] operations = [ - migrations.RemoveField( - model_name='event', - name='absence_reason', - ), + migrations.RemoveField(model_name="event", name="absence_reason",), ] diff --git a/aleksis/apps/chronos/migrations/0013_event_title_optional.py b/aleksis/apps/chronos/migrations/0013_event_title_optional.py index 4bc05c39f8307ca056d03102c8709c018a432f63..6f18311e5450a155dcab6c38a4efffeab878c3e1 100644 --- a/aleksis/apps/chronos/migrations/0013_event_title_optional.py +++ b/aleksis/apps/chronos/migrations/0013_event_title_optional.py @@ -6,13 +6,15 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('chronos', '0012_event_remove_absence_reason'), + ("chronos", "0012_event_remove_absence_reason"), ] operations = [ migrations.AlterField( - model_name='event', - name='title', - field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Title'), + model_name="event", + name="title", + field=models.CharField( + blank=True, max_length=255, null=True, verbose_name="Title" + ), ), ] diff --git a/aleksis/apps/chronos/migrations/0014_extra_lesson.py b/aleksis/apps/chronos/migrations/0014_extra_lesson.py index b0610a5ed76763d087d33ddbdb74581732bc9e78..d3a497d90560ff263a5e15d01e1e25d017432e19 100644 --- a/aleksis/apps/chronos/migrations/0014_extra_lesson.py +++ b/aleksis/apps/chronos/migrations/0014_extra_lesson.py @@ -9,45 +9,88 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('chronos', '0013_event_title_optional'), + ("chronos", "0013_event_title_optional"), ] operations = [ migrations.CreateModel( - name='ExtraLesson', + name="ExtraLesson", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('extended_data', django.contrib.postgres.fields.jsonb.JSONField(default=dict, editable=False)), - ('week', models.IntegerField(default=calendarweek.calendarweek.CalendarWeek.current_week, verbose_name='Week')), - ('comment', models.CharField(blank=True, max_length=255, null=True, verbose_name='Comment')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "extended_data", + django.contrib.postgres.fields.jsonb.JSONField( + default=dict, editable=False + ), + ), + ( + "week", + models.IntegerField( + default=calendarweek.calendarweek.CalendarWeek.current_week, + verbose_name="Week", + ), + ), + ( + "comment", + models.CharField( + blank=True, max_length=255, null=True, verbose_name="Comment" + ), + ), ], - options={ - 'abstract': False, - }, + options={"abstract": False,}, ), migrations.AddField( - model_name='extralesson', - name='groups', - field=models.ManyToManyField(related_name='extra_lessons', to='core.Group', verbose_name='Groups'), + model_name="extralesson", + name="groups", + field=models.ManyToManyField( + related_name="extra_lessons", to="core.Group", verbose_name="Groups" + ), ), migrations.AddField( - model_name='extralesson', - name='period', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='extra_lessons', to='chronos.TimePeriod'), + model_name="extralesson", + name="period", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="extra_lessons", + to="chronos.TimePeriod", + ), ), migrations.AddField( - model_name='extralesson', - name='room', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='extra_lessons', to='chronos.Room', verbose_name='Room'), + model_name="extralesson", + name="room", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="extra_lessons", + to="chronos.Room", + verbose_name="Room", + ), ), migrations.AddField( - model_name='extralesson', - name='subject', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='extra_lessons', to='chronos.Subject', verbose_name='Subject'), + model_name="extralesson", + name="subject", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="extra_lessons", + to="chronos.Subject", + verbose_name="Subject", + ), ), migrations.AddField( - model_name='extralesson', - name='teachers', - field=models.ManyToManyField(related_name='extra_lessons_as_teacher', to='core.Person', verbose_name='Teachers'), + model_name="extralesson", + name="teachers", + field=models.ManyToManyField( + related_name="extra_lessons_as_teacher", + to="core.Person", + verbose_name="Teachers", + ), ), ] diff --git a/aleksis/apps/chronos/migrations/0015_rename_abbrev_to_short_name.py b/aleksis/apps/chronos/migrations/0015_rename_abbrev_to_short_name.py index 5d79636e326cd65b552559aeb71422ae1bd0d2aa..4912dd0d5118f7938d2f2fb8346cd2f2d079bf38 100644 --- a/aleksis/apps/chronos/migrations/0015_rename_abbrev_to_short_name.py +++ b/aleksis/apps/chronos/migrations/0015_rename_abbrev_to_short_name.py @@ -7,17 +7,14 @@ from django.db.models import F class Migration(migrations.Migration): dependencies = [ - ('chronos', '0014_extra_lesson'), + ("chronos", "0014_extra_lesson"), ] operations = [ migrations.RenameField( - model_name='subject', - old_name='abbrev', - new_name='short_name', + model_name="subject", old_name="abbrev", new_name="short_name", ), migrations.AlterModelOptions( - name='subject', - options={'ordering': ['name', 'short_name']}, + name="subject", options={"ordering": ["name", "short_name"]}, ), ] diff --git a/aleksis/apps/chronos/migrations/0016_add_globalpermissions.py b/aleksis/apps/chronos/migrations/0016_add_globalpermissions.py index 785e2782c95b53321ee9940f289564ad2b495065..b43b2bf1061cb9f45143c87c67c07c0ee8fc907c 100644 --- a/aleksis/apps/chronos/migrations/0016_add_globalpermissions.py +++ b/aleksis/apps/chronos/migrations/0016_add_globalpermissions.py @@ -7,19 +7,36 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('chronos', '0015_rename_abbrev_to_short_name'), + ("chronos", "0015_rename_abbrev_to_short_name"), ] operations = [ migrations.CreateModel( - name='GlobalPermissions', + name="GlobalPermissions", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('extended_data', django.contrib.postgres.fields.jsonb.JSONField(default=dict, editable=False)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "extended_data", + django.contrib.postgres.fields.jsonb.JSONField( + default=dict, editable=False + ), + ), ], options={ - 'permissions': (('view_all_timetables', 'Can view all timetables'), ('view_timetable_overview', 'Can view timetable overview'), ('view_lessons_day', 'Can view all lessons per day')), - 'managed': False, + "permissions": ( + ("view_all_timetables", "Can view all timetables"), + ("view_timetable_overview", "Can view timetable overview"), + ("view_lessons_day", "Can view all lessons per day"), + ), + "managed": False, }, ), ] diff --git a/aleksis/apps/chronos/model_extensions.py b/aleksis/apps/chronos/model_extensions.py index 1607bd241868ee6c9c147ab22a90e1ed4a03647e..2877278e87361654e6c40fa48d4c80e145b25f39 100644 --- a/aleksis/apps/chronos/model_extensions.py +++ b/aleksis/apps/chronos/model_extensions.py @@ -88,8 +88,13 @@ def lesson_periods_as_teacher(self): def for_timetables(cls): + """Return all announcements that should be shown in timetable views.""" return cls.objects.filter(show_in_timetables=True) Announcement.class_method(for_timetables) -Announcement.field(show_in_timetables=BooleanField(verbose_name=_("Show announcement in timetable views?"))) +Announcement.field( + show_in_timetables=BooleanField( + verbose_name=_("Show announcement in timetable views?") + ) +) diff --git a/aleksis/apps/chronos/models.py b/aleksis/apps/chronos/models.py index 556f3a1f4b40a4ea7c6e5a94757fe9d74c058b8a..595ba172eb3b2bf458cd83b372fd606df33c5dde 100644 --- a/aleksis/apps/chronos/models.py +++ b/aleksis/apps/chronos/models.py @@ -3,9 +3,19 @@ from __future__ import annotations from datetime import date, datetime, timedelta, time from typing import Dict, Optional, Tuple, Union -from aleksis.apps.chronos.managers import GroupPropertiesMixin, TeacherPropertiesMixin, LessonSubstitutionManager, \ - LessonSubstitutionQuerySet, LessonPeriodManager, LessonPeriodQuerySet, AbsenceQuerySet, HolidayQuerySet, \ - SupervisionQuerySet, EventQuerySet, ExtraLessonQuerySet +from aleksis.apps.chronos.managers import ( + GroupPropertiesMixin, + TeacherPropertiesMixin, + LessonSubstitutionManager, + LessonSubstitutionQuerySet, + LessonPeriodManager, + LessonPeriodQuerySet, + AbsenceQuerySet, + HolidayQuerySet, + SupervisionQuerySet, + EventQuerySet, + ExtraLessonQuerySet, +) from django.core.exceptions import ValidationError from django.db import models from django.db.models import Max, Min, Q @@ -33,17 +43,16 @@ class TimePeriod(ExtensibleModel): WEEKDAY_CHOICES = list(enumerate(i18n_day_names_lazy())) WEEKDAY_CHOICES_SHORT = list(enumerate(i18n_day_abbrs_lazy())) - weekday = models.PositiveSmallIntegerField(verbose_name=_("Week day"), choices=WEEKDAY_CHOICES) + weekday = models.PositiveSmallIntegerField( + verbose_name=_("Week day"), choices=WEEKDAY_CHOICES + ) period = models.PositiveSmallIntegerField(verbose_name=_("Number of period")) time_start = models.TimeField(verbose_name=_("Start time")) time_end = models.TimeField(verbose_name=_("End time")) def __str__(self) -> str: - return "{}, {}.".format( - self.get_weekday_display(), - self.period, - ) + return "{}, {}.".format(self.get_weekday_display(), self.period,) @classmethod def get_times_dict(cls) -> Dict[int, Tuple[datetime, datetime]]: @@ -68,7 +77,9 @@ class TimePeriod(ExtensibleModel): return wanted_week[self.weekday] @classmethod - def get_next_relevant_day(cls, day: Optional[date] = None, time: Optional[time] = None, prev: bool = False) -> date: + def get_next_relevant_day( + cls, day: Optional[date] = None, time: Optional[time] = None, prev: bool = False + ) -> date: """ Returns next (previous) day with lessons depending on date and time """ if day is None: @@ -109,11 +120,15 @@ class TimePeriod(ExtensibleModel): @classproperty def period_min(cls) -> int: - return cls.objects.aggregate(period__min=Coalesce(Min("period"), 1)).get("period__min") + 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") + return cls.objects.aggregate(period__max=Coalesce(Max("period"), 7)).get( + "period__max" + ) @classproperty def time_min(cls) -> Optional[time]: @@ -125,11 +140,15 @@ class TimePeriod(ExtensibleModel): @classproperty def weekday_min(cls) -> int: - return cls.objects.aggregate(weekday__min=Coalesce(Min("weekday"), 0)).get("weekday__min") + 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") + return cls.objects.aggregate(weekday__max=Coalesce(Max("weekday"), 6)).get( + "weekday__max" + ) class Meta: unique_together = [["weekday", "period"]] @@ -140,7 +159,9 @@ class TimePeriod(ExtensibleModel): class Subject(ExtensibleModel): - short_name = models.CharField(verbose_name=_("Short name"), max_length=255, unique=True) + short_name = models.CharField( + verbose_name=_("Short name"), max_length=255, unique=True + ) name = models.CharField(verbose_name=_("Long name"), max_length=255, unique=True) colour_fg = ColorField(verbose_name=_("Foreground colour"), blank=True) @@ -156,7 +177,9 @@ class Subject(ExtensibleModel): class Room(ExtensibleModel): - short_name = models.CharField(verbose_name=_("Short name"), max_length=255, unique=True) + short_name = models.CharField( + verbose_name=_("Short name"), max_length=255, unique=True + ) name = models.CharField(verbose_name=_("Long name"), max_length=255) def __str__(self) -> str: @@ -168,13 +191,25 @@ class Room(ExtensibleModel): verbose_name_plural = _("Rooms") - - class Lesson(ExtensibleModel, GroupPropertiesMixin, TeacherPropertiesMixin): - subject = models.ForeignKey("Subject", on_delete=models.CASCADE, related_name="lessons", verbose_name=_("Subject")) - teachers = models.ManyToManyField("core.Person", related_name="lessons_as_teacher", verbose_name=_("Teachers")) - periods = models.ManyToManyField("TimePeriod", related_name="lessons", through="LessonPeriod", verbose_name=_("Periods")) - groups = models.ManyToManyField("core.Group", related_name="lessons", verbose_name=_("Groups")) + subject = models.ForeignKey( + "Subject", + on_delete=models.CASCADE, + related_name="lessons", + verbose_name=_("Subject"), + ) + teachers = models.ManyToManyField( + "core.Person", related_name="lessons_as_teacher", verbose_name=_("Teachers") + ) + periods = models.ManyToManyField( + "TimePeriod", + related_name="lessons", + through="LessonPeriod", + verbose_name=_("Periods"), + ) + groups = models.ManyToManyField( + "core.Group", related_name="lessons", verbose_name=_("Groups") + ) date_start = models.DateField(verbose_name=_("Start date"), null=True) date_end = models.DateField(verbose_name=_("End date"), null=True) @@ -188,9 +223,7 @@ class Lesson(ExtensibleModel, GroupPropertiesMixin, TeacherPropertiesMixin): def __str__(self): return "{}, {}, {}".format( - format_m2m(self.groups), - self.subject.short_name, - format_m2m(self.teachers), + format_m2m(self.groups), self.subject.short_name, format_m2m(self.teachers), ) class Meta: @@ -203,9 +236,13 @@ class Lesson(ExtensibleModel, GroupPropertiesMixin, TeacherPropertiesMixin): class LessonSubstitution(ExtensibleModel): objects = LessonSubstitutionManager.from_queryset(LessonSubstitutionQuerySet)() - 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", verbose_name=_("Lesson period")) + lesson_period = models.ForeignKey( + "LessonPeriod", models.CASCADE, "substitutions", verbose_name=_("Lesson period") + ) subject = models.ForeignKey( "Subject", @@ -216,18 +253,27 @@ class LessonSubstitution(ExtensibleModel): verbose_name=_("Subject"), ) teachers = models.ManyToManyField( - "core.Person", related_name="lesson_substitutions", blank=True, verbose_name=_("Teachers") + "core.Person", + related_name="lesson_substitutions", + blank=True, + verbose_name=_("Teachers"), + ) + room = models.ForeignKey( + "Room", models.CASCADE, null=True, blank=True, verbose_name=_("Room") ) - room = models.ForeignKey("Room", models.CASCADE, null=True, blank=True, verbose_name=_("Room")) cancelled = models.BooleanField(default=False, verbose_name=_("Cancelled?")) - cancelled_for_teachers = models.BooleanField(default=False, verbose_name=_("Cancelled for teachers?")) + cancelled_for_teachers = models.BooleanField( + default=False, verbose_name=_("Cancelled for teachers?") + ) comment = models.TextField(verbose_name=_("Comment"), blank=True, null=True) 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.") + ) @property def date(self): @@ -260,10 +306,26 @@ class LessonPeriod(ExtensibleModel): objects = LessonPeriodManager.from_queryset(LessonPeriodQuerySet)() - lesson = models.ForeignKey("Lesson", models.CASCADE, related_name="lesson_periods", verbose_name=_("Lesson")) - period = models.ForeignKey("TimePeriod", models.CASCADE, related_name="lesson_periods", verbose_name=_("Time period")) + lesson = models.ForeignKey( + "Lesson", + models.CASCADE, + related_name="lesson_periods", + verbose_name=_("Lesson"), + ) + period = models.ForeignKey( + "TimePeriod", + models.CASCADE, + related_name="lesson_periods", + verbose_name=_("Time period"), + ) - room = models.ForeignKey("Room", models.CASCADE, null=True, related_name="lesson_periods", verbose_name=_("Room")) + room = models.ForeignKey( + "Room", + models.CASCADE, + null=True, + related_name="lesson_periods", + verbose_name=_("Room"), + ) def get_substitution(self, week: Optional[int] = None) -> LessonSubstitution: wanted_week = week or getattr(self, "_week", None) or CalendarWeek().week @@ -301,13 +363,15 @@ class LessonPeriod(ExtensibleModel): return self.lesson.groups def __str__(self) -> str: - return "{}, {}".format( - str(self.period), - str(self.lesson) - ) + return "{}, {}".format(str(self.period), str(self.lesson)) class Meta: - ordering = ["lesson__date_start", "period__weekday", "period__period", "lesson__subject"] + ordering = [ + "lesson__date_start", + "period__weekday", + "period__period", + "lesson__subject", + ] indexes = [models.Index(fields=["lesson", "period"])] verbose_name = _("Lesson period") verbose_name_plural = _("Lesson periods") @@ -317,11 +381,13 @@ class TimetableWidget(DashboardWidget): template = "chronos/widget.html" def get_context(self): - from aleksis.apps.chronos.util.build import build_timetable # noqa + from aleksis.apps.chronos.util.build import build_timetable # noqa request = get_request() context = {"has_plan": True} - wanted_day = TimePeriod.get_next_relevant_day(timezone.now().date(), datetime.now().time()) + wanted_day = TimePeriod.get_next_relevant_day( + timezone.now().date(), datetime.now().time() + ) if has_person(request.user): person = request.user.person @@ -345,9 +411,7 @@ class TimetableWidget(DashboardWidget): return context - media = Media(css={ - "all": ("css/chronos/timetable.css",) - }) + media = Media(css={"all": ("css/chronos/timetable.css",)}) class Meta: proxy = True @@ -357,7 +421,9 @@ class TimetableWidget(DashboardWidget): class AbsenceReason(ExtensibleModel): short_name = models.CharField(verbose_name=_("Short name"), max_length=255) - name = models.CharField(verbose_name=_("Name"), blank=True, null=True, max_length=255) + name = models.CharField( + verbose_name=_("Name"), blank=True, null=True, max_length=255 + ) def __str__(self): if self.name: @@ -373,16 +439,56 @@ class AbsenceReason(ExtensibleModel): class Absence(ExtensibleModel): objects = models.Manager.from_queryset(AbsenceQuerySet)() - reason = models.ForeignKey("AbsenceReason", on_delete=models.SET_NULL, related_name="absences", blank=True, null=True, verbose_name=_("Absence reason")) + reason = models.ForeignKey( + "AbsenceReason", + on_delete=models.SET_NULL, + related_name="absences", + blank=True, + null=True, + verbose_name=_("Absence reason"), + ) - teacher = models.ForeignKey("core.Person", on_delete=models.CASCADE, related_name="absences", null=True, blank=True, verbose_name=_("Teacher")) - group = models.ForeignKey("core.Group", on_delete=models.CASCADE, related_name="absences", null=True, blank=True, verbose_name=_("Group")) - room = models.ForeignKey("Room", on_delete=models.CASCADE, related_name="absences", null=True, blank=True, verbose_name=_("Room")) + teacher = models.ForeignKey( + "core.Person", + on_delete=models.CASCADE, + related_name="absences", + null=True, + blank=True, + verbose_name=_("Teacher"), + ) + group = models.ForeignKey( + "core.Group", + on_delete=models.CASCADE, + related_name="absences", + null=True, + blank=True, + verbose_name=_("Group"), + ) + room = models.ForeignKey( + "Room", + on_delete=models.CASCADE, + related_name="absences", + null=True, + blank=True, + verbose_name=_("Room"), + ) date_start = models.DateField(verbose_name=_("Start date"), null=True) date_end = models.DateField(verbose_name=_("End date"), null=True) - period_from = models.ForeignKey("TimePeriod", on_delete=models.CASCADE, verbose_name=_("Start period"), null=True, related_name="+") - period_to = models.ForeignKey("TimePeriod", on_delete=models.CASCADE, verbose_name=_("End period"), null=True, related_name="+") + period_from = models.ForeignKey( + "TimePeriod", + on_delete=models.CASCADE, + verbose_name=_("Start period"), + null=True, + related_name="+", + ) + period_to = models.ForeignKey( + "TimePeriod", + on_delete=models.CASCADE, + verbose_name=_("End period"), + null=True, + related_name="+", + ) comment = models.TextField(verbose_name=_("Comment"), blank=True, null=True) def __str__(self): @@ -403,11 +509,28 @@ class Absence(ExtensibleModel): class Exam(ExtensibleModel): - lesson = models.ForeignKey("Lesson", on_delete=models.CASCADE, related_name="exams", verbose_name=_("Lesson")) + lesson = models.ForeignKey( + "Lesson", + on_delete=models.CASCADE, + related_name="exams", + verbose_name=_("Lesson"), + ) date = models.DateField(verbose_name=_("Date of exam"), null=True) - period_from = models.ForeignKey("TimePeriod", on_delete=models.CASCADE, verbose_name=_("Start period"), null=True, related_name="+") - period_to = models.ForeignKey("TimePeriod", on_delete=models.CASCADE, verbose_name=_("End period"), null=True, related_name="+") + period_from = models.ForeignKey( + "TimePeriod", + on_delete=models.CASCADE, + verbose_name=_("Start period"), + null=True, + related_name="+", + ) + period_to = models.ForeignKey( + "TimePeriod", + on_delete=models.CASCADE, + verbose_name=_("End period"), + null=True, + related_name="+", + ) title = models.CharField(verbose_name=_("Title"), max_length=255) comment = models.TextField(verbose_name=_("Comment"), blank=True, null=True) @@ -476,12 +599,22 @@ class Break(ExtensibleModel): short_name = models.CharField(verbose_name=_("Short name"), max_length=255) name = models.CharField(verbose_name=_("Long name"), max_length=255) - after_period = models.ForeignKey("TimePeriod", on_delete=models.CASCADE, - verbose_name=_("Time period after break starts"), - related_name="break_after", blank=True, null=True) - before_period = models.ForeignKey("TimePeriod", on_delete=models.CASCADE, - verbose_name=_("Time period before break ends"), - related_name="break_before", blank=True, null=True) + after_period = models.ForeignKey( + "TimePeriod", + on_delete=models.CASCADE, + verbose_name=_("Time period after break starts"), + related_name="break_after", + blank=True, + null=True, + ) + before_period = models.ForeignKey( + "TimePeriod", + on_delete=models.CASCADE, + verbose_name=_("Time period before break ends"), + related_name="break_before", + blank=True, + null=True, + ) @property def weekday(self): @@ -536,9 +669,21 @@ class Break(ExtensibleModel): class Supervision(ExtensibleModel): objects = models.Manager.from_queryset(SupervisionQuerySet)() - area = models.ForeignKey(SupervisionArea, models.CASCADE, verbose_name=_("Supervision area"), related_name="supervisions") - break_item = models.ForeignKey(Break, models.CASCADE, verbose_name=_("Break"), related_name="supervisions") - teacher = models.ForeignKey("core.Person", models.CASCADE, related_name="supervisions", verbose_name=_("Teacher")) + area = models.ForeignKey( + SupervisionArea, + models.CASCADE, + verbose_name=_("Supervision area"), + related_name="supervisions", + ) + break_item = models.ForeignKey( + Break, models.CASCADE, verbose_name=_("Break"), related_name="supervisions" + ) + teacher = models.ForeignKey( + "core.Person", + models.CASCADE, + related_name="supervisions", + verbose_name=_("Teacher"), + ) def get_substitution( self, week: Optional[int] = None @@ -563,14 +708,24 @@ class Supervision(ExtensibleModel): class Meta: ordering = ["area", "break_item"] - verbose_name= _("Supervision") + verbose_name = _("Supervision") verbose_name_plural = _("Supervisions") class SupervisionSubstitution(ExtensibleModel): date = models.DateField(verbose_name=_("Date")) - supervision = models.ForeignKey(Supervision, models.CASCADE, verbose_name=_("Supervision"), related_name="substitutions") - teacher = models.ForeignKey("core.Person", models.CASCADE, related_name="substituted_supervisions", verbose_name=_("Teacher")) + supervision = models.ForeignKey( + Supervision, + models.CASCADE, + verbose_name=_("Supervision"), + related_name="substitutions", + ) + teacher = models.ForeignKey( + "core.Person", + models.CASCADE, + related_name="substituted_supervisions", + verbose_name=_("Teacher"), + ) @property def teachers(self): @@ -590,17 +745,35 @@ class Event(ExtensibleModel, GroupPropertiesMixin, TeacherPropertiesMixin): objects = models.Manager.from_queryset(EventQuerySet)() - title = models.CharField(verbose_name=_("Title"), max_length=255, blank=True, null=True) + title = models.CharField( + verbose_name=_("Title"), max_length=255, blank=True, null=True + ) date_start = models.DateField(verbose_name=_("Start date"), null=True) date_end = models.DateField(verbose_name=_("End date"), null=True) - period_from = models.ForeignKey("TimePeriod", on_delete=models.CASCADE, verbose_name=_("Start time period"), related_name="+") - period_to = models.ForeignKey("TimePeriod", on_delete=models.CASCADE, verbose_name=_("End time period"), related_name="+") + period_from = models.ForeignKey( + "TimePeriod", + on_delete=models.CASCADE, + verbose_name=_("Start time period"), + related_name="+", + ) + period_to = models.ForeignKey( + "TimePeriod", + on_delete=models.CASCADE, + verbose_name=_("End time period"), + related_name="+", + ) - groups = models.ManyToManyField("core.Group", related_name="events", verbose_name=_("Groups")) - rooms = models.ManyToManyField("Room", related_name="events", verbose_name=_("Rooms")) - teachers = models.ManyToManyField("core.Person", related_name="events", verbose_name=_("Teachers")) + groups = models.ManyToManyField( + "core.Group", related_name="events", verbose_name=_("Groups") + ) + rooms = models.ManyToManyField( + "Room", related_name="events", verbose_name=_("Rooms") + ) + teachers = models.ManyToManyField( + "core.Person", related_name="events", verbose_name=_("Teachers") + ) def __str__(self): if self.title: @@ -626,7 +799,9 @@ class Event(ExtensibleModel, GroupPropertiesMixin, TeacherPropertiesMixin): class Meta: ordering = ["date_start"] - indexes = [models.Index(fields=["period_from", "period_to", "date_start", "date_end"])] + indexes = [ + models.Index(fields=["period_from", "period_to", "date_start", "date_end"]) + ] verbose_name = _("Event") verbose_name_plural = _("Events") @@ -636,15 +811,41 @@ class ExtraLesson(ExtensibleModel, GroupPropertiesMixin): objects = models.Manager.from_queryset(ExtraLessonQuerySet)() - week = models.IntegerField(verbose_name=_("Week"), default=CalendarWeek.current_week) - period = models.ForeignKey("TimePeriod", models.CASCADE, related_name="extra_lessons", verbose_name=_("Time period")) + week = models.IntegerField( + verbose_name=_("Week"), default=CalendarWeek.current_week + ) + period = models.ForeignKey( + "TimePeriod", + models.CASCADE, + related_name="extra_lessons", + verbose_name=_("Time period"), + ) - subject = models.ForeignKey("Subject", on_delete=models.CASCADE, related_name="extra_lessons", verbose_name=_("Subject")) - groups = models.ManyToManyField("core.Group", related_name="extra_lessons", verbose_name=_("Groups")) - teachers = models.ManyToManyField("core.Person", related_name="extra_lessons_as_teacher", verbose_name=_("Teachers")) - room = models.ForeignKey("Room", models.CASCADE, null=True, related_name="extra_lessons", verbose_name=_("Room")) + subject = models.ForeignKey( + "Subject", + on_delete=models.CASCADE, + related_name="extra_lessons", + verbose_name=_("Subject"), + ) + groups = models.ManyToManyField( + "core.Group", related_name="extra_lessons", verbose_name=_("Groups") + ) + teachers = models.ManyToManyField( + "core.Person", + related_name="extra_lessons_as_teacher", + verbose_name=_("Teachers"), + ) + room = models.ForeignKey( + "Room", + models.CASCADE, + null=True, + related_name="extra_lessons", + verbose_name=_("Room"), + ) - comment = models.CharField(verbose_name=_("Comment"), blank=True, null=True, max_length=255) + comment = models.CharField( + verbose_name=_("Comment"), blank=True, null=True, max_length=255 + ) def __str__(self): return "{}, {}, {}".format(self.week, self.period, self.subject) diff --git a/aleksis/apps/chronos/preferences.py b/aleksis/apps/chronos/preferences.py index 69b435a47a964809a02a351f5cb0068dd3c04b33..c0a4294db7d17bb65b9b483a5ea38940d632a0cf 100644 --- a/aleksis/apps/chronos/preferences.py +++ b/aleksis/apps/chronos/preferences.py @@ -3,7 +3,10 @@ from django.utils.translation import gettext as _ from dynamic_preferences.preferences import Section from dynamic_preferences.types import BooleanPreference, IntegerPreference -from aleksis.core.registries import site_preferences_registry, person_preferences_registry +from aleksis.core.registries import ( + site_preferences_registry, + person_preferences_registry, +) chronos = Section("chronos", verbose_name=_("Chronos")) @@ -50,10 +53,11 @@ class SubstitutionsPrintNumberOfDays(IntegerPreference): default = 2 verbose_name = _("Number of days shown on substitutions print view") + @site_preferences_registry.register class SubstitutionsShowHeaderBox(BooleanPreference): section = chronos name = "substitutions_show_header_box" default = True verbose_name = _("Show header box in substitution views") - help_text = _("The header box shows affected teachers/groups.") + help_text = _("The header box shows affected teachers/groups.") diff --git a/aleksis/apps/chronos/rules.py b/aleksis/apps/chronos/rules.py index ddc398cde95665f12e47e4dfe52f5f7c88d4177a..dc0369b08e6121c6be0187baad3471ca98573094 100644 --- a/aleksis/apps/chronos/rules.py +++ b/aleksis/apps/chronos/rules.py @@ -7,12 +7,12 @@ from aleksis.core.util.predicates import ( has_object_perm, ) from .models import LessonSubstitution -from .util.predicates import ( - has_timetable_perm -) +from .util.predicates import has_timetable_perm # View timetable overview -view_timetable_overview_predicate = has_person & has_global_perm("chronos.view_timetable_overview") +view_timetable_overview_predicate = has_person & has_global_perm( + "chronos.view_timetable_overview" +) add_perm("chronos.view_timetable_overview", view_timetable_overview_predicate) # View my timetable @@ -30,18 +30,21 @@ add_perm("chronos.view_lessons_day", view_lessons_day_predicate) # Edit substition edit_substitution_predicate = has_person & ( - has_global_perm("chronos.change_lessonsubstitution") | has_object_perm("chronos.change_lessonsubstitution") + has_global_perm("chronos.change_lessonsubstitution") + | has_object_perm("chronos.change_lessonsubstitution") ) add_perm("chronos.edit_substitution", edit_substitution_predicate) # Delete substitution delete_substitution_predicate = has_person & ( - has_global_perm("chronos.delete_lessonsubstitution") | has_object_perm("chronos.delete_lessonsubstitution") + has_global_perm("chronos.delete_lessonsubstitution") + | has_object_perm("chronos.delete_lessonsubstitution") ) add_perm("chronos.delete_substitution", delete_substitution_predicate) # View substitutions view_substitutions_predicate = has_person & ( - has_global_perm("chronos.view_lessonsubstitution") | has_any_object("chronos.view_lessonsubstitution", LessonSubstitution) + has_global_perm("chronos.view_lessonsubstitution") + | has_any_object("chronos.view_lessonsubstitution", LessonSubstitution) ) add_perm("chronos.view_substitutions", view_substitutions_predicate) diff --git a/aleksis/apps/chronos/tables.py b/aleksis/apps/chronos/tables.py index ed224b4c5470f1b9098ce972595bcd4697aeaf73..92a966fe11a3d866bdd9d160ebba712ab4e3066d 100644 --- a/aleksis/apps/chronos/tables.py +++ b/aleksis/apps/chronos/tables.py @@ -13,6 +13,7 @@ from .models import LessonPeriod def _css_class_from_lesson_state( record: Optional[LessonPeriod] = None, table: Optional[LessonsTable] = None ) -> str: + """Return CSS class depending on lesson state.""" if record.get_substitution(record._week): if record.get_substitution(record._week).cancelled: return "success" @@ -23,16 +24,25 @@ def _css_class_from_lesson_state( class LessonsTable(tables.Table): + """Table for daily lessons and management of substitutions.""" + class Meta: attrs = {"class": "highlight"} 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__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"), - attrs={"a": {"class": "btn-flat waves-effect waves-orange"}}, verbose_name=_("Manage substitution") + "edit_substitution", + args=[A("id"), A("_week")], + text=_("Substitution"), + attrs={"a": {"class": "btn-flat waves-effect waves-orange"}}, + verbose_name=_("Manage substitution"), ) diff --git a/aleksis/apps/chronos/templates/chronos/partials/datepicker.html b/aleksis/apps/chronos/templates/chronos/partials/datepicker.html index ede1fb06e37da69590468018bec6ca5384bd459f..3897461b9b81d79c003479be753ed5eb23ab1234 100644 --- a/aleksis/apps/chronos/templates/chronos/partials/datepicker.html +++ b/aleksis/apps/chronos/templates/chronos/partials/datepicker.html @@ -7,24 +7,24 @@ {% endif %} <div class="col s2 no-padding"> - <a class="waves-effect waves-teal btn-flat btn-flat-medium left" href="{{ url_prev }}"> - <i class="material-icons center">navigate_before</i> - </a> + <a class="waves-effect waves-teal btn-flat btn-flat-medium left" href="{{ url_prev }}"> + <i class="material-icons center">navigate_before</i> + </a> </div> {% if display_date_only %} - <div class="col s8"> + <div class="col s8"> <span class="card-title center-block" id="date"> {{ day|date:"l" }}, {{ day }} </span> - </div> + </div> {% else %} - <div class="col s8 no-padding"> - <input type="text" class="datepicker center-align" id="date"> - </div> + <div class="col s8 no-padding"> + <input type="text" class="datepicker center-align" id="date"> + </div> {% endif %} <div class="col s2 no-padding"> - <a class="waves-effect waves-teal btn-flat btn-flat-medium right" href="{{ url_next }}"> - <i class="material-icons center">navigate_next</i> - </a> + <a class="waves-effect waves-teal btn-flat btn-flat-medium right" href="{{ url_next }}"> + <i class="material-icons center">navigate_next</i> + </a> </div> diff --git a/aleksis/apps/chronos/templates/chronos/partials/week_select.html b/aleksis/apps/chronos/templates/chronos/partials/week_select.html index 6739290f11b07a2b787d3789f912bc284d6f2ec2..f2c8f31cf8653deae4c07dfe082d9bdd0e89d0a1 100644 --- a/aleksis/apps/chronos/templates/chronos/partials/week_select.html +++ b/aleksis/apps/chronos/templates/chronos/partials/week_select.html @@ -11,7 +11,8 @@ <select id="calendar-week-1"> {% for week in weeks %} <option value="{{ week.week }}" {% if week == wanted_week %} - selected {% endif %}>{% trans "CW" %} {{ week.week }} ({{ week.0|date:"SHORT_DATE_FORMAT" }}–{{ week.6|date:"SHORT_DATE_FORMAT" }}) + selected {% endif %}>{% trans "CW" %} {{ week.week }} + ({{ week.0|date:"SHORT_DATE_FORMAT" }}–{{ week.6|date:"SHORT_DATE_FORMAT" }}) </option> {% endfor %} </select> @@ -21,7 +22,8 @@ <select id="calendar-week-2"> {% for week in weeks %} <option value="{{ week.week }}" {% if week == wanted_week %} - selected {% endif %}>{% trans "CW" %} {{ week.week }} ({{ week.0|date:"j.n." }}–{{ week.6|date:"SHORT_DATE_FORMAT" }}) + selected {% endif %}>{% trans "CW" %} {{ week.week }} + ({{ week.0|date:"j.n." }}–{{ week.6|date:"SHORT_DATE_FORMAT" }}) </option> {% endfor %} </select> diff --git a/aleksis/apps/chronos/templates/chronos/substitutions.html b/aleksis/apps/chronos/templates/chronos/substitutions.html index 3f5d70b3c8f29bbebb68fa7593b73a5f2291c7c0..b5f8551d2566191b4fe1a2755e9a72d386e77974 100644 --- a/aleksis/apps/chronos/templates/chronos/substitutions.html +++ b/aleksis/apps/chronos/templates/chronos/substitutions.html @@ -64,7 +64,7 @@ {% include "chronos/partials/subs/groups.html" with type=item.type el=item.el %} </td> <td> - {% include "chronos/partials/subs/period.html" with type=item.type el=item.el %} + {% include "chronos/partials/subs/period.html" with type=item.type el=item.el %} </td> <td> {% include "chronos/partials/subs/teachers.html" with type=item.type el=item.el %} diff --git a/aleksis/apps/chronos/templatetags/common.py b/aleksis/apps/chronos/templatetags/common.py index 2a33c1b04b09d620cfb07fee9fb945f5dc61067b..cfdec15c8a19e0e2281b89490ce9e133000e741c 100644 --- a/aleksis/apps/chronos/templatetags/common.py +++ b/aleksis/apps/chronos/templatetags/common.py @@ -4,7 +4,6 @@ register = template.Library() class SetVarNode(template.Node): - def __init__(self, var_name, var_value): self.var_name = var_name self.var_value = var_value @@ -19,13 +18,15 @@ class SetVarNode(template.Node): return u"" -@register.tag(name='set') +@register.tag(name="set") def set_var(parser, token): """ {% set some_var = '123' %} """ parts = token.split_contents() if len(parts) < 4: - raise template.TemplateSyntaxError("'set' tag must be of the form: {% set <var_name> = <var_value> %}") + raise template.TemplateSyntaxError( + "'set' tag must be of the form: {% set <var_name> = <var_value> %}" + ) return SetVarNode(parts[1], parts[3]) diff --git a/aleksis/apps/chronos/templatetags/week_helpers.py b/aleksis/apps/chronos/templatetags/week_helpers.py index acfb3a3a8859cc50c9e250520d9bcb32f3764ebe..054d3f9e2c3feac25e5067db9df324124a771f88 100644 --- a/aleksis/apps/chronos/templatetags/week_helpers.py +++ b/aleksis/apps/chronos/templatetags/week_helpers.py @@ -4,7 +4,11 @@ from typing import Optional, Union from django import template from django.db.models.query import QuerySet -from aleksis.apps.chronos.util.date import CalendarWeek, week_period_to_date, week_weekday_to_date +from aleksis.apps.chronos.util.date import ( + CalendarWeek, + week_period_to_date, + week_weekday_to_date, +) register = template.Library() diff --git a/aleksis/apps/chronos/urls.py b/aleksis/apps/chronos/urls.py index d5311f492f6592dfb66f4fa5b03b48fc76126188..3d68c3a18451570254e2d5323221bd12a31352ba 100644 --- a/aleksis/apps/chronos/urls.py +++ b/aleksis/apps/chronos/urls.py @@ -5,12 +5,28 @@ from . import views urlpatterns = [ path("", views.all_timetables, name="all_timetables"), path("timetable/my/", views.my_timetable, name="my_timetable"), - path("timetable/my/<int:year>/<int:month>/<int:day>/", views.my_timetable, name="my_timetable_by_date"), + path( + "timetable/my/<int:year>/<int:month>/<int:day>/", + views.my_timetable, + name="my_timetable_by_date", + ), path("timetable/<str:type_>/<int:pk>/", views.timetable, name="timetable"), - path("timetable/<str:type_>/<int:pk>/<int:year>/<int:week>/", views.timetable, name="timetable_by_week"), - path("timetable/<str:type_>/<int:pk>/<str:regular>/", views.timetable, name="timetable_regular"), + path( + "timetable/<str:type_>/<int:pk>/<int:year>/<int:week>/", + views.timetable, + name="timetable_by_week", + ), + path( + "timetable/<str:type_>/<int:pk>/<str:regular>/", + views.timetable, + name="timetable_regular", + ), path("lessons/", views.lessons_day, name="lessons_day"), - path("lessons/<int:year>/<int:month>/<int:day>/", views.lessons_day, name="lessons_day_by_date"), + path( + "lessons/<int:year>/<int:month>/<int:day>/", + views.lessons_day, + name="lessons_day_by_date", + ), path( "lessons/<int:id_>/<int:week>/substition/", views.edit_substitution, @@ -22,7 +38,21 @@ urlpatterns = [ name="delete_substitution", ), path("substitutions/", views.substitutions, name="substitutions"), - path("substitutions/print/", views.substitutions, {"is_print": True}, name="substitutions_print"), - path("substitutions/<int:year>/<int:month>/<int:day>/", views.substitutions, name="substitutions_by_date"), - path("substitutions/<int:year>/<int:month>/<int:day>/print/", views.substitutions, {"is_print": True}, name="substitutions_print_by_date"), + path( + "substitutions/print/", + views.substitutions, + {"is_print": True}, + name="substitutions_print", + ), + path( + "substitutions/<int:year>/<int:month>/<int:day>/", + views.substitutions, + name="substitutions_by_date", + ), + path( + "substitutions/<int:year>/<int:month>/<int:day>/print/", + views.substitutions, + {"is_print": True}, + name="substitutions_print_by_date", + ), ] diff --git a/aleksis/apps/chronos/util/build.py b/aleksis/apps/chronos/util/build.py index 9fbc4ce5a629993983ca3adabc57c3d19f39864e..a73798e99ffaddad4b29d4abbaf22846d3bde184 100644 --- a/aleksis/apps/chronos/util/build.py +++ b/aleksis/apps/chronos/util/build.py @@ -21,7 +21,9 @@ ExtraLesson = apps.get_model("chronos", "ExtraLesson") def build_timetable( - type_: Union[TimetableType, str], obj: Union[int, Person], date_ref: Union[CalendarWeek, date] + type_: Union[TimetableType, str], + obj: Union[int, Person], + date_ref: Union[CalendarWeek, date], ): needed_breaks = [] @@ -59,7 +61,9 @@ def build_timetable( if is_person: extra_lessons = ExtraLesson.objects.on_day(date_ref).filter_from_person(obj) else: - extra_lessons = ExtraLesson.objects.filter(week=date_ref.week).filter_from_type(type_, obj) + extra_lessons = ExtraLesson.objects.filter(week=date_ref.week).filter_from_type( + type_, obj + ) # Sort lesson periods in a dict extra_lessons_per_period = extra_lessons.group_by_periods(is_person=is_person) @@ -108,7 +112,7 @@ def build_timetable( # If not end day, use max period period_to = TimePeriod.period_max - for period in range(period_from, period_to +1): + for period in range(period_from, period_to + 1): if period not in events_per_period: events_per_period[period] = [] if is_person else {} @@ -126,7 +130,9 @@ def build_timetable( week = CalendarWeek.from_date(date_ref) else: week = date_ref - supervisions = Supervision.objects.all().annotate_week(week).filter_by_teacher(obj) + supervisions = ( + Supervision.objects.all().annotate_week(week).filter_by_teacher(obj) + ) if is_person: supervisions.filter_by_weekday(date_ref.weekday()) @@ -280,7 +286,7 @@ def build_substitutions_list(wanted_day: date) -> List[dict]: "type": "supervision_substitution", "sort_a": "Z.{}".format(super_sub.teacher), "sort_b": "{}".format(super_sub.supervision.break_item.after_period_number), - "el": super_sub + "el": super_sub, } rows.append(row) diff --git a/aleksis/apps/chronos/util/format.py b/aleksis/apps/chronos/util/format.py index c85668bff93f274fdf27fc33ec0f07d064eaa901..dbf704c22ff01a413cbfc0f8df538c4386faac07 100644 --- a/aleksis/apps/chronos/util/format.py +++ b/aleksis/apps/chronos/util/format.py @@ -1,9 +1,13 @@ +from datetime import date + from django.utils.formats import date_format -def format_m2m(f, attr: str = "short_name"): +def format_m2m(f, attr: str = "short_name") -> str: + """Join a attribute of all elements of a ManyToManyField.""" return ", ".join([getattr(x, attr) for x in f.all()]) -def format_date_period(date, period): - return "{}, {}.".format(date_format(date), period.period) +def format_date_period(day: date, period: "TimePeriod") -> str: + """Format date and time period.""" + return "{}, {}.".format(date_format(day), period.period) diff --git a/aleksis/apps/chronos/views.py b/aleksis/apps/chronos/views.py index 72821e75ad47d8f112cb507a7f3f7c0d43a66959..532d0ed953445f5b4c3ffb84c074a7c1597bb6ab 100644 --- a/aleksis/apps/chronos/views.py +++ b/aleksis/apps/chronos/views.py @@ -64,7 +64,9 @@ def my_timetable( wanted_day = timezone.datetime(year=year, month=month, day=day).date() wanted_day = TimePeriod.get_next_relevant_day(wanted_day) else: - wanted_day = TimePeriod.get_next_relevant_day(timezone.now().date(), datetime.now().time()) + wanted_day = TimePeriod.get_next_relevant_day( + timezone.now().date(), datetime.now().time() + ) if has_person(request.user): person = request.user.person @@ -86,7 +88,9 @@ def my_timetable( context["day"] = wanted_day context["periods"] = TimePeriod.get_times_dict() context["smart"] = True - context["announcements"] = Announcement.for_timetables().on_date(wanted_day).for_person(person) + context["announcements"] = ( + Announcement.for_timetables().on_date(wanted_day).for_person(person) + ) context["url_prev"], context["url_next"] = TimePeriod.get_prev_next_by_day( wanted_day, "my_timetable_by_date" @@ -148,7 +152,9 @@ def timetable( # Build lists with weekdays and corresponding dates (long and short variant) context["weekdays"] = build_weekdays(TimePeriod.WEEKDAY_CHOICES, wanted_week) - context["weekdays_short"] = build_weekdays(TimePeriod.WEEKDAY_CHOICES_SHORT, wanted_week) + context["weekdays_short"] = build_weekdays( + TimePeriod.WEEKDAY_CHOICES_SHORT, wanted_week + ) context["weeks"] = get_weeks_for_year(year=wanted_week.year) context["week"] = wanted_week @@ -158,13 +164,15 @@ def timetable( context["smart"] = is_smart context["week_select"] = { "year": wanted_week.year, - "dest": reverse("timetable", args=[type_, pk]) + "dest": reverse("timetable", args=[type_, pk]), } if is_smart: start = wanted_week[TimePeriod.weekday_min] stop = wanted_week[TimePeriod.weekday_max] - context["announcements"] = Announcement.for_timetables().relevant_for(el).within_days(start, stop) + context["announcements"] = ( + Announcement.for_timetables().relevant_for(el).within_days(start, stop) + ) week_prev = wanted_week - 1 week_next = wanted_week + 1 @@ -193,7 +201,9 @@ def lessons_day( wanted_day = timezone.datetime(year=year, month=month, day=day).date() wanted_day = TimePeriod.get_next_relevant_day(wanted_day) else: - wanted_day = TimePeriod.get_next_relevant_day(timezone.now().date(), datetime.now().time()) + wanted_day = TimePeriod.get_next_relevant_day( + timezone.now().date(), datetime.now().time() + ) # Get lessons lesson_periods = LessonPeriod.objects.on_day(wanted_day) @@ -208,7 +218,7 @@ def lessons_day( context["datepicker"] = { "date": date_unix(wanted_day), - "dest": reverse("lessons_day") + "dest": reverse("lessons_day"), } context["url_prev"], context["url_next"] = TimePeriod.get_prev_next_by_day( @@ -257,8 +267,7 @@ 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 @@ -281,8 +290,7 @@ def delete_substitution(request: HttpRequest, id_: int, week: int) -> HttpRespon 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 ) @@ -301,7 +309,9 @@ def substitutions( wanted_day = timezone.datetime(year=year, month=month, day=day).date() wanted_day = TimePeriod.get_next_relevant_day(wanted_day) else: - wanted_day = TimePeriod.get_next_relevant_day(timezone.now().date(), datetime.now().time()) + wanted_day = TimePeriod.get_next_relevant_day( + timezone.now().date(), datetime.now().time() + ) day_number = get_site_preferences()["chronos__substitutions_print_number_of_days"] day_contexts = {} @@ -318,11 +328,14 @@ def substitutions( subs = build_substitutions_list(day) day_contexts[day]["substitutions"] = subs - day_contexts[day]["announcements"] = Announcement.for_timetables().on_date(day).filter(show_in_timetables=True) + day_contexts[day]["announcements"] = ( + Announcement.for_timetables().on_date(day).filter(show_in_timetables=True) + ) if get_site_preferences()["chronos__substitutions_show_header_box"]: - subs = LessonSubstitution.objects.on_day(day).order_by("lesson_period__lesson__groups", - "lesson_period__period") + subs = LessonSubstitution.objects.on_day(day).order_by( + "lesson_period__lesson__groups", "lesson_period__period" + ) absences = Absence.objects.on_day(day) day_contexts[day]["absent_teachers"] = absences.absent_teachers() day_contexts[day]["absent_groups"] = absences.absent_groups() diff --git a/poetry.lock b/poetry.lock index 5f2a2c53785d3a0d8a95cacbc0d598716780f600..9aea96d739f672441b972ef118dcf71921827a6c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -14,12 +14,16 @@ calendarweek = "^0.4.3" colour = "^0.1.5" django-any-js = "^1.0" django-bleach = "^0.6.1" +django-cache-memoize = "^0.1.6" django-ckeditor = "^5.8.0" django-colorfield = "^0.2.1" django-dbbackup = "^3.3.0" django-debug-toolbar = "^2.0" +django-dynamic-preferences = "rev develop" django-easy-audit = "^1.2rc1" +django-favicon-plus-reloaded = "^1.0.1" django-filter = "^2.2.0" +django-guardian = "^2.2.0" django-hattori = "^0.2" django-haystack = "3.0b1" django-image-cropping = "^1.2" @@ -29,12 +33,12 @@ django-js-reverse = "^0.9.1" django-jsonstore = "^0.4.1" django-maintenance-mode = "^0.14.0" django-material = "^1.6.0" -django-memoize = "^2.2.1" django-menu-generator = "^1.0.4" django-middleware-global-request = "^0.1.2" django-otp = "0.7.5" django-polymorphic = "^2.1.2" django-pwa = "^1.0.8" +django-reversion = "^3.0.7" django-sass-processor = "^0.8" django-settings-context-processor = "^0.2" django-tables2 = "^2.1" @@ -49,19 +53,16 @@ license-expression = "^1.2" psycopg2 = "^2.8" python-memcached = "^1.59" requests = "^2.22" +rules = "^2.2" spdx-license-list = "^0.4.0" -[package.dependencies.django-constance] -extras = ["database"] -version = "^2.6.0" - [package.dependencies.django-phonenumber-field] extras = ["phonenumbers"] version = ">=3.0, <5.0" [package.dependencies.django-two-factor-auth] -extras = ["YubiKey", "phonenumbers", "Call", "SMS"] -version = "^1.10.0" +extras = ["yubikey", "phonenumbers", "call", "sms"] +version = "^1.11.0" [package.dependencies.dynaconf] extras = ["yaml", "toml", "ini"] @@ -119,9 +120,10 @@ description = "An easy safelist-based HTML-sanitizing tool." name = "bleach" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "3.1.4" +version = "3.1.5" [package.dependencies] +packaging = "*" six = ">=1.9.0" webencodings = "*" @@ -166,7 +168,7 @@ description = "Composable command line interface toolkit" name = "click" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "7.1.1" +version = "7.1.2" [[package]] category = "main" @@ -261,6 +263,17 @@ version = "2.2.0" [package.dependencies] Django = ">=1.8" +[[package]] +category = "main" +description = "Django utility for a memoization decorator that uses the Django cache framework." +name = "django-cache-memoize" +optional = false +python-versions = "*" +version = "0.1.6" + +[package.extras] +dev = ["flake8", "tox", "twine", "therapist", "black"] + [[package]] category = "main" description = "Django admin CKEditor integration." @@ -280,23 +293,6 @@ optional = false python-versions = "*" version = "0.2.2" -[[package]] -category = "main" -description = "Django live settings with pluggable backends, including Redis." -name = "django-constance" -optional = false -python-versions = "*" -version = "2.6.0" - -[package.dependencies] -[package.dependencies.django-picklefield] -optional = true -version = "*" - -[package.extras] -database = ["django-picklefield"] -redis = ["redis"] - [[package]] category = "main" description = "Management commands to help backup and restore a project database and media" @@ -322,6 +318,23 @@ version = "2.2" Django = ">=1.11" sqlparse = ">=0.2.0" +[[package]] +category = "main" +description = "Dynamic global and instance settings for your django project" +name = "django-dynamic-preferences" +optional = false +python-versions = "*" +version = "1.8.1" + +[package.dependencies] +django = ">=1.11" +persisting_theory = ">=0.2.1" +six = "*" + +[package.source] +reference = "3cb2637e99455260e1988fb969bdce16ba7a9801" +type = "git" +url = "https://github.com/EliotBerriot/django-dynamic-preferences" [[package]] category = "main" description = "Yet another Django audit log app, hopefully the simplest one." @@ -333,6 +346,18 @@ version = "1.2.2b4" [package.dependencies] beautifulsoup4 = "*" +[[package]] +category = "main" +description = "simple Django app which allows you to upload a image and it renders a wide variety for html link tags to display the favicon" +name = "django-favicon-plus-reloaded" +optional = false +python-versions = "*" +version = "1.0.1" + +[package.dependencies] +django = "*" +pillow = "*" + [[package]] category = "main" description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically." @@ -355,6 +380,17 @@ version = "2.2" [package.dependencies] Django = ">=1.11" +[[package]] +category = "main" +description = "Implementation of per object permissions for Django." +name = "django-guardian" +optional = false +python-versions = ">=3.5" +version = "2.2.0" + +[package.dependencies] +Django = ">=2.1" + [[package]] category = "main" description = "Command to anonymize sensitive data." @@ -458,17 +494,6 @@ version = "1.6.3" [package.dependencies] six = "*" -[[package]] -category = "main" -description = "An implementation of memoization technique for Django." -name = "django-memoize" -optional = false -python-versions = "*" -version = "2.3.0" - -[package.dependencies] -django = "*" - [[package]] category = "main" description = "A straightforward menu generator for Django" @@ -503,6 +528,19 @@ six = ">=1.10.0" [package.extras] qrcode = ["qrcode"] +[[package]] +category = "main" +description = "A django-otp plugin that verifies YubiKey OTP tokens." +name = "django-otp-yubikey" +optional = false +python-versions = "*" +version = "0.5.2" + +[package.dependencies] +YubiOTP = ">=0.2.2" +django-otp = ">=0.5.0" +six = ">=1.10.0" + [[package]] category = "main" description = "An international phone number field for django models." @@ -515,24 +553,14 @@ version = "3.0.1" Django = ">=1.11.3" babel = "*" +[package.dependencies.phonenumbers] +optional = true +version = ">=7.0.2" + [package.extras] phonenumbers = ["phonenumbers (>=7.0.2)"] phonenumberslite = ["phonenumberslite (>=7.0.2)"] -[[package]] -category = "main" -description = "Pickled object field for Django" -name = "django-picklefield" -optional = false -python-versions = "*" -version = "2.1.1" - -[package.dependencies] -Django = ">=1.11" - -[package.extras] -tests = ["tox"] - [[package]] category = "main" description = "Seamless polymorphic inheritance for Django models" @@ -566,6 +594,17 @@ version = "0.6" [package.dependencies] django = ">=1.11" +[[package]] +category = "main" +description = "An extension to the Django web framework that provides version control for model instances." +name = "django-reversion" +optional = false +python-versions = ">=3.6" +version = "3.0.7" + +[package.dependencies] +django = ">=1.11" + [[package]] category = "main" description = "SASS processor to compile SCSS files into *.css, while rendering, or offline." @@ -639,10 +678,18 @@ django-otp = ">=0.6.0,<0.99" django-phonenumber-field = ">=1.1.0,<3.99" qrcode = ">=4.0.0,<6.99" +[package.dependencies.django-otp-yubikey] +optional = true +version = "*" + [package.dependencies.phonenumbers] optional = true version = ">=7.0.9,<8.99" +[package.dependencies.twilio] +optional = true +version = ">=6.0" + [package.extras] call = ["twilio (>=6.0)"] phonenumbers = ["phonenumbers (>=7.0.9,<8.99)"] @@ -763,13 +810,33 @@ version = "1.2" [package.dependencies] "boolean.py" = ">=3.6,<4.0.0" +[[package]] +category = "main" +description = "Core utilities for Python packages" +name = "packaging" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "20.3" + +[package.dependencies] +pyparsing = ">=2.0.2" +six = "*" + +[[package]] +category = "main" +description = "Registries that can autodiscover values accross your project apps" +name = "persisting-theory" +optional = false +python-versions = "*" +version = "0.2.1" + [[package]] category = "main" description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers." name = "phonenumbers" optional = false python-versions = "*" -version = "8.12.1" +version = "8.12.2" [[package]] category = "main" @@ -777,7 +844,7 @@ description = "Python Imaging Library (Fork)" name = "pillow" optional = false python-versions = ">=3.5" -version = "7.1.1" +version = "7.1.2" [[package]] category = "main" @@ -787,6 +854,35 @@ optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" version = "2.8.5" +[[package]] +category = "main" +description = "Cryptographic library for Python" +name = "pycryptodome" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "3.9.7" + +[[package]] +category = "main" +description = "JSON Web Token implementation in Python" +name = "pyjwt" +optional = false +python-versions = "*" +version = "1.7.1" + +[package.extras] +crypto = ["cryptography (>=1.4)"] +flake8 = ["flake8", "flake8-import-order", "pep8-naming"] +test = ["pytest (>=4.0.1,<5.0.0)", "pytest-cov (>=2.6.0,<3.0.0)", "pytest-runner (>=4.2,<5.0.0)"] + +[[package]] +category = "main" +description = "Python parsing module" +name = "pyparsing" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +version = "2.4.7" + [[package]] category = "main" description = "Advanced Python dictionaries with dot notation access" @@ -837,7 +933,7 @@ description = "World timezone definitions, modern and historical" name = "pytz" optional = false python-versions = "*" -version = "2019.3" +version = "2020.1" [[package]] category = "main" @@ -883,6 +979,14 @@ urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] +[[package]] +category = "main" +description = "Awesome Django authorization, without the database" +name = "rules" +optional = false +python-versions = "*" +version = "2.2" + [[package]] category = "main" description = "Python 2 and 3 compatibility utilities" @@ -937,11 +1041,28 @@ description = "Fast, Extensible Progress Meter" name = "tqdm" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*" -version = "4.45.0" +version = "4.46.0" [package.extras] dev = ["py-make (>=0.1.0)", "twine", "argopt", "pydoc-markdown"] +[[package]] +category = "main" +description = "Twilio API client and TwiML generator" +name = "twilio" +optional = false +python-versions = "*" +version = "6.39.0" + +[package.dependencies] +PyJWT = ">=1.4.2" +pytz = "*" +six = "*" + +[package.dependencies.requests] +python = ">=3.0" +version = ">=2.0.0" + [[package]] category = "main" description = "HTTP library with thread-safe connection pooling, file post, and more." @@ -963,6 +1084,18 @@ optional = false python-versions = "*" version = "0.5.1" +[[package]] +category = "main" +description = "A library for verifying YubiKey OTP tokens, both locally and through a Yubico web service." +name = "yubiotp" +optional = false +python-versions = "*" +version = "0.2.2.post1" + +[package.dependencies] +pycryptodome = "*" +six = "*" + [metadata] content-hash = "f2db0ef57bb256e1e7f9ae769792ce2b3288a37dccbae621f568844e2a656d0c" python-versions = "^3.7" @@ -983,8 +1116,8 @@ beautifulsoup4 = [ {file = "beautifulsoup4-4.9.0.tar.gz", hash = "sha256:594ca51a10d2b3443cbac41214e12dbb2a1cd57e1a7344659849e2e20ba6a8d8"}, ] bleach = [ - {file = "bleach-3.1.4-py2.py3-none-any.whl", hash = "sha256:cc8da25076a1fe56c3ac63671e2194458e0c4d9c7becfd52ca251650d517903c"}, - {file = "bleach-3.1.4.tar.gz", hash = "sha256:e78e426105ac07026ba098f04de8abe9b6e3e98b5befbf89b51a5ef0a4292b03"}, + {file = "bleach-3.1.5-py2.py3-none-any.whl", hash = "sha256:2bce3d8fab545a6528c8fa5d9f9ae8ebc85a56da365c7f85180bfe96a35ef22f"}, + {file = "bleach-3.1.5.tar.gz", hash = "sha256:3c4c520fdb9db59ef139915a5db79f8b51bc2a7257ea0389f30c846883430a4b"}, ] "boolean.py" = [ {file = "boolean.py-3.7-py2.py3-none-any.whl", hash = "sha256:82ae181f9c85cb5c893a5a4daba9f24d60b538a7dd27fd0c6752a77eba4fbeff"}, @@ -1003,8 +1136,8 @@ chardet = [ {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, ] click = [ - {file = "click-7.1.1-py2.py3-none-any.whl", hash = "sha256:e345d143d80bf5ee7534056164e5e112ea5e22716bbb1ce727941f4c8b471b9a"}, - {file = "click-7.1.1.tar.gz", hash = "sha256:8a18b4ea89d8820c5d0c7da8a64b2c324b4dabb695804dbfea19b9be9d88c0cc"}, + {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, + {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, ] colorama = [ {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, @@ -1036,6 +1169,10 @@ django-bulk-update = [ {file = "django-bulk-update-2.2.0.tar.gz", hash = "sha256:5ab7ce8a65eac26d19143cc189c0f041d5c03b9d1b290ca240dc4f3d6aaeb337"}, {file = "django_bulk_update-2.2.0-py2.py3-none-any.whl", hash = "sha256:49a403392ae05ea872494d74fb3dfa3515f8df5c07cc277c3dc94724c0ee6985"}, ] +django-cache-memoize = [ + {file = "django-cache-memoize-0.1.6.tar.gz", hash = "sha256:7f271be70b11155929ee8a4a2b5f53c9fb46b9befa1b546caffa3298e6ac8f7d"}, + {file = "django_cache_memoize-0.1.6-py2.py3-none-any.whl", hash = "sha256:d239e8c37734b0a70b74f94fa33b180b3b0c82c3784beb21209bb4ab64a3e6fb"}, +] django-ckeditor = [ {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"}, @@ -1044,9 +1181,6 @@ django-colorfield = [ {file = "django-colorfield-0.2.2.tar.gz", hash = "sha256:49cfce71365de88130e65ced8f2c5c4826b31e9ab0c5f0e721ff13a830b5be76"}, {file = "django_colorfield-0.2.2-py2-none-any.whl", hash = "sha256:ecb8af68f35028e35f973ddb687c2dcae86d028c6da1b72580c0d3fae915d3b7"}, ] -django-constance = [ - {file = "django-constance-2.6.0.tar.gz", hash = "sha256:12d827f9d5552ee39884fb6fb356f231f32b1ab8958acc715e3d1a6ecf913653"}, -] django-dbbackup = [ {file = "django-dbbackup-3.3.0.tar.gz", hash = "sha256:bb109735cae98b64ad084e5b461b7aca2d7b39992f10c9ed9435e3ebb6fb76c8"}, ] @@ -1054,10 +1188,15 @@ django-debug-toolbar = [ {file = "django-debug-toolbar-2.2.tar.gz", hash = "sha256:eabbefe89881bbe4ca7c980ff102e3c35c8e8ad6eb725041f538988f2f39a943"}, {file = "django_debug_toolbar-2.2-py3-none-any.whl", hash = "sha256:ff94725e7aae74b133d0599b9bf89bd4eb8f5d2c964106e61d11750228c8774c"}, ] +django-dynamic-preferences = [] django-easy-audit = [ {file = "django-easy-audit-1.2.2b4.tar.gz", hash = "sha256:eac94b76882c6ad3fdb76d15f4f4ea281dc61e0897e92a457e058b87ed21ff68"}, {file = "django_easy_audit-1.2.2b4-py3-none-any.whl", hash = "sha256:49ef3beea7bf439b349daa66d5e3d7624a7c9005d3bfd51f54d15dd5dcfaa202"}, ] +django-favicon-plus-reloaded = [ + {file = "django-favicon-plus-reloaded-1.0.1.tar.gz", hash = "sha256:ee48b9a86ee20e5285216dc76c69cc6e8bcdcb1ea83c2585785dff7a4eebfc7b"}, + {file = "django_favicon_plus_reloaded-1.0.1-py3-none-any.whl", hash = "sha256:d796bb994aa648a1d9c8276674a4de4e6af3ef2583dcd4d45495b4d3df85e283"}, +] django-filter = [ {file = "django-filter-2.2.0.tar.gz", hash = "sha256:c3deb57f0dd7ff94d7dce52a047516822013e2b441bed472b722a317658cfd14"}, {file = "django_filter-2.2.0-py3-none-any.whl", hash = "sha256:558c727bce3ffa89c4a7a0b13bc8976745d63e5fd576b3a9a851650ef11c401b"}, @@ -1066,6 +1205,10 @@ django-formtools = [ {file = "django-formtools-2.2.tar.gz", hash = "sha256:c5272c03c1cd51b2375abf7397a199a3148a9fbbf2f100e186467a84025d13b2"}, {file = "django_formtools-2.2-py2.py3-none-any.whl", hash = "sha256:304fa777b8ef9e0693ce7833f885cb89ba46b0e46fc23b01176900a93f46742f"}, ] +django-guardian = [ + {file = "django-guardian-2.2.0.tar.gz", hash = "sha256:8cacf49ebcc1e545f0a8997971eec0fe109f5ed31fc2a569a7bf5615453696e2"}, + {file = "django_guardian-2.2.0-py3-none-any.whl", hash = "sha256:ac81e88372fdf1795d84ba065550e739b42e9c6d07cdf201cf5bbf9efa7f396c"}, +] django-hattori = [ {file = "django-hattori-0.2.1.tar.gz", hash = "sha256:6953d40881317252f19f62c4e7fe8058924b852c7498bc42beb7bc4d268c252c"}, {file = "django_hattori-0.2.1-py2.py3-none-any.whl", hash = "sha256:e529ed7af8fc34a0169c797c477672b687a205a56f3f5206f90c260acb83b7ac"}, @@ -1103,9 +1246,6 @@ django-material = [ {file = "django-material-1.6.3.tar.gz", hash = "sha256:f8758afe1beabc16a3c54f5437c7fea15946b7d068eedd89c97d57a363793950"}, {file = "django_material-1.6.3-py2.py3-none-any.whl", hash = "sha256:502dc88c2f61f190fdc401666e83b47da00cbda98477af6ed8b7d43944ce6407"}, ] -django-memoize = [ - {file = "django-memoize-2.3.0.tar.gz", hash = "sha256:85decffbef7d38ffc569dc96527f598e6677bbc01ce29adf722b051da7efd4be"}, -] django-menu-generator = [ {file = "django-menu-generator-1.0.4.tar.gz", hash = "sha256:ce71a5055c16933c8aff64fb36c21e5cf8b6d505733aceed1252f8b99369a378"}, ] @@ -1116,14 +1256,14 @@ django-otp = [ {file = "django-otp-0.7.5.tar.gz", hash = "sha256:1f16c2b93fe484706ff16ac6f5e64ecc73dd240318c333e0560384ba548d3837"}, {file = "django_otp-0.7.5-py2.py3-none-any.whl", hash = "sha256:cd4975539be478417033561e9832a1a69a583189f680e92a649f412c661f90aa"}, ] +django-otp-yubikey = [ + {file = "django-otp-yubikey-0.5.2.tar.gz", hash = "sha256:f0b1881562fb42ee9f12c28d284cbdb90d1f0383f2d53a595373b080a19bc261"}, + {file = "django_otp_yubikey-0.5.2-py2.py3-none-any.whl", hash = "sha256:26b12c763b37e99b95b8b8a54d06d8d54c3774eb26133a452f54558033de732b"}, +] django-phonenumber-field = [ {file = "django-phonenumber-field-3.0.1.tar.gz", hash = "sha256:794ebbc3068a7af75aa72a80cb0cec67e714ff8409a965968040f1fd210b2d97"}, {file = "django_phonenumber_field-3.0.1-py3-none-any.whl", hash = "sha256:1ab19f723928582fed412bd9844221fa4ff466276d8526b8b4a9913ee1487c5e"}, ] -django-picklefield = [ - {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-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"}, @@ -1135,6 +1275,10 @@ django-pwa = [ django-render-block = [ {file = "django_render_block-0.6-py2.py3-none-any.whl", hash = "sha256:95c7dc9610378a10e0c4a10d8364ec7307210889afccd6a67a6aaa0fd599bd4d"}, ] +django-reversion = [ + {file = "django-reversion-3.0.7.tar.gz", hash = "sha256:72fc53580a6b538f0cfff10f27f42333f67d79c406399289c94ec5a193cfb3e1"}, + {file = "django_reversion-3.0.7-py3-none-any.whl", hash = "sha256:ecab4703ecc0871dc325c3e100139def84eb153622df3413fbcd9de7d3503c78"}, +] django-sass-processor = [ {file = "django-sass-processor-0.8.tar.gz", hash = "sha256:e039551994feaaba6fcf880412b25a772dd313162a34cbb4289814988cfae340"}, ] @@ -1190,6 +1334,7 @@ libsass = [ {file = "libsass-0.19.4-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:81a013a4c2a614927fd1ef7a386eddabbba695cbb02defe8f31cf495106e974c"}, {file = "libsass-0.19.4-cp35-cp35m-win32.whl", hash = "sha256:fcb7ab4dc81889e5fc99cafbc2017bc76996f9992fc6b175f7a80edac61d71df"}, {file = "libsass-0.19.4-cp35-cp35m-win_amd64.whl", hash = "sha256:fc5f8336750f76f1bfae82f7e9e89ae71438d26fc4597e3ab4c05ca8fcd41d8a"}, + {file = "libsass-0.19.4-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:53f87116e7441827878bd79bbad8debac23e1930423f61ab8d837ec4a4c36e0c"}, {file = "libsass-0.19.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:9b59afa0d755089c4165516400a39a289b796b5612eeef5736ab7a1ebf96a67c"}, {file = "libsass-0.19.4-cp36-cp36m-win32.whl", hash = "sha256:c93df526eeef90b1ea4799c1d33b6cd5aea3e9f4633738fb95c1287c13e6b404"}, {file = "libsass-0.19.4-cp36-cp36m-win_amd64.whl", hash = "sha256:0fd8b4337b3b101c6e6afda9112cc0dc4bacb9133b59d75d65968c7317aa3272"}, @@ -1197,6 +1342,7 @@ libsass = [ {file = "libsass-0.19.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:338e9ae066bf1fde874e335324d5355c52d2081d978b4f74fc59536564b35b08"}, {file = "libsass-0.19.4-cp37-cp37m-win32.whl", hash = "sha256:e318f06f06847ff49b1f8d086ac9ebce1e63404f7ea329adab92f4f16ba0e00e"}, {file = "libsass-0.19.4-cp37-cp37m-win_amd64.whl", hash = "sha256:a7e685466448c9b1bf98243339793978f654a1151eb5c975f09b83c7a226f4c1"}, + {file = "libsass-0.19.4-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:0fb4399f7bbecab7b181f2c2d82c3a0ba2916bf9169714b96e425355a5b23b9f"}, {file = "libsass-0.19.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6a51393d75f6e3c812785b0fa0b7d67c54258c28011921f204643b55f7355ec0"}, {file = "libsass-0.19.4.tar.gz", hash = "sha256:8b5b6d1a7c4ea1d954e0982b04474cc076286493f6af2d0a13c2e950fbe0be95"}, ] @@ -1204,34 +1350,41 @@ license-expression = [ {file = "license-expression-1.2.tar.gz", hash = "sha256:7960e1dfdf20d127e75ead931476f2b5c7556df05b117a73880b22ade17d1abc"}, {file = "license_expression-1.2-py2.py3-none-any.whl", hash = "sha256:6d97906380cecfc758a77f6d38c6760f2afade7e83d2b8295e234fe21f486fb8"}, ] +packaging = [ + {file = "packaging-20.3-py2.py3-none-any.whl", hash = "sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752"}, + {file = "packaging-20.3.tar.gz", hash = "sha256:3c292b474fda1671ec57d46d739d072bfd495a4f51ad01a055121d81e952b7a3"}, +] +persisting-theory = [ + {file = "persisting-theory-0.2.1.tar.gz", hash = "sha256:00ff7dcc8f481ff75c770ca5797d968e8725b6df1f77fe0cf7d20fa1e5790c0a"}, +] phonenumbers = [ - {file = "phonenumbers-8.12.1-py2.py3-none-any.whl", hash = "sha256:bebf881ef0e775b93062fbd107bf164b5baef877a7b8f702e93a9a5d24ae4065"}, - {file = "phonenumbers-8.12.1.tar.gz", hash = "sha256:59ae9cb25fb03027c9f2bf5584098e699be7eca12c443838b83752956be15cda"}, + {file = "phonenumbers-8.12.2-py2.py3-none-any.whl", hash = "sha256:eedbace07295109ce98b13b9bd1ac22dd43c1e90a3f0854c557c2298493fc731"}, + {file = "phonenumbers-8.12.2.tar.gz", hash = "sha256:61adadab01adaac571b04ddbe50f981c488ef00cfd51eef7e040ef4765871b00"}, ] pillow = [ - {file = "Pillow-7.1.1-cp35-cp35m-macosx_10_10_intel.whl", hash = "sha256:b7453750cf911785009423789d2e4e5393aae9cbb8b3f471dab854b85a26cb89"}, - {file = "Pillow-7.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:4510c6b33277970b1af83c987277f9a08ec2b02cc20ac0f9234e4026136bb137"}, - {file = "Pillow-7.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b99b2607b6cd58396f363b448cbe71d3c35e28f03e442ab00806463439629c2c"}, - {file = "Pillow-7.1.1-cp35-cp35m-win32.whl", hash = "sha256:cd47793f7bc9285a88c2b5551d3f16a2ddd005789614a34c5f4a598c2a162383"}, - {file = "Pillow-7.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:04a10558320eba9137d6a78ca6fc8f4a5801f1b971152938851dc4629d903579"}, - {file = "Pillow-7.1.1-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:50a10b048f4dd81c092adad99fa5f7ba941edaf2f9590510109ac2a15e706695"}, - {file = "Pillow-7.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:721c04d3c77c38086f1f95d1cd8df87f2f9a505a780acf8575912b3206479da1"}, - {file = "Pillow-7.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:a5dc9f28c0239ec2742d4273bd85b2aa84655be2564db7ad1eb8f64b1efcdc4c"}, - {file = "Pillow-7.1.1-cp36-cp36m-win32.whl", hash = "sha256:d6bf085f6f9ec6a1724c187083b37b58a8048f86036d42d21802ed5d1fae4853"}, - {file = "Pillow-7.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:251e5618125ec12ac800265d7048f5857a8f8f1979db9ea3e11382e159d17f68"}, - {file = "Pillow-7.1.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:433bbc2469a2351bea53666d97bb1eb30f0d56461735be02ea6b27654569f80f"}, - {file = "Pillow-7.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:eb84e7e5b07ff3725ab05977ac56d5eeb0c510795aeb48e8b691491be3c5745b"}, - {file = "Pillow-7.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:3713386d1e9e79cea1c5e6aaac042841d7eef838cc577a3ca153c8bedf570287"}, - {file = "Pillow-7.1.1-cp37-cp37m-win32.whl", hash = "sha256:291bad7097b06d648222b769bbfcd61e40d0abdfe10df686d20ede36eb8162b6"}, - {file = "Pillow-7.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:6c1924ed7dbc6ad0636907693bbbdd3fdae1d73072963e71f5644b864bb10b4d"}, - {file = "Pillow-7.1.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:670e58d3643971f4afd79191abd21623761c2ebe61db1c2cb4797d817c4ba1a7"}, - {file = "Pillow-7.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:8d5799243050c2833c2662b824dfb16aa98e408d2092805edea4300a408490e7"}, - {file = "Pillow-7.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:da737ab273f4d60ae552f82ad83f7cbd0e173ca30ca20b160f708c92742ee212"}, - {file = "Pillow-7.1.1-cp38-cp38-win32.whl", hash = "sha256:b2f3e8cc52ecd259b94ca880fea0d15f4ebc6da2cd3db515389bb878d800270f"}, - {file = "Pillow-7.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:2f0b52a08d175f10c8ea36685115681a484c55d24d0933f9fd911e4111c04144"}, - {file = "Pillow-7.1.1-pp373-pypy36_pp73-win32.whl", hash = "sha256:90cd441a1638ae176eab4d8b6b94ab4ec24b212ed4c3fbee2a6e74672481d4f8"}, - {file = "Pillow-7.1.1-py3.8-macosx-10.9-x86_64.egg", hash = "sha256:5eef904c82b5f8e4256e8d420c971357da2884c0b812ba4efa15a7ad2ec66247"}, - {file = "Pillow-7.1.1.tar.gz", hash = "sha256:0f89ddc77cf421b8cd34ae852309501458942bf370831b4a9b406156b599a14e"}, + {file = "Pillow-7.1.2-cp35-cp35m-macosx_10_10_intel.whl", hash = "sha256:ae2b270f9a0b8822b98655cb3a59cdb1bd54a34807c6c56b76dd2e786c3b7db3"}, + {file = "Pillow-7.1.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:d23e2aa9b969cf9c26edfb4b56307792b8b374202810bd949effd1c6e11ebd6d"}, + {file = "Pillow-7.1.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b532bcc2f008e96fd9241177ec580829dee817b090532f43e54074ecffdcd97f"}, + {file = "Pillow-7.1.2-cp35-cp35m-win32.whl", hash = "sha256:12e4bad6bddd8546a2f9771485c7e3d2b546b458ae8ff79621214119ac244523"}, + {file = "Pillow-7.1.2-cp35-cp35m-win_amd64.whl", hash = "sha256:9744350687459234867cbebfe9df8f35ef9e1538f3e729adbd8fde0761adb705"}, + {file = "Pillow-7.1.2-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:f54be399340aa602066adb63a86a6a5d4f395adfdd9da2b9a0162ea808c7b276"}, + {file = "Pillow-7.1.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:1f694e28c169655c50bb89a3fa07f3b854d71eb47f50783621de813979ba87f3"}, + {file = "Pillow-7.1.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:f784aad988f12c80aacfa5b381ec21fd3f38f851720f652b9f33facc5101cf4d"}, + {file = "Pillow-7.1.2-cp36-cp36m-win32.whl", hash = "sha256:b37bb3bd35edf53125b0ff257822afa6962649995cbdfde2791ddb62b239f891"}, + {file = "Pillow-7.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:b67a6c47ed963c709ed24566daa3f95a18f07d3831334da570c71da53d97d088"}, + {file = "Pillow-7.1.2-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:eaa83729eab9c60884f362ada982d3a06beaa6cc8b084cf9f76cae7739481dfa"}, + {file = "Pillow-7.1.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f46e0e024346e1474083c729d50de909974237c72daca05393ee32389dabe457"}, + {file = "Pillow-7.1.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:0e2a3bceb0fd4e0cb17192ae506d5f082b309ffe5fc370a5667959c9b2f85fa3"}, + {file = "Pillow-7.1.2-cp37-cp37m-win32.whl", hash = "sha256:ccc9ad2460eb5bee5642eaf75a0438d7f8887d484490d5117b98edd7f33118b7"}, + {file = "Pillow-7.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b943e71c2065ade6fef223358e56c167fc6ce31c50bc7a02dd5c17ee4338e8ac"}, + {file = "Pillow-7.1.2-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:04766c4930c174b46fd72d450674612ab44cca977ebbcc2dde722c6933290107"}, + {file = "Pillow-7.1.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:f455efb7a98557412dc6f8e463c1faf1f1911ec2432059fa3e582b6000fc90e2"}, + {file = "Pillow-7.1.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:ee94fce8d003ac9fd206496f2707efe9eadcb278d94c271f129ab36aa7181344"}, + {file = "Pillow-7.1.2-cp38-cp38-win32.whl", hash = "sha256:4b02b9c27fad2054932e89f39703646d0c543f21d3cc5b8e05434215121c28cd"}, + {file = "Pillow-7.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:3d25dd8d688f7318dca6d8cd4f962a360ee40346c15893ae3b95c061cdbc4079"}, + {file = "Pillow-7.1.2-pp373-pypy36_pp73-win32.whl", hash = "sha256:0f01e63c34f0e1e2580cc0b24e86a5ccbbfa8830909a52ee17624c4193224cd9"}, + {file = "Pillow-7.1.2-py3.8-macosx-10.9-x86_64.egg", hash = "sha256:70e3e0d99a0dcda66283a185f80697a9b08806963c6149c8e6c5f452b2aa59c0"}, + {file = "Pillow-7.1.2.tar.gz", hash = "sha256:a0b49960110bc6ff5fead46013bcb8825d101026d466f3a4de3476defe0fb0dd"}, ] psycopg2 = [ {file = "psycopg2-2.8.5-cp27-cp27m-win32.whl", hash = "sha256:a0984ff49e176062fcdc8a5a2a670c9bb1704a2f69548bce8f8a7bad41c661bf"}, @@ -1248,6 +1401,46 @@ psycopg2 = [ {file = "psycopg2-2.8.5-cp38-cp38-win_amd64.whl", hash = "sha256:132efc7ee46a763e68a815f4d26223d9c679953cd190f1f218187cb60decf535"}, {file = "psycopg2-2.8.5.tar.gz", hash = "sha256:f7d46240f7a1ae1dd95aab38bd74f7428d46531f69219954266d669da60c0818"}, ] +pycryptodome = [ + {file = "pycryptodome-3.9.7-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:0e10f352ccbbcb5bb2dc4ecaf106564e65702a717d72ab260f9ac4c19753cfc2"}, + {file = "pycryptodome-3.9.7-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:9c739b7795ccf2ef1fdad8d44e539a39ad300ee6786e804ea7f0c6a786eb5343"}, + {file = "pycryptodome-3.9.7-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9977086e0f93adb326379897437373871b80501e1d176fec63c7f46fb300c862"}, + {file = "pycryptodome-3.9.7-cp27-cp27m-win32.whl", hash = "sha256:83295a3fb5cf50c48631eb5b440cb5e9832d8c14d81d1d45f4497b67a9987de8"}, + {file = "pycryptodome-3.9.7-cp27-cp27m-win_amd64.whl", hash = "sha256:b1e332587b3b195542e77681389c296e1837ca01240399d88803a075447d3557"}, + {file = "pycryptodome-3.9.7-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:9378c309aec1f8cd8bad361ed0816a440151b97a2a3f6ffdaba1d1a1fb76873a"}, + {file = "pycryptodome-3.9.7-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d4f94368ce2d65873a87ad867eb3bf63f4ba81eb97a9ee66d38c2b71ce5a7439"}, + {file = "pycryptodome-3.9.7-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:f655addaaaa9974108d4808f4150652589cada96074c87115c52e575bfcd87d5"}, + {file = "pycryptodome-3.9.7-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:9a94fca11fdc161460bd8659c15b6adef45c1b20da86402256eaf3addfaab324"}, + {file = "pycryptodome-3.9.7-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:ea83bcd9d6c03248ebd46e71ac313858e0afd5aa2fa81478c0e653242f3eb476"}, + {file = "pycryptodome-3.9.7-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:07024fc364869eae8d6ac0d316e089956e6aeffe42dbdcf44fe1320d96becf7f"}, + {file = "pycryptodome-3.9.7-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:426c188c83c10df71f053e04b4003b1437bae5cb37606440e498b00f160d71d0"}, + {file = "pycryptodome-3.9.7-cp35-cp35m-win32.whl", hash = "sha256:d61b012baa8c2b659e9890011358455c0019a4108536b811602d2f638c40802a"}, + {file = "pycryptodome-3.9.7-cp35-cp35m-win_amd64.whl", hash = "sha256:1f4752186298caf2e9ff5354f2e694d607ca7342aa313a62005235d46e28cf04"}, + {file = "pycryptodome-3.9.7-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:767ad0fb5d23efc36a4d5c2fc608ac603f3de028909bcf59abc943e0d0bc5a36"}, + {file = "pycryptodome-3.9.7-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:2fbc472e0b567318fe2052281d5a8c0ae70099b446679815f655e9fbc18c3a65"}, + {file = "pycryptodome-3.9.7-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:9230fcb5d948c3fb40049bace4d33c5d254f8232c2c0bba05d2570aea3ba4520"}, + {file = "pycryptodome-3.9.7-cp36-cp36m-win32.whl", hash = "sha256:8f06556a8f7ea7b1e42eff39726bb0dca1c251205debae64e6eebea3cd7b438a"}, + {file = "pycryptodome-3.9.7-cp36-cp36m-win_amd64.whl", hash = "sha256:d6e1bc5c94873bec742afe2dfadce0d20445b18e75c47afc0c115b19e5dd38dd"}, + {file = "pycryptodome-3.9.7-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:3ec3dc2f80f71fd0c955ce48b81bfaf8914c6f63a41a738f28885a1c4892968a"}, + {file = "pycryptodome-3.9.7-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:cff31f5a8977534f255f729d5d2467526f2b10563a30bbdade92223e0bf264bd"}, + {file = "pycryptodome-3.9.7-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ed5761b37615a1f222c5345bbf45272ae2cf8c7dff88a4f53a1e9f977cbb6d95"}, + {file = "pycryptodome-3.9.7-cp37-cp37m-win32.whl", hash = "sha256:f011cd0062e54658b7086a76f8cf0f4222812acc66e219e196ea2d0a8849d0ed"}, + {file = "pycryptodome-3.9.7-cp37-cp37m-win_amd64.whl", hash = "sha256:626c0a1d4d83ec6303f970a17158114f75c3ba1736f7f2983f7b40a265861bd8"}, + {file = "pycryptodome-3.9.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8be56bde3312e022d9d1d6afa124556460ad5c844c2fc63642f6af723c098d35"}, + {file = "pycryptodome-3.9.7-cp38-cp38-manylinux1_i686.whl", hash = "sha256:c818dc1f3eace93ee50c2b6b5c2becf7c418fa5dd1ba6fc0ef7db279ea21d5e4"}, + {file = "pycryptodome-3.9.7-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:09b6d6bcc01a4eb1a2b4deeff5aa602a108ec5aed8ac75ae554f97d1d7f0a5ad"}, + {file = "pycryptodome-3.9.7-cp38-cp38-win32.whl", hash = "sha256:7ac729d9091ed5478af2b4a4f44f5335a98febbc008af619e4569a59fe503e40"}, + {file = "pycryptodome-3.9.7-cp38-cp38-win_amd64.whl", hash = "sha256:c109a26a21f21f695d369ff9b87f5d43e0d6c768d8384e10bc74142bed2e092e"}, + {file = "pycryptodome-3.9.7.tar.gz", hash = "sha256:f1add21b6d179179b3c177c33d18a2186a09cc0d3af41ff5ed3f377360b869f2"}, +] +pyjwt = [ + {file = "PyJWT-1.7.1-py2.py3-none-any.whl", hash = "sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e"}, + {file = "PyJWT-1.7.1.tar.gz", hash = "sha256:8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96"}, +] +pyparsing = [ + {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, + {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, +] python-box = [ {file = "python-box-3.4.6.tar.gz", hash = "sha256:694a7555e3ff9fbbce734bbaef3aad92b8e4ed0659d3ed04d56b6a0a0eff26a9"}, {file = "python_box-3.4.6-py2.py3-none-any.whl", hash = "sha256:a71d3dc9dbaa34c8597d3517c89a8041bd62fa875f23c0f3dad55e1958e3ce10"}, @@ -1265,8 +1458,8 @@ python-memcached = [ {file = "python_memcached-1.59-py2.py3-none-any.whl", hash = "sha256:4dac64916871bd3550263323fc2ce18e1e439080a2d5670c594cf3118d99b594"}, ] pytz = [ - {file = "pytz-2019.3-py2.py3-none-any.whl", hash = "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d"}, - {file = "pytz-2019.3.tar.gz", hash = "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"}, + {file = "pytz-2020.1-py2.py3-none-any.whl", hash = "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed"}, + {file = "pytz-2020.1.tar.gz", hash = "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"}, ] pyyaml = [ {file = "PyYAML-5.3.1-cp27-cp27m-win32.whl", hash = "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f"}, @@ -1289,6 +1482,9 @@ requests = [ {file = "requests-2.23.0-py2.py3-none-any.whl", hash = "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee"}, {file = "requests-2.23.0.tar.gz", hash = "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"}, ] +rules = [ + {file = "rules-2.2.tar.gz", hash = "sha256:9bae429f9d4f91a375402990da1541f9e093b0ac077221d57124d06eeeca4405"}, +] six = [ {file = "six-1.14.0-py2.py3-none-any.whl", hash = "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"}, {file = "six-1.14.0.tar.gz", hash = "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a"}, @@ -1315,8 +1511,11 @@ toml = [ {file = "toml-0.10.0.tar.gz", hash = "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c"}, ] tqdm = [ - {file = "tqdm-4.45.0-py2.py3-none-any.whl", hash = "sha256:ea9e3fd6bd9a37e8783d75bfc4c1faf3c6813da6bd1c3e776488b41ec683af94"}, - {file = "tqdm-4.45.0.tar.gz", hash = "sha256:00339634a22c10a7a22476ee946bbde2dbe48d042ded784e4d88e0236eca5d81"}, + {file = "tqdm-4.46.0-py2.py3-none-any.whl", hash = "sha256:acdafb20f51637ca3954150d0405ff1a7edde0ff19e38fb99a80a66210d2a28f"}, + {file = "tqdm-4.46.0.tar.gz", hash = "sha256:4733c4a10d0f2a4d098d801464bdaf5240c7dadd2a7fde4ee93b0a0efd9fb25e"}, +] +twilio = [ + {file = "twilio-6.39.0.tar.gz", hash = "sha256:7ef6ad19251fee6a41f1184e97b4fcb62f4a8c0e6f4b78797e40e9c92aed006d"}, ] urllib3 = [ {file = "urllib3-1.25.9-py2.py3-none-any.whl", hash = "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"}, @@ -1326,3 +1525,7 @@ webencodings = [ {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, ] +yubiotp = [ + {file = "YubiOTP-0.2.2.post1-py2.py3-none-any.whl", hash = "sha256:7e281801b24678f4bda855ce8ab975a7688a912f5a6cb22b6c2b16263a93cbd2"}, + {file = "YubiOTP-0.2.2.post1.tar.gz", hash = "sha256:de83b1560226e38b5923f6ab919f962c8c2abb7c722104cb45b2b6db2ac86e40"}, +]