diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index d67a50560c92e815b5b779dd651f3b1dbb4f4250..955750958d170e723e9714295750d0e1204e878f 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,8 +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_pypi.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/aleksis/core/settings.py b/aleksis/core/settings.py
index c550a0421567c521a494f19124ac59542e83946a..45571b0db5e19ee996162b5549b4aaad4652bf44 100644
--- a/aleksis/core/settings.py
+++ b/aleksis/core/settings.py
@@ -368,6 +368,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,
 ]
 
diff --git a/aleksis/core/util/core_helpers.py b/aleksis/core/util/core_helpers.py
index aeffad13fb6d1d869a8b6e2793b18f36a9063096..6b5c01c6f7cb5dc1aa5511aa249c765dce2744e6 100644
--- a/aleksis/core/util/core_helpers.py
+++ b/aleksis/core/util/core_helpers.py
@@ -1,5 +1,4 @@
 import os
-import pkgutil
 import time
 from datetime import datetime, timedelta
 from importlib import import_module
@@ -8,6 +7,11 @@ from operator import itemgetter
 from typing import Any, Callable, Optional, Sequence, Union
 from uuid import uuid4
 
+try:
+    from importlib import metadata
+except ImportError:
+    import importlib_metadata as metadata
+
 from django.conf import settings
 from django.db.models import Model, QuerySet
 from django.http import HttpRequest
@@ -59,14 +63,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()["aleksis.app"]]
 
 
 def merge_app_settings(
@@ -81,11 +79,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)
diff --git a/apps/official/AlekSIS-App-Chronos b/apps/official/AlekSIS-App-Chronos
deleted file mode 160000
index 0be06de63d5f56eab6f9c1e814a82c69c318ffd1..0000000000000000000000000000000000000000
--- a/apps/official/AlekSIS-App-Chronos
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 0be06de63d5f56eab6f9c1e814a82c69c318ffd1
diff --git a/apps/official/AlekSIS-App-DashboardFeeds b/apps/official/AlekSIS-App-DashboardFeeds
deleted file mode 160000
index 1cf18722553a016b8a9165de556bb00329f10173..0000000000000000000000000000000000000000
--- a/apps/official/AlekSIS-App-DashboardFeeds
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 1cf18722553a016b8a9165de556bb00329f10173
diff --git a/apps/official/AlekSIS-App-Hjelp b/apps/official/AlekSIS-App-Hjelp
deleted file mode 160000
index 0898d261d95df2079767cf58a316d4804a2e230e..0000000000000000000000000000000000000000
--- a/apps/official/AlekSIS-App-Hjelp
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 0898d261d95df2079767cf58a316d4804a2e230e
diff --git a/apps/official/AlekSIS-App-LDAP b/apps/official/AlekSIS-App-LDAP
deleted file mode 160000
index 412631fb73abf30c931c3f1951def102720e3f51..0000000000000000000000000000000000000000
--- a/apps/official/AlekSIS-App-LDAP
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 412631fb73abf30c931c3f1951def102720e3f51
diff --git a/apps/official/AlekSIS-App-Untis b/apps/official/AlekSIS-App-Untis
deleted file mode 160000
index 2f22b31c19a0bd7f479eac7f11d0943f8e297bae..0000000000000000000000000000000000000000
--- a/apps/official/AlekSIS-App-Untis
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 2f22b31c19a0bd7f479eac7f11d0943f8e297bae
diff --git a/ci/build_dist.yml b/ci/build_dist.yml
deleted file mode 100644
index ec4a102bffeb9f86032fac4bf50b16e48a1fcddd..0000000000000000000000000000000000000000
--- a/ci/build_dist.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-build_dist:
-  stage: build
-  script:
-    - if [ $CI_COMMIT_REF_NAME = master ]; then
-       poetry version $(poetry version | cut -d" " -f2)+$(date +%s).${CI_COMMIT_SHORT_SHA} ;
-      elif [ x$CI_OMMIT_REF_NAME = x$CI_COMMIT_TAG ]; then
-       if ! [ "$(poetry version | cut -d" " -f2)" = $CI_COMMIT_REF_NAME ]; then
-        echo "Package version does not match tag. Aborting build of tag!" >/dev/fd/2 ;
-        exit 1 ;
-       fi ;
-      fi
-    - tox -e build
-  artifacts:
-    paths:
-      - dist/
-  only:
-    - master
-    - tags
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/deploy_pypi.yml b/ci/deploy_pypi.yml
deleted file mode 100644
index 6b235ad05fe3eb2d70f706a93fedd91219cd6605..0000000000000000000000000000000000000000
--- a/ci/deploy_pypi.yml
+++ /dev/null
@@ -1,11 +0,0 @@
-deploy_pypi:
-  stage: deploy
-  script:
-    - if [ $CI_COMMIT_REF_NAME = master ]; then
-       poetry publish -r gitlab ;
-      elif [ x$CI_OMMIT_REF_NAME = x$CI_COMMIT_TAG ]; then
-       poetry publish ;
-      fi
-  only:
-    - master
-    - tags
diff --git a/ci/general.yml b/ci/general.yml
deleted file mode 100644
index 03d096526e19baf680d381ef16ce9cfca716fd6b..0000000000000000000000000000000000000000
--- a/ci/general.yml
+++ /dev/null
@@ -1,24 +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"
-  POETRY_REPOSITORIES_GITLAB_URL: "$CI_API_V4_URL/projects/$CI_PROJECT_ID/packages/pypi"
-  POETRY_HTTP_BASIC_GITLAB_USERNAME: gitlab-ci-token
-  POETRY_HTTP_BASIC_GITLAB_PASSWORD: "$CI_JOB_TOKEN"
-  POETRY_PYPI_TOKEN_PYPI: "$PYPI_TOKEN"
-
-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/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 2b1410b2e2d9779cf4fcb1e46364abaef7f846b5..29d94c7f1e176cc100173d4e118bec0aa34c62a7 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -6,6 +6,44 @@ category = "dev"
 optional = false
 python-versions = "*"
 
+[[package]]
+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 = "2.6.1"
@@ -222,7 +260,7 @@ django-appconf = ">=0.4.1"
 
 [[package]]
 name = "celery-progress"
-version = "0.0.12"
+version = "0.0.14"
 description = "Drop in, configurable, dependency-free progress bars for your Django/Celery applications."
 category = "main"
 optional = false
@@ -580,17 +618,6 @@ python-versions = "*"
 [package.dependencies]
 django = ">=1.11"
 
-[[package]]
-name = "django-image-cropping"
-version = "1.5.0"
-description = "A reusable app for cropping images easily and non-destructively in Django"
-category = "main"
-optional = false
-python-versions = ">=3.5"
-
-[package.dependencies]
-django-appconf = ">=1.0.2"
-
 [[package]]
 name = "django-impersonate"
 version = "1.5.1"
@@ -951,18 +978,6 @@ toml = ["toml"]
 vault = ["hvac"]
 yaml = ["ruamel.yaml"]
 
-[[package]]
-name = "easy-thumbnails"
-version = "2.7"
-description = "Easy thumbnails for Django"
-category = "main"
-optional = false
-python-versions = ">=3.5"
-
-[package.dependencies]
-django = ">=1.11,<4.0"
-pillow = "*"
-
 [[package]]
 name = "faker"
 version = "4.14.2"
@@ -1347,7 +1362,7 @@ scramp = "1.2.0"
 
 [[package]]
 name = "phonenumbers"
-version = "8.12.12"
+version = "8.12.13"
 description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers."
 category = "main"
 optional = false
@@ -1670,7 +1685,7 @@ hiredis = ["hiredis (>=0.1.3)"]
 
 [[package]]
 name = "regex"
-version = "2020.11.11"
+version = "2020.11.13"
 description = "Alternative regular expression module, to replace re."
 category = "dev"
 optional = false
@@ -1811,7 +1826,7 @@ python-versions = "*"
 
 [[package]]
 name = "sphinx"
-version = "3.3.0"
+version = "3.3.1"
 description = "Python documentation generator"
 category = "dev"
 optional = false
@@ -2060,7 +2075,7 @@ python-versions = "*"
 
 [[package]]
 name = "urllib3"
-version = "1.26.1"
+version = "1.26.2"
 description = "HTTP library with thread-safe connection pooling, file post, and more."
 category = "main"
 optional = false
@@ -2117,13 +2132,16 @@ ldap = ["django-auth-ldap"]
 [metadata]
 lock-version = "1.1"
 python-versions = "^3.7"
-content-hash = "f92e91f0541f9d10f25ba28110c66ccad38f513c811319f512efabde376a6c2a"
+content-hash = "ad08a984daa918b60656e0ae43f82cf7833097a121f783fdf6ad8d2603fdc60b"
 
 [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"},
@@ -2186,8 +2204,8 @@ celery-haystack = [
     {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.11.8-py2.py3-none-any.whl", hash = "sha256:1f422849db327d534e3d0c5f02a263458c3955ec0aae4ff09b95f195c59f4edd"},
@@ -2342,10 +2360,6 @@ django-health-check = [
     {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-image-cropping = [
-    {file = "django-image-cropping-1.5.0.tar.gz", hash = "sha256:59744e8df88db7e46e37b526fc715fdde665d9efa345922745f50411a6dadb3f"},
-    {file = "django_image_cropping-1.5.0-py3-none-any.whl", hash = "sha256:81dbcabb6421c5a1e88fac9d96f336d6109a23dcb8fa6c678329d3688c9973c4"},
-]
 django-impersonate = [
     {file = "django-impersonate-1.5.1.tar.gz", hash = "sha256:7c786ffaa7a5dd430f9277b53a64676c470b684eee5aa52c3b483298860d09b4"},
 ]
@@ -2457,9 +2471,6 @@ dynaconf = [
     {file = "dynaconf-3.1.2-py2.py3-none-any.whl", hash = "sha256:808adfe964f10695846dbf8dad7632e47fc3bc38860fd1887ed57dddffc4eff2"},
     {file = "dynaconf-3.1.2.tar.gz", hash = "sha256:9b34ab2f811a81755f5eb4beac77a69e1e0887528c7e37fc4bc83fed52dcf502"},
 ]
-easy-thumbnails = [
-    {file = "easy-thumbnails-2.7.tar.gz", hash = "sha256:e4e7a0dd4001f56bfd4058428f2c91eafe27d33ef3b8b33ac4e013b159b9ff91"},
-]
 faker = [
     {file = "Faker-4.14.2-py3-none-any.whl", hash = "sha256:ce1c38823eb0f927567cde5bf2e7c8ca565c7a70316139342050ce2ca74b4026"},
     {file = "Faker-4.14.2.tar.gz", hash = "sha256:6afc461ab3f779c9c16e299fc731d775e39ea7e8e063b3053ee359ae198a15ca"},
@@ -2643,8 +2654,8 @@ pg8000 = [
     {file = "pg8000-1.16.6.tar.gz", hash = "sha256:8fc1e6a62ccb7c9830f1e7e9288e2d20eaf373cc8875b5c55b7d5d9b7717be91"},
 ]
 phonenumbers = [
-    {file = "phonenumbers-8.12.12-py2.py3-none-any.whl", hash = "sha256:23944f9e628f32a975d3b221b6d76e6ba8ae618d53cb3d82fc23d9e100a59b29"},
-    {file = "phonenumbers-8.12.12.tar.gz", hash = "sha256:70aa98a50ba7bc7f6bf17851f806c927107e7c44e7d21eb46bdbec07b99d23ae"},
+    {file = "phonenumbers-8.12.13-py2.py3-none-any.whl", hash = "sha256:9de2937034deb040eb9ac56519b0887e0fe89811e57f6f5c88359e3be20ae3b5"},
+    {file = "phonenumbers-8.12.13.tar.gz", hash = "sha256:96d02120a3481e22d8a8eb5e4595ceec1930855749f6e4a06ef931881f59f562"},
 ]
 pillow = [
     {file = "Pillow-7.2.0-cp35-cp35m-macosx_10_10_intel.whl", hash = "sha256:1ca594126d3c4def54babee699c055a913efb01e106c309fa6b04405d474d5ae"},
@@ -2671,8 +2682,6 @@ pillow = [
     {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-macosx_10_10_x86_64.whl", hash = "sha256:9c87ef410a58dd54b92424ffd7e28fd2ec65d2f7fc02b76f5e9b2067e355ebf6"},
-    {file = "Pillow-7.2.0-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:e901964262a56d9ea3c2693df68bc9860b8bdda2b04768821e4c44ae797de117"},
     {file = "Pillow-7.2.0-pp36-pypy36_pp73-win32.whl", hash = "sha256:25930fadde8019f374400f7986e8404c8b781ce519da27792cbe46eabec00c4d"},
     {file = "Pillow-7.2.0.tar.gz", hash = "sha256:97f9e7953a77d5a70f49b9a48da7776dc51e9b738151b22dacf101641594a626"},
 ]
@@ -2866,47 +2875,47 @@ redis = [
     {file = "redis-3.5.3.tar.gz", hash = "sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2"},
 ]
 regex = [
-    {file = "regex-2020.11.11-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:dd7bee615680d940dd44ac0a479f2bc5f73d6ca63a5915cd8d30739c14ca522c"},
-    {file = "regex-2020.11.11-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:3002ee2d4e8bbe4656237627203d8290a562d1fc1962deee470905ab63570345"},
-    {file = "regex-2020.11.11-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:064d2fc83ab4ee0055fcc1ef38ec60e505742850a40061f854ac64cb3d8d6dd3"},
-    {file = "regex-2020.11.11-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:83a390a653c13be1ab26287240df1fd9324ca8a0d31b603fa57cd7d9520648fa"},
-    {file = "regex-2020.11.11-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:412969d58ecd4f576510ec88bcb7602e9e582bbef78859ed8c9ca4de4f9e891c"},
-    {file = "regex-2020.11.11-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:ccfea4911ac28a8f744096bce1559e0bd86b09a53c8a9d5856ca8e1f5f4de1f5"},
-    {file = "regex-2020.11.11-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:cefcdb2ac3b67fd9f7244820ce1965c8cf352366199cc1358d67c6cc3c5c8bbc"},
-    {file = "regex-2020.11.11-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:9e8b3187f6beea8e56cb4b33c35049cbe376cf69aefaee5bc035309d88c98ca5"},
-    {file = "regex-2020.11.11-cp36-cp36m-win32.whl", hash = "sha256:787e44e5f4fd027dd90b5ee0240b05dc1752cb43c2903617f25baa495fe551e9"},
-    {file = "regex-2020.11.11-cp36-cp36m-win_amd64.whl", hash = "sha256:a9f76d9122359b09e38f27cd9c41729169171cf0fd73ec5b22cc4628f9e486ca"},
-    {file = "regex-2020.11.11-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6d128368def4b0cd95c0fc9d99a89ae73c083b25e67f27a410830e30f9df0edc"},
-    {file = "regex-2020.11.11-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:df50ba964812606663ca9d23d374036bc5ae3d71e86168409cdd84ca7948d8a3"},
-    {file = "regex-2020.11.11-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d1e57c16c4840f1c3543507742e99b8398609474a0e6a6925476914479de3488"},
-    {file = "regex-2020.11.11-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:6e50b3b417ab2fd67bfa6235f0df4782fe2ff8be83f0c4435e1dc43d25052ee8"},
-    {file = "regex-2020.11.11-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bb17a7fe9c47167337009ce18cd6e6b3edf3ca0063bf6bed6ce02515129c016a"},
-    {file = "regex-2020.11.11-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:826d0119f14f9a9ce25999a13ed5922c785b50e469800f6e5a6721318650ef49"},
-    {file = "regex-2020.11.11-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:8cc3717146ce4040419639cf45455663a002a554806ddac46304acc5bd41dae2"},
-    {file = "regex-2020.11.11-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:86ad88c7c2512094a85b0a01ce053bab1e28eafb8f3868bb8c22f4903e33f147"},
-    {file = "regex-2020.11.11-cp37-cp37m-win32.whl", hash = "sha256:e03867f3baf64ecab47dfc9ddb58afc67acb6a0f80f6cf8ff9fa82962ec4d1cd"},
-    {file = "regex-2020.11.11-cp37-cp37m-win_amd64.whl", hash = "sha256:56d1e298bb6482d0466399a6383181bf2627c37ad414e205b3ce0f85aa140be7"},
-    {file = "regex-2020.11.11-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:19ac2bf0048a2f4d460ee20647e84ca160512a7ee8af844dc9207720778470f1"},
-    {file = "regex-2020.11.11-cp38-cp38-manylinux1_i686.whl", hash = "sha256:84ab584dcb5e81815040d86148805a808acb0bee303d19638fe2f9488d704bc1"},
-    {file = "regex-2020.11.11-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:4159ecf20dffea07f4a7241b2a236f90eb622c7e8caab9f43caba5f27ca37284"},
-    {file = "regex-2020.11.11-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:8060be04baec546fe3afa6975d2998e15d1b655d7255f0e6b0ed3f482cccc218"},
-    {file = "regex-2020.11.11-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:cdb98be55db1b94c950822cbc10d3d768f01e184365851ebb42cd377486ced7b"},
-    {file = "regex-2020.11.11-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:11d9100bd874ce8b2a037db9150e732cd768359fc25fe5f77973208aa24eb13e"},
-    {file = "regex-2020.11.11-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:0951c78fa4cb26d1278a4b3784fcf973fc97ec39c07483328a74b034b0cc569c"},
-    {file = "regex-2020.11.11-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:c8b1ad791debd67221fb1266f8d09730ae927acacb32d0dad9fd07a7d341a28f"},
-    {file = "regex-2020.11.11-cp38-cp38-win32.whl", hash = "sha256:beae9db1545f8116cfc9301a9601e9c975bb56ca22a38ac0fe06a72c3460f31a"},
-    {file = "regex-2020.11.11-cp38-cp38-win_amd64.whl", hash = "sha256:48e94218f06317b6d32feb4ecff8b6025695450009bcb3291fb23daf79689431"},
-    {file = "regex-2020.11.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c67fd5f3ad81f8301184354014e8e7510ab77e0c7e450a427d77f28ae8effbef"},
-    {file = "regex-2020.11.11-cp39-cp39-manylinux1_i686.whl", hash = "sha256:e7cdd5ee8053c82607432b7ebad37e2ece54548fef2b254f7bce6f7831904586"},
-    {file = "regex-2020.11.11-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:394b5be4fa72354a78763b317f82997ad881896dd4a860e429a6fa74afaacb07"},
-    {file = "regex-2020.11.11-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:3b46a4c73ec1f25361147a7a0fd86084f3627dc78d09bcbe14e70db12683efec"},
-    {file = "regex-2020.11.11-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:267d1b13f863e664150948ce2a9ed4927bf4ac7a068780f1ee8af83352aa17a2"},
-    {file = "regex-2020.11.11-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:68267a7a5fb0bd9676b86f967143b6a6ecefb3eed4042ecc9e7f0e014aef8f74"},
-    {file = "regex-2020.11.11-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:e899b69dd5d26655cb454835ea2fceb18832c9ee9c4fb45dc4cf8a6089d35312"},
-    {file = "regex-2020.11.11-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:396411bb5a7849aeda9c49873b8295919fdc118c50b57122b09cb2097047c118"},
-    {file = "regex-2020.11.11-cp39-cp39-win32.whl", hash = "sha256:32f8714c4bcc4b0d2aa259b1647e3c5b6cfe2e923c6c124234a5e03408224227"},
-    {file = "regex-2020.11.11-cp39-cp39-win_amd64.whl", hash = "sha256:bf02ab95ff5261ba108725dbd795bf6395eaac1b8468b41472d82d35b12b0295"},
-    {file = "regex-2020.11.11.tar.gz", hash = "sha256:0a235841237d4487329bcabcb5b902858f7967f5e684e08e968367f25b2c3d37"},
+    {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.25.0-py2.py3-none-any.whl", hash = "sha256:e786fa28d8c9154e6a4de5d46a1d921b8749f8b74e28bde23768e5e16eece998"},
@@ -2941,6 +2950,8 @@ restructuredtext-lint = [
     {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-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c6ac7e45367b1317e56f1461719c853fd6825226f45b835df7436bb04031fd8a"},
+    {file = "ruamel.yaml.clib-0.2.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:b4b0d31f2052b3f9f9b5327024dc629a253a83d8649d4734ca7f35b60ec3e9e5"},
     {file = "ruamel.yaml.clib-0.2.2.tar.gz", hash = "sha256:2d24bd98af676f4990c4d715bcdc2a60b19c56a3fb3a763164d2d8ca0e806ba7"},
 ]
 rules = [
@@ -2979,8 +2990,8 @@ spdx-license-list = [
     {file = "spdx_license_list-0.5.1.tar.gz", hash = "sha256:64cb5de37724c64cdeccafa2ae68667ff8ccdb7b688f51c1c2be82d7ebe3a112"},
 ]
 sphinx = [
-    {file = "Sphinx-3.3.0-py3-none-any.whl", hash = "sha256:3abdb2c57a65afaaa4f8573cbabd5465078eb6fd282c1e4f87f006875a7ec0c7"},
-    {file = "Sphinx-3.3.0.tar.gz", hash = "sha256:1c21e7c5481a31b531e6cbf59c3292852ccde175b504b00ce2ff0b8f4adc3649"},
+    {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.1.tar.gz", hash = "sha256:244ba6d3e2fdb854622f643c7763d6f95b6886eba24bec28e86edf205e4ddb20"},
@@ -3081,8 +3092,8 @@ typing-extensions = [
     {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"},
 ]
 urllib3 = [
-    {file = "urllib3-1.26.1-py2.py3-none-any.whl", hash = "sha256:61ad24434555a42c0439770462df38b47d05d9e8e353d93ec3742900975e3e65"},
-    {file = "urllib3-1.26.1.tar.gz", hash = "sha256:097116a6f16f13482d2a2e56792088b9b2920f4eb6b4f84a2c90555fb673db74"},
+    {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"},
diff --git a/pyproject.toml b/pyproject.toml
index 48f3b0fa4f1ded56d02f851be33bb2f33213cc94..8aed0db473956c158c48e68a39330daac0480f6e 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,6 +27,11 @@ 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"
@@ -45,8 +50,6 @@ django-settings-context-processor = "^0.2"
 django-auth-ldap = { version = "^2.2", optional = true }
 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"
@@ -85,37 +88,16 @@ 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-prometheus = "^2.1.0"
+importlib-metadata = {version = "^2.0.0", python = "<3.8"}
 
 [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.black]
 line-length = 100