diff --git a/.gitignore b/.gitignore
index 4e43c2cca29a68a028594844be13bbe590de8ca6..a74536c95c84dedbf6b7a43c30b86e7490732443 100644
--- a/.gitignore
+++ b/.gitignore
@@ -61,3 +61,5 @@ biscuit/node_modules/
 .coverage
 .tox/
 maintenance_mode_state.txt
+htmlcov/
+.mypy_cache/
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index d6e05525fe817c5f718b720c3d2e3f7d7bd41328..a1be1e5ec7c2ec8812cb822e62747ece3917a3dd 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,3 +1,5 @@
+image: registry.edugit.org/teckids/docker-images/python-pimped:master
+
 stages:
   - test
   - build
@@ -5,18 +7,63 @@ stages:
 
 variables:
   GIT_SUBMODULE_STRATEGY: recursive
+  PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
+  FF_NETWORK_PER_BUILD: "true"
+
+cache:
+  key:
+    files:
+      - poetry.lock
+      - pyproject.toml
+  paths:
+    - .cache/pip
+    - .tox
 
 test:
   stage: test
-  image:
-    name: python:3.8-buster
+  services:
+    - name: selenium/standalone-firefox
+      alias: selenium
   before_script:
-    - apt-get -y update && apt-get -y install postgresql libpq5 libpq-dev libssl-dev sudo
-    - pip install poetry
     - adduser --disabled-password --gecos "Test User" testuser
+    - chown -R testuser .
+  script:
+    - sudo -u testuser 
+      env TEST_SELENIUM_HUB=http://selenium:4444/wd/hub
+          TEST_SELENIUM_BROWSERS=firefox
+          TEST_HOST=build
+      tox -e selenium -- --junitxml=.tox/junit.xml
+  artifacts:
+    paths:
+      - .tox/screenshots
+    reports:
+      junit: .tox/junit.xml
+
+lint:
+  stage: test
+  script:
+    - tox -e lint,security
+  allow_failure: true
+
+build_dist:
+  stage: build
   script:
-    - sudo -u testuser poetry install
-    - sudo -u testuser poetry run tox
+    - tox -e build
+  artifacts:
+    paths:
+      - dist/
+
+pages:
+  stage: deploy
+  before_script:
+    - cp -r .tox/screenshots/firefox docs/screenshots
+  script:
+    - tox -e docs -- BUILDDIR=../public/docs
+  artifacts:
+    paths:
+    - public/
+  only:
+  - master
 
 build_docker:
   stage: build
@@ -39,14 +86,13 @@ build_docker:
        --cleanup
   only:
     - master
+    - tags
 
 deploy_demo-master:
   stage: deploy
   environment:
     name: demo/master
     url: http://demo-master.biscuit-sis.org  
-  image:
-    name: debian:buster-slim
   before_script:
     - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
     - eval $(ssh-agent -s)
@@ -65,25 +111,10 @@ deploy_demo-master:
     - grep -v "build:" docker-compose.yml | ssh root@demo-master.biscuit-sis.org
        env BISCUIT_IMAGE_TAG=${CI_COMMIT_REF_NAME}
            NGINX_HTTP_PORT=80
+           BISCUIT_maintenance__debug=true
        docker-compose
         -p biscuit-${CI_ENVIRONMENT_SLUG}
         -f /dev/stdin
         up -d
   only:
     - master
-
-pages:
-  stage: deploy
-  image:
-    name: python:3.8-buster
-  before_script:
-  - apt-get -y update && apt-get -y install make
-  - pip install poetry
-  script:
-  - poetry install
-  - poetry run make -C docs html BUILDDIR=../public/docs
-  artifacts:
-    paths:
-    - public/
-  only:
-  - master
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index 74f35ea4898a7e78de7d9927504601da7f442aee..8dd5324fd7c0c1ca5207c86f69329ea1a7e15a61 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -12,22 +12,30 @@ Coding layout and style
 The coding style is defined in `PEP 8`_, with the following differences and
 decisions:
 
+- The defaults of the `black`_ code formatter are used
+  - This implies all string literals usin double-quotes, if it does not lead
+    to more escaping. As proposed by `black`: "My recommendation here is to
+    keep using whatever is faster to type and let Black handle the transformation."
 - The maximum line length is 100 characters
 - Imports are structured in five blocks, each of them sorted as defined in
-  PEP 8:
+  PEP 8 and the Django style guide:
 
   1. Standard library imports
   2. Django imports
   3. Third-party imports
-  4. Imports from other BiscuIT apps (absolute imports)
+  4. Imports from BiscuIT core and other apps (absolute imports)
   5. Imports from the same BiscuIT app (realtive imports)
 
-- All string literals use single quotes
+  Use `isort` to take care of this
 
 For the layout of source trees and style recommendations specific to Django,
 the `Django coding style`_ is a good source of information, together with
 the `Django Best Practices`_ collection.
 
+To ensure code is styled correctly, before commiting, run::
+
+  tox -e reformat
+
 
 Working with the Git repository
 -------------------------------
@@ -82,6 +90,26 @@ giving the user control over these decisions is not possible. Developers
 need to decide what should resonably be followed.
 
 
+The case on supporting non-free services
+----------------------------------------
+
+Defined by the `Free Software Definition`_, it is an essential freedom to
+be allowed to use free software for any purpose, without limitation. Thus,
+interoperability with non-free services shall not be ruled out, and the
+BiscuIT project explicitly welcomes implementing support for
+interoperability with non-free services.
+
+However, to purposefullt foster free software and services, if
+interoperability for a certain kind of non-free service is implemented, this
+must be done in a generalised manner (i.e.  using open protocols and
+interfaces).  For example, if implementing interoperability with some
+cloud-hosted calendar provider can be implemented either through a
+proprietary API, or through a standard iCalendar/Webcal interfaces, the
+latter is to be preferred.  Lacking such support, if a proprietary service
+is connected through a proprietary, single-purpose interface, measures shall
+be taken to also support alternative free services.
+
+
 Text documents
 --------------
 
@@ -100,9 +128,11 @@ licence possible.
 
 .. _PEP 8: https://pep8.org/
 .. _Django coding style: https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/coding-style/
+.. _black: https://black.readthedocs.io/en/stable/
 .. _Django Best Practices: https://django-best-practices.readthedocs.io/en/latest/index.html
 .. _How to Write a Git Commit Message: https://chris.beams.io/posts/git-commit/
 .. _Sane software manifesto: https://sane-software.globalcode.info/
 .. _Accessibility Manifesto: http://accessibilitymanifesto.com/
 .. _User Data Manifesto: https://userdatamanifesto.org/
+.. _Free Software Definition: https://www.gnu.org/philosophy/free-sw.en.html
 .. _reStructuredText: http://docutils.sourceforge.net/rst.html
diff --git a/Dockerfile b/Dockerfile
index ec6f771d02496a26629df0f0959af7ff7cbf69c3..a377af5a6194b2e0c528eecae2431508e53d73d3 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -12,13 +12,11 @@ ENV BISCUIT_static__root /var/lib/biscuit/static
 ENV BISCUIT_media__root /var/lib/biscuit/media
 ENV BISCUIT_backup__location /var/lib/biscuit/backups
 
-# FIXME Use poetry pre-release for external build
-ENV POETRY_VERSION 1.0.0b3
-
 # Install necessary Debian packages for build and runtime
-RUN apt-get update && \
-    apt-get upgrade -y && \
-    apt-get install -y --no-install-recommends \
+RUN apt-get -y update && \
+    apt-get -y install eatmydata && \
+    eatmydata apt-get -y upgrade && \
+    eatmydata apt-get install -y --no-install-recommends \
         build-essential \
 	gettext \
 	libpq5 \
@@ -30,35 +28,32 @@ RUN apt-get update && \
 # Install core dependnecies
 WORKDIR /usr/src/app
 COPY poetry.lock pyproject.toml ./
-RUN pip install "poetry==$POETRY_VERSION"; \
-    poetry export -f requirements.txt | pip install -r /dev/stdin; \
-    pip install gunicorn
+RUN eatmydata pip install poetry; \
+    poetry export -f requirements.txt | eatmydata pip install -r /dev/stdin; \
+    eatmydata pip install gunicorn
 
 # Install core
 COPY biscuit ./biscuit/
 COPY LICENCE README.rst manage.py ./
 RUN mkdir -p /var/lib/biscuit/media /var/lib/biscuit/static /var/lib/biscuit/backups; \
-    poetry build && pip install dist/*.whl
+    poetry build && eatmydata pip install dist/*.whl
 
 # Build messages and assets
 RUN python manage.py compilemessages; \
-    python manage.py yarn install; \
-    python manage.py collectstatic --no-input --clear
+    eatmydata python manage.py yarn install
 
 # Clean up build dependencies
-RUN apt-get remove --purge -y \
+RUN eatmydata apt-get remove --purge -y \
         build-essential \
         gettext \
         libpq-dev \
         libssl-dev \
         yarnpkg; \
-    apt-get autoremove --purge -y; \
+    eatmydata apt-get autoremove --purge -y; \
     apt-get clean -y; \
-    pip uninstall -y poetry; \
+    eatmydata pip uninstall -y poetry; \
     rm -f /var/lib/apt/lists/*_*; \
-    rm -rf /root/.cache; \
-    rm -rf biscuit/node_modules; \
-    rm -rf /usr/local/lib/node_modules
+    rm -rf /root/.cache
 
 # Declare a persistent volume for all data
 VOLUME /var/lib/biscuit
diff --git a/apps/official/BiscuIT-App-Alsijil b/apps/official/BiscuIT-App-Alsijil
index 9014661e518c6d9457723571d7b0ef1ac9be6c6b..3973b529e0f0889376159fd83c7d6e49bef6d767 160000
--- a/apps/official/BiscuIT-App-Alsijil
+++ b/apps/official/BiscuIT-App-Alsijil
@@ -1 +1 @@
-Subproject commit 9014661e518c6d9457723571d7b0ef1ac9be6c6b
+Subproject commit 3973b529e0f0889376159fd83c7d6e49bef6d767
diff --git a/apps/official/BiscuIT-App-Chronos b/apps/official/BiscuIT-App-Chronos
index d4da734afa405a6af2adbe607f553e2b759737d6..51f8b6f703cbbeda26b64b45a2860c5e4921b1ba 160000
--- a/apps/official/BiscuIT-App-Chronos
+++ b/apps/official/BiscuIT-App-Chronos
@@ -1 +1 @@
-Subproject commit d4da734afa405a6af2adbe607f553e2b759737d6
+Subproject commit 51f8b6f703cbbeda26b64b45a2860c5e4921b1ba
diff --git a/apps/official/BiscuIT-App-Exlibris b/apps/official/BiscuIT-App-Exlibris
index b03369df8214f062a18a70824c6a257cf4ab427d..d76afcbcabf7e93a48f4710a779fa8f06c959577 160000
--- a/apps/official/BiscuIT-App-Exlibris
+++ b/apps/official/BiscuIT-App-Exlibris
@@ -1 +1 @@
-Subproject commit b03369df8214f062a18a70824c6a257cf4ab427d
+Subproject commit d76afcbcabf7e93a48f4710a779fa8f06c959577
diff --git a/apps/official/BiscuIT-App-SchILD-NRW b/apps/official/BiscuIT-App-SchILD-NRW
index d3503372579bb067e80c72d0c168ac6e998b8d56..679baee78cfe182f41536b061b8d0f3e2dc8d0bb 160000
--- a/apps/official/BiscuIT-App-SchILD-NRW
+++ b/apps/official/BiscuIT-App-SchILD-NRW
@@ -1 +1 @@
-Subproject commit d3503372579bb067e80c72d0c168ac6e998b8d56
+Subproject commit 679baee78cfe182f41536b061b8d0f3e2dc8d0bb
diff --git a/apps/official/BiscuIT-App-Untis b/apps/official/BiscuIT-App-Untis
index f216d0e9da645cfbd3d4e1d865bb16dc4de7946f..7b4f67f723c52fad8e4a58f4571a67b2f05c1363 160000
--- a/apps/official/BiscuIT-App-Untis
+++ b/apps/official/BiscuIT-App-Untis
@@ -1 +1 @@
-Subproject commit f216d0e9da645cfbd3d4e1d865bb16dc4de7946f
+Subproject commit 7b4f67f723c52fad8e4a58f4571a67b2f05c1363
diff --git a/biscuit/core/__init__.py b/biscuit/core/__init__.py
index ef696833a713e9bad35ffaeff41cdf46aa7ebd4e..4d87b04132c9610b1f3a2a340eb74cc9ede8ceb3 100644
--- a/biscuit/core/__init__.py
+++ b/biscuit/core/__init__.py
@@ -1,8 +1,8 @@
 import pkg_resources
 
 try:
-    __version__ = pkg_resources.get_distribution('BiscuIT-ng').version
+    __version__ = pkg_resources.get_distribution("BiscuIT-ng").version
 except Exception:
-    __version__ = 'unknown'
+    __version__ = "unknown"
 
-default_app_config = 'biscuit.core.apps.CoreConfig'
+default_app_config = "biscuit.core.apps.CoreConfig"
diff --git a/biscuit/core/admin.py b/biscuit/core/admin.py
index 0810d4504654e0e1337c70810c41696d6e2abbd0..106da0677831f722aefa1d28d23dde9fea838b06 100644
--- a/biscuit/core/admin.py
+++ b/biscuit/core/admin.py
@@ -2,7 +2,6 @@ from django.contrib import admin
 
 from .models import Group, Person, School, SchoolTerm
 
-
 admin.site.register(Person)
 admin.site.register(Group)
 admin.site.register(School)
diff --git a/biscuit/core/anonymizers.py b/biscuit/core/anonymizers.py
index 03d4f3bf3a8597a3a61a9683878eae1a9f1637ea..3a9094c4140347ac5b738402ef5731582d064a2e 100644
--- a/biscuit/core/anonymizers.py
+++ b/biscuit/core/anonymizers.py
@@ -7,17 +7,20 @@ class PersonAnonymizer(BaseAnonymizer):
     model = Person
 
     attributes = [
-        ('first_name', faker.first_name),
-        ('last_name', faker.last_name),
-        ('additional_name', ''),
-        ('short_name', lambda **kwargs: faker.pystr(min_chars=3, max_chars=5, **kwargs)),
-        ('street', faker.street_name),
-        ('housenumber', faker.building_number),
-        ('postal_code', faker.postcode),
-        ('place', faker.city),
-        ('phone_number', ''),
-        ('mobile_number', ''),
-        ('email', faker.email),
-        ('date_of_birth', lambda **kwargs: faker.date_of_birth(minimum_age=8, maximum_age=66, **kwargs)),
-        ('photo', '')
+        ("first_name", faker.first_name),
+        ("last_name", faker.last_name),
+        ("additional_name", ""),
+        ("short_name", lambda **kwargs: faker.pystr(min_chars=3, max_chars=5, **kwargs)),
+        ("street", faker.street_name),
+        ("housenumber", faker.building_number),
+        ("postal_code", faker.postcode),
+        ("place", faker.city),
+        ("phone_number", ""),
+        ("mobile_number", ""),
+        ("email", faker.email),
+        (
+            "date_of_birth",
+            lambda **kwargs: faker.date_of_birth(minimum_age=8, maximum_age=66, **kwargs),
+        ),
+        ("photo", ""),
     ]
diff --git a/biscuit/core/apps.py b/biscuit/core/apps.py
index ef62cfb3085fa7d763f4506bc94613fcc77c7c53..5c5b3093b16309d23ffa0a985e7458fe356b800b 100644
--- a/biscuit/core/apps.py
+++ b/biscuit/core/apps.py
@@ -5,9 +5,9 @@ from .signals import clean_scss
 
 
 class CoreConfig(AppConfig):
-    name = 'biscuit.core'
-    verbose_name = 'BiscuIT - The Free School Information System'
+    name = "biscuit.core"
+    verbose_name = "BiscuIT - The Free School Information System"
 
     def ready(self) -> None:
         clean_scss()
-        post_save.connect(clean_scss, sender=apps.get_model('dbsettings', 'Setting'))
+        post_save.connect(clean_scss, sender=apps.get_model("dbsettings", "Setting"))
diff --git a/biscuit/core/cronjobs.py b/biscuit/core/cronjobs.py
index 229e017cf840a2eabfa9b624aa47e28cac636686..2ffe250b7939061afddf6246a8309fcc8a3e0feb 100644
--- a/biscuit/core/cronjobs.py
+++ b/biscuit/core/cronjobs.py
@@ -1,5 +1,6 @@
 from django.conf import settings
 from django.core import management
+
 from django_cron import CronJobBase, Schedule
 
 
@@ -7,9 +8,11 @@ class Backup(CronJobBase):
     RUN_AT_TIMES = settings.DBBACKUP_CRON_TIMES
     RETRY_AFTER_FAILURE_MINS = 5
 
-    schedule = Schedule(run_at_times=RUN_AT_TIMES, retry_after_failure_mins=RETRY_AFTER_FAILURE_MINS)
-    code = 'biscuit.core.Backup'
+    schedule = Schedule(
+        run_at_times=RUN_AT_TIMES, retry_after_failure_mins=RETRY_AFTER_FAILURE_MINS
+    )
+    code = "biscuit.core.Backup"
 
     def do(self):
-        management.call_command('dbbackup', '-z')
-        management.call_command('mediabackup', '-z')
+        management.call_command("dbbackup", "-z")
+        management.call_command("mediabackup", "-z")
diff --git a/biscuit/core/decorators.py b/biscuit/core/decorators.py
index 732f120958924f683f05482bd57661cc503f2882..bd87884fbdace4a4277e9d81503a89c89924563d 100644
--- a/biscuit/core/decorators.py
+++ b/biscuit/core/decorators.py
@@ -1,8 +1,8 @@
 from typing import Callable
+
 from django.contrib.auth.decorators import user_passes_test
 
 
 def admin_required(function: Callable = None) -> Callable:
-    actual_decorator = user_passes_test(
-        lambda u: u.is_active and u.is_superuser)
+    actual_decorator = user_passes_test(lambda u: u.is_active and u.is_superuser)
     return actual_decorator(function)
diff --git a/biscuit/core/forms.py b/biscuit/core/forms.py
index c7e813fdec59f29777da3670fb1aeee377850b3f..cef045d867bd838acc8443c17eb695a48e90b31a 100644
--- a/biscuit/core/forms.py
+++ b/biscuit/core/forms.py
@@ -1,97 +1,134 @@
 from django import forms
 from django.contrib.auth import get_user_model
 from django.utils.translation import ugettext_lazy as _
+
 from django_select2.forms import ModelSelect2MultipleWidget, Select2Widget
 
-from .models import Person, Group, School, SchoolTerm
+from .models import Group, Person, School, SchoolTerm
 
 
 class PersonAccountForm(forms.ModelForm):
     class Meta:
         model = Person
-        fields = ['last_name', 'first_name', 'user']
-        widgets = {
-            'user': Select2Widget
-        }
+        fields = ["last_name", "first_name", "user"]
+        widgets = {"user": Select2Widget}
 
     new_user = forms.CharField(required=False)
 
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
-        self.fields['first_name'].disabled = True
-        self.fields['last_name'].disabled = True
+        self.fields["first_name"].disabled = True
+        self.fields["last_name"].disabled = True
 
     def clean(self) -> None:
         User = get_user_model()
 
-        if self.cleaned_data.get('new_user', None):
-            if self.cleaned_data.get('user', None):
-                self.add_error('new_user', _(
-                    'You cannot set a new username when also selecting an existing user.'))
-            elif User.objects.filter(username=self.cleaned_data['new_user']).exists():
-                self.add_error('new_user', _('This username is already in use.'))
+        if self.cleaned_data.get("new_user", None):
+            if self.cleaned_data.get("user", None):
+                self.add_error(
+                    "new_user",
+                    _("You cannot set a new username when also selecting an existing user."),
+                )
+            elif User.objects.filter(username=self.cleaned_data["new_user"]).exists():
+                self.add_error("new_user", _("This username is already in use."))
             else:
-                new_user_obj = User.objects.create_user(self.cleaned_data['new_user'],
-                                                        self.instance.email,
-                                                        first_name=self.instance.first_name,
-                                                        last_name=self.instance.last_name)
+                new_user_obj = User.objects.create_user(
+                    self.cleaned_data["new_user"],
+                    self.instance.email,
+                    first_name=self.instance.first_name,
+                    last_name=self.instance.last_name,
+                )
 
-                self.cleaned_data['user'] = new_user_obj
+                self.cleaned_data["user"] = new_user_obj
 
 
 PersonsAccountsFormSet = forms.modelformset_factory(
-    Person, form=PersonAccountForm, max_num=0, extra=0)
+    Person, form=PersonAccountForm, max_num=0, extra=0
+)
 
 
 class EditPersonForm(forms.ModelForm):
     class Meta:
         model = Person
-        fields = ['user', 'is_active', 'first_name', 'last_name', 'additional_name', 'short_name', 'street', 'housenumber',
-                  'postal_code', 'place', 'phone_number', 'mobile_number', 'email', 'date_of_birth', 'sex', 'photo', 'photo_cropping']
-        widgets = {
-            'user': Select2Widget
-        }
+        fields = [
+            "user",
+            "is_active",
+            "first_name",
+            "last_name",
+            "additional_name",
+            "short_name",
+            "street",
+            "housenumber",
+            "postal_code",
+            "place",
+            "phone_number",
+            "mobile_number",
+            "email",
+            "date_of_birth",
+            "sex",
+            "photo",
+            "photo_cropping",
+        ]
+        widgets = {"user": Select2Widget}
 
     new_user = forms.CharField(
-        required=False,
-        label=_('New user'),
-        help_text=_('Create a new account'))
+        required=False, label=_("New user"), help_text=_("Create a new account")
+    )
 
     def clean(self) -> None:
         User = get_user_model()
 
-        if self.cleaned_data.get('new_user', None):
-            if self.cleaned_data.get('user', None):
-                self.add_error('new_user', _(
-                    'You cannot set a new username when also selecting an existing user.'))
-            elif User.objects.filter(username=self.cleaned_data['new_user']).exists():
-                self.add_error('new_user', _('This username is already in use.'))
+        if self.cleaned_data.get("new_user", None):
+            if self.cleaned_data.get("user", None):
+                self.add_error(
+                    "new_user",
+                    _("You cannot set a new username when also selecting an existing user."),
+                )
+            elif User.objects.filter(username=self.cleaned_data["new_user"]).exists():
+                self.add_error("new_user", _("This username is already in use."))
             else:
-                new_user_obj = User.objects.create_user(self.cleaned_data['new_user'],
-                                                        self.instance.email,
-                                                        first_name=self.instance.first_name,
-                                                        last_name=self.instance.last_name)
+                new_user_obj = User.objects.create_user(
+                    self.cleaned_data["new_user"],
+                    self.instance.email,
+                    first_name=self.instance.first_name,
+                    last_name=self.instance.last_name,
+                )
 
-                self.cleaned_data['user'] = new_user_obj
+                self.cleaned_data["user"] = new_user_obj
 
 
 class EditGroupForm(forms.ModelForm):
     class Meta:
         model = Group
-        fields = ['name', 'short_name', 'members', 'owners', 'parent_groups']
+        fields = ["name", "short_name", "members", "owners", "parent_groups"]
         widgets = {
-            'members': ModelSelect2MultipleWidget(search_fields=['first_name__icontains', 'last_name__icontains', 'short_name__icontains']),
-            'owners': ModelSelect2MultipleWidget(search_fields=['first_name__icontains', 'last_name__icontains', 'short_name__icontains']),
-            'parent_groups': ModelSelect2MultipleWidget(search_fields=['name__icontains', 'short_name__icontains']),
+            "members": ModelSelect2MultipleWidget(
+                search_fields=[
+                    "first_name__icontains",
+                    "last_name__icontains",
+                    "short_name__icontains",
+                ]
+            ),
+            "owners": ModelSelect2MultipleWidget(
+                search_fields=[
+                    "first_name__icontains",
+                    "last_name__icontains",
+                    "short_name__icontains",
+                ]
+            ),
+            "parent_groups": ModelSelect2MultipleWidget(
+                search_fields=["name__icontains", "short_name__icontains"]
+            ),
         }
 
+
 class EditSchoolForm(forms.ModelForm):
     class Meta:
         model = School
-        fields = ['name', 'name_official', 'logo', 'logo_cropping']
+        fields = ["name", "name_official", "logo", "logo_cropping"]
 
 
 class EditTermForm(forms.ModelForm):
     class Meta:
         model = SchoolTerm
-        fields = ['caption', 'date_start', 'date_end']
+        fields = ["caption", "date_start", "date_end"]
diff --git a/biscuit/core/menus.py b/biscuit/core/menus.py
index a230037f8305813cd5ec1a13307894af9da4bc15..cbb745d053bb6565aca7fad34f332434c5612af2 100644
--- a/biscuit/core/menus.py
+++ b/biscuit/core/menus.py
@@ -2,121 +2,125 @@ from django.conf import settings
 from django.utils.translation import ugettext_lazy as _
 
 MENUS = {
-    'NAV_MENU_CORE': [
+    "NAV_MENU_CORE": [
         {
-            'name': _('Account'),
-            'url': '#',
-            'root': True,
-            'submenu': [
+            "name": _("Account"),
+            "url": "#",
+            "root": True,
+            "submenu": [
                 {
-                    'name': _('Stop impersonation'),
-                    'url': 'impersonate-stop',
-                    'validators': ['menu_generator.validators.is_authenticated', 'biscuit.core.util.core_helpers.is_impersonate']
+                    "name": _("Stop impersonation"),
+                    "url": "impersonate-stop",
+                    "validators": [
+                        "menu_generator.validators.is_authenticated",
+                        "biscuit.core.util.core_helpers.is_impersonate",
+                    ],
                 },
                 {
-                    'name': _('Login'),
-                    'url': settings.LOGIN_URL,
-                    'validators': ['menu_generator.validators.is_anonymous']
+                    "name": _("Login"),
+                    "url": settings.LOGIN_URL,
+                    "validators": ["menu_generator.validators.is_anonymous"],
                 },
                 {
-                    'name': _('Logout'),
-                    'url': 'logout',
-                    'validators': ['menu_generator.validators.is_authenticated']
+                    "name": _("Logout"),
+                    "url": "logout",
+                    "validators": ["menu_generator.validators.is_authenticated"],
                 },
                 {
-                    'name': _('Two factor auth'),
-                    'url': 'two_factor:profile',
-                    'validators': ['menu_generator.validators.is_authenticated', lambda request: 'two_factor' in settings.INSTALLED_APPS]
-                }
-            ]
+                    "name": _("Two factor auth"),
+                    "url": "two_factor:profile",
+                    "validators": [
+                        "menu_generator.validators.is_authenticated",
+                        lambda request: "two_factor" in settings.INSTALLED_APPS,
+                    ],
+                },
+            ],
         },
         {
-            'name': _('Admin'),
-            'url': '#',
-            'validators': ['menu_generator.validators.is_authenticated', 'menu_generator.validators.is_superuser'],
-            'submenu': [
+            "name": _("Admin"),
+            "url": "#",
+            "validators": [
+                "menu_generator.validators.is_authenticated",
+                "menu_generator.validators.is_superuser",
+            ],
+            "submenu": [
                 {
-                    'name': _('Data management'),
-                    'url': 'data_management',
-                    'validators': ['menu_generator.validators.is_authenticated', 'menu_generator.validators.is_superuser']
+                    "name": _("Data management"),
+                    "url": "data_management",
+                    "validators": [
+                        "menu_generator.validators.is_authenticated",
+                        "menu_generator.validators.is_superuser",
+                    ],
                 },
                 {
-                    'name': _('System status'),
-                    'url': 'system_status',
-                    'validators': ['menu_generator.validators.is_authenticated', 'menu_generator.validators.is_superuser']
+                    "name": _("System status"),
+                    "url": "system_status",
+                    "validators": [
+                        "menu_generator.validators.is_authenticated",
+                        "menu_generator.validators.is_superuser",
+                    ],
                 },
                 {
-                    'name': _('Impersonation'),
-                    'url': 'impersonate-list',
-                    'validators': ['menu_generator.validators.is_authenticated', 'menu_generator.validators.is_superuser']
+                    "name": _("Impersonation"),
+                    "url": "impersonate-list",
+                    "validators": [
+                        "menu_generator.validators.is_authenticated",
+                        "menu_generator.validators.is_superuser",
+                    ],
                 },
                 {
-                    'name': _('Manage school'),
-                    'url': 'school_management',
-                    'validators': ['menu_generator.validators.is_authenticated', 'menu_generator.validators.is_superuser']
-                }
-            ]
+                    "name": _("Manage school"),
+                    "url": "school_management",
+                    "validators": [
+                        "menu_generator.validators.is_authenticated",
+                        "menu_generator.validators.is_superuser",
+                    ],
+                },
+            ],
         },
         {
-            'name': _('People'),
-            'url': '#',
-            'root': True,
-            'validators': ['menu_generator.validators.is_authenticated', 'biscuit.core.util.core_helpers.has_person'],
-            'submenu': [
+            "name": _("People"),
+            "url": "#",
+            "root": True,
+            "validators": [
+                "menu_generator.validators.is_authenticated",
+                "biscuit.core.util.core_helpers.has_person",
+            ],
+            "submenu": [
                 {
-                    'name': _('Persons'),
-                    'url': 'persons',
-                    'validators': ['menu_generator.validators.is_authenticated']
+                    "name": _("Persons"),
+                    "url": "persons",
+                    "validators": ["menu_generator.validators.is_authenticated"],
                 },
                 {
-                    'name': _('Groups'),
-                    'url': 'groups',
-                    'validators': ['menu_generator.validators.is_authenticated']
+                    "name": _("Groups"),
+                    "url": "groups",
+                    "validators": ["menu_generator.validators.is_authenticated"],
                 },
                 {
-                    'name': _('Persons and accounts'),
-                    'url': 'persons_accounts',
-                    'validators': ['menu_generator.validators.is_authenticated', 'menu_generator.validators.is_superuser']
-                }
-            ]
-        }
-    ],
-    'FOOTER_MENU_CORE': [
-        {
-            'name': _('BiscuIT Software'),
-            'url': '#',
-            'submenu': [
-                {
-                    'name': _('Website'),
-                    'url': 'https://biscuit.edugit.org/'
+                    "name": _("Persons and accounts"),
+                    "url": "persons_accounts",
+                    "validators": [
+                        "menu_generator.validators.is_authenticated",
+                        "menu_generator.validators.is_superuser",
+                    ],
                 },
-                {
-                    'name': 'Teckids e.V.',
-                    'url': 'https://www.teckids.org/'
-                }
-            ]
+            ],
         },
-        {
-            'name': _('Support'),
-            'url': '#',
-            'submenu': [
-                {
-                    'name': _('Get support'),
-                    'url': 'contact_form'
-                }
-            ]
-        }
-    ],
-    'DATA_MANAGEMENT_MENU': [
     ],
-    'SCHOOL_MANAGEMENT_MENU': [
+    "FOOTER_MENU_CORE": [
         {
-            'name': _('Edit school information'),
-            'url': 'edit_school_information',
+            "name": _("BiscuIT Software"),
+            "url": "#",
+            "submenu": [
+                {"name": _("Website"), "url": "https://biscuit.edugit.org/"},
+                {"name": "Teckids e.V.", "url": "https://www.teckids.org/"},
+            ],
         },
-        {
-            'name': _('Edit school term'),
-            'url': 'edit_school_term',
-        }
+    ],
+    "DATA_MANAGEMENT_MENU": [],
+    "SCHOOL_MANAGEMENT_MENU": [
+        {"name": _("Edit school information"), "url": "edit_school_information",},
+        {"name": _("Edit school term"), "url": "edit_school_term",},
     ],
 }
diff --git a/biscuit/core/migrations/0001_initial.py b/biscuit/core/migrations/0001_initial.py
index 1a88ae010c8497c6e432931ec15438feda3e0aa1..36a2f4cbe4c667ee3cf89efa29232b83293259b4 100644
--- a/biscuit/core/migrations/0001_initial.py
+++ b/biscuit/core/migrations/0001_initial.py
@@ -1,9 +1,10 @@
 # Generated by Django 2.2.5 on 2019-09-03 18:30
 
+import django.db.models.deletion
 from django.conf import settings
 from django.db import migrations, models
 from django.utils.translation import ugettext_lazy as _
-import django.db.models.deletion
+
 import image_cropping.fields
 import phonenumber_field.modelfields
 
diff --git a/biscuit/core/migrations/0002_school_term.py b/biscuit/core/migrations/0002_school_term.py
index 026d7e3a518fe4a4d299ca01500605afbdced4ca..8b1923f5c7ee263eca740493051fb714b57de48e 100644
--- a/biscuit/core/migrations/0002_school_term.py
+++ b/biscuit/core/migrations/0002_school_term.py
@@ -1,7 +1,7 @@
 # Generated by Django 2.2.5 on 2019-09-14 12:55
 
-from django.db import migrations, models
 import django.db.models.deletion
+from django.db import migrations, models
 from django.utils.translation import ugettext_lazy as _
 
 
diff --git a/biscuit/core/migrations/0003_school_logo.py b/biscuit/core/migrations/0003_school_logo.py
index c855202b5e08e69df3ee133ed5fdc5046acf80d1..09a18d44e827a32f25774867092de3ac35f3a014 100644
--- a/biscuit/core/migrations/0003_school_logo.py
+++ b/biscuit/core/migrations/0003_school_logo.py
@@ -1,6 +1,7 @@
 # Generated by Django 2.2.5 on 2019-09-14 13:03
 
 from django.db import migrations
+
 import image_cropping.fields
 
 
diff --git a/biscuit/core/migrations/0006_create_superuser.py b/biscuit/core/migrations/0006_create_superuser.py
new file mode 100644
index 0000000000000000000000000000000000000000..861dee94e738bccea983c7e94093d8b24923bb1e
--- /dev/null
+++ b/biscuit/core/migrations/0006_create_superuser.py
@@ -0,0 +1,26 @@
+# Generated by Django 2.2.8 on 2019-12-09 21:04
+
+from django.contrib.auth import get_user_model
+from django.db import migrations
+
+
+def create_superuser(apps, schema_editor):
+    User = get_user_model()
+
+    if not User.objects.filter(is_superuser=True).exists():
+        User.objects.create_superuser(
+            username='admin',
+            email='root@example.com',
+            password='admin'
+        ).save()
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('core', '0005_unlink_school'),
+    ]
+
+    operations = [
+        migrations.RunPython(create_superuser)
+    ]
diff --git a/biscuit/core/migrations/0007_unlink_school_schoolterm.py b/biscuit/core/migrations/0007_unlink_school_schoolterm.py
new file mode 100644
index 0000000000000000000000000000000000000000..4e26febd83153a971022c950f77929b91efa462b
--- /dev/null
+++ b/biscuit/core/migrations/0007_unlink_school_schoolterm.py
@@ -0,0 +1,31 @@
+# Generated by Django 2.2.8 on 2019-12-11 23:27
+
+from django.db import migrations, models
+
+
+def mark_current_term(apps, schema_editor):
+    db_alias = schema_editor.connection.alias
+
+    SchoolTerm = apps.get_model('core', 'SchoolTerm')  # noqa
+
+    if not SchoolTerm.objects.filter(current=True).exists():
+        SchoolTerm.objects.using(db_alias).latest('date_start').update(current=True)
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('core', '0006_create_superuser'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='school',
+            name='current_term',
+        ),
+        migrations.AddField(
+            model_name='schoolterm',
+            name='current',
+            field=models.NullBooleanField(default=None, unique=True),
+        ),
+    ]
diff --git a/biscuit/core/mixins.py b/biscuit/core/mixins.py
index 9ec842a5ea99b793c688a63a9a2e8c1d5d9203b4..6db9ffe40229c36ffb51292925cfe62ad10f2408 100644
--- a/biscuit/core/mixins.py
+++ b/biscuit/core/mixins.py
@@ -46,11 +46,11 @@ class ExtensibleModel(object):
             if name.isidentifier():
                 prop_name = name
             else:
-                raise ValueError('%s is not a valid name.' % name)
+                raise ValueError("%s is not a valid name." % name)
 
         # Verify that property name does not clash with other names in the class
         if hasattr(cls, prop_name):
-            raise ValueError('%s already used.' % prop_name)
+            raise ValueError("%s already used." % prop_name)
 
         # Add function wrapped in property decorator if we got here
         setattr(cls, prop_name, obj)
@@ -67,6 +67,7 @@ class ExtensibleModel(object):
 
         cls._safe_add(func, func.__name__)
 
+
 class CRUDMixin(models.Model):
     class Meta:
         abstract = True
@@ -78,8 +79,5 @@ class CRUDMixin(models.Model):
         content_type = ContentType.objects.get_for_model(self)
 
         return CRUDEvent.objects.filter(
-            object_id=self.pk,
-            content_type=content_type
-        ).select_related(
-            'user'
-        )
+            object_id=self.pk, content_type=content_type
+        ).select_related("user")
diff --git a/biscuit/core/models.py b/biscuit/core/models.py
index ffd0189e1a901c1fa7349b8cf164b9da8f00b49d..eab46395fcea5d7681ae5fa1a2c90ba99437c72e 100644
--- a/biscuit/core/models.py
+++ b/biscuit/core/models.py
@@ -12,16 +12,17 @@ from .mixins import ExtensibleModel
 
 
 class ThemeSettings(dbsettings.Group):
-    colour_primary = dbsettings.StringValue(default='#007bff')
-    colour_secondary = dbsettings.StringValue(default='#6c757d')
-    colour_success = dbsettings.StringValue(default='#28a745')
-    colour_info = dbsettings.StringValue(default='#17a2b8')
-    colour_warning = dbsettings.StringValue(default='#ffc107')
-    colour_danger = dbsettings.StringValue(default='#dc3545')
-    colour_light = dbsettings.StringValue(default='#f8f9fa')
-    colour_dark = dbsettings.StringValue(default='#343a40')
+    colour_primary = dbsettings.StringValue(default="#007bff")
+    colour_secondary = dbsettings.StringValue(default="#6c757d")
+    colour_success = dbsettings.StringValue(default="#28a745")
+    colour_info = dbsettings.StringValue(default="#17a2b8")
+    colour_warning = dbsettings.StringValue(default="#ffc107")
+    colour_danger = dbsettings.StringValue(default="#dc3545")
+    colour_light = dbsettings.StringValue(default="#f8f9fa")
+    colour_dark = dbsettings.StringValue(default="#343a40")
 
-theme_settings = ThemeSettings('Global theme settings')
+
+theme_settings = ThemeSettings("Global theme settings")
 
 
 class School(models.Model):
@@ -31,17 +32,22 @@ class School(models.Model):
     currently logged-in user.
     """
 
-    name = models.CharField(verbose_name=_('Name'), max_length=30)
-    name_official = models.CharField(verbose_name=_('Official name'), max_length=200, help_text=_(
-        'Official name of the school, e.g. as given by supervisory authority'))
+    name = models.CharField(verbose_name=_("Name"), max_length=30)
+    name_official = models.CharField(
+        verbose_name=_("Official name"),
+        max_length=200,
+        help_text=_("Official name of the school, e.g. as given by supervisory authority"),
+    )
 
-    logo = ImageCropField(verbose_name=_('School logo'), blank=True, null=True)
-    logo_cropping = ImageRatioField('logo', '600x600', size_warning=True)
+    logo = ImageCropField(verbose_name=_("School logo"), blank=True, null=True)
+    logo_cropping = ImageRatioField("logo", "600x600", size_warning=True)
 
-    current_term = models.ForeignKey('SchoolTerm', models.CASCADE, related_name='+')
+    @property
+    def current_term(self):
+        return SchoolTerm.objects.get(current=True)
 
     class Meta:
-        ordering = ['name', 'name_official']
+        ordering = ["name", "name_official"]
 
 
 class SchoolTerm(models.Model):
@@ -49,13 +55,17 @@ class SchoolTerm(models.Model):
     be linked to.
     """
 
-    caption = models.CharField(verbose_name=_('Visible caption of the term'),
-                               max_length=30)
+    caption = models.CharField(verbose_name=_("Visible caption of the term"), max_length=30)
+
+    date_start = models.DateField(verbose_name=_("Effective start date of term"), null=True)
+    date_end = models.DateField(verbose_name=_("Effective end date of term"), null=True)
+
+    current = models.NullBooleanField(default=None, unique=True)
 
-    date_start = models.DateField(verbose_name=_(
-        'Effective start date of term'), null=True)
-    date_end = models.DateField(verbose_name=_(
-        'Effective end date of term'), null=True)
+    def save(self, *args, **kwargs):
+        if self.current is False:
+            self.current = None
+        super().save(*args, **kwargs)
 
 
 class Person(models.Model, ExtensibleModel):
@@ -64,58 +74,55 @@ class Person(models.Model, ExtensibleModel):
     """
 
     class Meta:
-        ordering = ['last_name', 'first_name']
+        ordering = ["last_name", "first_name"]
 
-    SEX_CHOICES = [
-        ('f', _('female')),
-        ('m', _('male'))
-    ]
+    SEX_CHOICES = [("f", _("female")), ("m", _("male"))]
 
     user = models.OneToOneField(
-        get_user_model(), on_delete=models.SET_NULL, blank=True, null=True,
-        related_name='person')
-    is_active = models.BooleanField(
-        verbose_name=_('Is person active?'), default=True)
+        get_user_model(), on_delete=models.SET_NULL, blank=True, null=True, related_name="person"
+    )
+    is_active = models.BooleanField(verbose_name=_("Is person active?"), default=True)
 
-    first_name = models.CharField(verbose_name=_('First name'), max_length=30)
-    last_name = models.CharField(verbose_name=_('Last name'), max_length=30)
-    additional_name = models.CharField(verbose_name=_(
-        'Additional name(s)'), max_length=30, blank=True)
+    first_name = models.CharField(verbose_name=_("First name"), max_length=30)
+    last_name = models.CharField(verbose_name=_("Last name"), max_length=30)
+    additional_name = models.CharField(
+        verbose_name=_("Additional name(s)"), max_length=30, blank=True
+    )
 
-    short_name = models.CharField(verbose_name=_(
-        'Short name'), max_length=5, blank=True, null=True, unique=True)
+    short_name = models.CharField(
+        verbose_name=_("Short name"), max_length=5, blank=True, null=True, unique=True
+    )
 
-    street = models.CharField(verbose_name=_(
-        'Street'), max_length=30, blank=True)
-    housenumber = models.CharField(verbose_name=_(
-        'Street number'), max_length=10, blank=True)
-    postal_code = models.CharField(verbose_name=_(
-        'Postal code'), max_length=5, blank=True)
-    place = models.CharField(verbose_name=_(
-        'Place'), max_length=30, blank=True)
+    street = models.CharField(verbose_name=_("Street"), max_length=30, blank=True)
+    housenumber = models.CharField(verbose_name=_("Street number"), max_length=10, blank=True)
+    postal_code = models.CharField(verbose_name=_("Postal code"), max_length=5, blank=True)
+    place = models.CharField(verbose_name=_("Place"), max_length=30, blank=True)
 
-    phone_number = PhoneNumberField(verbose_name=_('Home phone'), blank=True)
-    mobile_number = PhoneNumberField(
-        verbose_name=_('Mobile phone'), blank=True)
+    phone_number = PhoneNumberField(verbose_name=_("Home phone"), blank=True)
+    mobile_number = PhoneNumberField(verbose_name=_("Mobile phone"), blank=True)
 
-    email = models.EmailField(verbose_name=_('E-mail address'), blank=True)
+    email = models.EmailField(verbose_name=_("E-mail address"), blank=True)
 
-    date_of_birth = models.DateField(
-        verbose_name=_('Date of birth'), blank=True, null=True)
-    sex = models.CharField(verbose_name=_(
-        'Sex'), max_length=1, choices=SEX_CHOICES, blank=True)
+    date_of_birth = models.DateField(verbose_name=_("Date of birth"), blank=True, null=True)
+    sex = models.CharField(verbose_name=_("Sex"), max_length=1, choices=SEX_CHOICES, blank=True)
 
-    photo = ImageCropField(verbose_name=_('Photo'), blank=True, null=True)
-    photo_cropping = ImageRatioField('photo', '600x800', size_warning=True)
+    photo = ImageCropField(verbose_name=_("Photo"), blank=True, null=True)
+    photo_cropping = ImageRatioField("photo", "600x800", size_warning=True)
 
-    import_ref = models.CharField(verbose_name=_(
-        'Reference ID of import source'), max_length=64,
-        blank=True, null=True, editable=False, unique=True)
+    import_ref = models.CharField(
+        verbose_name=_("Reference ID of import source"),
+        max_length=64,
+        blank=True,
+        null=True,
+        editable=False,
+        unique=True,
+    )
 
-    guardians = models.ManyToManyField('self', verbose_name=_('Guardians / Parents'),
-                                       symmetrical=False, related_name='children')
+    guardians = models.ManyToManyField(
+        "self", verbose_name=_("Guardians / Parents"), symmetrical=False, related_name="children"
+    )
 
-    primary_group = models.ForeignKey('Group', models.SET_NULL, null=True)
+    primary_group = models.ForeignKey("Group", models.SET_NULL, null=True)
 
     @property
     def primary_group_short_name(self) -> Optional[str]:
@@ -134,13 +141,12 @@ class Person(models.Model, ExtensibleModel):
         if it can't find one.
         """
 
-        group, created = Group.objects.get_or_create(short_name=value,
-                                                     defaults={'name': value})
+        group, created = Group.objects.get_or_create(short_name=value, defaults={"name": value})
         self.primary_group = group
 
     @property
     def full_name(self) -> str:
-        return '%s, %s' % (self.last_name, self.first_name)
+        return "%s, %s" % (self.last_name, self.first_name)
 
     def __str__(self) -> str:
         return self.full_name
@@ -152,18 +158,21 @@ class Group(models.Model, ExtensibleModel):
     """
 
     class Meta:
-        ordering = ['short_name', 'name']
+        ordering = ["short_name", "name"]
 
-    name = models.CharField(verbose_name=_(
-        'Long name of group'), max_length=60, unique=True)
-    short_name = models.CharField(verbose_name=_(
-        'Short name of group'), max_length=16, unique=True)
+    name = models.CharField(verbose_name=_("Long name of group"), max_length=60, unique=True)
+    short_name = models.CharField(verbose_name=_("Short name of group"), max_length=16, unique=True)
 
-    members = models.ManyToManyField('Person', related_name='member_of')
-    owners = models.ManyToManyField('Person', related_name='owner_of')
+    members = models.ManyToManyField("Person", related_name="member_of")
+    owners = models.ManyToManyField("Person", related_name="owner_of")
 
-    parent_groups = models.ManyToManyField('self', related_name='child_groups',
-                                           symmetrical=False, verbose_name=_('Parent groups'), blank=True)
+    parent_groups = models.ManyToManyField(
+        "self",
+        related_name="child_groups",
+        symmetrical=False,
+        verbose_name=_("Parent groups"),
+        blank=True,
+    )
 
     def __str__(self) -> str:
-        return '%s (%s)' % (self.name, self.short_name)
+        return "%s (%s)" % (self.name, self.short_name)
diff --git a/biscuit/core/settings.py b/biscuit/core/settings.py
index 61c92d3ea50a109873dbdb3e13f8e1bbd4100f9c..65ab18f801923660f2c34c2facdd3ec7dc067aef 100644
--- a/biscuit/core/settings.py
+++ b/biscuit/core/settings.py
@@ -1,6 +1,6 @@
-from glob import glob
 import os
 import sys
+from glob import glob
 
 from django.utils.translation import ugettext_lazy as _
 
@@ -9,153 +9,153 @@ from easy_thumbnails.conf import Settings as thumbnail_settings
 
 from .util.core_helpers import get_app_packages
 
-ENVVAR_PREFIX_FOR_DYNACONF = 'BISCUIT'
-DIRS_FOR_DYNACONF = ['/etc/biscuit']
+ENVVAR_PREFIX_FOR_DYNACONF = "BISCUIT"
+DIRS_FOR_DYNACONF = ["/etc/biscuit"]
 
 SETTINGS_FILE_FOR_DYNACONF = []
 for directory in DIRS_FOR_DYNACONF:
-    SETTINGS_FILE_FOR_DYNACONF += glob(os.path.join(directory, '*.ini'))
-    SETTINGS_FILE_FOR_DYNACONF += glob(os.path.join(directory, '*.yaml'))
-    SETTINGS_FILE_FOR_DYNACONF += glob(os.path.join(directory, '*.toml'))
+    SETTINGS_FILE_FOR_DYNACONF += glob(os.path.join(directory, "*.ini"))
+    SETTINGS_FILE_FOR_DYNACONF += glob(os.path.join(directory, "*.yaml"))
+    SETTINGS_FILE_FOR_DYNACONF += glob(os.path.join(directory, "*.toml"))
 
 _settings = LazySettings(
     ENVVAR_PREFIX_FOR_DYNACONF=ENVVAR_PREFIX_FOR_DYNACONF,
-    SETTINGS_FILE_FOR_DYNACONF=SETTINGS_FILE_FOR_DYNACONF
+    SETTINGS_FILE_FOR_DYNACONF=SETTINGS_FILE_FOR_DYNACONF,
 )
 
 # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 
 # SECURITY WARNING: keep the secret key used in production secret!
-SECRET_KEY = _settings.get('secret_key', 'DoNotUseInProduction')
+SECRET_KEY = _settings.get("secret_key", "DoNotUseInProduction")
 
 # SECURITY WARNING: don't run with debug turned on in production!
-DEBUG = _settings.get('maintenance.debug', False)
-INTERNAL_IPS = _settings.get('maintenance.internal_ips', [])
+DEBUG = _settings.get("maintenance.debug", False)
+INTERNAL_IPS = _settings.get("maintenance.internal_ips", [])
 DEBUG_TOOLBAR_CONFIG = {
-    'RENDER_PANELS': True,
-    'SHOW_COLLAPSED': True
+    "RENDER_PANELS": True,
+    "SHOW_COLLAPSED": True,
+    "JQUERY_URL": "",
+    "SHOW_TOOLBAR_CALLBACK": "biscuit.core.util.core_helpers.dt_show_toolbar",
 }
 
-ALLOWED_HOSTS = _settings.get('http.allowed_hosts', [])
+ALLOWED_HOSTS = _settings.get("http.allowed_hosts", [])
 
 # Application definition
 INSTALLED_APPS = [
-    'django.contrib.admin',
-    'django.contrib.auth',
-    'django.contrib.contenttypes',
-    'django.contrib.sessions',
-    'django.contrib.messages',
-    'django.contrib.staticfiles',
-    'django_global_request',
-    'settings_context_processor',
-    'sass_processor',
-    'easyaudit',
-    'dbbackup',
-    'dbsettings',
-    'django_cron',
-    'bootstrap4',
-    'fa',
-    'django_any_js',
-    'django_yarnpkg',
-    'django_tables2',
-    'easy_thumbnails',
-    'image_cropping',
-    'maintenance_mode',
-    'menu_generator',
-    'phonenumber_field',
-    'debug_toolbar',
-    'contact_form',
-    'django_select2',
-    'hattori',
-    'django_otp.plugins.otp_totp',
-    'django_otp.plugins.otp_static',
-    'django_otp',
-    'otp_yubikey',
-    'biscuit.core',
-    'impersonate',
-    'two_factor'
+    "django.contrib.admin",
+    "django.contrib.auth",
+    "django.contrib.contenttypes",
+    "django.contrib.sessions",
+    "django.contrib.messages",
+    "django.contrib.staticfiles",
+    "django_global_request",
+    "settings_context_processor",
+    "sass_processor",
+    "easyaudit",
+    "dbbackup",
+    "dbsettings",
+    "django_cron",
+    "bootstrap4",
+    "django_any_js",
+    "django_yarnpkg",
+    "django_tables2",
+    "easy_thumbnails",
+    "image_cropping",
+    "maintenance_mode",
+    "menu_generator",
+    "phonenumber_field",
+    "debug_toolbar",
+    "django_select2",
+    "hattori",
+    "django_otp.plugins.otp_totp",
+    "django_otp.plugins.otp_static",
+    "django_otp",
+    "otp_yubikey",
+    "biscuit.core",
+    "impersonate",
+    "two_factor",
 ]
 
 INSTALLED_APPS += get_app_packages()
 
 STATICFILES_FINDERS = [
-    'django.contrib.staticfiles.finders.FileSystemFinder',
-    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
-    'django_yarnpkg.finders.NodeModulesFinder',
-    'sass_processor.finders.CssFinder'
+    "django.contrib.staticfiles.finders.FileSystemFinder",
+    "django.contrib.staticfiles.finders.AppDirectoriesFinder",
+    "django_yarnpkg.finders.NodeModulesFinder",
+    "sass_processor.finders.CssFinder",
 ]
 
 
 MIDDLEWARE = [
     #    'django.middleware.cache.UpdateCacheMiddleware',
-    'debug_toolbar.middleware.DebugToolbarMiddleware',
-    'django.middleware.security.SecurityMiddleware',
-    'django.contrib.sessions.middleware.SessionMiddleware',
-    'django.middleware.locale.LocaleMiddleware',
-    'django_global_request.middleware.GlobalRequestMiddleware',
-    'django.middleware.common.CommonMiddleware',
-    'django.middleware.csrf.CsrfViewMiddleware',
-    'django.contrib.auth.middleware.AuthenticationMiddleware',
-    'django_otp.middleware.OTPMiddleware',
-    'impersonate.middleware.ImpersonateMiddleware',
-    'django.contrib.messages.middleware.MessageMiddleware',
-    'django.middleware.clickjacking.XFrameOptionsMiddleware',
-    'easyaudit.middleware.easyaudit.EasyAuditMiddleware',
-    'maintenance_mode.middleware.MaintenanceModeMiddleware',
+    "django.middleware.security.SecurityMiddleware",
+    "django.contrib.sessions.middleware.SessionMiddleware",
+    "django.middleware.locale.LocaleMiddleware",
+    "django_global_request.middleware.GlobalRequestMiddleware",
+    "django.middleware.common.CommonMiddleware",
+    "django.middleware.csrf.CsrfViewMiddleware",
+    "django.contrib.auth.middleware.AuthenticationMiddleware",
+    "debug_toolbar.middleware.DebugToolbarMiddleware",
+    "django_otp.middleware.OTPMiddleware",
+    "impersonate.middleware.ImpersonateMiddleware",
+    "django.contrib.messages.middleware.MessageMiddleware",
+    "django.middleware.clickjacking.XFrameOptionsMiddleware",
+    "easyaudit.middleware.easyaudit.EasyAuditMiddleware",
+    "maintenance_mode.middleware.MaintenanceModeMiddleware",
     #    'django.middleware.cache.FetchFromCacheMiddleware'
 ]
 
-ROOT_URLCONF = 'biscuit.core.urls'
+ROOT_URLCONF = "biscuit.core.urls"
 
 TEMPLATES = [
     {
-        'BACKEND': 'django.template.backends.django.DjangoTemplates',
-        'DIRS': [],
-        'APP_DIRS': True,
-        'OPTIONS': {
-            'context_processors': [
-                'django.template.context_processors.debug',
-                'django.template.context_processors.request',
-                'django.contrib.auth.context_processors.auth',
-                'django.contrib.messages.context_processors.messages',
-                'maintenance_mode.context_processors.maintenance_mode',
-                'settings_context_processor.context_processors.settings'
+        "BACKEND": "django.template.backends.django.DjangoTemplates",
+        "DIRS": [],
+        "APP_DIRS": True,
+        "OPTIONS": {
+            "context_processors": [
+                "django.template.context_processors.debug",
+                "django.template.context_processors.request",
+                "django.contrib.auth.context_processors.auth",
+                "django.contrib.messages.context_processors.messages",
+                "maintenance_mode.context_processors.maintenance_mode",
+                "settings_context_processor.context_processors.settings",
             ],
         },
     },
 ]
 
 THUMBNAIL_PROCESSORS = (
-    'image_cropping.thumbnail_processors.crop_corners',
+    "image_cropping.thumbnail_processors.crop_corners",
 ) + thumbnail_settings.THUMBNAIL_PROCESSORS
 
 # Already included by base template / Bootstrap
 IMAGE_CROPPING_JQUERY_URL = None
 
-WSGI_APPLICATION = 'biscuit.core.wsgi.application'
+WSGI_APPLICATION = "biscuit.core.wsgi.application"
 
 
 # Database
 # https://docs.djangoproject.com/en/2.1/ref/settings/#databases
 
 DATABASES = {
-    'default': {
-        'ENGINE': 'django.db.backends.postgresql',
-        'NAME': _settings.get('database.name', 'biscuit'),
-        'USER': _settings.get('database.username', 'biscuit'),
-        'PASSWORD': _settings.get('database.password', None),
-        'HOST': _settings.get('database.host', '127.0.0.1'),
-        'PORT': _settings.get('database.port', '5432'),
-        'ATOMIC_REQUESTS': True
+    "default": {
+        "ENGINE": "django.db.backends.postgresql",
+        "NAME": _settings.get("database.name", "biscuit"),
+        "USER": _settings.get("database.username", "biscuit"),
+        "PASSWORD": _settings.get("database.password", None),
+        "HOST": _settings.get("database.host", "127.0.0.1"),
+        "PORT": _settings.get("database.port", "5432"),
+        "ATOMIC_REQUESTS": True,
     }
 }
 
-if _settings.get('caching.memcached.enabled', True):
+if _settings.get("caching.memcached.enabled", True):
     CACHES = {
-        'default': {
-            'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
-            'LOCATION': _settings.get('caching.memcached.address', '127.0.0.1:11211')
+        "default": {
+            "BACKEND": "django.core.cache.backends.memcached.MemcachedCache",
+            "LOCATION": _settings.get("caching.memcached.address", "127.0.0.1:11211"),
         }
     }
 
@@ -163,65 +163,57 @@ if _settings.get('caching.memcached.enabled', True):
 # https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators
 
 AUTH_PASSWORD_VALIDATORS = [
-    {
-        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
-    },
-    {
-        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
-    },
-    {
-        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
-    },
-    {
-        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
-    },
+    {"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",},
+    {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",},
+    {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",},
+    {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",},
 ]
 
 # Authentication backends are dynamically populated
 AUTHENTICATION_BACKENDS = []
 
-if _settings.get('ldap.uri', None):
+if _settings.get("ldap.uri", None):
     # LDAP dependencies are not necessarily installed, so import them here
     import ldap  # noqa
     from django_auth_ldap.config import LDAPSearch, GroupOfNamesType  # noqa
 
     # Enable Django's integration to LDAP
-    AUTHENTICATION_BACKENDS.append('django_auth_ldap.backend.LDAPBackend')
+    AUTHENTICATION_BACKENDS.append("django_auth_ldap.backend.LDAPBackend")
 
-    AUTH_LDAP_SERVER_URI = _settings.get('ldap.uri')
+    AUTH_LDAP_SERVER_URI = _settings.get("ldap.uri")
 
     # Optional: non-anonymous bind
-    if _settings.get('ldap.bind.dn', None):
-        AUTH_LDAP_BIND_DN = _settings.get('ldap.bind.dn')
-        AUTH_LDAP_BIND_PASSWORD = _settings.get('ldap.bind.password')
+    if _settings.get("ldap.bind.dn", None):
+        AUTH_LDAP_BIND_DN = _settings.get("ldap.bind.dn")
+        AUTH_LDAP_BIND_PASSWORD = _settings.get("ldap.bind.password")
 
     # Search attributes to find users by username
     AUTH_LDAP_USER_SEARCH = LDAPSearch(
-        _settings.get('ldap.users.base'),
+        _settings.get("ldap.users.base"),
         ldap.SCOPE_SUBTREE,
-        _settings.get('ldap.users.filter', '(uid=%(user)s)')
+        _settings.get("ldap.users.filter", "(uid=%(user)s)"),
     )
 
     # Mapping of LDAP attributes to Django model fields
     AUTH_LDAP_USER_ATTR_MAP = {
-        'first_name': _settings.get('ldap.map.first_name', 'givenName'),
-        'last_name': _settings.get('ldap.map.first_name', 'sn'),
-        'email': _settings.get('ldap.map.email', 'mail'),
+        "first_name": _settings.get("ldap.map.first_name", "givenName"),
+        "last_name": _settings.get("ldap.map.first_name", "sn"),
+        "email": _settings.get("ldap.map.email", "mail"),
     }
 
 # Add ModelBckend last so all other backends get a chance
 # to verify passwords first
-AUTHENTICATION_BACKENDS.append('django.contrib.auth.backends.ModelBackend')
+AUTHENTICATION_BACKENDS.append("django.contrib.auth.backends.ModelBackend")
 
 # Internationalization
 # https://docs.djangoproject.com/en/2.1/topics/i18n/
 
 LANGUAGES = [
-    ('de', _('German')),
-    ('en', _('English')),
+    ("de", _("German")),
+    ("en", _("English")),
 ]
-LANGUAGE_CODE = _settings.get('l10n.lang', 'en')
-TIME_ZONE = _settings.get('l10n.tz', 'UTC')
+LANGUAGE_CODE = _settings.get("l10n.lang", "en")
+TIME_ZONE = _settings.get("l10n.tz", "UTC")
 USE_I18N = True
 USE_L10N = True
 USE_TZ = True
@@ -230,121 +222,107 @@ USE_TZ = True
 # https://docs.djangoproject.com/en/2.1/howto/static-files/
 
 
-STATIC_URL = _settings.get('static.url', '/static/')
-MEDIA_URL = _settings.get('media.url', '/media/')
-
-LOGIN_REDIRECT_URL = 'index'
-LOGOUT_REDIRECT_URL = 'index'
+STATIC_URL = _settings.get("static.url", "/static/")
+MEDIA_URL = _settings.get("media.url", "/media/")
 
-STATIC_ROOT = _settings.get('static.root', os.path.join(BASE_DIR, 'static'))
-MEDIA_ROOT = _settings.get('media.root', os.path.join(BASE_DIR, 'media'))
-NODE_MODULES_ROOT = _settings.get('node_modules.root', os.path.join(BASE_DIR, 'node_modules'))
+LOGIN_REDIRECT_URL = "index"
+LOGOUT_REDIRECT_URL = "index"
 
-YARN_INSTALLED_APPS = [
-    'bootstrap',
-    'font-awesome',
-    'jquery',
-    'popper.js',
-    'datatables',
-    'select2'
-]
+STATIC_ROOT = _settings.get("static.root", os.path.join(BASE_DIR, "static"))
+MEDIA_ROOT = _settings.get("media.root", os.path.join(BASE_DIR, "media"))
+NODE_MODULES_ROOT = _settings.get("node_modules.root", os.path.join(BASE_DIR, "node_modules"))
 
-JS_URL = _settings.get('js_assets.url', STATIC_URL)
-JS_ROOT = _settings.get('js_assets.root', NODE_MODULES_ROOT+'/node_modules')
+YARN_INSTALLED_APPS = ["bootstrap", "@mdi/font", "jquery", "popper.js", "datatables", "select2"]
 
-FONT_AWESOME = {'url': JS_URL+'/font-awesome/css/font-awesome.min.css'}
+JS_URL = _settings.get("js_assets.url", STATIC_URL)
+JS_ROOT = _settings.get("js_assets.root", NODE_MODULES_ROOT + "/node_modules")
 
 BOOTSTRAP4 = {
-    'css_url': JS_URL+'/bootstrap/dist//css/bootstrap.min.css',
-    'javascript_url': JS_URL+'/bootstrap/dist/js/bootstrap.min.js',
-    'jquery_url': JS_URL+'/jquery/dist/jquery.min.js',
-    'popper_url': JS_URL+'/popper.js/dist/umd/popper.min.js',
-    'include_jquery': True,
-    'include_popper': True,
-    'javascript_in_head': True
+    "css_url": JS_URL + "/bootstrap/dist//css/bootstrap.min.css",
+    "javascript_url": JS_URL + "/bootstrap/dist/js/bootstrap.min.js",
+    "jquery_url": JS_URL + "/jquery/dist/jquery.min.js",
+    "popper_url": JS_URL + "/popper.js/dist/umd/popper.min.js",
+    "include_jquery": True,
+    "include_popper": True,
+    "javascript_in_head": True,
 }
 
-SELECT2_CSS = JS_URL+'/select2/dist/css/select2.min.css'
-SELECT2_JS = JS_URL+'/select2/dist/js/select2.min.js'
-SELECT2_I18N_PATH = JS_URL+'/select2/dist/js/i18n'
+SELECT2_CSS = JS_URL + "/select2/dist/css/select2.min.css"
+SELECT2_JS = JS_URL + "/select2/dist/js/select2.min.js"
+SELECT2_I18N_PATH = JS_URL + "/select2/dist/js/i18n"
 
 ANY_JS = {
-    'DataTables': {
-        'js_url': JS_URL+'/datatables/media/js/jquery.dataTables.min.js'
+    "DataTables": {"js_url": JS_URL + "/datatables/media/js/jquery.dataTables.min.js"},
+    "DataTables-Bootstrap4": {
+        "css_url": JS_URL + "/datatables/media/css/dataTables.bootstrap4.min.css",
+        "js_url": JS_URL + "/datatables/media/js/dataTables.bootstrap4.min.js",
     },
-    'DataTables-Bootstrap4': {
-        'css_url': JS_URL+'/datatables/media/css/dataTables.bootstrap4.min.css',
-        'js_url': JS_URL+'/datatables/media/js/dataTables.bootstrap4.min.js'
-    }
+    "material-design-icons": {"css_url": JS_URL + "@mdi/font/css/materialdesignicons.css"},
 }
 
 SASS_PROCESSOR_AUTO_INCLUDE = False
 SASS_PROCESSOR_CUSTOM_FUNCTIONS = {
-    'get-colour': 'biscuit.core.util.sass_helpers.get_colour',
-    'get-theme-setting': 'biscuit.core.util.sass_helpers.get_theme_setting',
+    "get-colour": "biscuit.core.util.sass_helpers.get_colour",
+    "get-theme-setting": "biscuit.core.util.sass_helpers.get_theme_setting",
 }
-SASS_PROCESSOR_INCLUDE_DIRS = [
-    _settings.get('bootstrap.sass_path', JS_ROOT+'/bootstrap/scss/')
-]
-
-ADMINS = _settings.get('contact.admins', [])
-SERVER_EMAIL = _settings.get('contact.from', 'root@localhost')
-DEFAULT_FROM_EMAIL = _settings.get('contact.from', 'root@localhost')
-MANAGERS = _settings.get('contact.admins', [])
-
-if _settings.get('mail.server.host', None):
-    EMAIL_HOST = _settings.get('mail.server.host')
-    EMAIL_USE_TLS = _settings.get('mail.server.tls', False)
-    EMAIL_USE_SSL = _settings.get('mail.server.ssl', False)
-    if _settings.get('mail.server.port', None):
-        EMAIL_PORT = _settings.get('mail.server.port')
-    if _settings.get('mail.server.user', None):
-        EMAIL_HOST_USER = _settings.get('mail.server.user')
-        EMAIL_HOST_PASSWORD = _settings.get('mail.server.password')
-
-TEMPLATE_VISIBLE_SETTINGS = ['ADMINS', 'DEBUG']
-
-MAINTENANCE_MODE = _settings.get('maintenance.enabled', None)
+SASS_PROCESSOR_INCLUDE_DIRS = [_settings.get("bootstrap.sass_path", JS_ROOT + "/bootstrap/scss/")]
+
+ADMINS = _settings.get("contact.admins", [])
+SERVER_EMAIL = _settings.get("contact.from", "root@localhost")
+DEFAULT_FROM_EMAIL = _settings.get("contact.from", "root@localhost")
+MANAGERS = _settings.get("contact.admins", [])
+
+if _settings.get("mail.server.host", None):
+    EMAIL_HOST = _settings.get("mail.server.host")
+    EMAIL_USE_TLS = _settings.get("mail.server.tls", False)
+    EMAIL_USE_SSL = _settings.get("mail.server.ssl", False)
+    if _settings.get("mail.server.port", None):
+        EMAIL_PORT = _settings.get("mail.server.port")
+    if _settings.get("mail.server.user", None):
+        EMAIL_HOST_USER = _settings.get("mail.server.user")
+        EMAIL_HOST_PASSWORD = _settings.get("mail.server.password")
+
+TEMPLATE_VISIBLE_SETTINGS = ["ADMINS", "DEBUG"]
+
+MAINTENANCE_MODE = _settings.get("maintenance.enabled", None)
 MAINTENANCE_MODE_IGNORE_IP_ADDRESSES = _settings.get(
-    'maintenance.ignore_ips', _settings.get('maintenance.internal_ips', []))
-MAINTENANCE_MODE_GET_CLIENT_IP_ADDRESS = 'ipware.ip.get_ip'
+    "maintenance.ignore_ips", _settings.get("maintenance.internal_ips", [])
+)
+MAINTENANCE_MODE_GET_CLIENT_IP_ADDRESS = "ipware.ip.get_ip"
 MAINTENANCE_MODE_IGNORE_SUPERUSER = True
-MAINTENANCE_MODE_STATE_FILE_PATH = _settings.get('maintenance.statefile', 'maintenance_mode_state.txt')
+MAINTENANCE_MODE_STATE_FILE_PATH = _settings.get(
+    "maintenance.statefile", "maintenance_mode_state.txt"
+)
 
-IMPERSONATE = {
-    'USE_HTTP_REFERER': True,
-    'REQUIRE_SUPERUSER': True,
-    'ALLOW_SUPERUSER': True
-}
+IMPERSONATE = {"USE_HTTP_REFERER": True, "REQUIRE_SUPERUSER": True, "ALLOW_SUPERUSER": True}
 
 DJANGO_TABLES2_TEMPLATE = "django_tables2/bootstrap4.html"
 
-DBBACKUP_STORAGE = _settings.get('backup.storage', 'django.core.files.storage.FileSystemStorage')
-DBBACKUP_STORAGE_OPTIONS = {
-    'location': _settings.get('backup.location', '/var/backups/biscuit')
-}
-DBBACKUP_CLEANUP_KEEP = _settings.get('backup.keep.database', 10)
-DBBACKUP_CLEANUP_KEEP_MEDIA = _settings.get('backup.keep.media', 10)
-DBBACKUP_CRON_TIMES = _settings.get('backup.times', None) or ['03:57']
+DBBACKUP_STORAGE = _settings.get("backup.storage", "django.core.files.storage.FileSystemStorage")
+DBBACKUP_STORAGE_OPTIONS = {"location": _settings.get("backup.location", "/var/backups/biscuit")}
+DBBACKUP_CLEANUP_KEEP = _settings.get("backup.keep.database", 10)
+DBBACKUP_CLEANUP_KEEP_MEDIA = _settings.get("backup.keep.media", 10)
+DBBACKUP_CRON_TIMES = _settings.get("backup.times", None) or ["03:57"]
 
-CRON_CLASSES = [
-    'biscuit.core.cronjobs.Backup'
-]
+CRON_CLASSES = ["biscuit.core.cronjobs.Backup"]
 
-ANONYMIZE_ENABLED = _settings.get('maintenance.anonymisable', True)
+ANONYMIZE_ENABLED = _settings.get("maintenance.anonymisable", True)
 
-LOGIN_URL = 'two_factor:login'
+LOGIN_URL = "two_factor:login"
 
-if _settings.get('2fa.call.enabled', False):
-    TWO_FACTOR_CALL_GATEWAY = 'two_factor.gateways.twilio.gateway.Twilio'
+if _settings.get("2fa.call.enabled", False):
+    TWO_FACTOR_CALL_GATEWAY = "two_factor.gateways.twilio.gateway.Twilio"
 
-if _settings.get('2fa.sms.enabled', False):
-    TWO_FACTOR_SMS_GATEWAY = 'two_factor.gateways.twilio.gateway.Twilio'
+if _settings.get("2fa.sms.enabled", False):
+    TWO_FACTOR_SMS_GATEWAY = "two_factor.gateways.twilio.gateway.Twilio"
 
-if _settings.get('2fa.twilio.sid', None):
-    MIDDLEWARE.insert(MIDDLEWARE.index('django_otp.middleware.OTPMiddleware')+1, 'two_factor.middleware.threadlocals.ThreadLocals')
-    TWILIO_SID = _settings.get('2fa.twilio.sid')
-    TWILIO_TOKEN = _settings.get('2fa.twilio.token')
-    TWILIO_CALLER_ID = _settings.get('2fa.twilio.callerid')
+if _settings.get("2fa.twilio.sid", None):
+    MIDDLEWARE.insert(
+        MIDDLEWARE.index("django_otp.middleware.OTPMiddleware") + 1,
+        "two_factor.middleware.threadlocals.ThreadLocals",
+    )
+    TWILIO_SID = _settings.get("2fa.twilio.sid")
+    TWILIO_TOKEN = _settings.get("2fa.twilio.token")
+    TWILIO_CALLER_ID = _settings.get("2fa.twilio.callerid")
 
 _settings.populate_obj(sys.modules[__name__])
diff --git a/biscuit/core/signals.py b/biscuit/core/signals.py
index e531c5b9ed0158c3986144ab68e6c0eaeea9eda9..bcd55fe623bc3f3952a0853e8f8688630978194a 100644
--- a/biscuit/core/signals.py
+++ b/biscuit/core/signals.py
@@ -1,11 +1,11 @@
-from glob import glob
 import os
+from glob import glob
 
 from django.conf import settings
 
 
 def clean_scss(*args, **kwargs) -> None:
-    for source_map in glob(os.path.join(settings.STATIC_ROOT, '*.css.map')):
+    for source_map in glob(os.path.join(settings.STATIC_ROOT, "*.css.map")):
         try:
             os.unlink(source_map)
         except OSError:
diff --git a/biscuit/core/tables.py b/biscuit/core/tables.py
index 39edd10b77bea1d75d1ad532ba387383bc093513..b8190272916e32cc67007d31409963d42bd02b8d 100644
--- a/biscuit/core/tables.py
+++ b/biscuit/core/tables.py
@@ -4,15 +4,15 @@ from django_tables2.utils import A
 
 class PersonsTable(tables.Table):
     class Meta:
-        attrs = {'class': 'table table-striped table-bordered table-hover table-responsive-xl'}
+        attrs = {"class": "table table-striped table-bordered table-hover table-responsive-xl"}
 
-    first_name = tables.LinkColumn('person_by_id', args=[A('id')])
-    last_name = tables.LinkColumn('person_by_id', args=[A('id')])
+    first_name = tables.LinkColumn("person_by_id", args=[A("id")])
+    last_name = tables.LinkColumn("person_by_id", args=[A("id")])
 
 
 class GroupsTable(tables.Table):
     class Meta:
-        attrs = {'class': 'table table-striped table-bordered table-hover table-responsive-xl'}
+        attrs = {"class": "table table-striped table-bordered table-hover table-responsive-xl"}
 
-    name = tables.LinkColumn('group_by_id', args=[A('id')])
-    short_name = tables.LinkColumn('group_by_id', args=[A('id')])
+    name = tables.LinkColumn("group_by_id", args=[A("id")])
+    short_name = tables.LinkColumn("group_by_id", args=[A("id")])
diff --git a/biscuit/core/templates/contact_form/contact_form.html b/biscuit/core/templates/contact_form/contact_form.html
deleted file mode 100644
index 00a6caad94102e477bea534a638846baffcb2fe9..0000000000000000000000000000000000000000
--- a/biscuit/core/templates/contact_form/contact_form.html
+++ /dev/null
@@ -1,17 +0,0 @@
-{# -*- engine:django -*- #}
-{% extends "core/base.html" %}
-{% load bootstrap4 i18n %}
-
-{% block page_title %}BiscuIT SIS support{% endblock %}
-
-{% block content %}
- <h1>{% blocktrans %}Get support{% endblocktrans %}</h1>
-
- <form method="post">
-  {% csrf_token %}
-  {% bootstrap_form form %}
-  <button type="submit" class="btn btn-dark">
-    {% blocktrans %}Send{% endblocktrans %}
-  </button>
- </form>
-{% endblock %}
diff --git a/biscuit/core/templates/contact_form/contact_form.txt b/biscuit/core/templates/contact_form/contact_form.txt
deleted file mode 100644
index 9a8f66d94083d6ab9dd9abb4a1ade3ffc0ee150c..0000000000000000000000000000000000000000
--- a/biscuit/core/templates/contact_form/contact_form.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-From: {{ name }} <{{ email }}>
-
-{{ body }}
diff --git a/biscuit/core/templates/contact_form/contact_form_sent.html b/biscuit/core/templates/contact_form/contact_form_sent.html
deleted file mode 100644
index 86d69b579fb81370e4842f88edf2f7263986de45..0000000000000000000000000000000000000000
--- a/biscuit/core/templates/contact_form/contact_form_sent.html
+++ /dev/null
@@ -1,13 +0,0 @@
-{# -*- engine:django -*- #}
-{% extends "core/base.html" %}
-{% load bootstrap4 i18n %}
-
-{% block page_title %}BiscuIT School Information System (SIS){% endblock %}
-
-{% block content %}
- <div class="alert alert-success" role="alert">
-  {% blocktrans %}
-   The message was successfully submitted.
-  {% endblocktrans %}
- </div>
-{% endblock %}
diff --git a/biscuit/core/templates/contact_form/contact_form_subject.txt b/biscuit/core/templates/contact_form/contact_form_subject.txt
deleted file mode 100644
index 5fcb23b2da374cb55dc32f3795caf0b3579334ec..0000000000000000000000000000000000000000
--- a/biscuit/core/templates/contact_form/contact_form_subject.txt
+++ /dev/null
@@ -1 +0,0 @@
-[BiscuIT Support]
diff --git a/biscuit/core/templates/core/base.html b/biscuit/core/templates/core/base.html
index 9b440b9ab6ac3ab7a41682f7819861a75ba74348..c87fe544e7a25d15dada319a14cffaca5b817936 100644
--- a/biscuit/core/templates/core/base.html
+++ b/biscuit/core/templates/core/base.html
@@ -2,11 +2,11 @@
 
 {% extends "bootstrap4/bootstrap4.html" %}
 
-{% load bootstrap4 i18n menu_generator static font_awesome any_js sass_tags %}
+{% load bootstrap4 i18n menu_generator static any_js sass_tags %}
 
 {% block bootstrap4_extra_head %}
-  {% fa_css %}
   {% include_css "DataTables-Bootstrap4" %}
+  {% include_css "material-design-icons"%}
   {% include 'core/icons.html' %}
   <link rel="stylesheet" href="{% sass_src 'bootstrap_modified.scss' %}" />
   <link rel="stylesheet" href="{% static 'css/style.css' %}" />
@@ -24,10 +24,10 @@
   <header>
     <nav class="navbar navbar-dark bg-dark navbar-expand-md fixed-top">
       <button type="button" class="navbar-toggler" data-toggle="collapse" data-target="#navbar-main">
-        {% fa 'fa-align-justify' %}
+        <span class="mdi mdi-menu"></span>
       </button>
       <a class="navbar-brand" href="{% url "index" %}">
-        {% fa 'fa-briefcase' %} BiscuIT SIS
+        <span class="mdi mdi-briefcase"></span> BiscuIT SIS
       </a>
 
       <div class="collapse navbar-collapse" id="navbar-main">
diff --git a/biscuit/core/templates/core/crud_events_ul.html b/biscuit/core/templates/core/crud_events_ul.html
index dbe27d8fc25d94e2e1899600be8e3d06dbfd4ae8..8eef4639e7fc56f3ac8a563f2dd7488f8c922479 100644
--- a/biscuit/core/templates/core/crud_events_ul.html
+++ b/biscuit/core/templates/core/crud_events_ul.html
@@ -1,5 +1,3 @@
-{% load font_awesome %}
-
 <ul class="{{ class_ul }}">
   {% for event in obj.crud_events %}
     <li class="{{ class_li }}">
@@ -9,15 +7,15 @@
       </div>
       <span class="badge badge-light text-dark">
         {% if event.event_type == event.CREATE %}
-          {% fa 'plus' %} 
+          <span class="mdi mdi-plus"></span>
         {% elif event.event_type == event.UPDATE %}
-          {% fa 'pencil' %} 
+          <span class="mdi mdi-pencil"></span>
         {% elif event.event_type == event.DELETE %}
-          {% fa 'trash' %} 
+          <span class="mdi mdi-delete"></span>
         {% elif event.event_type == event.M2M_CHANGE %}
-          {% fa 'pencil' %} 
+          <span class="mdi mdi-pencil"></span>
         {% elif event.event_type == event.M2M_CHANGE_REV %}
-          {% fa 'pencil' %} 
+          <span class="mdi mdi-pencil"></span>
         {% endif %}
       </span>
     </li>
diff --git a/biscuit/core/templates/core/edit_group.html b/biscuit/core/templates/core/edit_group.html
index 0eb63393e8c7326012cb6fe6209f4c7e33218126..33739a47f4fcfd94002c862de66a1a553cc1576b 100644
--- a/biscuit/core/templates/core/edit_group.html
+++ b/biscuit/core/templates/core/edit_group.html
@@ -23,7 +23,7 @@
     {% csrf_token %}
     {% bootstrap_form edit_group_form %}
     <button type="submit" class="btn btn-dark">
-      {% blocktrans %}Edit{% endblocktrans %}
+      <span class="mdi mdi-pencil"></span>
     </button>
   </form>
 
diff --git a/biscuit/core/templates/core/edit_person.html b/biscuit/core/templates/core/edit_person.html
index c1b4f8e22115bca0f821c7dcc04b67c8e88b3bf6..e698f01c6cf4c3154ae3c9cd4002d9be28be7b5f 100644
--- a/biscuit/core/templates/core/edit_person.html
+++ b/biscuit/core/templates/core/edit_person.html
@@ -25,7 +25,7 @@
     {% csrf_token %}
     {% bootstrap_form edit_person_form %}
     <button type="submit" class="btn btn-dark">
-      {% blocktrans %}Edit{% endblocktrans %}
+      <span class="mdi mdi-pencil"></span>
     </button>
   </form>
 
diff --git a/biscuit/core/templates/core/edit_school.html b/biscuit/core/templates/core/edit_school.html
index f26ffce4c61c9ccaa9df5205098a74d5ea78e227..8e1b03db0aeef2c080e37387752fc931d84c5b87 100644
--- a/biscuit/core/templates/core/edit_school.html
+++ b/biscuit/core/templates/core/edit_school.html
@@ -19,7 +19,7 @@
     {% csrf_token %}
     {% bootstrap_form edit_school_form %}
     <button type="submit" class="btn btn-dark">
-      {% blocktrans %}Edit{% endblocktrans %}
+      <span class="mdi mdi-pencil"></span>
     </button>
   </form>
 
diff --git a/biscuit/core/templates/core/edit_schoolterm.html b/biscuit/core/templates/core/edit_schoolterm.html
index 1ba2931b24a80b13eff22b6bd9900da00f312132..0a5f083b22c227d95348e89c84d8f589fb4db988 100644
--- a/biscuit/core/templates/core/edit_schoolterm.html
+++ b/biscuit/core/templates/core/edit_schoolterm.html
@@ -19,7 +19,7 @@
     {% csrf_token %}
     {% bootstrap_form edit_term_form %}
     <button type="submit" class="btn btn-dark">
-      {% blocktrans %}Edit{% endblocktrans %}
+      <span class="mdi mdi-pencil"></span>
     </button>
   </form>
 
diff --git a/biscuit/core/templates/core/footer-menu.html b/biscuit/core/templates/core/footer-menu.html
index 7f03b85514fab560ee776f9edc02fa2222bec2b1..6866c019fc2c410993d7df279a434a57567389ea 100644
--- a/biscuit/core/templates/core/footer-menu.html
+++ b/biscuit/core/templates/core/footer-menu.html
@@ -1,6 +1,6 @@
 {# -*- engine:django -*- #}
 
-{% load menu_generator font_awesome i18n %}
+{% load menu_generator i18n %}
 
 {% get_menu "FOOTER_MENU_CORE" as footer_menu %}
 {% regroup footer_menu by submenu|length_is:"0" as footer_menus %}
@@ -12,7 +12,7 @@
         <h5>{{ item.name }}</h5>
         <ul class="list-unstyled quick-links">
           {% for menu in item.submenu %}
-            <li><a class="font-weight-bold text-dark" href="{{ menu.url }}">{% fa 'fa-angle-double-right' %} {{ menu.name }}</a></li>
+            <li><a class="font-weight-bold text-dark" href="{{ menu.url }}"><span class="mdi mdi-chevron-double-right"></span> {{ menu.name }}</a></li>
           {% endfor %}
         </ul>
       </div>
@@ -26,7 +26,7 @@
       <h5>{% blocktrans %}Assorted{% endblocktrans %}</h5>
       <ul class="list-unstyled quick-links">
         {% for item in menu.list %}
-          <li><a class="font-weight-bold text-dark" href="{{ item.url }}">{% fa 'fa-angle-double-right' %} {{ item.name }}</a></li>
+          <li><a class="font-weight-bold text-dark" href="{{ item.url }}"><span class="mdi mdi-chevron-double-right"></span> {{ item.name }}</a></li>
         {% endfor %}
       </ul>
     </div>
diff --git a/biscuit/core/templates/core/group_full.html b/biscuit/core/templates/core/group_full.html
index cb3b8e83fd232312ccef2033ef23f057f15136ed..9cb841e18b5f9d9e68e238b8f246202e4ba97980 100644
--- a/biscuit/core/templates/core/group_full.html
+++ b/biscuit/core/templates/core/group_full.html
@@ -2,7 +2,7 @@
 
 {% extends "core/base.html" %}
 
-{% load bootstrap4 font_awesome i18n static %}
+{% load bootstrap4 i18n static %}
 {% load render_table from django_tables2 %}
 
 {% block bootstrap4_title %}{% blocktrans %}Group{% endblocktrans %} - {{ block.super }}{% endblock %}
@@ -13,13 +13,13 @@
       <h2>{{ group.name }} <small>{{ group.short_name }}</small></h2>
       <p>
         <a href="{% url 'edit_group_by_id' group.id %}">
-          {% blocktrans %}Edit group{% endblocktrans %}
+          <span class="mdi mdi-pencil"></span>
         </a>
       </p>
       <h3>{% blocktrans %}Details{% endblocktrans %}</h3>
       <table class="table table-responsive-xl table-border table-striped">
         <tr>
-          <td>{% fa 'users' %}</td>
+          <td><span class="mdi mdi-account-group"></span></td>
           <td>{{ group.name }}</td>
           <td>{{ group.short_name }}</td>
         </tr>
diff --git a/biscuit/core/templates/core/groups.html b/biscuit/core/templates/core/groups.html
index 72b7ead8d5b3286756ca65e6bb5009d80037f846..6534a8e384dbf48b4820eac1590ebdbcf923b06c 100644
--- a/biscuit/core/templates/core/groups.html
+++ b/biscuit/core/templates/core/groups.html
@@ -2,7 +2,7 @@
 
 {% extends "core/base.html" %}
 
-{% load bootstrap4 font_awesome i18n %}
+{% load bootstrap4 i18n %}
 {% load render_table from django_tables2 %}
 
 {% block bootstrap4_title %}{% blocktrans %}Groups{% endblocktrans %} - {{ block.super }}{% endblock %}
@@ -12,7 +12,7 @@
 {% block content %}
   <div class="btn-group" role="group" aria-lable="Group actions">
     <a href="{% url 'create_group' %}" class="btn btn-dark">
-      {% fa 'plus' %}
+      <span class="mdi mdi-plus"></span>
     </a>
   </div>
 
diff --git a/biscuit/core/templates/core/person_full.html b/biscuit/core/templates/core/person_full.html
index 684a72f40351f4551d154a32db8b5b5ec3656a21..b412765539e921c1ed18d7a7d55c0acad1812f8f 100644
--- a/biscuit/core/templates/core/person_full.html
+++ b/biscuit/core/templates/core/person_full.html
@@ -2,7 +2,7 @@
 
 {% extends "core/base.html" %}
 
-{% load bootstrap4 font_awesome i18n static cropping %}
+{% load bootstrap4 i18n static cropping %}
 {% load render_table from django_tables2 %}
 
 {% block bootstrap4_title %}{% blocktrans %}Person{% endblocktrans %} - {{ block.super }}{% endblock %}
@@ -13,7 +13,7 @@
       <h2>{{ person.first_name }} {{ person.last_name }}</h2>
       <p>
         <a href="{% url 'edit_person_by_id' person.id %}">
-          {% blocktrans %}Edit person{% endblocktrans %}
+          <span class="mdi mdi-pencil"></span>
         </a>
       </p>
       <h3>{% blocktrans %}Contact details{% endblocktrans %}</h3>
@@ -26,36 +26,36 @@
               <img class="person-img" src="{% static 'img/fallback.png' %}" alt="{{ person.first_name }} {{ person.last_name }}" />
             {% endif %}
           </td>
-          <td>{% fa 'user' %}</td>
+          <td><span class="mdi mdi-account"></span></td>
           <td>{{ person.first_name }}</td>
           <td>{{ person.additional_name }}</td>
           <td>{{ person.last_name }}</td>
         </tr>
         <tr>
-          <td>{% fa 'venus-mars' %}</td>
+          <td><span class="mdi mdi-gender-male-female"></td>
           <td colspan="3">{{ person.get_sex_display }}</td>
         </tr>
         <tr>
-          <td>{% fa 'fa-home' %}</td>
+          <td><span class="mdi mdi-home"></span></td>
           <td colspan="2">{{ person.street }} {{ person.housenumber }}</td>
           <td colspan="2">{{ person.postal_code }} {{ person.place }}</td>
         </tr>
         <tr>
-          <td>{% fa 'phone-square' %}</td>
+          <td><span class="mdi mdi-phone"></span></td>
           <td>{{ person.phone_number }}</td>
           <td>{{ person.mobile_number }}</td>
         </tr>
         <tr>
-          <td>{% fa 'envelope' %}</td>
+          <td><span class="mdi mdi-email"></span></td>
           <td colspan="3">{{ person.email }}</td>
         </tr>
         <tr>
-          <td>{% fa 'gift' %}</td>
+          <td><span class="mdi mdi-cake"></span></td>
           <td colspan="3">{{ person.date_of_birth|date }}</td>
         </tr>
         {% comment %}
           <tr>
-          <td>{% fa 'graduation-cap' %}</td>
+          <td><span class="mdi mdi-school"></span></td>
           <td>Class</td>
           <td>Teacher</td>
           </tr>
diff --git a/biscuit/core/templates/core/system_status.html b/biscuit/core/templates/core/system_status.html
index 881a244811787bd157ea45add6a93fce8319e1cd..7b8006dd28717c04e82c206756e7027f20b45111 100644
--- a/biscuit/core/templates/core/system_status.html
+++ b/biscuit/core/templates/core/system_status.html
@@ -1,6 +1,6 @@
 {# -*- engine:django -*- #}
 {% extends "core/base.html" %}
-{% load i18n font_awesome %}
+{% load i18n %}
 
 {% block bootstrap4_title %}{% blocktrans %}System status{% endblocktrans %} - {{ block.super }}{% endblock %}
 
@@ -13,18 +13,18 @@
     </div>
     <ul class="list-group list-group-flush">
       <li class="list-group-item d-flex justify-content-between align-items-center">
-       {% if maintenance_mode %} 
+       {% if maintenance_mode %}
         <div>
           <p>{% blocktrans %}Maintenance mode enabled{% endblocktrans %}</p>
           <p class="small">{% blocktrans%}Only admin and visitors from internal IPs can access the site.{% endblocktrans %}</p>
         </div>
-        <span class="badge badge-danger"><a href="{% url 'maintenance_mode_off' %}">{% fa 'power-off' %}</a></span>
+        <span class="badge badge-danger mdi mdi-power"><a href="{% url 'maintenance_mode_off' %}"></a></span>
        {% else %}
         <div>
           <p>{% blocktrans %}Maintenance mode disabled{% endblocktrans %}</p>
           <p class="small">{% blocktrans%}Everyone can access the site.{% endblocktrans %}</p>
         </div>
-        <span class="badge badge-success"><a href="{% url 'maintenance_mode_on' %}">{% fa 'power-off' %}</a></span>
+        <span class="badge badge-success mdi mdi-power"><a href="{% url 'maintenance_mode_on' %}"></a></span>
        {% endif %}
       </li>
       <li class="list-group-item d-flex justify-content-between align-items-center">
@@ -33,13 +33,13 @@
           <p>{% blocktrans %}Debug mode enabled{% endblocktrans %}</p>
           <p class="small">{% blocktrans%}The web server throws back debug information on errors. Do not use in production!{% endblocktrans %}</p>
         </div>
-        <span class="badge badge-danger">{% fa 'power-off' %}</span>
+        <span class="badge badge-danger mdi mdi-power"> </span>
        {% else %}
         <div>
           <p>{% blocktrans %}Debug mode disabled{% endblocktrans %}</p>
           <p class="small">{% blocktrans%}Debug mode is disabled. Default error pages are displayed on errors.{% endblocktrans %}</p>
         </div>
-        <span class="badge badge-success">{% fa 'power-off' %}</span>
+        <span class="badge badge-success mdi mdi-power"> </span>
         {% endif %}
       </li>
     </ul>
@@ -56,9 +56,9 @@
             <p>{{ backup.end_time }}</p>
           </div>
           {% if backup.is_success %}
-            <span class="badge badge-success">{% fa 'check' %}</span>
+            <span class="badge badge-success mdi mdi-check"> </span>
           {% else %}
-            <span class="badge badge-danger">{% fa 'times' %}</span>
+            <span class="badge badge-danger mdi mdi-error"> </span>
           {% endif %}
         </li>
       {% endfor %}
diff --git a/biscuit/core/templates/core/turnable.html b/biscuit/core/templates/core/turnable.html
index 61e4b3764b6fb439cdcf0221cc6974461a168ce5..237f7576f8a250bbe9ba1de42becbc093ffe6b96 100644
--- a/biscuit/core/templates/core/turnable.html
+++ b/biscuit/core/templates/core/turnable.html
@@ -2,7 +2,6 @@
 
 {% extends "core/base.html" %}
 
-{% load font_awesome %}
 
 {% block content %}
 
@@ -12,10 +11,10 @@
    </div>
    <div class="btn-group" role="group" aria-label="URL actions">
      <a href="{{ url_prev }}" class="btn btn-dark">
-       {% fa 'arrow-left' %}
+       <span class="mdi mdi-chevron-left"></span>
      </a>
      <a href="{{ url_next }}" class="btn btn-dark">
-       {% fa 'arrow-right' %}
+       <span class="mdi mdi-chevron-right"></span>
      </a>
    </div>
   </div>
diff --git a/biscuit/core/templatetags/data_helpers.py b/biscuit/core/templatetags/data_helpers.py
index 66b41efd37e7ddbae179dd2d6dfef619f0ff3c98..0b195d136d86587f892635d6d0e41be1aebada68 100644
--- a/biscuit/core/templatetags/data_helpers.py
+++ b/biscuit/core/templatetags/data_helpers.py
@@ -11,7 +11,7 @@ def get_dict(value: Any, arg: Any) -> Any:
 
     if hasattr(value, str(arg)):
         return getattr(value, arg)
-    elif hasattr(value, 'keys') and arg in value.keys():
+    elif hasattr(value, "keys") and arg in value.keys():
         return value[arg]
     elif str(arg).isnumeric() and len(value) > int(arg):
         return value[int(arg)]
diff --git a/biscuit/core/tests/browser/test_selenium.py b/biscuit/core/tests/browser/test_selenium.py
new file mode 100644
index 0000000000000000000000000000000000000000..4fff999cab330c75c6166b55307919f682970d4f
--- /dev/null
+++ b/biscuit/core/tests/browser/test_selenium.py
@@ -0,0 +1,60 @@
+import os
+
+from django.conf import settings
+from django.test.selenium import SeleniumTestCase, SeleniumTestCaseBase
+from django.urls import reverse
+
+import pytest
+
+pytestmark = pytest.mark.django_db
+
+SeleniumTestCaseBase.external_host = os.environ.get("TEST_HOST", "") or None
+SeleniumTestCaseBase.browsers = list(
+    filter(bool, os.environ.get("TEST_SELENIUM_BROWSERS", "").split(","))
+)
+SeleniumTestCaseBase.selenium_hub = os.environ.get("TEST_SELENIUM_HUB", "") or None
+
+
+class SeleniumTests(SeleniumTestCase):
+    serialized_rollback = True
+
+    @classmethod
+    def _screenshot(cls, filename):
+        screenshot_path = os.environ.get("TEST_SCREENSHOT_PATH", None)
+        if screenshot_path:
+            os.makedirs(os.path.join(screenshot_path, cls.browser), exist_ok=True)
+            return cls.selenium.save_screenshot(
+                os.path.join(screenshot_path, cls.browser, filename)
+            )
+        else:
+            return False
+
+    def test_index(self):
+        self.selenium.get(self.live_server_url + "/")
+        assert "BiscuIT" in self.selenium.title
+        self._screenshot("index.png")
+
+    def test_login_default_superuser(self):
+        username = "admin"
+        password = "admin"
+
+        # Navigate to configured login page
+        self.selenium.get(self.live_server_url + reverse(settings.LOGIN_URL))
+        self._screenshot("login_default_superuser_blank.png")
+
+        # Find login form input fields and enter defined credentials
+        self.selenium.find_element_by_xpath(
+            '//label[contains(text(), "Username")]/../input'
+        ).send_keys(username)
+        self.selenium.find_element_by_xpath(
+            '//label[contains(text(), "Password")]/../input'
+        ).send_keys(password)
+        self._screenshot("login_default_superuser_filled.png")
+
+        # Submit form by clicking django-two-factor-auth's Next button
+        self.selenium.find_element_by_xpath('//button[contains(text(), "Next")]').click()
+        self._screenshot("login_default_superuser_submitted.png")
+
+        # Should redirect away from login page and not put up an alert about wrong credentials
+        assert "Please enter a correct username and password." not in self.selenium.page_source
+        assert reverse(settings.LOGIN_URL) not in self.selenium.current_url
diff --git a/biscuit/core/tests/models/test_person.py b/biscuit/core/tests/models/test_person.py
index e454d3b60709498564c1c005242694b08ad805bb..62861ae1d82b7359080b653049038c9e142a7a27 100644
--- a/biscuit/core/tests/models/test_person.py
+++ b/biscuit/core/tests/models/test_person.py
@@ -2,12 +2,10 @@ import pytest
 
 from biscuit.core.models import Person
 
+pytestmark = pytest.mark.django_db
+
 
-@pytest.mark.django_db
 def test_full_name():
-    _person = Person.objects.create(
-            first_name='Jane',
-            last_name='Doe'
-    )
+    _person = Person.objects.create(first_name="Jane", last_name="Doe")
 
-    assert _person.full_name == 'Doe, Jane'
+    assert _person.full_name == "Doe, Jane"
diff --git a/biscuit/core/tests/templatetags/test_data_helpers.py b/biscuit/core/tests/templatetags/test_data_helpers.py
index 096903947c8c21870ad5c05fcffb5b0a8e522a12..5f8e975ffdffb5b522ee94927add0a38d742045f 100644
--- a/biscuit/core/tests/templatetags/test_data_helpers.py
+++ b/biscuit/core/tests/templatetags/test_data_helpers.py
@@ -1,22 +1,26 @@
 from biscuit.core.templatetags.data_helpers import get_dict
 
+
 def test_get_dict_object():
     class _Foo(object):
         bar = 12
 
-    assert _Foo.bar == get_dict(_Foo, 'bar')
+    assert _Foo.bar == get_dict(_Foo, "bar")
+
 
 def test_get_dict_dict():
-    _foo = {'bar': 12}
+    _foo = {"bar": 12}
+
+    assert _foo["bar"] == get_dict(_foo, "bar")
 
-    assert _foo['bar'] == get_dict(_foo, 'bar')
 
 def test_get_dict_list():
     _foo = [10, 11, 12]
 
     assert _foo[2] == get_dict(_foo, 2)
 
+
 def test_get_dict_invalid():
     _foo = 12
 
-    assert get_dict(_foo, 'bar') is None
+    assert get_dict(_foo, "bar") is None
diff --git a/biscuit/core/tests/views/test_account.py b/biscuit/core/tests/views/test_account.py
index 3bffcf17e9e226ac04230d367219e3cc5942a835..48cf07028fb2ae232a6b1018550a23e8edb04ff4 100644
--- a/biscuit/core/tests/views/test_account.py
+++ b/biscuit/core/tests/views/test_account.py
@@ -1,53 +1,55 @@
-import pytest
-
 from django.conf import settings
 from django.urls import reverse
 
-@pytest.mark.django_db
+import pytest
+
+pytestmark = pytest.mark.django_db
+
+
 def test_index_not_logged_in(client):
-    response = client.get('/')
+    response = client.get("/")
 
     assert response.status_code == 200
-    assert reverse(settings.LOGIN_URL) in response.content.decode('utf-8')
+    assert reverse(settings.LOGIN_URL) in response.content.decode("utf-8")
+
 
-@pytest.mark.django_db
 def test_login(client, django_user_model):
-    username = 'foo'
-    password = 'bar'
+    username = "foo"
+    password = "bar"
 
     django_user_model.objects.create_user(username=username, password=password)
     client.login(username=username, password=password)
 
-    response = client.get('/')
+    response = client.get("/")
 
     assert response.status_code == 200
-    assert reverse(settings.LOGIN_URL) not in response.content.decode('utf-8')
+    assert reverse(settings.LOGIN_URL) not in response.content.decode("utf-8")
+
 
-@pytest.mark.django_db
 def test_index_not_linked_to_person(client, django_user_model):
-    username = 'foo'
-    password = 'bar'
+    username = "foo"
+    password = "bar"
 
     django_user_model.objects.create_user(username=username, password=password)
     client.login(username=username, password=password)
 
-    response = client.get('/')
+    response = client.get("/")
 
     assert response.status_code == 200
-    assert 'You are not linked to a person' in response.content.decode('utf-8')
+    assert "You are not linked to a person" in response.content.decode("utf-8")
+
 
-@pytest.mark.django_db
 def test_logout(client, django_user_model):
-    username = 'foo'
-    password = 'bar'
+    username = "foo"
+    password = "bar"
 
     django_user_model.objects.create_user(username=username, password=password)
     client.login(username=username, password=password)
 
-    response = client.get('/')
+    response = client.get("/")
     assert response.status_code == 200
 
-    response = client.get(reverse('logout'), follow=True)
+    response = client.get(reverse("logout"), follow=True)
 
     assert response.status_code == 200
-    assert reverse(settings.LOGIN_URL) in response.content.decode('utf-8')
+    assert reverse(settings.LOGIN_URL) in response.content.decode("utf-8")
diff --git a/biscuit/core/urls.py b/biscuit/core/urls.py
index dd98dc0175b1e6a13605208615f6fa2ca1482342..1654982eb007266be149bdbfceb119aa177fdea9 100644
--- a/biscuit/core/urls.py
+++ b/biscuit/core/urls.py
@@ -1,7 +1,7 @@
 from django.apps import apps
-from django.contrib import admin
 from django.conf import settings
 from django.conf.urls.static import static
+from django.contrib import admin
 from django.contrib.auth import views as auth_views
 from django.urls import include, path
 
@@ -11,34 +11,30 @@ from two_factor.urls import urlpatterns as tf_urls
 from . import views
 
 urlpatterns = [
-    path('admin/', admin.site.urls),
-    path('data_management/', views.data_management, name='data_management'),
-    path('status/', views.system_status, name='system_status'),
-    path('school_management', views.school_management, name='school_management'),
-    path('school/information/edit', views.edit_school, name='edit_school_information'),
-    path('school/term/edit', views.edit_schoolterm, name='edit_school_term'),
-    path('', include(tf_urls)),
-    path('accounts/logout/', auth_views.LogoutView.as_view(), name='logout'),
-    path('persons', views.persons, name='persons'),
-    path('persons/accounts', views.persons_accounts, name='persons_accounts'),
-    path('person', views.person, name='person'),
-    path('person/<int:id_>', views.person,
-         {'template': 'full'}, name='person_by_id'),
-    path('person/<int:id_>/card', views.person,
-         {'template': 'card'}, name='person_by_id_card'),
-    path('person/<int:id_>/edit', views.edit_person, name='edit_person_by_id'),
-    path('groups', views.groups, name='groups'),
-    path('group/create', views.edit_group, name='create_group'),
-    path('group/<int:id_>', views.group,
-         {'template': 'full'}, name='group_by_id'),
-    path('group/<int:id_>/edit', views.edit_group, name='edit_group_by_id'),
-    path('', views.index, name='index'),
-    path('maintenance-mode/', include('maintenance_mode.urls')),
-    path('contact/', include('contact_form.urls')),
-    path('impersonate/', include('impersonate.urls')),
-    path('__i18n__/', include('django.conf.urls.i18n')),
-    path('select2/', include('django_select2.urls')),
-    path('settings/', include('dbsettings.urls'))
+    path("admin/", admin.site.urls),
+    path("data_management/", views.data_management, name="data_management"),
+    path("status/", views.system_status, name="system_status"),
+    path("school_management", views.school_management, name="school_management"),
+    path("school/information/edit", views.edit_school, name="edit_school_information"),
+    path("school/term/edit", views.edit_schoolterm, name="edit_school_term"),
+    path("", include(tf_urls)),
+    path("accounts/logout/", auth_views.LogoutView.as_view(), name="logout"),
+    path("persons", views.persons, name="persons"),
+    path("persons/accounts", views.persons_accounts, name="persons_accounts"),
+    path("person", views.person, name="person"),
+    path("person/<int:id_>", views.person, {"template": "full"}, name="person_by_id"),
+    path("person/<int:id_>/card", views.person, {"template": "card"}, name="person_by_id_card"),
+    path("person/<int:id_>/edit", views.edit_person, name="edit_person_by_id"),
+    path("groups", views.groups, name="groups"),
+    path("group/create", views.edit_group, name="create_group"),
+    path("group/<int:id_>", views.group, {"template": "full"}, name="group_by_id"),
+    path("group/<int:id_>/edit", views.edit_group, name="edit_group_by_id"),
+    path("", views.index, name="index"),
+    path("maintenance-mode/", include("maintenance_mode.urls")),
+    path("impersonate/", include("impersonate.urls")),
+    path("__i18n__/", include("django.conf.urls.i18n")),
+    path("select2/", include("django_select2.urls")),
+    path("settings/", include("dbsettings.urls")),
 ]
 
 # Serve static files from STATIC_ROOT to make it work with runserver
@@ -46,18 +42,18 @@ urlpatterns = [
 urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
 
 # Add URLs for optional features
-if hasattr(settings, 'TWILIO_ACCOUNT_SID'):
+if hasattr(settings, "TWILIO_ACCOUNT_SID"):
     from two_factor.gateways.twilio.urls import urlpatterns as tf_twilio_urls  # noqa
-    urlpatterns += [path('', include(tf_twilio_urls))]
+
+    urlpatterns += [path("", include(tf_twilio_urls))]
 
 # Serve javascript-common if in development
 if settings.DEBUG:
-    urlpatterns.append(path('__debug__/', include(debug_toolbar.urls)))
+    urlpatterns.append(path("__debug__/", include(debug_toolbar.urls)))
 
 # Automatically mount URLs from all installed BiscuIT apps
 for app_config in apps.app_configs.values():
-    if not app_config.name.startswith('biscuit.apps.'):
+    if not app_config.name.startswith("biscuit.apps."):
         continue
 
-    urlpatterns.append(path('app/%s/' % app_config.label,
-                            include('%s.urls' % app_config.name)))
+    urlpatterns.append(path("app/%s/" % app_config.label, include("%s.urls" % app_config.name)))
diff --git a/biscuit/core/util/apps.py b/biscuit/core/util/apps.py
index 306bb1f701c0419f29610d84766d06ce777f6cd9..f98649cdc261b21443cfc1696af2453c119b6f79 100644
--- a/biscuit/core/util/apps.py
+++ b/biscuit/core/util/apps.py
@@ -11,7 +11,9 @@ class AppConfig(django.apps.AppConfig):
 
         # Run model extension code
         try:
-            import_module('.'.join(self.__class__.__module__.split('.')[:-1] + ['model_extensions']))
+            import_module(
+                ".".join(self.__class__.__module__.split(".")[:-1] + ["model_extensions"])
+            )
         except ImportError:
             # ImportErrors are non-fatal because model extensions are optional.
             pass
diff --git a/biscuit/core/util/core_helpers.py b/biscuit/core/util/core_helpers.py
index e8c0b1fe28c167b25f39eae946077c2d28832723..232890ce4e1789204d8d6c30ed7638ee2499b73c 100644
--- a/biscuit/core/util/core_helpers.py
+++ b/biscuit/core/util/core_helpers.py
@@ -1,10 +1,25 @@
-from importlib import import_module
 import pkgutil
+from importlib import import_module
 from typing import Sequence
 
+from django.conf import settings
 from django.http import HttpRequest
 
 
+def dt_show_toolbar(request: HttpRequest) -> bool:
+    from debug_toolbar.middleware import show_toolbar  # noqa
+
+    if not settings.DEBUG:
+        return False
+
+    if show_toolbar(request):
+        return True
+    elif hasattr(request, "user") and request.user.is_superuser:
+        return True
+
+    return False
+
+
 def get_app_packages() -> Sequence[str]:
     """ Find all packages within the biscuit.apps namespace. """
 
@@ -16,28 +31,28 @@ def get_app_packages() -> Sequence[str]:
 
     pkgs = []
     for pkg in pkgutil.iter_modules(biscuit.apps.__path__):
-        mod = import_module('biscuit.apps.%s' % pkg[1])
+        mod = import_module("biscuit.apps.%s" % pkg[1])
 
         # Add additional apps defined in module's INSTALLED_APPS constant
-        additional_apps = getattr(mod, 'INSTALLED_APPS', [])
+        additional_apps = getattr(mod, "INSTALLED_APPS", [])
         for app in additional_apps:
             if app not in pkgs:
                 pkgs.append(app)
 
-        pkgs.append('biscuit.apps.%s' % pkg[1])
+        pkgs.append("biscuit.apps.%s" % pkg[1])
 
     return pkgs
 
 
 def is_impersonate(request: HttpRequest) -> bool:
-    if hasattr(request, 'user'):
-        return getattr(request.user, 'is_impersonate', False)
+    if hasattr(request, "user"):
+        return getattr(request.user, "is_impersonate", False)
     else:
         return False
 
 
 def has_person(request: HttpRequest) -> bool:
-    if hasattr(request, 'user'):
-        return getattr(request.user, 'person', None) is not None
+    if hasattr(request, "user"):
+        return getattr(request.user, "person", None) is not None
     else:
         return False
diff --git a/biscuit/core/util/messages.py b/biscuit/core/util/messages.py
index 7b847885567b989100b89de4c830d73f4346bb70..e3c93dbb0301ea3eff1c2c7f4b18556d33c09789 100644
--- a/biscuit/core/util/messages.py
+++ b/biscuit/core/util/messages.py
@@ -5,7 +5,9 @@ from django.contrib import messages
 from django.http import HttpRequest
 
 
-def add_message(request: Optional[HttpRequest], level: int, message: str, **kwargs) -> Optional[Any]:
+def add_message(
+    request: Optional[HttpRequest], level: int, message: str, **kwargs
+) -> Optional[Any]:
     if request:
         return messages.add_message(request, level, message, **kwargs)
     else:
diff --git a/biscuit/core/util/sass_helpers.py b/biscuit/core/util/sass_helpers.py
index c609c0ed9941c1485edfb1d7e8111c1a06b414eb..9dbb56bf2652dbd385b1faa2e21c5575642e459a 100644
--- a/biscuit/core/util/sass_helpers.py
+++ b/biscuit/core/util/sass_helpers.py
@@ -12,4 +12,4 @@ def get_colour(html_colour: str) -> SassColor:
 
 
 def get_theme_setting(setting: str) -> str:
-    return getattr(theme_settings, setting, '')
+    return getattr(theme_settings, setting, "")
diff --git a/biscuit/core/views.py b/biscuit/core/views.py
index 585d049ae72bd65c0a8ffd16bba88056850f2f04..509860c76b462b8efa3f86c9b45dfa103d8f4a0b 100644
--- a/biscuit/core/views.py
+++ b/biscuit/core/views.py
@@ -2,22 +2,28 @@ from typing import Optional
 
 from django.contrib.auth.decorators import login_required
 from django.http import Http404, HttpRequest, HttpResponse
-from django.shortcuts import get_object_or_404, render, redirect
-from django_tables2 import RequestConfig
+from django.shortcuts import get_object_or_404, redirect, render
 from django.utils.translation import ugettext_lazy as _
 
 from django_cron.models import CronJobLog
+from django_tables2 import RequestConfig
 
 from .decorators import admin_required
-from .forms import PersonsAccountsFormSet, EditPersonForm, EditGroupForm, EditSchoolForm, EditTermForm
-from .models import Person, Group, School
-from .tables import PersonsTable, GroupsTable
+from .forms import (
+    EditGroupForm,
+    EditPersonForm,
+    EditSchoolForm,
+    EditTermForm,
+    PersonsAccountsFormSet,
+)
+from .models import Group, Person, School
+from .tables import GroupsTable, PersonsTable
 from .util import messages
 
 
 def index(request: HttpRequest) -> HttpResponse:
     context = {}
-    return render(request, 'core/index.html', context)
+    return render(request, "core/index.html", context)
 
 
 @login_required
@@ -30,9 +36,9 @@ def persons(request: HttpRequest) -> HttpResponse:
     # Build table
     persons_table = PersonsTable(persons)
     RequestConfig(request).configure(persons_table)
-    context['persons_table'] = persons_table
+    context["persons_table"] = persons_table
 
-    return render(request, 'core/persons.html', context)
+    return render(request, "core/persons.html", context)
 
 
 @login_required
@@ -46,7 +52,7 @@ def person(request: HttpRequest, id_: int, template: str) -> HttpResponse:
         # Turn not-found object into a 404 error
         raise Http404 from e
 
-    context['person'] = person
+    context["person"] = person
 
     # Get groups where person is member of
     groups = Group.objects.filter(members=id_)
@@ -54,9 +60,9 @@ def person(request: HttpRequest, id_: int, template: str) -> HttpResponse:
     # Build table
     groups_table = GroupsTable(groups)
     RequestConfig(request).configure(groups_table)
-    context['groups_table'] = groups_table
+    context["groups_table"] = groups_table
 
-    return render(request, 'core/person_%s.html' % template, context)
+    return render(request, "core/person_%s.html" % template, context)
 
 
 @login_required
@@ -70,7 +76,7 @@ def group(request: HttpRequest, id_: int, template: str) -> HttpResponse:
         # Turn not-found object into a 404 error
         raise Http404 from e
 
-    context['group'] = group
+    context["group"] = group
 
     # Get group
     group = Group.objects.get(pk=id_)
@@ -81,7 +87,7 @@ def group(request: HttpRequest, id_: int, template: str) -> HttpResponse:
     # Build table
     members_table = PersonsTable(members)
     RequestConfig(request).configure(members_table)
-    context['members_table'] = members_table
+    context["members_table"] = members_table
 
     # Get owners
     owners = group.owners.filter(is_active=True)
@@ -89,9 +95,9 @@ def group(request: HttpRequest, id_: int, template: str) -> HttpResponse:
     # Build table
     owners_table = PersonsTable(owners)
     RequestConfig(request).configure(owners_table)
-    context['owners_table'] = owners_table
+    context["owners_table"] = owners_table
 
-    return render(request, 'core/group_%s.html' % template, context)
+    return render(request, "core/group_%s.html" % template, context)
 
 
 @login_required
@@ -104,9 +110,9 @@ def groups(request: HttpRequest) -> HttpResponse:
     # Build table
     groups_table = GroupsTable(groups)
     RequestConfig(request).configure(groups_table)
-    context['groups_table'] = groups_table
+    context["groups_table"] = groups_table
 
-    return render(request, 'core/groups.html', context)
+    return render(request, "core/groups.html", context)
 
 
 @admin_required
@@ -116,13 +122,13 @@ def persons_accounts(request: HttpRequest) -> HttpResponse:
     persons_qs = Person.objects.all()
     persons_accounts_formset = PersonsAccountsFormSet(request.POST or None, queryset=persons_qs)
 
-    if request.method == 'POST':
+    if request.method == "POST":
         if persons_accounts_formset.is_valid():
             persons_accounts_formset.save()
 
-    context['persons_accounts_formset'] = persons_accounts_formset
+    context["persons_accounts_formset"] = persons_accounts_formset
 
-    return render(request, 'core/persons_accounts.html', context)
+    return render(request, "core/persons_accounts.html", context)
 
 
 @admin_required
@@ -133,18 +139,18 @@ def edit_person(request: HttpRequest, id_: int) -> HttpResponse:
 
     edit_person_form = EditPersonForm(request.POST or None, request.FILES or None, instance=person)
 
-    context['person'] = person
+    context["person"] = person
 
-    if request.method == 'POST':
+    if request.method == "POST":
         if edit_person_form.is_valid():
             edit_person_form.save(commit=True)
 
-            messages.success(request, _('The person has been saved.'))
-            return redirect('edit_person_by_id', id_=person.id)
+            messages.success(request, _("The person has been saved."))
+            return redirect("edit_person_by_id", id_=person.id)
 
-    context['edit_person_form'] = edit_person_form
+    context["edit_person_form"] = edit_person_form
 
-    return render(request, 'core/edit_person.html', context)
+    return render(request, "core/edit_person.html", context)
 
 
 @admin_required
@@ -158,40 +164,40 @@ def edit_group(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse:
         group = None
         edit_group_form = EditGroupForm(request.POST or None)
 
-    if request.method == 'POST':
+    if request.method == "POST":
         if edit_group_form.is_valid():
             edit_group_form.save(commit=True)
 
-            messages.success(request, _('The group has been saved.'))
-            return redirect('groups')
+            messages.success(request, _("The group has been saved."))
+            return redirect("groups")
 
-    context['group'] = group
-    context['edit_group_form'] = edit_group_form
+    context["group"] = group
+    context["edit_group_form"] = edit_group_form
 
-    return render(request, 'core/edit_group.html', context)
+    return render(request, "core/edit_group.html", context)
 
 
 @admin_required
 def data_management(request: HttpRequest) -> HttpResponse:
     context = {}
-    return render(request, 'core/data_management.html', context)
+    return render(request, "core/data_management.html", context)
 
 
 @admin_required
 def system_status(request: HttpRequest) -> HttpResponse:
     context = {}
 
-    context['backups'] = CronJobLog.objects.filter(
-        code='biscuit.core.Backup'
-    ).order_by('-end_time')[:10]
+    context["backups"] = CronJobLog.objects.filter(code="biscuit.core.Backup").order_by(
+        "-end_time"
+    )[:10]
 
-    return render(request, 'core/system_status.html', context)
+    return render(request, "core/system_status.html", context)
 
 
 @admin_required
 def school_management(request: HttpRequest) -> HttpResponse:
     context = {}
-    return render(request, 'core/school_management.html', context)
+    return render(request, "core/school_management.html", context)
 
 
 @admin_required
@@ -201,18 +207,18 @@ def edit_school(request: HttpRequest) -> HttpResponse:
     school = School.objects.first()
     edit_school_form = EditSchoolForm(request.POST or None, request.FILES or None, instance=school)
 
-    context['school'] = school
+    context["school"] = school
 
-    if request.method == 'POST':
+    if request.method == "POST":
         if edit_school_form.is_valid():
             edit_school_form.save(commit=True)
 
-            messages.success(request, _('The school has been saved.'))
-            return redirect('index')
+            messages.success(request, _("The school has been saved."))
+            return redirect("index")
 
-    context['edit_school_form'] = edit_school_form
+    context["edit_school_form"] = edit_school_form
 
-    return render(request, 'core/edit_school.html', context)
+    return render(request, "core/edit_school.html", context)
 
 
 @admin_required
@@ -222,13 +228,13 @@ def edit_schoolterm(request: HttpRequest) -> HttpResponse:
     term = School.objects.first().current_term
     edit_term_form = EditTermForm(request.POST or None, instance=term)
 
-    if request.method == 'POST':
+    if request.method == "POST":
         if edit_term_form.is_valid():
             edit_term_form.save(commit=True)
 
-            messages.success(request, _('The term has been saved.'))
-            return redirect('index')
+            messages.success(request, _("The term has been saved."))
+            return redirect("index")
 
-    context['edit_term_form'] = edit_term_form
+    context["edit_term_form"] = edit_term_form
 
-    return render(request, 'core/edit_schoolterm.html', context)
+    return render(request, "core/edit_schoolterm.html", context)
diff --git a/biscuit/core/wsgi.py b/biscuit/core/wsgi.py
index afb5bc6b7711d61a5160f88bea3ccc4d2e776a0d..c364569d798e00c5b4b7c178eb1554dc853101c4 100644
--- a/biscuit/core/wsgi.py
+++ b/biscuit/core/wsgi.py
@@ -2,6 +2,6 @@ import os
 
 from django.core.wsgi import get_wsgi_application
 
-os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'biscuit.core.settings')
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "biscuit.core.settings")
 
 application = get_wsgi_application()
diff --git a/dev.sh b/dev.sh
index 6a183f4000d6ce6e9ccf71c186e35193ec1a34c7..1840ccf811167de60ebed47ebf6459ea30ce2efb 100755
--- a/dev.sh
+++ b/dev.sh
@@ -15,7 +15,6 @@ case "$1" in
 	    poetry run sh -c "cd $d; poetry install"
 	done
 	remove_pip_metadata
-	poetry run ./manage.py migrate
 	poetry run ./manage.py compilemessages
 	poetry run ./manage.py yarn install
 	poetry run ./manage.py collectstatic --no-input
diff --git a/docker-compose.yml b/docker-compose.yml
index e71f2f1dd9fcd1b815995b4c6ca999a1ce66d715..ca09c5af38ebb0f79d60d0794d0a882ba4ceaace 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -19,6 +19,7 @@ services:
       - BISCUIT_http__allowed_hosts="['*']"
       - BISCUIT_caching__memcached__address=memcached:11211
       - BISCUIT_database__host=db
+      - BISCUIT_maintenance__debug=${BISCUIT_maintenance__debug:-false}
     depends_on:
       - db
       - memcached
diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh
index 4dcf89fce710430ead493366a36f26863dffcd69..eb1668a08a600197c2abb0b20813e8aff433b9cf 100755
--- a/docker/entrypoint.sh
+++ b/docker/entrypoint.sh
@@ -19,6 +19,7 @@ done
 
 python manage.py flush --no-input
 python manage.py migrate
+python manage.py collectstatic --no-input --clear
 
 if [[ -n "$@" ]]; then
     exec "$@"
diff --git a/docker/nginx/Dockerfile b/docker/nginx/Dockerfile
index e47ed67f547c508c1d18dc5ae6184f1fef90f873..4f5a2cd405ea5ff980d523df687de50e4835a6f3 100644
--- a/docker/nginx/Dockerfile
+++ b/docker/nginx/Dockerfile
@@ -1,6 +1,6 @@
 FROM nginx
 
 RUN rm /etc/nginx/conf.d/default.conf
-COPY nginx.conf /etc/nginx/conf.d
+COPY nginx.conf /etc/nginx/conf.d/default.conf
 
 RUN mkdir /var/lib/biscuit
diff --git a/docs/dev/01_setup.rst b/docs/dev/01_setup.rst
index 8d4cd0d8b9cfae6c631d7803eec7565274f5149d..49d166c88dae9d11357b6439a8adc6288dad357a 100644
--- a/docs/dev/01_setup.rst
+++ b/docs/dev/01_setup.rst
@@ -76,6 +76,13 @@ development server against a local PostgreSQL database with password
 
   BISCUIT_debug=true BISCUIT_database__password=biscuit poetry run ./manage.py runserver
 
+.. figure:: /screenshots/index.png
+   :scale: 50%
+   :alt: Screenshot of index page
+
+   After installing the development environment with default settings,
+   you should see the index page with the BOotstrap style.
+
 .. _Poetry: https://poetry.eustace.io/
 .. _Poetry installation methods: https://poetry.eustace.io/docs/#installation
 .. _Yarn: https://yarnpkg.com
diff --git a/docs/dev/02_install_apps.rst b/docs/dev/02_install_apps.rst
index b083baf6cb5710db1a84a7cb8255ee678e9db07e..f8e1e7d8cf8b986151654a6e0a51a02579a3eaae 100644
--- a/docs/dev/02_install_apps.rst
+++ b/docs/dev/02_install_apps.rst
@@ -17,5 +17,5 @@ This will install the Exlibris app (library management) app by using a
 shell for first ``cd``'ing into the app directory and then using
 poetry to install the app.
 
-DO not forget to run the maintenance tasks described earlier after
-installign any app.
+Do not forget to run the maintenance tasks described earlier after
+installing any app.
diff --git a/docs/dev/03_run_tests.rst b/docs/dev/03_run_tests.rst
new file mode 100644
index 0000000000000000000000000000000000000000..ae38192ed8fb8fdaae51046839269cac1ef61af4
--- /dev/null
+++ b/docs/dev/03_run_tests.rst
@@ -0,0 +1,75 @@
+Running tests and reports
+=========================
+
+Running default test suite
+--------------------------
+
+The test suite can be run using the `tox` tool::
+
+  poetry run tox
+
+
+Enabling Selenium browser tests
+-------------------------------
+
+The test suite contains tests that use Selenium to do browser based tests.
+They need to be enabled when running the test suite, which can be done by
+setting certain environment variables:
+
++------------------------+------------------------------------------------------+------------------------------+
+| Variable               | Meaning                                              | Example                      |
++========================+======================================================+==============================+
+| TEST_SELENIUM_BROWSERS | List of webdrivers to test against, comma-separated. | chrome,firefox               |
++------------------------+------------------------------------------------------+------------------------------+
+| TEST_SELENIUM_HUB      | Address of Selenium hub if using remote grid         | http://127.0.0.1:4444/wd/hub |
++------------------------+------------------------------------------------------+------------------------------+
+| TEST_HOST              | Hostname reachable from Selenium for live server     | 172.17.0.1                   |
++------------------------+------------------------------------------------------+------------------------------+
+| TEST_SCREENSHOT_PATH   | Path to directory to create screenshots in           | ./screenshots                |
++------------------------+------------------------------------------------------+------------------------------+
+
+Selenium tests are enabled if `TEST_SELENIUM_BROWSERS` is non-empty.
+
+To set variables, use env to wrap the tox ommand::
+
+  poetry run env TEST_SELENIUM_BROWSERS=chrome,firefox tox
+
+
+Using a Selenium hub on local Docker host
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+One way to setup Selenium is to use the official images on the local
+machine.
+
+First, get Selenium Hub and one or more browser nodes up and running::
+
+  docker run -d -p 4444:4444 --name selenium-hub selenium/hub
+  docker run -d --link selenium-hub:hub selenium/node-chrome
+  docker run -d --link selenium-hub:hub selenium/node-firefox
+
+After that, you can run the test suite, setting the needed variables to use
+Docker Hub::
+
+  poetry run env \
+    TEST_SELENIUM_BROWSERS=chrome,firefox \
+    TEST_SELENIUM_HUB=http://127.0.0.1:4444/wd/hub \
+    TEST_HOST=172.17.0.1 \
+  tox
+
+The `TEST_HOST` variable is set to the Docker host's IP address, where the
+Selenium nodes can access Django's live server.  Django automatically
+configures the live server to be reachable if a Selenium hub is used.
+
+
+Taking screenshots
+~~~~~~~~~~~~~~~~~~
+
+The browser test suites automatically take screenshots at certain steps if
+enabled in the test run.  This can be used to visually verify that views
+look like they should or for documentation purposes.
+
+To enable screenshots, add the `TEST_SCREENSHOT_PATH` environment variable
+when running the tests.
+
+If runnin multiple browsers, screenshots are placed in separate directories
+per browser.
diff --git a/poetry.lock b/poetry.lock
index c3f515ad8164aed668ea043acac62ddc859dbf77..283d8064e726d2667a8175c219580bb7ded76b5d 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -6,6 +6,14 @@ optional = false
 python-versions = "*"
 version = "0.7.12"
 
+[[package]]
+category = "dev"
+description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
+name = "appdirs"
+optional = false
+python-versions = "*"
+version = "1.4.3"
+
 [[package]]
 category = "main"
 description = "ASGI specs, helper code, and adapters"
@@ -14,6 +22,9 @@ optional = false
 python-versions = "*"
 version = "3.2.3"
 
+[package.extras]
+tests = ["pytest (>=4.3.0,<4.4.0)", "pytest-asyncio (>=0.10.0,<0.11.0)"]
+
 [[package]]
 category = "dev"
 description = "Atomic file writes."
@@ -31,16 +42,11 @@ optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 version = "19.3.0"
 
-[[package]]
-category = "dev"
-description = "A tool that automatically formats Python code to conform to the PEP 8 style guide"
-name = "autopep8"
-optional = false
-python-versions = "*"
-version = "1.4.4"
-
-[package.dependencies]
-pycodestyle = ">=2.4.0"
+[package.extras]
+azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"]
+dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"]
+docs = ["sphinx", "zope.interface"]
+tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"]
 
 [[package]]
 category = "main"
@@ -53,17 +59,56 @@ version = "2.7.0"
 [package.dependencies]
 pytz = ">=2015.7"
 
+[[package]]
+category = "dev"
+description = "Security oriented static analyser for python code."
+name = "bandit"
+optional = false
+python-versions = "*"
+version = "1.6.2"
+
+[package.dependencies]
+GitPython = ">=1.0.1"
+PyYAML = ">=3.13"
+colorama = ">=0.3.9"
+six = ">=1.10.0"
+stevedore = ">=1.20.0"
+
 [[package]]
 category = "main"
 description = "Screen-scraping library"
 name = "beautifulsoup4"
 optional = false
 python-versions = "*"
-version = "4.8.1"
+version = "4.8.2"
 
 [package.dependencies]
 soupsieve = ">=1.2"
 
+[package.extras]
+html5lib = ["html5lib"]
+lxml = ["lxml"]
+
+[[package]]
+category = "dev"
+description = "The uncompromising code formatter."
+name = "black"
+optional = false
+python-versions = ">=3.6"
+version = "19.10b0"
+
+[package.dependencies]
+appdirs = "*"
+attrs = ">=18.1.0"
+click = ">=6.5"
+pathspec = ">=0.6,<1"
+regex = "*"
+toml = ">=0.9.4"
+typed-ast = ">=1.4.0"
+
+[package.extras]
+d = ["aiohttp (>=3.3.2)", "aiohttp-cors"]
+
 [[package]]
 category = "main"
 description = "Python package for providing Mozilla's CA Bundle."
@@ -91,6 +136,7 @@ version = "7.0"
 [[package]]
 category = "main"
 description = "Cross-platform colored terminal text."
+marker = "platform_system == \"Windows\" or sys_platform == \"win32\""
 name = "colorama"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
@@ -104,10 +150,12 @@ optional = false
 python-versions = "*"
 version = "0.1.5"
 
+[package.extras]
+test = ["nose"]
+
 [[package]]
 category = "main"
 description = "Config file reading, writing and validation."
-marker = "extra == \"ini\""
 name = "configobj"
 optional = false
 python-versions = "*"
@@ -121,8 +169,11 @@ category = "dev"
 description = "Code coverage measurement for Python"
 name = "coverage"
 optional = false
-python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4"
-version = "4.5.4"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
+version = "5.0.1"
+
+[package.extras]
+toml = ["toml"]
 
 [[package]]
 category = "dev"
@@ -138,13 +189,17 @@ description = "A high-level Python Web framework that encourages rapid developme
 name = "django"
 optional = false
 python-versions = ">=3.6"
-version = "3.0"
+version = "3.0.1"
 
 [package.dependencies]
 asgiref = ">=3.2,<4.0"
 pytz = "*"
 sqlparse = ">=0.2.2"
 
+[package.extras]
+argon2 = ["argon2-cffi (>=16.1.0)"]
+bcrypt = ["bcrypt"]
+
 [[package]]
 category = "main"
 description = "Include JavaScript libraries with readable template tags"
@@ -186,7 +241,7 @@ description = "Bootstrap support for Django projects"
 name = "django-bootstrap4"
 optional = false
 python-versions = "*"
-version = "1.1.0"
+version = "1.1.1"
 
 [package.dependencies]
 beautifulsoup4 = "*"
@@ -213,17 +268,6 @@ version = "0.9.2"
 [package.dependencies]
 Django = ">=1.8.0"
 
-[[package]]
-category = "main"
-description = "A generic contact-form application for Django"
-name = "django-contact-form"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "1.7"
-
-[package.dependencies]
-Django = ">=1.11"
-
 [[package]]
 category = "main"
 description = "Running python crons in a Django project"
@@ -255,7 +299,7 @@ description = "Application settings whose values can be updated while a project
 name = "django-dbsettings"
 optional = false
 python-versions = "*"
-version = "0.11.0"
+version = "1.0.0"
 
 [[package]]
 category = "main"
@@ -277,17 +321,6 @@ optional = false
 python-versions = "*"
 version = "1.1.1"
 
-[[package]]
-category = "main"
-description = "Font Awesome for Django"
-name = "django-fa"
-optional = false
-python-versions = "*"
-version = "1.0.0"
-
-[package.dependencies]
-Django = ">1.4"
-
 [[package]]
 category = "main"
 description = "A set of high-level abstractions for Django forms"
@@ -320,11 +353,10 @@ description = "A reusable app for cropping images easily and non-destructively i
 name = "django-image-cropping"
 optional = false
 python-versions = "*"
-version = "1.2.0"
+version = "1.3.0"
 
 [package.dependencies]
 django-appconf = ">=1.0.2"
-six = "*"
 
 [[package]]
 category = "main"
@@ -348,7 +380,7 @@ description = "django-maintenance-mode shows a 503 error page when maintenance-m
 name = "django-maintenance-mode"
 optional = false
 python-versions = "*"
-version = "0.13.3"
+version = "0.14.0"
 
 [[package]]
 category = "main"
@@ -381,10 +413,12 @@ version = "0.7.4"
 django = ">=1.11"
 six = ">=1.10.0"
 
+[package.extras]
+qrcode = ["qrcode"]
+
 [[package]]
 category = "main"
 description = "A django-otp plugin that verifies YubiKey OTP tokens."
-marker = "extra == \"YubiKey\""
 name = "django-otp-yubikey"
 optional = false
 python-versions = "*"
@@ -407,6 +441,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 = "SASS processor to compile SCSS files into *.css, while rendering, or offline."
@@ -415,6 +457,9 @@ optional = false
 python-versions = "*"
 version = "0.7.5"
 
+[package.extras]
+dev = ["libsass (>=0.13)"]
+
 [[package]]
 category = "main"
 description = "Select2 option fields for Django"
@@ -441,11 +486,11 @@ description = "Mypy stubs for Django"
 name = "django-stubs"
 optional = false
 python-versions = ">=3.6"
-version = "1.1.0"
+version = "1.4.0"
 
 [package.dependencies]
 django = "*"
-mypy = ">=0.720,<0.730"
+mypy = ">=0.760,<0.770"
 typing-extensions = "*"
 
 [[package]]
@@ -459,28 +504,43 @@ version = "2.2.1"
 [package.dependencies]
 Django = ">=1.11"
 
+[package.extras]
+tablib = ["tablib"]
+
 [[package]]
 category = "main"
 description = "Complete Two-Factor Authentication for Django"
 name = "django-two-factor-auth"
 optional = false
 python-versions = "*"
-version = "1.9.1"
+version = "1.10.0"
 
 [package.dependencies]
 Django = ">=1.11"
 django-formtools = "*"
-django-otp-yubikey = "*"
+django-otp = ">=0.6.0,<0.99"
 django-phonenumber-field = ">=1.1.0,<3.99"
-django_otp = ">=0.6.0,<0.99"
-phonenumbers = ">=7.0.9,<8.99"
 qrcode = ">=4.0.0,<6.99"
-twilio = ">=6.0"
 
-[package.source]
-reference = "bf9d0812ab11320a6cadc6709c382a03184f2e31"
-type = "git"
-url = "https://github.com/Bouke/django-two-factor-auth"
+[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)"]
+SMS = ["twilio (>=6.0)"]
+YubiKey = ["django-otp-yubikey"]
+phonenumbers = ["phonenumbers (>=7.0.9,<8.99)"]
+phonenumberslite = ["phonenumberslite (>=7.0.9,<8.99)"]
+
 [[package]]
 category = "main"
 description = "Integrate django with yarnpkg"
@@ -501,38 +561,70 @@ optional = false
 python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
 version = "0.15.2"
 
+[[package]]
+category = "dev"
+description = "A parser for Python dependency files"
+name = "dparse"
+optional = false
+python-versions = "*"
+version = "0.4.1"
+
+[package.dependencies]
+packaging = "*"
+pyyaml = "*"
+six = "*"
+
+[package.extras]
+pipenv = ["pipenv"]
+
 [[package]]
 category = "main"
 description = "The dynamic configurator for your Python Project"
 name = "dynaconf"
 optional = false
 python-versions = "*"
-version = "2.2.1"
+version = "2.2.2"
 
 [package.dependencies]
-PyYAML = "*"
-click = "*"
-configobj = "*"
-python-box = "*"
-python-dotenv = "*"
-toml = "*"
+click = "<=7.0"
+python-box = "<4.0.0"
+python-dotenv = "<=0.10.3"
+
+[[package.dependencies.toml]]
+version = "<=0.10.0"
+
+[[package.dependencies.toml]]
+optional = true
+version = "*"
+
+[package.dependencies.PyYAML]
+optional = true
+version = "*"
+
+[package.dependencies.configobj]
+optional = true
+version = "*"
+
+[package.extras]
+all = ["redis", "pyyaml", "configobj", "hvac"]
+configobj = ["configobj"]
+ini = ["configobj"]
+redis = ["redis"]
+toml = ["toml"]
+vault = ["hvac"]
+yaml = ["pyyaml"]
 
 [[package]]
 category = "main"
 description = "Easy thumbnails for Django"
 name = "easy-thumbnails"
 optional = false
-python-versions = "*"
-version = "2.6"
+python-versions = ">=3.5"
+version = "2.7"
 
 [package.dependencies]
-[package.dependencies.django]
-python = ">=3"
-version = ">=1.8"
-
-[package.dependencies.pillow]
-python = ">=2.7"
-version = "*"
+django = ">=1.11,<4.0"
+pillow = "*"
 
 [[package]]
 category = "dev"
@@ -555,14 +647,6 @@ python-dateutil = ">=2.4"
 six = ">=1.10"
 text-unidecode = "1.3"
 
-[[package]]
-category = "dev"
-description = "A platform independent file lock."
-name = "filelock"
-optional = false
-python-versions = "*"
-version = "3.0.12"
-
 [[package]]
 category = "dev"
 description = "the modular source code checker: pep8, pyflakes and co"
@@ -577,6 +661,109 @@ mccabe = ">=0.6.0,<0.7.0"
 pycodestyle = ">=2.5.0,<2.6.0"
 pyflakes = ">=2.1.0,<2.2.0"
 
+[[package]]
+category = "dev"
+description = "Automated security testing with bandit and flake8."
+name = "flake8-bandit"
+optional = false
+python-versions = "*"
+version = "2.1.2"
+
+[package.dependencies]
+bandit = "*"
+flake8 = "*"
+flake8-polyfill = "*"
+pycodestyle = "*"
+
+[[package]]
+category = "dev"
+description = "flake8 plugin to call black as a code style validator"
+name = "flake8-black"
+optional = false
+python-versions = "*"
+version = "0.1.1"
+
+[package.dependencies]
+black = ">=19.3b0"
+flake8 = ">=3.0.0"
+
+[[package]]
+category = "dev"
+description = "Check for python builtins being used as variables or parameters."
+name = "flake8-builtins"
+optional = false
+python-versions = "*"
+version = "1.4.2"
+
+[package.dependencies]
+flake8 = "*"
+
+[package.extras]
+test = ["coverage", "coveralls", "mock", "pytest", "pytest-cov"]
+
+[[package]]
+category = "dev"
+description = "Plugin to catch bad style specific to Django Projects"
+name = "flake8-django"
+optional = false
+python-versions = "*"
+version = "0.0.4"
+
+[package.dependencies]
+flake8 = "*"
+
+[[package]]
+category = "dev"
+description = "Extension for flake8 which uses pydocstyle to check docstrings"
+name = "flake8-docstrings"
+optional = false
+python-versions = "*"
+version = "1.5.0"
+
+[package.dependencies]
+flake8 = ">=3"
+pydocstyle = ">=2.1"
+
+[[package]]
+category = "dev"
+description = "Check for FIXME, TODO and other temporary developer notes. Plugin for flake8."
+name = "flake8-fixme"
+optional = false
+python-versions = "*"
+version = "1.1.1"
+
+[[package]]
+category = "dev"
+description = "flake8 plugin that integrates isort ."
+name = "flake8-isort"
+optional = false
+python-versions = "*"
+version = "2.8.0"
+
+[package.dependencies]
+flake8 = ">=3.2.1"
+testfixtures = "*"
+
+[package.dependencies.isort]
+extras = ["pyproject"]
+version = ">=4.3.0"
+
+[package.extras]
+test = ["pytest"]
+
+[[package]]
+category = "dev"
+description = "A plugin for flake8 integrating mypy."
+name = "flake8-mypy"
+optional = false
+python-versions = "*"
+version = "17.8.0"
+
+[package.dependencies]
+attrs = "*"
+flake8 = ">=3.0.0"
+mypy = "*"
+
 [[package]]
 category = "dev"
 description = "Polyfill package for Flake8 plugins"
@@ -590,11 +777,37 @@ flake8 = "*"
 
 [[package]]
 category = "dev"
-description = "Clean single-source support for Python 3 and 2"
-name = "future"
+description = "Python docstring reStructuredText (RST) validator"
+name = "flake8-rst-docstrings"
 optional = false
-python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
-version = "0.18.2"
+python-versions = "*"
+version = "0.0.12"
+
+[package.dependencies]
+flake8 = ">=3.0.0"
+restructuredtext_lint = "*"
+
+[[package]]
+category = "dev"
+description = "Git Object Database"
+name = "gitdb2"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "2.0.6"
+
+[package.dependencies]
+smmap2 = ">=2.0.0"
+
+[[package]]
+category = "dev"
+description = "Python Git Library"
+name = "gitpython"
+optional = false
+python-versions = ">=3.0, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "3.0.5"
+
+[package.dependencies]
+gitdb2 = ">=2.0.0"
 
 [[package]]
 category = "main"
@@ -610,7 +823,7 @@ description = "Getting image size from png/jpeg/jpeg2000/gif file"
 name = "imagesize"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "1.1.0"
+version = "1.2.0"
 
 [[package]]
 category = "dev"
@@ -619,11 +832,29 @@ marker = "python_version < \"3.8\""
 name = "importlib-metadata"
 optional = false
 python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
-version = "1.2.0"
+version = "1.3.0"
 
 [package.dependencies]
 zipp = ">=0.5"
 
+[package.extras]
+docs = ["sphinx", "rst.linker"]
+testing = ["packaging", "importlib-resources"]
+
+[[package]]
+category = "dev"
+description = "A Python utility / library to sort Python imports."
+name = "isort"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "4.3.21"
+
+[package.extras]
+pipfile = ["pipreqs", "requirementslib"]
+pyproject = ["toml"]
+requirements = ["pipreqs", "pip-api"]
+xdg_home = ["appdirs (>=1.4.0)"]
+
 [[package]]
 category = "dev"
 description = "A very fast and expressive template engine."
@@ -635,6 +866,9 @@ version = "2.10.3"
 [package.dependencies]
 MarkupSafe = ">=0.23"
 
+[package.extras]
+i18n = ["Babel (>=0.8)"]
+
 [[package]]
 category = "main"
 description = "Sass for Python: A straightforward binding of libsass for Python."
@@ -646,17 +880,6 @@ version = "0.19.4"
 [package.dependencies]
 six = "*"
 
-[[package]]
-category = "dev"
-description = "Create Python CLI apps with little to no effort at all!"
-name = "mando"
-optional = false
-python-versions = "*"
-version = "0.6.4"
-
-[package.dependencies]
-six = "*"
-
 [[package]]
 category = "dev"
 description = "Safely add untrusted strings to HTML/XML markup."
@@ -686,14 +909,17 @@ category = "dev"
 description = "Optional static typing for Python"
 name = "mypy"
 optional = false
-python-versions = "*"
-version = "0.720"
+python-versions = ">=3.5"
+version = "0.761"
 
 [package.dependencies]
-mypy-extensions = ">=0.4.0,<0.5.0"
+mypy-extensions = ">=0.4.3,<0.5.0"
 typed-ast = ">=1.4.0,<1.5.0"
 typing-extensions = ">=3.7.4"
 
+[package.extras]
+dmypy = ["psutil (>=4.0)"]
+
 [[package]]
 category = "dev"
 description = "Experimental type system extensions for programs checked with the mypy typechecker."
@@ -714,6 +940,22 @@ version = "19.2"
 pyparsing = ">=2.0.2"
 six = "*"
 
+[[package]]
+category = "dev"
+description = "Utility library for gitignore style pattern matching of file paths."
+name = "pathspec"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "0.6.0"
+
+[[package]]
+category = "dev"
+description = "Python Build Reasonableness"
+name = "pbr"
+optional = false
+python-versions = "*"
+version = "5.4.4"
+
 [[package]]
 category = "dev"
 description = "PostgreSQL interface library"
@@ -728,11 +970,10 @@ scramp = "1.1.0"
 [[package]]
 category = "main"
 description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers."
-marker = "extra == \"phonenumbers\""
 name = "phonenumbers"
 optional = false
 python-versions = "*"
-version = "8.11.0"
+version = "8.11.1"
 
 [[package]]
 category = "main"
@@ -755,6 +996,9 @@ version = "0.13.1"
 python = "<3.8"
 version = ">=0.12"
 
+[package.extras]
+dev = ["pre-commit", "tox"]
+
 [[package]]
 category = "main"
 description = "psycopg2 - Python-PostgreSQL Database Adapter"
@@ -801,7 +1045,6 @@ version = "2.5.0"
 [[package]]
 category = "main"
 description = "Cryptographic library for Python"
-marker = "extra == \"YubiKey\""
 name = "pycryptodome"
 optional = false
 python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
@@ -813,7 +1056,7 @@ description = "Python docstring style checker"
 name = "pydocstyle"
 optional = false
 python-versions = ">=3.5"
-version = "5.0.0"
+version = "5.0.1"
 
 [package.dependencies]
 snowballstemmer = "*"
@@ -837,25 +1080,15 @@ version = "2.5.2"
 [[package]]
 category = "main"
 description = "JSON Web Token implementation in Python"
-marker = "extra == \"Call\""
 name = "pyjwt"
 optional = false
 python-versions = "*"
 version = "1.7.1"
 
-[[package]]
-category = "dev"
-description = "pylama -- Code audit tool for python"
-name = "pylama"
-optional = false
-python-versions = "*"
-version = "7.7.1"
-
-[package.dependencies]
-mccabe = ">=0.5.2"
-pycodestyle = ">=2.3.1"
-pydocstyle = ">=2.0.0"
-pyflakes = ">=1.5.0"
+[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 = "dev"
@@ -863,7 +1096,7 @@ description = "Python parsing module"
 name = "pyparsing"
 optional = false
 python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
-version = "2.4.5"
+version = "2.4.6"
 
 [[package]]
 category = "dev"
@@ -871,7 +1104,7 @@ description = "pytest: simple powerful testing with Python"
 name = "pytest"
 optional = false
 python-versions = ">=3.5"
-version = "5.3.1"
+version = "5.3.2"
 
 [package.dependencies]
 atomicwrites = ">=1.0"
@@ -887,6 +1120,9 @@ wcwidth = "*"
 python = "<3.8"
 version = ">=0.12"
 
+[package.extras]
+testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
+
 [[package]]
 category = "dev"
 description = "Pytest plugin for measuring coverage."
@@ -899,6 +1135,9 @@ version = "2.8.1"
 coverage = ">=4.4"
 pytest = ">=3.6"
 
+[package.extras]
+testing = ["fields", "hunter", "process-tests (2.0.2)", "six", "virtualenv"]
+
 [[package]]
 category = "dev"
 description = "A Django plugin for pytest."
@@ -910,6 +1149,10 @@ version = "3.7.0"
 [package.dependencies]
 pytest = ">=3.6"
 
+[package.extras]
+docs = ["sphinx", "sphinx-rtd-theme"]
+testing = ["django", "django-configurations (>=2.0)", "six"]
+
 [[package]]
 category = "dev"
 description = "Use a temporary PostgreSQL database with pytest-django"
@@ -922,6 +1165,19 @@ version = "0.1.post0"
 dj-database-url = "*"
 "testing.postgresql" = "*"
 
+[[package]]
+category = "dev"
+description = "pytest-sugar is a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly)."
+name = "pytest-sugar"
+optional = false
+python-versions = "*"
+version = "0.9.2"
+
+[package.dependencies]
+packaging = ">=14.1"
+pytest = ">=2.9"
+termcolor = ">=1.1.0"
+
 [[package]]
 category = "main"
 description = "Advanced Python dictionaries with dot notation access"
@@ -930,6 +1186,9 @@ optional = false
 python-versions = "*"
 version = "3.4.6"
 
+[package.extras]
+testing = ["pytest", "coverage (>=3.6)", "pytest-cov"]
+
 [[package]]
 category = "main"
 description = "Extensions to the standard Python datetime module"
@@ -949,6 +1208,9 @@ optional = false
 python-versions = "*"
 version = "0.10.3"
 
+[package.extras]
+cli = ["click (>=5.0)"]
+
 [[package]]
 category = "main"
 description = "Python modules for implementing LDAP clients"
@@ -983,7 +1245,6 @@ version = "2019.3"
 [[package]]
 category = "main"
 description = "YAML parser and emitter for Python"
-marker = "extra == \"yaml\""
 name = "pyyaml"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
@@ -1001,19 +1262,19 @@ version = "6.1"
 colorama = "*"
 six = "*"
 
+[package.extras]
+dev = ["tox", "pytest", "mock"]
+maintainer = ["zest.releaser"]
+pil = ["pillow"]
+test = ["pytest", "pytest-cov", "mock"]
+
 [[package]]
 category = "dev"
-description = "Code Metrics in Python"
-name = "radon"
+description = "Alternative regular expression module, to replace re."
+name = "regex"
 optional = false
 python-versions = "*"
-version = "3.0.3"
-
-[package.dependencies]
-colorama = ">=0.4,<0.5"
-flake8-polyfill = "*"
-future = "*"
-mando = ">=0.6,<0.7"
+version = "2019.12.20"
 
 [[package]]
 category = "main"
@@ -1029,6 +1290,36 @@ chardet = ">=3.0.2,<3.1.0"
 idna = ">=2.5,<2.9"
 urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26"
 
+[package.extras]
+security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)"]
+socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"]
+
+[[package]]
+category = "dev"
+description = "reStructuredText linter"
+name = "restructuredtext-lint"
+optional = false
+python-versions = "*"
+version = "1.3.0"
+
+[package.dependencies]
+docutils = ">=0.11,<1.0"
+
+[[package]]
+category = "dev"
+description = "Safety checks your installed dependencies for known security vulnerabilities."
+name = "safety"
+optional = false
+python-versions = "*"
+version = "1.8.5"
+
+[package.dependencies]
+Click = ">=6.0"
+dparse = ">=0.4.1"
+packaging = "*"
+requests = "*"
+setuptools = "*"
+
 [[package]]
 category = "dev"
 description = "An implementation of the SCRAM protocol."
@@ -1037,6 +1328,17 @@ optional = false
 python-versions = ">=3.5"
 version = "1.1.0"
 
+[[package]]
+category = "dev"
+description = "Python bindings for Selenium"
+name = "selenium"
+optional = false
+python-versions = "*"
+version = "3.141.0"
+
+[package.dependencies]
+urllib3 = "*"
+
 [[package]]
 category = "main"
 description = "Python 2 and 3 compatibility utilities"
@@ -1045,6 +1347,14 @@ optional = false
 python-versions = ">=2.6, !=3.0.*, !=3.1.*"
 version = "1.13.0"
 
+[[package]]
+category = "dev"
+description = "A pure Python implementation of a sliding window memory map manager"
+name = "smmap2"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "2.0.5"
+
 [[package]]
 category = "dev"
 description = "This package provides 26 stemmers for 25 languages generated from Snowball algorithms."
@@ -1067,7 +1377,7 @@ description = "Python documentation generator"
 name = "sphinx"
 optional = false
 python-versions = ">=3.5"
-version = "2.2.2"
+version = "2.3.1"
 
 [package.dependencies]
 Jinja2 = ">=2.3"
@@ -1088,6 +1398,10 @@ sphinxcontrib-jsmath = "*"
 sphinxcontrib-qthelp = "*"
 sphinxcontrib-serializinghtml = "*"
 
+[package.extras]
+docs = ["sphinxcontrib-websupport"]
+test = ["pytest", "pytest-cov", "html5lib", "flake8 (>=3.5.0)", "flake8-import-order", "mypy (>=0.761)", "docutils-stubs"]
+
 [[package]]
 category = "dev"
 description = "Type hints (PEP 484) support for the Sphinx autodoc extension"
@@ -1099,6 +1413,10 @@ version = "1.10.3"
 [package.dependencies]
 Sphinx = ">=2.1"
 
+[package.extras]
+test = ["pytest (>=3.1.0)", "typing-extensions (>=3.5)", "sphobjinv (>=2.0)", "dataclasses"]
+type_comments = ["typed-ast (>=1.4.0)"]
+
 [[package]]
 category = "dev"
 description = ""
@@ -1107,6 +1425,9 @@ optional = false
 python-versions = "*"
 version = "1.0.1"
 
+[package.extras]
+test = ["pytest", "flake8", "mypy"]
+
 [[package]]
 category = "dev"
 description = ""
@@ -1115,6 +1436,9 @@ optional = false
 python-versions = "*"
 version = "1.0.1"
 
+[package.extras]
+test = ["pytest", "flake8", "mypy"]
+
 [[package]]
 category = "dev"
 description = "Improve the Sphinx autodoc for Django classes."
@@ -1131,6 +1455,9 @@ optional = false
 python-versions = "*"
 version = "1.0.2"
 
+[package.extras]
+test = ["pytest", "flake8", "mypy", "html5lib"]
+
 [[package]]
 category = "dev"
 description = "A sphinx extension which renders display math in HTML via JavaScript"
@@ -1139,6 +1466,9 @@ optional = false
 python-versions = ">=3.5"
 version = "1.0.1"
 
+[package.extras]
+test = ["pytest", "flake8", "mypy"]
+
 [[package]]
 category = "dev"
 description = ""
@@ -1147,6 +1477,9 @@ optional = false
 python-versions = "*"
 version = "1.0.2"
 
+[package.extras]
+test = ["pytest", "flake8", "mypy"]
+
 [[package]]
 category = "dev"
 description = ""
@@ -1155,6 +1488,9 @@ optional = false
 python-versions = "*"
 version = "1.1.3"
 
+[package.extras]
+test = ["pytest", "flake8", "mypy"]
+
 [[package]]
 category = "main"
 description = "Non-validating SQL parser"
@@ -1163,6 +1499,39 @@ optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 version = "0.3.0"
 
+[[package]]
+category = "dev"
+description = "Manage dynamic plugins for Python applications"
+name = "stevedore"
+optional = false
+python-versions = "*"
+version = "1.31.0"
+
+[package.dependencies]
+pbr = ">=2.0.0,<2.1.0 || >2.1.0"
+six = ">=1.10.0"
+
+[[package]]
+category = "dev"
+description = "ANSII Color formatting for output in terminal."
+name = "termcolor"
+optional = false
+python-versions = "*"
+version = "1.1.0"
+
+[[package]]
+category = "dev"
+description = "A collection of helpers and mock objects for unit tests and doc tests."
+name = "testfixtures"
+optional = false
+python-versions = "*"
+version = "6.10.3"
+
+[package.extras]
+build = ["setuptools-git", "wheel", "twine"]
+docs = ["sphinx"]
+test = ["pytest (>=3.6)", "pytest-cov", "pytest-django", "sybil", "zope.component", "twisted", "mock", "django (<2)", "django"]
+
 [[package]]
 category = "dev"
 description = "utilities for testing.* packages"
@@ -1171,6 +1540,9 @@ optional = false
 python-versions = "*"
 version = "2.0.3"
 
+[package.extras]
+testing = ["nose"]
+
 [[package]]
 category = "dev"
 description = "automatically setups a postgresql instance in a temporary directory, and destroys it after testing"
@@ -1183,6 +1555,9 @@ version = "1.3.0"
 pg8000 = ">=1.10"
 "testing.common.database" = "*"
 
+[package.extras]
+testing = ["sqlalchemy", "nose", "psycopg2"]
+
 [[package]]
 category = "main"
 description = "The most basic Text::Unidecode port"
@@ -1199,44 +1574,24 @@ optional = false
 python-versions = "*"
 version = "0.10.0"
 
-[[package]]
-category = "dev"
-description = "tox is a generic virtualenv management and test command line tool"
-name = "tox"
-optional = false
-python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
-version = "3.14.2"
-
-[package.dependencies]
-colorama = ">=0.4.1"
-filelock = ">=3.0.0,<4"
-packaging = ">=14"
-pluggy = ">=0.12.0,<1"
-py = ">=1.4.17,<2"
-six = ">=1.0.0,<2"
-toml = ">=0.9.4"
-virtualenv = ">=16.0.0"
-
-[package.dependencies.importlib-metadata]
-python = "<3.8"
-version = ">=1.1.0,<2"
-
 [[package]]
 category = "main"
 description = "Fast, Extensible Progress Meter"
 name = "tqdm"
 optional = false
 python-versions = ">=2.6, !=3.0.*, !=3.1.*"
-version = "4.40.1"
+version = "4.41.0"
+
+[package.extras]
+dev = ["py-make (>=0.1.0)", "twine", "argopt", "pydoc-markdown"]
 
 [[package]]
 category = "main"
 description = "Twilio API client and TwiML generator"
-marker = "extra == \"Call\""
 name = "twilio"
 optional = false
 python-versions = "*"
-version = "6.34.0"
+version = "6.35.1"
 
 [package.dependencies]
 PyJWT = ">=1.4.2"
@@ -1271,13 +1626,10 @@ optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4"
 version = "1.25.7"
 
-[[package]]
-category = "dev"
-description = "Virtual Python Environment builder"
-name = "virtualenv"
-optional = false
-python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
-version = "16.7.8"
+[package.extras]
+brotli = ["brotlipy (>=0.6.0)"]
+secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
+socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"]
 
 [[package]]
 category = "dev"
@@ -1290,7 +1642,6 @@ version = "0.1.7"
 [[package]]
 category = "main"
 description = "A library for verifying YubiKey OTP tokens, both locally and through a Yubico web service."
-marker = "extra == \"YubiKey\""
 name = "yubiotp"
 optional = false
 python-versions = "*"
@@ -1312,137 +1663,791 @@ version = "0.6.0"
 [package.dependencies]
 more-itertools = "*"
 
+[package.extras]
+docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
+testing = ["pathlib2", "contextlib2", "unittest2"]
+
 [extras]
+app-alsijil = []
+app-chronos = []
+app-exlibris = []
+app-schild-nrw = []
+app-untis = []
 ldap = ["django-auth-ldap"]
 
 [metadata]
-content-hash = "d5e0af51d66f1c4adc471e23109035555529e083d777a3c5b655629da32c1a4f"
+content-hash = "0f1ffee31adf4e8caaf64df3f5feda1d455f9349f6b59338084ceb5bcc3e6617"
 python-versions = "^3.7"
 
-[metadata.hashes]
-alabaster = ["446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359", "a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"]
-asgiref = ["7e06d934a7718bf3975acbf87780ba678957b87c7adc056f13b6215d610695a0", "ea448f92fc35a0ef4b1508f53a04c4670255a3f33d22a81c8fc9c872036adbe5"]
-atomicwrites = ["03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", "75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"]
-attrs = ["08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", "f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"]
-autopep8 = ["4d8eec30cc81bc5617dbf1218201d770dc35629363547f17577c61683ccfb3ee"]
-babel = ["af92e6106cb7c55286b25b38ad7695f8b4efb36a90ba483d7f7a6628c46158ab", "e86135ae101e31e2c8ec20a4e0c5220f4eed12487d5cf3f78be7e98d3a57fc28"]
-beautifulsoup4 = ["5279c36b4b2ec2cb4298d723791467e3000e5384a43ea0cdf5d45207c7e97169", "6135db2ba678168c07950f9a16c4031822c6f4aec75a65e0a97bc5ca09789931", "dcdef580e18a76d54002088602eba453eec38ebbcafafeaabd8cab12b6155d57"]
-certifi = ["017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3", "25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"]
-chardet = ["84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", "fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"]
-click = ["2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", "5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"]
-colorama = ["7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff", "e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"]
-colour = ["33f6db9d564fadc16e59921a56999b79571160ce09916303d35346dddc17978c", "af20120fefd2afede8b001fbef2ea9da70ad7d49fafdb6489025dae8745c3aee"]
-configobj = ["a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902"]
-coverage = ["08907593569fe59baca0bf152c43f3863201efb6113ecb38ce7e97ce339805a6", "0be0f1ed45fc0c185cfd4ecc19a1d6532d72f86a2bac9de7e24541febad72650", "141f08ed3c4b1847015e2cd62ec06d35e67a3ac185c26f7635f4406b90afa9c5", "19e4df788a0581238e9390c85a7a09af39c7b539b29f25c89209e6c3e371270d", "23cc09ed395b03424d1ae30dcc292615c1372bfba7141eb85e11e50efaa6b351", "245388cda02af78276b479f299bbf3783ef0a6a6273037d7c60dc73b8d8d7755", "331cb5115673a20fb131dadd22f5bcaf7677ef758741312bee4937d71a14b2ef", "386e2e4090f0bc5df274e720105c342263423e77ee8826002dcffe0c9533dbca", "3a794ce50daee01c74a494919d5ebdc23d58873747fa0e288318728533a3e1ca", "60851187677b24c6085248f0a0b9b98d49cba7ecc7ec60ba6b9d2e5574ac1ee9", "63a9a5fc43b58735f65ed63d2cf43508f462dc49857da70b8980ad78d41d52fc", "6b62544bb68106e3f00b21c8930e83e584fdca005d4fffd29bb39fb3ffa03cb5", "6ba744056423ef8d450cf627289166da65903885272055fb4b5e113137cfa14f", "7494b0b0274c5072bddbfd5b4a6c6f18fbbe1ab1d22a41e99cd2d00c8f96ecfe", "826f32b9547c8091679ff292a82aca9c7b9650f9fda3e2ca6bf2ac905b7ce888", "93715dffbcd0678057f947f496484e906bf9509f5c1c38fc9ba3922893cda5f5", "9a334d6c83dfeadae576b4d633a71620d40d1c379129d587faa42ee3e2a85cce", "af7ed8a8aa6957aac47b4268631fa1df984643f07ef00acd374e456364b373f5", "bf0a7aed7f5521c7ca67febd57db473af4762b9622254291fbcbb8cd0ba5e33e", "bf1ef9eb901113a9805287e090452c05547578eaab1b62e4ad456fcc049a9b7e", "c0afd27bc0e307a1ffc04ca5ec010a290e49e3afbe841c5cafc5c5a80ecd81c9", "dd579709a87092c6dbee09d1b7cfa81831040705ffa12a1b248935274aee0437", "df6712284b2e44a065097846488f66840445eb987eb81b3cc6e4149e7b6982e1", "e07d9f1a23e9e93ab5c62902833bf3e4b1f65502927379148b6622686223125c", "e2ede7c1d45e65e209d6093b762e98e8318ddeff95317d07a27a2140b80cfd24", "e4ef9c164eb55123c62411f5936b5c2e521b12356037b6e1c2617cef45523d47", "eca2b7343524e7ba246cab8ff00cab47a2d6d54ada3b02772e908a45675722e2", "eee64c616adeff7db37cc37da4180a3a5b6177f5c46b187894e633f088fb5b28", "ef824cad1f980d27f26166f86856efe11eff9912c4fed97d3804820d43fa550c", "efc89291bd5a08855829a3c522df16d856455297cf35ae827a37edac45f466a7", "fa964bae817babece5aa2e8c1af841bebb6d0b9add8e637548809d040443fee0", "ff37757e068ae606659c28c3bd0d923f9d29a85de79bf25b2b34b148473b5025"]
-dj-database-url = ["4aeaeb1f573c74835b0686a2b46b85990571159ffc21aa57ecd4d1e1cb334163", "851785365761ebe4994a921b433062309eb882fedd318e1b0fcecc607ed02da9"]
-django = ["6f857bd4e574442ba35a7172f1397b303167dae964cf18e53db5e85fe248d000", "d98c9b6e5eed147bc51f47c014ff6826bd1ab50b166956776ee13db5a58804ae"]
-django-any-js = ["1da88b44b861b0f54f6b8ea0eb4c7c4fa1a5772e9a4320532cd4e0871a4e23f7"]
-django-appconf = ["35f13ca4d567f132b960e2cd4c832c2d03cb6543452d34e29b7ba10371ba80e3", "c98a7af40062e996b921f5962a1c4f3f0c979fa7885f7be4710cceb90ebe13a6"]
-django-auth-ldap = ["4d68d21058bd57a316a9e1fcd7a36d0f25d054d4d9d9ec85f766a4991176b454", "5f48232c85ddfa33e3573153e6080526ac2eef5e7ec9cf42b5c4ba3c62afb96d"]
-django-bootstrap4 = ["180edf28d8dcdcee56ca8d7919147c3c98031ecc898c063b228f9855746de80a", "e56d752bd208f703ed04ca9695b4381314220e708ae9036db3ffc1ed5c7361e0"]
-django-bulk-update = ["49a403392ae05ea872494d74fb3dfa3515f8df5c07cc277c3dc94724c0ee6985", "5ab7ce8a65eac26d19143cc189c0f041d5c03b9d1b290ca240dc4f3d6aaeb337"]
-django-common-helpers = ["2d56be6fa261d829a6a224f189bf276267b9082a17d613fe5f015dd4d65c17b4"]
-django-contact-form = ["b42b7e04d6af3318b8427c1eaf62385ec66da252aa79b607ee55d956c7af4a2d", "c31f73faa13f52efa81ac95f41007f3a84eca617f92773a1bed7ca90c61cb3ed"]
-django-cron = ["08d22708c8b2ecab8cda989019a66c7e1e2424c59d822796fd45abf7731d261d"]
-django-dbbackup = ["9470e5d8bdaee4feb878b1b66c59eb9b27a131cccd648bf7cbfe70930acd4fc0"]
-django-dbsettings = ["e3147ced54b7db3371df10df8845e4514aeae96720000bca1a01d0a6490a1404"]
-django-debug-toolbar = ["24c157bc6c0e1648e0a6587511ecb1b007a00a354ce716950bff2de12693e7a8", "77cfba1d6e91b9bc3d36dc7dc74a9bb80be351948db5f880f2562a0cbf20b6c5"]
-django-easy-audit = ["1c5d5e6d6a33f50f696ed53cdaf51de0a4ae2f110ef8c41b33bc139b737729a6", "4b40a30599fe721eb0a9946f5023254fa0904d531c9f4adb23ee52601efaf89b"]
-django-fa = ["e3ebf97b90e374b5ccb5b8a70e4a932c8787f2ee995c09a97a63bf9a1366c3ff"]
-django-formtools = ["304fa777b8ef9e0693ce7833f885cb89ba46b0e46fc23b01176900a93f46742f", "c5272c03c1cd51b2375abf7397a199a3148a9fbbf2f100e186467a84025d13b2"]
-django-hattori = ["6953d40881317252f19f62c4e7fe8058924b852c7498bc42beb7bc4d268c252c", "e529ed7af8fc34a0169c797c477672b687a205a56f3f5206f90c260acb83b7ac"]
-django-image-cropping = ["157c6f96b2bbe485bde00108cbf379ea0fcb6d7a7252648f7548aa795108dde0"]
-django-impersonate = ["63b62d06f93b0318698c68f7314c78473914c262d4164eb66ad860bb83e04771"]
-django-ipware = ["a7c7a8fd019dbdc9c357e6e582f65034e897572fc79a7e467674efa8aef9d00b"]
-django-maintenance-mode = ["0afcfa6ff4a87348e40c44f58f8a8c4cd3e8eca40ddcdbeb620b68ca78ecbf9c", "473850f80e7762ae586f8347129e73e0d23b89a36b98a70e0c06f1778cacff7c"]
-django-menu-generator = ["ce71a5055c16933c8aff64fb36c21e5cf8b6d505733aceed1252f8b99369a378"]
-django-middleware-global-request = ["f6490759bc9f7dbde4001709554e29ca715daf847f2222914b4e47117dca9313"]
-django-otp = ["1b6025bbbd2517b7c246828b1d11c83d53567904836ae6d57bc0058f3cd18b50", "76a698466178ce40473726ffd8c33f68d1c47f27c53f67fa4aeeb6fdde74d37b"]
-django-otp-yubikey = ["26b12c763b37e99b95b8b8a54d06d8d54c3774eb26133a452f54558033de732b", "f0b1881562fb42ee9f12c28d284cbdb90d1f0383f2d53a595373b080a19bc261"]
-django-phonenumber-field = ["1ab19f723928582fed412bd9844221fa4ff466276d8526b8b4a9913ee1487c5e", "794ebbc3068a7af75aa72a80cb0cec67e714ff8409a965968040f1fd210b2d97"]
-django-sass-processor = ["9267e5fcc7fcde2ec0c7d6ad045b4c6c7e9aea92498ed1725312035a5469b410"]
-django-select2 = ["4c8c4c7c4afee12c17e64d1eabf77af4cd2666a784cba1e74455ce759dd29393", "bcd7e5fc920ceee0e63c36372d8e75bd2254d8a37ac67fc16581634766680d30"]
-django-settings-context-processor = ["d37c853d69a3069f5abbf94c7f4f6fc0fac38bbd0524190cd5a250ba800e496a"]
-django-stubs = ["cd6a7333d518b9168f001b8a31c4ea89a91dea40a9dd1535c798635f69a5f80a", "e3673348a42c7259e81a4ea141dae2b2e711220ec631a6215ba9dc23cdcabdf4"]
-django-tables2 = ["0d9b17f5c030ba1b5fcaeb206d8397bf58f1fdfc6beaf56e7874841b8647aa94", "6afa0496695e15b332e98537265d09fe01a55b28c75a85323d8e6b0dc2350280"]
-django-two-factor-auth = []
-django-yarnpkg = ["010af70049cca94496d4c96ca45e62f13339edd1c22653ab8bfe055acbccd41b", "0d63c7b17e4b9c6c144c4093de3877ce70152f957b36fd7a50b259dc500a4948"]
-docutils = ["6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0", "9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827", "a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99"]
-dynaconf = ["52e3e41290763e405723b13a893592f8bca06f676854e59623052ffeee1a658f", "75691e9dd4093a1a2dc530d33369ae9296cfba30d29b72b00715dfb98b3f82e4"]
-easy-thumbnails = ["23fbe3415c93b2369ece8ebdfb5faa05540943bef8b941b3118ce769ba95e275"]
-entrypoints = ["589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", "c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"]
-faker = ["202ad3b2ec16ae7c51c02904fb838831f8d2899e61bf18db1e91a5a582feab11", "92c84a10bec81217d9cb554ee12b3838c8986ce0b5d45f72f769da22e4bb5432"]
-filelock = ["18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59", "929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"]
-flake8 = ["45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb", "49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca"]
-flake8-polyfill = ["12be6a34ee3ab795b19ca73505e7b55826d5f6ad7230d31b18e106400169b9e9", "e44b087597f6da52ec6393a709e7108b2905317d0c0b744cdca6208e670d8eda"]
-future = ["b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"]
-idna = ["c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", "ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"]
-imagesize = ["3f349de3eb99145973fefb7dbe38554414e5c30abd0c8e4b970a7c9d09f3a1d8", "f3832918bc3c66617f92e35f5d70729187676313caa60c187eb0f28b8fe5e3b5"]
-importlib-metadata = ["3a8b2dfd0a2c6a3636e7c016a7e54ae04b997d30e69d5eacdca7a6c2221a1402", "41e688146d000891f32b1669e8573c57e39e5060e7f5f647aa617cd9a9568278"]
-jinja2 = ["74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f", "9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de"]
-libsass = ["003a65b4facb4c5dbace53fb0f70f61c5aae056a04b4d112a198c3c9674b31f2", "0fd8b4337b3b101c6e6afda9112cc0dc4bacb9133b59d75d65968c7317aa3272", "338e9ae066bf1fde874e335324d5355c52d2081d978b4f74fc59536564b35b08", "4dcfd561fb100250b89496e1362b96f2cc804f689a59731eb0f94f9a9e144f4a", "50778d4be269a021ba2bf42b5b8f6ff3704ab96a82175a052680bddf3ba7cc9f", "6a51393d75f6e3c812785b0fa0b7d67c54258c28011921f204643b55f7355ec0", "74acd9adf506142699dfa292f0e569fdccbd9e7cf619e8226f7117de73566e32", "81a013a4c2a614927fd1ef7a386eddabbba695cbb02defe8f31cf495106e974c", "845a9573b25c141164972d498855f4ad29367c09e6d76fad12955ad0e1c83013", "8b5b6d1a7c4ea1d954e0982b04474cc076286493f6af2d0a13c2e950fbe0be95", "9b59afa0d755089c4165516400a39a289b796b5612eeef5736ab7a1ebf96a67c", "a7e685466448c9b1bf98243339793978f654a1151eb5c975f09b83c7a226f4c1", "c93df526eeef90b1ea4799c1d33b6cd5aea3e9f4633738fb95c1287c13e6b404", "e318f06f06847ff49b1f8d086ac9ebce1e63404f7ea329adab92f4f16ba0e00e", "fc5f8336750f76f1bfae82f7e9e89ae71438d26fc4597e3ab4c05ca8fcd41d8a", "fcb7ab4dc81889e5fc99cafbc2017bc76996f9992fc6b175f7a80edac61d71df"]
-mando = ["4ce09faec7e5192ffc3c57830e26acba0fd6cd11e1ee81af0d4df0657463bd1c", "79feb19dc0f097daa64a1243db578e7674909b75f88ac2220f1c065c10a0d960"]
-markupsafe = ["00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", "09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", "09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", "1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", "24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", "29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", "43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", "46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", "500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", "535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", "62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", "6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", "717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", "79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", "7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", "88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", "8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", "98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", "9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", "9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", "ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", "b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", "b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", "b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", "ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", "c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", "cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", "e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"]
-mccabe = ["ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", "dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"]
-more-itertools = ["b84b238cce0d9adad5ed87e745778d20a3f8487d0f0cb8b8a586816c7496458d", "c833ef592a0324bcc6a60e48440da07645063c453880c9477ceb22490aec1564"]
-mypy = ["0107bff4f46a289f0e4081d59b77cef1c48ea43da5a0dbf0005d54748b26df2a", "07957f5471b3bb768c61f08690c96d8a09be0912185a27a68700f3ede99184e4", "10af62f87b6921eac50271e667cc234162a194e742d8e02fc4ddc121e129a5b0", "11fd60d2f69f0cefbe53ce551acf5b1cec1a89e7ce2d47b4e95a84eefb2899ae", "15e43d3b1546813669bd1a6ec7e6a11d2888db938e0607f7b5eef6b976671339", "352c24ba054a89bb9a35dd064ee95ab9b12903b56c72a8d3863d882e2632dc76", "437020a39417e85e22ea8edcb709612903a9924209e10b3ec6d8c9f05b79f498", "49925f9da7cee47eebf3420d7c0e00ec662ec6abb2780eb0a16260a7ba25f9c4", "6724fcd5777aa6cebfa7e644c526888c9d639bd22edd26b2a8038c674a7c34bd", "7a17613f7ea374ab64f39f03257f22b5755335b73251d0d253687a69029701ba", "cdc1151ced496ca1496272da7fc356580e95f2682be1d32377c22ddebdf73c91"]
-mypy-extensions = ["090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", "2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"]
-packaging = ["28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47", "d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108"]
-pg8000 = ["e4c2b173178ba41bba19366b9cdeced91fecec2caf31c7ece719138633213cca", "eebcb4176a7e407987e525a07454882f611985e0becb2b73f76efb93bbdc0aab"]
-phonenumbers = ["964ecfcf261b98fa7a749ddabd289a484bef9ed8be42d2ed46c35c68c44683bd", "975f1638a743ef53f1b80a8440159047f79ce484f28704b61479e119acc0a503"]
-pillow = ["047d9473cf68af50ac85f8ee5d5f21a60f849bc17d348da7fc85711287a75031", "0f66dc6c8a3cc319561a633b6aa82c44107f12594643efa37210d8c924fc1c71", "12c9169c4e8fe0a7329e8658c7e488001f6b4c8e88740e76292c2b857af2e94c", "248cffc168896982f125f5c13e9317c059f74fffdb4152893339f3be62a01340", "27faf0552bf8c260a5cee21a76e031acaea68babb64daf7e8f2e2540745082aa", "285edafad9bc60d96978ed24d77cdc0b91dace88e5da8c548ba5937c425bca8b", "384b12c9aa8ef95558abdcb50aada56d74bc7cc131dd62d28c2d0e4d3aadd573", "38950b3a707f6cef09cd3cbb142474357ad1a985ceb44d921bdf7b4647b3e13e", "4aad1b88933fd6dc2846552b89ad0c74ddbba2f0884e2c162aa368374bf5abab", "4ac6148008c169603070c092e81f88738f1a0c511e07bd2bb0f9ef542d375da9", "4deb1d2a45861ae6f0b12ea0a786a03d19d29edcc7e05775b85ec2877cb54c5e", "59aa2c124df72cc75ed72c8d6005c442d4685691a30c55321e00ed915ad1a291", "5a47d2123a9ec86660fe0e8d0ebf0aa6bc6a17edc63f338b73ea20ba11713f12", "5cc901c2ab9409b4b7ac7b5bcc3e86ac14548627062463da0af3b6b7c555a871", "6c1db03e8dff7b9f955a0fb9907eb9ca5da75b5ce056c0c93d33100a35050281", "7ce80c0a65a6ea90ef9c1f63c8593fcd2929448613fc8da0adf3e6bfad669d08", "809c19241c14433c5d6135e1b6c72da4e3b56d5c865ad5736ab99af8896b8f41", "83792cb4e0b5af480588601467c0764242b9a483caea71ef12d22a0d0d6bdce2", "846fa202bd7ee0f6215c897a1d33238ef071b50766339186687bd9b7a6d26ac5", "9f5529fc02009f96ba95bea48870173426879dc19eec49ca8e08cd63ecd82ddb", "a423c2ea001c6265ed28700df056f75e26215fd28c001e93ef4380b0f05f9547", "ac4428094b42907aba5879c7c000d01c8278d451a3b7cccd2103e21f6397ea75", "b1ae48d87f10d1384e5beecd169c77502fcc04a2c00a4c02b85f0a94b419e5f9", "bf4e972a88f8841d8fdc6db1a75e0f8d763e66e3754b03006cbc3854d89f1cb1", "c6414f6aad598364aaf81068cabb077894eb88fed99c6a65e6e8217bab62ae7a", "c710fcb7ee32f67baf25aa9ffede4795fd5d93b163ce95fdc724383e38c9df96", "c7be4b8a09852291c3c48d3c25d1b876d2494a0a674980089ac9d5e0d78bd132", "c9e5ffb910b14f090ac9c38599063e354887a5f6d7e6d26795e916b4514f2c1a", "e0697b826da6c2472bb6488db4c0a7fa8af0d52fa08833ceb3681358914b14e5", "e9a3edd5f714229d41057d56ac0f39ad9bdba6767e8c888c951869f0bdd129b0"]
-pluggy = ["15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", "966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"]
-psycopg2 = ["4212ca404c4445dc5746c0d68db27d2cbfb87b523fe233dc84ecd24062e35677", "47fc642bf6f427805daf52d6e52619fe0637648fe27017062d898f3bf891419d", "72772181d9bad1fa349792a1e7384dde56742c14af2b9986013eb94a240f005b", "8396be6e5ff844282d4d49b81631772f80dabae5658d432202faf101f5283b7c", "893c11064b347b24ecdd277a094413e1954f8a4e8cdaf7ffbe7ca3db87c103f0", "92a07dfd4d7c325dd177548c4134052d4842222833576c8391aab6f74038fc3f", "965c4c93e33e6984d8031f74e51227bd755376a9df6993774fd5b6fb3288b1f4", "9ab75e0b2820880ae24b7136c4d230383e07db014456a476d096591172569c38", "b0845e3bdd4aa18dc2f9b6fb78fbd3d9d371ad167fd6d1b7ad01c0a6cdad4fc6", "dca2d7203f0dfce8ea4b3efd668f8ea65cd2b35112638e488a4c12594015f67b", "ed686e5926929887e2c7ae0a700e32c6129abb798b4ad2b846e933de21508151", "ef6df7e14698e79c59c7ee7cf94cd62e5b869db369ed4b1b8f7b729ea825712a", "f898e5cc0a662a9e12bde6f931263a1bbd350cfb18e1d5336a12927851825bb6"]
-py = ["64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", "dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53"]
-pyasn1 = ["014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359", "03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576", "0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf", "08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7", "39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d", "5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00", "6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8", "78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86", "7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12", "99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776", "aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba", "e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2", "fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"]
-pyasn1-modules = ["0c35a52e00b672f832e5846826f1fb7507907f7d52fba6faa9e3c4cbe874fe4b", "13a6955947d8a554de78fc305a4d651f20fb5580b88612a5f0661d4f189d27ac", "233f55c840e821e76828262db976ac894b285909d22d060c2bdb522e7bf28cc6", "24d54188cb7abd750e0a2cba61b7b46a75608175a0c3c1b1eee08322915d8d21", "27581362b4253b9c999882a64df974124cde12be0bf2c04148a0d68bc6bbb7b8", "33c220a2701032261a23eea6e9881404ac6fc7ff96f183b5353fea8fc8962547", "64f6aecf26e93f6a3ba3725b4eb9f532551747d7a63ca9ff43aef12f4bf11eac", "7b4edf07ca2f759d7cf693184be09f22e067c2eb52b03c770d0a2e9de1c67dfd", "9b972f81f59d896cebb9ebb1d44296f1acb28bf7869443c37551f4eed8d74f83", "9ca5e376a6d9dee35bb3a62608dfa2e6698798aa6b8db3c7afd0eb31af0d63c7", "b6ada4f840fe51abf5a6bd545b45bf537bea62221fa0dde2e8a553ed9f06a4e3", "c14b107a67ee36a7f183ae9f4803ffde4a03b67f3192eab0a62e851af71371d3", "eaf35047a0b068e3e0c2a99618b13b65c98c329661daa78c9d44a4ef0fe8139e"]
-pycodestyle = ["95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", "e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"]
-pycryptodome = ["042ae873baadd0c33b4d699a5c5b976ade3233a979d972f98ca82314632d868c", "0502876279772b1384b660ccc91563d04490d562799d8e2e06b411e2d81128a9", "2de33ed0a95855735d5a0fc0c39603314df9e78ee8bbf0baa9692fb46b3b8bbb", "319e568baf86620b419d53063b18c216abf924875966efdfe06891b987196a45", "4372ec7518727172e1605c0843cdc5375d4771e447b8148c787b860260aae151", "48821950ffb9c836858d8fa09d7840b6df52eadd387a3c5acece55cb387743f9", "4b9533d4166ca07abdd49ce9d516666b1df944997fe135d4b21ac376aa624aff", "54456cf85130e01674d21fb1ab89ffccacb138a8ade88d72fa2b0ac898d2798b", "56fdd0e425f1b8fd3a00b6d96351f86226674974814c50534864d0124d48871f", "57b1b707363490c495ad0eeb38bd1b0e1697c497af25fad78d3a1ebf0477fd5b", "5c485ed6e9718ebcaa81138fa70ace9c563d202b56a8cee119b4085b023931f5", "63c103a22cbe9752f6ea9f1a0de129995bad91c4d03a66c67cffcf6ee0c9f1e1", "68fab8455efcbfe87c5d75015476f9b606227ffe244d57bfd66269451706e899", "6c2720696b10ae356040e888bde1239b8957fe18885ccf5e7b4e8dec882f0856", "72166c2ac520a5dbd2d90208b9c279161ec0861662a621892bd52fb6ca13ab91", "7c52308ac5b834331b2f107a490b2c27de024a229b61df4cdc5c131d563dfe98", "87d8d85b4792ca5e730fb7a519fbc3ed976c59dcf79c5204589c59afd56b9926", "896e9b6fd0762aa07b203c993fbbee7a1f1a4674c6886afd7bfa86f3d1be98a8", "8a799bea3c6617736e914a2e77c409f52893d382f619f088f8a80e2e21f573c1", "9d9945ac8375d5d8e60bd2a2e1df5882eaa315522eedf3ca868b1546dfa34eba", "9ef966c727de942de3e41aa8462c4b7b4bca70f19af5a3f99e31376589c11aac", "a168e73879619b467072509a223282a02c8047d932a48b74fbd498f27224aa04", "a30f501bbb32e01a49ef9e09ca1260e5ab49bf33a257080ec553e08997acc487", "a8ca2450394d3699c9f15ef25e8de9a24b401933716a1e39d37fa01f5fe3c58b", "aec4d42deb836b8fb3ba32f2ba1ef0d33dd3dc9d430b1479ee7a914490d15b5e", "b4af098f2a50f8d048ab12cabb59456585c0acf43d90ee79782d2d6d0ed59dba", "b55c60c321ac91945c60a40ac9896ac7a3d432bb3e8c14006dfd82ad5871c331", "c53348358408d94869059e16fba5ff3bef8c52c25b18421472aba272b9bb450f", "cbfd97f9e060f0d30245cd29fa267a9a84de9da97559366fca0a3f7655acc63f", "d3fe3f33ad52bf0c19ee6344b695ba44ffbfa16f3c29ca61116b48d97bd970fb", "e3a79a30d15d9c7c284a7734036ee8abdb5ca3a6f5774d293cdc9e1358c1dc10", "eec0689509389f19875f66ae8dedd59f982240cdab31b9f78a8dc266011df93a"]
-pydocstyle = ["7f2b7e70c4e6951d0f2aff445b670a336a28c56c0a40275dc8620a93ac2140c3", "d85eb88a0650756ea44d91917c1eb3a20903eeb163285774721a4807190fce24"]
-pyflakes = ["17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", "d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"]
-pygments = ["2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b", "98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe"]
-pyjwt = ["5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e", "8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96"]
-pylama = ["9bae53ef9c1a431371d6a8dca406816a60d547147b60a4934721898f553b7d8f", "fd61c11872d6256b019ef1235be37b77c922ef37ac9797df6bd489996dddeb15"]
-pyparsing = ["20f995ecd72f2a1f4bf6b072b63b22e2eb457836601e76d6e5dfcd75436acc1f", "4ca62001be367f01bd3e92ecbb79070272a9d4964dce6a48a82ff0b8bc7e683a"]
-pytest = ["63344a2e3bce2e4d522fd62b4fdebb647c019f1f9e4ca075debbd13219db4418", "f67403f33b2b1d25a6756184077394167fe5e2f9d8bdaab30707d19ccec35427"]
-pytest-cov = ["cc6742d8bac45070217169f5f72ceee1e0e55b0221f54bcf24845972d3a47f2b", "cdbdef4f870408ebdbfeb44e63e07eb18bb4619fae852f6e760645fa36172626"]
-pytest-django = ["17592f06d51c2ef4b7a0fb24aa32c8b6998506a03c8439606cb96db160106659", "ef3d15b35ed7e46293475e6f282e71a53bcd8c6f87bdc5d5e7ad0f4d49352127"]
-pytest-django-testing-postgresql = ["78b0c58930084cb4393407b2e5a2a3b8734c627b841ecef7d62d39bbfb8e8a45", "78e52e3d1b0ef5f906d5d69247dd6ac7dfb10d840bd81abab92f3f8c30872cd3"]
-python-box = ["694a7555e3ff9fbbce734bbaef3aad92b8e4ed0659d3ed04d56b6a0a0eff26a9", "a71d3dc9dbaa34c8597d3517c89a8041bd62fa875f23c0f3dad55e1958e3ce10"]
-python-dateutil = ["73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", "75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"]
-python-dotenv = ["debd928b49dbc2bf68040566f55cdb3252458036464806f4094487244e2a4093", "f157d71d5fec9d4bd5f51c82746b6344dffa680ee85217c123f4a0c8117c4544"]
-python-ldap = ["7d1c4b15375a533564aad3d3deade789221e450052b21ebb9720fb822eccdb8e"]
-python-memcached = ["4dac64916871bd3550263323fc2ce18e1e439080a2d5670c594cf3118d99b594", "a2e28637be13ee0bf1a8b6843e7490f9456fd3f2a4cb60471733c7b5d5557e4f"]
-pytz = ["1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d", "b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"]
-pyyaml = ["0e7f69397d53155e55d10ff68fdfb2cf630a35e6daf65cf0bdeaf04f127c09dc", "2e9f0b7c5914367b0916c3c104a024bb68f269a486b9d04a2e8ac6f6597b7803", "35ace9b4147848cafac3db142795ee42deebe9d0dad885ce643928e88daebdcc", "38a4f0d114101c58c0f3a88aeaa44d63efd588845c5a2df5290b73db8f246d15", "483eb6a33b671408c8529106df3707270bfacb2447bf8ad856a4b4f57f6e3075", "4b6be5edb9f6bb73680f5bf4ee08ff25416d1400fbd4535fe0069b2994da07cd", "7f38e35c00e160db592091751d385cd7b3046d6d51f578b29943225178257b31", "8100c896ecb361794d8bfdb9c11fce618c7cf83d624d73d5ab38aef3bc82d43f", "c0ee8eca2c582d29c3c2ec6e2c4f703d1b7f1fb10bc72317355a746057e7346c", "e4c015484ff0ff197564917b4b4246ca03f411b9bd7f16e02a2f586eb48b6d04", "ebc4ed52dcc93eeebeae5cf5deb2ae4347b3a81c3fa12b0b8c976544829396a4"]
-qrcode = ["3996ee560fc39532910603704c82980ff6d4d5d629f9c3f25f34174ce8606cf5", "505253854f607f2abf4d16092c61d4e9d511a3b4392e60bff957a68592b04369"]
-radon = ["0cde1953547a164d24420ed6ccdfa18b61f1457b96a2b99ff0de76b22d504a0f", "ee20308ce8bae7a89b067425b63b141a0077632ab318d5288da649c830882b3d"]
-requests = ["11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", "9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"]
-scramp = ["475aa6296deb2737b86e9df9098e8eca0f30c8ad1cc0a8adadb99ef012a5ceba", "e09d2a9be5adeb94cbeb56fc54a61fc5f5b6e140e679b2b60d1f7a8d6478d906"]
-six = ["1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd", "30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66"]
-snowballstemmer = ["209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0", "df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"]
-soupsieve = ["bdb0d917b03a1369ce964056fc195cfdff8819c40de04695a80bc813c3cfa1f5", "e2c1c5dee4a1c36bcb790e0fabd5492d874b8ebd4617622c4f6a731701060dda"]
-sphinx = ["3b16e48e791a322d584489ab28d8800652123d1fbfdd173e2965a31d40bf22d7", "559c1a8ed1365a982f77650720b41114414139a635692a23c2990824d0a84cf2"]
-sphinx-autodoc-typehints = ["27c9e6ef4f4451766ab8d08b2d8520933b97beb21c913f3df9ab2e59b56e6c6c", "a6b3180167479aca2c4d1ed3b5cb044a70a76cccd6b38662d39288ebd9f0dff0"]
-sphinxcontrib-applehelp = ["edaa0ab2b2bc74403149cb0209d6775c96de797dfd5b5e2a71981309efab3897", "fb8dee85af95e5c30c91f10e7eb3c8967308518e0f7488a2828ef7bc191d0d5d"]
-sphinxcontrib-devhelp = ["6c64b077937330a9128a4da74586e8c2130262f014689b4b89e2d08ee7294a34", "9512ecb00a2b0821a146736b39f7aeb90759834b07e81e8cc23a9c70bacb9981"]
-sphinxcontrib-django = ["95831d5d58f780010b0255f298ea03d6bbf971c0f94f631268abd4320400b7db", "f5bb52d20b64b51087b44247789adaebfc51fa2fad71bfed42c4ca6c05723838"]
-sphinxcontrib-htmlhelp = ["4670f99f8951bd78cd4ad2ab962f798f5618b17675c35c5ac3b2132a14ea8422", "d4fd39a65a625c9df86d7fa8a2d9f3cd8299a3a4b15db63b50aac9e161d8eff7"]
-sphinxcontrib-jsmath = ["2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", "a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"]
-sphinxcontrib-qthelp = ["513049b93031beb1f57d4daea74068a4feb77aa5630f856fcff2e50de14e9a20", "79465ce11ae5694ff165becda529a600c754f4bc459778778c7017374d4d406f"]
-sphinxcontrib-serializinghtml = ["c0efb33f8052c04fd7a26c0a07f1678e8512e0faec19f4aa8f2473a8b81d5227", "db6615af393650bf1151a6cd39120c29abaf93cc60db8c48eb2dddbfdc3a9768"]
-sqlparse = ["40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177", "7c3dca29c022744e95b547e867cee89f4fce4373f3549ccd8797d8eb52cdb873"]
-"testing.common.database" = ["965d80b2985315325dc358c3061b174a712f4d4d5bf6a80b58b11f9a1dd86d73", "e3ed492bf480a87f271f74c53b262caf5d85c8bc09989a8f534fa2283ec52492"]
-"testing.postgresql" = ["1b41daeb98dfc8cd4a584bb91e8f5f4ab182993870f95257afe5f1ba6151a598", "8e1a69760369a7a8ffe63a66b6d95a5cd82db2fb976e4a8f85ffd24fbfc447d8"]
-text-unidecode = ["1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", "bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"]
-toml = ["229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", "235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e", "f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"]
-tox = ["7efd010a98339209f3a8292f02909b51c58417bfc6838ab7eca14cf90f96117a", "8dd653bf0c6716a435df363c853cad1f037f9d5fddd0abc90d0f48ad06f39d03"]
-tqdm = ["895796ea8df435b6f502bf122f2b2034a3d48e6d8ff52175606ac1051b0e3e12", "e405d16c98fcf30725d0c9d493ed07302a18846b5452de5253030ccd18996f87"]
-twilio = ["da282a9c02bd9dfb190b798528b478833d8d28cb51464e8c45da0f0794384cde"]
-typed-ast = ["1170afa46a3799e18b4c977777ce137bb53c7485379d9706af8a59f2ea1aa161", "18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e", "262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e", "2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0", "354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c", "48e5b1e71f25cfdef98b013263a88d7145879fbb2d5185f2a0c79fa7ebbeae47", "4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631", "630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4", "66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34", "71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b", "7954560051331d003b4e2b3eb822d9dd2e376fa4f6d98fee32f452f52dd6ebb2", "838997f4310012cf2e1ad3803bce2f3402e9ffb71ded61b5ee22617b3a7f6b6e", "95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a", "bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233", "cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1", "d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36", "d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d", "d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a", "fdc1c9bbf79510b76408840e009ed65958feba92a88833cdceecff93ae8fff66", "ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12"]
-typing-extensions = ["091ecc894d5e908ac75209f10d5b4f118fbdb2eb1ede6a63544054bb1edb41f2", "910f4656f54de5993ad9304959ce9bb903f90aadc7c67a0bef07e678014e892d", "cf8b63fedea4d89bab840ecbb93e75578af28f76f66c35889bd7065f5af88575"]
-urllib3 = ["a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293", "f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745"]
-virtualenv = ["116655188441670978117d0ebb6451eb6a7526f9ae0796cc0dee6bd7356909b0", "b57776b44f91511866594e477dd10e76a6eb44439cdd7f06dcd30ba4c5bd854f"]
-wcwidth = ["3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", "f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"]
-yubiotp = ["7e281801b24678f4bda855ce8ab975a7688a912f5a6cb22b6c2b16263a93cbd2", "de83b1560226e38b5923f6ab919f962c8c2abb7c722104cb45b2b6db2ac86e40"]
-zipp = ["3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e", "f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335"]
+[metadata.files]
+alabaster = [
+    {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"},
+    {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"},
+]
+appdirs = [
+    {file = "appdirs-1.4.3-py2.py3-none-any.whl", hash = "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"},
+    {file = "appdirs-1.4.3.tar.gz", hash = "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92"},
+]
+asgiref = [
+    {file = "asgiref-3.2.3-py2.py3-none-any.whl", hash = "sha256:ea448f92fc35a0ef4b1508f53a04c4670255a3f33d22a81c8fc9c872036adbe5"},
+    {file = "asgiref-3.2.3.tar.gz", hash = "sha256:7e06d934a7718bf3975acbf87780ba678957b87c7adc056f13b6215d610695a0"},
+]
+atomicwrites = [
+    {file = "atomicwrites-1.3.0-py2.py3-none-any.whl", hash = "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4"},
+    {file = "atomicwrites-1.3.0.tar.gz", hash = "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"},
+]
+attrs = [
+    {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"},
+    {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"},
+]
+babel = [
+    {file = "Babel-2.7.0-py2.py3-none-any.whl", hash = "sha256:af92e6106cb7c55286b25b38ad7695f8b4efb36a90ba483d7f7a6628c46158ab"},
+    {file = "Babel-2.7.0.tar.gz", hash = "sha256:e86135ae101e31e2c8ec20a4e0c5220f4eed12487d5cf3f78be7e98d3a57fc28"},
+]
+bandit = [
+    {file = "bandit-1.6.2-py2.py3-none-any.whl", hash = "sha256:336620e220cf2d3115877685e264477ff9d9abaeb0afe3dc7264f55fa17a3952"},
+    {file = "bandit-1.6.2.tar.gz", hash = "sha256:41e75315853507aa145d62a78a2a6c5e3240fe14ee7c601459d0df9418196065"},
+]
+beautifulsoup4 = [
+    {file = "beautifulsoup4-4.8.2-py2-none-any.whl", hash = "sha256:e1505eeed31b0f4ce2dbb3bc8eb256c04cc2b3b72af7d551a4ab6efd5cbe5dae"},
+    {file = "beautifulsoup4-4.8.2-py3-none-any.whl", hash = "sha256:9fbb4d6e48ecd30bcacc5b63b94088192dcda178513b2ae3c394229f8911b887"},
+    {file = "beautifulsoup4-4.8.2.tar.gz", hash = "sha256:05fd825eb01c290877657a56df4c6e4c311b3965bda790c613a3d6fb01a5462a"},
+]
+black = [
+    {file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"},
+    {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"},
+]
+certifi = [
+    {file = "certifi-2019.11.28-py2.py3-none-any.whl", hash = "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3"},
+    {file = "certifi-2019.11.28.tar.gz", hash = "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"},
+]
+chardet = [
+    {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"},
+    {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"},
+]
+click = [
+    {file = "Click-7.0-py2.py3-none-any.whl", hash = "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13"},
+    {file = "Click-7.0.tar.gz", hash = "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"},
+]
+colorama = [
+    {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"},
+    {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"},
+]
+colour = [
+    {file = "colour-0.1.5-py2.py3-none-any.whl", hash = "sha256:33f6db9d564fadc16e59921a56999b79571160ce09916303d35346dddc17978c"},
+    {file = "colour-0.1.5.tar.gz", hash = "sha256:af20120fefd2afede8b001fbef2ea9da70ad7d49fafdb6489025dae8745c3aee"},
+]
+configobj = [
+    {file = "configobj-5.0.6.tar.gz", hash = "sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902"},
+]
+coverage = [
+    {file = "coverage-5.0.1-cp27-cp27m-macosx_10_12_x86_64.whl", hash = "sha256:c90bda74e16bcd03861b09b1d37c0a4158feda5d5a036bb2d6e58de6ff65793e"},
+    {file = "coverage-5.0.1-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:bb3d29df5d07d5399d58a394d0ef50adf303ab4fbf66dfd25b9ef258effcb692"},
+    {file = "coverage-5.0.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:1ca43dbd739c0fc30b0a3637a003a0d2c7edc1dd618359d58cc1e211742f8bd1"},
+    {file = "coverage-5.0.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:591506e088901bdc25620c37aec885e82cc896528f28c57e113751e3471fc314"},
+    {file = "coverage-5.0.1-cp27-cp27m-win32.whl", hash = "sha256:a50b0888d8a021a3342d36a6086501e30de7d840ab68fca44913e97d14487dc1"},
+    {file = "coverage-5.0.1-cp27-cp27m-win_amd64.whl", hash = "sha256:c792d3707a86c01c02607ae74364854220fb3e82735f631cd0a345dea6b4cee5"},
+    {file = "coverage-5.0.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f425f50a6dd807cb9043d15a4fcfba3b5874a54d9587ccbb748899f70dc18c47"},
+    {file = "coverage-5.0.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:25b8f60b5c7da71e64c18888f3067d5b6f1334b9681876b2fb41eea26de881ae"},
+    {file = "coverage-5.0.1-cp35-cp35m-macosx_10_12_x86_64.whl", hash = "sha256:7362a7f829feda10c7265b553455de596b83d1623b3d436b6d3c51c688c57bf6"},
+    {file = "coverage-5.0.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:fcd4459fe35a400b8f416bc57906862693c9f88b66dc925e7f2a933e77f6b18b"},
+    {file = "coverage-5.0.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:40fbfd6b044c9db13aeec1daf5887d322c710d811f944011757526ef6e323fd9"},
+    {file = "coverage-5.0.1-cp35-cp35m-win32.whl", hash = "sha256:7f2675750c50151f806070ec11258edf4c328340916c53bac0adbc465abd6b1e"},
+    {file = "coverage-5.0.1-cp35-cp35m-win_amd64.whl", hash = "sha256:24bcfa86fd9ce86b73a8368383c39d919c497a06eebb888b6f0c12f13e920b1a"},
+    {file = "coverage-5.0.1-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:eeafb646f374988c22c8e6da5ab9fb81367ecfe81c70c292623373d2a021b1a1"},
+    {file = "coverage-5.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:2ca2cd5264e84b2cafc73f0045437f70c6378c0d7dbcddc9ee3fe192c1e29e5d"},
+    {file = "coverage-5.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2cc707fc9aad2592fc686d63ef72dc0031fc98b6fb921d2f5395d9ab84fbc3ef"},
+    {file = "coverage-5.0.1-cp36-cp36m-win32.whl", hash = "sha256:04b961862334687549eb91cd5178a6fbe977ad365bddc7c60f2227f2f9880cf4"},
+    {file = "coverage-5.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:232f0b52a5b978288f0bbc282a6c03fe48cd19a04202df44309919c142b3bb9c"},
+    {file = "coverage-5.0.1-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:cfce79ce41cc1a1dc7fc85bb41eeeb32d34a4cf39a645c717c0550287e30ff06"},
+    {file = "coverage-5.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c9c6a1d1190c0b75ec7c0f339088309952b82ae8d67a79ff1319eb4e749b96"},
+    {file = "coverage-5.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:1cbb88b34187bdb841f2599770b7e6ff8e259dc3bb64fc7893acf44998acf5f8"},
+    {file = "coverage-5.0.1-cp37-cp37m-win32.whl", hash = "sha256:ff3936dd5feaefb4f91c8c1f50a06c588b5dc69fba4f7d9c79a6617ad80bb7df"},
+    {file = "coverage-5.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:65bead1ac8c8930cf92a1ccaedcce19a57298547d5d1db5c9d4d068a0675c38b"},
+    {file = "coverage-5.0.1-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:348630edea485f4228233c2f310a598abf8afa5f8c716c02a9698089687b6085"},
+    {file = "coverage-5.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:960d7f42277391e8b1c0b0ae427a214e1b31a1278de6b73f8807b20c2e913bba"},
+    {file = "coverage-5.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:0101888bd1592a20ccadae081ba10e8b204d20235d18d05c6f7d5e904a38fc10"},
+    {file = "coverage-5.0.1-cp38-cp38m-win32.whl", hash = "sha256:c0fff2733f7c2950f58a4fd09b5db257b00c6fec57bf3f68c5bae004d804b407"},
+    {file = "coverage-5.0.1-cp38-cp38m-win_amd64.whl", hash = "sha256:5f622f19abda4e934938e24f1d67599249abc201844933a6f01aaa8663094489"},
+    {file = "coverage-5.0.1-cp39-cp39m-win32.whl", hash = "sha256:2714160a63da18aed9340c70ed514973971ee7e665e6b336917ff4cca81a25b1"},
+    {file = "coverage-5.0.1-cp39-cp39m-win_amd64.whl", hash = "sha256:b7dbc5e8c39ea3ad3db22715f1b5401cd698a621218680c6daf42c2f9d36e205"},
+    {file = "coverage-5.0.1.tar.gz", hash = "sha256:5ac71bba1e07eab403b082c4428f868c1c9e26a21041436b4905c4c3d4e49b08"},
+]
+dj-database-url = [
+    {file = "dj-database-url-0.5.0.tar.gz", hash = "sha256:4aeaeb1f573c74835b0686a2b46b85990571159ffc21aa57ecd4d1e1cb334163"},
+    {file = "dj_database_url-0.5.0-py2.py3-none-any.whl", hash = "sha256:851785365761ebe4994a921b433062309eb882fedd318e1b0fcecc607ed02da9"},
+]
+django = [
+    {file = "Django-3.0.1-py3-none-any.whl", hash = "sha256:b61295749be7e1c42467c55bcabdaee9fbe9496fdf9ed2e22cef44d9de2ff953"},
+    {file = "Django-3.0.1.tar.gz", hash = "sha256:315b11ea265dd15348d47f2cbb044ef71da2018f6e582fed875c889758e6f844"},
+]
+django-any-js = [
+    {file = "django-any-js-1.0.3.post0.tar.gz", hash = "sha256:1da88b44b861b0f54f6b8ea0eb4c7c4fa1a5772e9a4320532cd4e0871a4e23f7"},
+]
+django-appconf = [
+    {file = "django-appconf-1.0.3.tar.gz", hash = "sha256:35f13ca4d567f132b960e2cd4c832c2d03cb6543452d34e29b7ba10371ba80e3"},
+    {file = "django_appconf-1.0.3-py2.py3-none-any.whl", hash = "sha256:c98a7af40062e996b921f5962a1c4f3f0c979fa7885f7be4710cceb90ebe13a6"},
+]
+django-auth-ldap = [
+    {file = "django-auth-ldap-2.1.0.tar.gz", hash = "sha256:5f48232c85ddfa33e3573153e6080526ac2eef5e7ec9cf42b5c4ba3c62afb96d"},
+    {file = "django_auth_ldap-2.1.0-py3-none-any.whl", hash = "sha256:4d68d21058bd57a316a9e1fcd7a36d0f25d054d4d9d9ec85f766a4991176b454"},
+]
+django-bootstrap4 = [
+    {file = "django-bootstrap4-1.1.1.tar.gz", hash = "sha256:39f97cbce85eb66f6d76be2029bae171bd3863d0c6932b1c2dae7f299c569b90"},
+    {file = "django_bootstrap4-1.1.1-py3-none-any.whl", hash = "sha256:0fcd84f8414a58b43df0b331c00c8b2f1786ae28f75f419b4d33b06fca43e0d1"},
+]
+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-common-helpers = [
+    {file = "django-common-helpers-0.9.2.tar.gz", hash = "sha256:2d56be6fa261d829a6a224f189bf276267b9082a17d613fe5f015dd4d65c17b4"},
+]
+django-cron = [
+    {file = "django-cron-0.5.1.tar.gz", hash = "sha256:08d22708c8b2ecab8cda989019a66c7e1e2424c59d822796fd45abf7731d261d"},
+]
+django-dbbackup = [
+    {file = "django-dbbackup-3.2.0.tar.gz", hash = "sha256:9470e5d8bdaee4feb878b1b66c59eb9b27a131cccd648bf7cbfe70930acd4fc0"},
+]
+django-dbsettings = [
+    {file = "django-dbsettings-1.0.0.tar.gz", hash = "sha256:42b04dffd3bc90d91718c822f1e0212d9368e8efe340f7ef09517b5fb1cf49f5"},
+]
+django-debug-toolbar = [
+    {file = "django-debug-toolbar-2.1.tar.gz", hash = "sha256:24c157bc6c0e1648e0a6587511ecb1b007a00a354ce716950bff2de12693e7a8"},
+    {file = "django_debug_toolbar-2.1-py3-none-any.whl", hash = "sha256:77cfba1d6e91b9bc3d36dc7dc74a9bb80be351948db5f880f2562a0cbf20b6c5"},
+]
+django-easy-audit = [
+    {file = "django-easy-audit-1.1.1.tar.gz", hash = "sha256:4b40a30599fe721eb0a9946f5023254fa0904d531c9f4adb23ee52601efaf89b"},
+    {file = "django_easy_audit-1.1.1-py2.py3-none-any.whl", hash = "sha256:1c5d5e6d6a33f50f696ed53cdaf51de0a4ae2f110ef8c41b33bc139b737729a6"},
+]
+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-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"},
+]
+django-image-cropping = [
+    {file = "django-image-cropping-1.3.0.tar.gz", hash = "sha256:5c102d87bc66de025517ad06e485c100f73313ebf725e7482a728944276f6463"},
+]
+django-impersonate = [
+    {file = "django-impersonate-1.4.1.tar.gz", hash = "sha256:63b62d06f93b0318698c68f7314c78473914c262d4164eb66ad860bb83e04771"},
+]
+django-ipware = [
+    {file = "django-ipware-2.1.0.tar.gz", hash = "sha256:a7c7a8fd019dbdc9c357e6e582f65034e897572fc79a7e467674efa8aef9d00b"},
+]
+django-maintenance-mode = [
+    {file = "django-maintenance-mode-0.14.0.tar.gz", hash = "sha256:f3fef1760fdcda5e0bf6c2966aadc77eea6f328580a9c751920daba927281a68"},
+    {file = "django_maintenance_mode-0.14.0-py2-none-any.whl", hash = "sha256:b4cc24a469ed10897826a28f05d64e6166a58d130e4940ac124ce198cd4cc778"},
+]
+django-menu-generator = [
+    {file = "django-menu-generator-1.0.4.tar.gz", hash = "sha256:ce71a5055c16933c8aff64fb36c21e5cf8b6d505733aceed1252f8b99369a378"},
+]
+django-middleware-global-request = [
+    {file = "django-middleware-global-request-0.1.2.tar.gz", hash = "sha256:f6490759bc9f7dbde4001709554e29ca715daf847f2222914b4e47117dca9313"},
+]
+django-otp = [
+    {file = "django-otp-0.7.4.tar.gz", hash = "sha256:1b6025bbbd2517b7c246828b1d11c83d53567904836ae6d57bc0058f3cd18b50"},
+    {file = "django_otp-0.7.4-py2.py3-none-any.whl", hash = "sha256:76a698466178ce40473726ffd8c33f68d1c47f27c53f67fa4aeeb6fdde74d37b"},
+]
+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-sass-processor = [
+    {file = "django-sass-processor-0.7.5.tar.gz", hash = "sha256:9267e5fcc7fcde2ec0c7d6ad045b4c6c7e9aea92498ed1725312035a5469b410"},
+]
+django-select2 = [
+    {file = "django-select2-7.1.2.tar.gz", hash = "sha256:bcd7e5fc920ceee0e63c36372d8e75bd2254d8a37ac67fc16581634766680d30"},
+    {file = "django_select2-7.1.2-py2.py3-none-any.whl", hash = "sha256:4c8c4c7c4afee12c17e64d1eabf77af4cd2666a784cba1e74455ce759dd29393"},
+]
+django-settings-context-processor = [
+    {file = "django-settings-context-processor-0.2.tar.gz", hash = "sha256:d37c853d69a3069f5abbf94c7f4f6fc0fac38bbd0524190cd5a250ba800e496a"},
+]
+django-stubs = [
+    {file = "django-stubs-1.4.0.tar.gz", hash = "sha256:a84860e02d5ada1a96e4c6f6800be4729ea3df354ff1156f0616db647dc2fe9e"},
+    {file = "django_stubs-1.4.0-py3-none-any.whl", hash = "sha256:9a1bd302c42b1132e1537ba2315b94de01e673a1926ac2b3e1523ed7b81411c8"},
+]
+django-tables2 = [
+    {file = "django-tables2-2.2.1.tar.gz", hash = "sha256:0d9b17f5c030ba1b5fcaeb206d8397bf58f1fdfc6beaf56e7874841b8647aa94"},
+    {file = "django_tables2-2.2.1-py2.py3-none-any.whl", hash = "sha256:6afa0496695e15b332e98537265d09fe01a55b28c75a85323d8e6b0dc2350280"},
+]
+django-two-factor-auth = [
+    {file = "django-two-factor-auth-1.10.0.tar.gz", hash = "sha256:3c3af3cd747462be18e7494c4068a2bdc606d7a2d2b2914f8d4590fc80995a71"},
+    {file = "django_two_factor_auth-1.10.0-py2.py3-none-any.whl", hash = "sha256:0945260fa84e4522d8fa951c35e401616579fd8564938441614399dc588a1c1f"},
+]
+django-yarnpkg = [
+    {file = "django-yarnpkg-6.0.0.tar.gz", hash = "sha256:010af70049cca94496d4c96ca45e62f13339edd1c22653ab8bfe055acbccd41b"},
+    {file = "django_yarnpkg-6.0.0-py3-none-any.whl", hash = "sha256:0d63c7b17e4b9c6c144c4093de3877ce70152f957b36fd7a50b259dc500a4948"},
+]
+docutils = [
+    {file = "docutils-0.15.2-py2-none-any.whl", hash = "sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827"},
+    {file = "docutils-0.15.2-py3-none-any.whl", hash = "sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0"},
+    {file = "docutils-0.15.2.tar.gz", hash = "sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99"},
+]
+dparse = [
+    {file = "dparse-0.4.1-py2-none-any.whl", hash = "sha256:cef95156fa0adedaf042cd42f9990974bec76f25dfeca4dc01f381a243d5aa5b"},
+    {file = "dparse-0.4.1.tar.gz", hash = "sha256:00a5fdfa900629e5159bf3600d44905b333f4059a3366f28e0dbd13eeab17b19"},
+]
+dynaconf = [
+    {file = "dynaconf-2.2.2-py2.py3-none-any.whl", hash = "sha256:3cfc1ad7efae08b9cf91d81043a3230175e74e01157d875ce5ec6709bf4e6b9d"},
+    {file = "dynaconf-2.2.2.tar.gz", hash = "sha256:4bac78b432e090d8ed66f1c23fb32e03ca91a590bf0a51ac36137e0e45ac31ca"},
+]
+easy-thumbnails = [
+    {file = "easy-thumbnails-2.7.tar.gz", hash = "sha256:e4e7a0dd4001f56bfd4058428f2c91eafe27d33ef3b8b33ac4e013b159b9ff91"},
+]
+entrypoints = [
+    {file = "entrypoints-0.3-py2.py3-none-any.whl", hash = "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19"},
+    {file = "entrypoints-0.3.tar.gz", hash = "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"},
+]
+faker = [
+    {file = "Faker-3.0.0-py2.py3-none-any.whl", hash = "sha256:202ad3b2ec16ae7c51c02904fb838831f8d2899e61bf18db1e91a5a582feab11"},
+    {file = "Faker-3.0.0.tar.gz", hash = "sha256:92c84a10bec81217d9cb554ee12b3838c8986ce0b5d45f72f769da22e4bb5432"},
+]
+flake8 = [
+    {file = "flake8-3.7.9-py2.py3-none-any.whl", hash = "sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca"},
+    {file = "flake8-3.7.9.tar.gz", hash = "sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb"},
+]
+flake8-bandit = [
+    {file = "flake8_bandit-2.1.2.tar.gz", hash = "sha256:687fc8da2e4a239b206af2e54a90093572a60d0954f3054e23690739b0b0de3b"},
+]
+flake8-black = [
+    {file = "flake8-black-0.1.1.tar.gz", hash = "sha256:56f85aaa5a83f06a3f61e680e3b50f156b5e557ebdcb964d823d86f4c108b0c8"},
+]
+flake8-builtins = [
+    {file = "flake8-builtins-1.4.2.tar.gz", hash = "sha256:c44415fb19162ef3737056e700d5b99d48c3612a533943b4e16419a5d3de3a64"},
+    {file = "flake8_builtins-1.4.2-py2.py3-none-any.whl", hash = "sha256:29bc0f7e68af481d088f5c96f8aeb02520abdfc900500484e3af969f42a38a5f"},
+]
+flake8-django = [
+    {file = "flake8-django-0.0.4.tar.gz", hash = "sha256:7329ec2e2b8b194e8109639c534359014c79df4d50b14f4b85b8395edc5d6760"},
+]
+flake8-docstrings = [
+    {file = "flake8-docstrings-1.5.0.tar.gz", hash = "sha256:3d5a31c7ec6b7367ea6506a87ec293b94a0a46c0bce2bb4975b7f1d09b6f3717"},
+    {file = "flake8_docstrings-1.5.0-py2.py3-none-any.whl", hash = "sha256:a256ba91bc52307bef1de59e2a009c3cf61c3d0952dbe035d6ff7208940c2edc"},
+]
+flake8-fixme = [
+    {file = "flake8-fixme-1.1.1.tar.gz", hash = "sha256:50cade07d27a4c30d4f12351478df87339e67640c83041b664724bda6d16f33a"},
+    {file = "flake8_fixme-1.1.1-py2.py3-none-any.whl", hash = "sha256:226a6f2ef916730899f29ac140bed5d4a17e5aba79f00a0e3ae1eff1997cb1ac"},
+]
+flake8-isort = [
+    {file = "flake8-isort-2.8.0.tar.gz", hash = "sha256:64454d1f154a303cfe23ee715aca37271d4f1d299b2f2663f45b73bff14e36a9"},
+    {file = "flake8_isort-2.8.0-py2.py3-none-any.whl", hash = "sha256:aa0c4d004e6be47e74f122f5b7f36554d0d78ad8bf99b497a460dedccaa7cce9"},
+]
+flake8-mypy = [
+    {file = "flake8-mypy-17.8.0.tar.gz", hash = "sha256:47120db63aff631ee1f84bac6fe8e64731dc66da3efc1c51f85e15ade4a3ba18"},
+    {file = "flake8_mypy-17.8.0-py35.py36-none-any.whl", hash = "sha256:cff009f4250e8391bf48990093cff85802778c345c8449d6498b62efefeebcbc"},
+]
+flake8-polyfill = [
+    {file = "flake8-polyfill-1.0.2.tar.gz", hash = "sha256:e44b087597f6da52ec6393a709e7108b2905317d0c0b744cdca6208e670d8eda"},
+    {file = "flake8_polyfill-1.0.2-py2.py3-none-any.whl", hash = "sha256:12be6a34ee3ab795b19ca73505e7b55826d5f6ad7230d31b18e106400169b9e9"},
+]
+flake8-rst-docstrings = [
+    {file = "flake8-rst-docstrings-0.0.12.tar.gz", hash = "sha256:01d38327801781b26c3dfeb71ae37e5a02c5ca1b774a686f63feab8824ca6f9c"},
+]
+gitdb2 = [
+    {file = "gitdb2-2.0.6-py2.py3-none-any.whl", hash = "sha256:96bbb507d765a7f51eb802554a9cfe194a174582f772e0d89f4e87288c288b7b"},
+    {file = "gitdb2-2.0.6.tar.gz", hash = "sha256:1b6df1433567a51a4a9c1a5a0de977aa351a405cc56d7d35f3388bad1f630350"},
+]
+gitpython = [
+    {file = "GitPython-3.0.5-py3-none-any.whl", hash = "sha256:c155c6a2653593ccb300462f6ef533583a913e17857cfef8fc617c246b6dc245"},
+    {file = "GitPython-3.0.5.tar.gz", hash = "sha256:9c2398ffc3dcb3c40b27324b316f08a4f93ad646d5a6328cafbb871aa79f5e42"},
+]
+idna = [
+    {file = "idna-2.8-py2.py3-none-any.whl", hash = "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"},
+    {file = "idna-2.8.tar.gz", hash = "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407"},
+]
+imagesize = [
+    {file = "imagesize-1.2.0-py2.py3-none-any.whl", hash = "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1"},
+    {file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"},
+]
+importlib-metadata = [
+    {file = "importlib_metadata-1.3.0-py2.py3-none-any.whl", hash = "sha256:d95141fbfa7ef2ec65cfd945e2af7e5a6ddbd7c8d9a25e66ff3be8e3daf9f60f"},
+    {file = "importlib_metadata-1.3.0.tar.gz", hash = "sha256:073a852570f92da5f744a3472af1b61e28e9f78ccf0c9117658dc32b15de7b45"},
+]
+isort = [
+    {file = "isort-4.3.21-py2.py3-none-any.whl", hash = "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"},
+    {file = "isort-4.3.21.tar.gz", hash = "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1"},
+]
+jinja2 = [
+    {file = "Jinja2-2.10.3-py2.py3-none-any.whl", hash = "sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f"},
+    {file = "Jinja2-2.10.3.tar.gz", hash = "sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de"},
+]
+libsass = [
+    {file = "libsass-0.19.4-cp27-cp27m-macosx_10_14_intel.whl", hash = "sha256:74acd9adf506142699dfa292f0e569fdccbd9e7cf619e8226f7117de73566e32"},
+    {file = "libsass-0.19.4-cp27-cp27m-win32.whl", hash = "sha256:50778d4be269a021ba2bf42b5b8f6ff3704ab96a82175a052680bddf3ba7cc9f"},
+    {file = "libsass-0.19.4-cp27-cp27m-win_amd64.whl", hash = "sha256:4dcfd561fb100250b89496e1362b96f2cc804f689a59731eb0f94f9a9e144f4a"},
+    {file = "libsass-0.19.4-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:845a9573b25c141164972d498855f4ad29367c09e6d76fad12955ad0e1c83013"},
+    {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-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"},
+    {file = "libsass-0.19.4-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:003a65b4facb4c5dbace53fb0f70f61c5aae056a04b4d112a198c3c9674b31f2"},
+    {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-manylinux1_x86_64.whl", hash = "sha256:6a51393d75f6e3c812785b0fa0b7d67c54258c28011921f204643b55f7355ec0"},
+    {file = "libsass-0.19.4.tar.gz", hash = "sha256:8b5b6d1a7c4ea1d954e0982b04474cc076286493f6af2d0a13c2e950fbe0be95"},
+]
+markupsafe = [
+    {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"},
+    {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"},
+    {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"},
+    {file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"},
+    {file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"},
+    {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"},
+    {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"},
+    {file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"},
+    {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"},
+    {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"},
+    {file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"},
+    {file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"},
+    {file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"},
+    {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"},
+    {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"},
+    {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"},
+    {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"},
+    {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"},
+    {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"},
+    {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"},
+    {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"},
+    {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"},
+    {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"},
+    {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"},
+    {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"},
+    {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"},
+    {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"},
+    {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"},
+]
+mccabe = [
+    {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
+    {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
+]
+more-itertools = [
+    {file = "more-itertools-8.0.2.tar.gz", hash = "sha256:b84b238cce0d9adad5ed87e745778d20a3f8487d0f0cb8b8a586816c7496458d"},
+    {file = "more_itertools-8.0.2-py3-none-any.whl", hash = "sha256:c833ef592a0324bcc6a60e48440da07645063c453880c9477ceb22490aec1564"},
+]
+mypy = [
+    {file = "mypy-0.761-cp35-cp35m-macosx_10_6_x86_64.whl", hash = "sha256:7f672d02fffcbace4db2b05369142e0506cdcde20cea0e07c7c2171c4fd11dd6"},
+    {file = "mypy-0.761-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:87c556fb85d709dacd4b4cb6167eecc5bbb4f0a9864b69136a0d4640fdc76a36"},
+    {file = "mypy-0.761-cp35-cp35m-win_amd64.whl", hash = "sha256:c6d27bd20c3ba60d5b02f20bd28e20091d6286a699174dfad515636cb09b5a72"},
+    {file = "mypy-0.761-cp36-cp36m-macosx_10_6_x86_64.whl", hash = "sha256:4b9365ade157794cef9685791032521233729cb00ce76b0ddc78749abea463d2"},
+    {file = "mypy-0.761-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:634aef60b4ff0f650d3e59d4374626ca6153fcaff96ec075b215b568e6ee3cb0"},
+    {file = "mypy-0.761-cp36-cp36m-win_amd64.whl", hash = "sha256:53ea810ae3f83f9c9b452582261ea859828a9ed666f2e1ca840300b69322c474"},
+    {file = "mypy-0.761-cp37-cp37m-macosx_10_6_x86_64.whl", hash = "sha256:0a9a45157e532da06fe56adcfef8a74629566b607fa2c1ac0122d1ff995c748a"},
+    {file = "mypy-0.761-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:7eadc91af8270455e0d73565b8964da1642fe226665dd5c9560067cd64d56749"},
+    {file = "mypy-0.761-cp37-cp37m-win_amd64.whl", hash = "sha256:e2bb577d10d09a2d8822a042a23b8d62bc3b269667c9eb8e60a6edfa000211b1"},
+    {file = "mypy-0.761-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c35cae79ceb20d47facfad51f952df16c2ae9f45db6cb38405a3da1cf8fc0a7"},
+    {file = "mypy-0.761-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:f97a605d7c8bc2c6d1172c2f0d5a65b24142e11a58de689046e62c2d632ca8c1"},
+    {file = "mypy-0.761-cp38-cp38-win_amd64.whl", hash = "sha256:a6bd44efee4dc8c3324c13785a9dc3519b3ee3a92cada42d2b57762b7053b49b"},
+    {file = "mypy-0.761-py3-none-any.whl", hash = "sha256:7e396ce53cacd5596ff6d191b47ab0ea18f8e0ec04e15d69728d530e86d4c217"},
+    {file = "mypy-0.761.tar.gz", hash = "sha256:85baab8d74ec601e86134afe2bcccd87820f79d2f8d5798c889507d1088287bf"},
+]
+mypy-extensions = [
+    {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
+    {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
+]
+packaging = [
+    {file = "packaging-19.2-py2.py3-none-any.whl", hash = "sha256:d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108"},
+    {file = "packaging-19.2.tar.gz", hash = "sha256:28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47"},
+]
+pathspec = [
+    {file = "pathspec-0.6.0.tar.gz", hash = "sha256:e285ccc8b0785beadd4c18e5708b12bb8fcf529a1e61215b3feff1d1e559ea5c"},
+]
+pbr = [
+    {file = "pbr-5.4.4-py2.py3-none-any.whl", hash = "sha256:61aa52a0f18b71c5cc58232d2cf8f8d09cd67fcad60b742a60124cb8d6951488"},
+    {file = "pbr-5.4.4.tar.gz", hash = "sha256:139d2625547dbfa5fb0b81daebb39601c478c21956dc57e2e07b74450a8c506b"},
+]
+pg8000 = [
+    {file = "pg8000-1.13.2-py3-none-any.whl", hash = "sha256:e4c2b173178ba41bba19366b9cdeced91fecec2caf31c7ece719138633213cca"},
+    {file = "pg8000-1.13.2.tar.gz", hash = "sha256:eebcb4176a7e407987e525a07454882f611985e0becb2b73f76efb93bbdc0aab"},
+]
+phonenumbers = [
+    {file = "phonenumbers-8.11.1-py2.py3-none-any.whl", hash = "sha256:aaa19bc1f2c7efbf7a94be33558e0c5b71620377c9271692d3e314c558962460"},
+    {file = "phonenumbers-8.11.1.tar.gz", hash = "sha256:239507184ee5b1b83557005af1d5fcce70f83ae18f5dff45b94a67226db10d63"},
+]
+pillow = [
+    {file = "Pillow-6.2.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:4ac6148008c169603070c092e81f88738f1a0c511e07bd2bb0f9ef542d375da9"},
+    {file = "Pillow-6.2.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:4aad1b88933fd6dc2846552b89ad0c74ddbba2f0884e2c162aa368374bf5abab"},
+    {file = "Pillow-6.2.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c710fcb7ee32f67baf25aa9ffede4795fd5d93b163ce95fdc724383e38c9df96"},
+    {file = "Pillow-6.2.1-cp27-cp27m-win32.whl", hash = "sha256:e9a3edd5f714229d41057d56ac0f39ad9bdba6767e8c888c951869f0bdd129b0"},
+    {file = "Pillow-6.2.1-cp27-cp27m-win_amd64.whl", hash = "sha256:b1ae48d87f10d1384e5beecd169c77502fcc04a2c00a4c02b85f0a94b419e5f9"},
+    {file = "Pillow-6.2.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:a423c2ea001c6265ed28700df056f75e26215fd28c001e93ef4380b0f05f9547"},
+    {file = "Pillow-6.2.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:9f5529fc02009f96ba95bea48870173426879dc19eec49ca8e08cd63ecd82ddb"},
+    {file = "Pillow-6.2.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:5cc901c2ab9409b4b7ac7b5bcc3e86ac14548627062463da0af3b6b7c555a871"},
+    {file = "Pillow-6.2.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c6414f6aad598364aaf81068cabb077894eb88fed99c6a65e6e8217bab62ae7a"},
+    {file = "Pillow-6.2.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:384b12c9aa8ef95558abdcb50aada56d74bc7cc131dd62d28c2d0e4d3aadd573"},
+    {file = "Pillow-6.2.1-cp35-cp35m-win32.whl", hash = "sha256:248cffc168896982f125f5c13e9317c059f74fffdb4152893339f3be62a01340"},
+    {file = "Pillow-6.2.1-cp35-cp35m-win_amd64.whl", hash = "sha256:285edafad9bc60d96978ed24d77cdc0b91dace88e5da8c548ba5937c425bca8b"},
+    {file = "Pillow-6.2.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:846fa202bd7ee0f6215c897a1d33238ef071b50766339186687bd9b7a6d26ac5"},
+    {file = "Pillow-6.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:7ce80c0a65a6ea90ef9c1f63c8593fcd2929448613fc8da0adf3e6bfad669d08"},
+    {file = "Pillow-6.2.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:e0697b826da6c2472bb6488db4c0a7fa8af0d52fa08833ceb3681358914b14e5"},
+    {file = "Pillow-6.2.1-cp36-cp36m-win32.whl", hash = "sha256:047d9473cf68af50ac85f8ee5d5f21a60f849bc17d348da7fc85711287a75031"},
+    {file = "Pillow-6.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:83792cb4e0b5af480588601467c0764242b9a483caea71ef12d22a0d0d6bdce2"},
+    {file = "Pillow-6.2.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:c9e5ffb910b14f090ac9c38599063e354887a5f6d7e6d26795e916b4514f2c1a"},
+    {file = "Pillow-6.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:4deb1d2a45861ae6f0b12ea0a786a03d19d29edcc7e05775b85ec2877cb54c5e"},
+    {file = "Pillow-6.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:0f66dc6c8a3cc319561a633b6aa82c44107f12594643efa37210d8c924fc1c71"},
+    {file = "Pillow-6.2.1-cp37-cp37m-win32.whl", hash = "sha256:59aa2c124df72cc75ed72c8d6005c442d4685691a30c55321e00ed915ad1a291"},
+    {file = "Pillow-6.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:6c1db03e8dff7b9f955a0fb9907eb9ca5da75b5ce056c0c93d33100a35050281"},
+    {file = "Pillow-6.2.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:12c9169c4e8fe0a7329e8658c7e488001f6b4c8e88740e76292c2b857af2e94c"},
+    {file = "Pillow-6.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:27faf0552bf8c260a5cee21a76e031acaea68babb64daf7e8f2e2540745082aa"},
+    {file = "Pillow-6.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:809c19241c14433c5d6135e1b6c72da4e3b56d5c865ad5736ab99af8896b8f41"},
+    {file = "Pillow-6.2.1-cp38-cp38-win32.whl", hash = "sha256:ac4428094b42907aba5879c7c000d01c8278d451a3b7cccd2103e21f6397ea75"},
+    {file = "Pillow-6.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:38950b3a707f6cef09cd3cbb142474357ad1a985ceb44d921bdf7b4647b3e13e"},
+    {file = "Pillow-6.2.1-pp272-pypy_41-win32.whl", hash = "sha256:5a47d2123a9ec86660fe0e8d0ebf0aa6bc6a17edc63f338b73ea20ba11713f12"},
+    {file = "Pillow-6.2.1-pp372-pp372-win32.whl", hash = "sha256:c7be4b8a09852291c3c48d3c25d1b876d2494a0a674980089ac9d5e0d78bd132"},
+    {file = "Pillow-6.2.1.tar.gz", hash = "sha256:bf4e972a88f8841d8fdc6db1a75e0f8d763e66e3754b03006cbc3854d89f1cb1"},
+]
+pluggy = [
+    {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
+    {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
+]
+psycopg2 = [
+    {file = "psycopg2-2.8.4-cp27-cp27m-win32.whl", hash = "sha256:72772181d9bad1fa349792a1e7384dde56742c14af2b9986013eb94a240f005b"},
+    {file = "psycopg2-2.8.4-cp27-cp27m-win_amd64.whl", hash = "sha256:893c11064b347b24ecdd277a094413e1954f8a4e8cdaf7ffbe7ca3db87c103f0"},
+    {file = "psycopg2-2.8.4-cp34-cp34m-win32.whl", hash = "sha256:9ab75e0b2820880ae24b7136c4d230383e07db014456a476d096591172569c38"},
+    {file = "psycopg2-2.8.4-cp34-cp34m-win_amd64.whl", hash = "sha256:b0845e3bdd4aa18dc2f9b6fb78fbd3d9d371ad167fd6d1b7ad01c0a6cdad4fc6"},
+    {file = "psycopg2-2.8.4-cp35-cp35m-win32.whl", hash = "sha256:ef6df7e14698e79c59c7ee7cf94cd62e5b869db369ed4b1b8f7b729ea825712a"},
+    {file = "psycopg2-2.8.4-cp35-cp35m-win_amd64.whl", hash = "sha256:965c4c93e33e6984d8031f74e51227bd755376a9df6993774fd5b6fb3288b1f4"},
+    {file = "psycopg2-2.8.4-cp36-cp36m-win32.whl", hash = "sha256:ed686e5926929887e2c7ae0a700e32c6129abb798b4ad2b846e933de21508151"},
+    {file = "psycopg2-2.8.4-cp36-cp36m-win_amd64.whl", hash = "sha256:dca2d7203f0dfce8ea4b3efd668f8ea65cd2b35112638e488a4c12594015f67b"},
+    {file = "psycopg2-2.8.4-cp37-cp37m-win32.whl", hash = "sha256:8396be6e5ff844282d4d49b81631772f80dabae5658d432202faf101f5283b7c"},
+    {file = "psycopg2-2.8.4-cp37-cp37m-win_amd64.whl", hash = "sha256:47fc642bf6f427805daf52d6e52619fe0637648fe27017062d898f3bf891419d"},
+    {file = "psycopg2-2.8.4-cp38-cp38-win32.whl", hash = "sha256:4212ca404c4445dc5746c0d68db27d2cbfb87b523fe233dc84ecd24062e35677"},
+    {file = "psycopg2-2.8.4-cp38-cp38-win_amd64.whl", hash = "sha256:92a07dfd4d7c325dd177548c4134052d4842222833576c8391aab6f74038fc3f"},
+    {file = "psycopg2-2.8.4.tar.gz", hash = "sha256:f898e5cc0a662a9e12bde6f931263a1bbd350cfb18e1d5336a12927851825bb6"},
+]
+py = [
+    {file = "py-1.8.0-py2.py3-none-any.whl", hash = "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa"},
+    {file = "py-1.8.0.tar.gz", hash = "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53"},
+]
+pyasn1 = [
+    {file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"},
+    {file = "pyasn1-0.4.8-py2.5.egg", hash = "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf"},
+    {file = "pyasn1-0.4.8-py2.6.egg", hash = "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00"},
+    {file = "pyasn1-0.4.8-py2.7.egg", hash = "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8"},
+    {file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"},
+    {file = "pyasn1-0.4.8-py3.1.egg", hash = "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86"},
+    {file = "pyasn1-0.4.8-py3.2.egg", hash = "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7"},
+    {file = "pyasn1-0.4.8-py3.3.egg", hash = "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576"},
+    {file = "pyasn1-0.4.8-py3.4.egg", hash = "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12"},
+    {file = "pyasn1-0.4.8-py3.5.egg", hash = "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2"},
+    {file = "pyasn1-0.4.8-py3.6.egg", hash = "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359"},
+    {file = "pyasn1-0.4.8-py3.7.egg", hash = "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776"},
+    {file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"},
+]
+pyasn1-modules = [
+    {file = "pyasn1-modules-0.2.7.tar.gz", hash = "sha256:0c35a52e00b672f832e5846826f1fb7507907f7d52fba6faa9e3c4cbe874fe4b"},
+    {file = "pyasn1_modules-0.2.7-py2.4.egg", hash = "sha256:233f55c840e821e76828262db976ac894b285909d22d060c2bdb522e7bf28cc6"},
+    {file = "pyasn1_modules-0.2.7-py2.5.egg", hash = "sha256:9ca5e376a6d9dee35bb3a62608dfa2e6698798aa6b8db3c7afd0eb31af0d63c7"},
+    {file = "pyasn1_modules-0.2.7-py2.6.egg", hash = "sha256:27581362b4253b9c999882a64df974124cde12be0bf2c04148a0d68bc6bbb7b8"},
+    {file = "pyasn1_modules-0.2.7-py2.7.egg", hash = "sha256:13a6955947d8a554de78fc305a4d651f20fb5580b88612a5f0661d4f189d27ac"},
+    {file = "pyasn1_modules-0.2.7-py2.py3-none-any.whl", hash = "sha256:b6ada4f840fe51abf5a6bd545b45bf537bea62221fa0dde2e8a553ed9f06a4e3"},
+    {file = "pyasn1_modules-0.2.7-py3.1.egg", hash = "sha256:9b972f81f59d896cebb9ebb1d44296f1acb28bf7869443c37551f4eed8d74f83"},
+    {file = "pyasn1_modules-0.2.7-py3.2.egg", hash = "sha256:64f6aecf26e93f6a3ba3725b4eb9f532551747d7a63ca9ff43aef12f4bf11eac"},
+    {file = "pyasn1_modules-0.2.7-py3.3.egg", hash = "sha256:33c220a2701032261a23eea6e9881404ac6fc7ff96f183b5353fea8fc8962547"},
+    {file = "pyasn1_modules-0.2.7-py3.4.egg", hash = "sha256:24d54188cb7abd750e0a2cba61b7b46a75608175a0c3c1b1eee08322915d8d21"},
+    {file = "pyasn1_modules-0.2.7-py3.5.egg", hash = "sha256:7b4edf07ca2f759d7cf693184be09f22e067c2eb52b03c770d0a2e9de1c67dfd"},
+    {file = "pyasn1_modules-0.2.7-py3.6.egg", hash = "sha256:eaf35047a0b068e3e0c2a99618b13b65c98c329661daa78c9d44a4ef0fe8139e"},
+    {file = "pyasn1_modules-0.2.7-py3.7.egg", hash = "sha256:c14b107a67ee36a7f183ae9f4803ffde4a03b67f3192eab0a62e851af71371d3"},
+]
+pycodestyle = [
+    {file = "pycodestyle-2.5.0-py2.py3-none-any.whl", hash = "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56"},
+    {file = "pycodestyle-2.5.0.tar.gz", hash = "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"},
+]
+pycryptodome = [
+    {file = "pycryptodome-3.9.4-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:6c2720696b10ae356040e888bde1239b8957fe18885ccf5e7b4e8dec882f0856"},
+    {file = "pycryptodome-3.9.4-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:5c485ed6e9718ebcaa81138fa70ace9c563d202b56a8cee119b4085b023931f5"},
+    {file = "pycryptodome-3.9.4-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:56fdd0e425f1b8fd3a00b6d96351f86226674974814c50534864d0124d48871f"},
+    {file = "pycryptodome-3.9.4-cp27-cp27m-win32.whl", hash = "sha256:2de33ed0a95855735d5a0fc0c39603314df9e78ee8bbf0baa9692fb46b3b8bbb"},
+    {file = "pycryptodome-3.9.4-cp27-cp27m-win_amd64.whl", hash = "sha256:eec0689509389f19875f66ae8dedd59f982240cdab31b9f78a8dc266011df93a"},
+    {file = "pycryptodome-3.9.4-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:68fab8455efcbfe87c5d75015476f9b606227ffe244d57bfd66269451706e899"},
+    {file = "pycryptodome-3.9.4-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:4b9533d4166ca07abdd49ce9d516666b1df944997fe135d4b21ac376aa624aff"},
+    {file = "pycryptodome-3.9.4-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:d3fe3f33ad52bf0c19ee6344b695ba44ffbfa16f3c29ca61116b48d97bd970fb"},
+    {file = "pycryptodome-3.9.4-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:319e568baf86620b419d53063b18c216abf924875966efdfe06891b987196a45"},
+    {file = "pycryptodome-3.9.4-cp34-cp34m-win32.whl", hash = "sha256:042ae873baadd0c33b4d699a5c5b976ade3233a979d972f98ca82314632d868c"},
+    {file = "pycryptodome-3.9.4-cp34-cp34m-win_amd64.whl", hash = "sha256:a30f501bbb32e01a49ef9e09ca1260e5ab49bf33a257080ec553e08997acc487"},
+    {file = "pycryptodome-3.9.4-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:b55c60c321ac91945c60a40ac9896ac7a3d432bb3e8c14006dfd82ad5871c331"},
+    {file = "pycryptodome-3.9.4-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:9d9945ac8375d5d8e60bd2a2e1df5882eaa315522eedf3ca868b1546dfa34eba"},
+    {file = "pycryptodome-3.9.4-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:4372ec7518727172e1605c0843cdc5375d4771e447b8148c787b860260aae151"},
+    {file = "pycryptodome-3.9.4-cp35-cp35m-win32.whl", hash = "sha256:0502876279772b1384b660ccc91563d04490d562799d8e2e06b411e2d81128a9"},
+    {file = "pycryptodome-3.9.4-cp35-cp35m-win_amd64.whl", hash = "sha256:72166c2ac520a5dbd2d90208b9c279161ec0861662a621892bd52fb6ca13ab91"},
+    {file = "pycryptodome-3.9.4-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:b4af098f2a50f8d048ab12cabb59456585c0acf43d90ee79782d2d6d0ed59dba"},
+    {file = "pycryptodome-3.9.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:8a799bea3c6617736e914a2e77c409f52893d382f619f088f8a80e2e21f573c1"},
+    {file = "pycryptodome-3.9.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:7c52308ac5b834331b2f107a490b2c27de024a229b61df4cdc5c131d563dfe98"},
+    {file = "pycryptodome-3.9.4-cp36-cp36m-win32.whl", hash = "sha256:63c103a22cbe9752f6ea9f1a0de129995bad91c4d03a66c67cffcf6ee0c9f1e1"},
+    {file = "pycryptodome-3.9.4-cp36-cp36m-win_amd64.whl", hash = "sha256:54456cf85130e01674d21fb1ab89ffccacb138a8ade88d72fa2b0ac898d2798b"},
+    {file = "pycryptodome-3.9.4-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:aec4d42deb836b8fb3ba32f2ba1ef0d33dd3dc9d430b1479ee7a914490d15b5e"},
+    {file = "pycryptodome-3.9.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:896e9b6fd0762aa07b203c993fbbee7a1f1a4674c6886afd7bfa86f3d1be98a8"},
+    {file = "pycryptodome-3.9.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:57b1b707363490c495ad0eeb38bd1b0e1697c497af25fad78d3a1ebf0477fd5b"},
+    {file = "pycryptodome-3.9.4-cp37-cp37m-win32.whl", hash = "sha256:87d8d85b4792ca5e730fb7a519fbc3ed976c59dcf79c5204589c59afd56b9926"},
+    {file = "pycryptodome-3.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:e3a79a30d15d9c7c284a7734036ee8abdb5ca3a6f5774d293cdc9e1358c1dc10"},
+    {file = "pycryptodome-3.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:48821950ffb9c836858d8fa09d7840b6df52eadd387a3c5acece55cb387743f9"},
+    {file = "pycryptodome-3.9.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cbfd97f9e060f0d30245cd29fa267a9a84de9da97559366fca0a3f7655acc63f"},
+    {file = "pycryptodome-3.9.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9ef966c727de942de3e41aa8462c4b7b4bca70f19af5a3f99e31376589c11aac"},
+    {file = "pycryptodome-3.9.4-cp38-cp38-win32.whl", hash = "sha256:a8ca2450394d3699c9f15ef25e8de9a24b401933716a1e39d37fa01f5fe3c58b"},
+    {file = "pycryptodome-3.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:c53348358408d94869059e16fba5ff3bef8c52c25b18421472aba272b9bb450f"},
+    {file = "pycryptodome-3.9.4.tar.gz", hash = "sha256:a168e73879619b467072509a223282a02c8047d932a48b74fbd498f27224aa04"},
+]
+pydocstyle = [
+    {file = "pydocstyle-5.0.1-py3-none-any.whl", hash = "sha256:4167fe954b8f27ebbbef2fbcf73c6e8ad1e7bb31488fce44a69fdfc4b0cd0fae"},
+    {file = "pydocstyle-5.0.1.tar.gz", hash = "sha256:a0de36e549125d0a16a72a8c8c6c9ba267750656e72e466e994c222f1b6e92cb"},
+]
+pyflakes = [
+    {file = "pyflakes-2.1.1-py2.py3-none-any.whl", hash = "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0"},
+    {file = "pyflakes-2.1.1.tar.gz", hash = "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"},
+]
+pygments = [
+    {file = "Pygments-2.5.2-py2.py3-none-any.whl", hash = "sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b"},
+    {file = "Pygments-2.5.2.tar.gz", hash = "sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe"},
+]
+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.6-py2.py3-none-any.whl", hash = "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"},
+    {file = "pyparsing-2.4.6.tar.gz", hash = "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f"},
+]
+pytest = [
+    {file = "pytest-5.3.2-py3-none-any.whl", hash = "sha256:e41d489ff43948babd0fad7ad5e49b8735d5d55e26628a58673c39ff61d95de4"},
+    {file = "pytest-5.3.2.tar.gz", hash = "sha256:6b571215b5a790f9b41f19f3531c53a45cf6bb8ef2988bc1ff9afb38270b25fa"},
+]
+pytest-cov = [
+    {file = "pytest-cov-2.8.1.tar.gz", hash = "sha256:cc6742d8bac45070217169f5f72ceee1e0e55b0221f54bcf24845972d3a47f2b"},
+    {file = "pytest_cov-2.8.1-py2.py3-none-any.whl", hash = "sha256:cdbdef4f870408ebdbfeb44e63e07eb18bb4619fae852f6e760645fa36172626"},
+]
+pytest-django = [
+    {file = "pytest-django-3.7.0.tar.gz", hash = "sha256:17592f06d51c2ef4b7a0fb24aa32c8b6998506a03c8439606cb96db160106659"},
+    {file = "pytest_django-3.7.0-py2.py3-none-any.whl", hash = "sha256:ef3d15b35ed7e46293475e6f282e71a53bcd8c6f87bdc5d5e7ad0f4d49352127"},
+]
+pytest-django-testing-postgresql = [
+    {file = "pytest-django-testing-postgresql-0.1.post0.tar.gz", hash = "sha256:78b0c58930084cb4393407b2e5a2a3b8734c627b841ecef7d62d39bbfb8e8a45"},
+    {file = "pytest_django_testing_postgresql-0.1.post0-py3-none-any.whl", hash = "sha256:78e52e3d1b0ef5f906d5d69247dd6ac7dfb10d840bd81abab92f3f8c30872cd3"},
+]
+pytest-sugar = [
+    {file = "pytest-sugar-0.9.2.tar.gz", hash = "sha256:fcd87a74b2bce5386d244b49ad60549bfbc4602527797fac167da147983f58ab"},
+    {file = "pytest_sugar-0.9.2-py2.py3-none-any.whl", hash = "sha256:26cf8289fe10880cbbc130bd77398c4e6a8b936d8393b116a5c16121d95ab283"},
+]
+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"},
+]
+python-dateutil = [
+    {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"},
+    {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"},
+]
+python-dotenv = [
+    {file = "python-dotenv-0.10.3.tar.gz", hash = "sha256:f157d71d5fec9d4bd5f51c82746b6344dffa680ee85217c123f4a0c8117c4544"},
+    {file = "python_dotenv-0.10.3-py2.py3-none-any.whl", hash = "sha256:debd928b49dbc2bf68040566f55cdb3252458036464806f4094487244e2a4093"},
+]
+python-ldap = [
+    {file = "python-ldap-3.2.0.tar.gz", hash = "sha256:7d1c4b15375a533564aad3d3deade789221e450052b21ebb9720fb822eccdb8e"},
+]
+python-memcached = [
+    {file = "python-memcached-1.59.tar.gz", hash = "sha256:a2e28637be13ee0bf1a8b6843e7490f9456fd3f2a4cb60471733c7b5d5557e4f"},
+    {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"},
+]
+pyyaml = [
+    {file = "PyYAML-5.2-cp27-cp27m-win32.whl", hash = "sha256:35ace9b4147848cafac3db142795ee42deebe9d0dad885ce643928e88daebdcc"},
+    {file = "PyYAML-5.2-cp27-cp27m-win_amd64.whl", hash = "sha256:ebc4ed52dcc93eeebeae5cf5deb2ae4347b3a81c3fa12b0b8c976544829396a4"},
+    {file = "PyYAML-5.2-cp35-cp35m-win32.whl", hash = "sha256:38a4f0d114101c58c0f3a88aeaa44d63efd588845c5a2df5290b73db8f246d15"},
+    {file = "PyYAML-5.2-cp35-cp35m-win_amd64.whl", hash = "sha256:483eb6a33b671408c8529106df3707270bfacb2447bf8ad856a4b4f57f6e3075"},
+    {file = "PyYAML-5.2-cp36-cp36m-win32.whl", hash = "sha256:7f38e35c00e160db592091751d385cd7b3046d6d51f578b29943225178257b31"},
+    {file = "PyYAML-5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:0e7f69397d53155e55d10ff68fdfb2cf630a35e6daf65cf0bdeaf04f127c09dc"},
+    {file = "PyYAML-5.2-cp37-cp37m-win32.whl", hash = "sha256:e4c015484ff0ff197564917b4b4246ca03f411b9bd7f16e02a2f586eb48b6d04"},
+    {file = "PyYAML-5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:4b6be5edb9f6bb73680f5bf4ee08ff25416d1400fbd4535fe0069b2994da07cd"},
+    {file = "PyYAML-5.2-cp38-cp38-win32.whl", hash = "sha256:8100c896ecb361794d8bfdb9c11fce618c7cf83d624d73d5ab38aef3bc82d43f"},
+    {file = "PyYAML-5.2-cp38-cp38-win_amd64.whl", hash = "sha256:2e9f0b7c5914367b0916c3c104a024bb68f269a486b9d04a2e8ac6f6597b7803"},
+    {file = "PyYAML-5.2.tar.gz", hash = "sha256:c0ee8eca2c582d29c3c2ec6e2c4f703d1b7f1fb10bc72317355a746057e7346c"},
+]
+qrcode = [
+    {file = "qrcode-6.1-py2.py3-none-any.whl", hash = "sha256:3996ee560fc39532910603704c82980ff6d4d5d629f9c3f25f34174ce8606cf5"},
+    {file = "qrcode-6.1.tar.gz", hash = "sha256:505253854f607f2abf4d16092c61d4e9d511a3b4392e60bff957a68592b04369"},
+]
+regex = [
+    {file = "regex-2019.12.20-cp27-cp27m-win32.whl", hash = "sha256:7bbbdbada3078dc360d4692a9b28479f569db7fc7f304b668787afc9feb38ec8"},
+    {file = "regex-2019.12.20-cp27-cp27m-win_amd64.whl", hash = "sha256:a83049eb717ae828ced9cf607845929efcb086a001fc8af93ff15c50012a5716"},
+    {file = "regex-2019.12.20-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:27d1bd20d334f50b7ef078eba0f0756a640fd25f5f1708d3b5bed18a5d6bced9"},
+    {file = "regex-2019.12.20-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:1768cf42a78a11dae63152685e7a1d90af7a8d71d2d4f6d2387edea53a9e0588"},
+    {file = "regex-2019.12.20-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:4850c78b53acf664a6578bba0e9ebeaf2807bb476c14ec7e0f936f2015133cae"},
+    {file = "regex-2019.12.20-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:78b3712ec529b2a71731fbb10b907b54d9c53a17ca589b42a578bc1e9a2c82ea"},
+    {file = "regex-2019.12.20-cp36-cp36m-win32.whl", hash = "sha256:8d9ef7f6c403e35e73b7fc3cde9f6decdc43b1cb2ff8d058c53b9084bfcb553e"},
+    {file = "regex-2019.12.20-cp36-cp36m-win_amd64.whl", hash = "sha256:faad39fdbe2c2ccda9846cd21581063086330efafa47d87afea4073a08128656"},
+    {file = "regex-2019.12.20-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:adc35d38952e688535980ae2109cad3a109520033642e759f987cf47fe278aa1"},
+    {file = "regex-2019.12.20-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ef0b828a7e22e58e06a1cceddba7b4665c6af8afeb22a0d8083001330572c147"},
+    {file = "regex-2019.12.20-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:0e6cf1e747f383f52a0964452658c04300a9a01e8a89c55ea22813931b580aa8"},
+    {file = "regex-2019.12.20-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:032fdcc03406e1a6485ec09b826eac78732943840c4b29e503b789716f051d8d"},
+    {file = "regex-2019.12.20-cp37-cp37m-win32.whl", hash = "sha256:77ae8d926f38700432807ba293d768ba9e7652df0cbe76df2843b12f80f68885"},
+    {file = "regex-2019.12.20-cp37-cp37m-win_amd64.whl", hash = "sha256:c29a77ad4463f71a506515d9ec3a899ed026b4b015bf43245c919ff36275444b"},
+    {file = "regex-2019.12.20-cp38-cp38-manylinux1_i686.whl", hash = "sha256:57eacd38a5ec40ed7b19a968a9d01c0d977bda55664210be713e750dd7b33540"},
+    {file = "regex-2019.12.20-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:724eb24b92fc5fdc1501a1b4df44a68b9c1dda171c8ef8736799e903fb100f63"},
+    {file = "regex-2019.12.20-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d508875793efdf6bab3d47850df8f40d4040ae9928d9d80864c1768d6aeaf8e3"},
+    {file = "regex-2019.12.20-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:cfd31b3300fefa5eecb2fe596c6dee1b91b3a05ece9d5cfd2631afebf6c6fadd"},
+    {file = "regex-2019.12.20-cp38-cp38-win32.whl", hash = "sha256:29b20f66f2e044aafba86ecf10a84e611b4667643c42baa004247f5dfef4f90b"},
+    {file = "regex-2019.12.20-cp38-cp38-win_amd64.whl", hash = "sha256:d3ee0b035816e0520fac928de31b6572106f0d75597f6fa3206969a02baba06f"},
+    {file = "regex-2019.12.20.tar.gz", hash = "sha256:106e25a841921d8259dcef2a42786caae35bc750fb996f830065b3dfaa67b77e"},
+]
+requests = [
+    {file = "requests-2.22.0-py2.py3-none-any.whl", hash = "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"},
+    {file = "requests-2.22.0.tar.gz", hash = "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4"},
+]
+restructuredtext-lint = [
+    {file = "restructuredtext_lint-1.3.0.tar.gz", hash = "sha256:97b3da356d5b3a8514d8f1f9098febd8b41463bed6a1d9f126cf0a048b6fd908"},
+]
+safety = [
+    {file = "safety-1.8.5-py2.py3-none-any.whl", hash = "sha256:0a3a8a178a9c96242b224f033ee8d1d130c0448b0e6622d12deaf37f6c3b4e59"},
+    {file = "safety-1.8.5.tar.gz", hash = "sha256:5059f3ffab3648330548ea9c7403405bbfaf085b11235770825d14c58f24cb78"},
+]
+scramp = [
+    {file = "scramp-1.1.0-py3-none-any.whl", hash = "sha256:e09d2a9be5adeb94cbeb56fc54a61fc5f5b6e140e679b2b60d1f7a8d6478d906"},
+    {file = "scramp-1.1.0.tar.gz", hash = "sha256:475aa6296deb2737b86e9df9098e8eca0f30c8ad1cc0a8adadb99ef012a5ceba"},
+]
+selenium = [
+    {file = "selenium-3.141.0-py2.py3-none-any.whl", hash = "sha256:2d7131d7bc5a5b99a2d9b04aaf2612c411b03b8ca1b1ee8d3de5845a9be2cb3c"},
+    {file = "selenium-3.141.0.tar.gz", hash = "sha256:deaf32b60ad91a4611b98d8002757f29e6f2c2d5fcaf202e1c9ad06d6772300d"},
+]
+six = [
+    {file = "six-1.13.0-py2.py3-none-any.whl", hash = "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd"},
+    {file = "six-1.13.0.tar.gz", hash = "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66"},
+]
+smmap2 = [
+    {file = "smmap2-2.0.5-py2.py3-none-any.whl", hash = "sha256:0555a7bf4df71d1ef4218e4807bbf9b201f910174e6e08af2e138d4e517b4dde"},
+    {file = "smmap2-2.0.5.tar.gz", hash = "sha256:29a9ffa0497e7f2be94ca0ed1ca1aa3cd4cf25a1f6b4f5f87f74b46ed91d609a"},
+]
+snowballstemmer = [
+    {file = "snowballstemmer-2.0.0-py2.py3-none-any.whl", hash = "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0"},
+    {file = "snowballstemmer-2.0.0.tar.gz", hash = "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"},
+]
+soupsieve = [
+    {file = "soupsieve-1.9.5-py2.py3-none-any.whl", hash = "sha256:bdb0d917b03a1369ce964056fc195cfdff8819c40de04695a80bc813c3cfa1f5"},
+    {file = "soupsieve-1.9.5.tar.gz", hash = "sha256:e2c1c5dee4a1c36bcb790e0fabd5492d874b8ebd4617622c4f6a731701060dda"},
+]
+sphinx = [
+    {file = "Sphinx-2.3.1-py3-none-any.whl", hash = "sha256:298537cb3234578b2d954ff18c5608468229e116a9757af3b831c2b2b4819159"},
+    {file = "Sphinx-2.3.1.tar.gz", hash = "sha256:e6e766b74f85f37a5f3e0773a1e1be8db3fcb799deb58ca6d18b70b0b44542a5"},
+]
+sphinx-autodoc-typehints = [
+    {file = "sphinx-autodoc-typehints-1.10.3.tar.gz", hash = "sha256:a6b3180167479aca2c4d1ed3b5cb044a70a76cccd6b38662d39288ebd9f0dff0"},
+    {file = "sphinx_autodoc_typehints-1.10.3-py3-none-any.whl", hash = "sha256:27c9e6ef4f4451766ab8d08b2d8520933b97beb21c913f3df9ab2e59b56e6c6c"},
+]
+sphinxcontrib-applehelp = [
+    {file = "sphinxcontrib-applehelp-1.0.1.tar.gz", hash = "sha256:edaa0ab2b2bc74403149cb0209d6775c96de797dfd5b5e2a71981309efab3897"},
+    {file = "sphinxcontrib_applehelp-1.0.1-py2.py3-none-any.whl", hash = "sha256:fb8dee85af95e5c30c91f10e7eb3c8967308518e0f7488a2828ef7bc191d0d5d"},
+]
+sphinxcontrib-devhelp = [
+    {file = "sphinxcontrib-devhelp-1.0.1.tar.gz", hash = "sha256:6c64b077937330a9128a4da74586e8c2130262f014689b4b89e2d08ee7294a34"},
+    {file = "sphinxcontrib_devhelp-1.0.1-py2.py3-none-any.whl", hash = "sha256:9512ecb00a2b0821a146736b39f7aeb90759834b07e81e8cc23a9c70bacb9981"},
+]
+sphinxcontrib-django = [
+    {file = "sphinxcontrib-django-0.5.tar.gz", hash = "sha256:95831d5d58f780010b0255f298ea03d6bbf971c0f94f631268abd4320400b7db"},
+    {file = "sphinxcontrib_django-0.5-py2.py3-none-any.whl", hash = "sha256:f5bb52d20b64b51087b44247789adaebfc51fa2fad71bfed42c4ca6c05723838"},
+]
+sphinxcontrib-htmlhelp = [
+    {file = "sphinxcontrib-htmlhelp-1.0.2.tar.gz", hash = "sha256:4670f99f8951bd78cd4ad2ab962f798f5618b17675c35c5ac3b2132a14ea8422"},
+    {file = "sphinxcontrib_htmlhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:d4fd39a65a625c9df86d7fa8a2d9f3cd8299a3a4b15db63b50aac9e161d8eff7"},
+]
+sphinxcontrib-jsmath = [
+    {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"},
+    {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"},
+]
+sphinxcontrib-qthelp = [
+    {file = "sphinxcontrib-qthelp-1.0.2.tar.gz", hash = "sha256:79465ce11ae5694ff165becda529a600c754f4bc459778778c7017374d4d406f"},
+    {file = "sphinxcontrib_qthelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:513049b93031beb1f57d4daea74068a4feb77aa5630f856fcff2e50de14e9a20"},
+]
+sphinxcontrib-serializinghtml = [
+    {file = "sphinxcontrib-serializinghtml-1.1.3.tar.gz", hash = "sha256:c0efb33f8052c04fd7a26c0a07f1678e8512e0faec19f4aa8f2473a8b81d5227"},
+    {file = "sphinxcontrib_serializinghtml-1.1.3-py2.py3-none-any.whl", hash = "sha256:db6615af393650bf1151a6cd39120c29abaf93cc60db8c48eb2dddbfdc3a9768"},
+]
+sqlparse = [
+    {file = "sqlparse-0.3.0-py2.py3-none-any.whl", hash = "sha256:40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177"},
+    {file = "sqlparse-0.3.0.tar.gz", hash = "sha256:7c3dca29c022744e95b547e867cee89f4fce4373f3549ccd8797d8eb52cdb873"},
+]
+stevedore = [
+    {file = "stevedore-1.31.0-py2.py3-none-any.whl", hash = "sha256:01d9f4beecf0fbd070ddb18e5efb10567801ba7ef3ddab0074f54e3cd4e91730"},
+    {file = "stevedore-1.31.0.tar.gz", hash = "sha256:e0739f9739a681c7a1fda76a102b65295e96a144ccdb552f2ae03c5f0abe8a14"},
+]
+termcolor = [
+    {file = "termcolor-1.1.0.tar.gz", hash = "sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b"},
+]
+testfixtures = [
+    {file = "testfixtures-6.10.3-py2.py3-none-any.whl", hash = "sha256:9334f64d4210b734d04abff516d6ddaab7328306a0c4c1268ce4624df51c4f6d"},
+    {file = "testfixtures-6.10.3.tar.gz", hash = "sha256:8f22100d4fb841b958f64e71c8820a32dc46f57d4d7e077777b932acd87b7327"},
+]
+"testing.common.database" = [
+    {file = "testing.common.database-2.0.3-py2.py3-none-any.whl", hash = "sha256:e3ed492bf480a87f271f74c53b262caf5d85c8bc09989a8f534fa2283ec52492"},
+    {file = "testing.common.database-2.0.3.tar.gz", hash = "sha256:965d80b2985315325dc358c3061b174a712f4d4d5bf6a80b58b11f9a1dd86d73"},
+]
+"testing.postgresql" = [
+    {file = "testing.postgresql-1.3.0-py2.py3-none-any.whl", hash = "sha256:1b41daeb98dfc8cd4a584bb91e8f5f4ab182993870f95257afe5f1ba6151a598"},
+    {file = "testing.postgresql-1.3.0.tar.gz", hash = "sha256:8e1a69760369a7a8ffe63a66b6d95a5cd82db2fb976e4a8f85ffd24fbfc447d8"},
+]
+text-unidecode = [
+    {file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"},
+    {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"},
+]
+toml = [
+    {file = "toml-0.10.0-py2.7.egg", hash = "sha256:f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"},
+    {file = "toml-0.10.0-py2.py3-none-any.whl", hash = "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"},
+    {file = "toml-0.10.0.tar.gz", hash = "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c"},
+]
+tqdm = [
+    {file = "tqdm-4.41.0-py2.py3-none-any.whl", hash = "sha256:9036904496bd2afacf836a6f206c5a766ce11d3e9319d54a4e794c0f34b111dc"},
+    {file = "tqdm-4.41.0.tar.gz", hash = "sha256:166a82cdea964ae45528e0cc89436255ff2be73dc848bdf239f13c501cae5dc7"},
+]
+twilio = [
+    {file = "twilio-6.35.1.tar.gz", hash = "sha256:c784e55d150ebeb2ba837afbab7168edfb91db57e77a9da49f2a1892688a1930"},
+]
+typed-ast = [
+    {file = "typed_ast-1.4.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e"},
+    {file = "typed_ast-1.4.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b"},
+    {file = "typed_ast-1.4.0-cp35-cp35m-win32.whl", hash = "sha256:630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4"},
+    {file = "typed_ast-1.4.0-cp35-cp35m-win_amd64.whl", hash = "sha256:ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12"},
+    {file = "typed_ast-1.4.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631"},
+    {file = "typed_ast-1.4.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233"},
+    {file = "typed_ast-1.4.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1"},
+    {file = "typed_ast-1.4.0-cp36-cp36m-win32.whl", hash = "sha256:d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a"},
+    {file = "typed_ast-1.4.0-cp36-cp36m-win_amd64.whl", hash = "sha256:354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c"},
+    {file = "typed_ast-1.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a"},
+    {file = "typed_ast-1.4.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e"},
+    {file = "typed_ast-1.4.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d"},
+    {file = "typed_ast-1.4.0-cp37-cp37m-win32.whl", hash = "sha256:d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36"},
+    {file = "typed_ast-1.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0"},
+    {file = "typed_ast-1.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fdc1c9bbf79510b76408840e009ed65958feba92a88833cdceecff93ae8fff66"},
+    {file = "typed_ast-1.4.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:7954560051331d003b4e2b3eb822d9dd2e376fa4f6d98fee32f452f52dd6ebb2"},
+    {file = "typed_ast-1.4.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:48e5b1e71f25cfdef98b013263a88d7145879fbb2d5185f2a0c79fa7ebbeae47"},
+    {file = "typed_ast-1.4.0-cp38-cp38-win32.whl", hash = "sha256:1170afa46a3799e18b4c977777ce137bb53c7485379d9706af8a59f2ea1aa161"},
+    {file = "typed_ast-1.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:838997f4310012cf2e1ad3803bce2f3402e9ffb71ded61b5ee22617b3a7f6b6e"},
+    {file = "typed_ast-1.4.0.tar.gz", hash = "sha256:66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34"},
+]
+typing-extensions = [
+    {file = "typing_extensions-3.7.4.1-py2-none-any.whl", hash = "sha256:910f4656f54de5993ad9304959ce9bb903f90aadc7c67a0bef07e678014e892d"},
+    {file = "typing_extensions-3.7.4.1-py3-none-any.whl", hash = "sha256:cf8b63fedea4d89bab840ecbb93e75578af28f76f66c35889bd7065f5af88575"},
+    {file = "typing_extensions-3.7.4.1.tar.gz", hash = "sha256:091ecc894d5e908ac75209f10d5b4f118fbdb2eb1ede6a63544054bb1edb41f2"},
+]
+urllib3 = [
+    {file = "urllib3-1.25.7-py2.py3-none-any.whl", hash = "sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293"},
+    {file = "urllib3-1.25.7.tar.gz", hash = "sha256:f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745"},
+]
+wcwidth = [
+    {file = "wcwidth-0.1.7-py2.py3-none-any.whl", hash = "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"},
+    {file = "wcwidth-0.1.7.tar.gz", hash = "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e"},
+]
+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"},
+]
+zipp = [
+    {file = "zipp-0.6.0-py2.py3-none-any.whl", hash = "sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335"},
+    {file = "zipp-0.6.0.tar.gz", hash = "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e"},
+]
diff --git a/pyproject.toml b/pyproject.toml
index 93c9ddd9143721f55a77f4487b6abd10ebfbb6e2..4980fae106a5eeef3d033e7f6df446588920b1c6 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -23,25 +23,23 @@ python = "^3.7"
 Django = "^3.0"
 django-any-js = "^1.0"
 django-bootstrap4 = "^1.0"
-django-fa = "^1.0"
 django-debug-toolbar = "^2.0"
 django-easy-audit = "^1.1"
 django-middleware-global-request = "^0.1.2"
 django-menu-generator = "^1.0.4"
 django-tables2 = "^2.1"
 Pillow = "^6.1"
-django-phonenumber-field = {version = "^3.0", extras = ["phonenumbers"]}
+django-phonenumber-field = {version = ">=3.0, <5.0", extras = ["phonenumbers"]}
 django-sass-processor = "^0.7.3"
 libsass = "^0.19.2"
 colour = "^0.1.5"
 dynaconf = {version = "^2.0", extras = ["yaml", "toml", "ini"]}
 django-settings-context-processor = "^0.2"
 django-auth-ldap = { version = "^2.0", optional = true }
-django-maintenance-mode = "^0.13.3"
+django-maintenance-mode = "^0.14.0"
 django-ipware = "^2.1"
 easy-thumbnails = "^2.6"
 django-image-cropping = "^1.2"
-django-contact-form = "^1.7"
 django-impersonate = "^1.4"
 python-memcached = "^1.59"
 django-dbbackup = "^3.2"
@@ -50,27 +48,46 @@ django-hattori = "^0.2"
 psycopg2 = "^2.8"
 django_select2 = "^7.1"
 requests = "^2.22"
-django-two-factor-auth = { git = "https://github.com/Bouke/django-two-factor-auth", rev = "bf9d0812ab11320a6cadc6709c382a03184f2e31", extras = [ "YubiKey", "phonenumbers", "Call", "SMS" ] }
+django-two-factor-auth = { version = "^1.10.0", extras = [ "YubiKey", "phonenumbers", "Call", "SMS" ] }
 django-yarnpkg = "^6.0"
-django-dbsettings = "^0.11.0"
+django-dbsettings = "^1.0.0"
 
 [tool.poetry.extras]
+app-alsijil = ['BiscuIT-App-Alsijil']
+app-chronos = ['BiscuIT-App-Chronos']
+app-exlibris = ['BiscuIT-App-Exlibris']
+app-schild-nrw = ['BiscuIT-App-SchILD-NRW']
+app-untis = ['BiscuIT-App-Untis']
 ldap = ["django-auth-ldap"]
 
 [tool.poetry.dev-dependencies]
 sphinx = "^2.1"
 sphinxcontrib-django = "^0.5.0"
 sphinx-autodoc-typehints = "^1.7"
-autopep8 = "^1.4"
-pylama = "^7.7"
-radon = "^3.0"
-mypy = "^0.720.0"
 django-stubs = "^1.1"
 pytest = "^5.3"
 pytest-django = "^3.7"
 pytest-django-testing-postgresql = "^0.1"
-tox = "^3.14"
-pytest-cov = "^2.8"
+selenium = "^3.141.0"
+safety = "^1.8.5"
+flake8 = "^3.7.9"
+flake8-django = "^0.0.4"
+flake8-fixme = "^1.1.1"
+flake8-mypy = "^17.8.0"
+flake8-bandit = "^2.1.2"
+flake8-builtins = "^1.4.1"
+flake8-docstrings = "^1.5.0"
+flake8-rst-docstrings = "^0.0.12"
+black = "^19.10b0"
+flake8-black = "^0.1.1"
+isort = "^4.3.21"
+flake8-isort = "^2.8.0"
+pytest-cov = "^2.8.1"
+pytest-sugar = "^0.9.2"
+
+[tool.black]
+line-length = 100
+exclude = "/migrations/"
 
 [build-system]
 requires = ["poetry>=0.12"]
diff --git a/renovate.json b/renovate.json
new file mode 100644
index 0000000000000000000000000000000000000000..0967ef424bce6791893e9a57bb952f80fd536e93
--- /dev/null
+++ b/renovate.json
@@ -0,0 +1 @@
+{}
diff --git a/tox.ini b/tox.ini
index defacd8dd0cd0f2a60820b8c881ecc512d804421..3671faf24d8700820474a1a41023468e076463d5 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,32 +1,81 @@
 [tox]
 skipsdist = True
+skip_missing_interpreters = true
+envlist = py37,py38
 
 [testenv]
 whitelist_externals = poetry
-    pytest
 skip_install = true
-commands = pytest --pylama --cov=biscuit biscuit/core/
+envdir = {toxworkdir}/globalenv
+commands_pre = ./dev.sh install-all
+commands =
+    poetry run python manage.py compilemessages
+    poetry run python manage.py yarn install
+    poetry run python manage.py collectstatic --no-input --clear
+    poetry run pytest --cov=. {posargs} biscuit/ apps/official/
 
-[pylama]
-linters = pycodestyle,pyflakes,radon
+[testenv:selenium]
+setenv =
+    TEST_SCREENSHOT_PATH = {env:TEST_SCREENSHOT_PATH:.tox/screenshots}
+    TEST_SELENIUM_HUB = {env:TEST_SELENIUM_HUB:http://127.0.0.1:4444/wd/hub}
+    TEST_SELENIUM_BROWSERS = {env:TEST_SELENIUM_BROWSERS:chrome,firefox}
+    TEST_HOST = {env:TEST_HOST:172.17.0.1}
 
-[pycodestyle]
+[testenv:lint]
+commands =
+    - poetry run black --check --diff biscuit/ apps/official/
+    - poetry run isort -c --diff --stdout -rc biscuit/ apps/official/
+    poetry run flake8 {posargs} biscuit/ apps/official/
+
+[testenv:security]
+commands = poetry run safety check --full-report
+
+[testenv:build]
+commands_pre =
+commands = poetry build
+
+[testenv:docs]
+commands = poetry run make -C docs/ html {posargs}
+
+[testenv:reformat]
+commands =
+    poetry run isort -rc biscuit/ apps/official/
+    poetry run black biscuit/ apps/official/
+
+[flake8]
 max_line_length = 100
-ignore = E501
+exclude = migrations,tests
+ignore = E203,E231,W503
 
-[autopep8]
-jobs = 0
-aggressive = true
+[isort]
+line_length = 100
+multi_line_output = 3
+include_trailing_comma = 1
+use_parantheses = 1
+default_section = THIRDPARTY
+known_first_party = biscuit
+known_django = django
+sections = FUTURE,STDLIB,DJANGO,THIRDPARTY,FIRSTPARTY,LOCALFOLDER
 
 [mypy]
 plugins = mypy_django_plugin.main
+python_version = 3.8
+platform = linux
+show_column_numbers = True
+follow_imports = skip
+ignore_missing_imports = True
+cache_dir = /dev/null
 
 [mypy.plugins.django-stubs]
 django_settings_module = biscuit.core.settings
 
 [pytest]
 DJANGO_SETTINGS_MODULE = biscuit.core.settings
+junit_family = legacy
 
 [coverage:run]
-omit = */migrations/*
+omit =
+    */migrations/*
     */tests/*
+    .tox/*
+    manage.py