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/mixins.py b/aleksis/core/mixins.py
index 2fcab29dac2250ad3fcc5dd70df67cea4ea8d8cd..e3f36630b169e912ceea50e8b759f4f078459b36 100644
--- a/aleksis/core/mixins.py
+++ b/aleksis/core/mixins.py
@@ -367,11 +367,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/settings.py b/aleksis/core/settings.py
index dc47829571406eafc75cbb3d72209f11d46c07ad..7b60994db688436284becd9acce1f7ae0a1ea79f 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,
@@ -86,7 +85,6 @@ INSTALLED_APPS = [
     "django_any_js",
     "django_yarnpkg",
     "django_tables2",
-    "easy_thumbnails",
     "maintenance_mode",
     "menu_generator",
     "reversion",
@@ -177,8 +175,6 @@ TEMPLATES = [
     },
 ]
 
-THUMBNAIL_PROCESSORS = () + thumbnail_settings.THUMBNAIL_PROCESSORS
-
 WSGI_APPLICATION = "aleksis.core.wsgi.application"
 
 # Database
@@ -392,6 +388,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,
 ]
 
@@ -441,6 +438,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}
 
diff --git a/aleksis/core/util/core_helpers.py b/aleksis/core/util/core_helpers.py
index aeffad13fb6d1d869a8b6e2793b18f36a9063096..060a0ef7aee06c51efe8ef2034e32b576e37fec6 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().get("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/poetry.lock b/poetry.lock
index fbccba44f562678cbd99b1595c13366b55e4ad3c..13733b9e1ba3f0113416d8dc5d4abd362c1b7235 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
@@ -591,17 +629,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"
@@ -670,7 +697,7 @@ six = "*"
 
 [[package]]
 name = "django-menu-generator"
-version = "1.0.4"
+version = "1.1.0"
 description = "A straightforward menu generator for Django"
 category = "main"
 optional = false
@@ -962,21 +989,9 @@ 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"
+version = "4.16.0"
 description = "Faker is a Python package that generates fake data for you."
 category = "main"
 optional = false
@@ -1358,7 +1373,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
@@ -1388,7 +1403,7 @@ dev = ["pre-commit", "tox"]
 
 [[package]]
 name = "prometheus-client"
-version = "0.8.0"
+version = "0.9.0"
 description = "Python client for the Prometheus monitoring system."
 category = "main"
 optional = false
@@ -2030,14 +2045,14 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
 
 [[package]]
 name = "tqdm"
-version = "4.51.0"
+version = "4.52.0"
 description = "Fast, Extensible Progress Meter"
 category = "main"
 optional = false
 python-versions = ">=2.6, !=3.0.*, !=3.1.*"
 
 [package.extras]
-dev = ["py-make (>=0.1.0)", "twine", "argopt", "pydoc-markdown"]
+dev = ["py-make (>=0.1.0)", "twine", "argopt", "pydoc-markdown", "wheel"]
 
 [[package]]
 name = "twilio"
@@ -2128,13 +2143,16 @@ ldap = ["django-auth-ldap"]
 [metadata]
 lock-version = "1.1"
 python-versions = "^3.7"
-content-hash = "5467245e7e26215aee4aae65b9ce76f1fd405d362f451b4d8447f32327022184"
+content-hash = "2ab7023c30df438ace8a6a2aa5fabfb72ba6f83115cf8ff95a3da7b14e3b6869"
 
 [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"},
@@ -2197,8 +2215,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"},
@@ -2214,6 +2232,7 @@ click = [
 ]
 colorama = [
     {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
+    {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
 ]
 colour = [
     {file = "colour-0.1.5-py2.py3-none-any.whl", hash = "sha256:33f6db9d564fadc16e59921a56999b79571160ce09916303d35346dddc17978c"},
@@ -2356,10 +2375,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"},
 ]
@@ -2386,7 +2401,7 @@ django-material = [
     {file = "django_material-1.7.1-py2.py3-none-any.whl", hash = "sha256:2b70ef05d4c6805554e91efc81c34f71081a5287f016d0172b7368b0a465d759"},
 ]
 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"},
@@ -2471,12 +2486,9 @@ 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"},
+    {file = "Faker-4.16.0-py3-none-any.whl", hash = "sha256:4d038ba51ae5e0a956d79cadd684d856e5750bfd608b61dad1807f8f08b1da49"},
+    {file = "Faker-4.16.0.tar.gz", hash = "sha256:f260f0375a44cd1e1a735c9b8c9b914304f607b5eef431d20e098c7c2f5b50a6"},
 ]
 flake8 = [
     {file = "flake8-3.8.4-py2.py3-none-any.whl", hash = "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839"},
@@ -2544,6 +2556,7 @@ importlib-metadata = [
     {file = "importlib_metadata-2.0.0.tar.gz", hash = "sha256:77a540690e24b0305878c37ffd421785a6f7e53c8b5720d211b211de8d0e95da"},
 ]
 iniconfig = [
+    {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
     {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
 ]
 isort = [
@@ -2656,8 +2669,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"},
@@ -2692,8 +2705,8 @@ pluggy = [
     {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
 ]
 prometheus-client = [
-    {file = "prometheus_client-0.8.0-py2.py3-none-any.whl", hash = "sha256:983c7ac4b47478720db338f1491ef67a100b474e3bc7dafcbaefb7d0b8f9b01c"},
-    {file = "prometheus_client-0.8.0.tar.gz", hash = "sha256:c6e6b706833a6bd1fd51711299edee907857be10ece535126a158f911ee80915"},
+    {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"},
 ]
 psutil = [
     {file = "psutil-5.7.3-cp27-none-win32.whl", hash = "sha256:1cd6a0c9fb35ece2ccf2d1dd733c1e165b342604c67454fd56a4c12e0a106787"},
@@ -2765,6 +2778,8 @@ pycryptodome = [
     {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-cp27m-win32.whl", hash = "sha256:a3d8a9efa213be8232c59cdc6b65600276508e375e0a119d710826248fd18d37"},
+    {file = "pycryptodome-3.9.9-cp27-cp27m-win_amd64.whl", hash = "sha256:50826b49fbca348a61529693b0031cdb782c39060fb9dca5ac5dff858159dc5a"},
     {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"},
@@ -2775,17 +2790,26 @@ pycryptodome = [
     {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-cp36-cp36m-win32.whl", hash = "sha256:b68794fba45bdb367eeb71249c26d23e61167510a1d0c3d6cf0f2f14636e62ee"},
+    {file = "pycryptodome-3.9.9-cp36-cp36m-win_amd64.whl", hash = "sha256:60febcf5baf70c566d9d9351c47fbd8321da9a4edf2eff45c4c31c86164ca794"},
     {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-cp37-cp37m-win32.whl", hash = "sha256:834b790bbb6bd18956f625af4004d9c15eed12d5186d8e57851454ae76d52215"},
+    {file = "pycryptodome-3.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:70d807d11d508433daf96244ec1c64e55039e8a35931fc5ea9eee94dbe3cb6b5"},
     {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-cp38-cp38-win32.whl", hash = "sha256:76b1a34d74bb2c91bce460cdc74d1347592045627a955e9a252554481c17c52f"},
+    {file = "pycryptodome-3.9.9-cp38-cp38-win_amd64.whl", hash = "sha256:6e4227849e4231a3f5b35ea5bdedf9a82b3883500e5624f00a19156e9a9ef861"},
     {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"},
+    {file = "pycryptodome-3.9.9-cp39-cp39-win32.whl", hash = "sha256:a199e9ca46fc6e999e5f47fce342af4b56c7de85fae893c69ab6aa17531fb1e1"},
+    {file = "pycryptodome-3.9.9-cp39-cp39-win_amd64.whl", hash = "sha256:6e89bb3826e6f84501e8e3b205c22595d0c5492c2f271cbb9ee1c48eb1866645"},
+    {file = "pycryptodome-3.9.9.tar.gz", hash = "sha256:910e202a557e1131b1c1b3f17a63914d57aac55cf9fb9b51644962841c3995c4"},
 ]
 pydocstyle = [
     {file = "pydocstyle-5.1.1-py3-none-any.whl", hash = "sha256:aca749e190a01726a4fb472dd4ef23b5c9da7b9205c0a7857c06533de13fd678"},
@@ -2941,6 +2965,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 = [
@@ -3046,8 +3072,8 @@ toml = [
     {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
 ]
 tqdm = [
-    {file = "tqdm-4.51.0-py2.py3-none-any.whl", hash = "sha256:9ad44aaf0fc3697c06f6e05c7cf025dd66bc7bcb7613c66d85f4464c47ac8fad"},
-    {file = "tqdm-4.51.0.tar.gz", hash = "sha256:ef54779f1c09f346b2b5a8e5c61f96fbcb639929e640e59f8cf810794f406432"},
+    {file = "tqdm-4.52.0-py2.py3-none-any.whl", hash = "sha256:80d9d5165d678dbd027dd102dfb99f71bf05f333b61fb761dbba13b4ab719ead"},
+    {file = "tqdm-4.52.0.tar.gz", hash = "sha256:18d6a615aedd09ec8456d9524489dab330af4bd5c2a14a76eb3f9a0e14471afe"},
 ]
 twilio = [
     {file = "twilio-6.47.0.tar.gz", hash = "sha256:effb4d6e9e9a9069065fbe21dea844597376ae6d6333626f14b05ba6b35bbb22"},
diff --git a/pyproject.toml b/pyproject.toml
index 84b10709ecbf0e9cd0e67aeac341f5127c8f7a37..33523b99d1840fdbc0dc0f16e765b16eebc6b266 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -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"
@@ -46,8 +51,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"
@@ -86,38 +89,17 @@ 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 = "^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