diff --git a/aleksis/apps/alsijil/migrations/0007_personal_note_lesson_documentation_year.py b/aleksis/apps/alsijil/migrations/0007_personal_note_lesson_documentation_year.py new file mode 100644 index 0000000000000000000000000000000000000000..4cf8743b0ba5a2e16e0e1dcb491fe49b72dbb9c4 --- /dev/null +++ b/aleksis/apps/alsijil/migrations/0007_personal_note_lesson_documentation_year.py @@ -0,0 +1,55 @@ +# Generated by Django 3.0.9 on 2020-08-15 09:39 + +from django.db import migrations, models + +import aleksis.apps.chronos.util.date + + +def migrate_data(apps, schema_editor): + PersonalNote = apps.get_model("alsijil", "PersonalNote") + LessonDocumentation = apps.get_model("alsijil", "LessonDocumentation") + + db_alias = schema_editor.connection.alias + + for note in PersonalNote.objects.using(db_alias).all(): + year = note.lesson_period.lesson.validity.date_start.year + if note.week < int( + note.lesson_period.lesson.validity.date_start.strftime("%V") + ): + year += 1 + note.year = year + note.save() + + for doc in LessonDocumentation.objects.using(db_alias).all(): + year = doc.lesson_period.lesson.validity.date_start.year + if doc.week < int(doc.lesson_period.lesson.validity.date_start.strftime("%V")): + year += 1 + doc.year = year + doc.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ("alsijil", "0006_delete_personal_notes_filter"), + ] + + operations = [ + migrations.AddField( + model_name="lessondocumentation", + name="year", + field=models.IntegerField( + default=aleksis.apps.chronos.util.date.get_current_year, + verbose_name="Year", + ), + ), + migrations.AddField( + model_name="personalnote", + name="year", + field=models.IntegerField( + default=aleksis.apps.chronos.util.date.get_current_year, + verbose_name="Year", + ), + ), + migrations.RunPython(migrate_data), + ] diff --git a/aleksis/apps/alsijil/model_extensions.py b/aleksis/apps/alsijil/model_extensions.py index 3f0b0df19bc8ed0ceb02b52f209fa7b74b668d64..d840b273b6cc3ddd8b8cbc682219836247fffc03 100644 --- a/aleksis/apps/alsijil/model_extensions.py +++ b/aleksis/apps/alsijil/model_extensions.py @@ -46,8 +46,10 @@ def mark_absent( person=self, lesson_period=lesson_period, week=wanted_week.week, + year=wanted_week.year, defaults={"absent": absent, "excused": excused, "excuse_type": excuse_type}, ) + personal_note.groups_of_person.set(self.member_of.all()) if remarks: if personal_note.remarks: @@ -74,7 +76,10 @@ def get_personal_notes(self, persons: QuerySet, wanted_week: CalendarWeek): missing_persons = persons.annotate( no_personal_notes=~Exists( PersonalNote.objects.filter( - week=wanted_week.week, lesson_period=self, person__pk=OuterRef("pk") + week=wanted_week.week, + year=wanted_week.year, + lesson_period=self, + person__pk=OuterRef("pk"), ) ) ).filter( @@ -84,15 +89,22 @@ def get_personal_notes(self, persons: QuerySet, wanted_week: CalendarWeek): ) # Create all missing personal notes - PersonalNote.objects.bulk_create( - [ - PersonalNote(person=person, lesson_period=self, week=wanted_week.week) - for person in missing_persons - ] - ) + new_personal_notes = [ + PersonalNote( + person=person, + lesson_period=self, + week=wanted_week.week, + year=wanted_week.year, + ) + for person in missing_persons + ] + PersonalNote.objects.bulk_create(new_personal_notes) + + for personal_note in new_personal_notes: + personal_note.groups_of_person.set(personal_note.person.member_of.all()) return PersonalNote.objects.select_related("person").filter( - lesson_period=self, week=wanted_week.week + lesson_period=self, week=wanted_week.week, year=wanted_week.year ) @@ -135,7 +147,9 @@ def get_lesson_documentation( if not week: week = self.week try: - return LessonDocumentation.objects.get(lesson_period=self, week=week.week) + return LessonDocumentation.objects.get( + lesson_period=self, week=week.week, year=week.year + ) except LessonDocumentation.DoesNotExist: return None @@ -148,7 +162,7 @@ def get_or_create_lesson_documentation( if not week: week = self.week lesson_documentation, created = LessonDocumentation.objects.get_or_create( - lesson_period=self, week=week.week + lesson_period=self, week=week.week, year=week.year ) return lesson_documentation @@ -158,7 +172,7 @@ def get_absences(self, week: Optional[CalendarWeek] = None) -> QuerySet: """Get all personal notes of absent persons for this lesson.""" if not week: week = self.week - return self.personal_notes.filter(week=week.week, absent=True) + return self.personal_notes.filter(week=week.week, year=week.year, absent=True) @LessonPeriod.method @@ -166,7 +180,9 @@ def get_excused_absences(self, week: Optional[CalendarWeek] = None) -> QuerySet: """Get all personal notes of excused absent persons for this lesson.""" if not week: week = self.week - return self.personal_notes.filter(week=week.week, absent=True, excused=True) + return self.personal_notes.filter( + week=week.week, year=week.year, absent=True, excused=True + ) @LessonPeriod.method @@ -174,7 +190,9 @@ def get_unexcused_absences(self, week: Optional[CalendarWeek] = None) -> QuerySe """Get all personal notes of unexcused absent persons for this lesson.""" if not week: week = self.week - return self.personal_notes.filter(week=week.week, absent=True, excused=False) + return self.personal_notes.filter( + week=week.week, year=week.year, absent=True, excused=False + ) @LessonPeriod.method @@ -182,7 +200,7 @@ def get_tardinesses(self, week: Optional[CalendarWeek] = None) -> QuerySet: """Get all personal notes of late persons for this lesson.""" if not week: week = self.week - return self.personal_notes.filter(week=week.week, late__gt=0) + return self.personal_notes.filter(week=week.week, year=week.year, late__gt=0) @LessonPeriod.method @@ -195,7 +213,9 @@ def get_extra_marks( stats = {} for extra_mark in ExtraMark.objects.all(): - qs = self.personal_notes.filter(week=week.week, extra_marks=extra_mark) + qs = self.personal_notes.filter( + week=week.week, year=week.year, extra_marks=extra_mark + ) if qs: stats[extra_mark] = qs diff --git a/aleksis/apps/alsijil/models.py b/aleksis/apps/alsijil/models.py index ffb369384517c8ffdf74a581a5254ec9c756b9f2..733855a4fd62fa01c24f1b5ee75cb290bc2ba3ae 100644 --- a/aleksis/apps/alsijil/models.py +++ b/aleksis/apps/alsijil/models.py @@ -1,7 +1,13 @@ from django.db import models from django.utils.translation import gettext_lazy as _ +from calendarweek import CalendarWeek + +from aleksis.apps.chronos.mixins import WeekRelatedMixin +from aleksis.apps.chronos.models import LessonPeriod +from aleksis.apps.chronos.util.date import get_current_year from aleksis.core.mixins import ExtensibleModel +from aleksis.core.util.core_helpers import get_site_preferences def isidentifier(value: str) -> bool: @@ -32,7 +38,7 @@ class ExcuseType(ExtensibleModel): verbose_name_plural = _("Excuse types") -class PersonalNote(ExtensibleModel): +class PersonalNote(ExtensibleModel, WeekRelatedMixin): """A personal note about a single person. Used in the class register to note absences, excuses @@ -45,6 +51,8 @@ class PersonalNote(ExtensibleModel): groups_of_person = models.ManyToManyField("core.Group", related_name="+") week = models.IntegerField() + year = models.IntegerField(verbose_name=_("Year"), default=get_current_year) + lesson_period = models.ForeignKey( "chronos.LessonPeriod", models.CASCADE, related_name="personal_notes" ) @@ -76,7 +84,7 @@ class PersonalNote(ExtensibleModel): verbose_name_plural = _("Personal notes") unique_together = [["lesson_period", "week", "person"]] ordering = [ - "lesson_period__lesson__validity__date_start", + "year", "week", "lesson_period__period__weekday", "lesson_period__period__period", @@ -85,13 +93,15 @@ class PersonalNote(ExtensibleModel): ] -class LessonDocumentation(ExtensibleModel): +class LessonDocumentation(ExtensibleModel, WeekRelatedMixin): """A documentation on a single lesson period. Non-personal, includes the topic and homework of the lesson. """ week = models.IntegerField() + year = models.IntegerField(verbose_name=_("Year"), default=get_current_year) + lesson_period = models.ForeignKey( "chronos.LessonPeriod", models.CASCADE, related_name="documentations" ) @@ -102,12 +112,51 @@ class LessonDocumentation(ExtensibleModel): verbose_name=_("Group note"), max_length=200, blank=True ) + def _carry_over_data(self): + """Carry over data to directly adjacent periods in this lesson if data is not already set. + + Can be deactivated using site preference ``alsijil__carry_over``. + """ + following_periods = LessonPeriod.objects.filter( + lesson=self.lesson_period.lesson, + period__weekday=self.lesson_period.period.weekday, + period__period__gt=self.lesson_period.period.period, + ) + for period in following_periods: + lesson_documentation = period.get_or_create_lesson_documentation( + CalendarWeek(week=self.week, year=self.year) + ) + + changed = False + + if not lesson_documentation.topic: + lesson_documentation.topic = self.topic + changed = True + + if not lesson_documentation.homework: + lesson_documentation.homework = self.homework + changed = True + + if not lesson_documentation.group_note: + lesson_documentation.group_note = self.group_note + changed = True + + if changed: + lesson_documentation.save() + + def save(self, *args, **kwargs): + if get_site_preferences()["alsijil__carry_over"] and ( + self.topic or self.homework or self.group_note + ): + self._carry_over_data() + super().save(*args, **kwargs) + class Meta: verbose_name = _("Lesson documentation") verbose_name_plural = _("Lesson documentations") unique_together = [["lesson_period", "week"]] ordering = [ - "lesson_period__lesson__validity__date_start", + "year", "week", "lesson_period__period__weekday", "lesson_period__period__period", diff --git a/aleksis/apps/alsijil/preferences.py b/aleksis/apps/alsijil/preferences.py index 9fe3a62812123a8e701ad0ccb2a730470d9ad350..9958d72d831b8978178301c8fa7123d0222e47e4 100644 --- a/aleksis/apps/alsijil/preferences.py +++ b/aleksis/apps/alsijil/preferences.py @@ -22,3 +22,29 @@ class ViewOwnPersonalNotes(BooleanPreference): name = "view_own_personal_notes" default = True verbose_name = _("Allow users to view their own personal notes") + + +@site_preferences_registry.register +class CarryOverDataToNextPeriods(BooleanPreference): + section = alsijil + name = "carry_over" + default = True + verbose_name = _( + "Carry over data from first lesson period to the following lesson periods in lessons over multiple periods" + ) + help_text = _( + "This will carry over data only if the data in the following periods are empty." + ) + + +@site_preferences_registry.register +class AllowOpenPeriodsOnSameDay(BooleanPreference): + section = alsijil + name = "open_periods_same_day" + default = False + verbose_name = _( + "Allow teachers to open lesson periods on the same day and not just at the beginning of the period" + ) + help_text = _( + "Lessons in the past are not affected by this setting, you can open them whenever you want." + ) diff --git a/aleksis/apps/alsijil/views.py b/aleksis/apps/alsijil/views.py index 7f0edb132be81f4217ddd11534eca3c568cbb87a..ff282633df99999cfc61caef20f8fa0a8e6400cb 100644 --- a/aleksis/apps/alsijil/views.py +++ b/aleksis/apps/alsijil/views.py @@ -82,6 +82,10 @@ def lesson( wanted_week[lesson_period.period.weekday], lesson_period.period.time_start, ) > datetime.now() + and not ( + get_site_preferences()["alsijil__open_periods_same_day"] + and wanted_week[lesson_period.period.weekday] <= datetime.now().date() + ) and not request.user.is_superuser ): raise PermissionDenied( @@ -173,7 +177,10 @@ def week_view( lesson_periods = LessonPeriod.objects.in_week(wanted_week).annotate( has_documentation=Exists( LessonDocumentation.objects.filter( - ~Q(topic__exact=""), lesson_period=OuterRef("pk"), week=wanted_week.week + ~Q(topic__exact=""), + lesson_period=OuterRef("pk"), + week=wanted_week.week, + year=wanted_week.year, ) ) ) @@ -243,6 +250,7 @@ def week_view( filter=Q( personal_notes__lesson_period__in=lesson_periods_pk, personal_notes__week=wanted_week.week, + personal_notes__year=wanted_week.year, personal_notes__absent=True, ), distinct=True, @@ -252,6 +260,7 @@ def week_view( filter=Q( personal_notes__lesson_period__in=lesson_periods_pk, personal_notes__week=wanted_week.week, + personal_notes__year=wanted_week.year, personal_notes__absent=True, personal_notes__excused=False, ), @@ -262,6 +271,7 @@ def week_view( pk=OuterRef("pk"), personal_notes__lesson_period__in=lesson_periods_pk, personal_notes__week=wanted_week.week, + personal_notes__year=wanted_week.year, ) .distinct() .annotate(tardiness_sum=Sum("personal_notes__late")) @@ -278,6 +288,7 @@ def week_view( filter=Q( personal_notes__lesson_period__in=lesson_periods_pk, personal_notes__week=wanted_week.week, + personal_notes__year=wanted_week.year, personal_notes__extra_marks=extra_mark, ), distinct=True, @@ -291,7 +302,9 @@ def week_view( { "person": person, "personal_notes": person.personal_notes.filter( - week=wanted_week.week, lesson_period__in=lesson_periods_pk + week=wanted_week.week, + year=wanted_week.year, + lesson_period__in=lesson_periods_pk, ), } ) @@ -370,17 +383,17 @@ def full_register_group(request: HttpRequest, id_: int) -> HttpResponse: ): documentations = list( filter( - lambda d: d.week == week.week, + lambda d: d.week == week.week and d.year == week.year, lesson_period.documentations.all(), ) ) notes = list( filter( - lambda d: d.week == week.week, + lambda d: d.week == week.week and d.year == week.year, lesson_period.personal_notes.all(), ) ) - substitution = lesson_period.get_substitution(week.week) + substitution = lesson_period.get_substitution(week) periods_by_day.setdefault(day, []).append( (lesson_period, documentations, notes, substitution) diff --git a/poetry.lock b/poetry.lock index 978de725f9360d5c85e23b370ac282a33a793243..6869ab2867552636ad4b834e68ba13ca26fb47c9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -68,11 +68,11 @@ extras = ["phonenumbers"] version = ">=3.0,<4.0" [package.dependencies.django-two-factor-auth] -extras = ["phonenumbers", "sms", "call", "yubikey"] +extras = ["yubikey", "call", "phonenumbers", "sms"] version = ">=1.11.0,<2.0.0" [package.dependencies.dynaconf] -extras = ["toml", "yaml", "ini"] +extras = ["ini", "yaml", "toml"] version = ">=2.0,<3.0" [package.extras] @@ -995,18 +995,15 @@ description = "flake8 plugin that integrates isort ." name = "flake8-isort" optional = false python-versions = "*" -version = "3.0.0" +version = "4.0.0" [package.dependencies] -flake8 = ">=3.2.1" -testfixtures = "*" - -[package.dependencies.isort] -extras = ["pyproject"] -version = ">=4.3.5" +flake8 = ">=3.2.1,<4" +isort = ">=4.3.5,<6" +testfixtures = ">=6.8.0,<7" [package.extras] -test = ["pytest"] +test = ["pytest (>=4.0.2,<6)", "toml"] [[package]] category = "dev" @@ -1106,6 +1103,14 @@ zipp = ">=0.5" docs = ["sphinx", "rst.linker"] testing = ["packaging", "pep517", "importlib-resources (>=1.3)"] +[[package]] +category = "dev" +description = "iniconfig: brain-dead simple config-ini parsing" +name = "iniconfig" +optional = false +python-versions = "*" +version = "1.0.1" + [[package]] category = "dev" description = "A Python utility / library to sort Python imports." @@ -1368,24 +1373,25 @@ description = "pytest: simple powerful testing with Python" name = "pytest" optional = false python-versions = ">=3.5" -version = "5.4.3" +version = "6.0.1" [package.dependencies] atomicwrites = ">=1.0" attrs = ">=17.4.0" colorama = "*" +iniconfig = "*" more-itertools = ">=4.0.0" packaging = "*" pluggy = ">=0.12,<1.0" -py = ">=1.5.0" -wcwidth = "*" +py = ">=1.8.2" +toml = "*" [package.dependencies.importlib-metadata] python = "<3.8" version = ">=0.12" [package.extras] -checkqa-mypy = ["mypy (v0.761)"] +checkqa_mypy = ["mypy (0.780)"] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] [[package]] @@ -1909,14 +1915,6 @@ brotli = ["brotlipy (>=0.6.0)"] secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)", "ipaddress"] socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] -[[package]] -category = "dev" -description = "Measures the displayed width of unicode strings in a terminal" -name = "wcwidth" -optional = false -python-versions = "*" -version = "0.2.5" - [[package]] category = "main" description = "Character encoding aliases for legacy web content" @@ -1951,7 +1949,7 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] testing = ["jaraco.itertools", "func-timeout"] [metadata] -content-hash = "c948e61801e83c7105de786b85616a1b61957e6950cd649039bd67f87193559c" +content-hash = "4345c57e5e3244ce0bbdc8ad8a7cffa385fe33b7da1b5fc87ffe32dabf45c5a9" python-versions = "^3.7" [metadata.files] @@ -2285,8 +2283,8 @@ flake8-fixme = [ {file = "flake8_fixme-1.1.1-py2.py3-none-any.whl", hash = "sha256:226a6f2ef916730899f29ac140bed5d4a17e5aba79f00a0e3ae1eff1997cb1ac"}, ] flake8-isort = [ - {file = "flake8-isort-3.0.0.tar.gz", hash = "sha256:3ce227b5c5342b6d63937d3863e8de8783ae21863cb035cf992cdb0ba5990aa3"}, - {file = "flake8_isort-3.0.0-py2.py3-none-any.whl", hash = "sha256:f5322a85cea89998e0df954162fd35a1f1e5b5eb4fc0c79b5975aa2799106baa"}, + {file = "flake8-isort-4.0.0.tar.gz", hash = "sha256:2b91300f4f1926b396c2c90185844eb1a3d5ec39ea6138832d119da0a208f4d9"}, + {file = "flake8_isort-4.0.0-py2.py3-none-any.whl", hash = "sha256:729cd6ef9ba3659512dee337687c05d79c78e1215fdf921ed67e5fe46cce2f3c"}, ] flake8-mypy = [ {file = "flake8-mypy-17.8.0.tar.gz", hash = "sha256:47120db63aff631ee1f84bac6fe8e64731dc66da3efc1c51f85e15ade4a3ba18"}, @@ -2323,6 +2321,10 @@ importlib-metadata = [ {file = "importlib_metadata-1.7.0-py2.py3-none-any.whl", hash = "sha256:dc15b2969b4ce36305c51eebe62d418ac7791e9a157911d58bfb1f9ccd8e2070"}, {file = "importlib_metadata-1.7.0.tar.gz", hash = "sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83"}, ] +iniconfig = [ + {file = "iniconfig-1.0.1-py3-none-any.whl", hash = "sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437"}, + {file = "iniconfig-1.0.1.tar.gz", hash = "sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69"}, +] isort = [ {file = "isort-5.2.2-py3-none-any.whl", hash = "sha256:aea484023188ef1c38256dd24afa96e914adafe3a911a1786800a74e433006d1"}, {file = "isort-5.2.2.tar.gz", hash = "sha256:96b27045e3187b9bdde001143b79f9b10a462f372bff7062302818013b6c86f3"}, @@ -2544,8 +2546,8 @@ pyparsing = [ {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, ] pytest = [ - {file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"}, - {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"}, + {file = "pytest-6.0.1-py3-none-any.whl", hash = "sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad"}, + {file = "pytest-6.0.1.tar.gz", hash = "sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4"}, ] pytest-cov = [ {file = "pytest-cov-2.10.0.tar.gz", hash = "sha256:1a629dc9f48e53512fcbfda6b07de490c374b0c83c55ff7a1720b3fccff0ac87"}, @@ -2617,6 +2619,7 @@ regex = [ {file = "regex-2020.7.14-cp38-cp38-manylinux1_i686.whl", hash = "sha256:5ea81ea3dbd6767873c611687141ec7b06ed8bab43f68fad5b7be184a920dc99"}, {file = "regex-2020.7.14-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:bbb332d45b32df41200380fff14712cb6093b61bd142272a10b16778c418e98e"}, {file = "regex-2020.7.14-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:c11d6033115dc4887c456565303f540c44197f4fc1a2bfb192224a301534888e"}, + {file = "regex-2020.7.14-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:75aaa27aa521a182824d89e5ab0a1d16ca207318a6b65042b046053cfc8ed07a"}, {file = "regex-2020.7.14-cp38-cp38-win32.whl", hash = "sha256:d6cff2276e502b86a25fd10c2a96973fdb45c7a977dca2138d661417f3728341"}, {file = "regex-2020.7.14-cp38-cp38-win_amd64.whl", hash = "sha256:7a2dd66d2d4df34fa82c9dc85657c5e019b87932019947faece7983f2089a840"}, {file = "regex-2020.7.14.tar.gz", hash = "sha256:3a3af27a8d23143c49a3420efe5b3f8cf1a48c6fc8bc6856b03f638abc1833bb"}, @@ -2769,10 +2772,6 @@ urllib3 = [ {file = "urllib3-1.25.10-py2.py3-none-any.whl", hash = "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"}, {file = "urllib3-1.25.10.tar.gz", hash = "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a"}, ] -wcwidth = [ - {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, - {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, -] webencodings = [ {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, diff --git a/pyproject.toml b/pyproject.toml index 6db7e5c53cacd7c21225a23a9cbaafc154d5cb8f..2a341597ebd03a855ebb772111a6005b8fb75cc1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,7 @@ sphinx = "^3.0" sphinxcontrib-django = "^0.5.0" sphinx-autodoc-typehints = "^1.7" django-stubs = "^1.1" -pytest = "^5.3" +pytest = "^6.0" pytest-django = "^3.7" pytest-django-testing-postgresql = "^0.1" selenium = "^3.141.0" @@ -45,7 +45,7 @@ flake8-rst-docstrings = "^0.0.13" black = "^19.10b0" flake8-black = "^0.2.0" isort = "^5.0.0" -flake8-isort = "^3.0.0" +flake8-isort = "^4.0.0" pytest-cov = "^2.8.1" pytest-sugar = "^0.9.2"