diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 7e79081953846c2db12c3c9e2a91656e5126759b..955750958d170e723e9714295750d0e1204e878f 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,7 +1,9 @@
 include:
-    - local: "/ci/general.yml"
-    - local: "/ci/test.yml"
-    - local: "/ci/build_dist.yml"
-    - local: "/ci/build_docker.yml"
-    - local: "/ci/pages.yml"
-    - local: "/ci/deploy.yml"
+    - project: "AlekSIS/official/AlekSIS"
+      file: /ci/general.yml
+    - project: "AlekSIS/official/AlekSIS"
+      file: /ci/test.yml
+    - project: "AlekSIS/official/AlekSIS"
+      file: /ci/build_dist.yml
+    - project: "AlekSIS/official/AlekSIS"
+      file: /ci/deploy_pypi.yml
diff --git a/.gitmodules b/.gitmodules
index a7fa3c3c15f5415169a0f2b0ce67876831caf5f7..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,20 +0,0 @@
-[submodule "apps/official/AlekSIS-App-Chronos"]
-	path = apps/official/AlekSIS-App-Chronos
-	url = https://edugit.org/AlekSIS/Official/AlekSIS-App-Chronos
-	ignore = untracked
-[submodule "apps/official/AlekSIS-App-DashboardFeeds"]
-	path = apps/official/AlekSIS-App-DashboardFeeds
-	url = https://edugit.org/AlekSIS/Official/AlekSIS-App-DashboardFeeds
-	ignore = untracked
-[submodule "apps/official/AlekSIS-App-LDAP"]
-	path = apps/official/AlekSIS-App-LDAP
-	url = https://edugit.org/AlekSIS/official/AlekSIS-App-LDAP
-	ignore = untracked
-[submodule "apps/official/AlekSIS-App-Untis"]
-	path = apps/official/AlekSIS-App-Untis
-	url = https://edugit.org/AlekSIS/official/AlekSIS-App-Untis
-	ignore = untracked
-[submodule "apps/official/AlekSIS-App-Hjelp"]
-	path = apps/official/AlekSIS-App-Hjelp
-	url = https://edugit.org/AlekSIS/official/AlekSIS-App-Hjelp
-	ignore = untracked
diff --git a/CODE_OF_CONDUCT.rst b/CODE_OF_CONDUCT.rst
deleted file mode 100644
index 9618c5f7d4285f366f05a5f8b2fd4cef43cf829b..0000000000000000000000000000000000000000
--- a/CODE_OF_CONDUCT.rst
+++ /dev/null
@@ -1,144 +0,0 @@
-Contributor Covenant Code of Conduct
-====================================
-
-Our Pledge
-----------
-
-We as members, contributors, and leaders pledge to make participation in
-our community a harassment-free experience for everyone, regardless of
-age, body size, visible or invisible disability, ethnicity, sex
-characteristics, gender identity and expression, level of experience,
-education, socio-economic status, nationality, personal appearance,
-race, religion, or sexual identity and orientation.
-
-We pledge to act and interact in ways that contribute to an open,
-welcoming, diverse, inclusive, and healthy community.
-
-Our Standards
--------------
-
-Examples of behavior that contributes to a positive environment for our
-community include:
-
--  Demonstrating empathy and kindness toward other people
--  Being respectful of differing opinions, viewpoints, and experiences
--  Giving and gracefully accepting constructive feedback
--  Accepting responsibility and apologizing to those affected by our
-   mistakes, and learning from the experience
--  Focusing on what is best not just for us as individuals, but for the
-   overall community
-
-Examples of unacceptable behavior include:
-
--  The use of sexualized language or imagery, and sexual attention or
-   advances of any kind
--  Trolling, insulting or derogatory comments, and personal or political
-   attacks
--  Public or private harassment
--  Publishing others’ private information, such as a physical or email
-   address, without their explicit permission
--  Other conduct which could reasonably be considered inappropriate in a
-   professional setting
-
-Enforcement Responsibilities
-----------------------------
-
-Community leaders are responsible for clarifying and enforcing our
-standards of acceptable behavior and will take appropriate and fair
-corrective action in response to any behavior that they deem
-inappropriate, threatening, offensive, or harmful.
-
-Community leaders have the right and responsibility to remove, edit, or
-reject comments, commits, code, wiki edits, issues, and other
-contributions that are not aligned to this Code of Conduct, and will
-communicate reasons for moderation decisions when appropriate.
-
-Scope
------
-
-This Code of Conduct applies within all community spaces, and also
-applies when an individual is officially representing the community in
-public spaces. Examples of representing our community include using an
-official e-mail address, posting via an official social media account,
-or acting as an appointed representative at an online or offline event.
-
-Enforcement
------------
-
-Instances of abusive, harassing, or otherwise unacceptable behavior may
-be reported to the community leaders responsible for enforcement at
-foss@teckids.org. All complaints will be reviewed and investigated
-promptly and fairly.
-
-All community leaders are obligated to respect the privacy and security
-of the reporter of any incident.
-
-Enforcement Guidelines
-----------------------
-
-Community leaders will follow these Community Impact Guidelines in
-determining the consequences for any action they deem in violation of
-this Code of Conduct:
-
-1. Correction
-~~~~~~~~~~~~~
-
-**Community Impact**: Use of inappropriate language or other behavior
-deemed unprofessional or unwelcome in the community.
-
-**Consequence**: A private, written warning from community leaders,
-providing clarity around the nature of the violation and an explanation
-of why the behavior was inappropriate. A public apology may be
-requested.
-
-2. Warning
-~~~~~~~~~~
-
-**Community Impact**: A violation through a single incident or series of
-actions.
-
-**Consequence**: A warning with consequences for continued behavior. No
-interaction with the people involved, including unsolicited interaction
-with those enforcing the Code of Conduct, for a specified period of
-time. This includes avoiding interactions in community spaces as well as
-external channels like social media. Violating these terms may lead to a
-temporary or permanent ban.
-
-3. Temporary Ban
-~~~~~~~~~~~~~~~~
-
-**Community Impact**: A serious violation of community standards,
-including sustained inappropriate behavior.
-
-**Consequence**: A temporary ban from any sort of interaction or public
-communication with the community for a specified period of time. No
-public or private interaction with the people involved, including
-unsolicited interaction with those enforcing the Code of Conduct, is
-allowed during this period. Violating these terms may lead to a
-permanent ban.
-
-4. Permanent Ban
-~~~~~~~~~~~~~~~~
-
-**Community Impact**: Demonstrating a pattern of violation of community
-standards, including sustained inappropriate behavior, harassment of an
-individual, or aggression toward or disparagement of classes of
-individuals.
-
-**Consequence**: A permanent ban from any sort of public interaction
-within the project community.
-
-Attribution
------------
-
-This Code of Conduct is adapted from the `Contributor
-Covenant <https://www.contributor-covenant.org>`__, version 2.0,
-available at
-https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
-
-Community Impact Guidelines were inspired by `Mozilla’s code of conduct
-enforcement ladder <https://github.com/mozilla/diversity>`__.
-
-For answers to common questions about this code of conduct, see the FAQ
-at https://www.contributor-covenant.org/faq. Translations are available
-at https://www.contributor-covenant.org/translations.
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
deleted file mode 100644
index be12afc1552dc2b6d6cb398bc2a456392ef21b9d..0000000000000000000000000000000000000000
--- a/CONTRIBUTING.rst
+++ /dev/null
@@ -1,232 +0,0 @@
-Development principles and contribution guidelines
-==================================================
-
-In order to create a high-quality software product, the AlekSIS developers
-have agreed upon fundamental principles governing the code layout, coding
-style and repository management for AlekSIS and all official apps.
-
-
-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 and the Django style guide:
-
-  1. Standard library imports
-  2. Django imports
-  3. Third-party imports
-  4. Imports from AlekSIS core and other apps (absolute imports)
-  5. Imports from the same AlekSIS app (realtive imports)
-
-  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
-
-Text documents
-~~~~~~~~~~~~~~
-
-If there is no objective reason against it, all text documents accompanying
-the source use `reStructuredText`_.
-
-
-Working with the Git repository
--------------------------------
-
-The Git repository shall be used as a historic documentation of development
-and as change management. It is important that the Git commit history
-describes what was changed, by whom and why.
-
-Help and information on Git for beginners are available in the `Git guide`_
-
-Feature and issue branches
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-All features and bug fixes should be developed in their own branch and later
-merged into the master branch as a whole. Of course, sometimes, it is
-sensible to not do that, e.g. for fixing mere typos and the like.
-
-Within the feature branch, every logical step should be commited separately.
-It is neither required nor desired to do micro-commits about every
-development step. The commit history should describe the trains of thought
-the design and implementation is based on.
-
-If you work on multiple issues at the same time, you have to change between
-branches. Never work on unrelated issues in the same branch.
-
-Branches should either contain the number and title of the related issue (as
-generated by GitLab), or follow the naming convention type/name, where type
-is one of bugfix, feature, or refactor.
-
-All changes on the code should be commited and pushed before stopping work on
-in order to prevent data loss. If a logical step is continued later, you
-should amend and force-push the commit.
-
-Issue branches should be rebased onto the current master regularly to avoid
-merge conflicts.
-
-Commit messages
-~~~~~~~~~~~~~~~
-
-Commit messages should be written as described in `How to Write a Git Commit
-Message`_.
-
-Commit messages should mention or even close any related issues. For merely
-mentioning progress on an issue, use the keyword `advances`; for closing an
-issue, use `closes`; for referring to a related issue for informational
-purposes, use `cf.`. This should be done in the body of the commit message.
-
-The subject of a commit message can (and should) be prepended with a tag in
-square brackets if it relates to a certain part of the repository, e.g. [CI]
-when changing CI/CD configuration or support code, [Dev] when changing
-something in the development utilities, etc.
-
-Example::
-
-  Solve LDAP connection problems
-
-  - Add the ldap-with-unicorn-dust dependency
-  - Configure settings.py to accept the correct groups from LDAP
-
-  Closes #10.
-
-Merge Requests
-~~~~~~~~~~~~~~
-
-If you think that the work on your feature branch is finished, you have to
-create a merge request on EduGit in order to let other developers and the
-maintainers take a look at it.
-
-See below on how to submit patches if you cannot use the development
-platform.
-
-Manifestos governing development
---------------------------------
-
-The FOSS community has created some manifestos describing several aspects of
-software development, to agree upon a baseline for these aspects. The
-AlekSIS developers have agreed to adhere to the following manifestos:
-
-- The `Sane software manifesto`_
-- The `Accessibility Manifesto`_
-- The `User Data Manifesto`_
-
-Not all theses from these manifestos are applicable. For example, most data
-about persons in a school information system are dictated by the school and
-probably governed by laws defining what and when to store. In that case,
-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
-AlekSIS project explicitly welcomes implementing support for
-interoperability with non-free services.
-
-However, to purposefully 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.
-
-
-Documentation
--------------
-
-The documentation in the AlekSIS project shall consist of three layers.
-
-Source code comments
-~~~~~~~~~~~~~~~~~~~~
-
-The parts of your code that are not self-explaining have to be commented.
-Ideally, source code is self-explaining, in the sense that its logical
-structure, naming of variables, and the like makes it easy to read and
-understand, for a reasonably talented programmer, to follow what it does.
-
-Docstrings
-~~~~~~~~~~
-
-All functions, methods, classes and modules that are newly added (or changed
-extensively) must contain a docstring for other developers to understand
-what it does. Docstrings of public elements will be included in the
-developer documentation.
-
-Sphinx documentation
-~~~~~~~~~~~~~~~~~~~~
-
-In addition to that you should document the function or the way the app
-works in the project documentation (`docs/` directory). Use that especially
-for functionality which is shared by your app for other apps (public APIs).
-
-Your Sphinx documentation should contain what the API can and shall be sued
-for, and how other apps can benefit from it.
-
-When creating a new app, also include documentation about it targeted at
-administrators and users.  At least you have to document what new developers
-and users have to do in order to get a working instance of the app.
-
-Sphinx documentation for all official apps will be published together.
-
-
-Contributing to upstream
-------------------------
-
-If possible and reasonable, code that can be of use to others in the general
-Django ecosystem shall be contributed to any upstream dependency, or a new
-generalised upstream dependency be created, under the most permissive
-licence possible.
-
-
-How to contact the team
------------------------
-
-Development platform
-~~~~~~~~~~~~~~~~~~~~
-
-Main development of AlekSIS is done on the `EduGit`_ platform in the
-`AlekSIS group`_ and discussions are held on the linked `Mattermost team`_.
-
-All platforms and tools mandated for development are free software and
-freely usable. EduGit accepts a variety of sources for login, so
-contributors are free to decide where they want to register in order to
-participate.
-
-If any contributor cannot use the platforms for whatever reasons, patches and
-questions directed at the developers can also be e-mailed to
-<aleksis-dev@lists.teckids.org>.
-
-
-.. _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
-.. _Git guide: https://rogerdudler.github.io/git-guide/
-.. _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
-.. _EduGit: https://edugit.org/
-.. _AlekSIS group: https://edugit.org/AlekSIS/
-.. _Mattermost team: https://mattermost.edugit.org/biscuit/
diff --git a/Dockerfile b/Dockerfile
deleted file mode 100644
index 0c4f3b52ededb87de3ea36f7814d155aaa20026d..0000000000000000000000000000000000000000
--- a/Dockerfile
+++ /dev/null
@@ -1,97 +0,0 @@
-FROM python:3.8-buster AS core
-
-# Configure Python to be nice inside Docker and pip to stfu
-ENV PYTHONUNBUFFERED 1
-ENV PYTHONDONTWRITEBYTECODE 1
-ENV PIP_DEFAULT_TIMEOUT 100
-ENV PIP_DISABLE_PIP_VERSION_CHECK 1
-ENV PIP_NO_CACHE_DIR 1
-
-# Configure app settings for build and runtime
-ENV ALEKSIS_static__root /usr/share/aleksis/static
-ENV ALEKSIS_media__root /var/lib/aleksis/media
-ENV ALEKSIS_backup__location /var/lib/aleksis/backups
-
-# Install necessary Debian packages for build and runtime
-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 \
-	libpq-dev \
-	libssl-dev \
-	netcat-openbsd \
-	yarnpkg
-
-# Install core
-WORKDIR /usr/src/app
-COPY LICENCE.rst README.rst manage.py poetry.lock pyproject.toml ./
-COPY aleksis ./aleksis/
-RUN set -e; \
-    mkdir -p /var/lib/aleksis/media /usr/share/aleksis/static /var/lib/aleksis/backups; \
-    eatmydata pip install poetry; \
-    poetry config virtualenvs.create false; \
-    eatmydata poetry install; \
-    eatmydata pip install gunicorn django-compressor
-
-# Declare a persistent volume for all data
-VOLUME /var/lib/aleksis
-
-# Define entrypoint and gunicorn running on port 8000
-EXPOSE 8000
-COPY docker/entrypoint.sh /usr/local/bin/
-ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
-
-# Install core extras
-FROM core AS core-extras
-ARG EXTRA_LDAP=1
-ARG EXTRA_CELERY=1
-WORKDIR /usr/src/app
-
-# LDAP
-RUN   if [ $EXTRA_LDAP = 1 ] ; then \
-        eatmydata apt-get install -y --no-install-recommends \
-        libldap2-dev \
-        libsasl2-dev \
-        ldap-utils; \
-        eatmydata poetry install -E ldap; \
-        fi;
-
-# Celery
-RUN   if [ $EXTRA_CELERY = 1 ] ; then \
-        eatmydata poetry install -E celery; \
-        fi;
-
-# Install official apps
-FROM core-extras AS apps
-COPY apps ./apps/
-RUN set -e; \
-    for d in apps/official/*; do \
-        cd $d; \
-        rm -rf .git; \
-        poetry install; \
-        cd ../../..; \
-    done
-
-# Build messages and assets
-FROM apps as assets
-RUN eatmydata python manage.py compilemessages && \
-    eatmydata python manage.py yarn install && \
-    eatmydata python manage.py collectstatic --no-input --clear
-
-# Clean up build dependencies
-FROM assets AS clean
-RUN set -e; \
-    eatmydata apt-get remove --purge -y \
-        build-essential \
-        gettext \
-        libpq-dev \
-        libssl-dev \
-        yarnpkg; \
-    eatmydata apt-get autoremove --purge -y; \
-    apt-get clean -y; \
-    eatmydata pip uninstall -y poetry; \
-    rm -f /var/lib/apt/lists/*_*; \
-    rm -rf /root/.cache
diff --git a/README.rst b/README.rst
index e5d0251f38d496da608ba3c99526b31678378caf..664d44feccd825af419d30650fa31a27e97234c2 100644
--- a/README.rst
+++ b/README.rst
@@ -1,38 +1,16 @@
-AlekSIS — All-libre extensible kit for school information systems
-=================================================================
+AlekSIS (School Information System) — Core (Core functionality and app framework)
+=================================================================================
 
-Warning
--------
-
-**This is an alpha version of AlekSIS, the free school information system.
-The AlekSIS team is looking for schools who want to help shape the 2.0
-final release and supports interested schools in operating AlekSIS.**
-
-What AlekSIS is
-----------------
-
-`AlekSIS`_ is a web-based school information system (SIS) which can be used to
-manage and/or publish organisational subjects of educational institutions.
-
-Formerly two separate projects (BiscuIT and SchoolApps), developed by
-`Teckids e.V.`_ and a team of students at `Katharineum zu Lübeck`_, they
-were merged into the AlekSIS project in 2020.
+AlekSIS standard distribution
+-----------------------------
 
-AlekSIS is a platform based on Django, that provides central funstions
-and data structures that can be used by apps that are developed and provided
-seperately. The AlekSIS team also maintains a set of official apps which
-make AlekSIS a fully-featured software solutions for the information
-management needs of schools.
+The AlekSIS standard distribution with information about all official apps
+can be found on `EduGit`_.
 
-By design, the platform can be used by schools to write their own apps for
-specific needs they face, also in coding classes. Students are empowered to
-create real-world applications that bring direct value to their environment.
+Features
+--------
 
-AlekSIS is part of the `schul-frei`_ project as a component in sustainable
-educational networks.
-
-Core features
---------------
+The AlekSIS-Core currently provides the following features:
 
 * For users:
 
@@ -53,24 +31,6 @@ Core features
  * Authentication via LDAP
  * Automatic backup of database, static and media files
 
-Official apps
--------------
-
-+--------------------------------------+---------------------------------------------------------------------------------------------+
-| App name                             | Purpose                                                                                     |
-+======================================+=============================================================================================+
-| `AlekSIS-App-Chronos`_               | The Chronos app provides functionality for digital timetables.                              |
-+--------------------------------------+---------------------------------------------------------------------------------------------+
-| `AlekSIS-App-DashboardFeeds`_        | The DashboardFeeds app provides functionality to add RSS or Atom feeds to dashboard         |
-+--------------------------------------+---------------------------------------------------------------------------------------------+
-| `AlekSIS-App-Hjelp`_                 | The Hjelp app provides functionality for aiding users.                                      |
-+--------------------------------------+---------------------------------------------------------------------------------------------+
-| `AlekSIS-App-LDAP`_                  | The LDAP app provides functionality to import users and groups from LDAP                    |
-+--------------------------------------+---------------------------------------------------------------------------------------------+
-| `AlekSIS-App-Untis`_                 | This app provides import and export functions to interact with Untis, a timetable software. |
-+--------------------------------------+---------------------------------------------------------------------------------------------+
-
-
 Licence
 -------
 
@@ -91,13 +51,6 @@ full licence text or on the `European Union Public Licence`_ website
 https://joinup.ec.europa.eu/collection/eupl/guidelines-users-and-developers
 (including all other official language versions).
 
-.. _AlekSIS: https://aleksis.org/
-.. _Teckids e.V.: https://www.teckids.org/
-.. _Katharineum zu Lübeck: https://www.katharineum.de/
+.. _AlekSIS: https://edugit.org/AlekSIS/Official/AlekSIS
 .. _European Union Public Licence: https://eupl.eu/
-.. _schul-frei: https://schul-frei.org/
-.. _AlekSIS-App-Chronos: https://edugit.org/AlekSIS/official/AlekSIS-App-Chronos
-.. _AlekSIS-App-DashboardFeeds: https://edugit.org/AlekSIS/official/AlekSIS-App-DashboardFeeds
-.. _AlekSIS-App-Hjelp: https://edugit.org/AlekSIS/official/AlekSIS-App-Hjelp
-.. _AlekSIS-App-LDAP: https://edugit.org/AlekSIS/official/AlekSIS-App-LDAP
-.. _AlekSIS-App-Untis: https://edugit.org/AlekSIS/official/AlekSIS-App-Untis
+.. _EduGit: https://edugit.org/AlekSIS/official/AlekSIS
diff --git a/aleksis/core/apps.py b/aleksis/core/apps.py
index e627b0a2e6b93a6e661cdceaef21ec824e4daedb..e88d812bde928fcdc2f38027b8323aa8f09ad07b 100644
--- a/aleksis/core/apps.py
+++ b/aleksis/core/apps.py
@@ -2,7 +2,7 @@ from typing import Any, List, Optional, Tuple
 
 import django.apps
 from django.conf import settings
-from django.db import ProgrammingError
+from django.db import OperationalError, ProgrammingError
 from django.http import HttpRequest
 from django.utils.module_loading import autodiscover_modules
 
@@ -67,7 +67,7 @@ class CoreConfig(AppConfig):
             for backend in get_site_preferences()["auth__backends"]:
                 settings._wrapped.AUTHENTICATION_BACKENDS.insert(idx, backend)
                 idx += 1
-        except ProgrammingError:
+        except (ProgrammingError, OperationalError):
             pass
 
     def preference_updated(
diff --git a/aleksis/core/forms.py b/aleksis/core/forms.py
index e1c0be7ef4f28e609c93f3b58208aa0ea4d58b3a..f4599d4d0e13ac02d01fda147331a3997d0f8dd0 100644
--- a/aleksis/core/forms.py
+++ b/aleksis/core/forms.py
@@ -5,12 +5,20 @@ from django.contrib.auth import get_user_model
 from django.core.exceptions import ValidationError
 from django.utils.translation import gettext_lazy as _
 
-from django_select2.forms import ModelSelect2MultipleWidget, Select2Widget
+from django_select2.forms import ModelSelect2MultipleWidget, ModelSelect2Widget, Select2Widget
 from dynamic_preferences.forms import PreferenceForm
 from material import Fieldset, Layout, Row
 
 from .mixins import ExtensibleForm, SchoolTermRelatedExtensibleForm
-from .models import AdditionalField, Announcement, Group, GroupType, Person, SchoolTerm
+from .models import (
+    AdditionalField,
+    Announcement,
+    DashboardWidget,
+    Group,
+    GroupType,
+    Person,
+    SchoolTerm,
+)
 from .registries import (
     group_preferences_registry,
     person_preferences_registry,
@@ -24,7 +32,7 @@ class PersonAccountForm(forms.ModelForm):
     class Meta:
         model = Person
         fields = ["last_name", "first_name", "user"]
-        widgets = {"user": Select2Widget}
+        widgets = {"user": Select2Widget(attrs={"class": "browser-default"})}
 
     new_user = forms.CharField(required=False)
 
@@ -107,7 +115,19 @@ class EditPersonForm(ExtensibleForm):
             "primary_group",
         ]
         widgets = {
-            "user": Select2Widget,
+            "user": Select2Widget(attrs={"class": "browser-default"}),
+            "primary_group": ModelSelect2Widget(
+                search_fields=["name__icontains", "short_name__icontains"],
+                attrs={"data-minimum-input-length": 0, "class": "browser-default"},
+            ),
+            "guardians": ModelSelect2MultipleWidget(
+                search_fields=[
+                    "first_name__icontains",
+                    "last_name__icontains",
+                    "short_name__icontains",
+                ],
+                attrs={"data-minimum-input-length": 0, "class": "browser-default"},
+            ),
         }
 
     new_user = forms.CharField(
@@ -138,19 +158,25 @@ class EditGroupForm(SchoolTermRelatedExtensibleForm):
                     "first_name__icontains",
                     "last_name__icontains",
                     "short_name__icontains",
-                ]
+                ],
+                attrs={"data-minimum-input-length": 0, "class": "browser-default"},
             ),
             "owners": ModelSelect2MultipleWidget(
                 search_fields=[
                     "first_name__icontains",
                     "last_name__icontains",
                     "short_name__icontains",
-                ]
+                ],
+                attrs={"data-minimum-input-length": 0, "class": "browser-default"},
             ),
             "parent_groups": ModelSelect2MultipleWidget(
-                search_fields=["name__icontains", "short_name__icontains"]
+                search_fields=["name__icontains", "short_name__icontains"],
+                attrs={"data-minimum-input-length": 0, "class": "browser-default"},
+            ),
+            "additional_fields": ModelSelect2MultipleWidget(
+                search_fields=["title__icontains",],
+                attrs={"data-minimum-input-length": 0, "class": "browser-default"},
             ),
-            "additional_fields": ModelSelect2MultipleWidget(search_fields=["title__icontains",]),
         }
 
 
@@ -167,9 +193,27 @@ class AnnouncementForm(ExtensibleForm):
     valid_until_time = forms.TimeField(label=_("Time"))
 
     persons = forms.ModelMultipleChoiceField(
-        Person.objects.all(), label=_("Persons"), required=False
+        queryset=Person.objects.all(),
+        label=_("Persons"),
+        required=False,
+        widget=ModelSelect2MultipleWidget(
+            search_fields=[
+                "first_name__icontains",
+                "last_name__icontains",
+                "short_name__icontains",
+            ],
+            attrs={"data-minimum-input-length": 0, "class": "browser-default"},
+        ),
+    )
+    groups = forms.ModelMultipleChoiceField(
+        queryset=None,
+        label=_("Groups"),
+        required=False,
+        widget=ModelSelect2MultipleWidget(
+            search_fields=["name__icontains", "short_name__icontains",],
+            attrs={"data-minimum-input-length": 0, "class": "browser-default"},
+        ),
     )
-    groups = forms.ModelMultipleChoiceField(queryset=None, label=_("Groups"), required=False)
 
     layout = Layout(
         Fieldset(
@@ -309,3 +353,20 @@ class SchoolTermForm(ExtensibleForm):
     class Meta:
         model = SchoolTerm
         exclude = []
+
+
+class DashboardWidgetOrderForm(ExtensibleForm):
+    pk = forms.ModelChoiceField(
+        queryset=DashboardWidget.objects.all(),
+        widget=forms.HiddenInput(attrs={"class": "pk-input"}),
+    )
+    order = forms.IntegerField(initial=0, widget=forms.HiddenInput(attrs={"class": "order-input"}))
+
+    class Meta:
+        model = DashboardWidget
+        fields = []
+
+
+DashboardWidgetOrderFormSet = forms.formset_factory(
+    form=DashboardWidgetOrderForm, max_num=0, extra=0
+)
diff --git a/aleksis/core/locale/ar/LC_MESSAGES/django.po b/aleksis/core/locale/ar/LC_MESSAGES/django.po
index 92ad8782664c5f8ec329b96d4054bdc057596d40..a3e781c6b389c7e3c6814fe9491358e6810b1b41 100644
--- a/aleksis/core/locale/ar/LC_MESSAGES/django.po
+++ b/aleksis/core/locale/ar/LC_MESSAGES/django.po
@@ -1506,8 +1506,8 @@ msgstr ""
 msgid ""
 "\n"
 "        To start using a token generator, please use your\n"
-"        smartphone to scan the QR code below. For example, use Google\n"
-"        Authenticator. Then, enter the token generated by the app.\n"
+"        favourite two factor authentication (TOTP) app to scan the QR code below.\n"
+"        Then, enter the token generated by the app.\n"
 "      "
 msgstr ""
 
diff --git a/aleksis/core/locale/de_DE/LC_MESSAGES/django.po b/aleksis/core/locale/de_DE/LC_MESSAGES/django.po
index e59806834f65d4acced6f3adea79b24ccf3b2f3f..6f6d8fc83f732fbd2abdedbb8f4793e2591c1ca1 100644
--- a/aleksis/core/locale/de_DE/LC_MESSAGES/django.po
+++ b/aleksis/core/locale/de_DE/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgstr ""
 "Project-Id-Version: AlekSIS (School Information System) 0.1\n"
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2020-08-02 16:29+0200\n"
-"PO-Revision-Date: 2020-08-02 15:00+0000\n"
+"PO-Revision-Date: 2020-12-19 12:57+0000\n"
 "Last-Translator: Jonathan Weth <teckids@jonathanweth.de>\n"
 "Language-Team: German <https://translate.edugit.org/projects/aleksis/aleksis/"
 "de/>\n"
@@ -17,7 +17,7 @@ msgstr ""
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=2; plural=n != 1;\n"
-"X-Generator: Weblate 4.0.1\n"
+"X-Generator: Weblate 4.3.2\n"
 
 #: filters.py:37 templates/core/base.html:77 templates/core/group/list.html:20
 #: templates/core/person/list.html:24 templates/search/search.html:7
@@ -412,7 +412,7 @@ msgstr "Kann Kindgruppen zu Gruppen zuordnen"
 
 #: models.py:330
 msgid "Long name"
-msgstr "Langer Name"
+msgstr "Langname"
 
 #: models.py:340 templates/core/group/full.html:65
 msgid "Members"
@@ -1655,14 +1655,14 @@ msgstr ""
 msgid ""
 "\n"
 "        To start using a token generator, please use your\n"
-"        smartphone to scan the QR code below. For example, use Google\n"
-"        Authenticator. Then, enter the token generated by the app.\n"
+"        favourite two factor authentication (TOTP) app to scan the QR code below.\n"
+"        Then, enter the token generated by the app.\n"
 "      "
 msgstr ""
 "\n"
-"        Um mit dem Codegenerator zu starten, benutzen Sie bitte Ihr Smartphone,\n"
-"um diesen QR-Code zu scannen (z. B. den Google Authenticator). Dann geben Sie \n"
-"den in der App angezeigten Code an:\n"
+"        Um mit dem Codegenerator zu starten, benutzen Sie bitte Ihre\n"
+"App für Zwei-Faktor-Authentifizierung (TOTP), um diesen QR-Code zu scannen.\n"
+"Dann geben Sie den in der App angezeigten Code an:\n"
 "      "
 
 #: templates/two_factor/core/setup.html:34
diff --git a/aleksis/core/locale/fr/LC_MESSAGES/django.po b/aleksis/core/locale/fr/LC_MESSAGES/django.po
index d800a36f8aa7db3f7279e2ddfc496e3a4f10ff8f..a8556727d98887123c05abb853a0bd46e829ead4 100644
--- a/aleksis/core/locale/fr/LC_MESSAGES/django.po
+++ b/aleksis/core/locale/fr/LC_MESSAGES/django.po
@@ -1558,8 +1558,8 @@ msgstr ""
 msgid ""
 "\n"
 "        To start using a token generator, please use your\n"
-"        smartphone to scan the QR code below. For example, use Google\n"
-"        Authenticator. Then, enter the token generated by the app.\n"
+"        favourite two factor authentication (TOTP) app to scan the QR code below.\n"
+"        Then, enter the token generated by the app.\n"
 "      "
 msgstr ""
 
diff --git a/aleksis/core/locale/la/LC_MESSAGES/django.po b/aleksis/core/locale/la/LC_MESSAGES/django.po
index 0fe692359d53218a555763890f363f7c9ed9e382..6fa38602c123109a48b5f34b07415b76a666f5a5 100644
--- a/aleksis/core/locale/la/LC_MESSAGES/django.po
+++ b/aleksis/core/locale/la/LC_MESSAGES/django.po
@@ -8,27 +8,26 @@ msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2020-08-02 16:29+0200\n"
-"PO-Revision-Date: 2020-04-27 13:03+0000\n"
+"PO-Revision-Date: 2020-12-19 12:57+0000\n"
 "Last-Translator: Julian <leuckerj@gmail.com>\n"
-"Language-Team: Latin <https://translate.edugit.org/projects/aleksis/aleksis/la/>\n"
+"Language-Team: Latin <https://translate.edugit.org/projects/aleksis/aleksis/"
+"la/>\n"
 "Language: la\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=2; plural=n != 1;\n"
-"X-Generator: Weblate 4.0.1\n"
+"X-Generator: Weblate 4.3.2\n"
 
 #: filters.py:37 templates/core/base.html:77 templates/core/group/list.html:20
 #: templates/core/person/list.html:24 templates/search/search.html:7
 #: templates/search/search.html:22
 msgid "Search"
-msgstr ""
+msgstr "Quaerere"
 
 #: filters.py:53
-#, fuzzy
-#| msgid "Short name"
 msgid "Search by name"
-msgstr "Breve nomen"
+msgstr "Quaerere cum breve nomine"
 
 #: filters.py:65
 #, fuzzy
@@ -249,7 +248,7 @@ msgstr ""
 
 #: models.py:36
 msgid "Date and time"
-msgstr ""
+msgstr "Dies et hora"
 
 #: models.py:37
 msgid "Decimal number"
@@ -1631,8 +1630,8 @@ msgstr ""
 msgid ""
 "\n"
 "        To start using a token generator, please use your\n"
-"        smartphone to scan the QR code below. For example, use Google\n"
-"        Authenticator. Then, enter the token generated by the app.\n"
+"        favourite two factor authentication (TOTP) app to scan the QR code below.\n"
+"        Then, enter the token generated by the app.\n"
 "      "
 msgstr ""
 
diff --git a/aleksis/core/locale/nb_NO/LC_MESSAGES/django.po b/aleksis/core/locale/nb_NO/LC_MESSAGES/django.po
index 6cfc5ee7e1a2e39ea35727fd66d5bd22d8e8b235..5be24e272186dd1985d749b948562bb13f222bc7 100644
--- a/aleksis/core/locale/nb_NO/LC_MESSAGES/django.po
+++ b/aleksis/core/locale/nb_NO/LC_MESSAGES/django.po
@@ -1505,8 +1505,8 @@ msgstr ""
 msgid ""
 "\n"
 "        To start using a token generator, please use your\n"
-"        smartphone to scan the QR code below. For example, use Google\n"
-"        Authenticator. Then, enter the token generated by the app.\n"
+"        favourite two factor authentication (TOTP) app to scan the QR code below.\n"
+"        Then, enter the token generated by the app.\n"
 "      "
 msgstr ""
 
diff --git a/aleksis/core/locale/tr_TR/LC_MESSAGES/django.po b/aleksis/core/locale/tr_TR/LC_MESSAGES/django.po
index d1efaccfa330d6f6989f9baf39bca163cd3cadf2..d0417b32bd8aa0df398290eacf5a23491641abd2 100644
--- a/aleksis/core/locale/tr_TR/LC_MESSAGES/django.po
+++ b/aleksis/core/locale/tr_TR/LC_MESSAGES/django.po
@@ -1505,8 +1505,8 @@ msgstr ""
 msgid ""
 "\n"
 "        To start using a token generator, please use your\n"
-"        smartphone to scan the QR code below. For example, use Google\n"
-"        Authenticator. Then, enter the token generated by the app.\n"
+"        favourite two factor authentication (TOTP) app to scan the QR code below.\n"
+"        Then, enter the token generated by the app.\n"
 "      "
 msgstr ""
 
diff --git a/aleksis/core/menus.py b/aleksis/core/menus.py
index 2e71c357384c1bc7e4ffa4ff1485f4edc4ffc7de..9b1c42e931235333fb8d4a9dca55cd7f977b47de 100644
--- a/aleksis/core/menus.py
+++ b/aleksis/core/menus.py
@@ -93,6 +93,17 @@ MENUS = {
                         ),
                     ],
                 },
+                {
+                    "name": _("Dashboard widgets"),
+                    "url": "dashboard_widgets",
+                    "icon": "dashboard",
+                    "validators": [
+                        (
+                            "aleksis.core.util.predicates.permission_validator",
+                            "core.view_dashboardwidget",
+                        ),
+                    ],
+                },
                 {
                     "name": _("Data management"),
                     "url": "data_management",
diff --git a/aleksis/core/migrations/0001_initial.py b/aleksis/core/migrations/0001_initial.py
index 4dd6486f478a00e23a42876d302bbbe954648084..9999e1a6615138251c1745d4e33b3d9d404d0c7b 100644
--- a/aleksis/core/migrations/0001_initial.py
+++ b/aleksis/core/migrations/0001_initial.py
@@ -8,7 +8,6 @@ import django.contrib.postgres.fields.jsonb
 import django.contrib.sites.managers
 from django.db import migrations, models
 import django.db.models.deletion
-import image_cropping.fields
 import phonenumber_field.modelfields
 
 
@@ -125,8 +124,8 @@ class Migration(migrations.Migration):
                 ('email', models.EmailField(blank=True, max_length=254, verbose_name='E-mail address')),
                 ('date_of_birth', models.DateField(blank=True, null=True, verbose_name='Date of birth')),
                 ('sex', models.CharField(blank=True, choices=[('f', 'female'), ('m', 'male')], max_length=1, verbose_name='Sex')),
-                ('photo', image_cropping.fields.ImageCropField(blank=True, null=True, upload_to='', verbose_name='Photo')),
-                ('photo_cropping', image_cropping.fields.ImageRatioField('photo', '600x800', adapt_rotation=False, allow_fullsize=False, free_crop=False, help_text=None, hide_image_field=False, size_warning=True, verbose_name='photo cropping')),
+                ('photo', models.CharField(blank=True, max_length=1)),
+                ('photo_cropping', models.CharField(blank=True, max_length=1)),
                 ('description', models.TextField(blank=True, verbose_name='Description')),
                 ('guardians', models.ManyToManyField(blank=True, related_name='children', to='core.Person', verbose_name='Guardians / Parents')),
                 ('primary_group', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='core.Group', verbose_name='Primary group')),
diff --git a/aleksis/core/migrations/0004_add_permissions_for_group_stats.py b/aleksis/core/migrations/0004_add_permissions_for_group_stats.py
new file mode 100644
index 0000000000000000000000000000000000000000..ab0e6bf5fbad7af778589c0cf3c64076dfba4fe3
--- /dev/null
+++ b/aleksis/core/migrations/0004_add_permissions_for_group_stats.py
@@ -0,0 +1,22 @@
+# Generated by Django 3.0.7 on 2020-06-28 14:02
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('core', '0003_drop_image_cropping'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='group',
+            options={'ordering': ['short_name', 'name'], 'permissions': (('assign_child_groups_to_groups', 'Can assign child groups to groups'), ('view_group_stats', 'Can view statistics about group.')), 'verbose_name': 'Group', 'verbose_name_plural': 'Groups'},
+        ),
+        migrations.AlterField(
+            model_name='group',
+            name='additional_fields',
+            field=models.ManyToManyField(blank=True, to='core.AdditionalField', verbose_name='Additional fields'),
+        ),
+    ]
diff --git a/aleksis/core/migrations/0005_timestamped_activity_notification.py b/aleksis/core/migrations/0005_timestamped_activity_notification.py
new file mode 100644
index 0000000000000000000000000000000000000000..611c55196dc8c703662380e7c73ca0ae20a42059
--- /dev/null
+++ b/aleksis/core/migrations/0005_timestamped_activity_notification.py
@@ -0,0 +1,35 @@
+# Generated by Django 3.1.3 on 2020-11-16 13:04
+
+from django.db import migrations, models
+import django.utils.timezone
+import model_utils.fields
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('core', '0004_add_permissions_for_group_stats'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='activity',
+            name='created',
+            field=model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created'),
+        ),
+        migrations.AddField(
+            model_name='activity',
+            name='modified',
+            field=model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified'),
+        ),
+        migrations.AddField(
+            model_name='notification',
+            name='created',
+            field=model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created'),
+        ),
+        migrations.AddField(
+            model_name='notification',
+            name='modified',
+            field=model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified'),
+        ),
+    ]
diff --git a/aleksis/core/migrations/0006_dashboard_widget_size.py b/aleksis/core/migrations/0006_dashboard_widget_size.py
new file mode 100644
index 0000000000000000000000000000000000000000..713ff1792ce87e4124025a34561cd602004ea8c7
--- /dev/null
+++ b/aleksis/core/migrations/0006_dashboard_widget_size.py
@@ -0,0 +1,36 @@
+# Generated by Django 3.1.4 on 2020-12-20 15:55
+
+import django.core.validators
+from django.db import migrations, models
+import django.db.models.deletion
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('core', '0005_timestamped_activity_notification'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='dashboardwidget',
+            name='size_l',
+            field=models.PositiveSmallIntegerField(default=6, help_text='> 992 px, 12 columns', validators=[django.core.validators.MaxValueValidator(12)], verbose_name='Size on desktop devices'),
+        ),
+        migrations.AddField(
+            model_name='dashboardwidget',
+            name='size_m',
+            field=models.PositiveSmallIntegerField(default=12, help_text='> 600 px, 12 columns', validators=[django.core.validators.MaxValueValidator(12)], verbose_name='Size on tablet devices'),
+        ),
+        migrations.AddField(
+            model_name='dashboardwidget',
+            name='size_s',
+            field=models.PositiveSmallIntegerField(default=12, help_text='<= 600 px, 12 columns', validators=[django.core.validators.MaxValueValidator(12)], verbose_name='Size on mobile devices'),
+        ),
+        migrations.AddField(
+            model_name='dashboardwidget',
+            name='size_xl',
+            field=models.PositiveSmallIntegerField(default=4, help_text='> 1200 px>, 12 columns', validators=[django.core.validators.MaxValueValidator(12)], verbose_name='Size on large desktop devices'),
+        ),
+    ]
diff --git a/aleksis/core/migrations/0007_dashboard_widget_order.py b/aleksis/core/migrations/0007_dashboard_widget_order.py
new file mode 100644
index 0000000000000000000000000000000000000000..9387f4ca7c0dbb3307d2a3681f091e4bf631f847
--- /dev/null
+++ b/aleksis/core/migrations/0007_dashboard_widget_order.py
@@ -0,0 +1,35 @@
+# Generated by Django 3.1.4 on 2020-12-21 13:38
+
+import django.contrib.sites.managers
+from django.db import migrations, models
+import django.db.models.deletion
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('sites', '0002_alter_domain_unique'),
+        ('core', '0006_dashboard_widget_size'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='DashboardWidgetOrder',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('extended_data', models.JSONField(default=dict, editable=False)),
+                ('order', models.PositiveIntegerField(verbose_name='Order')),
+                ('person', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.person', verbose_name='Person')),
+                ('site', models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.CASCADE, to='sites.site')),
+                ('widget', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.dashboardwidget', verbose_name='Dashboard widget')),
+            ],
+            options={
+                'verbose_name': 'Dashboard widget order',
+                'verbose_name_plural': 'Dashboard widget orders',
+            },
+            managers=[
+                ('objects', django.contrib.sites.managers.CurrentSiteManager()),
+            ],
+        ),
+    ]
diff --git a/aleksis/core/mixins.py b/aleksis/core/mixins.py
index 9926457faa08b33e8ec4c0c23f95dbee664cf9c8..f3edd31338ebf5fce1dd915aed3cdb373c71086a 100644
--- a/aleksis/core/mixins.py
+++ b/aleksis/core/mixins.py
@@ -9,7 +9,7 @@ from django.contrib.contenttypes.models import ContentType
 from django.contrib.sites.managers import CurrentSiteManager
 from django.contrib.sites.models import Site
 from django.db import models
-from django.db.models import QuerySet
+from django.db.models import JSONField, QuerySet
 from django.forms.forms import BaseForm
 from django.forms.models import ModelForm, ModelFormMetaclass
 from django.http import HttpResponse
@@ -19,9 +19,8 @@ from django.views.generic import CreateView, UpdateView
 from django.views.generic.edit import DeleteView, ModelFormMixin
 
 import reversion
-from easyaudit.models import CRUDEvent
 from guardian.admin import GuardedModelAdmin
-from jsonstore.fields import IntegerField, JSONField, JSONFieldMixin
+from jsonstore.fields import IntegerField, JSONFieldMixin
 from material.base import Layout, LayoutNode
 from rules.contrib.admin import ObjectPermissionsModelAdmin
 
@@ -54,8 +53,7 @@ class ExtensibleModel(models.Model, metaclass=_ExtensibleModelBase):
     This base model ensures all objects in AlekSIS apps fulfill the
     following properties:
 
-     * crud_events property to retrieve easyaudit's CRUD event log
-     * created_at and updated_at properties based n CRUD events
+     * `versions` property to retrieve all versions of the model from reversion
      * Allow injection of fields and code from AlekSIS apps to extend
        model functionality.
 
@@ -109,50 +107,30 @@ class ExtensibleModel(models.Model, metaclass=_ExtensibleModelBase):
         pass
 
     @property
-    def crud_events(self) -> QuerySet:
-        """Get all CRUD events connected to this object from easyaudit."""
-        content_type = ContentType.objects.get_for_model(self)
+    def versions(self) -> List[Tuple[str, Tuple[Any, Any]]]:
+        """Get all versions of this object from django-reversion.
 
-        return CRUDEvent.objects.filter(
-            object_id=self.pk, content_type=content_type
-        ).select_related("user", "user__person")
+        Includes diffs to previous version.
+        """
+        versions = reversion.models.Version.objects.get_for_object(self)
 
-    @property
-    def crud_event_create(self) -> Optional[CRUDEvent]:
-        """Return create event of this object."""
-        return self.crud_events.filter(event_type=CRUDEvent.CREATE).latest("datetime")
+        versions_with_changes = []
+        for i, version in enumerate(versions):
+            diff = {}
+            if i > 0:
+                prev_version = versions[i - 1]
 
-    @property
-    def crud_event_update(self) -> Optional[CRUDEvent]:
-        """Return last event of this object."""
-        return self.crud_events.latest("datetime")
+                for k, val in version.field_dict.items():
+                    prev_val = prev_version.field_dict.get(k, None)
+                    if prev_val != val:
+                        diff[k] = (prev_val, val)
 
-    @property
-    def created_at(self) -> Optional[datetime]:
-        """Determine creation timestamp from CRUD log."""
-        if self.crud_event_create:
-            return self.crud_event_create.datetime
+            versions_with_changes.append((version, diff))
 
-    @property
-    def updated_at(self) -> Optional[datetime]:
-        """Determine last timestamp from CRUD log."""
-        if self.crud_event_update:
-            return self.crud_event_update.datetime
+        return versions_with_changes
 
     extended_data = JSONField(default=dict, editable=False)
 
-    @property
-    def created_by(self) -> Optional[models.Model]:
-        """Determine user who created this object from CRUD log."""
-        if self.crud_event_create:
-            return self.crud_event_create.user
-
-    @property
-    def updated_by(self) -> Optional[models.Model]:
-        """Determine user who last updated this object from CRUD log."""
-        if self.crud_event_update:
-            return self.crud_event_update.user
-
     extended_data = JSONField(default=dict, editable=False)
 
     @classmethod
@@ -366,11 +344,11 @@ class SuccessMessageMixin(ModelFormMixin):
         return super().form_valid(form)
 
 
-class AdvancedCreateView(CreateView, SuccessMessageMixin):
+class AdvancedCreateView(SuccessMessageMixin, CreateView):
     pass
 
 
-class AdvancedEditView(UpdateView, SuccessMessageMixin):
+class AdvancedEditView(SuccessMessageMixin, UpdateView):
     pass
 
 
diff --git a/aleksis/core/models.py b/aleksis/core/models.py
index 2231fb36057c2c33edec8d6b4e1384e5693ac331..0f7a80d416cfe1dec30b57f2bf1e070e664a8966 100644
--- a/aleksis/core/models.py
+++ b/aleksis/core/models.py
@@ -9,18 +9,20 @@ from django.contrib.contenttypes.fields import GenericForeignKey
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.sites.models import Site
 from django.core.exceptions import ValidationError
+from django.core.validators import MaxValueValidator
 from django.db import models, transaction
 from django.db.models import QuerySet
 from django.forms.widgets import Media
 from django.urls import reverse
 from django.utils import timezone
-from django.utils.decorators import classproperty
+from django.utils.functional import classproperty
 from django.utils.text import slugify
 from django.utils.translation import gettext_lazy as _
 
 import jsonstore
 from cache_memoize import cache_memoize
 from dynamic_preferences.models import PerInstancePreferenceModel
+from model_utils.models import TimeStampedModel
 from phonenumber_field.modelfields import PhoneNumberField
 from polymorphic.models import PolymorphicModel
 
@@ -221,16 +223,22 @@ class Person(ExtensibleModel):
     @property
     def age(self):
         """Age of the person at current time."""
-        return self.age_at(timezone.datetime.now().date())
+        return self.age_at(timezone.now().date())
 
     def age_at(self, today):
-        """Age of the person at a given date and time."""
-        years = today.year - self.date_of_birth.year
-        if self.date_of_birth.month > today.month or (
-            self.date_of_birth.month == today.month and self.date_of_birth.day > today.day
-        ):
-            years -= 1
-        return years
+        if self.date_of_birth:
+            years = today.year - self.date_of_birth.year
+            if self.date_of_birth.month > today.month or (
+                self.date_of_birth.month == today.month and self.date_of_birth.day > today.day
+            ):
+                years -= 1
+            return years
+
+    @property
+    def dashboard_widgets(self):
+        return [
+            w.widget for w in DashboardWidgetOrder.objects.filter(person=self).order_by("order")
+        ]
 
     def save(self, *args, **kwargs):
         super().save(*args, **kwargs)
@@ -326,7 +334,10 @@ class Group(SchoolTermRelatedExtensibleModel):
         ordering = ["short_name", "name"]
         verbose_name = _("Group")
         verbose_name_plural = _("Groups")
-        permissions = (("assign_child_groups_to_groups", _("Can assign child groups to groups")),)
+        permissions = (
+            ("assign_child_groups_to_groups", _("Can assign child groups to groups")),
+            ("view_group_stats", _("Can view statistics about group.")),
+        )
         constraints = [
             models.UniqueConstraint(fields=["school_term", "name"], name="unique_school_term_name"),
             models.UniqueConstraint(
@@ -380,6 +391,22 @@ class Group(SchoolTermRelatedExtensibleModel):
         """Flat list of all members and owners to fulfill announcement API contract."""
         return list(self.members.all()) + list(self.owners.all())
 
+    @property
+    def get_group_stats(self) -> dict:
+        """ Get stats about a given group """
+        stats = {}
+
+        stats["members"] = len(self.members.all())
+
+        ages = [person.age for person in self.members.filter(date_of_birth__isnull=False)]
+
+        if ages:
+            stats["age_avg"] = sum(ages) / len(ages)
+            stats["age_range_min"] = min(ages)
+            stats["age_range_max"] = max(ages)
+
+        return stats
+
     def __str__(self) -> str:
         if self.school_term:
             return f"{self.name} ({self.short_name}) ({self.school_term})"
@@ -421,7 +448,7 @@ class PersonGroupThrough(ExtensibleModel):
             setattr(self, field_name, field_instance)
 
 
-class Activity(ExtensibleModel):
+class Activity(ExtensibleModel, TimeStampedModel):
     """Activity of a user to trace some actions done in AlekSIS in displayable form."""
 
     user = models.ForeignKey(
@@ -441,7 +468,7 @@ class Activity(ExtensibleModel):
         verbose_name_plural = _("Activities")
 
 
-class Notification(ExtensibleModel):
+class Notification(ExtensibleModel, TimeStampedModel):
     """Notification to submit to a user."""
 
     sender = models.CharField(max_length=100, verbose_name=_("Sender"))
@@ -494,7 +521,7 @@ class AnnouncementQuerySet(models.QuerySet):
 
     def at_time(self, when: Optional[datetime] = None) -> models.QuerySet:
         """Get all announcements at a certain time."""
-        when = when or timezone.datetime.now()
+        when = when or timezone.now()
 
         # Get announcements by time
         announcements = self.filter(valid_from__lte=when, valid_until__gte=when)
@@ -503,7 +530,7 @@ class AnnouncementQuerySet(models.QuerySet):
 
     def on_date(self, when: Optional[date] = None) -> models.QuerySet:
         """Get all announcements at a certain date."""
-        when = when or timezone.datetime.now().date()
+        when = when or timezone.now().date()
 
         # Get announcements by time
         announcements = self.filter(valid_from__date__lte=when, valid_until__date__gte=when)
@@ -542,7 +569,7 @@ class Announcement(ExtensibleModel):
     link = models.URLField(blank=True, verbose_name=_("Link to detailed view"))
 
     valid_from = models.DateTimeField(
-        verbose_name=_("Date and time from when to show"), default=timezone.datetime.now
+        verbose_name=_("Date and time from when to show"), default=timezone.now
     )
     valid_until = models.DateTimeField(
         verbose_name=_("Date and time until when to show"), default=now_tomorrow,
@@ -661,6 +688,31 @@ class DashboardWidget(PolymorphicModel, PureDjangoModel):
     title = models.CharField(max_length=150, verbose_name=_("Widget Title"))
     active = models.BooleanField(verbose_name=_("Activate Widget"))
 
+    size_s = models.PositiveSmallIntegerField(
+        verbose_name=_("Size on mobile devices"),
+        help_text=_("<= 600 px, 12 columns"),
+        validators=[MaxValueValidator(12)],
+        default=12,
+    )
+    size_m = models.PositiveSmallIntegerField(
+        verbose_name=_("Size on tablet devices"),
+        help_text=_("> 600 px, 12 columns"),
+        validators=[MaxValueValidator(12)],
+        default=12,
+    )
+    size_l = models.PositiveSmallIntegerField(
+        verbose_name=_("Size on desktop devices"),
+        help_text=_("> 992 px, 12 columns"),
+        validators=[MaxValueValidator(12)],
+        default=6,
+    )
+    size_xl = models.PositiveSmallIntegerField(
+        verbose_name=_("Size on large desktop devices"),
+        help_text=_("> 1200 px>, 12 columns"),
+        validators=[MaxValueValidator(12)],
+        default=4,
+    )
+
     def get_context(self):
         """Get the context dictionary to pass to the widget template."""
         raise NotImplementedError("A widget subclass needs to implement the get_context method.")
@@ -681,6 +733,18 @@ class DashboardWidget(PolymorphicModel, PureDjangoModel):
         verbose_name_plural = _("Dashboard Widgets")
 
 
+class DashboardWidgetOrder(ExtensibleModel):
+    widget = models.ForeignKey(
+        DashboardWidget, on_delete=models.CASCADE, verbose_name=_("Dashboard widget")
+    )
+    person = models.ForeignKey(Person, on_delete=models.CASCADE, verbose_name=_("Person"))
+    order = models.PositiveIntegerField(verbose_name=_("Order"))
+
+    class Meta:
+        verbose_name = _("Dashboard widget order")
+        verbose_name_plural = _("Dashboard widget orders")
+
+
 class CustomMenu(ExtensibleModel):
     """A custom menu to display in the footer."""
 
diff --git a/aleksis/core/preferences.py b/aleksis/core/preferences.py
index 150ab5f24f0bab113aa852369fc13b56acd2cf74..2f079e295fe57b4cd680208bdc6513966621e199 100644
--- a/aleksis/core/preferences.py
+++ b/aleksis/core/preferences.py
@@ -1,5 +1,6 @@
 from django.conf import settings
 from django.forms import EmailField, ImageField, URLField
+from django.forms.widgets import SelectMultiple
 from django.utils.translation import gettext_lazy as _
 
 from dynamic_preferences.preferences import Section
@@ -22,6 +23,7 @@ notification = Section("notification")
 footer = Section("footer")
 account = Section("account")
 auth = Section("auth", verbose_name=_("Authentication"))
+internationalisation = Section("internationalisation", verbose_name=_("Internationalisation"))
 
 
 @site_preferences_registry.register
@@ -230,3 +232,14 @@ class AuthenticationBackends(MultipleChoicePreference):
 
     def get_choices(self):
         return [(b, b) for b in settings.CUSTOM_AUTHENTICATION_BACKENDS]
+
+
+@site_preferences_registry.register
+class AvailableLanguages(MultipleChoicePreference):
+    section = internationalisation
+    name = "languages"
+    default = [code[0] for code in settings.LANGUAGES]
+    widget = SelectMultiple
+    verbose_name = _("Available languages")
+    field_attribute = {"initial": []}
+    choices = settings.LANGUAGES
diff --git a/aleksis/core/rules.py b/aleksis/core/rules.py
index 35b00f23770f80e3a5ca821393af3bf0956833f8..2e36bc08bf3a307c0b8a116a5731f0f4793f2475 100644
--- a/aleksis/core/rules.py
+++ b/aleksis/core/rules.py
@@ -1,4 +1,4 @@
-from rules import add_perm, always_allow
+import rules
 
 from .models import AdditionalField, Announcement, Group, GroupType, Person
 from .util.predicates import (
@@ -11,32 +11,32 @@ from .util.predicates import (
     is_notification_recipient,
 )
 
-add_perm("core", always_allow)
+rules.add_perm("core", rules.always_allow)
 
 # View dashboard
-add_perm("core.view_dashboard", has_person)
+rules.add_perm("core.view_dashboard", has_person)
 
 # Use search
 search_predicate = has_person & has_global_perm("core.search")
-add_perm("core.search", search_predicate)
+rules.add_perm("core.search", search_predicate)
 
 # View persons
 view_persons_predicate = has_person & (
     has_global_perm("core.view_person") | has_any_object("core.view_person", Person)
 )
-add_perm("core.view_persons", view_persons_predicate)
+rules.add_perm("core.view_persons", view_persons_predicate)
 
 # View person
 view_person_predicate = has_person & (
     has_global_perm("core.view_person") | has_object_perm("core.view_person") | is_current_person
 )
-add_perm("core.view_person", view_person_predicate)
+rules.add_perm("core.view_person", view_person_predicate)
 
 # View person address
 view_address_predicate = has_person & (
     has_global_perm("core.view_address") | has_object_perm("core.view_address") | is_current_person
 )
-add_perm("core.view_address", view_address_predicate)
+rules.add_perm("core.view_address", view_address_predicate)
 
 # View person contact details
 view_contact_details_predicate = has_person & (
@@ -44,13 +44,13 @@ view_contact_details_predicate = has_person & (
     | has_object_perm("core.view_contact_details")
     | is_current_person
 )
-add_perm("core.view_contact_details", view_contact_details_predicate)
+rules.add_perm("core.view_contact_details", view_contact_details_predicate)
 
 # View person photo
 view_photo_predicate = has_person & (
     has_global_perm("core.view_photo") | has_object_perm("core.view_photo") | is_current_person
 )
-add_perm("core.view_photo", view_photo_predicate)
+rules.add_perm("core.view_photo", view_photo_predicate)
 
 # View persons groups
 view_groups_predicate = has_person & (
@@ -58,96 +58,96 @@ view_groups_predicate = has_person & (
     | has_object_perm("core.view_person_groups")
     | is_current_person
 )
-add_perm("core.view_person_groups", view_groups_predicate)
+rules.add_perm("core.view_person_groups", view_groups_predicate)
 
 # Edit person
 edit_person_predicate = has_person & (
     has_global_perm("core.change_person") | has_object_perm("core.change_person")
 )
-add_perm("core.edit_person", edit_person_predicate)
+rules.add_perm("core.edit_person", edit_person_predicate)
 
 # Delete person
 delete_person_predicate = has_person & (
     has_global_perm("core.delete_person") | has_object_perm("core.delete_person")
 )
-add_perm("core.delete_person", delete_person_predicate)
+rules.add_perm("core.delete_person", delete_person_predicate)
 
 # Link persons with accounts
 link_persons_accounts_predicate = has_person & has_global_perm("core.link_persons_accounts")
-add_perm("core.link_persons_accounts", link_persons_accounts_predicate)
+rules.add_perm("core.link_persons_accounts", link_persons_accounts_predicate)
 
 # View groups
 view_groups_predicate = has_person & (
     has_global_perm("core.view_group") | has_any_object("core.view_group", Group)
 )
-add_perm("core.view_groups", view_groups_predicate)
+rules.add_perm("core.view_groups", view_groups_predicate)
 
 # View group
 view_group_predicate = has_person & (
     has_global_perm("core.view_group") | has_object_perm("core.view_group")
 )
-add_perm("core.view_group", view_group_predicate)
+rules.add_perm("core.view_group", view_group_predicate)
 
 # Edit group
 edit_group_predicate = has_person & (
     has_global_perm("core.change_group") | has_object_perm("core.change_group")
 )
-add_perm("core.edit_group", edit_group_predicate)
+rules.add_perm("core.edit_group", edit_group_predicate)
 
 # Delete group
 delete_group_predicate = has_person & (
     has_global_perm("core.delete_group") | has_object_perm("core.delete_group")
 )
-add_perm("core.delete_group", delete_group_predicate)
+rules.add_perm("core.delete_group", delete_group_predicate)
 
 # Assign child groups to groups
 assign_child_groups_to_groups_predicate = has_person & has_global_perm(
     "core.assign_child_groups_to_groups"
 )
-add_perm("core.assign_child_groups_to_groups", assign_child_groups_to_groups_predicate)
+rules.add_perm("core.assign_child_groups_to_groups", assign_child_groups_to_groups_predicate)
 
 # Edit school information
 edit_school_information_predicate = has_person & has_global_perm("core.change_school")
-add_perm("core.edit_school_information", edit_school_information_predicate)
+rules.add_perm("core.edit_school_information", edit_school_information_predicate)
 
 # Manage data
 manage_data_predicate = has_person & has_global_perm("core.manage_data")
-add_perm("core.manage_data", manage_data_predicate)
+rules.add_perm("core.manage_data", manage_data_predicate)
 
 # Mark notification as read
 mark_notification_as_read_predicate = has_person & is_notification_recipient
-add_perm("core.mark_notification_as_read", mark_notification_as_read_predicate)
+rules.add_perm("core.mark_notification_as_read", mark_notification_as_read_predicate)
 
 # View announcements
 view_announcements_predicate = has_person & (
     has_global_perm("core.view_announcement")
     | has_any_object("core.view_announcement", Announcement)
 )
-add_perm("core.view_announcements", view_announcements_predicate)
+rules.add_perm("core.view_announcements", view_announcements_predicate)
 
 # Create or edit announcement
 create_or_edit_announcement_predicate = has_person & (
     has_global_perm("core.add_announcement")
     & (has_global_perm("core.change_announcement") | has_object_perm("core.change_announcement"))
 )
-add_perm("core.create_or_edit_announcement", create_or_edit_announcement_predicate)
+rules.add_perm("core.create_or_edit_announcement", create_or_edit_announcement_predicate)
 
 # Delete announcement
 delete_announcement_predicate = has_person & (
     has_global_perm("core.delete_announcement") | has_object_perm("core.delete_announcement")
 )
-add_perm("core.delete_announcement", delete_announcement_predicate)
+rules.add_perm("core.delete_announcement", delete_announcement_predicate)
 
 # Use impersonate
 impersonate_predicate = has_person & has_global_perm("core.impersonate")
-add_perm("core.impersonate", impersonate_predicate)
+rules.add_perm("core.impersonate", impersonate_predicate)
 
 # View system status
 view_system_status_predicate = has_person & has_global_perm("core.view_system_status")
-add_perm("core.view_system_status", view_system_status_predicate)
+rules.add_perm("core.view_system_status", view_system_status_predicate)
 
 # View people menu (persons + objects)
-add_perm(
+rules.add_perm(
     "core.view_people_menu",
     has_person
     & (
@@ -164,14 +164,14 @@ view_personal_details_predicate = has_person & (
     | has_object_perm("core.view_personal_details")
     | is_current_person
 )
-add_perm("core.view_personal_details", view_personal_details_predicate)
+rules.add_perm("core.view_personal_details", view_personal_details_predicate)
 
 # Change site preferences
 change_site_preferences = has_person & (
     has_global_perm("core.change_site_preferences")
     | has_object_perm("core.change_site_preferences")
 )
-add_perm("core.change_site_preferences", change_site_preferences)
+rules.add_perm("core.change_site_preferences", change_site_preferences)
 
 # Change person preferences
 change_person_preferences = has_person & (
@@ -179,7 +179,7 @@ change_person_preferences = has_person & (
     | has_object_perm("core.change_person_preferences")
     | is_current_person
 )
-add_perm("core.change_person_preferences", change_person_preferences)
+rules.add_perm("core.change_person_preferences", change_person_preferences)
 
 # Change group preferences
 change_group_preferences = has_person & (
@@ -187,81 +187,81 @@ change_group_preferences = has_person & (
     | has_object_perm("core.change_group_preferences")
     | is_group_owner
 )
-add_perm("core.change_group_preferences", change_group_preferences)
+rules.add_perm("core.change_group_preferences", change_group_preferences)
 
 
 # Edit additional field
 change_additional_field_predicate = has_person & (
     has_global_perm("core.change_additionalfield") | has_object_perm("core.change_additionalfield")
 )
-add_perm("core.change_additionalfield", change_additional_field_predicate)
+rules.add_perm("core.change_additionalfield", change_additional_field_predicate)
 
 # Edit additional field
 create_additional_field_predicate = has_person & (
     has_global_perm("core.create_additionalfield") | has_object_perm("core.create_additionalfield")
 )
-add_perm("core.create_additionalfield", create_additional_field_predicate)
+rules.add_perm("core.create_additionalfield", create_additional_field_predicate)
 
 
 # Delete additional field
 delete_additional_field_predicate = has_person & (
     has_global_perm("core.delete_additionalfield") | has_object_perm("core.delete_additionalfield")
 )
-add_perm("core.delete_additionalfield", delete_additional_field_predicate)
+rules.add_perm("core.delete_additionalfield", delete_additional_field_predicate)
 
 # View additional fields
 view_additional_field_predicate = has_person & (
     has_global_perm("core.view_additionalfield")
     | has_any_object("core.view_additionalfield", AdditionalField)
 )
-add_perm("core.view_additionalfield", view_additional_field_predicate)
+rules.add_perm("core.view_additionalfield", view_additional_field_predicate)
 
 # Edit group type
 change_group_type_predicate = has_person & (
     has_global_perm("core.change_grouptype") | has_object_perm("core.change_grouptype")
 )
-add_perm("core.edit_grouptype", change_group_type_predicate)
+rules.add_perm("core.edit_grouptype", change_group_type_predicate)
 
 # Create group type
 create_group_type_predicate = has_person & (
     has_global_perm("core.create_grouptype") | has_object_perm("core.change_grouptype")
 )
-add_perm("core.create_grouptype", create_group_type_predicate)
+rules.add_perm("core.create_grouptype", create_group_type_predicate)
 
 
 # Delete group type
 delete_group_type_predicate = has_person & (
     has_global_perm("core.delete_grouptype") | has_object_perm("core.delete_grouptype")
 )
-add_perm("core.delete_grouptype", delete_group_type_predicate)
+rules.add_perm("core.delete_grouptype", delete_group_type_predicate)
 
 # View group types
 view_group_type_predicate = has_person & (
     has_global_perm("core.view_grouptype") | has_any_object("core.view_grouptype", GroupType)
 )
-add_perm("core.view_grouptype", view_group_type_predicate)
+rules.add_perm("core.view_grouptype", view_group_type_predicate)
 
 # Create person
 create_person_predicate = has_person & (
     has_global_perm("core.create_person") | has_object_perm("core.create_person")
 )
-add_perm("core.create_person", create_person_predicate)
+rules.add_perm("core.create_person", create_person_predicate)
 
 # Create group
 create_group_predicate = has_person & (
     has_global_perm("core.create_group") | has_object_perm("core.create_group")
 )
-add_perm("core.create_group", create_group_predicate)
+rules.add_perm("core.create_group", create_group_predicate)
 
 # School years
 view_school_term_predicate = has_person & has_global_perm("core.view_schoolterm")
-add_perm("core.view_schoolterm", view_school_term_predicate)
+rules.add_perm("core.view_schoolterm", view_school_term_predicate)
 
 create_school_term_predicate = has_person & has_global_perm("core.add_schoolterm")
-add_perm("core.create_schoolterm", create_school_term_predicate)
+rules.add_perm("core.create_schoolterm", create_school_term_predicate)
 
 edit_school_term_predicate = has_person & has_global_perm("core.change_schoolterm")
-add_perm("core.edit_schoolterm", edit_school_term_predicate)
+rules.add_perm("core.edit_schoolterm", edit_school_term_predicate)
 
 # View admin menu
 view_admin_menu_predicate = has_person & (
@@ -271,4 +271,23 @@ view_admin_menu_predicate = has_person & (
     | view_system_status_predicate
     | view_announcements_predicate
 )
-add_perm("core.view_admin_menu", view_admin_menu_predicate)
+rules.add_perm("core.view_admin_menu", view_admin_menu_predicate)
+
+# View group stats
+view_group_stats_predicate = has_person & (
+    has_global_perm("core.view_group_stats") | has_object_perm("core.view_group_stats")
+)
+rules.add_perm("core.view_group_stats", view_group_stats_predicate)
+
+
+view_dashboard_widget_predicate = has_person & has_global_perm("core.view_dashboardwidget")
+rules.add_perm("core.view_dashboardwidget", view_dashboard_widget_predicate)
+
+create_dashboard_widget_predicate = has_person & has_global_perm("core.add_dashboardwidget")
+rules.add_perm("core.create_dashboardwidget", create_dashboard_widget_predicate)
+
+edit_dashboard_widget_predicate = has_person & has_global_perm("core.change_dashboardwidget")
+rules.add_perm("core.edit_dashboardwidget", edit_dashboard_widget_predicate)
+
+delete_dashboard_widget_predicate = has_person & has_global_perm("core.delete_dashboardwidget")
+rules.add_perm("core.delete_dashboardwidget", delete_dashboard_widget_predicate)
diff --git a/aleksis/core/settings.py b/aleksis/core/settings.py
index 5eea8c972a3edb2510f944ed33cfa10bcc9ff31e..e8870dc656e05c566ab1e83131d7fc4c70e0b9d5 100644
--- a/aleksis/core/settings.py
+++ b/aleksis/core/settings.py
@@ -4,7 +4,6 @@ from glob import glob
 from django.utils.translation import gettext_lazy as _
 
 from dynaconf import LazySettings
-from easy_thumbnails.conf import settings as thumbnail_settings
 
 from .util.core_helpers import (
     get_app_packages,
@@ -45,6 +44,23 @@ DEBUG_TOOLBAR_CONFIG = {
     "SHOW_TOOLBAR_CALLBACK": "aleksis.core.util.core_helpers.dt_show_toolbar",
 }
 
+DEBUG_TOOLBAR_PANELS = [
+    "debug_toolbar.panels.versions.VersionsPanel",
+    "debug_toolbar.panels.timer.TimerPanel",
+    "debug_toolbar.panels.settings.SettingsPanel",
+    "debug_toolbar.panels.headers.HeadersPanel",
+    "debug_toolbar.panels.request.RequestPanel",
+    "debug_toolbar.panels.sql.SQLPanel",
+    "debug_toolbar.panels.cache.CachePanel",
+    "debug_toolbar.panels.staticfiles.StaticFilesPanel",
+    "debug_toolbar.panels.templates.TemplatesPanel",
+    "debug_toolbar.panels.signals.SignalsPanel",
+    "debug_toolbar.panels.logging.LoggingPanel",
+    "debug_toolbar.panels.redirects.RedirectsPanel",
+    "debug_toolbar.panels.profiling.ProfilingPanel",
+]
+
+
 ALLOWED_HOSTS = _settings.get("http.allowed_hosts", [])
 
 # Application definition
@@ -65,16 +81,15 @@ INSTALLED_APPS = [
     "dbbackup",
     "settings_context_processor",
     "sass_processor",
-    "easyaudit",
     "django_any_js",
     "django_yarnpkg",
     "django_tables2",
-    "easy_thumbnails",
     "maintenance_mode",
     "menu_generator",
     "reversion",
     "phonenumber_field",
     "debug_toolbar",
+    "django_prometheus",
     "django_select2",
     "hattori",
     "templated_email",
@@ -89,6 +104,7 @@ INSTALLED_APPS = [
     "health_check.cache",
     "health_check.storage",
     "health_check.contrib.psutil",
+    "health_check.contrib.migrations",
     "dynamic_preferences",
     "dynamic_preferences.users.apps.UserPreferencesConfig",
     "impersonate",
@@ -100,6 +116,7 @@ INSTALLED_APPS = [
     "colorfield",
     "django_bleach",
     "favicon",
+    "django_filters",
 ]
 
 merge_app_settings("INSTALLED_APPS", INSTALLED_APPS, True)
@@ -114,6 +131,7 @@ STATICFILES_FINDERS = [
 
 MIDDLEWARE = [
     #    'django.middleware.cache.UpdateCacheMiddleware',
+    "django_prometheus.middleware.PrometheusBeforeMiddleware",
     "django.middleware.security.SecurityMiddleware",
     "django.contrib.sessions.middleware.SessionMiddleware",
     "django.middleware.locale.LocaleMiddleware",
@@ -128,9 +146,9 @@ MIDDLEWARE = [
     "impersonate.middleware.ImpersonateMiddleware",
     "django.contrib.messages.middleware.MessageMiddleware",
     "django.middleware.clickjacking.XFrameOptionsMiddleware",
-    "easyaudit.middleware.easyaudit.EasyAuditMiddleware",
     "maintenance_mode.middleware.MaintenanceModeMiddleware",
     "aleksis.core.util.middlewares.EnsurePersonMiddleware",
+    "django_prometheus.middleware.PrometheusAfterMiddleware",
     #    'django.middleware.cache.FetchFromCacheMiddleware'
 ]
 
@@ -156,8 +174,6 @@ TEMPLATES = [
     },
 ]
 
-THUMBNAIL_PROCESSORS = () + thumbnail_settings.THUMBNAIL_PROCESSORS
-
 WSGI_APPLICATION = "aleksis.core.wsgi.application"
 
 # Database
@@ -165,7 +181,7 @@ WSGI_APPLICATION = "aleksis.core.wsgi.application"
 
 DATABASES = {
     "default": {
-        "ENGINE": "django.db.backends.postgresql",
+        "ENGINE": "django_prometheus.db.backends.postgresql",
         "NAME": _settings.get("database.name", "aleksis"),
         "USER": _settings.get("database.username", "aleksis"),
         "PASSWORD": _settings.get("database.password", None),
@@ -181,10 +197,15 @@ merge_app_settings("DATABASES", DATABASES, False)
 if _settings.get("caching.memcached.enabled", False):
     CACHES = {
         "default": {
-            "BACKEND": "django.core.cache.backends.memcached.MemcachedCache",
+            "BACKEND": "django_prometheus.cache.backends.memcached.MemcachedCache",
             "LOCATION": _settings.get("caching.memcached.address", "127.0.0.1:11211"),
         }
     }
+    INSTALLED_APPS.append("cachalot")
+    DEBUG_TOOLBAR_PANELS.append("cachalot.panels.CachalotPanel")
+    CACHALOT_TIMEOUT = _settings.get("caching.cachalot.timeout", None)
+    CACHALOT_DATABASES = set(["default"])
+    SILENCED_SYSTEM_CHECKS.append("cachalot.W001")
 
 # Password validation
 # https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators
@@ -331,6 +352,8 @@ YARN_INSTALLED_APPS = [
     "select2",
     "select2-materialize",
     "paper-css",
+    "jquery-sortablejs",
+    "sortablejs",
 ]
 
 merge_app_settings("YARN_INSTALLED_APPS", YARN_INSTALLED_APPS, True)
@@ -354,6 +377,8 @@ ANY_JS = {
         "css_url": JS_URL + "/select2-materialize/select2-materialize.css",
         "js_url": JS_URL + "/select2-materialize/index.js",
     },
+    "sortablejs": {"js_url": JS_URL + "/sortablejs/dist/sortable.umd.js"},
+    "jquery-sortablejs": {"js_url": JS_URL + "/jquery-sortablejs/jquery-sortable.js"},
 }
 
 merge_app_settings("ANY_JS", ANY_JS, True)
@@ -366,6 +391,7 @@ SASS_PROCESSOR_CUSTOM_FUNCTIONS = {
 }
 SASS_PROCESSOR_INCLUDE_DIRS = [
     _settings.get("materialize.sass_path", JS_ROOT + "/materialize-css/sass/"),
+    STATIC_ROOT + "/materialize-css/sass/",
     STATIC_ROOT,
 ]
 
@@ -415,6 +441,9 @@ DBBACKUP_COMPRESS_MEDIA = _settings.get("backup.media.compress", True)
 DBBACKUP_ENCRYPT_MEDIA = _settings.get("backup.media.encrypt", DBBACKUP_GPG_RECIPIENT is not None)
 DBBACKUP_CLEANUP_DB = _settings.get("backup.database.clean", True)
 DBBACKUP_CLEANUP_MEDIA = _settings.get("backup.media.clean", True)
+DBBACKUP_CONNECTOR_MAPPING = {
+    "django_prometheus.db.backends.postgresql": "dbbackup.db.postgresql.PgDumpConnector",
+}
 
 IMPERSONATE = {"USE_HTTP_REFERER": True, "REQUIRE_SUPERUSER": True, "ALLOW_SUPERUSER": True}
 
@@ -695,3 +724,5 @@ HEALTH_CHECK = {
 }
 
 ORIGINAL_AUTHENTICATION_BACKENDS = AUTHENTICATION_BACKENDS[:]
+
+PROMETHEUS_EXPORT_MIGRATIONS = False
diff --git a/aleksis/core/static/js/edit_dashboard.js b/aleksis/core/static/js/edit_dashboard.js
new file mode 100644
index 0000000000000000000000000000000000000000..0cc90de60305497a682d219b121e77550d273d1d
--- /dev/null
+++ b/aleksis/core/static/js/edit_dashboard.js
@@ -0,0 +1,22 @@
+function refreshOrder() {
+    $(".order-input").val(0);
+    $("#widgets > .col").each(function (index) {
+        const order = (index + 1) * 10;
+        let pk = $(this).attr("data-pk");
+        let sel = $("#order-form input[value=" + pk + "].pk-input").next();
+        sel.val(order);
+    })
+}
+
+$(document).ready(function () {
+    $('#not-used-widgets').sortable({
+        group: 'widgets',
+        animation: 150,
+        onEnd: refreshOrder
+    });
+    $('#widgets').sortable({
+        group: 'widgets',
+        animation: 150,
+        onEnd: refreshOrder
+    });
+});
diff --git a/aleksis/core/static/js/main.js b/aleksis/core/static/js/main.js
index ba4e56cd54d8909d1f74cb54bbdb3c669b4cfeab..615f8b287df18ed38c57d1f44358073bb74bcf61 100644
--- a/aleksis/core/static/js/main.js
+++ b/aleksis/core/static/js/main.js
@@ -1,21 +1,15 @@
-$(document).ready( function () {
-    $("dmc-datetime input").addClass("datepicker");
-    $("[data-form-control='date']").addClass("datepicker");
-    $("[data-form-control='time']").addClass("timepicker");
-
-    // Initialize sidenav [MAT]
-    $(".sidenav").sidenav();
-
+function initDatePicker(sel) {
     // Initialize datepicker [MAT]
-    $('.datepicker').datepicker({
-        format: get_format('SHORT_DATE_FORMAT').toLowerCase().replace('d', 'dd').replace('m', 'mm').replace('y', 'yyyy'),
+    const format = get_format('SHORT_DATE_FORMAT').toLowerCase().replace('d', 'dd').replace('m', 'mm').replace('y', 'yyyy');
+    const el = $(sel).datepicker({
+        format: format,
         // Pull translations from Django helpers
         i18n: {
             months: calendarweek_i18n.month_names,
             monthsShort: calendarweek_i18n.month_abbrs,
             weekdays: calendarweek_i18n.day_names,
             weekdaysShort: calendarweek_i18n.day_abbrs,
-            weekdaysAbbrev: calendarweek_i18n.day_abbrs.map(([v])=> v),
+            weekdaysAbbrev: calendarweek_i18n.day_abbrs.map(([v]) => v),
 
             // Buttons
             today: gettext('Today'),
@@ -27,9 +21,13 @@ $(document).ready( function () {
         firstDay: get_format('FIRST_DAY_OF_WEEK'),
         autoClose: true
     });
+    el.datepicker("setDate", $(sel).val());
+    return el;
+}
 
+function initTimePicker(sel) {
     // Initialize timepicker [MAT]
-    $('.timepicker').timepicker({
+    return $(sel).timepicker({
         twelveHour: false,
         autoClose: true,
         i18n: {
@@ -38,6 +36,21 @@ $(document).ready( function () {
             done: 'OK'
         },
     });
+}
+
+$(document).ready(function () {
+    $("dmc-datetime input").addClass("datepicker");
+    $("[data-form-control='date']").addClass("datepicker");
+    $("[data-form-control='time']").addClass("timepicker");
+
+    // Initialize sidenav [MAT]
+    $(".sidenav").sidenav();
+
+    // Initialize datepicker [MAT]
+    initDatePicker(".datepicker");
+
+    // Initialize timepicker [MAT]
+    initTimePicker(".timepicker");
 
     // Initialize tooltip [MAT]
     $('.tooltipped').tooltip();
diff --git a/aleksis/core/static/js/serviceworker.js b/aleksis/core/static/js/serviceworker.js
index 93d9797c78db9756cba6f1f6caa46fc8c14ae57b..818e27cb7d28e820431a4ac28ba6fdf0c422deb7 100644
--- a/aleksis/core/static/js/serviceworker.js
+++ b/aleksis/core/static/js/serviceworker.js
@@ -1,35 +1,9 @@
-// This is the AlekSIS service worker
-
-const CACHE = "aleksis-cache";
-
-const precacheFiles = [
-    '',
-];
 
-const offlineFallbackPage = '/offline';
-
-const avoidCachingPaths = [
-    '/admin',
-    '/settings',
-    '/accounts/login'
-]; // TODO: More paths are needed
-
-function pathComparer(requestUrl, pathRegEx) {
-    return requestUrl.match(new RegExp(pathRegEx));
-}
+// This is the AlekSIS service worker
 
-function comparePaths(requestUrl, pathsArray) {
-    if (requestUrl) {
-        for (let index = 0; index < pathsArray.length; index++) {
-            const pathRegEx = pathsArray[index];
-            if (pathComparer(requestUrl, pathRegEx)) {
-                return true;
-            }
-        }
-    }
+const CACHE = 'aleksis-cache';
 
-    return false;
-}
+const offlineFallbackPage = 'offline/';
 
 self.addEventListener("install", function (event) {
     console.log("[AlekSIS PWA] Install Event processing.");
@@ -40,10 +14,7 @@ self.addEventListener("install", function (event) {
     event.waitUntil(
         caches.open(CACHE).then(function (cache) {
             console.log("[AlekSIS PWA] Caching pages during install.");
-
-            return cache.addAll(precacheFiles).then(function () {
-                return cache.add(offlineFallbackPage);
-            });
+            return cache.add(offlineFallbackPage);
         })
     );
 });
@@ -95,11 +66,11 @@ function fromCache(event) {
 }
 
 function updateCache(request, response) {
-    if (!comparePaths(request.url, avoidCachingPaths)) {
+    if (response.headers.get('cache-control') && response.headers.get('cache-control').includes('no-cache')) {
+        return Promise.resolve();
+    } else {
         return caches.open(CACHE).then(function (cache) {
             return cache.put(request, response);
         });
     }
-
-    return Promise.resolve();
 }
diff --git a/aleksis/core/static/style.scss b/aleksis/core/static/style.scss
index e2ea3bbd9953477469350e1c477556947bf8f0e2..81f443f75ab79c34121390bf03da9ab1e611dd5b 100644
--- a/aleksis/core/static/style.scss
+++ b/aleksis/core/static/style.scss
@@ -70,6 +70,10 @@ header, main, footer {
   }
 }
 
+.materialize-circle {
+  @extend .circle;
+}
+
 /**********/
 /* HEADER */
 /**********/
@@ -622,3 +626,7 @@ main .alert p:first-child, main .alert div:first-child {
   overflow: visible;
   width: 100%;
 }
+
+.draggable {
+  cursor: grab;
+}
diff --git a/aleksis/core/tables.py b/aleksis/core/tables.py
index f562b61a151487c5047ef2861255a00ef7a5f785..54d3f3e5c75e4c5ac0dca4b8ccfe868f70b7efb2 100644
--- a/aleksis/core/tables.py
+++ b/aleksis/core/tables.py
@@ -70,3 +70,24 @@ class GroupTypesTable(tables.Table):
     delete = tables.LinkColumn(
         "delete_group_type_by_id", args=[A("id")], verbose_name=_("Delete"), text=_("Delete")
     )
+
+
+class DashboardWidgetTable(tables.Table):
+    """Table to list dashboard widgets."""
+
+    class Meta:
+        attrs = {"class": "responsive-table highlight"}
+
+    widget_name = tables.Column(accessor="pk")
+    title = tables.LinkColumn("edit_dashboard_widget", args=[A("id")])
+    active = tables.BooleanColumn(yesno="check,cancel", attrs={"span": {"class": "material-icons"}})
+    delete = tables.LinkColumn(
+        "delete_dashboard_widget",
+        args=[A("id")],
+        text=_("Delete"),
+        attrs={"a": {"class": "btn-flat waves-effect waves-red red-text"}},
+        verbose_name=_("Actions"),
+    )
+
+    def render_widget_name(self, value, record):
+        return record._meta.verbose_name
diff --git a/aleksis/core/templates/core/announcement/form.html b/aleksis/core/templates/core/announcement/form.html
index 0cd31cdefc11e8f1a2d29a30fb72a001357a946f..44d60280c6d358f74c60cb9c5ccff2c7aba89b07 100644
--- a/aleksis/core/templates/core/announcement/form.html
+++ b/aleksis/core/templates/core/announcement/form.html
@@ -2,8 +2,12 @@
 
 {% extends "core/base.html" %}
 
-{% load i18n material_form %}
+{% load i18n material_form any_js %}
 
+{% block extra_head %}
+    {{ form.media.css }}
+    {% include_css "select2-materialize" %}
+{% endblock %}
 
 {% block browser_title %}
   {% if mode == "edit" %}
@@ -30,4 +34,6 @@
       {% trans "Save und publish announcement" %}
     </button>
   </form>
+  {% include_js "select2-materialize" %}
+  {{ form.media.js }}
 {% endblock %}
diff --git a/aleksis/core/templates/core/base.html b/aleksis/core/templates/core/base.html
index 07db50c7fb3a29dc169543ae03c4f26dff1ec25f..2087829da24c157b7dc0a797da776d7bb1cc9a3b 100644
--- a/aleksis/core/templates/core/base.html
+++ b/aleksis/core/templates/core/base.html
@@ -170,6 +170,8 @@
 
 
 {% include_js "materialize" %}
+{% include_js "sortablejs" %}
+{% include_js "jquery-sortablejs" %}
 <script type="text/javascript" src="{% static 'js/search.js' %}"></script>
 <script type="text/javascript" src="{% static 'js/main.js' %}"></script>
 </body>
diff --git a/aleksis/core/templates/core/dashboard_widget/create.html b/aleksis/core/templates/core/dashboard_widget/create.html
new file mode 100644
index 0000000000000000000000000000000000000000..cfaa296eefc7afb0abce56ce1802eeba2ef70a76
--- /dev/null
+++ b/aleksis/core/templates/core/dashboard_widget/create.html
@@ -0,0 +1,23 @@
+{# -*- engine:django -*- #}
+
+{% extends "core/base.html" %}
+{% load material_form i18n data_helpers %}
+
+{% block browser_title %}
+  {% verbose_name_object model as widget_title %}
+  {% blocktrans with widget=widget_title %}Create {{ widget }}{% endblocktrans %}
+{% endblock %}
+{% block page_title %}
+  {% verbose_name_object model as widget_title %}
+  {% blocktrans with widget=widget_title %}Create {{ widget }}{% endblocktrans %}
+{% endblock %}
+
+{% block content %}
+
+  <form method="post">
+    {% csrf_token %}
+    {% form form=form %}{% endform %}
+    {% include "core/partials/save_button.html" %}
+  </form>
+
+{% endblock %}
diff --git a/aleksis/core/templates/core/dashboard_widget/edit.html b/aleksis/core/templates/core/dashboard_widget/edit.html
new file mode 100644
index 0000000000000000000000000000000000000000..64dfe0eed64c36257fed529475f355d0ee2e8fc0
--- /dev/null
+++ b/aleksis/core/templates/core/dashboard_widget/edit.html
@@ -0,0 +1,23 @@
+{# -*- engine:django -*- #}
+
+{% extends "core/base.html" %}
+{% load material_form i18n data_helpers %}
+
+{% block browser_title %}
+  {% verbose_name_object object as widget_title %}
+  {% blocktrans with widget=widget_title %}Edit {{ widget }}{% endblocktrans %}
+{% endblock %}
+{% block page_title %}
+  {% verbose_name_object object as widget_title %}
+  {% blocktrans with widget=widget_title %}Edit {{ widget }}{% endblocktrans %}
+{% endblock %}
+
+{% block content %}
+
+  <form method="post">
+    {% csrf_token %}
+    {% form form=form %}{% endform %}
+    {% include "core/partials/save_button.html" %}
+  </form>
+
+{% endblock %}
diff --git a/aleksis/core/templates/core/dashboard_widget/list.html b/aleksis/core/templates/core/dashboard_widget/list.html
new file mode 100644
index 0000000000000000000000000000000000000000..ab384e8620f7c6499adb3e663fdb98e4eb598b2c
--- /dev/null
+++ b/aleksis/core/templates/core/dashboard_widget/list.html
@@ -0,0 +1,22 @@
+{# -*- engine:django -*- #}
+
+{% extends "core/base.html" %}
+
+{% load i18n data_helpers %}
+{% load render_table from django_tables2 %}
+
+{% block browser_title %}{% blocktrans %}Dashboard widgets{% endblocktrans %}{% endblock %}
+{% block page_title %}{% blocktrans %}Dashboard widgets{% endblocktrans %}{% endblock %}
+
+{% block content %}
+
+  {% for ct, model in widget_types %}
+    <a class="btn green waves-effect waves-light" href="{% url 'create_dashboard_widget' ct.app_label ct.model  %}">
+      <i class="material-icons left">add</i>
+      {% verbose_name_object model as widget_name %}
+      {% blocktrans with name=widget_name %}Create {{ name }}{% endblocktrans %}
+    </a>
+  {% endfor %}
+
+  {% render_table table %}
+{% endblock %}
diff --git a/aleksis/core/templates/core/edit_dashboard.html b/aleksis/core/templates/core/edit_dashboard.html
new file mode 100644
index 0000000000000000000000000000000000000000..a15f24bff8ebcca6a7f9b4ea081d9d8c008f3b0a
--- /dev/null
+++ b/aleksis/core/templates/core/edit_dashboard.html
@@ -0,0 +1,41 @@
+{% extends 'core/base.html' %}
+{% load i18n static dashboard any_js %}
+
+{% block browser_title %}{% blocktrans %}Edit dashboard{% endblocktrans %}{% endblock %}
+{% block page_title %}{% blocktrans %}Edit dashboard{% endblocktrans %}{% endblock %}
+
+{% block content %}
+  <div class="alert primary">
+    <p>
+      <i class="material-icons left">info</i>
+      On this page you can arrange your personal dashboard. You can drag any items from "Available widgets" to "Your
+      Dashboard" or change the order by moving the widgets. After you have finished, please don't forget to click on
+      "Save".
+    </p>
+  </div>
+
+  <form action="" method="post" id="order-form">
+    {% csrf_token %}
+    {{ formset.management_form }}
+    {% for form in formset %}
+      {{ form.as_p }}
+    {% endfor %}
+    {% include "core/partials/save_button.html" %}
+  </form>
+
+  <h5>{% trans "Available widgets" %}</h5>
+  <div class="row card-panel grey lighten-3" id="not-used-widgets">
+    {% for widget in not_used_widgets %}
+      {% include "core/partials/edit_dashboard_widget.html" %}
+    {% endfor %}
+  </div>
+
+  <h5>{% trans "Your dashboard" %}</h5>
+  <div class="row card-panel grey lighten-3" id="widgets">
+    {% for widget in widgets %}
+      {% include "core/partials/edit_dashboard_widget.html" %}
+    {% endfor %}
+  </div>
+
+  <script src="{% static "js/edit_dashboard.js" %}"></script>
+{% endblock %}
diff --git a/aleksis/core/templates/core/group/edit.html b/aleksis/core/templates/core/group/edit.html
index b26a28d1efc5ee292a257220bca00754512c1b99..146a5d83abe78307c20f5f70aaff28dc82b62fed 100644
--- a/aleksis/core/templates/core/group/edit.html
+++ b/aleksis/core/templates/core/group/edit.html
@@ -1,7 +1,12 @@
 {# -*- engine:django -*- #}
 
 {% extends "core/base.html" %}
-{% load material_form i18n %}
+{% load material_form i18n any_js %}
+
+{% block extra_head %}
+    {{ edit_group_form.media.css }}
+    {% include_css "select2-materialize" %}
+{% endblock %}
 
 {% block browser_title %}{% blocktrans %}Edit group{% endblocktrans %}{% endblock %}
 {% block page_title %}{% blocktrans %}Edit group{% endblocktrans %}{% endblock %}
@@ -13,5 +18,7 @@
     {% form form=edit_group_form %}{% endform %}
     {% include "core/partials/save_button.html" %}
   </form>
+  {% include_js "select2-materialize" %}
+  {{ edit_group_form.media.js }}
 
 {% endblock %}
diff --git a/aleksis/core/templates/core/group/full.html b/aleksis/core/templates/core/group/full.html
index def54269f884515d3f07f96ee353616df2db3c7e..1a15daf5c490c4cd1af12e7c43c02a3872c8b883 100644
--- a/aleksis/core/templates/core/group/full.html
+++ b/aleksis/core/templates/core/group/full.html
@@ -14,6 +14,7 @@
   {% has_perm 'core.edit_group' user group as can_change_group %}
   {% has_perm 'core.change_group_preferences' user group as can_change_group_preferences %}
   {% has_perm 'core.delete_group' user group as can_delete_group %}
+  {% has_perm 'core.view_group_stats' user group as can_view_group_stats %}
 
   {% if can_change_group or can_change_group_preferences or can_delete_group %}
     <p>
@@ -59,6 +60,25 @@
     </tr>
   </table>
 
+  {% if can_view_group_stats %}
+    <h5>{% blocktrans %}Statistics{% endblocktrans %}</h5>
+    <ul>
+      <li>
+        {% trans "Count of members" %}: {{ stats.members }}
+      </li>
+      {% if stats.age_avg %}
+        <li>
+          {% trans "Average age" %}: {{ stats.age_avg|floatformat }}
+        </li>
+      {% endif %}
+      {% if stats.age_range_min %}
+        <li>
+          {% trans "Age range" %}: {{ stats.age_range_min }} {% trans "years to" %} {{ stats.age_range_max }} {% trans "years "%}
+        </li>
+      {% endif %}
+    </ul>
+  {% endif %}
+
   <h5>{% blocktrans %}Owners{% endblocktrans %}</h5>
   {% render_table owners_table %}
 
diff --git a/aleksis/core/templates/core/index.html b/aleksis/core/templates/core/index.html
index 683adca900d46a8ba50790829db4d3ecd0ba2716..93b80ddefffaa8cf72db56a7ef503a694f0514f5 100644
--- a/aleksis/core/templates/core/index.html
+++ b/aleksis/core/templates/core/index.html
@@ -2,13 +2,21 @@
 {% load i18n static dashboard %}
 
 {% block browser_title %}{% blocktrans %}Home{% endblocktrans %}{% endblock %}
-{% block page_title %}{{ request.site.preferences.general__title }}{% endblock %}
+{% block no_page_title %}{% endblock %}
 
 {% block extra_head %}
   {{ media }}
 {% endblock %}
 
 {% block content %}
+  <a class="btn-flat waves-effect waves-light right" href="{% url "edit_dashboard" %}">
+    <i class="material-icons left">edit</i>
+    {% trans "Edit dashboard" %}
+  </a>
+  <h4>
+    {{ request.site.preferences.general__title }}
+  </h4>
+
   {% if user.is_authenticated %}
     {% for notification in unread_notifications %}
       <div class="alert primary scale-transition">
@@ -31,9 +39,19 @@
 
     <div class="row" id="live_load">
       {% for widget in widgets %}
-        <div class="col s12 m12 l6 xl4">
+        <div class="col s{{ widget.size_s }} m{{ widget.size_m }} l{{ widget.size_l }} xl{{ widget.size_xl }}">
           {% include_widget widget %}
         </div>
+        {% empty %}
+        <div class="col s12 grey-text center">
+          <i class="material-icons medium ">widgets</i>
+          <p class="flow-text">
+            {% blocktrans %}
+              You haven't selected any dashboard widgets. Please click on "Edit dashboard" to add widgets to your
+              personal dashboard.
+            {% endblocktrans %}
+          </p>
+        </div>
       {% endfor %}
     </div>
 
@@ -48,7 +66,7 @@
                 <span class="badge new primary-color">{{ activity.app }}</span>
                 <span class="title">{{ activity.title }}</span>
                 <p>
-                  <i class="material-icons left">access_time</i> {{ activity.created_at }}
+                  <i class="material-icons left">access_time</i> {{ activity.created }}
                 </p>
                 <p>
                   {{ activity.description }}
@@ -71,7 +89,7 @@
                 <span class="badge new primary-color">{{ notification.app }}</span>
                 <span class="title">{{ notification.title }}</span>
                 <p>
-                  <i class="material-icons left">access_time</i> {{ notification.created_at }}
+                  <i class="material-icons left">access_time</i> {{ notification.created }}
                 </p>
                 <p>
                   {{ notification.description }}
diff --git a/aleksis/core/templates/core/partials/crud_events.html b/aleksis/core/templates/core/partials/crud_events.html
index 50e4f73cab492804b163b7ddfe0dda08b9e267b7..b0edf14d154690e33a2fac523f5fa38429fcdb5e 100644
--- a/aleksis/core/templates/core/partials/crud_events.html
+++ b/aleksis/core/templates/core/partials/crud_events.html
@@ -1,62 +1,32 @@
 {% load i18n data_helpers %}
 
-<ul class="collection">
-  {% for event in obj.crud_events %}
-    {% if no_m2m and event.event_type == event.M2M_CHANGE or event.event_type == event.M2M_CHANGE_REV %}
-    {% else %}
-      <li class="collection-item">
-        <strong>
-          {% if event.event_type == event.CREATE %}
-            {% blocktrans with person=event.user.person %}
-              Created by {{ person }}
-            {% endblocktrans %}
-          {% elif event.event_type == event.UPDATE %}
-            {% blocktrans with person=event.user.person %}
-              Updated by {{ person }}
-            {% endblocktrans %}
-          {% elif event.event_type == event.DELETE %}
-            {% blocktrans with person=event.user.person %}
-              Deleted by {{ person }}
-            {% endblocktrans %}
-          {% elif event.event_type == event.M2M_CHANGE %}
-            {% blocktrans with person=event.user.person %}
-              Updated by {{ person }}
-            {% endblocktrans %}
-          {% elif event.event_type == event.M2M_CHANGE_REV %}
-            {% blocktrans with person=event.user.person %}
-              Updated by {{ person }}
-            {% endblocktrans %}
-          {% endif %}
-        </strong>
-
-        <div class="left" style="margin-right: 10px;">
-          {% if event.event_type == event.CREATE %}
-            <i class="material-icons">add_circle</i>
-          {% elif event.event_type == event.UPDATE %}
-            <i class="material-icons">edit</i>
-          {% elif event.event_type == event.DELETE %}
-            <i class="material-icons">delete</i>
-          {% elif event.event_type == event.M2M_CHANGE %}
-            <i class="material-icons">edit</i>
-          {% elif event.event_type == event.M2M_CHANGE_REV %}
-            <i class="material-icons">edit</i>
-          {% endif %}
-        </div>
-        <div class="right">
-          {{ event.datetime }}
-        </div>
-        {% parse_json event.changed_fields as changed_fields %}
-        {% if changed_fields %}
-          <ul>
-            {% for field, change in changed_fields.items %}
-              {% verbose_name event.content_type.app_label event.content_type.model field as verbose_name %}
-              <li>
-                {{ verbose_name }}: <s>{{ change.0 }}</s> → {{ change.1 }}
-              </li>
-            {% endfor %}
-          </ul>
+<div class="collection">
+  {% for version in obj.versions %}
+    <div class="collection-item">
+      <div class="left" style="margin-right: 10px;">
+        {% if forloop.first %}
+          <i class="material-icons">add_circle</i>
+        {% else %}
+          <i class="material-icons">edit</i>
         {% endif %}
-      </li>
-    {% endif %}
+      </div>
+      <strong>
+        {{ version.0.revision.get_comment }}
+        {% trans "Changed by" %} {% firstof version.0.revision.user.person _("Unknown") %}
+      </strong>
+      <div class="right">
+        {{ version.0.revision.date_created }}
+      </div>
+      {% if version.1 %}
+        <ul>
+          {% for field, change in version.1.items %}
+            {% verbose_name version.0.content_type.app_label version.0.content_type.model field as verbose_name %}
+            <li>
+              {{ verbose_name }}: <s>{{ change.0 }}</s> → {{ change.1 }}
+            </li>
+          {% endfor %}
+        </ul>
+      {% endif %}
+    </div>
   {% endfor %}
-</ul>
+</div>
diff --git a/aleksis/core/templates/core/partials/edit_dashboard_widget.html b/aleksis/core/templates/core/partials/edit_dashboard_widget.html
new file mode 100644
index 0000000000000000000000000000000000000000..b842c0937f05c4e90a4d7337871460130fa43012
--- /dev/null
+++ b/aleksis/core/templates/core/partials/edit_dashboard_widget.html
@@ -0,0 +1,9 @@
+<div class="col draggable s{{ widget.size_s }} m{{ widget.size_m }} l{{ widget.size_l }} xl{{ widget.size_xl }}"
+     data-pk="{{ widget.pk }}">
+  <div class="card placeholder">
+    <div class="card-content">
+      <i class="material-icons left small">drag_handle</i>
+      <span class="card-title">{{ widget.title }}</span>
+    </div>
+  </div>
+</div>
diff --git a/aleksis/core/templates/core/partials/language_form.html b/aleksis/core/templates/core/partials/language_form.html
index 36ffa81a8c6017c4fdc86dac8749b0b681e0553f..9a1c62e2c009070b40e4aca11649a8e0ce322101 100644
--- a/aleksis/core/templates/core/partials/language_form.html
+++ b/aleksis/core/templates/core/partials/language_form.html
@@ -8,8 +8,7 @@
   <input name="next" type="hidden" value="{{ request.get_full_path }}">
 
   {% get_current_language as LANGUAGE_CODE %}
-  {% get_available_languages as LANGUAGES %}
-  {% get_language_info_list for LANGUAGES as languages %}
+  {% get_language_info_list for request.site.preferences.internationalisation__languages as languages %}
 
   {# Select #}
   <div class="input-field language-field">
diff --git a/aleksis/core/templates/core/person/accounts.html b/aleksis/core/templates/core/person/accounts.html
index 672b089aaa949560e958d829597e1b7abd986265..03725518dfcbf8552a53199790daa44d541bac32 100644
--- a/aleksis/core/templates/core/person/accounts.html
+++ b/aleksis/core/templates/core/person/accounts.html
@@ -2,7 +2,12 @@
 
 {% extends "core/base.html" %}
 
-{% load i18n %}
+{% load i18n any_js %}
+
+{% block extra_head %}
+  {{ persons_accounts_formset.media.css }}
+  {% include_css "select2-materialize" %}
+{% endblock %}
 
 {% block browser_title %}{% blocktrans %}Link persons to accounts{% endblocktrans %}{% endblock %}
 {% block page_title %}
@@ -55,4 +60,6 @@
       {% blocktrans %}Update{% endblocktrans %}
     </button>
   </form>
+  {% include_js "select2-materialize" %}
+  {{ persons_accounts_formset.media.js }}
 {% endblock %}
diff --git a/aleksis/core/templates/core/person/edit.html b/aleksis/core/templates/core/person/edit.html
index 261249f6867a4370745f66d3bd0abc05891442cf..3bf16ca3521ea744acfe48c6829dfae21db06eb5 100644
--- a/aleksis/core/templates/core/person/edit.html
+++ b/aleksis/core/templates/core/person/edit.html
@@ -2,10 +2,11 @@
 
 {% extends "core/base.html" %}
 
-{% load material_form i18n %}
+{% load material_form i18n any_js %}
 
 {% block extra_head %}
   {{ edit_person_form.media }}
+  {% include_css "select2-materialize" %}
 {% endblock %}
 
 {% block browser_title %}{% blocktrans %}Edit person{% endblocktrans %}{% endblock %}
@@ -19,5 +20,7 @@
     {% form form=edit_person_form %}{% endform %}
     {% include "core/partials/save_button.html" %}
   </form>
+  {% include_js "select2-materialize" %}
+  {{ edit_group_form.media.js }}
 
 {% endblock %}
diff --git a/aleksis/core/templates/material/fields/django_select2_modelselect2multiplewidget.html b/aleksis/core/templates/material/fields/django_select2_modelselect2multiplewidget.html
new file mode 100644
index 0000000000000000000000000000000000000000..1b19d8ec88bf72a694e32066c935327691c00738
--- /dev/null
+++ b/aleksis/core/templates/material/fields/django_select2_modelselect2multiplewidget.html
@@ -0,0 +1,21 @@
+{% load l10n material_form material_form_internal %}
+{% part bound_field.field %}<{{ field.widget.component|default:'dmc-select' }}>
+  <label>{{ bound_field.label }}</label>
+  <div class="row">
+    <div{% attrs bound_field 'group' %}
+        id="id_{{ bound_field.html_name }}_container"
+        class="input-field col s12{% if field.required %} required{% endif %}{% if bound_field.errors %} has-error{% endif %}"
+        style="margin-top: 0"
+    {% endattrs %}>
+        {% part field prefix %}{% endpart %}
+        {% part field control %}
+        {{ bound_field }}
+        {% endpart %}
+        {% part field help_text %}{% if field.help_text %}
+            <div class="help-block">{{ bound_field.help_text|safe }}</div>
+        {% endif %}{% endpart %}{% part field errors %}
+        {% if bound_field.errors %}
+            {% include  'material/field_errors.html' %}
+        {% endif %}{% endpart %}{{ hidden_initial }}
+    </div>
+</div></{{ field.widget.component|default:'dmc-select' }}>{% endpart %}
diff --git a/aleksis/core/templates/material/fields/django_select2_select2widget.html b/aleksis/core/templates/material/fields/django_select2_select2widget.html
new file mode 100644
index 0000000000000000000000000000000000000000..1b19d8ec88bf72a694e32066c935327691c00738
--- /dev/null
+++ b/aleksis/core/templates/material/fields/django_select2_select2widget.html
@@ -0,0 +1,21 @@
+{% load l10n material_form material_form_internal %}
+{% part bound_field.field %}<{{ field.widget.component|default:'dmc-select' }}>
+  <label>{{ bound_field.label }}</label>
+  <div class="row">
+    <div{% attrs bound_field 'group' %}
+        id="id_{{ bound_field.html_name }}_container"
+        class="input-field col s12{% if field.required %} required{% endif %}{% if bound_field.errors %} has-error{% endif %}"
+        style="margin-top: 0"
+    {% endattrs %}>
+        {% part field prefix %}{% endpart %}
+        {% part field control %}
+        {{ bound_field }}
+        {% endpart %}
+        {% part field help_text %}{% if field.help_text %}
+            <div class="help-block">{{ bound_field.help_text|safe }}</div>
+        {% endif %}{% endpart %}{% part field errors %}
+        {% if bound_field.errors %}
+            {% include  'material/field_errors.html' %}
+        {% endif %}{% endpart %}{{ hidden_initial }}
+    </div>
+</div></{{ field.widget.component|default:'dmc-select' }}>{% endpart %}
diff --git a/aleksis/core/templates/core/pages/offline.html b/aleksis/core/templates/offline.html
similarity index 87%
rename from aleksis/core/templates/core/pages/offline.html
rename to aleksis/core/templates/offline.html
index a6a70dc19f074e8c3f3ede50b5e9b5b80b5682f1..bd741268a8902ac6708f33e353337695c03158a3 100644
--- a/aleksis/core/templates/core/pages/offline.html
+++ b/aleksis/core/templates/offline.html
@@ -2,6 +2,8 @@
 
 {% load i18n %}
 
+{% block browser_title %}{% blocktrans %}Network error{% endblocktrans %}{% endblock %}
+
 {% block content %}
   <h3><i class="material-icons left medium" style="font-size: 2.92rem;">signal_wifi_off</i>{% blocktrans %}No internet
     connection.{% endblocktrans %}</h3>
diff --git a/aleksis/core/templates/templated_email/notification.email b/aleksis/core/templates/templated_email/notification.email
index 8e61fd0df28b5bc6342c891921831e147e9cc8d9..0e5d9bdee3c8bd9ec3df5c9d008ae68d1f274c23 100644
--- a/aleksis/core/templates/templated_email/notification.email
+++ b/aleksis/core/templates/templated_email/notification.email
@@ -15,7 +15,7 @@
         {% trans "More information" %} → {{ notification.link }}
     {% endif %}
 
-    {% blocktrans with trans_sender=notification.sender trans_created_at=notification.created_at %}
+    {% blocktrans with trans_sender=notification.sender trans_created_at=notification.created %}
         Sent by {{ trans_sender }} at {{ trans_created_at }}
     {% endblocktrans %}
 
@@ -37,7 +37,7 @@
     </blockquote>
 
     <p>
-        {% blocktrans with trans_sender=notification.sender trans_created_at=notification.created_at %}
+        {% blocktrans with trans_sender=notification.sender trans_created_at=notification.created %}
             Sent by {{ trans_sender }} at {{ trans_created_at }}
         {% endblocktrans %}
     </p>
diff --git a/aleksis/core/templates/two_factor/core/setup.html b/aleksis/core/templates/two_factor/core/setup.html
index 2eb4ecb2828ac60104f284b2d75857e9b6be303b..8048403964a5bd4b7c9761d0189f18bb53524a22 100644
--- a/aleksis/core/templates/two_factor/core/setup.html
+++ b/aleksis/core/templates/two_factor/core/setup.html
@@ -22,8 +22,8 @@
     <p>
       {% blocktrans %}
         To start using a token generator, please use your
-        smartphone to scan the QR code below. For example, use Google
-        Authenticator. Then, enter the token generated by the app.
+        favourite two factor authentication (TOTP) app to scan the QR code below.
+        Then, enter the token generated by the app.
       {% endblocktrans %}
     </p>
     <p>
diff --git a/aleksis/core/urls.py b/aleksis/core/urls.py
index 51048e5c83bdb6c540254a36754f32cdc3059591..7f0f81b0960174c8df67fb862a38e4d2bdd38164 100644
--- a/aleksis/core/urls.py
+++ b/aleksis/core/urls.py
@@ -16,6 +16,7 @@ from . import views
 from .util.core_helpers import is_celery_enabled
 
 urlpatterns = [
+    path("", include("django_prometheus.urls")),
     path("", include("pwa.urls"), name="pwa"),
     path("about/", views.about, name="about_aleksis"),
     path("admin/", admin.site.urls),
@@ -56,6 +57,7 @@ urlpatterns = [
     path("group/<int:id_>/edit", views.edit_group, name="edit_group_by_id"),
     path("group/<int:id_>/delete", views.delete_group, name="delete_group_by_id"),
     path("", views.index, name="index"),
+    path("dashboard/edit/", views.EditDashboardView.as_view(), name="edit_dashboard"),
     path(
         "notifications/mark-read/<int:id_>",
         views.notification_mark_read,
@@ -152,6 +154,22 @@ urlpatterns = [
         name="preferences_group",
     ),
     path("health/", include(health_urls)),
+    path("dashboard_widgets/", views.DashboardWidgetListView.as_view(), name="dashboard_widgets"),
+    path(
+        "dashboard_widgets/<int:pk>/edit/",
+        views.DashboardWidgetEditView.as_view(),
+        name="edit_dashboard_widget",
+    ),
+    path(
+        "dashboard_widgets/<int:pk>/delete/",
+        views.DashboardWidgetDeleteView.as_view(),
+        name="delete_dashboard_widget",
+    ),
+    path(
+        "dashboard_widgets/<str:app>/<str:model>/new/",
+        views.DashboardWidgetCreateView.as_view(),
+        name="create_dashboard_widget",
+    ),
 ]
 
 # Serve static files from STATIC_ROOT to make it work with runserver
diff --git a/aleksis/core/util/core_helpers.py b/aleksis/core/util/core_helpers.py
index 6428e293c158ecd37af26b8e3a3625b24011a5a0..bef457feee2b0dd53f72591523387d912c4038b1 100644
--- a/aleksis/core/util/core_helpers.py
+++ b/aleksis/core/util/core_helpers.py
@@ -1,5 +1,5 @@
 import os
-import pkgutil
+import sys
 import time
 from datetime import datetime, timedelta
 from importlib import import_module
@@ -8,6 +8,11 @@ from operator import itemgetter
 from typing import Any, Callable, Optional, Sequence, Union
 from uuid import uuid4
 
+if sys.version_info >= (3, 9):
+    from importlib import metadata
+else:
+    import importlib_metadata as metadata
+
 from django.conf import settings
 from django.db.models import Model, QuerySet
 from django.http import HttpRequest
@@ -59,14 +64,8 @@ def dt_show_toolbar(request: HttpRequest) -> bool:
 
 
 def get_app_packages() -> Sequence[str]:
-    """Find all packages within the aleksis.apps namespace."""
-    # Import error are non-fatal here because probably simply no app is installed.
-    try:
-        import aleksis.apps
-    except ImportError:
-        return []
-
-    return [f"aleksis.apps.{pkg[1]}" for pkg in pkgutil.iter_modules(aleksis.apps.__path__)]
+    """Find all registered apps from the setuptools entrypoint."""
+    return [f"{ep.module}.{ep.attr}" for ep in metadata.entry_points().get("aleksis.app", [])]
 
 
 def merge_app_settings(
@@ -81,11 +80,19 @@ def merge_app_settings(
     Note: Only selected names will be imported frm it to minimise impact of
     potentially malicious apps!
     """
-    for pkg in get_app_packages():
-        try:
-            mod_settings = import_module(pkg + ".settings")
-        except ImportError:
-            # Import errors are non-fatal. They mean that the app has no settings.py.
+    for app in get_app_packages():
+        pkg = ".".join(app.split(".")[:-2])
+        mod_settings = None
+        while "." in pkg:
+            try:
+                mod_settings = import_module(pkg + ".settings")
+            except ImportError:
+                # Import errors are non-fatal.
+                pkg = ".".join(pkg.split(".")[:-1])
+                continue
+            break
+        if not mod_settings:
+            # The app does not have settings
             continue
 
         app_setting = getattr(mod_settings, setting, None)
@@ -361,6 +368,18 @@ def handle_uploaded_file(f, filename: str):
             destination.write(chunk)
 
 
+@cache_memoize(3600)
+def get_content_type_by_perm(perm: str) -> Union["ContentType", None]:
+    from django.contrib.contenttypes.models import ContentType  # noqa
+
+    try:
+        return ContentType.objects.get(
+            app_label=perm.split(".", 1)[0], permission__codename=perm.split(".", 1)[1]
+        )
+    except ContentType.DoesNotExist:
+        return None
+
+
 @cache_memoize(3600)
 def queryset_rules_filter(
     obj: Union[HttpRequest, Model], queryset: QuerySet, perm: str
diff --git a/aleksis/core/util/manage.py b/aleksis/core/util/manage.py
new file mode 100644
index 0000000000000000000000000000000000000000..64441f62dfdadbb589b973dcd65633e100bf647b
--- /dev/null
+++ b/aleksis/core/util/manage.py
@@ -0,0 +1,12 @@
+"""Management utilities for an AlekSIS installation."""
+
+import os
+import sys
+
+from django.core.management import execute_from_command_line
+
+
+def aleksis_cmd():
+    """Run django-admin command with correct settings path."""
+    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "aleksis.core.settings")
+    execute_from_command_line(sys.argv)
diff --git a/aleksis/core/util/predicates.py b/aleksis/core/util/predicates.py
index 46fe98c7379034eb07b3bad2a7c442ab4d9e02b5..d107b425bab206b9a1a0c57d0cb4a4e997a82d34 100644
--- a/aleksis/core/util/predicates.py
+++ b/aleksis/core/util/predicates.py
@@ -1,6 +1,5 @@
 from django.contrib.auth.backends import ModelBackend
 from django.contrib.auth.models import User
-from django.contrib.contenttypes.models import ContentType
 from django.db.models import Model
 from django.http import HttpRequest
 
@@ -9,7 +8,7 @@ from guardian.shortcuts import get_objects_for_user
 from rules import predicate
 
 from ..models import Group
-from .core_helpers import get_site_preferences
+from .core_helpers import get_content_type_by_perm, get_site_preferences
 from .core_helpers import has_person as has_person_helper
 from .core_helpers import queryset_rules_filter
 
@@ -65,12 +64,7 @@ def has_any_object(perm: str, klass):
 
     @predicate(name)
     def fn(user: User) -> bool:
-        try:
-            ct_perm = ContentType.objects.get(
-                app_label=perm.split(".", 1)[0], permission__codename=perm.split(".", 1)[1]
-            )
-        except ContentType.DoesNotExist:
-            ct_perm = None
+        ct_perm = get_content_type_by_perm(perm)
         if ct_perm and ct_perm.model_class() == klass:
             return get_objects_for_user(user, perm, klass).exists()
         else:
diff --git a/aleksis/core/views.py b/aleksis/core/views.py
index af2f285a7ec48c5499015c9f54388818e68d2bc1..14cbf2dbeafc7dd84edfadcd5375e97560159011 100644
--- a/aleksis/core/views.py
+++ b/aleksis/core/views.py
@@ -1,13 +1,18 @@
-from typing import Optional
+from typing import Any, Dict, Optional, Type
 
 from django.apps import apps
 from django.conf import settings
+from django.contrib.contenttypes.models import ContentType
 from django.core.exceptions import PermissionDenied
 from django.core.paginator import Paginator
+from django.forms.models import BaseModelForm, modelform_factory
 from django.http import HttpRequest, HttpResponse, HttpResponseNotFound
 from django.shortcuts import get_object_or_404, redirect, render
 from django.urls import reverse_lazy
+from django.utils.decorators import method_decorator
 from django.utils.translation import gettext_lazy as _
+from django.views.decorators.cache import never_cache
+from django.views.generic.base import View
 
 import reversion
 from django_tables2 import RequestConfig, SingleTableView
@@ -17,12 +22,14 @@ from haystack.inputs import AutoQuery
 from haystack.query import SearchQuerySet
 from haystack.views import SearchView
 from health_check.views import MainView
+from reversion import set_user
 from rules.contrib.views import PermissionRequiredMixin, permission_required
 
 from .filters import GroupFilter, PersonFilter
 from .forms import (
     AnnouncementForm,
     ChildGroupsForm,
+    DashboardWidgetOrderFormSet,
     EditAdditionalFieldForm,
     EditGroupForm,
     EditGroupTypeForm,
@@ -33,11 +40,12 @@ from .forms import (
     SchoolTermForm,
     SitePreferenceForm,
 )
-from .mixins import AdvancedCreateView, AdvancedEditView
+from .mixins import AdvancedCreateView, AdvancedDeleteView, AdvancedEditView
 from .models import (
     AdditionalField,
     Announcement,
     DashboardWidget,
+    DashboardWidgetOrder,
     Group,
     GroupType,
     Notification,
@@ -51,6 +59,7 @@ from .registries import (
 )
 from .tables import (
     AdditionalFieldsTable,
+    DashboardWidgetTable,
     GroupsTable,
     GroupTypesTable,
     PersonsTable,
@@ -77,7 +86,7 @@ def index(request: HttpRequest) -> HttpResponse:
     announcements = Announcement.objects.at_time().for_person(request.user.person)
     context["announcements"] = announcements
 
-    widgets = DashboardWidget.objects.filter(active=True)
+    widgets = request.user.person.dashboard_widgets
     media = DashboardWidget.get_media(widgets)
 
     context["widgets"] = widgets
@@ -86,11 +95,6 @@ def index(request: HttpRequest) -> HttpResponse:
     return render(request, "core/index.html", context)
 
 
-def offline(request: HttpRequest) -> HttpResponse:
-    """Offline message for PWA."""
-    return render(request, "core/pages/offline.html")
-
-
 def about(request: HttpRequest) -> HttpResponse:
     """About page listing all apps."""
     context = {}
@@ -111,6 +115,7 @@ class SchoolTermListView(SingleTableView, PermissionRequiredMixin):
     template_name = "core/school_term/list.html"
 
 
+@method_decorator(never_cache, name="dispatch")
 class SchoolTermCreateView(AdvancedCreateView, PermissionRequiredMixin):
     """Create view for school terms."""
 
@@ -122,6 +127,7 @@ class SchoolTermCreateView(AdvancedCreateView, PermissionRequiredMixin):
     success_message = _("The school term has been created.")
 
 
+@method_decorator(never_cache, name="dispatch")
 class SchoolTermEditView(AdvancedEditView, PermissionRequiredMixin):
     """Edit view for school terms."""
 
@@ -203,6 +209,9 @@ def group(request: HttpRequest, id_: int) -> HttpResponse:
     RequestConfig(request).configure(owners_table)
     context["owners_table"] = owners_table
 
+    # Get statistics
+    context["stats"] = group.get_group_stats
+
     return render(request, "core/group/full.html", context)
 
 
@@ -226,6 +235,7 @@ def groups(request: HttpRequest) -> HttpResponse:
     return render(request, "core/group/list.html", context)
 
 
+@never_cache
 @permission_required("core.link_persons_accounts")
 def persons_accounts(request: HttpRequest) -> HttpResponse:
     """View allowing to batch-process linking of users to persons."""
@@ -246,6 +256,7 @@ def persons_accounts(request: HttpRequest) -> HttpResponse:
     return render(request, "core/person/accounts.html", context)
 
 
+@never_cache
 @permission_required("core.assign_child_groups_to_groups")
 def groups_child_groups(request: HttpRequest) -> HttpResponse:
     """View for batch-processing assignment from child groups to groups."""
@@ -283,6 +294,7 @@ def groups_child_groups(request: HttpRequest) -> HttpResponse:
     return render(request, "core/group/child_groups.html", context)
 
 
+@never_cache
 @permission_required("core.edit_person", fn=objectgetter_optional(Person))
 def edit_person(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse:
     """Edit view for a single person, defaulting to logged-in person."""
@@ -305,6 +317,7 @@ def edit_person(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse
     if request.method == "POST":
         if edit_person_form.is_valid():
             with reversion.create_revision():
+                set_user(request.user)
                 edit_person_form.save(commit=True)
             messages.success(request, _("The person has been saved."))
 
@@ -320,6 +333,7 @@ def get_group_by_id(request: HttpRequest, id_: Optional[int] = None):
         return None
 
 
+@never_cache
 @permission_required("core.edit_group", fn=objectgetter_optional(Group, None, False))
 def edit_group(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse:
     """View to edit or create a group."""
@@ -341,6 +355,7 @@ def edit_group(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse:
     if request.method == "POST":
         if edit_group_form.is_valid():
             with reversion.create_revision():
+                set_user(request.user)
                 group = edit_group_form.save(commit=True)
 
             messages.success(request, _("The group has been saved."))
@@ -372,10 +387,11 @@ class SystemStatus(MainView, PermissionRequiredMixin):
 
         if "django_celery_results" in settings.INSTALLED_APPS:
             from django_celery_results.models import TaskResult  # noqa
-            from celery.task.control import inspect  # noqa
 
-            if inspect().registered_tasks():
-                job_list = list(inspect().registered_tasks().values())[0]
+            from .celery import app  # noqa
+
+            if app.control.inspect().registered_tasks():
+                job_list = list(app.control.inspect().registered_tasks().values())[0]
                 for job in job_list:
                     task_results.append(
                         TaskResult.objects.filter(task_name=job).order_by("date_done").last()
@@ -411,6 +427,7 @@ def announcements(request: HttpRequest) -> HttpResponse:
     return render(request, "core/announcement/list.html", context)
 
 
+@never_cache
 @permission_required(
     "core.create_or_edit_announcement", fn=objectgetter_optional(Announcement, None, False)
 )
@@ -478,6 +495,7 @@ class PermissionSearchView(PermissionRequiredMixin, SearchView):
         return render(self.request, self.template, context)
 
 
+@never_cache
 def preferences(
     request: HttpRequest,
     registry_name: str = "person",
@@ -540,6 +558,7 @@ def delete_person(request: HttpRequest, id_: int) -> HttpResponse:
     person = objectgetter_optional(Person)(request, id_)
 
     with reversion.create_revision():
+        set_user(request.user)
         person.save()
 
     person.delete()
@@ -553,6 +572,7 @@ def delete_group(request: HttpRequest, id_: int) -> HttpResponse:
     """View to delete an group."""
     group = objectgetter_optional(Group)(request, id_)
     with reversion.create_revision():
+        set_user(request.user)
         group.save()
 
     group.delete()
@@ -561,6 +581,7 @@ def delete_group(request: HttpRequest, id_: int) -> HttpResponse:
     return redirect("groups")
 
 
+@never_cache
 @permission_required(
     "core.change_additionalfield", fn=objectgetter_optional(AdditionalField, None, False)
 )
@@ -626,6 +647,7 @@ def delete_additional_field(request: HttpRequest, id_: int) -> HttpResponse:
     return redirect("additional_fields")
 
 
+@never_cache
 @permission_required("core.change_grouptype", fn=objectgetter_optional(GroupType, None, False))
 def edit_group_type(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse:
     """View to edit or create a group_type."""
@@ -678,3 +700,133 @@ def delete_group_type(request: HttpRequest, id_: int) -> HttpResponse:
     messages.success(request, _("The group type has been deleted."))
 
     return redirect("group_types")
+
+
+class DashboardWidgetListView(SingleTableView, PermissionRequiredMixin):
+    """Table of all dashboard widgets."""
+
+    model = DashboardWidget
+    table_class = DashboardWidgetTable
+    permission_required = "core.view_dashboardwidget"
+    template_name = "core/dashboard_widget/list.html"
+
+    def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
+        context = super().get_context_data(**kwargs)
+        context["widget_types"] = [
+            (ContentType.objects.get_for_model(m, False), m)
+            for m in DashboardWidget.__subclasses__()
+        ]
+        return context
+
+
+@method_decorator(never_cache, name="dispatch")
+class DashboardWidgetEditView(AdvancedEditView, PermissionRequiredMixin):
+    """Edit view for dashboard widgets."""
+
+    def get_form_class(self) -> Type[BaseModelForm]:
+        return modelform_factory(self.object.__class__, fields=self.fields)
+
+    model = DashboardWidget
+    fields = "__all__"
+    permission_required = "core.edit_dashboardwidget"
+    template_name = "core/dashboard_widget/edit.html"
+    success_url = reverse_lazy("dashboard_widgets")
+    success_message = _("The dashboard widget has been saved.")
+
+
+@method_decorator(never_cache, name="dispatch")
+class DashboardWidgetCreateView(AdvancedCreateView, PermissionRequiredMixin):
+    """Create view for dashboard widgets."""
+
+    def get_model(self, request, *args, **kwargs):
+        app_label = kwargs.get("app")
+        model = kwargs.get("model")
+        ct = get_object_or_404(ContentType, app_label=app_label, model=model)
+        return ct.model_class()
+
+    def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
+        context = super().get_context_data(**kwargs)
+        context["model"] = self.model
+        return context
+
+    def get(self, request, *args, **kwargs):
+        self.model = self.get_model(request, *args, **kwargs)
+        return super().get(request, *args, **kwargs)
+
+    def post(self, request, *args, **kwargs):
+        self.model = self.get_model(request, *args, **kwargs)
+        return super().post(request, *args, **kwargs)
+
+    fields = "__all__"
+    permission_required = "core.add_dashboardwidget"
+    template_name = "core/dashboard_widget/create.html"
+    success_url = reverse_lazy("dashboard_widgets")
+    success_message = _("The dashboard widget has been created.")
+
+
+class DashboardWidgetDeleteView(PermissionRequiredMixin, AdvancedDeleteView):
+    """Delete view for dashboard widgets."""
+
+    model = DashboardWidget
+    permission_required = "core.delete_dashboardwidget"
+    template_name = "core/pages/delete.html"
+    success_url = reverse_lazy("dashboard_widgets")
+    success_message = _("The dashboard widget has been deleted.")
+
+
+class EditDashboardView(View):
+    """View for editing dashboard widget order."""
+
+    def get_context_data(self, request):
+        context = {}
+
+        widgets = request.user.person.dashboard_widgets
+        not_used_widgets = DashboardWidget.objects.exclude(pk__in=[w.pk for w in widgets])
+        context["widgets"] = widgets
+        context["not_used_widgets"] = not_used_widgets
+
+        order = 10
+        initial = []
+        for widget in widgets:
+            initial.append({"pk": widget, "order": order})
+            order += 10
+        for widget in not_used_widgets:
+            initial.append({"pk": widget, "order": 0})
+
+        formset = DashboardWidgetOrderFormSet(
+            request.POST or None, initial=initial, prefix="widget_order"
+        )
+        context["formset"] = formset
+
+        return context
+
+    def post(self, request):
+        context = self.get_context_data(request)
+
+        if context["formset"].is_valid():
+            added_objects = []
+            for form in context["formset"]:
+                if not form.cleaned_data["order"]:
+                    continue
+
+                obj, created = DashboardWidgetOrder.objects.update_or_create(
+                    widget=form.cleaned_data["pk"],
+                    person=request.user.person,
+                    defaults={"order": form.cleaned_data["order"]},
+                )
+
+                added_objects.append(obj.pk)
+
+            DashboardWidgetOrder.objects.filter(person=request.user.person).exclude(
+                pk__in=added_objects
+            ).delete()
+
+            messages.success(
+                request, _("Your dashboard configuration has been saved successfully.")
+            )
+            return redirect("index")
+
+    def get(self, request):
+        context = self.get_context_data(request)
+
+        return render(request, "core/edit_dashboard.html", context=context)
diff --git a/apps/official/AlekSIS-App-Chronos b/apps/official/AlekSIS-App-Chronos
deleted file mode 160000
index b081e4d32922d38e4f6229f33e7cecf5a1b408e0..0000000000000000000000000000000000000000
--- a/apps/official/AlekSIS-App-Chronos
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit b081e4d32922d38e4f6229f33e7cecf5a1b408e0
diff --git a/apps/official/AlekSIS-App-DashboardFeeds b/apps/official/AlekSIS-App-DashboardFeeds
deleted file mode 160000
index 85814a4f6c4fc1d94a853b46be8544c11b5767ee..0000000000000000000000000000000000000000
--- a/apps/official/AlekSIS-App-DashboardFeeds
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 85814a4f6c4fc1d94a853b46be8544c11b5767ee
diff --git a/apps/official/AlekSIS-App-Hjelp b/apps/official/AlekSIS-App-Hjelp
deleted file mode 160000
index 31500a0580b6839122df35f114e471334903d26e..0000000000000000000000000000000000000000
--- a/apps/official/AlekSIS-App-Hjelp
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 31500a0580b6839122df35f114e471334903d26e
diff --git a/apps/official/AlekSIS-App-LDAP b/apps/official/AlekSIS-App-LDAP
deleted file mode 160000
index 487a3423abc8b8bc8a4d89e474336c76b5cafa61..0000000000000000000000000000000000000000
--- a/apps/official/AlekSIS-App-LDAP
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 487a3423abc8b8bc8a4d89e474336c76b5cafa61
diff --git a/apps/official/AlekSIS-App-Untis b/apps/official/AlekSIS-App-Untis
deleted file mode 160000
index 0b417dfef095fb7a52f03301abb66066a841b467..0000000000000000000000000000000000000000
--- a/apps/official/AlekSIS-App-Untis
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 0b417dfef095fb7a52f03301abb66066a841b467
diff --git a/ci/build_dist.yml b/ci/build_dist.yml
deleted file mode 100644
index 939cdf326decd7642edff67a62f84ff028f30d56..0000000000000000000000000000000000000000
--- a/ci/build_dist.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-build_dist:
-  stage: build
-  script:
-    - tox -e build
-  artifacts:
-    paths:
-      - dist/
diff --git a/ci/build_docker.yml b/ci/build_docker.yml
deleted file mode 100644
index 92a1572c6ac4d1878110500f458262a6a7c5bdf4..0000000000000000000000000000000000000000
--- a/ci/build_docker.yml
+++ /dev/null
@@ -1,22 +0,0 @@
-build_docker:
-  stage: build
-  image:
-    name: gcr.io/kaniko-project/executor:debug
-    entrypoint: [""]
-  script:
-    - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" >/kaniko/.docker/config.json
-    - /kaniko/executor
-       --context $CI_PROJECT_DIR
-       --dockerfile $CI_PROJECT_DIR/Dockerfile
-       --destination $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
-       --cache=true
-       --cleanup
-    - /kaniko/executor
-       --context $CI_PROJECT_DIR/docker/nginx
-       --dockerfile $CI_PROJECT_DIR/docker/nginx/Dockerfile
-       --destination $CI_REGISTRY_IMAGE/nginx:$CI_COMMIT_REF_NAME
-       --cache=true
-       --cleanup
-  only:
-    - master
-    - tags
diff --git a/ci/deploy.yml b/ci/deploy.yml
deleted file mode 100644
index 249e4c3333cf7c8800600a7801727110305a487e..0000000000000000000000000000000000000000
--- a/ci/deploy.yml
+++ /dev/null
@@ -1,30 +0,0 @@
-deploy_demo-master:
-  stage: deploy
-  environment:
-    name: demo/master
-    url: http://demo-master.aleksis.org
-  before_script:
-    - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
-    - eval $(ssh-agent -s)
-    - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
-    - mkdir -p ~/.ssh
-    - chmod 700 ~/.ssh
-    - echo "$SSH_KNOWN_HOSTS" >~/.ssh/known_hosts
-    - chmod 644 ~/.ssh/known_hosts
-  script:
-    - grep -v "build:" docker-compose.yml | ssh root@demo-master.aleksis.org
-       env ALEKSIS_IMAGE_TAG=${CI_COMMIT_REF_NAME}
-       docker-compose
-        -p aleksis-${CI_ENVIRONMENT_SLUG}
-        -f /dev/stdin
-        pull
-    - grep -v "build:" docker-compose.yml | ssh root@demo-master.aleksis.org
-       env ALEKSIS_IMAGE_TAG=${CI_COMMIT_REF_NAME}
-           NGINX_HTTP_PORT=80
-           ALEKSIS_maintenance__debug=true
-       docker-compose
-        -p aleksis-${CI_ENVIRONMENT_SLUG}
-        -f /dev/stdin
-        up -d
-  only:
-    - master
diff --git a/ci/general.yml b/ci/general.yml
deleted file mode 100644
index 58891983dbeda6b9c8f49dc07e490a021a4c31dd..0000000000000000000000000000000000000000
--- a/ci/general.yml
+++ /dev/null
@@ -1,20 +0,0 @@
-image: registry.edugit.org/teckids/team-sysadmin/docker-images/python-pimped:latest
-
-stages:
-  - test
-  - build
-  - deploy
-
-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
diff --git a/ci/pages.yml b/ci/pages.yml
deleted file mode 100644
index 31d8866e1d1c1b665d12ce15da9a7c918440aa7a..0000000000000000000000000000000000000000
--- a/ci/pages.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-pages:
-  stage: deploy
-  before_script:
-    - cp -r .tox/screenshots/firefox docs/screenshots
-  script:
-    - export LC_ALL=en_GB.utf8
-    - tox -e docs -- BUILDDIR=../public/docs
-  artifacts:
-    paths:
-    - public/
-  only:
-    - master
diff --git a/ci/test.yml b/ci/test.yml
deleted file mode 100644
index 2ab64bc324536c8716c5cb8641d09d9c43dbf844..0000000000000000000000000000000000000000
--- a/ci/test.yml
+++ /dev/null
@@ -1,26 +0,0 @@
-test:
-  stage: test
-  services:
-    - name: selenium/standalone-firefox
-      alias: selenium
-  before_script:
-    - adduser --disabled-password --gecos "Test User" testuser
-    - chown -R testuser .
-  script:
-    - sudo apt update
-    - sudo apt install python3-ldap libldap2-dev libssl-dev libsasl2-dev python3.7-dev -y
-    - 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
diff --git a/docker-compose.yml b/docker-compose.yml
deleted file mode 100644
index 56d9d1edb57546360eaf73a18d2a95097b950288..0000000000000000000000000000000000000000
--- a/docker-compose.yml
+++ /dev/null
@@ -1,74 +0,0 @@
-version: '3'
-
-services:
-  db:
-    image: postgres:12
-    volumes:
-      - postgres_data:/var/lib/postgresql/data/
-    environment:
-      - POSTGRES_USER=aleksis
-      - POSTGRES_DB=aleksis
-  memcached:
-    image: memcached:latest
-  app:
-    build: .
-    image: registry.edugit.org/aleksis/official/aleksis:${ALEKSIS_IMAGE_TAG:-latest}
-    volumes:
-      - aleksis_data:/var/lib/aleksis/
-      - aleksis_static:/usr/share/aleksis/static/
-    environment:
-      - ALEKSIS_http__allowed_hosts="['*']"
-      - ALEKSIS_caching__memcached__address=memcached:11211
-      - ALEKSIS_caching__memcached__enabled=true
-      - ALEKSIS_database__host=db
-      - ALEKSIS_maintenance__debug=${ALEKSIS_maintenance__debug:-false}
-      - ALEKSIS_backup__location=/var/lib/aleksis/backups
-    depends_on:
-      - db
-      - memcached
-  worker:
-    build: .
-    image: registry.edugit.org/aleksis/official/aleksis:${ALEKSIS_IMAGE_TAG:-latest}
-    volumes:
-      - aleksis_data:/var/lib/aleksis/
-      - aleksis_static:/usr/share/aleksis/static/
-    command: celery_worker
-    environment:
-      - ALEKSIS_http__allowed_hosts="['*']"
-      - ALEKSIS_caching__memcached__address=memcached:11211
-      - ALEKSIS_caching__memcached__enabled=true
-      - ALEKSIS_database__host=db
-      - ALEKSIS_maintenance__debug=${ALEKSIS_maintenance__debug:-false}
-      - ALEKSIS_backup__location=/var/lib/aleksis/backups
-    depends_on:
-      - app
-  scheduler:
-    build: .
-    image: registry.edugit.org/aleksis/official/aleksis:${ALEKSIS_IMAGE_TAG:-latest}
-    volumes:
-      - aleksis_data:/var/lib/aleksis/
-      - aleksis_static:/usr/share/aleksis/static/
-    command: celery_beat
-    environment:
-      - ALEKSIS_http__allowed_hosts="['*']"
-      - ALEKSIS_caching__memcached__address=memcached:11211
-      - ALEKSIS_caching__memcached__enabled=true
-      - ALEKSIS_database__host=db
-      - ALEKSIS_maintenance__debug=${ALEKSIS_maintenance__debug:-false}
-    depends_on:
-      - worker
-  web:
-    build: ./docker/nginx
-    image: registry.edugit.org/aleksis/official/aleksis/nginx:${ALEKSIS_IMAGE_TAG:-latest}
-    volumes:
-      - aleksis_data:/var/lib/aleksis/
-      - aleksis_static:/usr/share/aleksis/static/:ro
-    ports:
-      - ${NGINX_HTTP_PORT:-8080}:80
-    depends_on:
-      - app
-
-volumes:
-  postgres_data:
-  aleksis_data:
-  aleksis_static:
diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh
deleted file mode 100755
index 4b68fa884420b1a40ed34d4bd4410aabd2ac1e3f..0000000000000000000000000000000000000000
--- a/docker/entrypoint.sh
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/bin/bash
-
-GUNICORN_BIND=${GUNICORN_BIND:-0.0.0.0:8000}
-
-export ALEKSIS_database__host=${ALEKSIS_database__host:-127.0.0.1}
-export ALEKSIS_database__port=${ALEKSIS_database__port:-5432}
-
-if [[ -z $ALEKSIS_secret_key ]]; then
-    if [[ ! -e /var/lib/aleksis/secret_key ]]; then
-	touch /var/lib/aleksis/secret_key; chmod 600 /var/lib/aleksis/secret_key
-	LC_ALL=C tr -dc 'A-Za-z0-9!"#$%&'\''()*+,-./:;<=>?@[\]^_`{|}~' </dev/urandom | head -c 64 >/var/lib/aleksis/secret_key
-    fi
-    ALEKSIS_secret_key=$(</var/lib/aleksis/secret_key)
-fi
-
-while ! nc -z $ALEKSIS_database__host $ALEKSIS_database__port; do
-    sleep 0.1
-done
-
-python manage.py compilescss
-python manage.py collectstatic --no-input --clear
-python manage.py migrate
-python manage.py createinitialrevisions
-
-ARG=${$1:-"gunicorn"}
-
-if [ $ARG = "celery_worker" ]; then
-    exec celery -A aleksis.core worker -l info
-elif [ $ARG = "celery_beat" ]; then
-    exec celery -A aleksis.core beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler
-else
-    exec gunicorn aleksis.core.wsgi --bind ${GUNICORN_BIND}
-fi
diff --git a/docker/nginx/Dockerfile b/docker/nginx/Dockerfile
deleted file mode 100644
index 18b91b5f17398ee5b2dc6e95bdedc225eb934821..0000000000000000000000000000000000000000
--- a/docker/nginx/Dockerfile
+++ /dev/null
@@ -1,6 +0,0 @@
-FROM nginx
-
-RUN rm /etc/nginx/conf.d/default.conf
-COPY nginx.conf /etc/nginx/conf.d/default.conf
-
-RUN mkdir /var/lib/aleksis
diff --git a/docker/nginx/nginx.conf b/docker/nginx/nginx.conf
deleted file mode 100644
index 759f385041ce7bd7dc06b487ff2dfb2c43635482..0000000000000000000000000000000000000000
--- a/docker/nginx/nginx.conf
+++ /dev/null
@@ -1,23 +0,0 @@
-upstream aleksis {
-    server app:8000;
-}
-
-server {
-    listen 80;
-
-    location /media/ {
-        alias /var/lib/aleksis/media/;
-    }
-
-    location /static/ {
-        alias /usr/share/aleksis/static/;
-    }
-
-    location / {
-        proxy_pass http://aleksis;
-        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
-        proxy_set_header Host $host;
-        proxy_redirect off;
-    }
-
-}
diff --git a/docs/admin/01_config.rst b/docs/admin/01_config.rst
index 089c4416b031f7cf78d92ef8a57e3d86084f9094..a8e593b4afb1bf98b980c1bb548a75fb513b6bbf 100644
--- a/docs/admin/01_config.rst
+++ b/docs/admin/01_config.rst
@@ -27,25 +27,24 @@ configuration by topic.
 
 A configuration file might look like this::
 
-  [default]
   secret_key = "VerySecretKeyForSessionSecurity"
 
-  [default.http]
+  [http]
   allowed_hosts = [ "aleksis.myschool.example.com", "localhost" ]
 
-  [default.database]
+  [database]
   name = "aleksis"
   user = "aleksis"
   password = "SuperSecretPassword"
 
-  [default.caching]
+  [caching]
   memcached = { enabled = true, address = "127.0.0.1" }
 
 The `secret_key` setting above defines a single value. The following `http`
 section defines a table (cf. a dictionary) in one way, and you can see the
 second form of such a table in the `memcached` setting (we could as well
-have defined another section called `[default.caching.memcached]` and placed
-`enabled` and `address` below it as scalars).
+have defined another section and placed `enabled` and `address` below it
+as scalars).
 
 This can be a bit confusing, so this documentation will explain how to
 configure AlekSIS on a per-feature basis.
diff --git a/docs/admin/02_ldap.rst b/docs/admin/02_ldap.rst
index 02f0e6d9ee0a19a0a30ffd91a2b19cbc06e41860..b8fbd657dedc3d3e7a6ef04a7fa36d9b7f1b66bf 100644
--- a/docs/admin/02_ldap.rst
+++ b/docs/admin/02_ldap.rst
@@ -25,17 +25,16 @@ configuration file. For example, add something like the following to your
 configuration (normally in ``/etc/aleksis``; you can either append to an
 existing file or add a new one)::
 
-    # Authentication via LDAP, optional
-    [default.ldap]
-    uri = "ldaps://ldap.myschool.edu"
-    bind = { dn = "cn=reader,dc=myschool,dc=edu", password = "secret" }
-    map = { first_name = "givenName", last_name = "sn", email = "mail" }
-
-    [default.ldap.users]
-    search = { base = "ou=people,dc=myschool,dc=edu", filter = "(uid=%(user)s)" }
-
-    [default.ldap.groups]
-    search = { base = "ou=groups,dc=myschool,dc=edu" }
-    type = "groupOfNames"
-    # Users in group "admins" are superusers
-    flags = { is_superuser = "cn=admins,ou=groups,dc=myschool,dc=edu" }
+  [ldap]
+  uri = "ldaps://ldap.myschool.edu"
+  bind = { dn = "cn=reader,dc=myschool,dc=edu", password = "secret" }
+
+  [ldap.users]
+  search = { base = "ou=people,dc=myschool,dc=edu", filter = "(uid=%(user)s)" }
+  map = { first_name = "givenName", last_name = "sn", email = "mail" }
+
+  [ldap.groups]
+  search = { base = "ou=groups,dc=myschool,dc=edu" }
+  type = "groupOfNames"
+  # Users in group "admins" are superusers
+  flags = { is_superuser = "cn=admins,ou=groups,dc=myschool,dc=edu" }
diff --git a/docs/admin/03_psql.rst b/docs/admin/03_psql.rst
index 8c544041fdce1dc6ee83d3311526bb3561dcb487..6d5ab57ae3b157ca906f3cfb7df1d3897efdfbd7 100644
--- a/docs/admin/03_psql.rst
+++ b/docs/admin/03_psql.rst
@@ -32,7 +32,7 @@ Configure AlekSIS to use PostgreSQL
 
 Fill in the configuration under `/etc/aleksis/aleksis.toml` (or a file with any other name in this directory)::
 
-  [default.database]
+  [database]
   host = "localhost"
   name = "aleksis"
   username = "aleksis"
diff --git a/docs/admin/04_monitoring.rst b/docs/admin/04_monitoring.rst
new file mode 100644
index 0000000000000000000000000000000000000000..1b80a43be057a285d223be803bc347ae2758fdc4
--- /dev/null
+++ b/docs/admin/04_monitoring.rst
@@ -0,0 +1,39 @@
+Monitoring
+##########
+
+Prometheus
+**********
+
+AlekSIS provides a metric endpoint at `/metrics`, so you can scrape metrics in
+your Prometheus instance.
+
+Available metrics
+=================
+
+The exporter provides metrics about responses and requests, e.g.  statistics
+about response codes, request latency and requests per view.  It also
+provides data about database operations.
+
+Prometheus config to get metrics
+================================
+
+To get metrics of your AlekSIS instance, just add the following to your
+`prometheus.yml`::
+
+  - job_name: aleksis
+    static_configs:
+      - targets: ['my.aleksis-instance.com']
+    metrics_path: /metrics
+
+
+Grafana
+*******
+
+Visualise metrics with Grafana
+==============================
+
+If you want to visualise your AlekSIS metrics with Grafana, you can use one
+of the public available Grafana dashboards, for example the following one,
+or just write your own.
+
+https://grafana.com/grafana/dashboards/9528
diff --git a/manage.py b/manage.py
deleted file mode 100755
index 023e9fd47cbc4c0308997db1061aace8bd8afb61..0000000000000000000000000000000000000000
--- a/manage.py
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/usr/bin/env python3
-import os
-import sys
-
-if __name__ == "__main__":
-    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "aleksis.core.settings")
-    try:
-        from django.core.management import execute_from_command_line
-    except ImportError as exc:
-        raise ImportError(
-            "Couldn't import Django. Are you sure it's installed and "
-            "available on your PYTHONPATH environment variable? Did you "
-            "forget to activate a virtual environment?"
-        ) from exc
-    execute_from_command_line(sys.argv)
diff --git a/poetry.lock b/poetry.lock
index a6e35ccdfb68b098d8fec8eafc152cd1915f049a..be0e61cd822675c59e9c9fc318fd2f104c7f900a 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,120 +1,142 @@
 [[package]]
-category = "dev"
-description = "A configurable sidebar-enabled Sphinx theme"
 name = "alabaster"
+version = "0.7.12"
+description = "A configurable sidebar-enabled Sphinx theme"
+category = "dev"
 optional = false
 python-versions = "*"
-version = "0.7.12"
 
 [[package]]
-category = "main"
-description = "Low-level AMQP client for Python (fork of amqplib)."
+name = "aleksis-builddeps"
+version = "1"
+description = "AlekSIS (School Information System) — Build/Dev dependencies for apps"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+black = ">=19.10b0,<20.0"
+django-stubs = ">=1.1,<2.0"
+flake8 = ">=3.7.9,<4.0.0"
+flake8-bandit = ">=2.1.2,<3.0.0"
+flake8-black = ">=0.2.0,<0.3.0"
+flake8-builtins = ">=1.4.1,<2.0.0"
+flake8-django = ">=1.0.0,<2.0.0"
+flake8-docstrings = ">=1.5.0,<2.0.0"
+flake8-fixme = ">=1.1.1,<2.0.0"
+flake8-isort = ">=4.0.0,<5.0.0"
+flake8-mypy = ">=17.8.0,<18.0.0"
+flake8-rst-docstrings = ">=0.0.13,<0.0.14"
+isort = ">=5.0.0,<6.0.0"
+pytest = ">=6.0,<7.0"
+pytest-cov = ">=2.8.1,<3.0.0"
+pytest-django = ">=3.7,<4.0"
+pytest-django-testing-postgresql = ">=0.1,<0.2"
+pytest-sugar = ">=0.9.2,<0.10.0"
+safety = ">=1.8.5,<2.0.0"
+selenium = ">=3.141.0,<4.0.0"
+sphinx = ">=3.0,<4.0"
+sphinx-autodoc-typehints = ">=1.7,<2.0"
+sphinxcontrib-django = ">=0.5.0,<0.6.0"
+
+[package.source]
+type = "legacy"
+url = "https://edugit.org/api/v4/projects/461/packages/pypi/simple"
+reference = "gitlab"
+
+[[package]]
 name = "amqp"
+version = "5.0.2"
+description = "Low-level AMQP client for Python (fork of amqplib)."
+category = "main"
 optional = true
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "2.6.1"
+python-versions = ">=3.6"
 
 [package.dependencies]
-vine = ">=1.1.3,<5.0.0a1"
+vine = "5.0.0"
 
 [[package]]
-category = "dev"
-description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
 name = "appdirs"
+version = "1.4.4"
+description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
+category = "dev"
 optional = false
 python-versions = "*"
-version = "1.4.4"
 
 [[package]]
-category = "main"
-description = "ASGI specs, helper code, and adapters"
 name = "asgiref"
+version = "3.3.1"
+description = "ASGI specs, helper code, and adapters"
+category = "main"
 optional = false
 python-versions = ">=3.5"
-version = "3.2.10"
 
 [package.extras]
 tests = ["pytest", "pytest-asyncio"]
 
 [[package]]
-category = "dev"
-description = "Atomic file writes."
-marker = "sys_platform == \"win32\""
 name = "atomicwrites"
+version = "1.4.0"
+description = "Atomic file writes."
+category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "1.4.0"
 
 [[package]]
-category = "dev"
-description = "Classes Without Boilerplate"
 name = "attrs"
+version = "20.3.0"
+description = "Classes Without Boilerplate"
+category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "20.2.0"
 
 [package.extras]
-dev = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "sphinx-rtd-theme", "pre-commit"]
-docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"]
-tests = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"]
-tests_no_zope = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"]
+dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"]
+docs = ["furo", "sphinx", "zope.interface"]
+tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"]
+tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"]
 
 [[package]]
-category = "main"
-description = "Internationalization utilities"
 name = "babel"
+version = "2.9.0"
+description = "Internationalization utilities"
+category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "2.8.0"
 
 [package.dependencies]
 pytz = ">=2015.7"
 
 [[package]]
-category = "dev"
-description = "Security oriented static analyser for python code."
 name = "bandit"
+version = "1.7.0"
+description = "Security oriented static analyser for python code."
+category = "dev"
 optional = false
-python-versions = "*"
-version = "1.6.2"
+python-versions = ">=3.5"
 
 [package.dependencies]
+colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""}
 GitPython = ">=1.0.1"
-PyYAML = ">=3.13"
-colorama = ">=0.3.9"
+PyYAML = ">=5.3.1"
 six = ">=1.10.0"
 stevedore = ">=1.20.0"
 
 [[package]]
-category = "main"
-description = "Screen-scraping library"
-name = "beautifulsoup4"
-optional = false
-python-versions = "*"
-version = "4.9.1"
-
-[package.dependencies]
-soupsieve = [">1.2", "<2.0"]
-
-[package.extras]
-html5lib = ["html5lib"]
-lxml = ["lxml"]
-
-[[package]]
-category = "main"
-description = "Python multiprocessing fork with improvements and bugfixes"
 name = "billiard"
+version = "3.6.3.0"
+description = "Python multiprocessing fork with improvements and bugfixes"
+category = "main"
 optional = true
 python-versions = "*"
-version = "3.6.3.0"
 
 [[package]]
-category = "dev"
-description = "The uncompromising code formatter."
 name = "black"
+version = "19.10b0"
+description = "The uncompromising code formatter."
+category = "dev"
 optional = false
 python-versions = ">=3.6"
-version = "19.10b0"
 
 [package.dependencies]
 appdirs = "*"
@@ -129,12 +151,12 @@ typed-ast = ">=1.4.0"
 d = ["aiohttp (>=3.3.2)", "aiohttp-cors"]
 
 [[package]]
-category = "main"
-description = "An easy safelist-based HTML-sanitizing tool."
 name = "bleach"
+version = "3.2.1"
+description = "An easy safelist-based HTML-sanitizing tool."
+category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "3.1.5"
 
 [package.dependencies]
 packaging = "*"
@@ -142,98 +164,96 @@ six = ">=1.9.0"
 webencodings = "*"
 
 [[package]]
-category = "main"
-description = "Define boolean algebras, create and parse boolean expressions and create custom boolean DSL."
 name = "boolean.py"
+version = "3.8"
+description = "Define boolean algebras, create and parse boolean expressions and create custom boolean DSL."
+category = "main"
 optional = false
 python-versions = "*"
-version = "3.8"
 
 [[package]]
-category = "main"
-description = "Utilities for working with calendar weeks in Python and Django"
 name = "calendarweek"
+version = "0.4.7"
+description = "Utilities for working with calendar weeks in Python and Django"
+category = "main"
 optional = false
 python-versions = ">=3.7,<4.0"
-version = "0.4.5"
 
 [package.extras]
 django = ["Django (>=2.2,<4.0)"]
 
 [[package]]
-category = "main"
-description = "Distributed Task Queue."
 name = "celery"
+version = "5.0.5"
+description = "Distributed Task Queue."
+category = "main"
 optional = true
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "4.4.7"
+python-versions = ">=3.6,"
 
 [package.dependencies]
 billiard = ">=3.6.3.0,<4.0"
-kombu = ">=4.6.10,<4.7"
+click = ">=7.0,<8.0"
+click-didyoumean = ">=0.0.3"
+click-plugins = ">=1.1.1"
+click-repl = ">=0.1.6"
+Django = {version = ">=1.11", optional = true, markers = "extra == \"django\""}
+kombu = ">=5.0.0,<6.0"
 pytz = ">0.0-dev"
-vine = "1.3.0"
-
-[package.dependencies.Django]
-optional = true
-version = ">=1.11"
-
-[package.dependencies.redis]
-optional = true
-version = ">=3.2.0"
+redis = {version = ">=3.2.0", optional = true, markers = "extra == \"redis\""}
+vine = ">=5.0.0,<6.0"
 
 [package.extras]
 arangodb = ["pyArango (>=1.3.2)"]
 auth = ["cryptography"]
-azureblockblob = ["azure-storage (0.36.0)", "azure-common (1.1.5)", "azure-storage-common (1.1.0)"]
+azureblockblob = ["azure-storage (==0.36.0)", "azure-common (==1.1.5)", "azure-storage-common (==1.1.0)"]
 brotli = ["brotli (>=1.0.0)", "brotlipy (>=0.7.0)"]
 cassandra = ["cassandra-driver (<3.21.0)"]
 consul = ["python-consul"]
-cosmosdbsql = ["pydocumentdb (2.3.2)"]
-couchbase = ["couchbase-cffi (<3.0.0)", "couchbase (<3.0.0)"]
+cosmosdbsql = ["pydocumentdb (==2.3.2)"]
+couchbase = ["couchbase (>=3.0.0)"]
 couchdb = ["pycouchdb"]
 django = ["Django (>=1.11)"]
 dynamodb = ["boto3 (>=1.9.178)"]
 elasticsearch = ["elasticsearch"]
-eventlet = ["eventlet (>=0.24.1)"]
-gevent = ["gevent"]
+eventlet = ["eventlet (>=0.26.1)"]
+gevent = ["gevent (>=1.0.0)"]
 librabbitmq = ["librabbitmq (>=1.5.0)"]
 lzma = ["backports.lzma"]
 memcache = ["pylibmc"]
-mongodb = ["pymongo (>=3.3.0)"]
+mongodb = ["pymongo[srv] (>=3.3.0)"]
 msgpack = ["msgpack"]
 pymemcache = ["python-memcached"]
 pyro = ["pyro4"]
+pytest = ["pytest-celery"]
 redis = ["redis (>=3.2.0)"]
-riak = ["riak (>=2.0)"]
 s3 = ["boto3 (>=1.9.125)"]
 slmq = ["softlayer-messaging (>=1.0.3)"]
 solar = ["ephem"]
 sqlalchemy = ["sqlalchemy"]
-sqs = ["boto3 (>=1.9.125)", "pycurl (7.43.0.5)"]
+sqs = ["boto3 (>=1.9.125)", "pycurl (==7.43.0.5)"]
 tblib = ["tblib (>=1.3.0)", "tblib (>=1.5.0)"]
 yaml = ["PyYAML (>=3.10)"]
 zookeeper = ["kazoo (>=1.3.1)"]
 zstd = ["zstandard"]
 
 [[package]]
-category = "main"
-description = "An app for integrating Celery with Haystack."
 name = "celery-haystack"
+version = "0.10"
+description = "An app for integrating Celery with Haystack."
+category = "main"
 optional = true
 python-versions = "*"
-version = "0.10"
 
 [package.dependencies]
 django-appconf = ">=0.4.1"
 
 [[package]]
-category = "main"
-description = "Drop in, configurable, dependency-free progress bars for your Django/Celery applications."
 name = "celery-progress"
+version = "0.0.14"
+description = "Drop in, configurable, dependency-free progress bars for your Django/Celery applications."
+category = "main"
 optional = false
 python-versions = "*"
-version = "0.0.12"
 
 [package.extras]
 rabbitmq = ["channels-rabbitmq"]
@@ -241,89 +261,126 @@ redis = ["channels-redis"]
 websockets = ["channels"]
 
 [[package]]
-category = "main"
-description = "Python package for providing Mozilla's CA Bundle."
 name = "certifi"
+version = "2020.12.5"
+description = "Python package for providing Mozilla's CA Bundle."
+category = "main"
 optional = false
 python-versions = "*"
-version = "2020.6.20"
 
 [[package]]
-category = "main"
-description = "Universal encoding detector for Python 2 and 3"
 name = "chardet"
+version = "4.0.0"
+description = "Universal encoding detector for Python 2 and 3"
+category = "main"
 optional = false
-python-versions = "*"
-version = "3.0.4"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 
 [[package]]
-category = "main"
-description = "Composable command line interface toolkit"
 name = "click"
+version = "7.1.2"
+description = "Composable command line interface toolkit"
+category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "7.1.2"
 
 [[package]]
+name = "click-didyoumean"
+version = "0.0.3"
+description = "Enable git-like did-you-mean feature in click."
 category = "main"
-description = "Cross-platform colored terminal text."
-marker = "platform_system == \"Windows\" or sys_platform == \"win32\""
+optional = true
+python-versions = "*"
+
+[package.dependencies]
+click = "*"
+
+[[package]]
+name = "click-plugins"
+version = "1.1.1"
+description = "An extension module for click to enable registering CLI commands via setuptools entry-points."
+category = "main"
+optional = true
+python-versions = "*"
+
+[package.dependencies]
+click = ">=4.0"
+
+[package.extras]
+dev = ["pytest (>=3.6)", "pytest-cov", "wheel", "coveralls"]
+
+[[package]]
+name = "click-repl"
+version = "0.1.6"
+description = "REPL plugin for Click"
+category = "main"
+optional = true
+python-versions = "*"
+
+[package.dependencies]
+click = "*"
+prompt-toolkit = "*"
+six = "*"
+
+[[package]]
 name = "colorama"
+version = "0.4.4"
+description = "Cross-platform colored terminal text."
+category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "0.4.3"
 
 [[package]]
-category = "main"
-description = "converts and manipulates various color representation (HSL, RVB, web, X11, ...)"
 name = "colour"
+version = "0.1.5"
+description = "converts and manipulates various color representation (HSL, RVB, web, X11, ...)"
+category = "main"
 optional = false
 python-versions = "*"
-version = "0.1.5"
 
 [package.extras]
 test = ["nose"]
 
 [[package]]
-category = "main"
-description = "Config file reading, writing and validation."
 name = "configobj"
+version = "5.0.6"
+description = "Config file reading, writing and validation."
+category = "main"
 optional = false
 python-versions = "*"
-version = "5.0.6"
 
 [package.dependencies]
 six = "*"
 
 [[package]]
-category = "dev"
-description = "Code coverage measurement for Python"
 name = "coverage"
+version = "5.3"
+description = "Code coverage measurement for Python"
+category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
-version = "5.2.1"
 
 [package.extras]
 toml = ["toml"]
 
 [[package]]
-category = "dev"
-description = "Use Database URLs in your Django Application."
 name = "dj-database-url"
+version = "0.5.0"
+description = "Use Database URLs in your Django Application."
+category = "dev"
 optional = false
 python-versions = "*"
-version = "0.5.0"
 
 [[package]]
-category = "main"
-description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design."
 name = "django"
+version = "3.1.4"
+description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design."
+category = "main"
 optional = false
 python-versions = ">=3.6"
-version = "3.1.1"
 
 [package.dependencies]
-asgiref = ">=3.2.10,<3.3.0"
+asgiref = ">=3.2.10,<4"
 pytz = "*"
 sqlparse = ">=0.2.2"
 
@@ -332,94 +389,105 @@ argon2 = ["argon2-cffi (>=16.1.0)"]
 bcrypt = ["bcrypt"]
 
 [[package]]
-category = "main"
-description = "Include JavaScript libraries with readable template tags"
 name = "django-any-js"
+version = "1.0.3.post0"
+description = "Include JavaScript libraries with readable template tags"
+category = "main"
 optional = false
 python-versions = "*"
-version = "1.0.3.post0"
 
 [package.dependencies]
 Django = ">=1.11"
 
 [[package]]
-category = "main"
-description = "A helper class for handling configuration defaults of packaged apps gracefully."
 name = "django-appconf"
+version = "1.0.4"
+description = "A helper class for handling configuration defaults of packaged apps gracefully."
+category = "main"
 optional = false
 python-versions = "*"
-version = "1.0.4"
 
 [package.dependencies]
 django = "*"
 
 [[package]]
-category = "main"
-description = "Django LDAP authentication backend."
 name = "django-auth-ldap"
+version = "2.2.0"
+description = "Django LDAP authentication backend."
+category = "main"
 optional = true
 python-versions = ">=3.5"
-version = "2.2.0"
 
 [package.dependencies]
 Django = ">=1.11"
 python-ldap = ">=3.1"
 
 [[package]]
-category = "main"
-description = "Easily use bleach with Django models and templates"
 name = "django-bleach"
+version = "0.6.1"
+description = "Easily use bleach with Django models and templates"
+category = "main"
 optional = false
 python-versions = "*"
-version = "0.6.1"
 
 [package.dependencies]
-Django = ">=1.11"
 bleach = ">=1.5.0"
+Django = ">=1.11"
 
 [[package]]
-category = "main"
-description = "Bulk update using one query over Django ORM."
 name = "django-bulk-update"
+version = "2.2.0"
+description = "Bulk update using one query over Django ORM."
+category = "main"
 optional = false
 python-versions = "*"
-version = "2.2.0"
 
 [package.dependencies]
 Django = ">=1.8"
 
 [[package]]
+name = "django-cachalot"
+version = "2.3.3"
+description = "Caches your Django ORM queries and automatically invalidates them."
 category = "main"
-description = "Django utility for a memoization decorator that uses the Django cache framework."
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+Django = ">=2"
+
+[[package]]
 name = "django-cache-memoize"
+version = "0.1.7"
+description = "Django utility for a memoization decorator that uses the Django cache framework."
+category = "main"
 optional = false
 python-versions = ">=3.4"
-version = "0.1.7"
 
 [package.extras]
 dev = ["flake8", "tox", "twine", "therapist", "black"]
 
 [[package]]
-category = "main"
-description = "Database-backed Periodic Tasks."
 name = "django-celery-beat"
+version = "2.1.0"
+description = "Database-backed Periodic Tasks."
+category = "main"
 optional = true
 python-versions = "*"
-version = "2.0.0"
 
 [package.dependencies]
-Django = ">=1.11.17"
-celery = "*"
+celery = ">=4.4,<6.0"
+Django = ">=2.2"
 django-timezone-field = ">=4.0,<5.0"
 python-crontab = ">=2.3.4"
 
 [[package]]
-category = "main"
-description = "An async Django email backend using celery"
 name = "django-celery-email"
+version = "3.0.0"
+description = "An async Django email backend using celery"
+category = "main"
 optional = true
 python-versions = "*"
-version = "3.0.0"
 
 [package.dependencies]
 celery = ">=4.0"
@@ -427,42 +495,42 @@ django = ">=2.2"
 django-appconf = "*"
 
 [[package]]
-category = "main"
-description = "Celery result backends for Django."
 name = "django-celery-results"
+version = "2.0.0"
+description = "Celery result backends for Django."
+category = "main"
 optional = true
 python-versions = "*"
-version = "1.2.1"
 
 [package.dependencies]
-celery = ">=4.4,<5.0"
+celery = ">=4.4,<6.0"
 
 [[package]]
-category = "main"
-description = "Django admin CKEditor integration."
 name = "django-ckeditor"
+version = "6.0.0"
+description = "Django admin CKEditor integration."
+category = "main"
 optional = false
 python-versions = "*"
-version = "6.0.0"
 
 [package.dependencies]
 django-js-asset = ">=1.2.2"
 
 [[package]]
-category = "main"
-description = "simple color field for your models with a nice color-picker in the admin-interface."
 name = "django-colorfield"
+version = "0.3.2"
+description = "simple color field for your models with a nice color-picker in the admin-interface."
+category = "main"
 optional = false
 python-versions = "*"
-version = "0.3.2"
 
 [[package]]
-category = "main"
-description = "Management commands to help backup and restore a project database and media"
 name = "django-dbbackup"
+version = "3.3.0"
+description = "Management commands to help backup and restore a project database and media"
+category = "main"
 optional = false
 python-versions = "*"
-version = "3.3.0"
 
 [package.dependencies]
 Django = ">=1.5"
@@ -470,24 +538,24 @@ pytz = "*"
 six = "*"
 
 [[package]]
-category = "main"
-description = "A configurable set of panels that display various debug information about the current request/response."
 name = "django-debug-toolbar"
+version = "2.2"
+description = "A configurable set of panels that display various debug information about the current request/response."
+category = "main"
 optional = false
 python-versions = ">=3.5"
-version = "2.2"
 
 [package.dependencies]
 Django = ">=1.11"
 sqlparse = ">=0.2.0"
 
 [[package]]
-category = "main"
-description = "Dynamic global and instance settings for your django project"
 name = "django-dynamic-preferences"
+version = "1.10.1"
+description = "Dynamic global and instance settings for your django project"
+category = "main"
 optional = false
 python-versions = "*"
-version = "1.10.1"
 
 [package.dependencies]
 django = ">=1.11"
@@ -495,300 +563,300 @@ persisting-theory = ">=0.2.1"
 six = "*"
 
 [[package]]
-category = "main"
-description = "Yet another Django audit log app, hopefully the simplest one."
-name = "django-easy-audit"
-optional = false
-python-versions = ">=3.5"
-version = "1.3.0a5"
-
-[package.dependencies]
-beautifulsoup4 = "*"
-django = ">=2.2,<3.2"
-
-[[package]]
-category = "main"
-description = "simple Django app which allows you to upload a image and it renders a wide variety for html link tags to display the favicon"
 name = "django-favicon-plus-reloaded"
+version = "1.0.4"
+description = "simple Django app which allows you to upload a image and it renders a wide variety for html link tags to display the favicon"
+category = "main"
 optional = false
 python-versions = "*"
-version = "1.0.4"
 
 [package.dependencies]
 django = "*"
 pillow = "*"
 
 [[package]]
-category = "main"
-description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically."
 name = "django-filter"
+version = "2.4.0"
+description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically."
+category = "main"
 optional = false
 python-versions = ">=3.5"
-version = "2.3.0"
 
 [package.dependencies]
 Django = ">=2.2"
 
 [[package]]
-category = "main"
-description = "A set of high-level abstractions for Django forms"
 name = "django-formtools"
+version = "2.2"
+description = "A set of high-level abstractions for Django forms"
+category = "main"
 optional = false
 python-versions = "*"
-version = "2.2"
 
 [package.dependencies]
 Django = ">=1.11"
 
 [[package]]
-category = "main"
-description = "Implementation of per object permissions for Django."
 name = "django-guardian"
+version = "2.3.0"
+description = "Implementation of per object permissions for Django."
+category = "main"
 optional = false
 python-versions = ">=3.5"
-version = "2.3.0"
 
 [package.dependencies]
 Django = ">=2.2"
 
 [[package]]
-category = "main"
-description = "Command to anonymize sensitive data."
 name = "django-hattori"
+version = "0.2.1"
+description = "Command to anonymize sensitive data."
+category = "main"
 optional = false
 python-versions = "*"
-version = "0.2.1"
 
 [package.dependencies]
 Django = ">=1.8"
-Faker = ">=0.8.13"
 django-bulk-update = ">=2.2.0"
+Faker = ">=0.8.13"
 six = "*"
 tqdm = ">=4.23.4"
 
 [[package]]
-category = "main"
-description = "Pluggable search for Django."
 name = "django-haystack"
+version = "3.0b1"
+description = "Pluggable search for Django."
+category = "main"
 optional = false
 python-versions = "*"
-version = "3.0b1"
 
 [package.dependencies]
 Django = ">=2.2"
 
 [[package]]
-category = "main"
-description = "Run checks on services like databases, queue servers, celery processes, etc."
 name = "django-health-check"
+version = "3.16.1"
+description = "Run checks on services like databases, queue servers, celery processes, etc."
+category = "main"
 optional = false
 python-versions = "*"
-version = "3.12.1"
 
 [package.dependencies]
 django = ">=1.11"
 
 [[package]]
-category = "main"
-description = "A reusable app for cropping images easily and non-destructively in Django"
-name = "django-image-cropping"
-optional = false
-python-versions = ">=3.5"
-version = "1.4.0"
-
-[package.dependencies]
-django-appconf = ">=1.0.2"
-
-[[package]]
-category = "main"
-description = "Django app to allow superusers to impersonate other users."
 name = "django-impersonate"
+version = "1.7"
+description = "Django app to allow superusers to impersonate other users."
+category = "main"
 optional = false
 python-versions = "*"
-version = "1.5.1"
 
 [[package]]
-category = "main"
-description = "A Django utility application that returns client's real IP address"
 name = "django-ipware"
+version = "3.0.2"
+description = "A Django utility application that returns client's real IP address"
+category = "main"
 optional = false
 python-versions = "*"
-version = "3.0.1"
 
 [[package]]
-category = "main"
-description = "script tag with additional attributes for django.forms.Media"
 name = "django-js-asset"
+version = "1.2.2"
+description = "script tag with additional attributes for django.forms.Media"
+category = "main"
 optional = false
 python-versions = "*"
-version = "1.2.2"
 
 [[package]]
-category = "main"
-description = "Javascript url handling for Django that doesn't hurt."
 name = "django-js-reverse"
+version = "0.9.1"
+description = "Javascript url handling for Django that doesn't hurt."
+category = "main"
 optional = false
 python-versions = "*"
-version = "0.9.1"
 
 [package.dependencies]
 Django = ">=1.5"
 
 [[package]]
-category = "main"
-description = "Expose JSONField data as a virtual django model fields."
 name = "django-jsonstore"
+version = "0.4.1"
+description = "Expose JSONField data as a virtual django model fields."
+category = "main"
 optional = false
 python-versions = "*"
-version = "0.4.1"
 
 [package.dependencies]
 Django = ">=1.11"
 six = "*"
 
 [[package]]
-category = "main"
-description = "django-maintenance-mode shows a 503 error page when maintenance-mode is on."
 name = "django-maintenance-mode"
+version = "0.15.1"
+description = "django-maintenance-mode shows a 503 error page when maintenance-mode is on."
+category = "main"
 optional = false
 python-versions = "*"
-version = "0.14.0"
 
 [[package]]
-category = "main"
-description = "Material design for django forms and admin"
 name = "django-material"
+version = "1.7.3"
+description = "Material design for django forms and admin"
+category = "main"
 optional = false
 python-versions = "*"
-version = "1.7.0"
 
 [package.dependencies]
 six = "*"
 
 [[package]]
-category = "main"
-description = "A straightforward menu generator for Django"
 name = "django-menu-generator"
+version = "1.1.0"
+description = "A straightforward menu generator for Django"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "django-middleware-global-request"
+version = "0.1.2"
+description = "Django middleware that keep request instance for every thread."
+category = "main"
 optional = false
 python-versions = "*"
-version = "1.0.4"
+
+[package.dependencies]
+django = "*"
 
 [[package]]
+name = "django-model-utils"
+version = "4.1.1"
+description = "Django model mixins and utilities"
 category = "main"
-description = "Django middleware that keep request instance for every thread."
-name = "django-middleware-global-request"
 optional = false
 python-versions = "*"
-version = "0.1.2"
 
 [package.dependencies]
-django = "*"
+Django = ">=2.0.1"
 
 [[package]]
-category = "main"
-description = "A pluggable framework for adding two-factor authentication to Django using one-time passwords."
 name = "django-otp"
+version = "1.0.2"
+description = "A pluggable framework for adding two-factor authentication to Django using one-time passwords."
+category = "main"
 optional = false
 python-versions = "*"
-version = "0.9.4"
 
 [package.dependencies]
-django = ">=1.11"
+django = ">=2.2"
 
 [package.extras]
 qrcode = ["qrcode"]
 
 [[package]]
-category = "main"
-description = "A django-otp plugin that verifies YubiKey OTP tokens."
 name = "django-otp-yubikey"
+version = "1.0.0"
+description = "A django-otp plugin that verifies YubiKey OTP tokens."
+category = "main"
 optional = false
 python-versions = "*"
-version = "0.6.0"
 
 [package.dependencies]
+django-otp = ">=1.0.0"
 YubiOTP = ">=0.2.2"
-django-otp = ">=0.5.0"
 
 [[package]]
-category = "main"
-description = "An international phone number field for django models."
 name = "django-phonenumber-field"
+version = "3.0.1"
+description = "An international phone number field for django models."
+category = "main"
 optional = false
 python-versions = ">=3.5"
-version = "3.0.1"
 
 [package.dependencies]
-Django = ">=1.11.3"
 babel = "*"
+Django = ">=1.11.3"
+phonenumbers = {version = ">=7.0.2", optional = true, markers = "extra == \"phonenumbers\""}
 
 [package.extras]
 phonenumbers = ["phonenumbers (>=7.0.2)"]
 phonenumberslite = ["phonenumberslite (>=7.0.2)"]
 
 [[package]]
-category = "main"
-description = "Seamless polymorphic inheritance for Django models"
 name = "django-polymorphic"
+version = "3.0.0"
+description = "Seamless polymorphic inheritance for Django models"
+category = "main"
 optional = false
 python-versions = "*"
-version = "2.1.2"
 
 [package.dependencies]
-Django = ">=1.11"
+Django = ">=2.1"
 
 [[package]]
+name = "django-prometheus"
+version = "2.1.0"
+description = "Django middlewares to monitor your application with Prometheus.io."
 category = "main"
-description = "A Django app to include a manifest.json and Service Worker instance to enable progressive web app behavior"
-name = "django-pwa"
 optional = false
 python-versions = "*"
+
+[package.dependencies]
+prometheus-client = ">=0.7"
+
+[[package]]
+name = "django-pwa"
 version = "1.0.10"
+description = "A Django app to include a manifest.json and Service Worker instance to enable progressive web app behavior"
+category = "main"
+optional = false
+python-versions = "*"
 
 [package.dependencies]
 django = ">=1.8"
 
 [[package]]
-category = "main"
-description = "Render a particular block from a template to a string."
 name = "django-render-block"
+version = "0.8.1"
+description = "Render a particular block from a template to a string."
+category = "main"
 optional = false
 python-versions = ">=3.5"
-version = "0.7"
 
 [package.dependencies]
 django = ">=2.2"
 
 [[package]]
-category = "main"
-description = "An extension to the Django web framework that provides version control for model instances."
 name = "django-reversion"
+version = "3.0.8"
+description = "An extension to the Django web framework that provides version control for model instances."
+category = "main"
 optional = false
 python-versions = ">=3.6"
-version = "3.0.8"
 
 [package.dependencies]
 django = ">=1.11"
 
 [[package]]
-category = "main"
-description = "SASS processor to compile SCSS files into *.css, while rendering, or offline."
 name = "django-sass-processor"
+version = "0.8.2"
+description = "SASS processor to compile SCSS files into *.css, while rendering, or offline."
+category = "main"
 optional = false
 python-versions = "*"
-version = "0.8.2"
 
 [package.extras]
 dev = ["libsass (>=0.13)"]
 management-command = ["django-compressor (>=2.4)"]
 
 [[package]]
-category = "main"
-description = "Select2 option fields for Django"
 name = "django-select2"
+version = "7.5.0"
+description = "Select2 option fields for Django"
+category = "main"
 optional = false
 python-versions = "*"
-version = "7.4.2"
 
 [package.dependencies]
 django = ">=2.2"
@@ -798,33 +866,33 @@ django-appconf = ">=0.6.0"
 test = ["pytest", "pytest-cov", "pytest-django", "selenium"]
 
 [[package]]
-category = "main"
-description = "Makes specified django settings visible in template rendering context."
 name = "django-settings-context-processor"
+version = "0.2"
+description = "Makes specified django settings visible in template rendering context."
+category = "main"
 optional = false
 python-versions = "*"
-version = "0.2"
 
 [[package]]
-category = "dev"
-description = "Mypy stubs for Django"
 name = "django-stubs"
+version = "1.7.0"
+description = "Mypy stubs for Django"
+category = "dev"
 optional = false
 python-versions = ">=3.6"
-version = "1.5.0"
 
 [package.dependencies]
 django = "*"
-mypy = ">=0.770,<0.780"
+mypy = ">=0.790"
 typing-extensions = "*"
 
 [[package]]
-category = "main"
-description = "Table/data-grid framework for Django"
 name = "django-tables2"
+version = "2.3.3"
+description = "Table/data-grid framework for Django"
+category = "main"
 optional = false
 python-versions = "*"
-version = "2.3.1"
 
 [package.dependencies]
 Django = ">=1.11"
@@ -833,55 +901,49 @@ Django = ">=1.11"
 tablib = ["tablib"]
 
 [[package]]
-category = "main"
-description = "A Django oriented templated / transaction email abstraction"
 name = "django-templated-email"
+version = "2.3.0"
+description = "A Django oriented templated / transaction email abstraction"
+category = "main"
 optional = false
 python-versions = "*"
-version = "2.3.0"
 
 [package.dependencies]
 django-render-block = ">=0.5"
 six = ">=1"
 
 [[package]]
-category = "main"
-description = "A Django app providing database and form fields for pytz timezone objects."
 name = "django-timezone-field"
+version = "4.1.1"
+description = "A Django app providing database and form fields for pytz timezone objects."
+category = "main"
 optional = true
 python-versions = ">=3.5"
-version = "4.0"
 
 [package.dependencies]
 django = ">=2.2"
 pytz = "*"
 
+[package.extras]
+rest_framework = ["djangorestframework (>=3.0.0)"]
+
 [[package]]
-category = "main"
-description = "Complete Two-Factor Authentication for Django"
 name = "django-two-factor-auth"
+version = "1.13"
+description = "Complete Two-Factor Authentication for Django"
+category = "main"
 optional = false
 python-versions = "*"
-version = "1.12.1"
 
 [package.dependencies]
 Django = ">=2.2"
 django-formtools = "*"
-django-otp = ">=0.6.0,<0.99"
+django-otp = ">=0.8.0"
+django-otp-yubikey = {version = "*", optional = true, markers = "extra == \"yubikey\""}
 django-phonenumber-field = ">=1.1.0,<3.99"
+phonenumbers = {version = ">=7.0.9,<8.99", optional = true, markers = "extra == \"phonenumbers\""}
 qrcode = ">=4.0.0,<6.99"
-
-[package.dependencies.django-otp-yubikey]
-optional = true
-version = "*"
-
-[package.dependencies.phonenumbers]
-optional = true
-version = ">=7.0.9,<8.99"
-
-[package.dependencies.twilio]
-optional = true
-version = ">=6.0"
+twilio = {version = ">=6.0", optional = true, markers = "extra == \"call\""}
 
 [package.extras]
 call = ["twilio (>=6.0)"]
@@ -891,40 +953,40 @@ sms = ["twilio (>=6.0)"]
 yubikey = ["django-otp-yubikey"]
 
 [[package]]
-category = "main"
-description = "Tweak the form field rendering in templates, not in python-level form definitions."
 name = "django-widget-tweaks"
+version = "1.4.8"
+description = "Tweak the form field rendering in templates, not in python-level form definitions."
+category = "main"
 optional = false
 python-versions = "*"
-version = "1.4.8"
 
 [[package]]
-category = "main"
-description = "Integrate django with yarnpkg"
 name = "django-yarnpkg"
+version = "6.0.1"
+description = "Integrate django with yarnpkg"
+category = "main"
 optional = false
 python-versions = "*"
-version = "6.0.1"
 
 [package.dependencies]
 django = "*"
 six = "*"
 
 [[package]]
-category = "dev"
-description = "Docutils -- Python Documentation Utilities"
 name = "docutils"
+version = "0.16"
+description = "Docutils -- Python Documentation Utilities"
+category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "0.16"
 
 [[package]]
-category = "dev"
-description = "A parser for Python dependency files"
 name = "dparse"
+version = "0.5.1"
+description = "A parser for Python dependency files"
+category = "dev"
 optional = false
 python-versions = ">=3.5"
-version = "0.5.1"
 
 [package.dependencies]
 packaging = "*"
@@ -935,84 +997,60 @@ toml = "*"
 pipenv = ["pipenv"]
 
 [[package]]
-category = "main"
-description = "The dynamic configurator for your Python Project"
 name = "dynaconf"
+version = "3.1.2"
+description = "The dynamic configurator for your Python Project"
+category = "main"
 optional = false
 python-versions = "*"
-version = "2.2.3"
 
 [package.dependencies]
-click = "*"
-python-box = "<4.0.0"
-python-dotenv = "*"
-toml = "*"
-
-[package.dependencies.PyYAML]
-optional = true
-version = "*"
-
-[package.dependencies.configobj]
-optional = true
-version = "*"
+configobj = {version = "*", optional = true, markers = "extra == \"ini\""}
+"ruamel.yaml" = {version = "*", optional = true, markers = "extra == \"yaml\""}
+toml = {version = "*", optional = true, markers = "extra == \"toml\""}
 
 [package.extras]
-all = ["redis", "pyyaml", "configobj", "hvac"]
+all = ["redis", "ruamel.yaml", "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 = ">=3.5"
-version = "2.7"
-
-[package.dependencies]
-django = ">=1.11,<4.0"
-pillow = "*"
+yaml = ["ruamel.yaml"]
 
 [[package]]
-category = "main"
-description = "Faker is a Python package that generates fake data for you."
 name = "faker"
+version = "5.0.2"
+description = "Faker is a Python package that generates fake data for you."
+category = "main"
 optional = false
-python-versions = ">=3.5"
-version = "4.1.2"
+python-versions = ">=3.6"
 
 [package.dependencies]
 python-dateutil = ">=2.4"
 text-unidecode = "1.3"
 
 [[package]]
-category = "dev"
-description = "the modular source code checker: pep8 pyflakes and co"
 name = "flake8"
+version = "3.8.4"
+description = "the modular source code checker: pep8 pyflakes and co"
+category = "dev"
 optional = false
 python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
-version = "3.8.3"
 
 [package.dependencies]
+importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
 mccabe = ">=0.6.0,<0.7.0"
 pycodestyle = ">=2.6.0a1,<2.7.0"
 pyflakes = ">=2.2.0,<2.3.0"
 
-[package.dependencies.importlib-metadata]
-python = "<3.8"
-version = "*"
-
 [[package]]
-category = "dev"
-description = "Automated security testing with bandit and flake8."
 name = "flake8-bandit"
+version = "2.1.2"
+description = "Automated security testing with bandit and flake8."
+category = "dev"
 optional = false
 python-versions = "*"
-version = "2.1.2"
 
 [package.dependencies]
 bandit = "*"
@@ -1021,24 +1059,24 @@ flake8-polyfill = "*"
 pycodestyle = "*"
 
 [[package]]
-category = "dev"
-description = "flake8 plugin to call black as a code style validator"
 name = "flake8-black"
+version = "0.2.1"
+description = "flake8 plugin to call black as a code style validator"
+category = "dev"
 optional = false
 python-versions = "*"
-version = "0.2.1"
 
 [package.dependencies]
 black = "*"
 flake8 = ">=3.0.0"
 
 [[package]]
-category = "dev"
-description = "Check for python builtins being used as variables or parameters."
 name = "flake8-builtins"
+version = "1.5.3"
+description = "Check for python builtins being used as variables or parameters."
+category = "dev"
 optional = false
 python-versions = "*"
-version = "1.5.3"
 
 [package.dependencies]
 flake8 = "*"
@@ -1047,43 +1085,43 @@ flake8 = "*"
 test = ["coverage", "coveralls", "mock", "pytest", "pytest-cov"]
 
 [[package]]
-category = "dev"
-description = "Plugin to catch bad style specific to Django Projects"
 name = "flake8-django"
+version = "1.1.1"
+description = "Plugin to catch bad style specific to Django Projects"
+category = "dev"
 optional = false
 python-versions = "*"
-version = "1.1.1"
 
 [package.dependencies]
 flake8 = "*"
 
 [[package]]
-category = "dev"
-description = "Extension for flake8 which uses pydocstyle to check docstrings"
 name = "flake8-docstrings"
+version = "1.5.0"
+description = "Extension for flake8 which uses pydocstyle to check docstrings"
+category = "dev"
 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"
+version = "1.1.1"
+description = "Check for FIXME, TODO and other temporary developer notes. Plugin for flake8."
+category = "dev"
 optional = false
 python-versions = "*"
-version = "1.1.1"
 
 [[package]]
-category = "dev"
-description = "flake8 plugin that integrates isort ."
 name = "flake8-isort"
+version = "4.0.0"
+description = "flake8 plugin that integrates isort ."
+category = "dev"
 optional = false
 python-versions = "*"
-version = "4.0.0"
 
 [package.dependencies]
 flake8 = ">=3.2.1,<4"
@@ -1094,12 +1132,12 @@ testfixtures = ">=6.8.0,<7"
 test = ["pytest (>=4.0.2,<6)", "toml"]
 
 [[package]]
-category = "dev"
-description = "A plugin for flake8 integrating mypy."
 name = "flake8-mypy"
+version = "17.8.0"
+description = "A plugin for flake8 integrating mypy."
+category = "dev"
 optional = false
 python-versions = "*"
-version = "17.8.0"
 
 [package.dependencies]
 attrs = "*"
@@ -1107,118 +1145,118 @@ flake8 = ">=3.0.0"
 mypy = "*"
 
 [[package]]
-category = "dev"
-description = "Polyfill package for Flake8 plugins"
 name = "flake8-polyfill"
+version = "1.0.2"
+description = "Polyfill package for Flake8 plugins"
+category = "dev"
 optional = false
 python-versions = "*"
-version = "1.0.2"
 
 [package.dependencies]
 flake8 = "*"
 
 [[package]]
-category = "dev"
-description = "Python docstring reStructuredText (RST) validator"
 name = "flake8-rst-docstrings"
+version = "0.0.13"
+description = "Python docstring reStructuredText (RST) validator"
+category = "dev"
 optional = false
 python-versions = "*"
-version = "0.0.13"
 
 [package.dependencies]
 flake8 = ">=3.0.0"
 restructuredtext_lint = "*"
 
 [[package]]
-category = "dev"
-description = "Git Object Database"
 name = "gitdb"
+version = "4.0.5"
+description = "Git Object Database"
+category = "dev"
 optional = false
 python-versions = ">=3.4"
-version = "4.0.5"
 
 [package.dependencies]
 smmap = ">=3.0.1,<4"
 
 [[package]]
-category = "dev"
-description = "Python Git Library"
 name = "gitpython"
+version = "3.1.11"
+description = "Python Git Library"
+category = "dev"
 optional = false
 python-versions = ">=3.4"
-version = "3.1.8"
 
 [package.dependencies]
 gitdb = ">=4.0.1,<5"
 
 [[package]]
-category = "main"
-description = "Turn HTML into equivalent Markdown-structured text."
 name = "html2text"
+version = "2020.1.16"
+description = "Turn HTML into equivalent Markdown-structured text."
+category = "main"
 optional = false
 python-versions = ">=3.5"
-version = "2020.1.16"
 
 [[package]]
-category = "main"
-description = "Internationalized Domain Names in Applications (IDNA)"
 name = "idna"
+version = "2.10"
+description = "Internationalized Domain Names in Applications (IDNA)"
+category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "2.10"
 
 [[package]]
-category = "dev"
-description = "Getting image size from png/jpeg/jpeg2000/gif file"
 name = "imagesize"
+version = "1.2.0"
+description = "Getting image size from png/jpeg/jpeg2000/gif file"
+category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "1.2.0"
 
 [[package]]
-category = "main"
-description = "Read metadata from Python packages"
-marker = "python_version < \"3.8\""
 name = "importlib-metadata"
+version = "3.3.0"
+description = "Read metadata from Python packages"
+category = "main"
 optional = false
-python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
-version = "1.7.0"
+python-versions = ">=3.6"
 
 [package.dependencies]
+typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""}
 zipp = ">=0.5"
 
 [package.extras]
-docs = ["sphinx", "rst.linker"]
-testing = ["packaging", "pep517", "importlib-resources (>=1.3)"]
+docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
+testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"]
 
 [[package]]
-category = "dev"
-description = "iniconfig: brain-dead simple config-ini parsing"
 name = "iniconfig"
+version = "1.1.1"
+description = "iniconfig: brain-dead simple config-ini parsing"
+category = "dev"
 optional = false
 python-versions = "*"
-version = "1.0.1"
 
 [[package]]
-category = "dev"
-description = "A Python utility / library to sort Python imports."
 name = "isort"
+version = "5.6.4"
+description = "A Python utility / library to sort Python imports."
+category = "dev"
 optional = false
 python-versions = ">=3.6,<4.0"
-version = "5.5.1"
 
 [package.extras]
-colors = ["colorama (>=0.4.3,<0.5.0)"]
 pipfile_deprecated_finder = ["pipreqs", "requirementslib"]
 requirements_deprecated_finder = ["pipreqs", "pip-api"]
+colors = ["colorama (>=0.4.3,<0.5.0)"]
 
 [[package]]
-category = "dev"
-description = "A very fast and expressive template engine."
 name = "jinja2"
+version = "2.11.2"
+description = "A very fast and expressive template engine."
+category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "2.11.2"
 
 [package.dependencies]
 MarkupSafe = ">=0.23"
@@ -1227,19 +1265,16 @@ MarkupSafe = ">=0.23"
 i18n = ["Babel (>=0.8)"]
 
 [[package]]
-category = "main"
-description = "Messaging library for Python."
 name = "kombu"
+version = "5.0.2"
+description = "Messaging library for Python."
+category = "main"
 optional = true
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "4.6.11"
+python-versions = ">=3.6"
 
 [package.dependencies]
-amqp = ">=2.6.0,<2.7"
-
-[package.dependencies.importlib-metadata]
-python = "<3.8"
-version = ">=0.18"
+amqp = ">=5.0.0,<6.0.0"
+importlib-metadata = {version = ">=0.18", markers = "python_version < \"3.8\""}
 
 [package.extras]
 azureservicebus = ["azure-servicebus (>=0.21.1)"]
@@ -1253,63 +1288,55 @@ qpid = ["qpid-python (>=0.26)", "qpid-tools (>=0.26)"]
 redis = ["redis (>=3.3.11)"]
 slmq = ["softlayer-messaging (>=1.0.3)"]
 sqlalchemy = ["sqlalchemy"]
-sqs = ["boto3 (>=1.4.4)", "pycurl (7.43.0.2)"]
+sqs = ["boto3 (>=1.4.4)", "pycurl (==7.43.0.2)"]
 yaml = ["PyYAML (>=3.10)"]
 zookeeper = ["kazoo (>=1.3.1)"]
 
 [[package]]
-category = "main"
-description = "Sass for Python: A straightforward binding of libsass for Python."
 name = "libsass"
+version = "0.20.1"
+description = "Sass for Python: A straightforward binding of libsass for Python."
+category = "main"
 optional = false
 python-versions = "*"
-version = "0.20.1"
 
 [package.dependencies]
 six = "*"
 
 [[package]]
-category = "main"
-description = "license-expression is small utility library to parse, compare, simplify and normalize license expressions (such as SPDX license expressions) using boolean logic."
 name = "license-expression"
+version = "1.2"
+description = "license-expression is small utility library to parse, compare, simplify and normalize license expressions (such as SPDX license expressions) using boolean logic."
+category = "main"
 optional = false
 python-versions = "*"
-version = "1.2"
 
 [package.dependencies]
 "boolean.py" = ">=3.6,<4.0.0"
 
 [[package]]
-category = "dev"
-description = "Safely add untrusted strings to HTML/XML markup."
 name = "markupsafe"
+version = "1.1.1"
+description = "Safely add untrusted strings to HTML/XML markup."
+category = "dev"
 optional = false
 python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
-version = "1.1.1"
 
 [[package]]
-category = "dev"
-description = "McCabe checker, plugin for flake8"
 name = "mccabe"
-optional = false
-python-versions = "*"
 version = "0.6.1"
-
-[[package]]
+description = "McCabe checker, plugin for flake8"
 category = "dev"
-description = "More routines for operating on iterables, beyond itertools"
-name = "more-itertools"
 optional = false
-python-versions = ">=3.5"
-version = "8.5.0"
+python-versions = "*"
 
 [[package]]
-category = "dev"
-description = "Optional static typing for Python"
 name = "mypy"
+version = "0.790"
+description = "Optional static typing for Python"
+category = "dev"
 optional = false
 python-versions = ">=3.5"
-version = "0.770"
 
 [package.dependencies]
 mypy-extensions = ">=0.4.3,<0.5.0"
@@ -1320,188 +1347,207 @@ typing-extensions = ">=3.7.4"
 dmypy = ["psutil (>=4.0)"]
 
 [[package]]
-category = "dev"
-description = "Experimental type system extensions for programs checked with the mypy typechecker."
 name = "mypy-extensions"
+version = "0.4.3"
+description = "Experimental type system extensions for programs checked with the mypy typechecker."
+category = "dev"
 optional = false
 python-versions = "*"
-version = "0.4.3"
 
 [[package]]
-category = "main"
-description = "Core utilities for Python packages"
 name = "packaging"
+version = "20.8"
+description = "Core utilities for Python packages"
+category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "20.4"
 
 [package.dependencies]
 pyparsing = ">=2.0.2"
-six = "*"
 
 [[package]]
-category = "dev"
-description = "Utility library for gitignore style pattern matching of file paths."
 name = "pathspec"
+version = "0.8.1"
+description = "Utility library for gitignore style pattern matching of file paths."
+category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "0.8.0"
 
 [[package]]
-category = "dev"
-description = "Python Build Reasonableness"
 name = "pbr"
+version = "5.5.1"
+description = "Python Build Reasonableness"
+category = "dev"
 optional = false
 python-versions = ">=2.6"
-version = "5.5.0"
 
 [[package]]
-category = "main"
-description = "Registries that can autodiscover values accross your project apps"
 name = "persisting-theory"
+version = "0.2.1"
+description = "Registries that can autodiscover values accross your project apps"
+category = "main"
 optional = false
 python-versions = "*"
-version = "0.2.1"
 
 [[package]]
-category = "dev"
-description = "PostgreSQL interface library"
 name = "pg8000"
+version = "1.16.6"
+description = "PostgreSQL interface library"
+category = "dev"
 optional = false
 python-versions = ">=3.5"
-version = "1.16.5"
 
 [package.dependencies]
 scramp = "1.2.0"
 
 [[package]]
-category = "main"
-description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers."
 name = "phonenumbers"
+version = "8.12.15"
+description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers."
+category = "main"
 optional = false
 python-versions = "*"
-version = "8.12.9"
 
 [[package]]
-category = "main"
-description = "Python Imaging Library (Fork)"
 name = "pillow"
+version = "8.0.1"
+description = "Python Imaging Library (Fork)"
+category = "main"
 optional = false
-python-versions = ">=3.5"
-version = "7.2.0"
+python-versions = ">=3.6"
 
 [[package]]
-category = "dev"
-description = "plugin and hook calling mechanisms for python"
 name = "pluggy"
+version = "0.13.1"
+description = "plugin and hook calling mechanisms for python"
+category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "0.13.1"
 
 [package.dependencies]
-[package.dependencies.importlib-metadata]
-python = "<3.8"
-version = ">=0.12"
+importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
 
 [package.extras]
 dev = ["pre-commit", "tox"]
 
 [[package]]
+name = "prometheus-client"
+version = "0.9.0"
+description = "Python client for the Prometheus monitoring system."
 category = "main"
-description = "Cross-platform lib for process and system monitoring in Python."
+optional = false
+python-versions = "*"
+
+[package.extras]
+twisted = ["twisted"]
+
+[[package]]
+name = "prompt-toolkit"
+version = "3.0.8"
+description = "Library for building powerful interactive command lines in Python"
+category = "main"
+optional = true
+python-versions = ">=3.6.1"
+
+[package.dependencies]
+wcwidth = "*"
+
+[[package]]
 name = "psutil"
+version = "5.8.0"
+description = "Cross-platform lib for process and system monitoring in Python."
+category = "main"
 optional = false
 python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "5.7.2"
 
 [package.extras]
 test = ["ipaddress", "mock", "unittest2", "enum34", "pywin32", "wmi"]
 
 [[package]]
-category = "main"
-description = "psycopg2 - Python-PostgreSQL Database Adapter"
 name = "psycopg2"
+version = "2.8.6"
+description = "psycopg2 - Python-PostgreSQL Database Adapter"
+category = "main"
 optional = false
 python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
-version = "2.8.6"
 
 [[package]]
-category = "dev"
-description = "library with cross-python path, ini-parsing, io, code, log facilities"
 name = "py"
+version = "1.10.0"
+description = "library with cross-python path, ini-parsing, io, code, log facilities"
+category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "1.9.0"
 
 [[package]]
-category = "main"
-description = "ASN.1 types and codecs"
 name = "pyasn1"
+version = "0.4.8"
+description = "ASN.1 types and codecs"
+category = "main"
 optional = true
 python-versions = "*"
-version = "0.4.8"
 
 [[package]]
-category = "main"
-description = "A collection of ASN.1-based protocols modules."
 name = "pyasn1-modules"
+version = "0.2.8"
+description = "A collection of ASN.1-based protocols modules."
+category = "main"
 optional = true
 python-versions = "*"
-version = "0.2.8"
 
 [package.dependencies]
 pyasn1 = ">=0.4.6,<0.5.0"
 
 [[package]]
-category = "dev"
-description = "Python style guide checker"
 name = "pycodestyle"
+version = "2.6.0"
+description = "Python style guide checker"
+category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "2.6.0"
 
 [[package]]
-category = "main"
-description = "Cryptographic library for Python"
 name = "pycryptodome"
+version = "3.9.9"
+description = "Cryptographic library for Python"
+category = "main"
 optional = false
 python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "3.9.8"
 
 [[package]]
-category = "dev"
-description = "Python docstring style checker"
 name = "pydocstyle"
+version = "5.1.1"
+description = "Python docstring style checker"
+category = "dev"
 optional = false
 python-versions = ">=3.5"
-version = "5.1.1"
 
 [package.dependencies]
 snowballstemmer = "*"
 
 [[package]]
-category = "dev"
-description = "passive checker of Python programs"
 name = "pyflakes"
+version = "2.2.0"
+description = "passive checker of Python programs"
+category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "2.2.0"
 
 [[package]]
-category = "dev"
-description = "Pygments is a syntax highlighting package written in Python."
 name = "pygments"
+version = "2.7.3"
+description = "Pygments is a syntax highlighting package written in Python."
+category = "dev"
 optional = false
 python-versions = ">=3.5"
-version = "2.6.1"
 
 [[package]]
-category = "main"
-description = "JSON Web Token implementation in Python"
 name = "pyjwt"
+version = "1.7.1"
+description = "JSON Web Token implementation in Python"
+category = "main"
 optional = false
 python-versions = "*"
-version = "1.7.1"
 
 [package.extras]
 crypto = ["cryptography (>=1.4)"]
@@ -1509,62 +1555,57 @@ flake8 = ["flake8", "flake8-import-order", "pep8-naming"]
 test = ["pytest (>=4.0.1,<5.0.0)", "pytest-cov (>=2.6.0,<3.0.0)", "pytest-runner (>=4.2,<5.0.0)"]
 
 [[package]]
-category = "main"
-description = "Python parsing module"
 name = "pyparsing"
+version = "2.4.7"
+description = "Python parsing module"
+category = "main"
 optional = false
 python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
-version = "2.4.7"
 
 [[package]]
-category = "dev"
-description = "pytest: simple powerful testing with Python"
 name = "pytest"
+version = "6.2.1"
+description = "pytest: simple powerful testing with Python"
+category = "dev"
 optional = false
-python-versions = ">=3.5"
-version = "6.0.1"
+python-versions = ">=3.6"
 
 [package.dependencies]
-atomicwrites = ">=1.0"
-attrs = ">=17.4.0"
-colorama = "*"
+atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
+attrs = ">=19.2.0"
+colorama = {version = "*", markers = "sys_platform == \"win32\""}
+importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
 iniconfig = "*"
-more-itertools = ">=4.0.0"
 packaging = "*"
-pluggy = ">=0.12,<1.0"
+pluggy = ">=0.12,<1.0.0a1"
 py = ">=1.8.2"
 toml = "*"
 
-[package.dependencies.importlib-metadata]
-python = "<3.8"
-version = ">=0.12"
-
 [package.extras]
-checkqa_mypy = ["mypy (0.780)"]
 testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
 
 [[package]]
-category = "dev"
-description = "Pytest plugin for measuring coverage."
 name = "pytest-cov"
+version = "2.10.1"
+description = "Pytest plugin for measuring coverage."
+category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "2.10.1"
 
 [package.dependencies]
 coverage = ">=4.4"
 pytest = ">=4.6"
 
 [package.extras]
-testing = ["fields", "hunter", "process-tests (2.0.2)", "six", "pytest-xdist", "virtualenv"]
+testing = ["fields", "hunter", "process-tests (==2.0.2)", "six", "pytest-xdist", "virtualenv"]
 
 [[package]]
-category = "dev"
-description = "A Django plugin for pytest."
 name = "pytest-django"
+version = "3.10.0"
+description = "A Django plugin for pytest."
+category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "3.9.0"
 
 [package.dependencies]
 pytest = ">=3.6"
@@ -1574,24 +1615,24 @@ docs = ["sphinx", "sphinx-rtd-theme"]
 testing = ["django", "django-configurations (>=2.0)", "six"]
 
 [[package]]
-category = "dev"
-description = "Use a temporary PostgreSQL database with pytest-django"
 name = "pytest-django-testing-postgresql"
+version = "0.1.post0"
+description = "Use a temporary PostgreSQL database with pytest-django"
+category = "dev"
 optional = false
 python-versions = "*"
-version = "0.1.post0"
 
 [package.dependencies]
 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"
+version = "0.9.4"
+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)."
+category = "dev"
 optional = false
 python-versions = "*"
-version = "0.9.4"
 
 [package.dependencies]
 packaging = ">=14.1"
@@ -1599,23 +1640,12 @@ pytest = ">=2.9"
 termcolor = ">=1.1.0"
 
 [[package]]
-category = "main"
-description = "Advanced Python dictionaries with dot notation access"
-name = "python-box"
-optional = false
-python-versions = "*"
-version = "3.4.6"
-
-[package.extras]
-testing = ["pytest", "coverage (>=3.6)", "pytest-cov"]
-
-[[package]]
-category = "main"
-description = "Python Crontab API"
 name = "python-crontab"
+version = "2.5.1"
+description = "Python Crontab API"
+category = "main"
 optional = true
 python-versions = "*"
-version = "2.5.1"
 
 [package.dependencies]
 python-dateutil = "*"
@@ -1625,76 +1655,65 @@ cron-description = ["cron-descriptor"]
 cron-schedule = ["croniter"]
 
 [[package]]
-category = "main"
-description = "Extensions to the standard Python datetime module"
 name = "python-dateutil"
+version = "2.8.1"
+description = "Extensions to the standard Python datetime module"
+category = "main"
 optional = false
 python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
-version = "2.8.1"
 
 [package.dependencies]
 six = ">=1.5"
 
 [[package]]
-category = "main"
-description = "Add .env support to your django/flask apps in development and deployments"
-name = "python-dotenv"
-optional = false
-python-versions = "*"
-version = "0.14.0"
-
-[package.extras]
-cli = ["click (>=5.0)"]
-
-[[package]]
-category = "main"
-description = "Python modules for implementing LDAP clients"
 name = "python-ldap"
+version = "3.3.1"
+description = "Python modules for implementing LDAP clients"
+category = "main"
 optional = true
 python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
-version = "3.3.1"
 
 [package.dependencies]
 pyasn1 = ">=0.3.7"
 pyasn1_modules = ">=0.1.5"
 
 [[package]]
-category = "main"
-description = "Pure python memcached client"
 name = "python-memcached"
+version = "1.59"
+description = "Pure python memcached client"
+category = "main"
 optional = false
 python-versions = "*"
-version = "1.59"
 
 [package.dependencies]
 six = ">=1.4.0"
 
 [[package]]
-category = "main"
-description = "World timezone definitions, modern and historical"
 name = "pytz"
+version = "2020.4"
+description = "World timezone definitions, modern and historical"
+category = "main"
 optional = false
 python-versions = "*"
-version = "2020.1"
 
 [[package]]
-category = "main"
-description = "YAML parser and emitter for Python"
 name = "pyyaml"
+version = "5.3.1"
+description = "YAML parser and emitter for Python"
+category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "5.3.1"
 
 [[package]]
-category = "main"
-description = "QR Code image generator"
 name = "qrcode"
+version = "6.1"
+description = "QR Code image generator"
+category = "main"
 optional = false
 python-versions = "*"
-version = "6.1"
 
 [package.dependencies]
-colorama = "*"
+colorama = {version = "*", markers = "platform_system == \"Windows\""}
 six = "*"
 
 [package.extras]
@@ -1704,154 +1723,167 @@ pil = ["pillow"]
 test = ["pytest", "pytest-cov", "mock"]
 
 [[package]]
-category = "main"
-description = "Python client for Redis key-value store"
 name = "redis"
+version = "3.5.3"
+description = "Python client for Redis key-value store"
+category = "main"
 optional = true
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "3.5.3"
 
 [package.extras]
 hiredis = ["hiredis (>=0.1.3)"]
 
 [[package]]
-category = "dev"
-description = "Alternative regular expression module, to replace re."
 name = "regex"
+version = "2020.11.13"
+description = "Alternative regular expression module, to replace re."
+category = "dev"
 optional = false
 python-versions = "*"
-version = "2020.7.14"
 
 [[package]]
-category = "main"
-description = "Python HTTP for Humans."
 name = "requests"
+version = "2.25.1"
+description = "Python HTTP for Humans."
+category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "2.24.0"
 
 [package.dependencies]
 certifi = ">=2017.4.17"
-chardet = ">=3.0.2,<4"
+chardet = ">=3.0.2,<5"
 idna = ">=2.5,<3"
-urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26"
+urllib3 = ">=1.21.1,<1.27"
 
 [package.extras]
 security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"]
-socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"]
+socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
 
 [[package]]
-category = "dev"
-description = "reStructuredText linter"
 name = "restructuredtext-lint"
+version = "1.3.2"
+description = "reStructuredText linter"
+category = "dev"
 optional = false
 python-versions = "*"
-version = "1.3.1"
 
 [package.dependencies]
 docutils = ">=0.11,<1.0"
 
 [[package]]
+name = "ruamel.yaml"
+version = "0.16.12"
+description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+"ruamel.yaml.clib" = {version = ">=0.1.2", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.9\""}
+
+[package.extras]
+docs = ["ryd"]
+jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"]
+
+[[package]]
+name = "ruamel.yaml.clib"
+version = "0.2.2"
+description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml"
 category = "main"
-description = "Awesome Django authorization, without the database"
-name = "rules"
 optional = false
 python-versions = "*"
+
+[[package]]
+name = "rules"
 version = "2.2"
+description = "Awesome Django authorization, without the database"
+category = "main"
+optional = false
+python-versions = "*"
 
 [[package]]
-category = "dev"
-description = "Checks installed dependencies for known vulnerabilities."
 name = "safety"
+version = "1.9.0"
+description = "Checks installed dependencies for known vulnerabilities."
+category = "dev"
 optional = false
 python-versions = ">=3.5"
-version = "1.9.0"
 
 [package.dependencies]
 Click = ">=6.0"
 dparse = ">=0.5.1"
 packaging = "*"
 requests = "*"
-setuptools = "*"
 
 [[package]]
-category = "dev"
-description = "An implementation of the SCRAM protocol."
 name = "scramp"
+version = "1.2.0"
+description = "An implementation of the SCRAM protocol."
+category = "dev"
 optional = false
 python-versions = ">=3.5"
-version = "1.2.0"
 
 [[package]]
-category = "dev"
-description = "Python bindings for Selenium"
 name = "selenium"
+version = "3.141.0"
+description = "Python bindings for Selenium"
+category = "dev"
 optional = false
 python-versions = "*"
-version = "3.141.0"
 
 [package.dependencies]
 urllib3 = "*"
 
 [[package]]
-category = "main"
-description = "Python 2 and 3 compatibility utilities"
 name = "six"
+version = "1.15.0"
+description = "Python 2 and 3 compatibility utilities"
+category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
-version = "1.15.0"
 
 [[package]]
-category = "dev"
-description = "A pure Python implementation of a sliding window memory map manager"
 name = "smmap"
+version = "3.0.4"
+description = "A pure Python implementation of a sliding window memory map manager"
+category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "3.0.4"
 
 [[package]]
-category = "dev"
-description = "This package provides 26 stemmers for 25 languages generated from Snowball algorithms."
 name = "snowballstemmer"
-optional = false
-python-versions = "*"
 version = "2.0.0"
-
-[[package]]
-category = "main"
-description = "A modern CSS selector implementation for Beautiful Soup."
-name = "soupsieve"
+description = "This package provides 26 stemmers for 25 languages generated from Snowball algorithms."
+category = "dev"
 optional = false
 python-versions = "*"
-version = "1.9.6"
 
 [[package]]
-category = "main"
-description = "A simple tool/library for working with SPDX license definitions."
 name = "spdx-license-list"
+version = "0.5.2"
+description = "A simple tool/library for working with SPDX license definitions."
+category = "main"
 optional = false
 python-versions = "*"
-version = "0.5.1"
 
 [[package]]
-category = "dev"
-description = "Python documentation generator"
 name = "sphinx"
+version = "3.3.1"
+description = "Python documentation generator"
+category = "dev"
 optional = false
 python-versions = ">=3.5"
-version = "3.2.1"
 
 [package.dependencies]
-Jinja2 = ">=2.3"
-Pygments = ">=2.0"
 alabaster = ">=0.7,<0.8"
 babel = ">=1.3"
-colorama = ">=0.3.5"
+colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""}
 docutils = ">=0.12"
 imagesize = "*"
+Jinja2 = ">=2.3"
 packaging = "*"
+Pygments = ">=2.0"
 requests = ">=2.5.0"
-setuptools = "*"
 snowballstemmer = ">=1.1"
 sphinxcontrib-applehelp = "*"
 sphinxcontrib-devhelp = "*"
@@ -1862,141 +1894,138 @@ sphinxcontrib-serializinghtml = "*"
 
 [package.extras]
 docs = ["sphinxcontrib-websupport"]
-lint = ["flake8 (>=3.5.0)", "flake8-import-order", "mypy (>=0.780)", "docutils-stubs"]
+lint = ["flake8 (>=3.5.0)", "flake8-import-order", "mypy (>=0.790)", "docutils-stubs"]
 test = ["pytest", "pytest-cov", "html5lib", "typed-ast", "cython"]
 
 [[package]]
-category = "dev"
-description = "Type hints (PEP 484) support for the Sphinx autodoc extension"
 name = "sphinx-autodoc-typehints"
+version = "1.11.1"
+description = "Type hints (PEP 484) support for the Sphinx autodoc extension"
+category = "dev"
 optional = false
 python-versions = ">=3.5.2"
-version = "1.11.0"
 
 [package.dependencies]
 Sphinx = ">=3.0"
 
 [package.extras]
-test = ["pytest (>=3.1.0)", "typing-extensions (>=3.5)", "sphobjinv (>=2.0)", "dataclasses"]
+test = ["pytest (>=3.1.0)", "typing-extensions (>=3.5)", "sphobjinv (>=2.0)", "Sphinx (>=3.2.0)", "dataclasses"]
 type_comments = ["typed-ast (>=1.4.0)"]
 
 [[package]]
-category = "dev"
-description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books"
 name = "sphinxcontrib-applehelp"
+version = "1.0.2"
+description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books"
+category = "dev"
 optional = false
 python-versions = ">=3.5"
-version = "1.0.2"
 
 [package.extras]
 lint = ["flake8", "mypy", "docutils-stubs"]
 test = ["pytest"]
 
 [[package]]
-category = "dev"
-description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document."
 name = "sphinxcontrib-devhelp"
+version = "1.0.2"
+description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document."
+category = "dev"
 optional = false
 python-versions = ">=3.5"
-version = "1.0.2"
 
 [package.extras]
 lint = ["flake8", "mypy", "docutils-stubs"]
 test = ["pytest"]
 
 [[package]]
-category = "dev"
-description = "Improve the Sphinx autodoc for Django classes."
 name = "sphinxcontrib-django"
+version = "0.5.1"
+description = "Improve the Sphinx autodoc for Django classes."
+category = "dev"
 optional = false
 python-versions = "*"
-version = "0.5.1"
 
 [[package]]
-category = "dev"
-description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files"
 name = "sphinxcontrib-htmlhelp"
+version = "1.0.3"
+description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files"
+category = "dev"
 optional = false
 python-versions = ">=3.5"
-version = "1.0.3"
 
 [package.extras]
 lint = ["flake8", "mypy", "docutils-stubs"]
 test = ["pytest", "html5lib"]
 
 [[package]]
-category = "dev"
-description = "A sphinx extension which renders display math in HTML via JavaScript"
 name = "sphinxcontrib-jsmath"
+version = "1.0.1"
+description = "A sphinx extension which renders display math in HTML via JavaScript"
+category = "dev"
 optional = false
 python-versions = ">=3.5"
-version = "1.0.1"
 
 [package.extras]
 test = ["pytest", "flake8", "mypy"]
 
 [[package]]
-category = "dev"
-description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document."
 name = "sphinxcontrib-qthelp"
+version = "1.0.3"
+description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document."
+category = "dev"
 optional = false
 python-versions = ">=3.5"
-version = "1.0.3"
 
 [package.extras]
 lint = ["flake8", "mypy", "docutils-stubs"]
 test = ["pytest"]
 
 [[package]]
-category = "dev"
-description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)."
 name = "sphinxcontrib-serializinghtml"
+version = "1.1.4"
+description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)."
+category = "dev"
 optional = false
 python-versions = ">=3.5"
-version = "1.1.4"
 
 [package.extras]
 lint = ["flake8", "mypy", "docutils-stubs"]
 test = ["pytest"]
 
 [[package]]
-category = "main"
-description = "Non-validating SQL parser"
 name = "sqlparse"
+version = "0.4.1"
+description = "A non-validating SQL parser."
+category = "main"
 optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "0.3.1"
+python-versions = ">=3.5"
 
 [[package]]
-category = "dev"
-description = "Manage dynamic plugins for Python applications"
 name = "stevedore"
+version = "3.3.0"
+description = "Manage dynamic plugins for Python applications"
+category = "dev"
 optional = false
 python-versions = ">=3.6"
-version = "3.2.1"
 
 [package.dependencies]
+importlib-metadata = {version = ">=1.7.0", markers = "python_version < \"3.8\""}
 pbr = ">=2.0.0,<2.1.0 || >2.1.0"
 
-[package.dependencies.importlib-metadata]
-python = "<3.8"
-version = ">=1.7.0"
-
 [[package]]
-category = "dev"
-description = "ANSII Color formatting for output in terminal."
 name = "termcolor"
+version = "1.1.0"
+description = "ANSII Color formatting for output in terminal."
+category = "dev"
 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"
+version = "6.17.0"
+description = "A collection of helpers and mock objects for unit tests and doc tests."
+category = "dev"
 optional = false
 python-versions = "*"
-version = "6.14.2"
 
 [package.extras]
 build = ["setuptools-git", "wheel", "twine"]
@@ -2004,23 +2033,23 @@ docs = ["sphinx", "zope.component", "sybil", "twisted", "mock", "django (<2)", "
 test = ["pytest (>=3.6)", "pytest-cov", "pytest-django", "zope.component", "sybil", "twisted", "mock", "django (<2)", "django"]
 
 [[package]]
-category = "dev"
-description = "utilities for testing.* packages"
 name = "testing.common.database"
+version = "2.0.3"
+description = "utilities for testing.* packages"
+category = "dev"
 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"
 name = "testing.postgresql"
+version = "1.3.0"
+description = "automatically setups a postgresql instance in a temporary directory, and destroys it after testing"
+category = "dev"
 optional = false
 python-versions = "*"
-version = "1.3.0"
 
 [package.dependencies]
 pg8000 = ">=1.10"
@@ -2030,164 +2059,166 @@ pg8000 = ">=1.10"
 testing = ["sqlalchemy", "nose", "psycopg2"]
 
 [[package]]
-category = "main"
-description = "The most basic Text::Unidecode port"
 name = "text-unidecode"
+version = "1.3"
+description = "The most basic Text::Unidecode port"
+category = "main"
 optional = false
 python-versions = "*"
-version = "1.3"
 
 [[package]]
-category = "main"
-description = "Python Library for Tom's Obvious, Minimal Language"
 name = "toml"
+version = "0.10.2"
+description = "Python Library for Tom's Obvious, Minimal Language"
+category = "main"
 optional = false
-python-versions = "*"
-version = "0.10.1"
+python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
 
 [[package]]
-category = "main"
-description = "Fast, Extensible Progress Meter"
 name = "tqdm"
+version = "4.54.1"
+description = "Fast, Extensible Progress Meter"
+category = "main"
 optional = false
-python-versions = ">=2.6, !=3.0.*, !=3.1.*"
-version = "4.48.2"
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
 
 [package.extras]
-dev = ["py-make (>=0.1.0)", "twine", "argopt", "pydoc-markdown"]
+dev = ["py-make (>=0.1.0)", "twine", "argopt", "pydoc-markdown", "wheel"]
 
 [[package]]
-category = "main"
-description = "Twilio API client and TwiML generator"
 name = "twilio"
+version = "6.50.1"
+description = "Twilio API client and TwiML generator"
+category = "main"
 optional = false
 python-versions = "*"
-version = "6.45.1"
 
 [package.dependencies]
 PyJWT = ">=1.4.2"
 pytz = "*"
+requests = {version = ">=2.0.0", markers = "python_version >= \"3.0\""}
 six = "*"
 
-[package.dependencies.requests]
-python = ">=3.0"
-version = ">=2.0.0"
-
 [[package]]
-category = "dev"
-description = "a fork of Python 2 and 3 ast modules with type comment support"
 name = "typed-ast"
+version = "1.4.1"
+description = "a fork of Python 2 and 3 ast modules with type comment support"
+category = "dev"
 optional = false
 python-versions = "*"
-version = "1.4.1"
 
 [[package]]
-category = "dev"
-description = "Backported and Experimental Type Hints for Python 3.5+"
 name = "typing-extensions"
+version = "3.7.4.3"
+description = "Backported and Experimental Type Hints for Python 3.5+"
+category = "main"
 optional = false
 python-versions = "*"
-version = "3.7.4.3"
 
 [[package]]
-category = "main"
-description = "HTTP library with thread-safe connection pooling, file post, and more."
 name = "urllib3"
+version = "1.26.2"
+description = "HTTP library with thread-safe connection pooling, file post, and more."
+category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
-version = "1.25.10"
 
 [package.extras]
 brotli = ["brotlipy (>=0.6.0)"]
-secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)", "ipaddress"]
-socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"]
+secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
+socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
 
 [[package]]
-category = "main"
-description = "Promises, promises, promises."
 name = "vine"
+version = "5.0.0"
+description = "Promises, promises, promises."
+category = "main"
 optional = true
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "1.3.0"
+python-versions = ">=3.6"
 
 [[package]]
+name = "wcwidth"
+version = "0.2.5"
+description = "Measures the displayed width of unicode strings in a terminal"
 category = "main"
-description = "Character encoding aliases for legacy web content"
+optional = true
+python-versions = "*"
+
+[[package]]
 name = "webencodings"
+version = "0.5.1"
+description = "Character encoding aliases for legacy web content"
+category = "main"
 optional = false
 python-versions = "*"
-version = "0.5.1"
 
 [[package]]
-category = "main"
-description = "A library for verifying YubiKey OTP tokens, both locally and through a Yubico web service."
 name = "yubiotp"
+version = "1.0.0.post1"
+description = "A library for verifying YubiKey OTP tokens, both locally and through a Yubico web service."
+category = "main"
 optional = false
 python-versions = "*"
-version = "1.0.0.post1"
 
 [package.dependencies]
 pycryptodome = "*"
 
 [[package]]
-category = "main"
-description = "Backport of pathlib-compatible object wrapper for zip files"
-marker = "python_version < \"3.8\""
 name = "zipp"
+version = "3.4.0"
+description = "Backport of pathlib-compatible object wrapper for zip files"
+category = "main"
 optional = false
 python-versions = ">=3.6"
-version = "3.1.0"
 
 [package.extras]
 docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
-testing = ["jaraco.itertools", "func-timeout"]
+testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"]
 
 [extras]
 celery = ["Celery", "django-celery-results", "django-celery-beat", "django-celery-email", "celery-haystack"]
 ldap = ["django-auth-ldap"]
 
 [metadata]
-content-hash = "c914df99dc2cd3722b54f635d716e498ff7da050a867e85b9e85a8e5fae9949e"
-lock-version = "1.0"
+lock-version = "1.1"
 python-versions = "^3.7"
+content-hash = "65a4b7b891965a330e7fec9ed64213579550aac1ac8295aa8c3022a4978d488c"
 
 [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"},
 ]
+aleksis-builddeps = [
+    {file = "AlekSIS-Builddeps-1.tar.gz", hash = "sha256:97a19597f422593cbdc438aabf17f95748126c8951df6ac7db7991fc99c108c4"},
+]
 amqp = [
-    {file = "amqp-2.6.1-py2.py3-none-any.whl", hash = "sha256:aa7f313fb887c91f15474c1229907a04dac0b8135822d6603437803424c0aa59"},
-    {file = "amqp-2.6.1.tar.gz", hash = "sha256:70cdb10628468ff14e57ec2f751c7aa9e48e7e3651cfd62d431213c0c4e58f21"},
+    {file = "amqp-5.0.2-py3-none-any.whl", hash = "sha256:5b9062d5c0812335c75434bf17ce33d7a20ecfedaa0733faec7379868eb4068a"},
+    {file = "amqp-5.0.2.tar.gz", hash = "sha256:fcd5b3baeeb7fc19b3486ff6d10543099d40ae1f5c9196eae695d1cde1b2f784"},
 ]
 appdirs = [
     {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
     {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
 ]
 asgiref = [
-    {file = "asgiref-3.2.10-py3-none-any.whl", hash = "sha256:9fc6fb5d39b8af147ba40765234fa822b39818b12cc80b35ad9b0cef3a476aed"},
-    {file = "asgiref-3.2.10.tar.gz", hash = "sha256:7e51911ee147dd685c3c8b805c0ad0cb58d360987b56953878f8c06d2d1c6f1a"},
+    {file = "asgiref-3.3.1-py3-none-any.whl", hash = "sha256:5ee950735509d04eb673bd7f7120f8fa1c9e2df495394992c73234d526907e17"},
+    {file = "asgiref-3.3.1.tar.gz", hash = "sha256:7162a3cb30ab0609f1a4c95938fd73e8604f63bdba516a7f7d64b83ff09478f0"},
 ]
 atomicwrites = [
     {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"},
     {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
 ]
 attrs = [
-    {file = "attrs-20.2.0-py2.py3-none-any.whl", hash = "sha256:fce7fc47dfc976152e82d53ff92fa0407700c21acd20886a13777a0d20e655dc"},
-    {file = "attrs-20.2.0.tar.gz", hash = "sha256:26b54ddbbb9ee1d34d5d3668dd37d6cf74990ab23c828c2888dccdceee395594"},
+    {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"},
+    {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"},
 ]
 babel = [
-    {file = "Babel-2.8.0-py2.py3-none-any.whl", hash = "sha256:d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4"},
-    {file = "Babel-2.8.0.tar.gz", hash = "sha256:1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38"},
+    {file = "Babel-2.9.0-py2.py3-none-any.whl", hash = "sha256:9d35c22fcc79893c3ecc85ac4a56cde1ecf3f19c540bba0922308a6c06ca6fa5"},
+    {file = "Babel-2.9.0.tar.gz", hash = "sha256:da031ab54472314f210b0adcff1588ee5d1d1d0ba4dbd07b94dba82bde791e05"},
 ]
 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.9.1-py2-none-any.whl", hash = "sha256:e718f2342e2e099b640a34ab782407b7b676f47ee272d6739e60b8ea23829f2c"},
-    {file = "beautifulsoup4-4.9.1-py3-none-any.whl", hash = "sha256:a6237df3c32ccfaee4fd201c8f5f9d9df619b93121d01353a64a73ce8c6ef9a8"},
-    {file = "beautifulsoup4-4.9.1.tar.gz", hash = "sha256:73cc4d115b96f79c7d77c1c7f7a0a8d4c57860d1041df407dd1aae7f07a77fd7"},
+    {file = "bandit-1.7.0-py3-none-any.whl", hash = "sha256:216be4d044209fa06cf2a3e51b319769a51be8318140659719aa7a115c35ed07"},
+    {file = "bandit-1.7.0.tar.gz", hash = "sha256:8a4c7415254d75df8ff3c3b15cfe9042ecee628a1e40b44c15a98890fbfc2608"},
 ]
 billiard = [
     {file = "billiard-3.6.3.0-py3-none-any.whl", hash = "sha256:bff575450859a6e0fbc2f9877d9b715b0bbc07c3565bb7ed2280526a0cdf5ede"},
@@ -2198,44 +2229,54 @@ black = [
     {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"},
 ]
 bleach = [
-    {file = "bleach-3.1.5-py2.py3-none-any.whl", hash = "sha256:2bce3d8fab545a6528c8fa5d9f9ae8ebc85a56da365c7f85180bfe96a35ef22f"},
-    {file = "bleach-3.1.5.tar.gz", hash = "sha256:3c4c520fdb9db59ef139915a5db79f8b51bc2a7257ea0389f30c846883430a4b"},
+    {file = "bleach-3.2.1-py2.py3-none-any.whl", hash = "sha256:9f8ccbeb6183c6e6cddea37592dfb0167485c1e3b13b3363bc325aa8bda3adbd"},
+    {file = "bleach-3.2.1.tar.gz", hash = "sha256:52b5919b81842b1854196eaae5ca29679a2f2e378905c346d3ca8227c2c66080"},
 ]
 "boolean.py" = [
     {file = "boolean.py-3.8-py2.py3-none-any.whl", hash = "sha256:d75da0fd0354425fa64f6bbc6cec6ae1485d0eec3447b73187ff8cbf9b572e26"},
     {file = "boolean.py-3.8.tar.gz", hash = "sha256:cc24e20f985d60cd4a3a5a1c0956dd12611159d32a75081dabd0c9ab981acaa4"},
 ]
 calendarweek = [
-    {file = "calendarweek-0.4.5-py3-none-any.whl", hash = "sha256:b35fcc087073969d017cede62a7295bcd714a1304bcb4c4e2b0f23acb0265fb1"},
-    {file = "calendarweek-0.4.5.tar.gz", hash = "sha256:5b1788ca435022f9348fc81a718974e51dd85d080f9aa3dad717df70a1bc6e1f"},
+    {file = "calendarweek-0.4.7-py3-none-any.whl", hash = "sha256:ee65caea113503dcdb33d96bca9f79f88b3ab4f66279d4cb568d89f1f662608a"},
+    {file = "calendarweek-0.4.7.tar.gz", hash = "sha256:7655d6a4c3b4f6a4e01aa7d23b49cd121db0399050e9c08cd8d1210155be25dd"},
 ]
 celery = [
-    {file = "celery-4.4.7-py2.py3-none-any.whl", hash = "sha256:a92e1d56e650781fb747032a3997d16236d037c8199eacd5217d1a72893bca45"},
-    {file = "celery-4.4.7.tar.gz", hash = "sha256:d220b13a8ed57c78149acf82c006785356071844afe0b27012a4991d44026f9f"},
+    {file = "celery-5.0.5-py3-none-any.whl", hash = "sha256:5e8d364e058554e83bbb116e8377d90c79be254785f357cb2cec026e79febe13"},
+    {file = "celery-5.0.5.tar.gz", hash = "sha256:f4efebe6f8629b0da2b8e529424de376494f5b7a743c321c8a2ddc2b1414921c"},
 ]
 celery-haystack = [
     {file = "celery-haystack-0.10.tar.gz", hash = "sha256:b6e2a3c70071bef0838ca1a7d9f14fae6c2ecf385704092e59b82147a1ee552e"},
     {file = "celery_haystack-0.10-py2.py3-none-any.whl", hash = "sha256:ec1f39050661e033f554de99cb9393c2e94427667ff5401f16393b2a68f888fc"},
 ]
 celery-progress = [
-    {file = "celery-progress-0.0.12.tar.gz", hash = "sha256:df61d61ac2b29e51b61a2cbd070d28b69f9f538d31e5f4b8076d9721251d6c59"},
-    {file = "celery_progress-0.0.12-py3-none-any.whl", hash = "sha256:b3727b1b65c79ec072513eb42f1903eaec64a75d2f691b5664fa660f2bd319ad"},
+    {file = "celery-progress-0.0.14.tar.gz", hash = "sha256:002ead0d3fa3602bd74cf328206b8e2352994ab599711dc20058a5cf2b4db2d1"},
+    {file = "celery_progress-0.0.14-py3-none-any.whl", hash = "sha256:6d95c01fe044dd5dbb1e2d507724f9ace70bde796bc6db51ba19c8a95e94da07"},
 ]
 certifi = [
-    {file = "certifi-2020.6.20-py2.py3-none-any.whl", hash = "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"},
-    {file = "certifi-2020.6.20.tar.gz", hash = "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3"},
+    {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"},
+    {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"},
 ]
 chardet = [
-    {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"},
-    {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"},
+    {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"},
+    {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"},
 ]
 click = [
     {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"},
     {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"},
 ]
+click-didyoumean = [
+    {file = "click-didyoumean-0.0.3.tar.gz", hash = "sha256:112229485c9704ff51362fe34b2d4f0b12fc71cc20f6d2b3afabed4b8bfa6aeb"},
+]
+click-plugins = [
+    {file = "click-plugins-1.1.1.tar.gz", hash = "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b"},
+    {file = "click_plugins-1.1.1-py2.py3-none-any.whl", hash = "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8"},
+]
+click-repl = [
+    {file = "click-repl-0.1.6.tar.gz", hash = "sha256:b9f29d52abc4d6059f8e276132a111ab8d94980afe6a5432b9d996544afa95d5"},
+    {file = "click_repl-0.1.6-py3-none-any.whl", hash = "sha256:9c4c3d022789cae912aad8a3f5e1d7c2cdd016ee1225b5212ad3e8691563cda5"},
+]
 colorama = [
-    {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"},
-    {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"},
+    {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
 ]
 colour = [
     {file = "colour-0.1.5-py2.py3-none-any.whl", hash = "sha256:33f6db9d564fadc16e59921a56999b79571160ce09916303d35346dddc17978c"},
@@ -2245,48 +2286,48 @@ configobj = [
     {file = "configobj-5.0.6.tar.gz", hash = "sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902"},
 ]
 coverage = [
-    {file = "coverage-5.2.1-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:40f70f81be4d34f8d491e55936904db5c527b0711b2a46513641a5729783c2e4"},
-    {file = "coverage-5.2.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:675192fca634f0df69af3493a48224f211f8db4e84452b08d5fcebb9167adb01"},
-    {file = "coverage-5.2.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:2fcc8b58953d74d199a1a4d633df8146f0ac36c4e720b4a1997e9b6327af43a8"},
-    {file = "coverage-5.2.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:64c4f340338c68c463f1b56e3f2f0423f7b17ba6c3febae80b81f0e093077f59"},
-    {file = "coverage-5.2.1-cp27-cp27m-win32.whl", hash = "sha256:52f185ffd3291196dc1aae506b42e178a592b0b60a8610b108e6ad892cfc1bb3"},
-    {file = "coverage-5.2.1-cp27-cp27m-win_amd64.whl", hash = "sha256:30bc103587e0d3df9e52cd9da1dd915265a22fad0b72afe54daf840c984b564f"},
-    {file = "coverage-5.2.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:9ea749fd447ce7fb1ac71f7616371f04054d969d412d37611716721931e36efd"},
-    {file = "coverage-5.2.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ce7866f29d3025b5b34c2e944e66ebef0d92e4a4f2463f7266daa03a1332a651"},
-    {file = "coverage-5.2.1-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:4869ab1c1ed33953bb2433ce7b894a28d724b7aa76c19b11e2878034a4e4680b"},
-    {file = "coverage-5.2.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a3ee9c793ffefe2944d3a2bd928a0e436cd0ac2d9e3723152d6fd5398838ce7d"},
-    {file = "coverage-5.2.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:28f42dc5172ebdc32622a2c3f7ead1b836cdbf253569ae5673f499e35db0bac3"},
-    {file = "coverage-5.2.1-cp35-cp35m-win32.whl", hash = "sha256:e26c993bd4b220429d4ec8c1468eca445a4064a61c74ca08da7429af9bc53bb0"},
-    {file = "coverage-5.2.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4186fc95c9febeab5681bc3248553d5ec8c2999b8424d4fc3a39c9cba5796962"},
-    {file = "coverage-5.2.1-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:b360d8fd88d2bad01cb953d81fd2edd4be539df7bfec41e8753fe9f4456a5082"},
-    {file = "coverage-5.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:1adb6be0dcef0cf9434619d3b892772fdb48e793300f9d762e480e043bd8e716"},
-    {file = "coverage-5.2.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:098a703d913be6fbd146a8c50cc76513d726b022d170e5e98dc56d958fd592fb"},
-    {file = "coverage-5.2.1-cp36-cp36m-win32.whl", hash = "sha256:962c44070c281d86398aeb8f64e1bf37816a4dfc6f4c0f114756b14fc575621d"},
-    {file = "coverage-5.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1ed2bdb27b4c9fc87058a1cb751c4df8752002143ed393899edb82b131e0546"},
-    {file = "coverage-5.2.1-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:c890728a93fffd0407d7d37c1e6083ff3f9f211c83b4316fae3778417eab9811"},
-    {file = "coverage-5.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:538f2fd5eb64366f37c97fdb3077d665fa946d2b6d95447622292f38407f9258"},
-    {file = "coverage-5.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:27ca5a2bc04d68f0776f2cdcb8bbd508bbe430a7bf9c02315cd05fb1d86d0034"},
-    {file = "coverage-5.2.1-cp37-cp37m-win32.whl", hash = "sha256:aab75d99f3f2874733946a7648ce87a50019eb90baef931698f96b76b6769a46"},
-    {file = "coverage-5.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:c2ff24df02a125b7b346c4c9078c8936da06964cc2d276292c357d64378158f8"},
-    {file = "coverage-5.2.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:304fbe451698373dc6653772c72c5d5e883a4aadaf20343592a7abb2e643dae0"},
-    {file = "coverage-5.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:c96472b8ca5dc135fb0aa62f79b033f02aa434fb03a8b190600a5ae4102df1fd"},
-    {file = "coverage-5.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8505e614c983834239f865da2dd336dcf9d72776b951d5dfa5ac36b987726e1b"},
-    {file = "coverage-5.2.1-cp38-cp38-win32.whl", hash = "sha256:700997b77cfab016533b3e7dbc03b71d33ee4df1d79f2463a318ca0263fc29dd"},
-    {file = "coverage-5.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:46794c815e56f1431c66d81943fa90721bb858375fb36e5903697d5eef88627d"},
-    {file = "coverage-5.2.1-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:16042dc7f8e632e0dcd5206a5095ebd18cb1d005f4c89694f7f8aafd96dd43a3"},
-    {file = "coverage-5.2.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:c1bbb628ed5192124889b51204de27c575b3ffc05a5a91307e7640eff1d48da4"},
-    {file = "coverage-5.2.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4f6428b55d2916a69f8d6453e48a505c07b2245653b0aa9f0dee38785939f5e4"},
-    {file = "coverage-5.2.1-cp39-cp39-win32.whl", hash = "sha256:9e536783a5acee79a9b308be97d3952b662748c4037b6a24cbb339dc7ed8eb89"},
-    {file = "coverage-5.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:b8f58c7db64d8f27078cbf2a4391af6aa4e4767cc08b37555c4ae064b8558d9b"},
-    {file = "coverage-5.2.1.tar.gz", hash = "sha256:a34cb28e0747ea15e82d13e14de606747e9e484fb28d63c999483f5d5188e89b"},
+    {file = "coverage-5.3-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:bd3166bb3b111e76a4f8e2980fa1addf2920a4ca9b2b8ca36a3bc3dedc618270"},
+    {file = "coverage-5.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:9342dd70a1e151684727c9c91ea003b2fb33523bf19385d4554f7897ca0141d4"},
+    {file = "coverage-5.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:63808c30b41f3bbf65e29f7280bf793c79f54fb807057de7e5238ffc7cc4d7b9"},
+    {file = "coverage-5.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:4d6a42744139a7fa5b46a264874a781e8694bb32f1d76d8137b68138686f1729"},
+    {file = "coverage-5.3-cp27-cp27m-win32.whl", hash = "sha256:86e9f8cd4b0cdd57b4ae71a9c186717daa4c5a99f3238a8723f416256e0b064d"},
+    {file = "coverage-5.3-cp27-cp27m-win_amd64.whl", hash = "sha256:7858847f2d84bf6e64c7f66498e851c54de8ea06a6f96a32a1d192d846734418"},
+    {file = "coverage-5.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:530cc8aaf11cc2ac7430f3614b04645662ef20c348dce4167c22d99bec3480e9"},
+    {file = "coverage-5.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:381ead10b9b9af5f64646cd27107fb27b614ee7040bb1226f9c07ba96625cbb5"},
+    {file = "coverage-5.3-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:71b69bd716698fa62cd97137d6f2fdf49f534decb23a2c6fc80813e8b7be6822"},
+    {file = "coverage-5.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1d44bb3a652fed01f1f2c10d5477956116e9b391320c94d36c6bf13b088a1097"},
+    {file = "coverage-5.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:1c6703094c81fa55b816f5ae542c6ffc625fec769f22b053adb42ad712d086c9"},
+    {file = "coverage-5.3-cp35-cp35m-win32.whl", hash = "sha256:cedb2f9e1f990918ea061f28a0f0077a07702e3819602d3507e2ff98c8d20636"},
+    {file = "coverage-5.3-cp35-cp35m-win_amd64.whl", hash = "sha256:7f43286f13d91a34fadf61ae252a51a130223c52bfefb50310d5b2deb062cf0f"},
+    {file = "coverage-5.3-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:c851b35fc078389bc16b915a0a7c1d5923e12e2c5aeec58c52f4aa8085ac8237"},
+    {file = "coverage-5.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:aac1ba0a253e17889550ddb1b60a2063f7474155465577caa2a3b131224cfd54"},
+    {file = "coverage-5.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2b31f46bf7b31e6aa690d4c7a3d51bb262438c6dcb0d528adde446531d0d3bb7"},
+    {file = "coverage-5.3-cp36-cp36m-win32.whl", hash = "sha256:c5f17ad25d2c1286436761b462e22b5020d83316f8e8fcb5deb2b3151f8f1d3a"},
+    {file = "coverage-5.3-cp36-cp36m-win_amd64.whl", hash = "sha256:aef72eae10b5e3116bac6957de1df4d75909fc76d1499a53fb6387434b6bcd8d"},
+    {file = "coverage-5.3-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:e8caf961e1b1a945db76f1b5fa9c91498d15f545ac0ababbe575cfab185d3bd8"},
+    {file = "coverage-5.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:29a6272fec10623fcbe158fdf9abc7a5fa032048ac1d8631f14b50fbfc10d17f"},
+    {file = "coverage-5.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:2d43af2be93ffbad25dd959899b5b809618a496926146ce98ee0b23683f8c51c"},
+    {file = "coverage-5.3-cp37-cp37m-win32.whl", hash = "sha256:c3888a051226e676e383de03bf49eb633cd39fc829516e5334e69b8d81aae751"},
+    {file = "coverage-5.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9669179786254a2e7e57f0ecf224e978471491d660aaca833f845b72a2df3709"},
+    {file = "coverage-5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0203acd33d2298e19b57451ebb0bed0ab0c602e5cf5a818591b4918b1f97d516"},
+    {file = "coverage-5.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:582ddfbe712025448206a5bc45855d16c2e491c2dd102ee9a2841418ac1c629f"},
+    {file = "coverage-5.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:0f313707cdecd5cd3e217fc68c78a960b616604b559e9ea60cc16795c4304259"},
+    {file = "coverage-5.3-cp38-cp38-win32.whl", hash = "sha256:78e93cc3571fd928a39c0b26767c986188a4118edc67bc0695bc7a284da22e82"},
+    {file = "coverage-5.3-cp38-cp38-win_amd64.whl", hash = "sha256:8f264ba2701b8c9f815b272ad568d555ef98dfe1576802ab3149c3629a9f2221"},
+    {file = "coverage-5.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:50691e744714856f03a86df3e2bff847c2acede4c191f9a1da38f088df342978"},
+    {file = "coverage-5.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9361de40701666b034c59ad9e317bae95c973b9ff92513dd0eced11c6adf2e21"},
+    {file = "coverage-5.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:c1b78fb9700fc961f53386ad2fd86d87091e06ede5d118b8a50dea285a071c24"},
+    {file = "coverage-5.3-cp39-cp39-win32.whl", hash = "sha256:cb7df71de0af56000115eafd000b867d1261f786b5eebd88a0ca6360cccfaca7"},
+    {file = "coverage-5.3-cp39-cp39-win_amd64.whl", hash = "sha256:47a11bdbd8ada9b7ee628596f9d97fbd3851bd9999d398e9436bd67376dbece7"},
+    {file = "coverage-5.3.tar.gz", hash = "sha256:280baa8ec489c4f542f8940f9c4c2181f0306a8ee1a54eceba071a449fb870a0"},
 ]
 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.1.1-py3-none-any.whl", hash = "sha256:b5fbb818e751f660fa2d576d9f40c34a4c615c8b48dd383f5216e609f383371f"},
-    {file = "Django-3.1.1.tar.gz", hash = "sha256:59c8125ca873ed3bdae9c12b146fbbd6ed8d0f743e4cf5f5817af50c51f1fc2f"},
+    {file = "Django-3.1.4-py3-none-any.whl", hash = "sha256:5c866205f15e7a7123f1eec6ab939d22d5bde1416635cab259684af66d8e48a2"},
+    {file = "Django-3.1.4.tar.gz", hash = "sha256:edb10b5c45e7e9c0fb1dc00b76ec7449aca258a39ffd613dbd078c51d19c9f03"},
 ]
 django-any-js = [
     {file = "django-any-js-1.0.3.post0.tar.gz", hash = "sha256:1da88b44b861b0f54f6b8ea0eb4c7c4fa1a5772e9a4320532cd4e0871a4e23f7"},
@@ -2307,21 +2348,25 @@ 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-cachalot = [
+    {file = "django-cachalot-2.3.3.tar.gz", hash = "sha256:ba3a6cabf834139196179c4f6d77409ae9170267ee8ce40e27bbf6c3f6733b2b"},
+    {file = "django_cachalot-2.3.3-py3-none-any.whl", hash = "sha256:55f94e94f7000f5f6bd92188d3d7535cfdef79f2e697e36daf69cba8f435e156"},
+]
 django-cache-memoize = [
     {file = "django-cache-memoize-0.1.7.tar.gz", hash = "sha256:5e96349b0159aec1eb79257199a1902ea3ed538231ce7b4fee12e563127ca657"},
     {file = "django_cache_memoize-0.1.7-py2.py3-none-any.whl", hash = "sha256:bc7f53725558244af62197d0125732d7ec88ecc1281a3a2f37d77ae1a8c269d3"},
 ]
 django-celery-beat = [
-    {file = "django-celery-beat-2.0.0.tar.gz", hash = "sha256:fdf1255eecfbeb770c6521fe3e69989dfc6373cd5a7f0fe62038d37f80f47e48"},
-    {file = "django_celery_beat-2.0.0-py2.py3-none-any.whl", hash = "sha256:fe0b2a1b31d4a6234fea4b31986ddfd4644a48fab216ce1843f3ed0ddd2e9097"},
+    {file = "django-celery-beat-2.1.0.tar.gz", hash = "sha256:4eb0e8412e2e05ba0029912a6f80d1054731001eecbcb4d59688c4e07cf4d9d3"},
+    {file = "django_celery_beat-2.1.0-py2.py3-none-any.whl", hash = "sha256:8a169e11d96faed8b72d505ddbc70e7fe0b16cdc854df43cb209c153ed08d651"},
 ]
 django-celery-email = [
     {file = "django-celery-email-3.0.0.tar.gz", hash = "sha256:5546cbba80952cc3b8a0ffa4206ce90a4a996a7ffd1c385a2bdb65903ca18ece"},
     {file = "django_celery_email-3.0.0-py2.py3-none-any.whl", hash = "sha256:0f72da39cb2ea83c69440566e87f27cd72f68f247f98ce99fb29889fcf329406"},
 ]
 django-celery-results = [
-    {file = "django_celery_results-1.2.1-py2.py3-none-any.whl", hash = "sha256:a29ab580f0e38c66c39f51cc426bbdbb2a391b8cc0867df9dea748db2c961db2"},
-    {file = "django_celery_results-1.2.1.tar.gz", hash = "sha256:e390f70cc43bbc2cd7c8e4607dc29ab6211a2ab968f93677583f0160921f670c"},
+    {file = "django_celery_results-2.0.0-py2.py3-none-any.whl", hash = "sha256:f82280a9a25c44048b9e64ae4d47ade7d522c8221304b0e25388080021b95468"},
+    {file = "django_celery_results-2.0.0.tar.gz", hash = "sha256:754e01f22f70fddee5f2ca95c18f183fccee42ad98f9803577bffa717d45ac5d"},
 ]
 django-ckeditor = [
     {file = "django-ckeditor-6.0.0.tar.gz", hash = "sha256:29fd1a333cb9741ac2c3fd4e427a5c00115ed33a2389716a09af7656022dcdde"},
@@ -2342,17 +2387,13 @@ django-dynamic-preferences = [
     {file = "django-dynamic-preferences-1.10.1.tar.gz", hash = "sha256:e4b2bb7b2563c5064ba56dd76441c77e06b850ff1466a386a1cd308909a6c7de"},
     {file = "django_dynamic_preferences-1.10.1-py2.py3-none-any.whl", hash = "sha256:9419fa925fd2cbb665269ae72059eb3058bf080913d853419b827e4e7a141902"},
 ]
-django-easy-audit = [
-    {file = "django-easy-audit-1.3.0a5.tar.gz", hash = "sha256:bb6c0291c360fe305d5cdbbc02d9bdec82885788a675871f80ec1b2b0fd6d443"},
-    {file = "django_easy_audit-1.3.0a5-py3-none-any.whl", hash = "sha256:01f66d19b15b19c377754848157f87dc78fe88cd1ed9bd7314d24438fb95504a"},
-]
 django-favicon-plus-reloaded = [
     {file = "django-favicon-plus-reloaded-1.0.4.tar.gz", hash = "sha256:90c761c636a338e6e9fb1d086649d82095085f92cff816c9cf074607f28c85a5"},
     {file = "django_favicon_plus_reloaded-1.0.4-py3-none-any.whl", hash = "sha256:26e4316d41328a61ced52c7fc0ead795f0eb194d6a30311c34a9833c6fe30a7c"},
 ]
 django-filter = [
-    {file = "django-filter-2.3.0.tar.gz", hash = "sha256:11e63dd759835d9ba7a763926ffb2662cf8a6dcb4c7971a95064de34dbc7e5af"},
-    {file = "django_filter-2.3.0-py3-none-any.whl", hash = "sha256:616848eab6fc50193a1b3730140c49b60c57a3eda1f7fc57fa8505ac156c6c75"},
+    {file = "django-filter-2.4.0.tar.gz", hash = "sha256:84e9d5bb93f237e451db814ed422a3a625751cbc9968b484ecc74964a8696b06"},
+    {file = "django_filter-2.4.0-py3-none-any.whl", hash = "sha256:e00d32cebdb3d54273c48f4f878f898dced8d5dfaad009438fe61ebdf535ace1"},
 ]
 django-formtools = [
     {file = "django-formtools-2.2.tar.gz", hash = "sha256:c5272c03c1cd51b2375abf7397a199a3148a9fbbf2f100e186467a84025d13b2"},
@@ -2371,18 +2412,14 @@ django-haystack = [
     {file = "django_haystack-3.0b1-py3-none-any.whl", hash = "sha256:b83705e1cf8141cd1755fc6683ac65fea4e1281f4b4306bc9224af96495b0df3"},
 ]
 django-health-check = [
-    {file = "django-health-check-3.12.1.tar.gz", hash = "sha256:0563827e003d25fd4d9ebbd7467dea5f390435628d645aaa63f8889deaded73a"},
-    {file = "django_health_check-3.12.1-py2.py3-none-any.whl", hash = "sha256:9e6b7d93d4902901474efd4e25d31b5aaea7563b570c0260adce52cd3c3a9e36"},
-]
-django-image-cropping = [
-    {file = "django-image-cropping-1.4.0.tar.gz", hash = "sha256:6cc4a6bd8901e69b710caceea29b942fdb202da26626313cd9271ae989a83a52"},
-    {file = "django_image_cropping-1.4.0-py3-none-any.whl", hash = "sha256:fe6a139c6d5dfc480f2a1d4e7e3e928d5edaefc898e17be66bc5f73140762ad9"},
+    {file = "django-health-check-3.16.1.tar.gz", hash = "sha256:2cb3944e313e435bdf299288e109f398b6c08b610e09cc90d7f5f6a2bcf469fc"},
+    {file = "django_health_check-3.16.1-py2.py3-none-any.whl", hash = "sha256:8b0835f04ebaeb0d12498a5ef47dd22196237c3987ff28bcce9ed28b5a169d5e"},
 ]
 django-impersonate = [
-    {file = "django-impersonate-1.5.1.tar.gz", hash = "sha256:7c786ffaa7a5dd430f9277b53a64676c470b684eee5aa52c3b483298860d09b4"},
+    {file = "django-impersonate-1.7.tar.gz", hash = "sha256:1dadb5239a5cb79d4327ef3000cd989f9d4e76428a5e2a080496a53b41fa785f"},
 ]
 django-ipware = [
-    {file = "django-ipware-3.0.1.tar.gz", hash = "sha256:73a640a5bff00aa7503a35e92e462001cfabb07d73d649c262f117423beee953"},
+    {file = "django-ipware-3.0.2.tar.gz", hash = "sha256:c7df8e1410a8e5d6b1fbae58728402ea59950f043c3582e033e866f0f0cf5e94"},
 ]
 django-js-asset = [
     {file = "django-js-asset-1.2.2.tar.gz", hash = "sha256:c163ae80d2e0b22d8fb598047cd0dcef31f81830e127cfecae278ad574167260"},
@@ -2396,41 +2433,50 @@ django-jsonstore = [
     {file = "django-jsonstore-0.4.1.tar.gz", hash = "sha256:d6e42152af3f924e4657c99e80144ba9a6410799256f6134b5a4e9fa4282ec10"},
 ]
 django-maintenance-mode = [
-    {file = "django-maintenance-mode-0.14.0.tar.gz", hash = "sha256:f3fef1760fdcda5e0bf6c2966aadc77eea6f328580a9c751920daba927281a68"},
-    {file = "django_maintenance_mode-0.14.0-py2-none-any.whl", hash = "sha256:b4cc24a469ed10897826a28f05d64e6166a58d130e4940ac124ce198cd4cc778"},
+    {file = "django-maintenance-mode-0.15.1.tar.gz", hash = "sha256:d07102cab88dd707a82232f0c552c287e62aa53af582a0ca4f2aa31f14f5ed27"},
+    {file = "django_maintenance_mode-0.15.1-py3-none-any.whl", hash = "sha256:8c45b400253076655562c99a2ffb88f8353fc1c84496c1b9de812cc8132aea6f"},
 ]
 django-material = [
-    {file = "django-material-1.7.0.tar.gz", hash = "sha256:8d8e76605aa77de3d37d8ae4db1835479e484f1530485fd6b66535244f64131b"},
-    {file = "django_material-1.7.0-py2.py3-none-any.whl", hash = "sha256:ff36170e400158a22f9b675e1c07d42267bf1a92122518f68db0c2bf3410096d"},
+    {file = "django-material-1.7.3.tar.gz", hash = "sha256:00599cd87f19f3a66be065865b223801afa9da680744a5eac10c489a10d98eba"},
+    {file = "django_material-1.7.3-py2.py3-none-any.whl", hash = "sha256:6c010aa47618ceae2617f8a476b55aaed0884b1a4a6f5bdf969448a1ba72e352"},
 ]
 django-menu-generator = [
-    {file = "django-menu-generator-1.0.4.tar.gz", hash = "sha256:ce71a5055c16933c8aff64fb36c21e5cf8b6d505733aceed1252f8b99369a378"},
+    {file = "django-menu-generator-1.1.0.tar.gz", hash = "sha256:e8f9b808080c4b281f9c5962f39078c76c2007a5ef8ab1f7a81c81dbbe6a9848"},
 ]
 django-middleware-global-request = [
     {file = "django-middleware-global-request-0.1.2.tar.gz", hash = "sha256:f6490759bc9f7dbde4001709554e29ca715daf847f2222914b4e47117dca9313"},
 ]
+django-model-utils = [
+    {file = "django-model-utils-4.1.1.tar.gz", hash = "sha256:eb5dd05ef7d7ce6bc79cae54ea7c4a221f6f81e2aad7722933aee66489e7264b"},
+    {file = "django_model_utils-4.1.1-py3-none-any.whl", hash = "sha256:ef7c440024e797796a3811432abdd2be8b5225ae64ef346f8bfc6de7d8e5d73c"},
+]
 django-otp = [
-    {file = "django-otp-0.9.4.tar.gz", hash = "sha256:50e54bc09bc435e2ad88f0aa7008718079c3529c422b469b3991a97d28b147bb"},
-    {file = "django_otp-0.9.4-py3-none-any.whl", hash = "sha256:6b92c69021558765e80411479a01788977106d5696c391d2e5342074c1dd74d1"},
+    {file = "django-otp-1.0.2.tar.gz", hash = "sha256:f523fb9dec420f28a29d3e2ad72ac06f64588956ed4f2b5b430d8e957ebb8287"},
+    {file = "django_otp-1.0.2-py3-none-any.whl", hash = "sha256:8ba5ab9bd2738c7321376c349d7cce49cf4404e79f6804e0a3cc462a91728e18"},
 ]
 django-otp-yubikey = [
-    {file = "django-otp-yubikey-0.6.0.tar.gz", hash = "sha256:6961f16cfec1dddfa3f3c794ec5967cbb4d412f488de1d7d14441df462ba9843"},
-    {file = "django_otp_yubikey-0.6.0-py2.py3-none-any.whl", hash = "sha256:0dd73c2145afc66f43d537d2789a7068a99bc05b5ffa33d2ad73a49e4c4c7dcb"},
+    {file = "django-otp-yubikey-1.0.0.tar.gz", hash = "sha256:fbd409277892229b7e3578faa4f63ea766e242659456939164c8f71b845287b6"},
+    {file = "django_otp_yubikey-1.0.0-py2.py3-none-any.whl", hash = "sha256:07743473024900c3b7a14647039f2cf66148cf6243d6aee0853ba45516c224a4"},
 ]
 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-polymorphic = [
-    {file = "django-polymorphic-2.1.2.tar.gz", hash = "sha256:6e08a76c91066635ccb7ef3ebbe9a0ad149febae6b30be2579716ec16d3c6461"},
-    {file = "django_polymorphic-2.1.2-py2.py3-none-any.whl", hash = "sha256:0a25058e95e5e99fe0beeabb8f4734effe242d7b5b77dca416fba9fd3062da6a"},
+    {file = "django-polymorphic-3.0.0.tar.gz", hash = "sha256:9d886f19f031d26bb1391c055ed9be06fb226a04a4cec1842b372c58873b3caa"},
+    {file = "django_polymorphic-3.0.0-py2.py3-none-any.whl", hash = "sha256:73b75eb44ea302bd32820f8661e469509d245ce7f7ff09cd2ad149e5c42034ff"},
+]
+django-prometheus = [
+    {file = "django-prometheus-2.1.0.tar.gz", hash = "sha256:dd3f8da1399140fbef5c00d1526a23d1ade286b144281c325f8e409a781643f2"},
+    {file = "django_prometheus-2.1.0-py2.py3-none-any.whl", hash = "sha256:c338d6efde1ca336e90c540b5e87afe9287d7bcc82d651a778f302b0be17a933"},
 ]
 django-pwa = [
     {file = "django-pwa-1.0.10.tar.gz", hash = "sha256:07ed9dd57108838e3fe44b551a82032ca4ed76e31cb3c3e8d51604e0fe7e81e9"},
     {file = "django_pwa-1.0.10-py3-none-any.whl", hash = "sha256:b1a2057b1e72c40c3a14beb90b958482da185f1d40a141fcae3d76580984b930"},
 ]
 django-render-block = [
-    {file = "django_render_block-0.7-py3-none-any.whl", hash = "sha256:3e5963a2332727ca0db2bb8ca031404fba3ac9024702446eed5b7fb02209f7f3"},
+    {file = "django-render-block-0.8.1.tar.gz", hash = "sha256:edbc5d444cc50f3eb3387cf17f6f1014bf19d6018f680861cdeae9e0306003fa"},
+    {file = "django_render_block-0.8.1-py3-none-any.whl", hash = "sha256:903969efd0949f750c5fe71affe6e6b1ea66d03005c102a67fda36d5b9f4e1e1"},
 ]
 django-reversion = [
     {file = "django-reversion-3.0.8.tar.gz", hash = "sha256:49e9930f90322dc6a2754dd26144285cfcc1c5bd0c1c39ca95d5602c5054ae32"},
@@ -2440,30 +2486,30 @@ django-sass-processor = [
     {file = "django-sass-processor-0.8.2.tar.gz", hash = "sha256:9b46a12ca8bdcb397d46fbcc49e6a926ff9f76a93c5efeb23b495419fd01fc7a"},
 ]
 django-select2 = [
-    {file = "django-select2-7.4.2.tar.gz", hash = "sha256:9d3330fa0083a03fb69fceb5dcd2e78065cfd08e45c89d4fd727fce4673d3e08"},
-    {file = "django_select2-7.4.2-py2.py3-none-any.whl", hash = "sha256:06531d563ce33c3133682ae2bb9e6d762103a863d0054ffef51bae8b4cfcca6c"},
+    {file = "django-select2-7.5.0.tar.gz", hash = "sha256:df71dedba9a362041b65e3cd692cb8b4f9e1e17a19681c7b4e61f331868bae0c"},
+    {file = "django_select2-7.5.0-py2.py3-none-any.whl", hash = "sha256:6662aa1c21d4839b8fff38e4c9d402ed3da81f7c5ef7f7e703c862255ba3b9ed"},
 ]
 django-settings-context-processor = [
     {file = "django-settings-context-processor-0.2.tar.gz", hash = "sha256:d37c853d69a3069f5abbf94c7f4f6fc0fac38bbd0524190cd5a250ba800e496a"},
 ]
 django-stubs = [
-    {file = "django-stubs-1.5.0.tar.gz", hash = "sha256:b4c9042f2c2fcb89dc0fb37df070cce0f9bf93b39ae193847b6e5a392b13c430"},
-    {file = "django_stubs-1.5.0-py3-none-any.whl", hash = "sha256:4a03df70e062f4133085efc461148d0934f36ccfd60a2b6d7bc35821f62008b6"},
+    {file = "django-stubs-1.7.0.tar.gz", hash = "sha256:ddd190aca5b9adb4d30760d5c64f67cb3658703f5f42c3bb0c2c71ff4d752c39"},
+    {file = "django_stubs-1.7.0-py3-none-any.whl", hash = "sha256:30a7d99c694acf79c5d93d69a5a8e4b54d2a8c11dd672aa869006789e2189fa6"},
 ]
 django-tables2 = [
-    {file = "django-tables2-2.3.1.tar.gz", hash = "sha256:28da782f81f046c7d921246f43e7ba2df430cafe5a0e00a0f9dadef25a0e487d"},
-    {file = "django_tables2-2.3.1-py2.py3-none-any.whl", hash = "sha256:7e425ad51e22caf5470351981f0fcd4fd35d4cf2d4c3b76fa1b7bf56251778d1"},
+    {file = "django-tables2-2.3.3.tar.gz", hash = "sha256:ad38ece83157b8b9c1fb72b0316fcffc4b32d7fec53eec3f2847b83c4c0a2cb2"},
+    {file = "django_tables2-2.3.3-py2.py3-none-any.whl", hash = "sha256:9c175834130ebb2b3a5644431391e09128405e4538637804d5e74a8b96fc9f68"},
 ]
 django-templated-email = [
     {file = "django-templated-email-2.3.0.tar.gz", hash = "sha256:536c4e5ae099eabfb9aab36087d4d7799948c654e73da55a744213d086d5bb33"},
 ]
 django-timezone-field = [
-    {file = "django-timezone-field-4.0.tar.gz", hash = "sha256:7e3620fe2211c2d372fad54db8f86ff884098d018d56fda4dca5e64929e05ffc"},
-    {file = "django_timezone_field-4.0-py3-none-any.whl", hash = "sha256:758b7d41084e9ea2e89e59eb616e9b6326e6fbbf9d14b6ef062d624fe8cc6246"},
+    {file = "django-timezone-field-4.1.1.tar.gz", hash = "sha256:b5b587aabed8db66eb3453691522164915c1aa1b326d8ddeadc8832a8580faeb"},
+    {file = "django_timezone_field-4.1.1-py3-none-any.whl", hash = "sha256:068dc2c9b11c2230e126f511a515609d46f8cc49278b293e7536be07997fe892"},
 ]
 django-two-factor-auth = [
-    {file = "django-two-factor-auth-1.12.1.tar.gz", hash = "sha256:8e698d548a5a7c02c7ba343bc5376a7bbdc4e59c20ef13223743fe42fa4a1281"},
-    {file = "django_two_factor_auth-1.12.1-py2.py3-none-any.whl", hash = "sha256:612adb0dd6e9ed3b4ecd6763f2e3f56358d7b5afb843a3a49994d1d3bc91ffc2"},
+    {file = "django-two-factor-auth-1.13.tar.gz", hash = "sha256:24c2850a687c86800f4aa4131b7cebadf56f35be04ca359c4990578df1cc249a"},
+    {file = "django_two_factor_auth-1.13-py2.py3-none-any.whl", hash = "sha256:afb60e62f22b1f29a568666c0444ab05cabe8acc4d7c54d833d67f7b50f842fd"},
 ]
 django-widget-tweaks = [
     {file = "django-widget-tweaks-1.4.8.tar.gz", hash = "sha256:9f91ca4217199b7671971d3c1f323a2bec71a0c27dec6260b3c006fa541bc489"},
@@ -2481,19 +2527,16 @@ dparse = [
     {file = "dparse-0.5.1.tar.gz", hash = "sha256:a1b5f169102e1c894f9a7d5ccf6f9402a836a5d24be80a986c7ce9eaed78f367"},
 ]
 dynaconf = [
-    {file = "dynaconf-2.2.3-py2.py3-none-any.whl", hash = "sha256:e803cdab2d7addd539c4ee8d121f15ab0b63a83a5b723150e1746aa7e8063adb"},
-    {file = "dynaconf-2.2.3.tar.gz", hash = "sha256:26b84f2b234a203f6005463d954c9f007181c09345eaaab3fc38503acbdadc7d"},
-]
-easy-thumbnails = [
-    {file = "easy-thumbnails-2.7.tar.gz", hash = "sha256:e4e7a0dd4001f56bfd4058428f2c91eafe27d33ef3b8b33ac4e013b159b9ff91"},
+    {file = "dynaconf-3.1.2-py2.py3-none-any.whl", hash = "sha256:808adfe964f10695846dbf8dad7632e47fc3bc38860fd1887ed57dddffc4eff2"},
+    {file = "dynaconf-3.1.2.tar.gz", hash = "sha256:9b34ab2f811a81755f5eb4beac77a69e1e0887528c7e37fc4bc83fed52dcf502"},
 ]
 faker = [
-    {file = "Faker-4.1.2-py3-none-any.whl", hash = "sha256:bc4b8c908dfcd84e4fe5d9fa2e52fbe17546515fb8f126909b98c47badf05658"},
-    {file = "Faker-4.1.2.tar.gz", hash = "sha256:ff188c416864e3f7d8becd8f9ee683a4b4101a2a2d2bcdcb3e84bb1bdd06eaae"},
+    {file = "Faker-5.0.2-py3-none-any.whl", hash = "sha256:5b17c95cfb013a22b062b8df18286f08ce4ea880f9948ec74295e5a42dbb2e44"},
+    {file = "Faker-5.0.2.tar.gz", hash = "sha256:00ce4342c221b1931b2f35d46f5027d35bc62a4ca3a34628b2c5b514b4ca958a"},
 ]
 flake8 = [
-    {file = "flake8-3.8.3-py2.py3-none-any.whl", hash = "sha256:15e351d19611c887e482fb960eae4d44845013cc142d42896e9862f775d8cf5c"},
-    {file = "flake8-3.8.3.tar.gz", hash = "sha256:f04b9fcbac03b0a3e58c0ab3a0ecc462e023a9faf046d57794184028123aa208"},
+    {file = "flake8-3.8.4-py2.py3-none-any.whl", hash = "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839"},
+    {file = "flake8-3.8.4.tar.gz", hash = "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b"},
 ]
 flake8-bandit = [
     {file = "flake8_bandit-2.1.2.tar.gz", hash = "sha256:687fc8da2e4a239b206af2e54a90093572a60d0954f3054e23690739b0b0de3b"},
@@ -2537,8 +2580,8 @@ gitdb = [
     {file = "gitdb-4.0.5.tar.gz", hash = "sha256:c9e1f2d0db7ddb9a704c2a0217be31214e91a4fe1dea1efad19ae42ba0c285c9"},
 ]
 gitpython = [
-    {file = "GitPython-3.1.8-py3-none-any.whl", hash = "sha256:1858f4fd089abe92ae465f01d5aaaf55e937eca565fb2c1fce35a51b5f85c910"},
-    {file = "GitPython-3.1.8.tar.gz", hash = "sha256:080bf8e2cf1a2b907634761c2eaefbe83b69930c94c66ad11b65a8252959f912"},
+    {file = "GitPython-3.1.11-py3-none-any.whl", hash = "sha256:6eea89b655917b500437e9668e4a12eabdcf00229a0df1762aabd692ef9b746b"},
+    {file = "GitPython-3.1.11.tar.gz", hash = "sha256:befa4d101f91bad1b632df4308ec64555db684c360bd7d2130b4807d49ce86b8"},
 ]
 html2text = [
     {file = "html2text-2020.1.16-py3-none-any.whl", hash = "sha256:c7c629882da0cf377d66f073329ccf34a12ed2adf0169b9285ae4e63ef54c82b"},
@@ -2553,24 +2596,23 @@ imagesize = [
     {file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"},
 ]
 importlib-metadata = [
-    {file = "importlib_metadata-1.7.0-py2.py3-none-any.whl", hash = "sha256:dc15b2969b4ce36305c51eebe62d418ac7791e9a157911d58bfb1f9ccd8e2070"},
-    {file = "importlib_metadata-1.7.0.tar.gz", hash = "sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83"},
+    {file = "importlib_metadata-3.3.0-py3-none-any.whl", hash = "sha256:bf792d480abbd5eda85794e4afb09dd538393f7d6e6ffef6e9f03d2014cf9450"},
+    {file = "importlib_metadata-3.3.0.tar.gz", hash = "sha256:5c5a2720817414a6c41f0a49993908068243ae02c1635a228126519b509c8aed"},
 ]
 iniconfig = [
-    {file = "iniconfig-1.0.1-py3-none-any.whl", hash = "sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437"},
-    {file = "iniconfig-1.0.1.tar.gz", hash = "sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69"},
+    {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
 ]
 isort = [
-    {file = "isort-5.5.1-py3-none-any.whl", hash = "sha256:a200d47b7ee8b7f7d0a9646650160c4a51b6a91a9413fd31b1da2c4de789f5d3"},
-    {file = "isort-5.5.1.tar.gz", hash = "sha256:92533892058de0306e51c88f22ece002a209dc8e80288aa3cec6d443060d584f"},
+    {file = "isort-5.6.4-py3-none-any.whl", hash = "sha256:dcab1d98b469a12a1a624ead220584391648790275560e1a43e54c5dceae65e7"},
+    {file = "isort-5.6.4.tar.gz", hash = "sha256:dcaeec1b5f0eca77faea2a35ab790b4f3680ff75590bfcb7145986905aab2f58"},
 ]
 jinja2 = [
     {file = "Jinja2-2.11.2-py2.py3-none-any.whl", hash = "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"},
     {file = "Jinja2-2.11.2.tar.gz", hash = "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0"},
 ]
 kombu = [
-    {file = "kombu-4.6.11-py2.py3-none-any.whl", hash = "sha256:be48cdffb54a2194d93ad6533d73f69408486483d189fe9f5990ee24255b0e0a"},
-    {file = "kombu-4.6.11.tar.gz", hash = "sha256:ca1b45faac8c0b18493d02a8571792f3c40291cf2bcf1f55afed3d8f3aa7ba74"},
+    {file = "kombu-5.0.2-py2.py3-none-any.whl", hash = "sha256:6dc509178ac4269b0e66ab4881f70a2035c33d3a622e20585f965986a5182006"},
+    {file = "kombu-5.0.2.tar.gz", hash = "sha256:f4965fba0a4718d47d470beeb5d6446e3357a62402b16c510b6a2f251e05ac3c"},
 ]
 libsass = [
     {file = "libsass-0.20.1-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:4a246e4b88fd279abef8b669206228c92534d96ddcd0770d7012088c408dff23"},
@@ -2630,97 +2672,120 @@ 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.5.0.tar.gz", hash = "sha256:6f83822ae94818eae2612063a5101a7311e68ae8002005b5e05f03fd74a86a20"},
-    {file = "more_itertools-8.5.0-py3-none-any.whl", hash = "sha256:9b30f12df9393f0d28af9210ff8efe48d10c94f73e5daf886f10c4b0b0b4f03c"},
-]
 mypy = [
-    {file = "mypy-0.770-cp35-cp35m-macosx_10_6_x86_64.whl", hash = "sha256:a34b577cdf6313bf24755f7a0e3f3c326d5c1f4fe7422d1d06498eb25ad0c600"},
-    {file = "mypy-0.770-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:86c857510a9b7c3104cf4cde1568f4921762c8f9842e987bc03ed4f160925754"},
-    {file = "mypy-0.770-cp35-cp35m-win_amd64.whl", hash = "sha256:a8ffcd53cb5dfc131850851cc09f1c44689c2812d0beb954d8138d4f5fc17f65"},
-    {file = "mypy-0.770-cp36-cp36m-macosx_10_6_x86_64.whl", hash = "sha256:7687f6455ec3ed7649d1ae574136835a4272b65b3ddcf01ab8704ac65616c5ce"},
-    {file = "mypy-0.770-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:3beff56b453b6ef94ecb2996bea101a08f1f8a9771d3cbf4988a61e4d9973761"},
-    {file = "mypy-0.770-cp36-cp36m-win_amd64.whl", hash = "sha256:15b948e1302682e3682f11f50208b726a246ab4e6c1b39f9264a8796bb416aa2"},
-    {file = "mypy-0.770-cp37-cp37m-macosx_10_6_x86_64.whl", hash = "sha256:b90928f2d9eb2f33162405f32dde9f6dcead63a0971ca8a1b50eb4ca3e35ceb8"},
-    {file = "mypy-0.770-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:c56ffe22faa2e51054c5f7a3bc70a370939c2ed4de308c690e7949230c995913"},
-    {file = "mypy-0.770-cp37-cp37m-win_amd64.whl", hash = "sha256:8dfb69fbf9f3aeed18afffb15e319ca7f8da9642336348ddd6cab2713ddcf8f9"},
-    {file = "mypy-0.770-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:219a3116ecd015f8dca7b5d2c366c973509dfb9a8fc97ef044a36e3da66144a1"},
-    {file = "mypy-0.770-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7ec45a70d40ede1ec7ad7f95b3c94c9cf4c186a32f6bacb1795b60abd2f9ef27"},
-    {file = "mypy-0.770-cp38-cp38-win_amd64.whl", hash = "sha256:f91c7ae919bbc3f96cd5e5b2e786b2b108343d1d7972ea130f7de27fdd547cf3"},
-    {file = "mypy-0.770-py3-none-any.whl", hash = "sha256:3b1fc683fb204c6b4403a1ef23f0b1fac8e4477091585e0c8c54cbdf7d7bb164"},
-    {file = "mypy-0.770.tar.gz", hash = "sha256:8a627507ef9b307b46a1fea9513d5c98680ba09591253082b4c48697ba05a4ae"},
+    {file = "mypy-0.790-cp35-cp35m-macosx_10_6_x86_64.whl", hash = "sha256:bd03b3cf666bff8d710d633d1c56ab7facbdc204d567715cb3b9f85c6e94f669"},
+    {file = "mypy-0.790-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:2170492030f6faa537647d29945786d297e4862765f0b4ac5930ff62e300d802"},
+    {file = "mypy-0.790-cp35-cp35m-win_amd64.whl", hash = "sha256:e86bdace26c5fe9cf8cb735e7cedfe7850ad92b327ac5d797c656717d2ca66de"},
+    {file = "mypy-0.790-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e97e9c13d67fbe524be17e4d8025d51a7dca38f90de2e462243ab8ed8a9178d1"},
+    {file = "mypy-0.790-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0d34d6b122597d48a36d6c59e35341f410d4abfa771d96d04ae2c468dd201abc"},
+    {file = "mypy-0.790-cp36-cp36m-win_amd64.whl", hash = "sha256:72060bf64f290fb629bd4a67c707a66fd88ca26e413a91384b18db3876e57ed7"},
+    {file = "mypy-0.790-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:eea260feb1830a627fb526d22fbb426b750d9f5a47b624e8d5e7e004359b219c"},
+    {file = "mypy-0.790-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:c614194e01c85bb2e551c421397e49afb2872c88b5830e3554f0519f9fb1c178"},
+    {file = "mypy-0.790-cp37-cp37m-win_amd64.whl", hash = "sha256:0a0d102247c16ce93c97066443d11e2d36e6cc2a32d8ccc1f705268970479324"},
+    {file = "mypy-0.790-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cf4e7bf7f1214826cf7333627cb2547c0db7e3078723227820d0a2490f117a01"},
+    {file = "mypy-0.790-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:af4e9ff1834e565f1baa74ccf7ae2564ae38c8df2a85b057af1dbbc958eb6666"},
+    {file = "mypy-0.790-cp38-cp38-win_amd64.whl", hash = "sha256:da56dedcd7cd502ccd3c5dddc656cb36113dd793ad466e894574125945653cea"},
+    {file = "mypy-0.790-py3-none-any.whl", hash = "sha256:2842d4fbd1b12ab422346376aad03ff5d0805b706102e475e962370f874a5122"},
+    {file = "mypy-0.790.tar.gz", hash = "sha256:2b21ba45ad9ef2e2eb88ce4aeadd0112d0f5026418324176fd494a6824b74975"},
 ]
 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-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"},
-    {file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"},
+    {file = "packaging-20.8-py2.py3-none-any.whl", hash = "sha256:24e0da08660a87484d1602c30bb4902d74816b6985b93de36926f5bc95741858"},
+    {file = "packaging-20.8.tar.gz", hash = "sha256:78598185a7008a470d64526a8059de9aaa449238f280fc9eb6b13ba6c4109093"},
 ]
 pathspec = [
-    {file = "pathspec-0.8.0-py2.py3-none-any.whl", hash = "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0"},
-    {file = "pathspec-0.8.0.tar.gz", hash = "sha256:da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061"},
+    {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"},
+    {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"},
 ]
 pbr = [
-    {file = "pbr-5.5.0-py2.py3-none-any.whl", hash = "sha256:5adc0f9fc64319d8df5ca1e4e06eea674c26b80e6f00c530b18ce6a6592ead15"},
-    {file = "pbr-5.5.0.tar.gz", hash = "sha256:14bfd98f51c78a3dd22a1ef45cf194ad79eee4a19e8e1a0d5c7f8e81ffe182ea"},
+    {file = "pbr-5.5.1-py2.py3-none-any.whl", hash = "sha256:b236cde0ac9a6aedd5e3c34517b423cd4fd97ef723849da6b0d2231142d89c00"},
+    {file = "pbr-5.5.1.tar.gz", hash = "sha256:5fad80b613c402d5b7df7bd84812548b2a61e9977387a80a5fc5c396492b13c9"},
 ]
 persisting-theory = [
     {file = "persisting-theory-0.2.1.tar.gz", hash = "sha256:00ff7dcc8f481ff75c770ca5797d968e8725b6df1f77fe0cf7d20fa1e5790c0a"},
 ]
 pg8000 = [
-    {file = "pg8000-1.16.5-py3-none-any.whl", hash = "sha256:3d646b11227d94a3130a765a981dc6323bc959a3cd6ed54421d174b2ef256087"},
-    {file = "pg8000-1.16.5.tar.gz", hash = "sha256:8af70cdfcc1fadafa32468a6af563e1c0b5271c4dcc99a4490030a128cb295a3"},
+    {file = "pg8000-1.16.6-py3-none-any.whl", hash = "sha256:66fa16a402f38f8ba664206b4ba4040f24ea9641c4205b2b96a1ff3a613de3be"},
+    {file = "pg8000-1.16.6.tar.gz", hash = "sha256:8fc1e6a62ccb7c9830f1e7e9288e2d20eaf373cc8875b5c55b7d5d9b7717be91"},
 ]
 phonenumbers = [
-    {file = "phonenumbers-8.12.9-py2.py3-none-any.whl", hash = "sha256:b8644c1dccd45d4c0f54c5b10effcc8c3f733e6a3c2caf40c9175fabc5010ffe"},
-    {file = "phonenumbers-8.12.9.tar.gz", hash = "sha256:f887eceb3d9db17ec479a85245bd0ebe74c5f43489217784215ffb231f8c9e88"},
+    {file = "phonenumbers-8.12.15-py2.py3-none-any.whl", hash = "sha256:13d499f7114c4b37c54ee844b188d5e7441951a7da41de5fc1a25ff8fdceef80"},
+    {file = "phonenumbers-8.12.15.tar.gz", hash = "sha256:b734bfcf33e87ddae72196a40b3d1af35abd0beb263816ae18e1bff612926406"},
 ]
 pillow = [
-    {file = "Pillow-7.2.0-cp35-cp35m-macosx_10_10_intel.whl", hash = "sha256:1ca594126d3c4def54babee699c055a913efb01e106c309fa6b04405d474d5ae"},
-    {file = "Pillow-7.2.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c92302a33138409e8f1ad16731568c55c9053eee71bb05b6b744067e1b62380f"},
-    {file = "Pillow-7.2.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:8dad18b69f710bf3a001d2bf3afab7c432785d94fcf819c16b5207b1cfd17d38"},
-    {file = "Pillow-7.2.0-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:431b15cffbf949e89df2f7b48528be18b78bfa5177cb3036284a5508159492b5"},
-    {file = "Pillow-7.2.0-cp35-cp35m-win32.whl", hash = "sha256:09d7f9e64289cb40c2c8d7ad674b2ed6105f55dc3b09aa8e4918e20a0311e7ad"},
-    {file = "Pillow-7.2.0-cp35-cp35m-win_amd64.whl", hash = "sha256:0295442429645fa16d05bd567ef5cff178482439c9aad0411d3f0ce9b88b3a6f"},
-    {file = "Pillow-7.2.0-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:ec29604081f10f16a7aea809ad42e27764188fc258b02259a03a8ff7ded3808d"},
-    {file = "Pillow-7.2.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:612cfda94e9c8346f239bf1a4b082fdd5c8143cf82d685ba2dba76e7adeeb233"},
-    {file = "Pillow-7.2.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0a80dd307a5d8440b0a08bd7b81617e04d870e40a3e46a32d9c246e54705e86f"},
-    {file = "Pillow-7.2.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:06aba4169e78c439d528fdeb34762c3b61a70813527a2c57f0540541e9f433a8"},
-    {file = "Pillow-7.2.0-cp36-cp36m-win32.whl", hash = "sha256:f7e30c27477dffc3e85c2463b3e649f751789e0f6c8456099eea7ddd53be4a8a"},
-    {file = "Pillow-7.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:ffe538682dc19cc542ae7c3e504fdf54ca7f86fb8a135e59dd6bc8627eae6cce"},
-    {file = "Pillow-7.2.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:94cf49723928eb6070a892cb39d6c156f7b5a2db4e8971cb958f7b6b104fb4c4"},
-    {file = "Pillow-7.2.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6edb5446f44d901e8683ffb25ebdfc26988ee813da3bf91e12252b57ac163727"},
-    {file = "Pillow-7.2.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:52125833b070791fcb5710fabc640fc1df07d087fc0c0f02d3661f76c23c5b8b"},
-    {file = "Pillow-7.2.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:9ad7f865eebde135d526bb3163d0b23ffff365cf87e767c649550964ad72785d"},
-    {file = "Pillow-7.2.0-cp37-cp37m-win32.whl", hash = "sha256:c79f9c5fb846285f943aafeafda3358992d64f0ef58566e23484132ecd8d7d63"},
-    {file = "Pillow-7.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d350f0f2c2421e65fbc62690f26b59b0bcda1b614beb318c81e38647e0f673a1"},
-    {file = "Pillow-7.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:6d7741e65835716ceea0fd13a7d0192961212fd59e741a46bbed7a473c634ed6"},
-    {file = "Pillow-7.2.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:edf31f1150778abd4322444c393ab9c7bd2af271dd4dafb4208fb613b1f3cdc9"},
-    {file = "Pillow-7.2.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:d08b23fdb388c0715990cbc06866db554e1822c4bdcf6d4166cf30ac82df8c41"},
-    {file = "Pillow-7.2.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:5e51ee2b8114def244384eda1c82b10e307ad9778dac5c83fb0943775a653cd8"},
-    {file = "Pillow-7.2.0-cp38-cp38-win32.whl", hash = "sha256:725aa6cfc66ce2857d585f06e9519a1cc0ef6d13f186ff3447ab6dff0a09bc7f"},
-    {file = "Pillow-7.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:a060cf8aa332052df2158e5a119303965be92c3da6f2d93b6878f0ebca80b2f6"},
-    {file = "Pillow-7.2.0-pp36-pypy36_pp73-win32.whl", hash = "sha256:25930fadde8019f374400f7986e8404c8b781ce519da27792cbe46eabec00c4d"},
-    {file = "Pillow-7.2.0.tar.gz", hash = "sha256:97f9e7953a77d5a70f49b9a48da7776dc51e9b738151b22dacf101641594a626"},
+    {file = "Pillow-8.0.1-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:b63d4ff734263ae4ce6593798bcfee6dbfb00523c82753a3a03cbc05555a9cc3"},
+    {file = "Pillow-8.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5f9403af9c790cc18411ea398a6950ee2def2a830ad0cfe6dc9122e6d528b302"},
+    {file = "Pillow-8.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:6b4a8fd632b4ebee28282a9fef4c341835a1aa8671e2770b6f89adc8e8c2703c"},
+    {file = "Pillow-8.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:cc3ea6b23954da84dbee8025c616040d9aa5eaf34ea6895a0a762ee9d3e12e11"},
+    {file = "Pillow-8.0.1-cp36-cp36m-win32.whl", hash = "sha256:d8a96747df78cda35980905bf26e72960cba6d355ace4780d4bdde3b217cdf1e"},
+    {file = "Pillow-8.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:7ba0ba61252ab23052e642abdb17fd08fdcfdbbf3b74c969a30c58ac1ade7cd3"},
+    {file = "Pillow-8.0.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:795e91a60f291e75de2e20e6bdd67770f793c8605b553cb6e4387ce0cb302e09"},
+    {file = "Pillow-8.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0a2e8d03787ec7ad71dc18aec9367c946ef8ef50e1e78c71f743bc3a770f9fae"},
+    {file = "Pillow-8.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:006de60d7580d81f4a1a7e9f0173dc90a932e3905cc4d47ea909bc946302311a"},
+    {file = "Pillow-8.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:bd7bf289e05470b1bc74889d1466d9ad4a56d201f24397557b6f65c24a6844b8"},
+    {file = "Pillow-8.0.1-cp37-cp37m-win32.whl", hash = "sha256:95edb1ed513e68bddc2aee3de66ceaf743590bf16c023fb9977adc4be15bd3f0"},
+    {file = "Pillow-8.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:e38d58d9138ef972fceb7aeec4be02e3f01d383723965bfcef14d174c8ccd039"},
+    {file = "Pillow-8.0.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:d3d07c86d4efa1facdf32aa878bd508c0dc4f87c48125cc16b937baa4e5b5e11"},
+    {file = "Pillow-8.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:fbd922f702582cb0d71ef94442bfca57624352622d75e3be7a1e7e9360b07e72"},
+    {file = "Pillow-8.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:92c882b70a40c79de9f5294dc99390671e07fc0b0113d472cbea3fde15db1792"},
+    {file = "Pillow-8.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:7c9401e68730d6c4245b8e361d3d13e1035cbc94db86b49dc7da8bec235d0015"},
+    {file = "Pillow-8.0.1-cp38-cp38-win32.whl", hash = "sha256:6c1aca8231625115104a06e4389fcd9ec88f0c9befbabd80dc206c35561be271"},
+    {file = "Pillow-8.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:cc9ec588c6ef3a1325fa032ec14d97b7309db493782ea8c304666fb10c3bd9a7"},
+    {file = "Pillow-8.0.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:eb472586374dc66b31e36e14720747595c2b265ae962987261f044e5cce644b5"},
+    {file = "Pillow-8.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:0eeeae397e5a79dc088d8297a4c2c6f901f8fb30db47795113a4a605d0f1e5ce"},
+    {file = "Pillow-8.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:81f812d8f5e8a09b246515fac141e9d10113229bc33ea073fec11403b016bcf3"},
+    {file = "Pillow-8.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:895d54c0ddc78a478c80f9c438579ac15f3e27bf442c2a9aa74d41d0e4d12544"},
+    {file = "Pillow-8.0.1-cp39-cp39-win32.whl", hash = "sha256:2fb113757a369a6cdb189f8df3226e995acfed0a8919a72416626af1a0a71140"},
+    {file = "Pillow-8.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:59e903ca800c8cfd1ebe482349ec7c35687b95e98cefae213e271c8c7fffa021"},
+    {file = "Pillow-8.0.1-pp36-pypy36_pp73-macosx_10_10_x86_64.whl", hash = "sha256:5abd653a23c35d980b332bc0431d39663b1709d64142e3652890df4c9b6970f6"},
+    {file = "Pillow-8.0.1-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:4b0ef2470c4979e345e4e0cc1bbac65fda11d0d7b789dbac035e4c6ce3f98adb"},
+    {file = "Pillow-8.0.1-pp37-pypy37_pp73-win32.whl", hash = "sha256:8de332053707c80963b589b22f8e0229f1be1f3ca862a932c1bcd48dafb18dd8"},
+    {file = "Pillow-8.0.1.tar.gz", hash = "sha256:11c5c6e9b02c9dac08af04f093eb5a2f84857df70a7d4a6a6ad461aca803fb9e"},
 ]
 pluggy = [
     {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
     {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
 ]
+prometheus-client = [
+    {file = "prometheus_client-0.9.0-py2.py3-none-any.whl", hash = "sha256:b08c34c328e1bf5961f0b4352668e6c8f145b4a087e09b7296ef62cbe4693d35"},
+    {file = "prometheus_client-0.9.0.tar.gz", hash = "sha256:9da7b32f02439d8c04f7777021c304ed51d9ec180604700c1ba72a4d44dceb03"},
+]
+prompt-toolkit = [
+    {file = "prompt_toolkit-3.0.8-py3-none-any.whl", hash = "sha256:7debb9a521e0b1ee7d2fe96ee4bd60ef03c6492784de0547337ca4433e46aa63"},
+    {file = "prompt_toolkit-3.0.8.tar.gz", hash = "sha256:25c95d2ac813909f813c93fde734b6e44406d1477a9faef7c915ff37d39c0a8c"},
+]
 psutil = [
-    {file = "psutil-5.7.2-cp27-none-win32.whl", hash = "sha256:f2018461733b23f308c298653c8903d32aaad7873d25e1d228765e91ae42c3f2"},
-    {file = "psutil-5.7.2-cp27-none-win_amd64.whl", hash = "sha256:66c18ca7680a31bf16ee22b1d21b6397869dda8059dbdb57d9f27efa6615f195"},
-    {file = "psutil-5.7.2-cp35-cp35m-win32.whl", hash = "sha256:5e9d0f26d4194479a13d5f4b3798260c20cecf9ac9a461e718eb59ea520a360c"},
-    {file = "psutil-5.7.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4080869ed93cce662905b029a1770fe89c98787e543fa7347f075ade761b19d6"},
-    {file = "psutil-5.7.2-cp36-cp36m-win32.whl", hash = "sha256:d8a82162f23c53b8525cf5f14a355f5d1eea86fa8edde27287dd3a98399e4fdf"},
-    {file = "psutil-5.7.2-cp36-cp36m-win_amd64.whl", hash = "sha256:0ee3c36428f160d2d8fce3c583a0353e848abb7de9732c50cf3356dd49ad63f8"},
-    {file = "psutil-5.7.2-cp37-cp37m-win32.whl", hash = "sha256:ff1977ba1a5f71f89166d5145c3da1cea89a0fdb044075a12c720ee9123ec818"},
-    {file = "psutil-5.7.2-cp37-cp37m-win_amd64.whl", hash = "sha256:a5b120bb3c0c71dfe27551f9da2f3209a8257a178ed6c628a819037a8df487f1"},
-    {file = "psutil-5.7.2-cp38-cp38-win32.whl", hash = "sha256:10512b46c95b02842c225f58fa00385c08fa00c68bac7da2d9a58ebe2c517498"},
-    {file = "psutil-5.7.2-cp38-cp38-win_amd64.whl", hash = "sha256:68d36986ded5dac7c2dcd42f2682af1db80d4bce3faa126a6145c1637e1b559f"},
-    {file = "psutil-5.7.2.tar.gz", hash = "sha256:90990af1c3c67195c44c9a889184f84f5b2320dce3ee3acbd054e3ba0b4a7beb"},
+    {file = "psutil-5.8.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:0066a82f7b1b37d334e68697faba68e5ad5e858279fd6351c8ca6024e8d6ba64"},
+    {file = "psutil-5.8.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:0ae6f386d8d297177fd288be6e8d1afc05966878704dad9847719650e44fc49c"},
+    {file = "psutil-5.8.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:12d844996d6c2b1d3881cfa6fa201fd635971869a9da945cf6756105af73d2df"},
+    {file = "psutil-5.8.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:02b8292609b1f7fcb34173b25e48d0da8667bc85f81d7476584d889c6e0f2131"},
+    {file = "psutil-5.8.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:6ffe81843131ee0ffa02c317186ed1e759a145267d54fdef1bc4ea5f5931ab60"},
+    {file = "psutil-5.8.0-cp27-none-win32.whl", hash = "sha256:ea313bb02e5e25224e518e4352af4bf5e062755160f77e4b1767dd5ccb65f876"},
+    {file = "psutil-5.8.0-cp27-none-win_amd64.whl", hash = "sha256:5da29e394bdedd9144c7331192e20c1f79283fb03b06e6abd3a8ae45ffecee65"},
+    {file = "psutil-5.8.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:74fb2557d1430fff18ff0d72613c5ca30c45cdbfcddd6a5773e9fc1fe9364be8"},
+    {file = "psutil-5.8.0-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:74f2d0be88db96ada78756cb3a3e1b107ce8ab79f65aa885f76d7664e56928f6"},
+    {file = "psutil-5.8.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:99de3e8739258b3c3e8669cb9757c9a861b2a25ad0955f8e53ac662d66de61ac"},
+    {file = "psutil-5.8.0-cp36-cp36m-win32.whl", hash = "sha256:36b3b6c9e2a34b7d7fbae330a85bf72c30b1c827a4366a07443fc4b6270449e2"},
+    {file = "psutil-5.8.0-cp36-cp36m-win_amd64.whl", hash = "sha256:52de075468cd394ac98c66f9ca33b2f54ae1d9bff1ef6b67a212ee8f639ec06d"},
+    {file = "psutil-5.8.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c6a5fd10ce6b6344e616cf01cc5b849fa8103fbb5ba507b6b2dee4c11e84c935"},
+    {file = "psutil-5.8.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:61f05864b42fedc0771d6d8e49c35f07efd209ade09a5afe6a5059e7bb7bf83d"},
+    {file = "psutil-5.8.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:0dd4465a039d343925cdc29023bb6960ccf4e74a65ad53e768403746a9207023"},
+    {file = "psutil-5.8.0-cp37-cp37m-win32.whl", hash = "sha256:1bff0d07e76114ec24ee32e7f7f8d0c4b0514b3fae93e3d2aaafd65d22502394"},
+    {file = "psutil-5.8.0-cp37-cp37m-win_amd64.whl", hash = "sha256:fcc01e900c1d7bee2a37e5d6e4f9194760a93597c97fee89c4ae51701de03563"},
+    {file = "psutil-5.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6223d07a1ae93f86451d0198a0c361032c4c93ebd4bf6d25e2fb3edfad9571ef"},
+    {file = "psutil-5.8.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d225cd8319aa1d3c85bf195c4e07d17d3cd68636b8fc97e6cf198f782f99af28"},
+    {file = "psutil-5.8.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:28ff7c95293ae74bf1ca1a79e8805fcde005c18a122ca983abf676ea3466362b"},
+    {file = "psutil-5.8.0-cp38-cp38-win32.whl", hash = "sha256:ce8b867423291cb65cfc6d9c4955ee9bfc1e21fe03bb50e177f2b957f1c2469d"},
+    {file = "psutil-5.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:90f31c34d25b1b3ed6c40cdd34ff122b1887a825297c017e4cbd6796dd8b672d"},
+    {file = "psutil-5.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6323d5d845c2785efb20aded4726636546b26d3b577aded22492908f7c1bdda7"},
+    {file = "psutil-5.8.0-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:245b5509968ac0bd179287d91210cd3f37add77dad385ef238b275bad35fa1c4"},
+    {file = "psutil-5.8.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:90d4091c2d30ddd0a03e0b97e6a33a48628469b99585e2ad6bf21f17423b112b"},
+    {file = "psutil-5.8.0-cp39-cp39-win32.whl", hash = "sha256:ea372bcc129394485824ae3e3ddabe67dc0b118d262c568b4d2602a7070afdb0"},
+    {file = "psutil-5.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:f4634b033faf0d968bb9220dd1c793b897ab7f1189956e1aa9eae752527127d3"},
+    {file = "psutil-5.8.0.tar.gz", hash = "sha256:0c9ccb99ab76025f2f0bbecf341d4656e9c1351db8cc8a03ccd62e318ab4b5c6"},
 ]
 psycopg2 = [
     {file = "psycopg2-2.8.6-cp27-cp27m-win32.whl", hash = "sha256:068115e13c70dc5982dfc00c5d70437fe37c014c808acce119b5448361c03725"},
@@ -2738,8 +2803,8 @@ psycopg2 = [
     {file = "psycopg2-2.8.6.tar.gz", hash = "sha256:fb23f6c71107c37fd667cb4ea363ddeb936b348bbd6449278eb92c189699f543"},
 ]
 py = [
-    {file = "py-1.9.0-py2.py3-none-any.whl", hash = "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2"},
-    {file = "py-1.9.0.tar.gz", hash = "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"},
+    {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"},
+    {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"},
 ]
 pyasn1 = [
     {file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"},
@@ -2776,36 +2841,30 @@ pycodestyle = [
     {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"},
 ]
 pycryptodome = [
-    {file = "pycryptodome-3.9.8-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:50348edd283afdccddc0938cdc674484533912ba8a99a27c7bfebb75030aa856"},
-    {file = "pycryptodome-3.9.8-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:80d57177a0b7c14d4594c62bbb47fe2f6309ad3b0a34348a291d570925c97a82"},
-    {file = "pycryptodome-3.9.8-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:fbe65d5cfe04ff2f7684160d50f5118bdefb01e3af4718eeb618bfed40f19d94"},
-    {file = "pycryptodome-3.9.8-cp27-cp27m-win32.whl", hash = "sha256:bcd5b8416e73e4b0d48afba3704d8c826414764dafaed7a1a93c442188d90ccc"},
-    {file = "pycryptodome-3.9.8-cp27-cp27m-win_amd64.whl", hash = "sha256:360955eece2cd0fa694a708d10303c6abd7b39614fa2547b6bd245da76198beb"},
-    {file = "pycryptodome-3.9.8-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:1e655746f539421d923fd48df8f6f40b3443d80b75532501c0085b64afed9df5"},
-    {file = "pycryptodome-3.9.8-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:709b9f144d23e290b9863121d1ace14a72e01f66ea9c903fbdc690520dfdfcf0"},
-    {file = "pycryptodome-3.9.8-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:6276478ada411aca97c0d5104916354b3d740d368407912722bd4d11aa9ee4c2"},
-    {file = "pycryptodome-3.9.8-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:54bdedd28476dea8a3cd86cb67c0df1f0e3d71cae8022354b0f879c41a3d27b2"},
-    {file = "pycryptodome-3.9.8-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f521178e5a991ffd04182ed08f552daca1affcb826aeda0e1945cd989a9d4345"},
-    {file = "pycryptodome-3.9.8-cp35-cp35m-win32.whl", hash = "sha256:a207231a52426de3ff20f5608f0687261a3329d97a036c51f7d4c606a6f30c23"},
-    {file = "pycryptodome-3.9.8-cp35-cp35m-win_amd64.whl", hash = "sha256:2b998dc45ef5f4e5cf5248a6edfcd8d8e9fb5e35df8e4259b13a1b10eda7b16b"},
-    {file = "pycryptodome-3.9.8-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:03d5cca8618620f45fd40f827423f82b86b3a202c8d44108601b0f5f56b04299"},
-    {file = "pycryptodome-3.9.8-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:f78a68c2c820e4731e510a2df3eef0322f24fde1781ced970bf497b6c7d92982"},
-    {file = "pycryptodome-3.9.8-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:132a56abba24e2e06a479d8e5db7a48271a73a215f605017bbd476d31f8e71c1"},
-    {file = "pycryptodome-3.9.8-cp36-cp36m-win32.whl", hash = "sha256:67dcad1b8b201308586a8ca2ffe89df1e4f731d5a4cdd0610cc4ea790351c739"},
-    {file = "pycryptodome-3.9.8-cp36-cp36m-win_amd64.whl", hash = "sha256:b56638d58a3a4be13229c6a815cd448f9e3ce40c00880a5398471b42ee86f50e"},
-    {file = "pycryptodome-3.9.8-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:bec2bcdf7c9ce7f04d718e51887f3b05dc5c1cfaf5d2c2e9065ecddd1b2f6c9a"},
-    {file = "pycryptodome-3.9.8-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:abc2e126c9490e58a36a0f83516479e781d83adfb134576a5cbe5c6af2a3e93c"},
-    {file = "pycryptodome-3.9.8-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ef39c98d9b8c0736d91937d193653e47c3b19ddf4fc3bccdc5e09aaa4b0c5d21"},
-    {file = "pycryptodome-3.9.8-cp37-cp37m-win32.whl", hash = "sha256:4350a42028240c344ee855f032c7d4ad6ff4f813bfbe7121547b7dc579ecc876"},
-    {file = "pycryptodome-3.9.8-cp37-cp37m-win_amd64.whl", hash = "sha256:c8bf40cf6e281a4378e25846924327e728a887e8bf0ee83b2604a0f4b61692e8"},
-    {file = "pycryptodome-3.9.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d8074c8448cfd0705dfa71ca333277fce9786d0b9cac75d120545de6253f996a"},
-    {file = "pycryptodome-3.9.8-cp38-cp38-manylinux1_i686.whl", hash = "sha256:8063a712fba642f78d3c506b0896846601b6de7f5c3d534e388ad0cc07f5a149"},
-    {file = "pycryptodome-3.9.8-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:dd302b6ae3965afeb5ef1b0d92486f986c0e65183cd7835973f0b593800590e6"},
-    {file = "pycryptodome-3.9.8-cp38-cp38-win32.whl", hash = "sha256:02e51e1d5828d58f154896ddfd003e2e7584869c275e5acbe290443575370fba"},
-    {file = "pycryptodome-3.9.8-cp38-cp38-win_amd64.whl", hash = "sha256:55eb61aca2c883db770999f50d091ff7c14016f2769ad7bca3d9b75d1d7c1b68"},
-    {file = "pycryptodome-3.9.8-cp39-cp39-manylinux1_i686.whl", hash = "sha256:39ef9fb52d6ec7728fce1f1693cb99d60ce302aeebd59bcedea70ca3203fda60"},
-    {file = "pycryptodome-3.9.8-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:de6e1cd75677423ff64712c337521e62e3a7a4fc84caabbd93207752e831a85a"},
-    {file = "pycryptodome-3.9.8.tar.gz", hash = "sha256:0e24171cf01021bc5dc17d6a9d4f33a048f09d62cc3f62541e95ef104588bda4"},
+    {file = "pycryptodome-3.9.9-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:5598dc6c9dbfe882904e54584322893eff185b98960bbe2cdaaa20e8a437b6e5"},
+    {file = "pycryptodome-3.9.9-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:1cfdb92dca388e27e732caa72a1cc624520fe93752a665c3b6cd8f1a91b34916"},
+    {file = "pycryptodome-3.9.9-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5f19e6ef750f677d924d9c7141f54bade3cd56695bbfd8a9ef15d0378557dfe4"},
+    {file = "pycryptodome-3.9.9-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:19cb674df6c74a14b8b408aa30ba8a89bd1c01e23505100fb45f930fbf0ed0d9"},
+    {file = "pycryptodome-3.9.9-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:28f75e58d02019a7edc7d4135203d2501dfc47256d175c72c9798f9a129a49a7"},
+    {file = "pycryptodome-3.9.9-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:6d3baaf82681cfb1a842f1c8f77beac791ceedd99af911e4f5fabec32bae2259"},
+    {file = "pycryptodome-3.9.9-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:946399d15eccebafc8ce0257fc4caffe383c75e6b0633509bd011e357368306c"},
+    {file = "pycryptodome-3.9.9-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:eb01f9997e4d6a8ec8a1ad1f676ba5a362781ff64e8189fe2985258ba9cb9706"},
+    {file = "pycryptodome-3.9.9-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:411745c6dce4eff918906eebcde78771d44795d747e194462abb120d2e537cd9"},
+    {file = "pycryptodome-3.9.9-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:8f9f84059039b672a5a705b3c5aa21747867bacc30a72e28bf0d147cc8ef85ed"},
+    {file = "pycryptodome-3.9.9-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:7798e73225a699651888489fbb1dbc565e03a509942a8ce6194bbe6fb582a41f"},
+    {file = "pycryptodome-3.9.9-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:46e96aeb8a9ca8b1edf9b1fd0af4bf6afcf3f1ca7fa35529f5d60b98f3e4e959"},
+    {file = "pycryptodome-3.9.9-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:843e5f10ecdf9d307032b8b91afe9da1d6ed5bb89d0bbec5c8dcb4ba44008e11"},
+    {file = "pycryptodome-3.9.9-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:4ed27951b0a17afd287299e2206a339b5b6d12de9321e1a1575261ef9c4a851b"},
+    {file = "pycryptodome-3.9.9-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:9000877383e2189dafd1b2fc68c6c726eca9a3cfb6d68148fbb72ccf651959b6"},
+    {file = "pycryptodome-3.9.9-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:faa682c404c218e8788c3126c9a4b8fbcc54dc245b5b6e8ea5b46f3b63bd0c84"},
+    {file = "pycryptodome-3.9.9-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:62c488a21c253dadc9f731a32f0ac61e4e436d81a1ea6f7d1d9146ed4d20d6bd"},
+    {file = "pycryptodome-3.9.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:27397aee992af69d07502126561d851ba3845aa808f0e55c71ad0efa264dd7d4"},
+    {file = "pycryptodome-3.9.9-cp38-cp38-manylinux1_i686.whl", hash = "sha256:d7ec2bd8f57c559dd24e71891c51c25266a8deb66fc5f02cc97c7fb593d1780a"},
+    {file = "pycryptodome-3.9.9-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:e15bde67ccb7d4417f627dd16ffe2f5a4c2941ce5278444e884cb26d73ecbc61"},
+    {file = "pycryptodome-3.9.9-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:5c3c4865730dfb0263f822b966d6d58429d8b1e560d1ddae37685fd9e7c63161"},
+    {file = "pycryptodome-3.9.9-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2a68df525b387201a43b27b879ce8c08948a430e883a756d6c9e3acdaa7d7bd8"},
+    {file = "pycryptodome-3.9.9-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:a4599c0ca0fc027c780c1c45ed996d5bef03e571470b7b1c7171ec1e1a90914c"},
+    {file = "pycryptodome-3.9.9-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b4e6b269a8ddaede774e5c3adbef6bf452ee144e6db8a716d23694953348cd86"},
 ]
 pydocstyle = [
     {file = "pydocstyle-5.1.1-py3-none-any.whl", hash = "sha256:aca749e190a01726a4fb472dd4ef23b5c9da7b9205c0a7857c06533de13fd678"},
@@ -2816,8 +2875,8 @@ pyflakes = [
     {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"},
 ]
 pygments = [
-    {file = "Pygments-2.6.1-py3-none-any.whl", hash = "sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324"},
-    {file = "Pygments-2.6.1.tar.gz", hash = "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44"},
+    {file = "Pygments-2.7.3-py3-none-any.whl", hash = "sha256:f275b6c0909e5dafd2d6269a656aa90fa58ebf4a74f8fcf9053195d226b24a08"},
+    {file = "Pygments-2.7.3.tar.gz", hash = "sha256:ccf3acacf3782cbed4a989426012f1c535c9a90d3a7fc3f16d231b9372d2b716"},
 ]
 pyjwt = [
     {file = "PyJWT-1.7.1-py2.py3-none-any.whl", hash = "sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e"},
@@ -2828,16 +2887,16 @@ pyparsing = [
     {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
 ]
 pytest = [
-    {file = "pytest-6.0.1-py3-none-any.whl", hash = "sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad"},
-    {file = "pytest-6.0.1.tar.gz", hash = "sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4"},
+    {file = "pytest-6.2.1-py3-none-any.whl", hash = "sha256:1969f797a1a0dbd8ccf0fecc80262312729afea9c17f1d70ebf85c5e76c6f7c8"},
+    {file = "pytest-6.2.1.tar.gz", hash = "sha256:66e419b1899bc27346cb2c993e12c5e5e8daba9073c1fbce33b9807abc95c306"},
 ]
 pytest-cov = [
     {file = "pytest-cov-2.10.1.tar.gz", hash = "sha256:47bd0ce14056fdd79f93e1713f88fad7bdcc583dcd7783da86ef2f085a0bb88e"},
     {file = "pytest_cov-2.10.1-py2.py3-none-any.whl", hash = "sha256:45ec2d5182f89a81fc3eb29e3d1ed3113b9e9a873bcddb2a71faaab066110191"},
 ]
 pytest-django = [
-    {file = "pytest-django-3.9.0.tar.gz", hash = "sha256:664e5f42242e5e182519388f01b9f25d824a9feb7cd17d8f863c8d776f38baf9"},
-    {file = "pytest_django-3.9.0-py2.py3-none-any.whl", hash = "sha256:64f99d565dd9497af412fcab2989fe40982c1282d4118ff422b407f3f7275ca5"},
+    {file = "pytest-django-3.10.0.tar.gz", hash = "sha256:4de6dbd077ed8606616958f77655fed0d5e3ee45159475671c7fa67596c6dba6"},
+    {file = "pytest_django-3.10.0-py2.py3-none-any.whl", hash = "sha256:c33e3d3da14d8409b125d825d4e74da17bb252191bf6fc3da6856e27a8b73ea4"},
 ]
 pytest-django-testing-postgresql = [
     {file = "pytest-django-testing-postgresql-0.1.post0.tar.gz", hash = "sha256:78b0c58930084cb4393407b2e5a2a3b8734c627b841ecef7d62d39bbfb8e8a45"},
@@ -2846,10 +2905,6 @@ pytest-django-testing-postgresql = [
 pytest-sugar = [
     {file = "pytest-sugar-0.9.4.tar.gz", hash = "sha256:b1b2186b0a72aada6859bea2a5764145e3aaa2c1cfbb23c3a19b5f7b697563d3"},
 ]
-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-crontab = [
     {file = "python-crontab-2.5.1.tar.gz", hash = "sha256:4bbe7e720753a132ca4ca9d4094915f40e9d9dc8a807a4564007651018ce8c31"},
 ]
@@ -2857,10 +2912,6 @@ 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.14.0.tar.gz", hash = "sha256:8c10c99a1b25d9a68058a1ad6f90381a62ba68230ca93966882a4dbc3bc9c33d"},
-    {file = "python_dotenv-0.14.0-py2.py3-none-any.whl", hash = "sha256:c10863aee750ad720f4f43436565e4c1698798d763b63234fb5021b6c616e423"},
-]
 python-ldap = [
     {file = "python-ldap-3.3.1.tar.gz", hash = "sha256:4711cacf013e298754abd70058ccc995758177fb425f1c2d30e71adfc1d00aa5"},
 ]
@@ -2869,8 +2920,8 @@ python-memcached = [
     {file = "python_memcached-1.59-py2.py3-none-any.whl", hash = "sha256:4dac64916871bd3550263323fc2ce18e1e439080a2d5670c594cf3118d99b594"},
 ]
 pytz = [
-    {file = "pytz-2020.1-py2.py3-none-any.whl", hash = "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed"},
-    {file = "pytz-2020.1.tar.gz", hash = "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"},
+    {file = "pytz-2020.4-py2.py3-none-any.whl", hash = "sha256:5c55e189b682d420be27c6995ba6edce0c0a77dd67bfbe2ae6607134d5851ffd"},
+    {file = "pytz-2020.4.tar.gz", hash = "sha256:3e6b7dd2d1e0a59084bcee14a17af60c5c562cdc16d828e8eba2e683d3a7e268"},
 ]
 pyyaml = [
     {file = "PyYAML-5.3.1-cp27-cp27m-win32.whl", hash = "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f"},
@@ -2894,34 +2945,82 @@ redis = [
     {file = "redis-3.5.3.tar.gz", hash = "sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2"},
 ]
 regex = [
-    {file = "regex-2020.7.14-cp27-cp27m-win32.whl", hash = "sha256:e46d13f38cfcbb79bfdb2964b0fe12561fe633caf964a77a5f8d4e45fe5d2ef7"},
-    {file = "regex-2020.7.14-cp27-cp27m-win_amd64.whl", hash = "sha256:6961548bba529cac7c07af2fd4d527c5b91bb8fe18995fed6044ac22b3d14644"},
-    {file = "regex-2020.7.14-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:c50a724d136ec10d920661f1442e4a8b010a4fe5aebd65e0c2241ea41dbe93dc"},
-    {file = "regex-2020.7.14-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8a51f2c6d1f884e98846a0a9021ff6861bdb98457879f412fdc2b42d14494067"},
-    {file = "regex-2020.7.14-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:9c568495e35599625f7b999774e29e8d6b01a6fb684d77dee1f56d41b11b40cd"},
-    {file = "regex-2020.7.14-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:51178c738d559a2d1071ce0b0f56e57eb315bcf8f7d4cf127674b533e3101f88"},
-    {file = "regex-2020.7.14-cp36-cp36m-win32.whl", hash = "sha256:9eddaafb3c48e0900690c1727fba226c4804b8e6127ea409689c3bb492d06de4"},
-    {file = "regex-2020.7.14-cp36-cp36m-win_amd64.whl", hash = "sha256:14a53646369157baa0499513f96091eb70382eb50b2c82393d17d7ec81b7b85f"},
-    {file = "regex-2020.7.14-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:1269fef3167bb52631ad4fa7dd27bf635d5a0790b8e6222065d42e91bede4162"},
-    {file = "regex-2020.7.14-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d0a5095d52b90ff38592bbdc2644f17c6d495762edf47d876049cfd2968fbccf"},
-    {file = "regex-2020.7.14-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:4c037fd14c5f4e308b8370b447b469ca10e69427966527edcab07f52d88388f7"},
-    {file = "regex-2020.7.14-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bc3d98f621898b4a9bc7fecc00513eec8f40b5b83913d74ccb445f037d58cd89"},
-    {file = "regex-2020.7.14-cp37-cp37m-win32.whl", hash = "sha256:46bac5ca10fb748d6c55843a931855e2727a7a22584f302dd9bb1506e69f83f6"},
-    {file = "regex-2020.7.14-cp37-cp37m-win_amd64.whl", hash = "sha256:0dc64ee3f33cd7899f79a8d788abfbec168410be356ed9bd30bbd3f0a23a7204"},
-    {file = "regex-2020.7.14-cp38-cp38-manylinux1_i686.whl", hash = "sha256:5ea81ea3dbd6767873c611687141ec7b06ed8bab43f68fad5b7be184a920dc99"},
-    {file = "regex-2020.7.14-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:bbb332d45b32df41200380fff14712cb6093b61bd142272a10b16778c418e98e"},
-    {file = "regex-2020.7.14-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:c11d6033115dc4887c456565303f540c44197f4fc1a2bfb192224a301534888e"},
-    {file = "regex-2020.7.14-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:75aaa27aa521a182824d89e5ab0a1d16ca207318a6b65042b046053cfc8ed07a"},
-    {file = "regex-2020.7.14-cp38-cp38-win32.whl", hash = "sha256:d6cff2276e502b86a25fd10c2a96973fdb45c7a977dca2138d661417f3728341"},
-    {file = "regex-2020.7.14-cp38-cp38-win_amd64.whl", hash = "sha256:7a2dd66d2d4df34fa82c9dc85657c5e019b87932019947faece7983f2089a840"},
-    {file = "regex-2020.7.14.tar.gz", hash = "sha256:3a3af27a8d23143c49a3420efe5b3f8cf1a48c6fc8bc6856b03f638abc1833bb"},
+    {file = "regex-2020.11.13-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8b882a78c320478b12ff024e81dc7d43c1462aa4a3341c754ee65d857a521f85"},
+    {file = "regex-2020.11.13-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a63f1a07932c9686d2d416fb295ec2c01ab246e89b4d58e5fa468089cab44b70"},
+    {file = "regex-2020.11.13-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:6e4b08c6f8daca7d8f07c8d24e4331ae7953333dbd09c648ed6ebd24db5a10ee"},
+    {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bba349276b126947b014e50ab3316c027cac1495992f10e5682dc677b3dfa0c5"},
+    {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:56e01daca75eae420bce184edd8bb341c8eebb19dd3bce7266332258f9fb9dd7"},
+    {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:6a8ce43923c518c24a2579fda49f093f1397dad5d18346211e46f134fc624e31"},
+    {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:1ab79fcb02b930de09c76d024d279686ec5d532eb814fd0ed1e0051eb8bd2daa"},
+    {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:9801c4c1d9ae6a70aeb2128e5b4b68c45d4f0af0d1535500884d644fa9b768c6"},
+    {file = "regex-2020.11.13-cp36-cp36m-win32.whl", hash = "sha256:49cae022fa13f09be91b2c880e58e14b6da5d10639ed45ca69b85faf039f7a4e"},
+    {file = "regex-2020.11.13-cp36-cp36m-win_amd64.whl", hash = "sha256:749078d1eb89484db5f34b4012092ad14b327944ee7f1c4f74d6279a6e4d1884"},
+    {file = "regex-2020.11.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b2f4007bff007c96a173e24dcda236e5e83bde4358a557f9ccf5e014439eae4b"},
+    {file = "regex-2020.11.13-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:38c8fd190db64f513fe4e1baa59fed086ae71fa45083b6936b52d34df8f86a88"},
+    {file = "regex-2020.11.13-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5862975b45d451b6db51c2e654990c1820523a5b07100fc6903e9c86575202a0"},
+    {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:262c6825b309e6485ec2493ffc7e62a13cf13fb2a8b6d212f72bd53ad34118f1"},
+    {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bafb01b4688833e099d79e7efd23f99172f501a15c44f21ea2118681473fdba0"},
+    {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:e32f5f3d1b1c663af7f9c4c1e72e6ffe9a78c03a31e149259f531e0fed826512"},
+    {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:3bddc701bdd1efa0d5264d2649588cbfda549b2899dc8d50417e47a82e1387ba"},
+    {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:02951b7dacb123d8ea6da44fe45ddd084aa6777d4b2454fa0da61d569c6fa538"},
+    {file = "regex-2020.11.13-cp37-cp37m-win32.whl", hash = "sha256:0d08e71e70c0237883d0bef12cad5145b84c3705e9c6a588b2a9c7080e5af2a4"},
+    {file = "regex-2020.11.13-cp37-cp37m-win_amd64.whl", hash = "sha256:1fa7ee9c2a0e30405e21031d07d7ba8617bc590d391adfc2b7f1e8b99f46f444"},
+    {file = "regex-2020.11.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:baf378ba6151f6e272824b86a774326f692bc2ef4cc5ce8d5bc76e38c813a55f"},
+    {file = "regex-2020.11.13-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e3faaf10a0d1e8e23a9b51d1900b72e1635c2d5b0e1bea1c18022486a8e2e52d"},
+    {file = "regex-2020.11.13-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2a11a3e90bd9901d70a5b31d7dd85114755a581a5da3fc996abfefa48aee78af"},
+    {file = "regex-2020.11.13-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1ebb090a426db66dd80df8ca85adc4abfcbad8a7c2e9a5ec7513ede522e0a8f"},
+    {file = "regex-2020.11.13-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:b2b1a5ddae3677d89b686e5c625fc5547c6e492bd755b520de5332773a8af06b"},
+    {file = "regex-2020.11.13-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:2c99e97d388cd0a8d30f7c514d67887d8021541b875baf09791a3baad48bb4f8"},
+    {file = "regex-2020.11.13-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:c084582d4215593f2f1d28b65d2a2f3aceff8342aa85afd7be23a9cad74a0de5"},
+    {file = "regex-2020.11.13-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:a3d748383762e56337c39ab35c6ed4deb88df5326f97a38946ddd19028ecce6b"},
+    {file = "regex-2020.11.13-cp38-cp38-win32.whl", hash = "sha256:7913bd25f4ab274ba37bc97ad0e21c31004224ccb02765ad984eef43e04acc6c"},
+    {file = "regex-2020.11.13-cp38-cp38-win_amd64.whl", hash = "sha256:6c54ce4b5d61a7129bad5c5dc279e222afd00e721bf92f9ef09e4fae28755683"},
+    {file = "regex-2020.11.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1862a9d9194fae76a7aaf0150d5f2a8ec1da89e8b55890b1786b8f88a0f619dc"},
+    {file = "regex-2020.11.13-cp39-cp39-manylinux1_i686.whl", hash = "sha256:4902e6aa086cbb224241adbc2f06235927d5cdacffb2425c73e6570e8d862364"},
+    {file = "regex-2020.11.13-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7a25fcbeae08f96a754b45bdc050e1fb94b95cab046bf56b016c25e9ab127b3e"},
+    {file = "regex-2020.11.13-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:d2d8ce12b7c12c87e41123997ebaf1a5767a5be3ec545f64675388970f415e2e"},
+    {file = "regex-2020.11.13-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:f7d29a6fc4760300f86ae329e3b6ca28ea9c20823df123a2ea8693e967b29917"},
+    {file = "regex-2020.11.13-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:717881211f46de3ab130b58ec0908267961fadc06e44f974466d1887f865bd5b"},
+    {file = "regex-2020.11.13-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3128e30d83f2e70b0bed9b2a34e92707d0877e460b402faca908c6667092ada9"},
+    {file = "regex-2020.11.13-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:8f6a2229e8ad946e36815f2a03386bb8353d4bde368fdf8ca5f0cb97264d3b5c"},
+    {file = "regex-2020.11.13-cp39-cp39-win32.whl", hash = "sha256:f8f295db00ef5f8bae530fc39af0b40486ca6068733fb860b42115052206466f"},
+    {file = "regex-2020.11.13-cp39-cp39-win_amd64.whl", hash = "sha256:a15f64ae3a027b64496a71ab1f722355e570c3fac5ba2801cafce846bf5af01d"},
+    {file = "regex-2020.11.13.tar.gz", hash = "sha256:83d6b356e116ca119db8e7c6fc2983289d87b27b3fac238cfe5dca529d884562"},
 ]
 requests = [
-    {file = "requests-2.24.0-py2.py3-none-any.whl", hash = "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"},
-    {file = "requests-2.24.0.tar.gz", hash = "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b"},
+    {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"},
+    {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"},
 ]
 restructuredtext-lint = [
-    {file = "restructuredtext_lint-1.3.1.tar.gz", hash = "sha256:470e53b64817211a42805c3a104d2216f6f5834b22fe7adb637d1de4d6501fb8"},
+    {file = "restructuredtext_lint-1.3.2.tar.gz", hash = "sha256:d3b10a1fe2ecac537e51ae6d151b223b78de9fafdd50e5eb6b08c243df173c80"},
+]
+"ruamel.yaml" = [
+    {file = "ruamel.yaml-0.16.12-py2.py3-none-any.whl", hash = "sha256:012b9470a0ea06e4e44e99e7920277edf6b46eee0232a04487ea73a7386340a5"},
+    {file = "ruamel.yaml-0.16.12.tar.gz", hash = "sha256:076cc0bc34f1966d920a49f18b52b6ad559fbe656a0748e3535cf7b3f29ebf9e"},
+]
+"ruamel.yaml.clib" = [
+    {file = "ruamel.yaml.clib-0.2.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:28116f204103cb3a108dfd37668f20abe6e3cafd0d3fd40dba126c732457b3cc"},
+    {file = "ruamel.yaml.clib-0.2.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:daf21aa33ee9b351f66deed30a3d450ab55c14242cfdfcd377798e2c0d25c9f1"},
+    {file = "ruamel.yaml.clib-0.2.2-cp27-cp27m-win32.whl", hash = "sha256:30dca9bbcbb1cc858717438218d11eafb78666759e5094dd767468c0d577a7e7"},
+    {file = "ruamel.yaml.clib-0.2.2-cp27-cp27m-win_amd64.whl", hash = "sha256:f6061a31880c1ed6b6ce341215336e2f3d0c1deccd84957b6fa8ca474b41e89f"},
+    {file = "ruamel.yaml.clib-0.2.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:73b3d43e04cc4b228fa6fa5d796409ece6fcb53a6c270eb2048109cbcbc3b9c2"},
+    {file = "ruamel.yaml.clib-0.2.2-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:53b9dd1abd70e257a6e32f934ebc482dac5edb8c93e23deb663eac724c30b026"},
+    {file = "ruamel.yaml.clib-0.2.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:839dd72545ef7ba78fd2aa1a5dd07b33696adf3e68fae7f31327161c1093001b"},
+    {file = "ruamel.yaml.clib-0.2.2-cp35-cp35m-win32.whl", hash = "sha256:b1e981fe1aff1fd11627f531524826a4dcc1f26c726235a52fcb62ded27d150f"},
+    {file = "ruamel.yaml.clib-0.2.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4e52c96ca66de04be42ea2278012a2342d89f5e82b4512fb6fb7134e377e2e62"},
+    {file = "ruamel.yaml.clib-0.2.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a873e4d4954f865dcb60bdc4914af7eaae48fb56b60ed6daa1d6251c72f5337c"},
+    {file = "ruamel.yaml.clib-0.2.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ab845f1f51f7eb750a78937be9f79baea4a42c7960f5a94dde34e69f3cce1988"},
+    {file = "ruamel.yaml.clib-0.2.2-cp36-cp36m-win32.whl", hash = "sha256:e9f7d1d8c26a6a12c23421061f9022bb62704e38211fe375c645485f38df34a2"},
+    {file = "ruamel.yaml.clib-0.2.2-cp36-cp36m-win_amd64.whl", hash = "sha256:2602e91bd5c1b874d6f93d3086f9830f3e907c543c7672cf293a97c3fabdcd91"},
+    {file = "ruamel.yaml.clib-0.2.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:44c7b0498c39f27795224438f1a6be6c5352f82cb887bc33d962c3a3acc00df6"},
+    {file = "ruamel.yaml.clib-0.2.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:8e8fd0a22c9d92af3a34f91e8a2594eeb35cba90ab643c5e0e643567dc8be43e"},
+    {file = "ruamel.yaml.clib-0.2.2-cp37-cp37m-win32.whl", hash = "sha256:464e66a04e740d754170be5e740657a3b3b6d2bcc567f0c3437879a6e6087ff6"},
+    {file = "ruamel.yaml.clib-0.2.2-cp37-cp37m-win_amd64.whl", hash = "sha256:52ae5739e4b5d6317b52f5b040b1b6639e8af68a5b8fd606a8b08658fbd0cab5"},
+    {file = "ruamel.yaml.clib-0.2.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4df5019e7783d14b79217ad9c56edf1ba7485d614ad5a385d1b3c768635c81c0"},
+    {file = "ruamel.yaml.clib-0.2.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5254af7d8bdf4d5484c089f929cb7f5bafa59b4f01d4f48adda4be41e6d29f99"},
+    {file = "ruamel.yaml.clib-0.2.2-cp38-cp38-win32.whl", hash = "sha256:74161d827407f4db9072011adcfb825b5258a5ccb3d2cd518dd6c9edea9e30f1"},
+    {file = "ruamel.yaml.clib-0.2.2-cp38-cp38-win_amd64.whl", hash = "sha256:058a1cc3df2a8aecc12f983a48bda99315cebf55a3b3a5463e37bb599b05727b"},
+    {file = "ruamel.yaml.clib-0.2.2.tar.gz", hash = "sha256:2d24bd98af676f4990c4d715bcdc2a60b19c56a3fb3a763164d2d8ca0e806ba7"},
 ]
 rules = [
     {file = "rules-2.2.tar.gz", hash = "sha256:9bae429f9d4f91a375402990da1541f9e093b0ac077221d57124d06eeeca4405"},
@@ -2950,21 +3049,17 @@ 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.6-py2.py3-none-any.whl", hash = "sha256:feb1e937fa26a69e08436aad4a9037cd7e1d4c7212909502ba30701247ff8abd"},
-    {file = "soupsieve-1.9.6.tar.gz", hash = "sha256:7985bacc98c34923a439967c1a602dc4f1e15f923b6fcf02344184f86cc7efaa"},
-]
 spdx-license-list = [
-    {file = "spdx_license_list-0.5.1-py3-none-any.whl", hash = "sha256:32f1401e0077b46ba8b3d9c648b6503ef1d49c41aab51aa13816be2dde3b4a13"},
-    {file = "spdx_license_list-0.5.1.tar.gz", hash = "sha256:64cb5de37724c64cdeccafa2ae68667ff8ccdb7b688f51c1c2be82d7ebe3a112"},
+    {file = "spdx_license_list-0.5.2-py3-none-any.whl", hash = "sha256:1b338470c7b403dbecceca563a316382c7977516128ca6c1e8f7078e3ed6e7b0"},
+    {file = "spdx_license_list-0.5.2.tar.gz", hash = "sha256:952996f72ab807972dc2278bb9b91e5294767211e51f09aad9c0e2ff5b82a31b"},
 ]
 sphinx = [
-    {file = "Sphinx-3.2.1-py3-none-any.whl", hash = "sha256:ce6fd7ff5b215af39e2fcd44d4a321f6694b4530b6f2b2109b64d120773faea0"},
-    {file = "Sphinx-3.2.1.tar.gz", hash = "sha256:321d6d9b16fa381a5306e5a0b76cd48ffbc588e6340059a729c6fdd66087e0e8"},
+    {file = "Sphinx-3.3.1-py3-none-any.whl", hash = "sha256:d4e59ad4ea55efbb3c05cde3bfc83bfc14f0c95aa95c3d75346fcce186a47960"},
+    {file = "Sphinx-3.3.1.tar.gz", hash = "sha256:1e8d592225447104d1172be415bc2972bd1357e3e12fdc76edf2261105db4300"},
 ]
 sphinx-autodoc-typehints = [
-    {file = "sphinx-autodoc-typehints-1.11.0.tar.gz", hash = "sha256:bbf0b203f1019b0f9843ee8eef0cff856dc04b341f6dbe1113e37f2ebf243e11"},
-    {file = "sphinx_autodoc_typehints-1.11.0-py3-none-any.whl", hash = "sha256:89e19370a55db4aef1be2094d8fb1fb500ca455c55b3fcc8d2600ff805227e04"},
+    {file = "sphinx-autodoc-typehints-1.11.1.tar.gz", hash = "sha256:244ba6d3e2fdb854622f643c7763d6f95b6886eba24bec28e86edf205e4ddb20"},
+    {file = "sphinx_autodoc_typehints-1.11.1-py3-none-any.whl", hash = "sha256:da049791d719f4c9813642496ee4764203e317f0697eb75446183fa2a68e3f77"},
 ]
 sphinxcontrib-applehelp = [
     {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"},
@@ -2995,19 +3090,19 @@ sphinxcontrib-serializinghtml = [
     {file = "sphinxcontrib_serializinghtml-1.1.4-py2.py3-none-any.whl", hash = "sha256:f242a81d423f59617a8e5cf16f5d4d74e28ee9a66f9e5b637a18082991db5a9a"},
 ]
 sqlparse = [
-    {file = "sqlparse-0.3.1-py2.py3-none-any.whl", hash = "sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e"},
-    {file = "sqlparse-0.3.1.tar.gz", hash = "sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548"},
+    {file = "sqlparse-0.4.1-py3-none-any.whl", hash = "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0"},
+    {file = "sqlparse-0.4.1.tar.gz", hash = "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8"},
 ]
 stevedore = [
-    {file = "stevedore-3.2.1-py3-none-any.whl", hash = "sha256:ddc09a744dc224c84ec8e8efcb70595042d21c97c76df60daee64c9ad53bc7ee"},
-    {file = "stevedore-3.2.1.tar.gz", hash = "sha256:a34086819e2c7a7f86d5635363632829dab8014e5fd7be2454c7cba84ac7514e"},
+    {file = "stevedore-3.3.0-py3-none-any.whl", hash = "sha256:50d7b78fbaf0d04cd62411188fa7eedcb03eb7f4c4b37005615ceebe582aa82a"},
+    {file = "stevedore-3.3.0.tar.gz", hash = "sha256:3a5bbd0652bf552748871eaa73a4a8dc2899786bc497a2aa1fcb4dcdb0debeee"},
 ]
 termcolor = [
     {file = "termcolor-1.1.0.tar.gz", hash = "sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b"},
 ]
 testfixtures = [
-    {file = "testfixtures-6.14.2-py2.py3-none-any.whl", hash = "sha256:816557888877f498081c1b5c572049b4a2ddffedb77401308ff4cdc1bb9147b7"},
-    {file = "testfixtures-6.14.2.tar.gz", hash = "sha256:14d9907390f5f9c7189b3d511b64f34f1072d07cc13b604a57e1bb79029376e3"},
+    {file = "testfixtures-6.17.0-py2.py3-none-any.whl", hash = "sha256:ebcc3e024d47bb58a60cdc678604151baa0c920ae2814004c89ac9066de31b2c"},
+    {file = "testfixtures-6.17.0.tar.gz", hash = "sha256:fa7c170df68ca6367eda061e9ec339ae3e6d3679c31e04033f83ef97a7d7d0ce"},
 ]
 "testing.common.database" = [
     {file = "testing.common.database-2.0.3-py2.py3-none-any.whl", hash = "sha256:e3ed492bf480a87f271f74c53b262caf5d85c8bc09989a8f534fa2283ec52492"},
@@ -3022,15 +3117,15 @@ text-unidecode = [
     {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"},
 ]
 toml = [
-    {file = "toml-0.10.1-py2.py3-none-any.whl", hash = "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"},
-    {file = "toml-0.10.1.tar.gz", hash = "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f"},
+    {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
+    {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
 ]
 tqdm = [
-    {file = "tqdm-4.48.2-py2.py3-none-any.whl", hash = "sha256:1a336d2b829be50e46b84668691e0a2719f26c97c62846298dd5ae2937e4d5cf"},
-    {file = "tqdm-4.48.2.tar.gz", hash = "sha256:564d632ea2b9cb52979f7956e093e831c28d441c11751682f84c86fc46e4fd21"},
+    {file = "tqdm-4.54.1-py2.py3-none-any.whl", hash = "sha256:d4f413aecb61c9779888c64ddf0c62910ad56dcbe857d8922bb505d4dbff0df1"},
+    {file = "tqdm-4.54.1.tar.gz", hash = "sha256:38b658a3e4ecf9b4f6f8ff75ca16221ae3378b2e175d846b6b33ea3a20852cf5"},
 ]
 twilio = [
-    {file = "twilio-6.45.1.tar.gz", hash = "sha256:97752ed9595dc23cd7217a812804c4a9e8c1b92e84529e82db718c921ed80edf"},
+    {file = "twilio-6.50.1.tar.gz", hash = "sha256:dd8371c9b4ea422d6de7526b63b587da82e8488f2b3f6b1258d2cad6e4006a65"},
 ]
 typed-ast = [
     {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"},
@@ -3061,12 +3156,16 @@ typing-extensions = [
     {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"},
 ]
 urllib3 = [
-    {file = "urllib3-1.25.10-py2.py3-none-any.whl", hash = "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"},
-    {file = "urllib3-1.25.10.tar.gz", hash = "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a"},
+    {file = "urllib3-1.26.2-py2.py3-none-any.whl", hash = "sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473"},
+    {file = "urllib3-1.26.2.tar.gz", hash = "sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08"},
 ]
 vine = [
-    {file = "vine-1.3.0-py2.py3-none-any.whl", hash = "sha256:ea4947cc56d1fd6f2095c8d543ee25dad966f78692528e68b4fada11ba3f98af"},
-    {file = "vine-1.3.0.tar.gz", hash = "sha256:133ee6d7a9016f177ddeaf191c1f58421a1dcc6ee9a42c58b34bed40e1d2cd87"},
+    {file = "vine-5.0.0-py2.py3-none-any.whl", hash = "sha256:4c9dceab6f76ed92105027c49c823800dd33cacce13bdedc5b914e3514b7fb30"},
+    {file = "vine-5.0.0.tar.gz", hash = "sha256:7d3b1624a953da82ef63462013bbd271d3eb75751489f9807598e8f340bd637e"},
+]
+wcwidth = [
+    {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
+    {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"},
 ]
 webencodings = [
     {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"},
@@ -3077,6 +3176,6 @@ yubiotp = [
     {file = "YubiOTP-1.0.0.post1.tar.gz", hash = "sha256:c13825f7b76a69afb92f19521f4dea9f5031d70f45123b505dc2e0ac03132065"},
 ]
 zipp = [
-    {file = "zipp-3.1.0-py3-none-any.whl", hash = "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b"},
-    {file = "zipp-3.1.0.tar.gz", hash = "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"},
+    {file = "zipp-3.4.0-py3-none-any.whl", hash = "sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108"},
+    {file = "zipp-3.4.0.tar.gz", hash = "sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb"},
 ]
diff --git a/pyproject.toml b/pyproject.toml
index c960fdb9d09e722918156c00c38c7acfdce873d1..cfb02aa3649f591f00fac32004c0a397e43a09ab 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,19 +1,19 @@
 [tool.poetry]
-name = "AlekSIS"
+name = "AlekSIS-Core"
 version = "2.0a3.dev0"
 packages = [
     { include = "aleksis" }
 ]
 readme = "README.rst"
-include = ["CHANGELOG.rst", "CODE_OF_CONDUCT.rst", "CONTRIBUTING.rst", "Dockerfile", "LICENCE.rst", "manage.py", "docker/*", "docker/**/*", "docker-compose.yml", "docs/*", "docs/**/*", "tox.ini"]
+include = ["CHANGELOG.rst", "LICENCE.rst", "docs/*", "docs/**/*", "aleksis/**/*.mo", "tox.ini"]
 
 description = "AlekSIS (School Information System) — Core"
 authors = ["Dominik George <dominik.george@teckids.org>", "Julian Leucker <leuckeju@katharineum.de>", "mirabilos <thorsten.glaser@teckids.org>", "Frank Poetzsch-Heffter <p-h@katharineum.de>", "Tom Teichler <tom.teichler@teckids.org>", "Jonathan Weth <wethjo@katharineum.de>", "Hangzhi Yu <yuha@katharineum.de>"]
 maintainers = ["Jonathan Weth <wethjo@katharineum.de>", "Dominik George <dominik.george@teckids.org>"]
 license = "EUPL-1.2-or-later"
 homepage = "https://aleksis.org/"
-repository = "https://edugit.org/AlekSIS/official/AlekSIS"
-documentation = "https://aleksis.org/AlekSIS/docs/html/"
+repository = "https://edugit.org/AlekSIS/official/AlekSIS-Core"
+documentation = "https://aleksis.org/AlekSIS-Core/docs/html/"
 keywords = ["SIS", "education", "school", "digitisation", "school apps"]
 classifiers = [
     "Development Status :: 3 - Alpha",
@@ -27,27 +27,29 @@ classifiers = [
     "Typing :: Typed",
 ]
 
+[[tool.poetry.source]]
+name = "gitlab"
+url = "https://edugit.org/api/v4/projects/461/packages/pypi/simple"
+secondary = true
+
 [tool.poetry.dependencies]
 python = "^3.7"
 Django = "^3.0"
 django-any-js = "^1.0"
 django-debug-toolbar = "^2.0"
-django-easy-audit = {version ="^1.2rc1", allow-prereleases = true}
 django-middleware-global-request = "^0.1.2"
 django-menu-generator = "^1.0.4"
 django-tables2 = "^2.1"
-Pillow = "^7.0"
+Pillow = "^8.0"
 django-phonenumber-field = {version = "<5.1", extras = ["phonenumbers"]}
 django-sass-processor = "^0.8"
 libsass = "^0.20.0"
 colour = "^0.1.5"
-dynaconf = {version = "^2.0", extras = ["yaml", "toml", "ini"]}
+dynaconf = {version = "^3.1", extras = ["yaml", "toml", "ini"]}
 django-settings-context-processor = "^0.2"
 django-auth-ldap = { version = "^2.2", optional = true }
-django-maintenance-mode = "^0.14.0"
+django-maintenance-mode = "^0.15.0"
 django-ipware = "^3.0"
-easy-thumbnails = "^2.6"
-django-image-cropping = "^1.2"
 django-impersonate = "^1.4"
 python-memcached = "^1.59"
 django-hattori = "^0.2"
@@ -66,12 +68,12 @@ html2text = "^2020.0.0"
 django-ckeditor = "^6.0.0"
 django-js-reverse = "^0.9.1"
 calendarweek = "^0.4.3"
-Celery = {version="^4.4.0", optional=true, extras=["django", "redis"]}
-django-celery-results = {version="^1.1.2", optional=true}
+Celery = {version="^5.0.0", optional=true, extras=["django", "redis"]}
+django-celery-results = {version="^2.0.0", optional=true}
 django-celery-beat = {version="^2.0.0", optional=true}
 django-celery-email = {version="^3.0.0", optional=true}
 django-jsonstore = "^0.4.1"
-django-polymorphic = "^2.1.2"
+django-polymorphic = "^3.0.0"
 django-colorfield = "^0.3.0"
 django-bleach = "^0.6.1"
 django-guardian = "^2.2.0"
@@ -86,36 +88,21 @@ django-reversion = "^3.0.7"
 django-favicon-plus-reloaded = "^1.0.4"
 django-health-check = "^3.12.1"
 psutil = "^5.7.0"
-celery-progress = "^0.0.12"
+celery-progress = "^0.0.14"
+django-cachalot = "^2.3.2"
+django-prometheus = "^2.1.0"
+importlib-metadata = {version = "^3.0.0", python = "<3.9"}
+django-model-utils = "^4.0.0"
 
 [tool.poetry.extras]
 ldap = ["django-auth-ldap"]
 celery = ["Celery", "django-celery-results", "django-celery-beat", "django-celery-email", "celery-haystack"]
 
 [tool.poetry.dev-dependencies]
-sphinx = "^3.0"
-sphinxcontrib-django = "^0.5.0"
-sphinx-autodoc-typehints = "^1.7"
-django-stubs = "^1.1"
-pytest = "^6.0"
-pytest-django = "^3.7"
-pytest-django-testing-postgresql = "^0.1"
-selenium = "^3.141.0"
-safety = "^1.8.5"
-flake8 = "^3.7.9"
-flake8-django = "^1.0.0"
-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.13"
-black = "^19.10b0"
-flake8-black = "^0.2.0"
-isort = "^5.0.0"
-flake8-isort = "^4.0.0"
-pytest-cov = "^2.8.1"
-pytest-sugar = "^0.9.2"
+aleksis-builddeps = "*"
+
+[tool.poetry.scripts]
+aleksis-admin = 'aleksis.core.util.manage:aleksis_cmd'
 
 [tool.black]
 line-length = 100
diff --git a/tox.ini b/tox.ini
index eca63a758a5f6a20b1cd11887330817fec7908d7..98fce3af53aa48d1eac2e28454c26e7b74c23f72 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,7 +1,7 @@
 [tox]
 skipsdist = True
 skip_missing_interpreters = true
-envlist = py37,py38
+envlist = py37,py38,py39
 
 [testenv]
 whitelist_externals = poetry
@@ -22,8 +22,8 @@ setenv =
 
 [testenv:lint]
 commands =
-    - poetry run black --check --diff aleksis/
-    - poetry run isort -c --diff --stdout aleksis/
+    poetry run black --check --diff aleksis/
+    poetry run isort -c --diff --stdout aleksis/
     poetry run flake8 {posargs} aleksis/
 
 [testenv:security]
@@ -49,9 +49,8 @@ exclude = migrations,tests
 ignore = BLK100,E203,E231,W503,D100,D101,D102,D103,D104,D105,D106,D107,RST215,RST214,F821,F841,S106,T100,T101,DJ05
 
 [isort]
+profile = black
 line_length = 100
-multi_line_output = 3
-include_trailing_comma = 1
 default_section = THIRDPARTY
 known_first_party = aleksis
 known_django = django