diff --git a/.gitignore b/.gitignore
index 70d2c1202e645fc31260a0fdd6bac90fdd25e15a..a30a3fa095b66e018388937f3ffb79eed4692301 100644
--- a/.gitignore
+++ b/.gitignore
@@ -74,3 +74,8 @@ htmlcov/
 maintenance_mode_state.txt
 media/
 package-lock.json
+
+# VSCode
+.vscode/
+.history/
+*.code-workspace
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index bcc9e130e2238e0a6025b73456696c8283c2c0ce..d36458971660c0b5ab354ad1e4b03068fb46ebea 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,122 +1,6 @@
-image: registry.edugit.org/teckids/team-sysadmin/docker-images/python-pimped:master
-
-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
-
-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
-
-build_dist:
-  stage: build
-  script:
-    - tox -e build
-  artifacts:
-    paths:
-      - dist/
-
-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
-
-build_docker:
-  stage: build
-  image:
-    name: gcr.io/kaniko-project/executor:v0.22.0
-    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
-
-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
+include:
+    - local: "/ci/general.yml"
+    - local: "/ci/test.yml"
+    - local: "/ci/build_dist.yml"
+    - local: "/ci/build_docker.yml"
+    - local: "/ci/deploy.yml"
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index 2c24e62bb4d1acd60579eb0064c0a79c08748d7f..be12afc1552dc2b6d6cb398bc2a456392ef21b9d 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -48,7 +48,7 @@ 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 waht was changed, by whom and why.
+describes what was changed, by whom and why.
 
 Help and information on Git for beginners are available in the `Git guide`_
 
diff --git a/aleksis/core/apps.py b/aleksis/core/apps.py
index 0037842bc71590eb521aa67f25b5f4cc6b50fb92..aeda609326941428e483e05cabd6e5412b2832f7 100644
--- a/aleksis/core/apps.py
+++ b/aleksis/core/apps.py
@@ -2,6 +2,7 @@ from typing import Any, List, Optional, Tuple
 
 import django.apps
 from django.http import HttpRequest
+from django.utils.module_loading import autodiscover_modules
 
 from dynamic_preferences.registries import preference_models
 
@@ -36,6 +37,9 @@ class CoreConfig(AppConfig):
     def ready(self):
         super().ready()
 
+        # Autodiscover various modules defined by AlekSIS
+        autodiscover_modules("form_extensions", "model_extensions", "checks")
+
         sitepreferencemodel = self.get_model("SitePreferenceModel")
         personpreferencemodel = self.get_model("PersonPreferenceModel")
         grouppreferencemodel = self.get_model("GroupPreferenceModel")
diff --git a/aleksis/core/forms.py b/aleksis/core/forms.py
index 8e5233c531193f1c9bbccea011ebdc3f4b765c66..abd45aabdee3f9d933376a0fc034f59218a8523f 100644
--- a/aleksis/core/forms.py
+++ b/aleksis/core/forms.py
@@ -10,7 +10,7 @@ from dynamic_preferences.forms import PreferenceForm
 from material import Fieldset, Layout, Row
 
 from .mixins import ExtensibleForm
-from .models import Announcement, Group, GroupType, Person
+from .models import AdditionalField, Announcement, Group, GroupType, Person
 from .registries import (
     group_preferences_registry,
     person_preferences_registry,
@@ -127,6 +127,7 @@ class EditGroupForm(ExtensibleForm):
     layout = Layout(
         Fieldset(_("Common data"), "name", "short_name", "group_type"),
         Fieldset(_("Persons"), "members", "owners", "parent_groups"),
+        Fieldset(_("Additional data"), "additional_fields"),
     )
 
     class Meta:
@@ -150,6 +151,7 @@ class EditGroupForm(ExtensibleForm):
             "parent_groups": ModelSelect2MultipleWidget(
                 search_fields=["name__icontains", "short_name__icontains"]
             ),
+            "additional_fields": ModelSelect2MultipleWidget(search_fields=["title__icontains",]),
         }
 
 
@@ -282,6 +284,14 @@ class GroupPreferenceForm(PreferenceForm):
     registry = group_preferences_registry
 
 
+class EditAdditionalFieldForm(forms.ModelForm):
+    """Form to manage additional fields."""
+
+    class Meta:
+        model = AdditionalField
+        exclude = []
+
+
 class EditGroupTypeForm(forms.ModelForm):
     """Form to manage group types."""
 
diff --git a/aleksis/core/menus.py b/aleksis/core/menus.py
index 4767a8f3492c7401a22164fe25f54da4ec9fd702..c1b19bb3970eac18d756ec0eae6bdaf8ae285d07 100644
--- a/aleksis/core/menus.py
+++ b/aleksis/core/menus.py
@@ -186,6 +186,17 @@ MENUS = {
                         )
                     ],
                 },
+                {
+                    "name": _("Additional fields"),
+                    "url": "additional_fields",
+                    "icon": "style",
+                    "validators": [
+                        (
+                            "aleksis.core.util.predicates.permission_validator",
+                            "core.view_additionalfield",
+                        )
+                    ],
+                },
             ],
         },
     ],
diff --git a/aleksis/core/mixins.py b/aleksis/core/mixins.py
index f6a8c18c483d0833d296abd6310cc2103331ba18..6b52fb3820c366c1040905eed1226cdc1d4c0e3f 100644
--- a/aleksis/core/mixins.py
+++ b/aleksis/core/mixins.py
@@ -164,17 +164,17 @@ class ExtensibleModel(models.Model, metaclass=_ExtensibleModelBase):
     @classmethod
     def property_(cls, func: Callable[[], Any], name: Optional[str] = None) -> None:
         """Add the passed callable as a property."""
-        cls._safe_add(property(func), func.__name__)
+        cls._safe_add(property(func), name or func.__name__)
 
     @classmethod
     def method(cls, func: Callable[[], Any], name: Optional[str] = None) -> None:
         """Add the passed callable as a method."""
-        cls._safe_add(func, func.__name__)
+        cls._safe_add(func, name or func.__name__)
 
     @classmethod
     def class_method(cls, func: Callable[[], Any], name: Optional[str] = None) -> None:
         """Add the passed callable as a classmethod."""
-        cls._safe_add(classmethod(func), func.__name__)
+        cls._safe_add(classmethod(func), name or func.__name__)
 
     @classmethod
     def field(cls, **kwargs) -> None:
diff --git a/aleksis/core/models.py b/aleksis/core/models.py
index b5bb204c25139c463e6d53c27b7c1ddf51b75576..fe0d63d62713fbdfa6ff9b142844d1226c28be6f 100644
--- a/aleksis/core/models.py
+++ b/aleksis/core/models.py
@@ -302,7 +302,9 @@ class Group(ExtensibleModel):
         null=True,
         blank=True,
     )
-    additional_fields = models.ManyToManyField(AdditionalField, verbose_name=_("Additional fields"))
+    additional_fields = models.ManyToManyField(
+        AdditionalField, verbose_name=_("Additional fields"), blank=True
+    )
 
     def get_absolute_url(self) -> str:
         return reverse("group_by_id", args=[self.id])
diff --git a/aleksis/core/rules.py b/aleksis/core/rules.py
index 3325f136af03b52deb9be44f765cc82e2e832b70..4f97eafddf3cfdb9725cf0e9e0cde05ef83d1ad0 100644
--- a/aleksis/core/rules.py
+++ b/aleksis/core/rules.py
@@ -1,6 +1,6 @@
 from rules import add_perm, always_allow
 
-from .models import Announcement, Group, GroupType, Person
+from .models import AdditionalField, Announcement, Group, GroupType, Person
 from .util.predicates import (
     has_any_object,
     has_global_perm,
@@ -195,6 +195,33 @@ change_group_preferences = has_person & (
 )
 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)
+
+# 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)
+
+
+# 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)
+
+# 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)
+
 # Edit group type
 change_group_type_predicate = has_person & (
     has_global_perm("core.change_grouptype") | has_object_perm("core.change_grouptype")
diff --git a/aleksis/core/tables.py b/aleksis/core/tables.py
index ff8fd871e75e9b97453784928d65456c5f857e8b..6f7ba041457f11420cf9155ff0cc8ca5012fd9d1 100644
--- a/aleksis/core/tables.py
+++ b/aleksis/core/tables.py
@@ -24,6 +24,22 @@ class GroupsTable(tables.Table):
     short_name = tables.LinkColumn("group_by_id", args=[A("id")])
 
 
+class AdditionalFieldsTable(tables.Table):
+    """Table to list group types."""
+
+    class Meta:
+        attrs = {"class": "responsive-table hightlight"}
+
+    title = tables.LinkColumn("edit_additional_field_by_id", args=[A("id")])
+    delete = tables.LinkColumn(
+        "delete_additional_field_by_id",
+        args=[A("id")],
+        verbose_name=_("Delete"),
+        text=_("Delete"),
+        attrs={"a": {"class": "btn-flat waves-effect waves-red"}},
+    )
+
+
 class GroupTypesTable(tables.Table):
     """Table to list group types."""
 
diff --git a/aleksis/core/templates/403.html b/aleksis/core/templates/403.html
index c84e7f57ed0fa82dead672fde7ffd2c20a8bdc81..00da4145121cafa2319c806884bb5637ad09a798 100644
--- a/aleksis/core/templates/403.html
+++ b/aleksis/core/templates/403.html
@@ -15,7 +15,7 @@
             administrators:
           {% endblocktrans %}
         </p>
-        {% include "core/admins_list.html" %}
+        {% include "core/partials/admins_list.html" %}
       </div>
     </div>
   </div>
diff --git a/aleksis/core/templates/404.html b/aleksis/core/templates/404.html
index 45873cde006feaf32b27999cf357d4888d70f644..33c311fcaf4c106d44c53788b44426584d014c33 100644
--- a/aleksis/core/templates/404.html
+++ b/aleksis/core/templates/404.html
@@ -19,7 +19,7 @@
             administrators:
           {% endblocktrans %}
         </p>
-        {% include "core/admins_list.html" %}
+        {% include "core/partials/admins_list.html" %}
       </div>
     </div>
   </div>
diff --git a/aleksis/core/templates/500.html b/aleksis/core/templates/500.html
index 621b9e424054cb321072450140c60548ebec3a0d..0759bfdd070f339fe0eec4f3d5806485e34e4664 100644
--- a/aleksis/core/templates/500.html
+++ b/aleksis/core/templates/500.html
@@ -15,7 +15,7 @@
             error. You can also contact them directly:
           {% endblocktrans %}
         </p>
-        {% include "core/admins_list.html" %}
+        {% include "core/partials/admins_list.html" %}
       </div>
     </div>
   </div>
diff --git a/aleksis/core/templates/503.html b/aleksis/core/templates/503.html
index 9ed4fcecbaa5ef620264374fef1074288c4d7785..ac1dc4c3dea129f0798f18fd61dfdaaa5f291d17 100644
--- a/aleksis/core/templates/503.html
+++ b/aleksis/core/templates/503.html
@@ -14,7 +14,7 @@
             This page is currently unavailable. If this error persists, contact your site administrators:
           {% endblocktrans %}
         </p>
-        {% include "core/admins_list.html" %}
+        {% include "core/partials/admins_list.html" %}
       </div>
     </div>
   </div>
diff --git a/aleksis/core/templates/core/additional_field/edit.html b/aleksis/core/templates/core/additional_field/edit.html
new file mode 100644
index 0000000000000000000000000000000000000000..b1487eb259b44f1425c950574f7df845d4e22129
--- /dev/null
+++ b/aleksis/core/templates/core/additional_field/edit.html
@@ -0,0 +1,17 @@
+{# -*- engine:django -*- #}
+
+{% extends "core/base.html" %}
+{% load material_form i18n %}
+
+{% block browser_title %}{% blocktrans %}Edit additional field{% endblocktrans %}{% endblock %}
+{% block page_title %}{% blocktrans %}Edit additional field{% endblocktrans %}{% endblock %}
+
+{% block content %}
+
+  <form method="post">
+    {% csrf_token %}
+    {% form form=edit_additional_field_form %}{% endform %}
+    {% include "core/save_button.html" %}
+  </form>
+
+{% endblock %}
diff --git a/aleksis/core/templates/core/additional_field/list.html b/aleksis/core/templates/core/additional_field/list.html
new file mode 100644
index 0000000000000000000000000000000000000000..1ba2a77a32c528b8443f4dc5a4c201b5739414fe
--- /dev/null
+++ b/aleksis/core/templates/core/additional_field/list.html
@@ -0,0 +1,18 @@
+{# -*- engine:django -*- #}
+
+{% extends "core/base.html" %}
+
+{% load i18n %}
+{% load render_table from django_tables2 %}
+
+{% block browser_title %}{% blocktrans %}Additional fields{% endblocktrans %}{% endblock %}
+{% block page_title %}{% blocktrans %}Additional fields{% endblocktrans %}{% endblock %}
+
+{% block content %}
+  <a class="btn green waves-effect waves-light" href="{% url 'create_additional_field' %}">
+    <i class="material-icons left">add</i>
+    {% trans "Create additional field" %}
+  </a>
+
+  {% render_table additional_fields_table %}
+{% endblock %}
diff --git a/aleksis/core/templates/core/base.html b/aleksis/core/templates/core/base.html
index e30d428f060f540cbf7d4dbbb483a1371dbddf38..2d112a4f0fe54f4f1926811fbd589b32351909cf 100644
--- a/aleksis/core/templates/core/base.html
+++ b/aleksis/core/templates/core/base.html
@@ -7,7 +7,7 @@
 <!DOCTYPE html>
 <html lang="{{ LANGUAGE_CODE }}">
 <head>
-  {% include "core/meta.html" %}
+  {% include "core/partials/meta.html" %}
 
   <title>
     {% block no_browser_title %}
@@ -81,14 +81,14 @@
       </li>
     {% endif %}
     <li class="no-padding">
-      {% include "core/sidenav.html" %}
+      {% include "core/partials/sidenav.html" %}
     </li>
   </ul>
 </header>
 
 
 <main role="main">
-  {% include 'core/no_person.html' %}
+  {% include 'core/partials/no_person.html' %}
 
   {% if messages %}
     {% for message in messages %}
@@ -122,23 +122,23 @@
     <div class="row no-margin footer-row-large">
       <div class="col l6 s12 no-pad-left height-inherit">
         <p class="white-text valign-bot">
-          {% include 'core/language_form.html' %}
+          {% include 'core/partials/language_form.html' %}
 
         </p>
       </div>
       <div class="col xl15 l6 offset-xl01 s12 no-pad-right">
         <ul class="no-margin right">
-          {% include "core/footer-menu.html" %}
+          {% include "core/partials/footer-menu.html" %}
         </ul>
       </div>
     </div>
     <div class="row no-margin footer-row-small">
             <span class="white-text make-it-higher">
-  {% include 'core/language_form.html' %}
+  {% include 'core/partials/language_form.html' %}
 
             </span>
       <ul class="no-margin footer-ul">
-        {% include "core/footer-menu.html" %}
+        {% include "core/partials/footer-menu.html" %}
       </ul>
     </div>
   </div>
diff --git a/aleksis/core/templates/core/base_print.html b/aleksis/core/templates/core/base_print.html
index 6059d664fccb38ce18a55eb9ea6d1a406a6e9ad4..b0739d3a2e7e0ceb6d81d21ce575d6ef967d810f 100644
--- a/aleksis/core/templates/core/base_print.html
+++ b/aleksis/core/templates/core/base_print.html
@@ -5,7 +5,7 @@
 <!DOCTYPE html>
 <html lang="{{ LANGUAGE_CODE }}">
 <head>
-  {% include "core/meta.html" %}
+  {% include "core/partials/meta.html" %}
 
   <title>
     {% block no_browser_title %}
diff --git a/aleksis/core/templates/core/crud_events.html b/aleksis/core/templates/core/crud_events.html
deleted file mode 100644
index 30c4ec464f29a8e99d7b6dd72c0bc824a9e9825f..0000000000000000000000000000000000000000
--- a/aleksis/core/templates/core/crud_events.html
+++ /dev/null
@@ -1,49 +0,0 @@
-{% load i18n %}
-
-<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">
-        {% 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 %}
-
-        <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>
-      </li>
-    {% endif %}
-  {% endfor %}
-</ul>
diff --git a/aleksis/core/templates/core/groups_child_groups.html b/aleksis/core/templates/core/group/child_groups.html
similarity index 100%
rename from aleksis/core/templates/core/groups_child_groups.html
rename to aleksis/core/templates/core/group/child_groups.html
diff --git a/aleksis/core/templates/core/edit_group.html b/aleksis/core/templates/core/group/edit.html
similarity index 88%
rename from aleksis/core/templates/core/edit_group.html
rename to aleksis/core/templates/core/group/edit.html
index e7b3188076125020958105ee97ab0de00bd5f766..b26a28d1efc5ee292a257220bca00754512c1b99 100644
--- a/aleksis/core/templates/core/edit_group.html
+++ b/aleksis/core/templates/core/group/edit.html
@@ -11,7 +11,7 @@
   <form method="post">
     {% csrf_token %}
     {% form form=edit_group_form %}{% endform %}
-    {% include "core/save_button.html" %}
+    {% include "core/partials/save_button.html" %}
   </form>
 
 {% endblock %}
diff --git a/aleksis/core/templates/core/group_full.html b/aleksis/core/templates/core/group/full.html
similarity index 75%
rename from aleksis/core/templates/core/group_full.html
rename to aleksis/core/templates/core/group/full.html
index 0add6f1a3041b723e4a4e3d87972e07a8daebf97..2c778f837a785d431300ea57a1a283a05b6bd367 100644
--- a/aleksis/core/templates/core/group_full.html
+++ b/aleksis/core/templates/core/group/full.html
@@ -31,6 +31,25 @@
     </p>
   {% endif %}
 
+  <table>
+    <tr>
+      <th>
+        <i class="material-icons center" title="{% trans "Group type" %}">category</i>
+      </th>
+      <td>
+        {{ group.group_type }}
+      </td>
+    </tr>
+    <tr>
+      <th>
+        <i class="material-icons center" title="{% trans "Parent groups" %}">vertical_align_top</i>
+      </th>
+      <td>
+        {{ group.parent_groups.all|join:", " }}
+      </td>
+    </tr>
+  </table>
+
   <h5>{% blocktrans %}Owners{% endblocktrans %}</h5>
   {% render_table owners_table %}
 
diff --git a/aleksis/core/templates/core/groups.html b/aleksis/core/templates/core/group/list.html
similarity index 100%
rename from aleksis/core/templates/core/groups.html
rename to aleksis/core/templates/core/group/list.html
diff --git a/aleksis/core/templates/core/edit_group_type.html b/aleksis/core/templates/core/group_type/edit.html
similarity index 89%
rename from aleksis/core/templates/core/edit_group_type.html
rename to aleksis/core/templates/core/group_type/edit.html
index c857de98b893cf243c3c108f3c76b5aa142e6037..843975b16bb6ccbd7cf8eeed8f2d5713f5320e23 100644
--- a/aleksis/core/templates/core/edit_group_type.html
+++ b/aleksis/core/templates/core/group_type/edit.html
@@ -11,7 +11,7 @@
   <form method="post">
     {% csrf_token %}
     {% form form=edit_group_type_form %}{% endform %}
-    {% include "core/save_button.html" %}
+    {% include "core/partials/save_button.html" %}
   </form>
 
 {% endblock %}
diff --git a/aleksis/core/templates/core/group_types.html b/aleksis/core/templates/core/group_type/list.html
similarity index 100%
rename from aleksis/core/templates/core/group_types.html
rename to aleksis/core/templates/core/group_type/list.html
diff --git a/aleksis/core/templates/core/index.html b/aleksis/core/templates/core/index.html
index ac5d19b71bc6ac4acd00d7d3c509e1c07f089dcc..683adca900d46a8ba50790829db4d3ecd0ba2716 100644
--- a/aleksis/core/templates/core/index.html
+++ b/aleksis/core/templates/core/index.html
@@ -27,7 +27,7 @@
       </div>
     {% endfor %}
 
-    {% include "core/announcements.html" with announcements=announcements %}
+    {% include "core/partials/announcements.html" with announcements=announcements %}
 
     <div class="row" id="live_load">
       {% for widget in widgets %}
diff --git a/aleksis/core/templates/core/data_management.html b/aleksis/core/templates/core/management/data_management.html
similarity index 87%
rename from aleksis/core/templates/core/data_management.html
rename to aleksis/core/templates/core/management/data_management.html
index 520dcb6d54beb20c482158cfa538ccf5cf2dc1ab..fa8e61f5dac1d1e01e5a773b68015cc9befe8d8f 100644
--- a/aleksis/core/templates/core/data_management.html
+++ b/aleksis/core/templates/core/management/data_management.html
@@ -8,5 +8,5 @@
 
 {% block content %}
   {% get_menu "DATA_MANAGEMENT_MENU" as menu %}
-  {% include "core/on_page_menu.html" %}
+  {% include "core/partials/on_page_menu.html" %}
 {% endblock %}
diff --git a/aleksis/core/templates/core/about.html b/aleksis/core/templates/core/pages/about.html
similarity index 100%
rename from aleksis/core/templates/core/about.html
rename to aleksis/core/templates/core/pages/about.html
diff --git a/aleksis/core/templates/offline.html b/aleksis/core/templates/core/pages/offline.html
similarity index 92%
rename from aleksis/core/templates/offline.html
rename to aleksis/core/templates/core/pages/offline.html
index 6961e03de97ea1a51096df239ac55880729e0fe7..a6a70dc19f074e8c3f3ede50b5e9b5b80b5682f1 100644
--- a/aleksis/core/templates/offline.html
+++ b/aleksis/core/templates/core/pages/offline.html
@@ -13,5 +13,5 @@
       administrators:
     {% endblocktrans %}
   </p>
-  {% include "core/admins_list.html" %}
+  {% include "core/partials/admins_list.html" %}
 {% endblock %}
diff --git a/aleksis/core/templates/core/system_status.html b/aleksis/core/templates/core/pages/system_status.html
similarity index 50%
rename from aleksis/core/templates/core/system_status.html
rename to aleksis/core/templates/core/pages/system_status.html
index a1f313efd843c6fea63cbc41f5ff7921d2ad5eaa..a62d024cf9ff5875ce61c8305b84834e47393726 100644
--- a/aleksis/core/templates/core/system_status.html
+++ b/aleksis/core/templates/core/pages/system_status.html
@@ -62,4 +62,68 @@
       </div>
     </div>
   </div>
+
+
+  {% if tasks %}
+    <div class="card">
+      <div class="card-content">
+        <span class="card-title"> {% blocktrans %}Celery task results{% endblocktrans %}</span>
+
+        <table>
+          <thead>
+            <tr>
+              <th>{% trans "Task" %}</th>
+              <th>{% trans "ID" %}</th>
+              <th>{% trans "Date done" %}</th>
+              <th>{% trans "Status" %}</th>
+            </tr>
+          </thead>
+          <tbody>
+            {% for task in tasks %}
+              {% if task != None %}
+                <tr>
+                  <td>{{ task.task_name }}</td>
+                  <td>{{ task.task_id }}</td>
+                  <td>{{ task.date_done }}</td>
+                  <td>
+                    {% if task.status == "PENDING" %}
+                      <a class="tooltipped" data-position="top"
+                      data-tooltip="{{ task.status }}">
+                        <i class="material-icons orange-text">hourglass_empty</i>
+                      </a>
+                    {% elif task.status == "STARTED" %}
+                      <a class="tooltipped" data-position="top"
+                      data-tooltip="{{ task.status }}">
+                        <i class="material-icons orange-text">directions_run</i>
+                      </a>
+                    {% elif task.status == "SUCCESS" %}
+                      <a class="tooltipped" data-position="top"
+                      data-tooltip="{{ task.status }}">
+                        <i class="material-icons green-text">done</i>
+                      </a>
+                    {% elif task.status == "FAILURE" %}
+                      <a class="tooltipped" data-position="top"
+                      data-tooltip="{{ task.status }}">
+                        <i class="material-icons red-text">error</i>
+                      </a>
+                    {% elif task.status == "RETRY" %}
+                      <a class="tooltipped" data-position="top"
+                      data-tooltip="{{ task.status }}">
+                        <i class="material-icons orange-text">hourglass_full</i>
+                      </a>
+                    {% elif task.status == "REVOKED" %}
+                      <a class="tooltipped" data-position="top"
+                      data-tooltip="{{ task.status }}">
+                        <i class="material-icons red-text">clear</i>
+                      </a>
+                    {% endif %}
+                  </td>
+                </tr>
+              {% endif %}
+            {% endfor %}
+          </tbody>
+        </table>
+      </div>
+    </div>
+  {% endif %}
 {% endblock %}
diff --git a/aleksis/core/templates/core/admins_list.html b/aleksis/core/templates/core/partials/admins_list.html
similarity index 100%
rename from aleksis/core/templates/core/admins_list.html
rename to aleksis/core/templates/core/partials/admins_list.html
diff --git a/aleksis/core/templates/core/announcements.html b/aleksis/core/templates/core/partials/announcements.html
similarity index 100%
rename from aleksis/core/templates/core/announcements.html
rename to aleksis/core/templates/core/partials/announcements.html
diff --git a/aleksis/core/templates/core/partials/crud_events.html b/aleksis/core/templates/core/partials/crud_events.html
new file mode 100644
index 0000000000000000000000000000000000000000..50e4f73cab492804b163b7ddfe0dda08b9e267b7
--- /dev/null
+++ b/aleksis/core/templates/core/partials/crud_events.html
@@ -0,0 +1,62 @@
+{% 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>
+        {% endif %}
+      </li>
+    {% endif %}
+  {% endfor %}
+</ul>
diff --git a/aleksis/core/templates/core/footer-menu.html b/aleksis/core/templates/core/partials/footer-menu.html
similarity index 100%
rename from aleksis/core/templates/core/footer-menu.html
rename to aleksis/core/templates/core/partials/footer-menu.html
diff --git a/aleksis/core/templates/core/language_form.html b/aleksis/core/templates/core/partials/language_form.html
similarity index 100%
rename from aleksis/core/templates/core/language_form.html
rename to aleksis/core/templates/core/partials/language_form.html
diff --git a/aleksis/core/templates/core/meta.html b/aleksis/core/templates/core/partials/meta.html
similarity index 100%
rename from aleksis/core/templates/core/meta.html
rename to aleksis/core/templates/core/partials/meta.html
diff --git a/aleksis/core/templates/core/no_person.html b/aleksis/core/templates/core/partials/no_person.html
similarity index 100%
rename from aleksis/core/templates/core/no_person.html
rename to aleksis/core/templates/core/partials/no_person.html
diff --git a/aleksis/core/templates/core/on_page_menu.html b/aleksis/core/templates/core/partials/on_page_menu.html
similarity index 100%
rename from aleksis/core/templates/core/on_page_menu.html
rename to aleksis/core/templates/core/partials/on_page_menu.html
diff --git a/aleksis/core/templates/core/save_button.html b/aleksis/core/templates/core/partials/save_button.html
similarity index 100%
rename from aleksis/core/templates/core/save_button.html
rename to aleksis/core/templates/core/partials/save_button.html
diff --git a/aleksis/core/templates/core/sidenav.html b/aleksis/core/templates/core/partials/sidenav.html
similarity index 100%
rename from aleksis/core/templates/core/sidenav.html
rename to aleksis/core/templates/core/partials/sidenav.html
diff --git a/aleksis/core/templates/core/turnable.html b/aleksis/core/templates/core/partials/turnable.html
similarity index 100%
rename from aleksis/core/templates/core/turnable.html
rename to aleksis/core/templates/core/partials/turnable.html
diff --git a/aleksis/core/templates/core/persons_accounts.html b/aleksis/core/templates/core/person/accounts.html
similarity index 100%
rename from aleksis/core/templates/core/persons_accounts.html
rename to aleksis/core/templates/core/person/accounts.html
diff --git a/aleksis/core/templates/core/edit_person.html b/aleksis/core/templates/core/person/edit.html
similarity index 89%
rename from aleksis/core/templates/core/edit_person.html
rename to aleksis/core/templates/core/person/edit.html
index 8a5d0ca39a8fa0cbfd437519a09723ce59278c2d..8f854610e3424b9da47f142cef41b71c9e0fb097 100644
--- a/aleksis/core/templates/core/edit_person.html
+++ b/aleksis/core/templates/core/person/edit.html
@@ -14,7 +14,7 @@
   <form method="post" enctype="multipart/form-data">
     {% csrf_token %}
     {% form form=edit_person_form %}{% endform %}
-    {% include "core/save_button.html" %}
+    {% include "core/partials/save_button.html" %}
   </form>
 
 {% endblock %}
diff --git a/aleksis/core/templates/core/person_full.html b/aleksis/core/templates/core/person/full.html
similarity index 100%
rename from aleksis/core/templates/core/person_full.html
rename to aleksis/core/templates/core/person/full.html
diff --git a/aleksis/core/templates/core/persons.html b/aleksis/core/templates/core/person/list.html
similarity index 100%
rename from aleksis/core/templates/core/persons.html
rename to aleksis/core/templates/core/person/list.html
diff --git a/aleksis/core/templates/dynamic_preferences/form.html b/aleksis/core/templates/dynamic_preferences/form.html
index d8de37802c09a14327b0d949b5935fb69660c666..da3d608285c43673bd27932d0d19cef303b1cef7 100644
--- a/aleksis/core/templates/dynamic_preferences/form.html
+++ b/aleksis/core/templates/dynamic_preferences/form.html
@@ -22,7 +22,7 @@
     <form action="" enctype="multipart/form-data" method="post">
       {% csrf_token %}
       {% form form=form %}{% endform %}
-      {% include "core/save_button.html" with caption=_("Save preferences") %}
+      {% include "core/partials/save_button.html" with caption=_("Save preferences") %}
     </form>
   </div>
 {% endblock %}
diff --git a/aleksis/core/templates/templated_email/notification.email b/aleksis/core/templates/templated_email/notification.email
index bb39f5861876b1dc1c4c39639de649ec6d6590eb..8e61fd0df28b5bc6342c891921831e147e9cc8d9 100644
--- a/aleksis/core/templates/templated_email/notification.email
+++ b/aleksis/core/templates/templated_email/notification.email
@@ -2,21 +2,48 @@
 
 {% block subject %} {% trans "New notification for" %} {{ notification_user }} {% endblock %}
 
+{% block plain %}
+    {% blocktrans with notification_user=notification_user %}Dear {{ notification_user }},{% endblocktrans %}
+
+    {% trans "we got a new notification for you:" %}
+
+    {{ notification.title }}
+
+    {{ notification.description }}
+
+    {% if notification.link %}
+        {% trans "More information" %} → {{ notification.link }}
+    {% endif %}
+
+    {% blocktrans with trans_sender=notification.sender trans_created_at=notification.created_at %}
+        Sent by {{ trans_sender }} at {{ trans_created_at }}
+    {% endblocktrans %}
+
+    {% trans "Your AlekSIS team" %}
+{% endblock %}
+
 {% block html %}
 <main>
-    <p>{% trans "Dear" %} {{ notification_user }}, <br>
-        {% trans "we got a new notification for you:" %}</p>
+    <p>{% blocktrans with notification_user=notification_user %}Dear {{ notification_user }},{% endblocktrans %}</p>
+
+    <p>{% trans "we got a new notification for you:" %}</p>
+
     <blockquote>
+        <h5>{{ notification.title }}</h5>
         <p>{{ notification.description }}</p>
         {% if notification.link %}
             <a href="{{ notification.link }}">{% trans "More information" %} →</a>
         {% endif %}
     </blockquote>
 
-    {% blocktrans with trans_sender=notification.sender trans_created_at=notification.created_at %}
-    <p>By {{ trans_sender }} at {{ trans_created_at }}</p>
+    <p>
+        {% blocktrans with trans_sender=notification.sender trans_created_at=notification.created_at %}
+            Sent by {{ trans_sender }} at {{ trans_created_at }}
+        {% endblocktrans %}
+    </p>
 
-    <i>Your AlekSIS team</i>
-    {% endblocktrans %}
+    <p>
+        <i>{% trans "Your AlekSIS team" %}</i>
+    </p>
 </main>
 {% endblock %}
diff --git a/aleksis/core/templatetags/data_helpers.py b/aleksis/core/templatetags/data_helpers.py
index 19f286434db34eb9e74b88950b8d87ee854959ef..f7393c73fd86eba6f861f87e321bf8dc5cbce6fd 100644
--- a/aleksis/core/templatetags/data_helpers.py
+++ b/aleksis/core/templatetags/data_helpers.py
@@ -1,6 +1,8 @@
-from typing import Any
+import json
+from typing import Any, Optional, Union
 
 from django import template
+from django.contrib.contenttypes.models import ContentType
 
 register = template.Library()
 
@@ -16,3 +18,24 @@ def get_dict(value: Any, arg: Any) -> Any:
         return value[int(arg)]
     else:
         return None
+
+
+@register.simple_tag
+def verbose_name(app_label: str, model: str, field: Optional[str] = None) -> str:
+    """Get a verbose name of a model or a field by app label and model name."""
+    ct = ContentType.objects.get(app_label=app_label, model=model).model_class()
+
+    if field:
+        # Field
+        return ct._meta.get_field(field).verbose_name.title()
+    else:
+        # Whole model
+        return ct._meta.verbose_name.title()
+
+
+@register.simple_tag
+def parse_json(value: Optional[str] = None) -> Union[dict, None]:
+    """Template tag for parsing JSON from a string."""
+    if not value:
+        return None
+    return json.loads(value)
diff --git a/aleksis/core/tests/models/test_notification.py b/aleksis/core/tests/models/test_notification.py
new file mode 100644
index 0000000000000000000000000000000000000000..1b1a6df054fe24f06bd363e825df3f1259af53e8
--- /dev/null
+++ b/aleksis/core/tests/models/test_notification.py
@@ -0,0 +1,34 @@
+import pytest
+
+from aleksis.core.models import Notification, Person
+
+pytestmark = pytest.mark.django_db
+
+
+def test_email_notification(mailoutbox):
+    email = "doe@example.com"
+    recipient = Person.objects.create(first_name="Jane", last_name="Doe", email=email)
+
+    sender = "Foo"
+    title = "There is happened something."
+    description = "Here you get some more information."
+    link = "https://aleksis.org/"
+
+    notification = Notification(
+        sender=sender, recipient=recipient, title=title, description=description, link=link
+    )
+    notification.save()
+
+    assert notification.sent
+
+    assert len(mailoutbox) == 1
+
+    mail = mailoutbox[0]
+
+    assert email in mail.to
+    assert title in mail.body
+    assert description in mail.body
+    assert link in mail.body
+    assert sender in mail.body
+    assert recipient.addressing_name in mail.subject
+    assert recipient.addressing_name in mail.body
diff --git a/aleksis/core/tests/templatetags/test_data_helpers.py b/aleksis/core/tests/templatetags/test_data_helpers.py
index ce43e578dbd84f95ed96a6cf1426d615f7e9c816..176e08b22ecb252d5b0113583ab534f46e978072 100644
--- a/aleksis/core/tests/templatetags/test_data_helpers.py
+++ b/aleksis/core/tests/templatetags/test_data_helpers.py
@@ -1,4 +1,10 @@
-from aleksis.core.templatetags.data_helpers import get_dict
+import json
+
+import pytest
+
+from aleksis.core.templatetags.data_helpers import get_dict, parse_json, verbose_name
+
+pytestmark = pytest.mark.django_db
 
 
 def test_get_dict_object():
@@ -24,3 +30,24 @@ def test_get_dict_invalid():
     _foo = 12
 
     assert get_dict(_foo, "bar") is None
+
+
+def test_verbose_name_model():
+    assert verbose_name("core", "person") == "Person"
+
+
+def test_verbose_name_field():
+    assert verbose_name("core", "person", "first_name") == "First Name"
+
+
+def test_parse_json_json():
+    foo = {"foo": 12, "bar": "12", "baz": []}
+    foo_json = json.dumps(foo)
+
+    assert parse_json(foo_json) == foo
+    assert parse_json("{}") == {}
+
+
+def test_parse_json_empty():
+    assert parse_json(None) is None
+    assert parse_json("") is None
diff --git a/aleksis/core/tests/views/test_account.py b/aleksis/core/tests/views/test_account.py
index da1c2b90ef0f867b7239817b7ce60fc1cc2478b0..28686eabf8290a7a72444d8fe5f4b71bb74f4626 100644
--- a/aleksis/core/tests/views/test_account.py
+++ b/aleksis/core/tests/views/test_account.py
@@ -39,4 +39,4 @@ def test_logout(client, django_user_model):
     response = client.get(reverse("logout"), follow=True)
 
     assert response.status_code == 200
-    assert "Enter your credentials." in response.content.decode("utf-8")
+    assert "Please login to see this page." in response.content.decode("utf-8")
diff --git a/aleksis/core/urls.py b/aleksis/core/urls.py
index 4d9f84de5f69e21f70a880c6075b6df3eba46df4..373d35a8b22998f81be159292ba2ea03f64fabcf 100644
--- a/aleksis/core/urls.py
+++ b/aleksis/core/urls.py
@@ -28,7 +28,23 @@ urlpatterns = [
     path("person/<int:id_>", views.person, name="person_by_id"),
     path("person/<int:id_>/edit", views.edit_person, name="edit_person_by_id"),
     path("groups", views.groups, name="groups"),
+    path("groups/additional_fields", views.additional_fields, name="additional_fields"),
     path("groups/child_groups/", views.groups_child_groups, name="groups_child_groups"),
+    path(
+        "groups/additional_field/<int:id_>/edit",
+        views.edit_additional_field,
+        name="edit_additional_field_by_id",
+    ),
+    path(
+        "groups/additional_field/create",
+        views.edit_additional_field,
+        name="create_additional_field",
+    ),
+    path(
+        "groups/additional_field/<int:id_>/delete",
+        views.delete_additional_field,
+        name="delete_additional_field_by_id",
+    ),
     path("group/create", views.edit_group, name="create_group"),
     path("group/<int:id_>", views.group, name="group_by_id"),
     path("group/<int:id_>/edit", views.edit_group, name="edit_group_by_id"),
diff --git a/aleksis/core/util/apps.py b/aleksis/core/util/apps.py
index 1500537334ebf5d339955c55a480776053b01d03..d263feb9a2ba0c9e36d821a56e12f77726a7a672 100644
--- a/aleksis/core/util/apps.py
+++ b/aleksis/core/util/apps.py
@@ -1,4 +1,3 @@
-from importlib import import_module
 from typing import Any, List, Optional, Sequence, Tuple
 
 import django.apps
@@ -19,15 +18,6 @@ class AppConfig(django.apps.AppConfig):
     def ready(self):
         super().ready()
 
-        # Run model extension code
-        try:
-            import_module(
-                ".".join(self.__class__.__module__.split(".")[:-1] + ["model_extensions"])
-            )
-        except ImportError:
-            # ImportErrors are non-fatal because model extensions are optional.
-            pass
-
         # Register default listeners
         pre_migrate.connect(self.pre_migrate, sender=self)
         post_migrate.connect(self.post_migrate, sender=self)
@@ -38,13 +28,6 @@ class AppConfig(django.apps.AppConfig):
         # Getting an app ready means it should look at its config once
         self.preference_updated(self)
 
-        # Register system checks of this app
-        try:
-            import_module(".".join(self.__class__.__module__.split(".")[:-1] + ["checks"]))
-        except ImportError:
-            # ImportErrors are non-fatal because checks are optional.
-            pass
-
     @classmethod
     def get_name(cls):
         """Get name of application package."""
diff --git a/aleksis/core/util/notifications.py b/aleksis/core/util/notifications.py
index c9cbcb9f76700d3e26f9c448a5e02c5e103a4936..38d27057033752d6ae7cdefce9c53b4728447b80 100644
--- a/aleksis/core/util/notifications.py
+++ b/aleksis/core/util/notifications.py
@@ -73,13 +73,13 @@ def send_notification(notification: Union[int, "Notification"], resend: bool = F
     If resend is passed as True, the notification is sent even if it was
     previously marked as sent.
     """
-    channels = lazy_preference("notification", "channels")
-
     if isinstance(notification, int):
-        notification = apps.get_model("core", "Notification")
-        notification_ = notification.objects.get(pk=notification)
+        Notification = apps.get_model("core", "Notification")
+        notification = Notification.objects.get(pk=notification)
+
+    channels = [notification.recipient.preferences["notification__channels"]]
 
-    if resend or not notification_.sent:
+    if resend or not notification.sent:
         for channel in channels:
             name, check, send = _CHANNELS_MAP[channel]
             if check():
diff --git a/aleksis/core/util/predicates.py b/aleksis/core/util/predicates.py
index 0e452ddb0cb560ba4edbe174c560ade56c6261ed..975273d7d934ae41df8ecefb494b121ed69b2a94 100644
--- a/aleksis/core/util/predicates.py
+++ b/aleksis/core/util/predicates.py
@@ -98,6 +98,12 @@ def is_group_owner(user: User, group: Group) -> bool:
     return group.owners.filter(owners=user.person).exists()
 
 
+@predicate
+def is_group_member(user: User, group: Group) -> bool:
+    """Predicate which checks if the user is a member of the provided group."""
+    return user.person in group.members.all()
+
+
 @predicate
 def is_notification_recipient(user: User, obj: Model) -> bool:
     """Check if is a notification recipient.
diff --git a/aleksis/core/views.py b/aleksis/core/views.py
index 81dc709017dac791c2e239379cc9cf262bfe3e71..92f0bf2fc5d5fd863c9427f4acac9a65081c5b5f 100644
--- a/aleksis/core/views.py
+++ b/aleksis/core/views.py
@@ -1,6 +1,7 @@
 from typing import Optional
 
 from django.apps import apps
+from django.conf import settings
 from django.contrib.auth.mixins import PermissionRequiredMixin
 from django.core.exceptions import PermissionDenied
 from django.core.paginator import Paginator
@@ -20,6 +21,7 @@ from .filters import GroupFilter
 from .forms import (
     AnnouncementForm,
     ChildGroupsForm,
+    EditAdditionalFieldForm,
     EditGroupForm,
     EditGroupTypeForm,
     EditPersonForm,
@@ -28,13 +30,21 @@ from .forms import (
     PersonsAccountsFormSet,
     SitePreferenceForm,
 )
-from .models import Announcement, DashboardWidget, Group, GroupType, Notification, Person
+from .models import (
+    AdditionalField,
+    Announcement,
+    DashboardWidget,
+    Group,
+    GroupType,
+    Notification,
+    Person,
+)
 from .registries import (
     group_preferences_registry,
     person_preferences_registry,
     site_preferences_registry,
 )
-from .tables import GroupsTable, GroupTypesTable, PersonsTable
+from .tables import AdditionalFieldsTable, GroupsTable, GroupTypesTable, PersonsTable
 from .util import messages
 from .util.apps import AppConfig
 from .util.core_helpers import objectgetter_optional
@@ -67,7 +77,7 @@ def index(request: HttpRequest) -> HttpResponse:
 
 def offline(request: HttpRequest) -> HttpResponse:
     """Offline message for PWA."""
-    return render(request, "core/offline.html")
+    return render(request, "core/pages/offline.html")
 
 
 def about(request: HttpRequest) -> HttpResponse:
@@ -78,7 +88,7 @@ def about(request: HttpRequest) -> HttpResponse:
         filter(lambda a: isinstance(a, AppConfig), apps.get_app_configs())
     )
 
-    return render(request, "core/about.html", context)
+    return render(request, "core/pages/about.html", context)
 
 
 @permission_required("core.view_persons")
@@ -96,7 +106,7 @@ def persons(request: HttpRequest) -> HttpResponse:
     RequestConfig(request).configure(persons_table)
     context["persons_table"] = persons_table
 
-    return render(request, "core/persons.html", context)
+    return render(request, "core/person/list.html", context)
 
 
 @permission_required(
@@ -117,7 +127,7 @@ def person(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse:
     RequestConfig(request).configure(groups_table)
     context["groups_table"] = groups_table
 
-    return render(request, "core/person_full.html", context)
+    return render(request, "core/person/full.html", context)
 
 
 @permission_required("core.view_group", fn=objectgetter_optional(Group, None, False))
@@ -147,7 +157,7 @@ def group(request: HttpRequest, id_: int) -> HttpResponse:
     RequestConfig(request).configure(owners_table)
     context["owners_table"] = owners_table
 
-    return render(request, "core/group_full.html", context)
+    return render(request, "core/group/full.html", context)
 
 
 @permission_required("core.view_groups")
@@ -163,7 +173,7 @@ def groups(request: HttpRequest) -> HttpResponse:
     RequestConfig(request).configure(groups_table)
     context["groups_table"] = groups_table
 
-    return render(request, "core/groups.html", context)
+    return render(request, "core/group/list.html", context)
 
 
 @permission_required("core.link_persons_accounts")
@@ -183,7 +193,7 @@ def persons_accounts(request: HttpRequest) -> HttpResponse:
 
     context["persons_accounts_formset"] = persons_accounts_formset
 
-    return render(request, "core/persons_accounts.html", context)
+    return render(request, "core/person/accounts.html", context)
 
 
 @permission_required("core.assign_child_groups_to_groups")
@@ -220,7 +230,7 @@ def groups_child_groups(request: HttpRequest) -> HttpResponse:
         context["group"] = group
         context["form"] = form
 
-    return render(request, "core/groups_child_groups.html", context)
+    return render(request, "core/group/child_groups.html", context)
 
 
 @permission_required(
@@ -245,7 +255,7 @@ def edit_person(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse
 
     context["edit_person_form"] = edit_person_form
 
-    return render(request, "core/edit_person.html", context)
+    return render(request, "core/person/edit.html", context)
 
 
 def get_group_by_id(request: HttpRequest, id_: Optional[int] = None):
@@ -272,22 +282,22 @@ def edit_group(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse:
 
     if request.method == "POST":
         if edit_group_form.is_valid():
-            edit_group_form.save(commit=True)
+            group = edit_group_form.save(commit=True)
 
             messages.success(request, _("The group has been saved."))
 
-            return redirect("groups")
+            return redirect("group_by_id", group.pk)
 
     context["edit_group_form"] = edit_group_form
 
-    return render(request, "core/edit_group.html", context)
+    return render(request, "core/group/edit.html", context)
 
 
 @permission_required("core.manage_data")
 def data_management(request: HttpRequest) -> HttpResponse:
     """View with special menu for data management."""
     context = {}
-    return render(request, "core/data_management.html", context)
+    return render(request, "core/management/data_management.html", context)
 
 
 @permission_required("core.view_system_status")
@@ -295,7 +305,17 @@ def system_status(request: HttpRequest) -> HttpResponse:
     """View giving information about the system status."""
     context = {}
 
-    return render(request, "core/system_status.html", context)
+    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]
+            results = []
+            for job in job_list:
+                results.append(TaskResult.objects.filter(task_name=job).last())
+            context["tasks"] = results
+
+    return render(request, "core/pages/system_status.html", context)
 
 
 @permission_required(
@@ -447,6 +467,71 @@ def preferences(
     return render(request, "dynamic_preferences/form.html", context)
 
 
+@permission_required(
+    "core.change_additionalfield", fn=objectgetter_optional(AdditionalField, None, False)
+)
+def edit_additional_field(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse:
+    """View to edit or create a additional_field."""
+    context = {}
+
+    additional_field = objectgetter_optional(AdditionalField, None, False)(request, id_)
+    context["additional_field"] = additional_field
+
+    if id_:
+        # Edit form for existing additional_field
+        edit_additional_field_form = EditAdditionalFieldForm(
+            request.POST or None, instance=additional_field
+        )
+    else:
+        if request.user.has_perm("core.create_additionalfield"):
+            # Empty form to create a new additional_field
+            edit_additional_field_form = EditAdditionalFieldForm(request.POST or None)
+        else:
+            raise PermissionDenied()
+
+    if request.method == "POST":
+        if edit_additional_field_form.is_valid():
+            edit_additional_field_form.save(commit=True)
+
+            messages.success(request, _("The additional_field has been saved."))
+
+            return redirect("additional_fields")
+
+    context["edit_additional_field_form"] = edit_additional_field_form
+
+    return render(request, "core/additional_field/edit.html", context)
+
+
+@permission_required("core.view_additionalfield")
+def additional_fields(request: HttpRequest) -> HttpResponse:
+    """List view for listing all additional fields."""
+    context = {}
+
+    # Get all additional fields
+    additional_fields = get_objects_for_user(
+        request.user, "core.view_additionalfield", AdditionalField
+    )
+
+    # Build table
+    additional_fields_table = AdditionalFieldsTable(additional_fields)
+    RequestConfig(request).configure(additional_fields_table)
+    context["additional_fields_table"] = additional_fields_table
+
+    return render(request, "core/additional_field/list.html", context)
+
+
+@permission_required(
+    "core.delete_additionalfield", fn=objectgetter_optional(AdditionalField, None, False)
+)
+def delete_additional_field(request: HttpRequest, id_: int) -> HttpResponse:
+    """View to delete an additional field."""
+    additional_field = objectgetter_optional(AdditionalField, None, False)(request, id_)
+    additional_field.delete()
+    messages.success(request, _("The additional field has been deleted."))
+
+    return redirect("additional_fields")
+
+
 @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."""
@@ -472,7 +557,7 @@ def edit_group_type(request: HttpRequest, id_: Optional[int] = None) -> HttpResp
 
     context["edit_group_type_form"] = edit_group_type_form
 
-    return render(request, "core/edit_group_type.html", context)
+    return render(request, "core/group_type/edit.html", context)
 
 
 @permission_required("core.view_grouptype")
@@ -488,7 +573,7 @@ def group_types(request: HttpRequest) -> HttpResponse:
     RequestConfig(request).configure(group_types_table)
     context["group_types_table"] = group_types_table
 
-    return render(request, "core/group_types.html", context)
+    return render(request, "core/group_type/list.html", context)
 
 
 @permission_required("core.delete_grouptype", fn=objectgetter_optional(GroupType, None, False))
diff --git a/ci/build_dist.yml b/ci/build_dist.yml
new file mode 100644
index 0000000000000000000000000000000000000000..939cdf326decd7642edff67a62f84ff028f30d56
--- /dev/null
+++ b/ci/build_dist.yml
@@ -0,0 +1,7 @@
+build_dist:
+  stage: build
+  script:
+    - tox -e build
+  artifacts:
+    paths:
+      - dist/
diff --git a/ci/build_docker.yml b/ci/build_docker.yml
new file mode 100644
index 0000000000000000000000000000000000000000..92a1572c6ac4d1878110500f458262a6a7c5bdf4
--- /dev/null
+++ b/ci/build_docker.yml
@@ -0,0 +1,22 @@
+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
new file mode 100644
index 0000000000000000000000000000000000000000..d01e23e4f3436bc8dd4849d283038591157e9668
--- /dev/null
+++ b/ci/deploy.yml
@@ -0,0 +1,43 @@
+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
+
+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
new file mode 100644
index 0000000000000000000000000000000000000000..58891983dbeda6b9c8f49dc07e490a021a4c31dd
--- /dev/null
+++ b/ci/general.yml
@@ -0,0 +1,20 @@
+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/test.yml b/ci/test.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2ab64bc324536c8716c5cb8641d09d9c43dbf844
--- /dev/null
+++ b/ci/test.yml
@@ -0,0 +1,26 @@
+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/docs/dev/01_setup.rst b/docs/dev/01_setup.rst
index df0ffd11c21112912f0934c9f16aed909bd8cbb9..d61c21e9a074310ec599f8bcffb6824dabe64bac 100644
--- a/docs/dev/01_setup.rst
+++ b/docs/dev/01_setup.rst
@@ -28,7 +28,7 @@ Install native dependencies
 
 Some system libraries are required to install AlekSIS::
 
-  sudo apt install build-essential libpq-dev libpq5 libssl-dev python3-dev python3-pip python3-venv yarnpkg
+  sudo apt install build-essential libpq-dev libpq5 libssl-dev python3-dev python3-pip python3-venv yarnpkg gettext
 
 
 Get Poetry
diff --git a/poetry.lock b/poetry.lock
index 6a1072253c76129eec46481ec04b584724013e5b..2722fb59608ca242b559e8991b00dca23df2328f 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -12,7 +12,7 @@ description = "Low-level AMQP client for Python (fork of amqplib)."
 name = "amqp"
 optional = true
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "2.5.2"
+version = "2.6.0"
 
 [package.dependencies]
 vine = ">=1.1.3,<5.0.0a1"
@@ -23,7 +23,7 @@ description = "A small Python module for determining appropriate platform-specif
 name = "appdirs"
 optional = false
 python-versions = "*"
-version = "1.4.3"
+version = "1.4.4"
 
 [[package]]
 category = "main"
@@ -91,7 +91,7 @@ description = "Screen-scraping library"
 name = "beautifulsoup4"
 optional = false
 python-versions = "*"
-version = "4.9.0"
+version = "4.9.1"
 
 [package.dependencies]
 soupsieve = [">1.2", "<2.0"]
@@ -165,12 +165,12 @@ category = "main"
 description = "Distributed Task Queue."
 name = "celery"
 optional = true
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*,"
-version = "4.4.2"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+version = "4.4.4"
 
 [package.dependencies]
 billiard = ">=3.6.3.0,<4.0"
-kombu = ">=4.6.8,<4.7"
+kombu = ">=4.6.10,<4.7"
 pytz = ">0.0-dev"
 vine = "1.3.0"
 
@@ -187,10 +187,10 @@ arangodb = ["pyArango (>=1.3.2)"]
 auth = ["cryptography"]
 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"]
+cassandra = ["cassandra-driver (<3.21.0)"]
 consul = ["python-consul"]
 cosmosdbsql = ["pydocumentdb (2.3.2)"]
-couchbase = ["couchbase", "couchbase-cffi"]
+couchbase = ["couchbase-cffi (<3.0.0)", "couchbase (<3.0.0)"]
 couchdb = ["pycouchdb"]
 django = ["Django (>=1.11)"]
 dynamodb = ["boto3 (>=1.9.178)"]
@@ -210,7 +210,7 @@ s3 = ["boto3 (>=1.9.125)"]
 slmq = ["softlayer-messaging (>=1.0.3)"]
 solar = ["ephem"]
 sqlalchemy = ["sqlalchemy"]
-sqs = ["boto3 (>=1.9.125)", "pycurl (7.43.0.2)"]
+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)"]
@@ -222,11 +222,10 @@ description = "An app for integrating Celery with Haystack."
 name = "celery-haystack"
 optional = true
 python-versions = "*"
-version = "0.7.2"
+version = "0.10"
 
 [package.dependencies]
 django-appconf = ">=0.4.1"
-django-celery-transactions = ">=0.1.2"
 
 [[package]]
 category = "main"
@@ -308,7 +307,7 @@ description = "A high-level Python Web framework that encourages rapid developme
 name = "django"
 optional = false
 python-versions = ">=3.6"
-version = "3.0.6"
+version = "3.0.7"
 
 [package.dependencies]
 asgiref = ">=3.2,<4.0"
@@ -347,7 +346,7 @@ description = "Django LDAP authentication backend."
 name = "django-auth-ldap"
 optional = true
 python-versions = ">=3.5"
-version = "2.1.1"
+version = "2.2.0"
 
 [package.dependencies]
 Django = ">=1.11"
@@ -381,8 +380,8 @@ category = "main"
 description = "Django utility for a memoization decorator that uses the Django cache framework."
 name = "django-cache-memoize"
 optional = false
-python-versions = "*"
-version = "0.1.6"
+python-versions = ">=3.4"
+version = "0.1.7"
 
 [package.extras]
 dev = ["flake8", "tox", "twine", "therapist", "black"]
@@ -425,18 +424,6 @@ version = "1.2.1"
 [package.dependencies]
 celery = ">=4.4,<5.0"
 
-[[package]]
-category = "main"
-description = "Django transaction support for Celery tasks."
-name = "django-celery-transactions"
-optional = true
-python-versions = "*"
-version = "0.3.6"
-
-[package.dependencies]
-Django = ">=1.2.4"
-celery = ">=2.2.7"
-
 [[package]]
 category = "main"
 description = "Django admin CKEditor integration."
@@ -500,7 +487,7 @@ description = "Yet another Django audit log app, hopefully the simplest one."
 name = "django-easy-audit"
 optional = false
 python-versions = "*"
-version = "1.2.2"
+version = "1.2.3a4"
 
 [package.dependencies]
 beautifulsoup4 = "*"
@@ -648,7 +635,7 @@ description = "Material design for django forms and admin"
 name = "django-material"
 optional = false
 python-versions = "*"
-version = "1.6.3"
+version = "1.6.7"
 
 [package.dependencies]
 six = "*"
@@ -678,7 +665,7 @@ description = "A pluggable framework for adding two-factor authentication to Dja
 name = "django-otp"
 optional = false
 python-versions = "*"
-version = "0.9.0"
+version = "0.9.1"
 
 [package.dependencies]
 django = ">=1.11"
@@ -732,7 +719,7 @@ description = "A Django app to include a manifest.json and Service Worker instan
 name = "django-pwa"
 optional = false
 python-versions = "*"
-version = "1.0.8"
+version = "1.0.9"
 
 [package.dependencies]
 django = ">=1.8"
@@ -777,12 +764,15 @@ description = "Select2 option fields for Django"
 name = "django-select2"
 optional = false
 python-versions = "*"
-version = "7.2.3"
+version = "7.4.2"
 
 [package.dependencies]
 django = ">=2.2"
 django-appconf = ">=0.6.0"
 
+[package.extras]
+test = ["pytest", "pytest-cov", "pytest-django", "selenium"]
+
 [[package]]
 category = "main"
 description = "Makes specified django settings visible in template rendering context."
@@ -963,21 +953,13 @@ version = "2.7"
 django = ">=1.11,<4.0"
 pillow = "*"
 
-[[package]]
-category = "dev"
-description = "Discover and load entry points from installed packages."
-name = "entrypoints"
-optional = false
-python-versions = ">=2.7"
-version = "0.3"
-
 [[package]]
 category = "main"
 description = "Faker is a Python package that generates fake data for you."
 name = "faker"
 optional = false
 python-versions = ">=3.4"
-version = "4.0.3"
+version = "4.1.0"
 
 [package.dependencies]
 python-dateutil = ">=2.4"
@@ -985,17 +967,20 @@ text-unidecode = "1.3"
 
 [[package]]
 category = "dev"
-description = "the modular source code checker: pep8, pyflakes and co"
+description = "the modular source code checker: pep8 pyflakes and co"
 name = "flake8"
 optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "3.7.9"
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
+version = "3.8.2"
 
 [package.dependencies]
-entrypoints = ">=0.3.0,<0.4.0"
 mccabe = ">=0.6.0,<0.7.0"
-pycodestyle = ">=2.5.0,<2.6.0"
-pyflakes = ">=2.1.0,<2.2.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"
@@ -1017,10 +1002,10 @@ description = "flake8 plugin to call black as a code style validator"
 name = "flake8-black"
 optional = false
 python-versions = "*"
-version = "0.1.1"
+version = "0.2.0"
 
 [package.dependencies]
-black = ">=19.3b0"
+black = "*"
 flake8 = ">=3.0.0"
 
 [[package]]
@@ -1029,7 +1014,7 @@ description = "Check for python builtins being used as variables or parameters."
 name = "flake8-builtins"
 optional = false
 python-versions = "*"
-version = "1.5.2"
+version = "1.5.3"
 
 [package.dependencies]
 flake8 = "*"
@@ -1043,7 +1028,7 @@ description = "Plugin to catch bad style specific to Django Projects"
 name = "flake8-django"
 optional = false
 python-versions = "*"
-version = "1.0.0"
+version = "1.1.1"
 
 [package.dependencies]
 flake8 = "*"
@@ -1140,7 +1125,7 @@ description = "Python Git Library"
 name = "gitpython"
 optional = false
 python-versions = ">=3.4"
-version = "3.1.2"
+version = "3.1.3"
 
 [package.dependencies]
 gitdb = ">=4.0.1,<5"
@@ -1219,10 +1204,10 @@ description = "Messaging library for Python."
 name = "kombu"
 optional = true
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "4.6.8"
+version = "4.6.10"
 
 [package.dependencies]
-amqp = ">=2.5.2,<2.6"
+amqp = ">=2.6.0,<2.7"
 
 [package.dependencies.importlib-metadata]
 python = "<3.8"
@@ -1288,7 +1273,7 @@ description = "More routines for operating on iterables, beyond itertools"
 name = "more-itertools"
 optional = false
 python-versions = ">=3.5"
-version = "8.2.0"
+version = "8.3.0"
 
 [[package]]
 category = "dev"
@@ -1320,7 +1305,7 @@ description = "Core utilities for Python packages"
 name = "packaging"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "20.3"
+version = "20.4"
 
 [package.dependencies]
 pyparsing = ">=2.0.2"
@@ -1367,7 +1352,7 @@ description = "Python version of Google's common library for parsing, formatting
 name = "phonenumbers"
 optional = false
 python-versions = "*"
-version = "8.12.2"
+version = "8.12.4"
 
 [[package]]
 category = "main"
@@ -1434,7 +1419,7 @@ description = "Python style guide checker"
 name = "pycodestyle"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "2.5.0"
+version = "2.6.0"
 
 [[package]]
 category = "main"
@@ -1461,7 +1446,7 @@ description = "passive checker of Python programs"
 name = "pyflakes"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "2.1.1"
+version = "2.2.0"
 
 [[package]]
 category = "dev"
@@ -1498,7 +1483,7 @@ description = "pytest: simple powerful testing with Python"
 name = "pytest"
 optional = false
 python-versions = ">=3.5"
-version = "5.4.1"
+version = "5.4.3"
 
 [package.dependencies]
 atomicwrites = ">=1.0"
@@ -1523,15 +1508,15 @@ category = "dev"
 description = "Pytest plugin for measuring coverage."
 name = "pytest-cov"
 optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "2.8.1"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+version = "2.9.0"
 
 [package.dependencies]
 coverage = ">=4.4"
 pytest = ">=3.6"
 
 [package.extras]
-testing = ["fields", "hunter", "process-tests (2.0.2)", "six", "virtualenv"]
+testing = ["fields", "hunter", "process-tests (2.0.2)", "six", "pytest-xdist", "virtualenv"]
 
 [[package]]
 category = "dev"
@@ -1590,7 +1575,7 @@ description = "Python Crontab API"
 name = "python-crontab"
 optional = true
 python-versions = "*"
-version = "2.4.2"
+version = "2.5.1"
 
 [package.dependencies]
 python-dateutil = "*"
@@ -1684,7 +1669,7 @@ description = "Python client for Redis key-value store"
 name = "redis"
 optional = true
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "3.5.0"
+version = "3.5.3"
 
 [package.extras]
 hiredis = ["hiredis (>=0.1.3)"]
@@ -1695,7 +1680,7 @@ description = "Alternative regular expression module, to replace re."
 name = "regex"
 optional = false
 python-versions = "*"
-version = "2020.4.4"
+version = "2020.5.14"
 
 [[package]]
 category = "main"
@@ -1721,7 +1706,7 @@ description = "reStructuredText linter"
 name = "restructuredtext-lint"
 optional = false
 python-versions = "*"
-version = "1.3.0"
+version = "1.3.1"
 
 [package.dependencies]
 docutils = ">=0.11,<1.0"
@@ -1774,7 +1759,7 @@ description = "Python 2 and 3 compatibility utilities"
 name = "six"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
-version = "1.14.0"
+version = "1.15.0"
 
 [[package]]
 category = "dev"
@@ -1798,7 +1783,7 @@ description = "A modern CSS selector implementation for Beautiful Soup."
 name = "soupsieve"
 optional = false
 python-versions = "*"
-version = "1.9.5"
+version = "1.9.6"
 
 [[package]]
 category = "main"
@@ -1806,7 +1791,7 @@ description = "A simple tool/library for working with SPDX license definitions."
 name = "spdx-license-list"
 optional = false
 python-versions = "*"
-version = "0.4.0"
+version = "0.5.0"
 
 [[package]]
 category = "dev"
@@ -1814,7 +1799,7 @@ description = "Python documentation generator"
 name = "sphinx"
 optional = false
 python-versions = ">=3.5"
-version = "3.0.3"
+version = "3.0.4"
 
 [package.dependencies]
 Jinja2 = ">=2.3"
@@ -2015,7 +2000,7 @@ description = "Python Library for Tom's Obvious, Minimal Language"
 name = "toml"
 optional = false
 python-versions = "*"
-version = "0.10.0"
+version = "0.10.1"
 
 [[package]]
 category = "main"
@@ -2023,7 +2008,7 @@ description = "Fast, Extensible Progress Meter"
 name = "tqdm"
 optional = false
 python-versions = ">=2.6, !=3.0.*, !=3.1.*"
-version = "4.46.0"
+version = "4.46.1"
 
 [package.extras]
 dev = ["py-make (>=0.1.0)", "twine", "argopt", "pydoc-markdown"]
@@ -2034,7 +2019,7 @@ description = "Twilio API client and TwiML generator"
 name = "twilio"
 optional = false
 python-versions = "*"
-version = "6.39.0"
+version = "6.41.0"
 
 [package.dependencies]
 PyJWT = ">=1.4.2"
@@ -2084,11 +2069,11 @@ version = "1.3.0"
 
 [[package]]
 category = "dev"
-description = "Measures number of Terminal column cells of wide-character codes"
+description = "Measures the displayed width of unicode strings in a terminal"
 name = "wcwidth"
 optional = false
 python-versions = "*"
-version = "0.1.9"
+version = "0.2.3"
 
 [[package]]
 category = "main"
@@ -2128,7 +2113,7 @@ celery = ["Celery", "django-celery-results", "django-celery-beat", "django-celer
 ldap = ["django-auth-ldap"]
 
 [metadata]
-content-hash = "8123606bea67a68e0df620d9760ab06f724c70eea16a651be634a832a3fb3fe2"
+content-hash = "3e85d3bfff56719272c13ad591ca4ccd7a2b72324a33c2192be383536b66b5f8"
 python-versions = "^3.7"
 
 [metadata.files]
@@ -2137,12 +2122,12 @@ alabaster = [
     {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"},
 ]
 amqp = [
-    {file = "amqp-2.5.2-py2.py3-none-any.whl", hash = "sha256:6e649ca13a7df3faacdc8bbb280aa9a6602d22fd9d545336077e573a1f4ff3b8"},
-    {file = "amqp-2.5.2.tar.gz", hash = "sha256:77f1aef9410698d20eaeac5b73a87817365f457a507d82edf292e12cbb83b08d"},
+    {file = "amqp-2.6.0-py2.py3-none-any.whl", hash = "sha256:bb68f8d2bced8f93ccfd07d96c689b716b3227720add971be980accfc2952139"},
+    {file = "amqp-2.6.0.tar.gz", hash = "sha256:24dbaff8ce4f30566bb88976b398e8c4e77637171af3af6f1b9650f48890e60b"},
 ]
 appdirs = [
-    {file = "appdirs-1.4.3-py2.py3-none-any.whl", hash = "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"},
-    {file = "appdirs-1.4.3.tar.gz", hash = "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92"},
+    {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.7-py2.py3-none-any.whl", hash = "sha256:9ca8b952a0a9afa61d30aa6d3d9b570bb3fd6bafcf7ec9e6bed43b936133db1c"},
@@ -2165,9 +2150,9 @@ bandit = [
     {file = "bandit-1.6.2.tar.gz", hash = "sha256:41e75315853507aa145d62a78a2a6c5e3240fe14ee7c601459d0df9418196065"},
 ]
 beautifulsoup4 = [
-    {file = "beautifulsoup4-4.9.0-py2-none-any.whl", hash = "sha256:a4bbe77fd30670455c5296242967a123ec28c37e9702a8a81bd2f20a4baf0368"},
-    {file = "beautifulsoup4-4.9.0-py3-none-any.whl", hash = "sha256:d4e96ac9b0c3a6d3f0caae2e4124e6055c5dcafde8e2f831ff194c104f0775a0"},
-    {file = "beautifulsoup4-4.9.0.tar.gz", hash = "sha256:594ca51a10d2b3443cbac41214e12dbb2a1cd57e1a7344659849e2e20ba6a8d8"},
+    {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"},
 ]
 billiard = [
     {file = "billiard-3.6.3.0-py3-none-any.whl", hash = "sha256:bff575450859a6e0fbc2f9877d9b715b0bbc07c3565bb7ed2280526a0cdf5ede"},
@@ -2190,11 +2175,12 @@ calendarweek = [
     {file = "calendarweek-0.4.5.tar.gz", hash = "sha256:5b1788ca435022f9348fc81a718974e51dd85d080f9aa3dad717df70a1bc6e1f"},
 ]
 celery = [
-    {file = "celery-4.4.2-py2.py3-none-any.whl", hash = "sha256:5b4b37e276033fe47575107a2775469f0b721646a08c96ec2c61531e4fe45f2a"},
-    {file = "celery-4.4.2.tar.gz", hash = "sha256:108a0bf9018a871620936c33a3ee9f6336a89f8ef0a0f567a9001f4aa361415f"},
+    {file = "celery-4.4.4-py2.py3-none-any.whl", hash = "sha256:9ae2e73b93cc7d6b48b56aaf49a68c91752d0ffd7dfdcc47f842ca79a6f13eae"},
+    {file = "celery-4.4.4.tar.gz", hash = "sha256:c2037b6a8463da43b19969a0fc13f9023ceca6352b4dd51be01c66fbbb13647e"},
 ]
 celery-haystack = [
-    {file = "celery-haystack-0.7.2.tar.gz", hash = "sha256:5ee3dfb9d5c1b0cf13b5a8e38cf3cbbde9009fe4470042f3087485743b6971fb"},
+    {file = "celery-haystack-0.10.tar.gz", hash = "sha256:b6e2a3c70071bef0838ca1a7d9f14fae6c2ecf385704092e59b82147a1ee552e"},
+    {file = "celery_haystack-0.10-py2.py3-none-any.whl", hash = "sha256:ec1f39050661e033f554de99cb9393c2e94427667ff5401f16393b2a68f888fc"},
 ]
 certifi = [
     {file = "certifi-2020.4.5.1-py2.py3-none-any.whl", hash = "sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304"},
@@ -2257,8 +2243,8 @@ dj-database-url = [
     {file = "dj_database_url-0.5.0-py2.py3-none-any.whl", hash = "sha256:851785365761ebe4994a921b433062309eb882fedd318e1b0fcecc607ed02da9"},
 ]
 django = [
-    {file = "Django-3.0.6-py3-none-any.whl", hash = "sha256:051ba55d42daa3eeda3944a8e4df2bc96d4c62f94316dea217248a22563c3621"},
-    {file = "Django-3.0.6.tar.gz", hash = "sha256:9aaa6a09678e1b8f0d98a948c56482eac3e3dd2ddbfb8de70a868135ef3b5e01"},
+    {file = "Django-3.0.7-py3-none-any.whl", hash = "sha256:e1630333248c9b3d4e38f02093a26f1e07b271ca896d73097457996e0fae12e8"},
+    {file = "Django-3.0.7.tar.gz", hash = "sha256:5052b34b34b3425233c682e0e11d658fd6efd587d11335a0203d827224ada8f2"},
 ]
 django-any-js = [
     {file = "django-any-js-1.0.3.post0.tar.gz", hash = "sha256:1da88b44b861b0f54f6b8ea0eb4c7c4fa1a5772e9a4320532cd4e0871a4e23f7"},
@@ -2268,8 +2254,8 @@ django-appconf = [
     {file = "django_appconf-1.0.4-py2.py3-none-any.whl", hash = "sha256:1b1d0e1069c843ebe8ae5aa48ec52403b1440402b320c3e3a206a0907e97bb06"},
 ]
 django-auth-ldap = [
-    {file = "django-auth-ldap-2.1.1.tar.gz", hash = "sha256:fabbbc35a5d28ce6a12e3f6309229d82bfe6a410391914938593e4b96ce42ec8"},
-    {file = "django_auth_ldap-2.1.1-py3-none-any.whl", hash = "sha256:43c47c8eac1d0b1f1ee70d28534c7cef33deefddff996f8fae11dd937cc65e82"},
+    {file = "django-auth-ldap-2.2.0.tar.gz", hash = "sha256:11af1773b08613339d2c3a0cec1308a4d563518f17b1719c3759994d0b4d04bf"},
+    {file = "django_auth_ldap-2.2.0-py3-none-any.whl", hash = "sha256:0ed2d88d81c39be915a9ab53b97ec0a33a3d16055518ab4c9bcffe8236d40370"},
 ]
 django-bleach = [
     {file = "django-bleach-0.6.1.tar.gz", hash = "sha256:674709c26040618aff0741ce8261fd151e5ead405bd50568c2034662d69daac3"},
@@ -2280,8 +2266,8 @@ django-bulk-update = [
     {file = "django_bulk_update-2.2.0-py2.py3-none-any.whl", hash = "sha256:49a403392ae05ea872494d74fb3dfa3515f8df5c07cc277c3dc94724c0ee6985"},
 ]
 django-cache-memoize = [
-    {file = "django-cache-memoize-0.1.6.tar.gz", hash = "sha256:7f271be70b11155929ee8a4a2b5f53c9fb46b9befa1b546caffa3298e6ac8f7d"},
-    {file = "django_cache_memoize-0.1.6-py2.py3-none-any.whl", hash = "sha256:d239e8c37734b0a70b74f94fa33b180b3b0c82c3784beb21209bb4ab64a3e6fb"},
+    {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"},
@@ -2295,9 +2281,6 @@ 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"},
 ]
-django-celery-transactions = [
-    {file = "django-celery-transactions-0.3.6.tar.gz", hash = "sha256:cdf966ec461e9ec736a7bedcf47cb219fc79ea86f2b39191cb258082dd4f4b33"},
-]
 django-ckeditor = [
     {file = "django-ckeditor-5.9.0.tar.gz", hash = "sha256:e4d112851a72c5bf8b586e1c674d34084cab16d28f2553ad15cc770d1e9639c7"},
     {file = "django_ckeditor-5.9.0-py2.py3-none-any.whl", hash = "sha256:71c3c7bb46b0cbfb9712ef64af0d2a406eab233f44ecd7c42c24bdfa39ae3bde"},
@@ -2318,8 +2301,8 @@ django-dynamic-preferences = [
     {file = "django_dynamic_preferences-1.9-py2.py3-none-any.whl", hash = "sha256:a3c84696f0459d8d6d9c43374ff3db7daa59b46670b461bb954057d08af607e1"},
 ]
 django-easy-audit = [
-    {file = "django-easy-audit-1.2.2.tar.gz", hash = "sha256:86e84910bb21928018d8f31a0eed003bf4adfde6dff345bab1bd7a4429868d0a"},
-    {file = "django_easy_audit-1.2.2-py3-none-any.whl", hash = "sha256:e9a0c88fbd802da08f84b1d4c49ae99c7cfb46b025be635f1b2ddf8abf7b3613"},
+    {file = "django-easy-audit-1.2.3a4.tar.gz", hash = "sha256:55a6512c012fcffc47bca38376d775d15d44d24e823682ea59418c4edabe8f54"},
+    {file = "django_easy_audit-1.2.3a4-py3-none-any.whl", hash = "sha256:37c90a273559ba003d691fa0c30ee5ff792b7739d13953f7e8923c954480240f"},
 ]
 django-favicon-plus-reloaded = [
     {file = "django-favicon-plus-reloaded-1.0.4.tar.gz", hash = "sha256:90c761c636a338e6e9fb1d086649d82095085f92cff816c9cf074607f28c85a5"},
@@ -2371,8 +2354,8 @@ django-maintenance-mode = [
     {file = "django_maintenance_mode-0.14.0-py2-none-any.whl", hash = "sha256:b4cc24a469ed10897826a28f05d64e6166a58d130e4940ac124ce198cd4cc778"},
 ]
 django-material = [
-    {file = "django-material-1.6.3.tar.gz", hash = "sha256:f8758afe1beabc16a3c54f5437c7fea15946b7d068eedd89c97d57a363793950"},
-    {file = "django_material-1.6.3-py2.py3-none-any.whl", hash = "sha256:502dc88c2f61f190fdc401666e83b47da00cbda98477af6ed8b7d43944ce6407"},
+    {file = "django-material-1.6.7.tar.gz", hash = "sha256:3cc68b34348634f019bf529f3e0b99b1474ab36ec9b50040f5e557b5b65add1d"},
+    {file = "django_material-1.6.7-py2.py3-none-any.whl", hash = "sha256:9da268532c92c270b512d9610c9723a07dbfea06db98434dac8aa1dd2910778f"},
 ]
 django-menu-generator = [
     {file = "django-menu-generator-1.0.4.tar.gz", hash = "sha256:ce71a5055c16933c8aff64fb36c21e5cf8b6d505733aceed1252f8b99369a378"},
@@ -2381,8 +2364,8 @@ django-middleware-global-request = [
     {file = "django-middleware-global-request-0.1.2.tar.gz", hash = "sha256:f6490759bc9f7dbde4001709554e29ca715daf847f2222914b4e47117dca9313"},
 ]
 django-otp = [
-    {file = "django-otp-0.9.0.tar.gz", hash = "sha256:f5faa95a3e85391e70e433205509fa070ed25646f15fcafd2cd2fbd987c33262"},
-    {file = "django_otp-0.9.0-py3-none-any.whl", hash = "sha256:334e2a0ece7e5d9de3263e17bd3b6aee2809d1f8d70555408d5bf8f0c33b13fb"},
+    {file = "django-otp-0.9.1.tar.gz", hash = "sha256:f456639addace8b6d1eb77f9edaada1a53dbb4d6f3c19f17c476c4e3e4beb73f"},
+    {file = "django_otp-0.9.1-py3-none-any.whl", hash = "sha256:0c67cf6f4bd6fca84027879ace9049309213b6ac81f88e954376a6b5535d96c4"},
 ]
 django-otp-yubikey = [
     {file = "django-otp-yubikey-0.5.2.tar.gz", hash = "sha256:f0b1881562fb42ee9f12c28d284cbdb90d1f0383f2d53a595373b080a19bc261"},
@@ -2397,8 +2380,8 @@ django-polymorphic = [
     {file = "django_polymorphic-2.1.2-py2.py3-none-any.whl", hash = "sha256:0a25058e95e5e99fe0beeabb8f4734effe242d7b5b77dca416fba9fd3062da6a"},
 ]
 django-pwa = [
-    {file = "django-pwa-1.0.8.tar.gz", hash = "sha256:caf9d6e2a792def272e6cb496f594a9821c4d73cb5117d33560bc7b7b82d6132"},
-    {file = "django_pwa-1.0.8-py3-none-any.whl", hash = "sha256:88a844095ec3dc38ec8edc8d1f95247eccaebefeb41484fb94c10631881b0eb7"},
+    {file = "django-pwa-1.0.9.tar.gz", hash = "sha256:c11bcb40bbbb65f9037e4ae4d7233e6fa724c4410419b257cce4b6624a9542e9"},
+    {file = "django_pwa-1.0.9-py3-none-any.whl", hash = "sha256:8706cbd84489fb63d3523d5037d2cdfd8ff177417292bd7845b0f177d3c4ed3f"},
 ]
 django-render-block = [
     {file = "django_render_block-0.6-py2.py3-none-any.whl", hash = "sha256:95c7dc9610378a10e0c4a10d8364ec7307210889afccd6a67a6aaa0fd599bd4d"},
@@ -2411,8 +2394,8 @@ django-sass-processor = [
     {file = "django-sass-processor-0.8.tar.gz", hash = "sha256:e039551994feaaba6fcf880412b25a772dd313162a34cbb4289814988cfae340"},
 ]
 django-select2 = [
-    {file = "django-select2-7.2.3.tar.gz", hash = "sha256:b4cd3e8c42bdbdc582c38c03a23bc115a90c2f86563282329d37c567d8c34c36"},
-    {file = "django_select2-7.2.3-py2.py3-none-any.whl", hash = "sha256:1482be84449c254ec944c4da0a236b00ec57445304377b783850fd95b269d1ad"},
+    {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"},
 ]
 django-settings-context-processor = [
     {file = "django-settings-context-processor-0.2.tar.gz", hash = "sha256:d37c853d69a3069f5abbf94c7f4f6fc0fac38bbd0524190cd5a250ba800e496a"},
@@ -2458,30 +2441,27 @@ dynaconf = [
 easy-thumbnails = [
     {file = "easy-thumbnails-2.7.tar.gz", hash = "sha256:e4e7a0dd4001f56bfd4058428f2c91eafe27d33ef3b8b33ac4e013b159b9ff91"},
 ]
-entrypoints = [
-    {file = "entrypoints-0.3-py2.py3-none-any.whl", hash = "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19"},
-    {file = "entrypoints-0.3.tar.gz", hash = "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"},
-]
 faker = [
-    {file = "Faker-4.0.3-py3-none-any.whl", hash = "sha256:53bf2c8a2de8af271466e7b9cc2f08ecf83c4c947981680eb61080779a0adace"},
-    {file = "Faker-4.0.3.tar.gz", hash = "sha256:7292806948ed848f1bcea1e7b963bae6f398687d1da0ea096e156fea2787f454"},
+    {file = "Faker-4.1.0-py3-none-any.whl", hash = "sha256:34ae397aef03a0a17910452f1e8430d57fa59e2d67b20e9b637218e8f7dd22b3"},
+    {file = "Faker-4.1.0.tar.gz", hash = "sha256:103c46b9701a151299c5bffe6fefcd4fb5fb04c3b5d06bee4952d36255d44ea2"},
 ]
 flake8 = [
-    {file = "flake8-3.7.9-py2.py3-none-any.whl", hash = "sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca"},
-    {file = "flake8-3.7.9.tar.gz", hash = "sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb"},
+    {file = "flake8-3.8.2-py2.py3-none-any.whl", hash = "sha256:ccaa799ef9893cebe69fdfefed76865aeaefbb94cb8545617b2298786a4de9a5"},
+    {file = "flake8-3.8.2.tar.gz", hash = "sha256:c69ac1668e434d37a2d2880b3ca9aafd54b3a10a3ac1ab101d22f29e29cf8634"},
 ]
 flake8-bandit = [
     {file = "flake8_bandit-2.1.2.tar.gz", hash = "sha256:687fc8da2e4a239b206af2e54a90093572a60d0954f3054e23690739b0b0de3b"},
 ]
 flake8-black = [
-    {file = "flake8-black-0.1.1.tar.gz", hash = "sha256:56f85aaa5a83f06a3f61e680e3b50f156b5e557ebdcb964d823d86f4c108b0c8"},
+    {file = "flake8-black-0.2.0.tar.gz", hash = "sha256:10e7ff9f81f637a9471684e5624d6a32b11cba362b38df4e20fc8f761184440b"},
 ]
 flake8-builtins = [
-    {file = "flake8-builtins-1.5.2.tar.gz", hash = "sha256:fe7be13fe51bfb06bdae6096c6488e328c822c3aa080e24b91b77116a4fbb8b0"},
-    {file = "flake8_builtins-1.5.2-py2.py3-none-any.whl", hash = "sha256:a0296d23da92a6f2494243b9f2039bfdb73f34aba20054c1b70b2a60c84745bb"},
+    {file = "flake8-builtins-1.5.3.tar.gz", hash = "sha256:09998853b2405e98e61d2ff3027c47033adbdc17f9fe44ca58443d876eb00f3b"},
+    {file = "flake8_builtins-1.5.3-py2.py3-none-any.whl", hash = "sha256:7706babee43879320376861897e5d1468e396a40b8918ed7bccf70e5f90b8687"},
 ]
 flake8-django = [
-    {file = "flake8-django-1.0.0.tar.gz", hash = "sha256:2c9e4b8bdcad3084d3a335a0e305cbf66cd9a2af937d66c50c859c1f966036b2"},
+    {file = "flake8-django-1.1.1.tar.gz", hash = "sha256:fb4e8f669d3cf44297bb6e1c5d0a358ab0aba373cd4c69268cf2798de6bcbd9b"},
+    {file = "flake8_django-1.1.1-py3-none-any.whl", hash = "sha256:c71da0e61b6119dae91cbffdbdb00f1d6ebe3f5d0c43f5bf136929997ab0b72d"},
 ]
 flake8-docstrings = [
     {file = "flake8-docstrings-1.5.0.tar.gz", hash = "sha256:3d5a31c7ec6b7367ea6506a87ec293b94a0a46c0bce2bb4975b7f1d09b6f3717"},
@@ -2511,8 +2491,8 @@ gitdb = [
     {file = "gitdb-4.0.5.tar.gz", hash = "sha256:c9e1f2d0db7ddb9a704c2a0217be31214e91a4fe1dea1efad19ae42ba0c285c9"},
 ]
 gitpython = [
-    {file = "GitPython-3.1.2-py3-none-any.whl", hash = "sha256:da3b2cf819974789da34f95ac218ef99f515a928685db141327c09b73dd69c09"},
-    {file = "GitPython-3.1.2.tar.gz", hash = "sha256:864a47472548f3ba716ca202e034c1900f197c0fb3a08f641c20c3cafd15ed94"},
+    {file = "GitPython-3.1.3-py3-none-any.whl", hash = "sha256:ef1d60b01b5ce0040ad3ec20bc64f783362d41fa0822a2742d3586e1f49bb8ac"},
+    {file = "GitPython-3.1.3.tar.gz", hash = "sha256:e107af4d873daed64648b4f4beb89f89f0cfbe3ef558fc7821ed2331c2f8da1a"},
 ]
 html2text = [
     {file = "html2text-2020.1.16-py3-none-any.whl", hash = "sha256:c7c629882da0cf377d66f073329ccf34a12ed2adf0169b9285ae4e63ef54c82b"},
@@ -2539,8 +2519,8 @@ jinja2 = [
     {file = "Jinja2-2.11.2.tar.gz", hash = "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0"},
 ]
 kombu = [
-    {file = "kombu-4.6.8-py2.py3-none-any.whl", hash = "sha256:598e7e749d6ab54f646b74b2d2df67755dee13894f73ab02a2a9feb8870c7cb2"},
-    {file = "kombu-4.6.8.tar.gz", hash = "sha256:2d1cda774126a044d91a7ff5fa6d09edf99f46924ab332a810760fe6740e9b76"},
+    {file = "kombu-4.6.10-py2.py3-none-any.whl", hash = "sha256:dc282bb277197d723bccda1a9ba30a27a28c9672d0ab93e9e51bb05a37bd29c3"},
+    {file = "kombu-4.6.10.tar.gz", hash = "sha256:437b9cdea193cc2ed0b8044c85fd0f126bb3615ca2f4d4a35b39de7cacfa3c1a"},
 ]
 libsass = [
     {file = "libsass-0.20.0-cp27-cp27m-win32.whl", hash = "sha256:98f6dee9850b29e62977a963e3beb3cfeb98b128a267d59d2c3d675e298c8d57"},
@@ -2599,8 +2579,8 @@ mccabe = [
     {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
 ]
 more-itertools = [
-    {file = "more-itertools-8.2.0.tar.gz", hash = "sha256:b1ddb932186d8a6ac451e1d95844b382f55e12686d51ca0c68b6f61f2ab7a507"},
-    {file = "more_itertools-8.2.0-py3-none-any.whl", hash = "sha256:5dd8bcf33e5f9513ffa06d5ad33d78f31e1931ac9a18f33d37e77a180d393a7c"},
+    {file = "more-itertools-8.3.0.tar.gz", hash = "sha256:558bb897a2232f5e4f8e2399089e35aecb746e1f9191b6584a151647e89267be"},
+    {file = "more_itertools-8.3.0-py3-none-any.whl", hash = "sha256:7818f596b1e87be009031c7653d01acc46ed422e6656b394b0f765ce66ed4982"},
 ]
 mypy = [
     {file = "mypy-0.770-cp35-cp35m-macosx_10_6_x86_64.whl", hash = "sha256:a34b577cdf6313bf24755f7a0e3f3c326d5c1f4fe7422d1d06498eb25ad0c600"},
@@ -2623,8 +2603,8 @@ mypy-extensions = [
     {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
 ]
 packaging = [
-    {file = "packaging-20.3-py2.py3-none-any.whl", hash = "sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752"},
-    {file = "packaging-20.3.tar.gz", hash = "sha256:3c292b474fda1671ec57d46d739d072bfd495a4f51ad01a055121d81e952b7a3"},
+    {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"},
+    {file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"},
 ]
 pathspec = [
     {file = "pathspec-0.8.0-py2.py3-none-any.whl", hash = "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0"},
@@ -2642,8 +2622,8 @@ pg8000 = [
     {file = "pg8000-1.15.2.tar.gz", hash = "sha256:eb42ba62fbc048c91d5cf1ac729e0ea4ee329cc526bddafed4e7a8aa6b57fbbb"},
 ]
 phonenumbers = [
-    {file = "phonenumbers-8.12.2-py2.py3-none-any.whl", hash = "sha256:eedbace07295109ce98b13b9bd1ac22dd43c1e90a3f0854c557c2298493fc731"},
-    {file = "phonenumbers-8.12.2.tar.gz", hash = "sha256:61adadab01adaac571b04ddbe50f981c488ef00cfd51eef7e040ef4765871b00"},
+    {file = "phonenumbers-8.12.4-py2.py3-none-any.whl", hash = "sha256:c6c43d6459aac85b646d6b7a7ab79b3b629eb168f0e9b851b331e2e5872bbd01"},
+    {file = "phonenumbers-8.12.4.tar.gz", hash = "sha256:46c5997fe076026aa2d4b66d0c53eea4babae2e808e8a5f39c09e2dfa6612d08"},
 ]
 pillow = [
     {file = "Pillow-7.1.2-cp35-cp35m-macosx_10_10_intel.whl", hash = "sha256:ae2b270f9a0b8822b98655cb3a59cdb1bd54a34807c6c56b76dd2e786c3b7db3"},
@@ -2724,8 +2704,8 @@ pyasn1-modules = [
     {file = "pyasn1_modules-0.2.8-py3.7.egg", hash = "sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd"},
 ]
 pycodestyle = [
-    {file = "pycodestyle-2.5.0-py2.py3-none-any.whl", hash = "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56"},
-    {file = "pycodestyle-2.5.0.tar.gz", hash = "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"},
+    {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"},
+    {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"},
 ]
 pycryptodome = [
     {file = "pycryptodome-3.9.7-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:0e10f352ccbbcb5bb2dc4ecaf106564e65702a717d72ab260f9ac4c19753cfc2"},
@@ -2764,8 +2744,8 @@ pydocstyle = [
     {file = "pydocstyle-5.0.2.tar.gz", hash = "sha256:f4f5d210610c2d153fae39093d44224c17429e2ad7da12a8b419aba5c2f614b5"},
 ]
 pyflakes = [
-    {file = "pyflakes-2.1.1-py2.py3-none-any.whl", hash = "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0"},
-    {file = "pyflakes-2.1.1.tar.gz", hash = "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"},
+    {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"},
+    {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"},
 ]
 pygments = [
     {file = "Pygments-2.6.1-py3-none-any.whl", hash = "sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324"},
@@ -2780,12 +2760,12 @@ pyparsing = [
     {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
 ]
 pytest = [
-    {file = "pytest-5.4.1-py3-none-any.whl", hash = "sha256:0e5b30f5cb04e887b91b1ee519fa3d89049595f428c1db76e73bd7f17b09b172"},
-    {file = "pytest-5.4.1.tar.gz", hash = "sha256:84dde37075b8805f3d1f392cc47e38a0e59518fb46a431cfdaf7cf1ce805f970"},
+    {file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"},
+    {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"},
 ]
 pytest-cov = [
-    {file = "pytest-cov-2.8.1.tar.gz", hash = "sha256:cc6742d8bac45070217169f5f72ceee1e0e55b0221f54bcf24845972d3a47f2b"},
-    {file = "pytest_cov-2.8.1-py2.py3-none-any.whl", hash = "sha256:cdbdef4f870408ebdbfeb44e63e07eb18bb4619fae852f6e760645fa36172626"},
+    {file = "pytest-cov-2.9.0.tar.gz", hash = "sha256:b6a814b8ed6247bd81ff47f038511b57fe1ce7f4cc25b9106f1a4b106f1d9322"},
+    {file = "pytest_cov-2.9.0-py2.py3-none-any.whl", hash = "sha256:c87dfd8465d865655a8213859f1b4749b43448b5fae465cb981e16d52a811424"},
 ]
 pytest-django = [
     {file = "pytest-django-3.9.0.tar.gz", hash = "sha256:664e5f42242e5e182519388f01b9f25d824a9feb7cd17d8f863c8d776f38baf9"},
@@ -2803,7 +2783,7 @@ python-box = [
     {file = "python_box-3.4.6-py2.py3-none-any.whl", hash = "sha256:a71d3dc9dbaa34c8597d3517c89a8041bd62fa875f23c0f3dad55e1958e3ce10"},
 ]
 python-crontab = [
-    {file = "python-crontab-2.4.2.tar.gz", hash = "sha256:ed9583cff430715d9560cbad28309429ac13f3fdb13051ac2288050e05abc392"},
+    {file = "python-crontab-2.5.1.tar.gz", hash = "sha256:4bbe7e720753a132ca4ca9d4094915f40e9d9dc8a807a4564007651018ce8c31"},
 ]
 python-dateutil = [
     {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"},
@@ -2842,38 +2822,38 @@ qrcode = [
     {file = "qrcode-6.1.tar.gz", hash = "sha256:505253854f607f2abf4d16092c61d4e9d511a3b4392e60bff957a68592b04369"},
 ]
 redis = [
-    {file = "redis-3.5.0-py2.py3-none-any.whl", hash = "sha256:174101a3ce04560d716616290bb40e0a2af45d5844c8bd474c23fc5c52e7a46a"},
-    {file = "redis-3.5.0.tar.gz", hash = "sha256:7378105cd8ea20c4edc49f028581e830c01ad5f00be851def0f4bc616a83cd89"},
+    {file = "redis-3.5.3-py2.py3-none-any.whl", hash = "sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24"},
+    {file = "redis-3.5.3.tar.gz", hash = "sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2"},
 ]
 regex = [
-    {file = "regex-2020.4.4-cp27-cp27m-win32.whl", hash = "sha256:90742c6ff121a9c5b261b9b215cb476eea97df98ea82037ec8ac95d1be7a034f"},
-    {file = "regex-2020.4.4-cp27-cp27m-win_amd64.whl", hash = "sha256:24f4f4062eb16c5bbfff6a22312e8eab92c2c99c51a02e39b4eae54ce8255cd1"},
-    {file = "regex-2020.4.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:08119f707f0ebf2da60d2f24c2f39ca616277bb67ef6c92b72cbf90cbe3a556b"},
-    {file = "regex-2020.4.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:c9423a150d3a4fc0f3f2aae897a59919acd293f4cb397429b120a5fcd96ea3db"},
-    {file = "regex-2020.4.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:c087bff162158536387c53647411db09b6ee3f9603c334c90943e97b1052a156"},
-    {file = "regex-2020.4.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:1cbe0fa0b7f673400eb29e9ef41d4f53638f65f9a2143854de6b1ce2899185c3"},
-    {file = "regex-2020.4.4-cp36-cp36m-win32.whl", hash = "sha256:0ce9537396d8f556bcfc317c65b6a0705320701e5ce511f05fc04421ba05b8a8"},
-    {file = "regex-2020.4.4-cp36-cp36m-win_amd64.whl", hash = "sha256:7e1037073b1b7053ee74c3c6c0ada80f3501ec29d5f46e42669378eae6d4405a"},
-    {file = "regex-2020.4.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:4385f12aa289d79419fede43f979e372f527892ac44a541b5446617e4406c468"},
-    {file = "regex-2020.4.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:a58dd45cb865be0ce1d5ecc4cfc85cd8c6867bea66733623e54bd95131f473b6"},
-    {file = "regex-2020.4.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:ccccdd84912875e34c5ad2d06e1989d890d43af6c2242c6fcfa51556997af6cd"},
-    {file = "regex-2020.4.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:ea4adf02d23b437684cd388d557bf76e3afa72f7fed5bbc013482cc00c816948"},
-    {file = "regex-2020.4.4-cp37-cp37m-win32.whl", hash = "sha256:2294f8b70e058a2553cd009df003a20802ef75b3c629506be20687df0908177e"},
-    {file = "regex-2020.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:e91ba11da11cf770f389e47c3f5c30473e6d85e06d7fd9dcba0017d2867aab4a"},
-    {file = "regex-2020.4.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:5635cd1ed0a12b4c42cce18a8d2fb53ff13ff537f09de5fd791e97de27b6400e"},
-    {file = "regex-2020.4.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:23069d9c07e115537f37270d1d5faea3e0bdded8279081c4d4d607a2ad393683"},
-    {file = "regex-2020.4.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:c162a21e0da33eb3d31a3ac17a51db5e634fc347f650d271f0305d96601dc15b"},
-    {file = "regex-2020.4.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:fb95debbd1a824b2c4376932f2216cc186912e389bdb0e27147778cf6acb3f89"},
-    {file = "regex-2020.4.4-cp38-cp38-win32.whl", hash = "sha256:2a3bf8b48f8e37c3a40bb3f854bf0121c194e69a650b209628d951190b862de3"},
-    {file = "regex-2020.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:5bfed051dbff32fd8945eccca70f5e22b55e4148d2a8a45141a3b053d6455ae3"},
-    {file = "regex-2020.4.4.tar.gz", hash = "sha256:295badf61a51add2d428a46b8580309c520d8b26e769868b922750cf3ce67142"},
+    {file = "regex-2020.5.14-cp27-cp27m-win32.whl", hash = "sha256:e565569fc28e3ba3e475ec344d87ed3cd8ba2d575335359749298a0899fe122e"},
+    {file = "regex-2020.5.14-cp27-cp27m-win_amd64.whl", hash = "sha256:d466967ac8e45244b9dfe302bbe5e3337f8dc4dec8d7d10f5e950d83b140d33a"},
+    {file = "regex-2020.5.14-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:27ff7325b297fb6e5ebb70d10437592433601c423f5acf86e5bc1ee2919b9561"},
+    {file = "regex-2020.5.14-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ea55b80eb0d1c3f1d8d784264a6764f931e172480a2f1868f2536444c5f01e01"},
+    {file = "regex-2020.5.14-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:c9bce6e006fbe771a02bda468ec40ffccbf954803b470a0345ad39c603402577"},
+    {file = "regex-2020.5.14-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:d881c2e657c51d89f02ae4c21d9adbef76b8325fe4d5cf0e9ad62f850f3a98fd"},
+    {file = "regex-2020.5.14-cp36-cp36m-win32.whl", hash = "sha256:99568f00f7bf820c620f01721485cad230f3fb28f57d8fbf4a7967ec2e446994"},
+    {file = "regex-2020.5.14-cp36-cp36m-win_amd64.whl", hash = "sha256:70c14743320a68c5dac7fc5a0f685be63bc2024b062fe2aaccc4acc3d01b14a1"},
+    {file = "regex-2020.5.14-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:a7c37f048ec3920783abab99f8f4036561a174f1314302ccfa4e9ad31cb00eb4"},
+    {file = "regex-2020.5.14-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:89d76ce33d3266173f5be80bd4efcbd5196cafc34100fdab814f9b228dee0fa4"},
+    {file = "regex-2020.5.14-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:51f17abbe973c7673a61863516bdc9c0ef467407a940f39501e786a07406699c"},
+    {file = "regex-2020.5.14-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:ce5cc53aa9fbbf6712e92c7cf268274eaff30f6bd12a0754e8133d85a8fb0f5f"},
+    {file = "regex-2020.5.14-cp37-cp37m-win32.whl", hash = "sha256:8044d1c085d49673aadb3d7dc20ef5cb5b030c7a4fa253a593dda2eab3059929"},
+    {file = "regex-2020.5.14-cp37-cp37m-win_amd64.whl", hash = "sha256:c2062c7d470751b648f1cacc3f54460aebfc261285f14bc6da49c6943bd48bdd"},
+    {file = "regex-2020.5.14-cp38-cp38-manylinux1_i686.whl", hash = "sha256:329ba35d711e3428db6b45a53b1b13a0a8ba07cbbcf10bbed291a7da45f106c3"},
+    {file = "regex-2020.5.14-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:579ea215c81d18da550b62ff97ee187b99f1b135fd894a13451e00986a080cad"},
+    {file = "regex-2020.5.14-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:3a9394197664e35566242686d84dfd264c07b20f93514e2e09d3c2b3ffdf78fe"},
+    {file = "regex-2020.5.14-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ce367d21f33e23a84fb83a641b3834dd7dd8e9318ad8ff677fbfae5915a239f7"},
+    {file = "regex-2020.5.14-cp38-cp38-win32.whl", hash = "sha256:1386e75c9d1574f6aa2e4eb5355374c8e55f9aac97e224a8a5a6abded0f9c927"},
+    {file = "regex-2020.5.14-cp38-cp38-win_amd64.whl", hash = "sha256:7e61be8a2900897803c293247ef87366d5df86bf701083b6c43119c7c6c99108"},
+    {file = "regex-2020.5.14.tar.gz", hash = "sha256:ce450ffbfec93821ab1fea94779a8440e10cf63819be6e176eb1973a6017aff5"},
 ]
 requests = [
     {file = "requests-2.23.0-py2.py3-none-any.whl", hash = "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee"},
     {file = "requests-2.23.0.tar.gz", hash = "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"},
 ]
 restructuredtext-lint = [
-    {file = "restructuredtext_lint-1.3.0.tar.gz", hash = "sha256:97b3da356d5b3a8514d8f1f9098febd8b41463bed6a1d9f126cf0a048b6fd908"},
+    {file = "restructuredtext_lint-1.3.1.tar.gz", hash = "sha256:470e53b64817211a42805c3a104d2216f6f5834b22fe7adb637d1de4d6501fb8"},
 ]
 rules = [
     {file = "rules-2.2.tar.gz", hash = "sha256:9bae429f9d4f91a375402990da1541f9e093b0ac077221d57124d06eeeca4405"},
@@ -2891,8 +2871,8 @@ selenium = [
     {file = "selenium-3.141.0.tar.gz", hash = "sha256:deaf32b60ad91a4611b98d8002757f29e6f2c2d5fcaf202e1c9ad06d6772300d"},
 ]
 six = [
-    {file = "six-1.14.0-py2.py3-none-any.whl", hash = "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"},
-    {file = "six-1.14.0.tar.gz", hash = "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a"},
+    {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"},
+    {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"},
 ]
 smmap = [
     {file = "smmap-3.0.4-py2.py3-none-any.whl", hash = "sha256:54c44c197c819d5ef1991799a7e30b662d1e520f2ac75c9efbeb54a742214cf4"},
@@ -2903,16 +2883,16 @@ snowballstemmer = [
     {file = "snowballstemmer-2.0.0.tar.gz", hash = "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"},
 ]
 soupsieve = [
-    {file = "soupsieve-1.9.5-py2.py3-none-any.whl", hash = "sha256:bdb0d917b03a1369ce964056fc195cfdff8819c40de04695a80bc813c3cfa1f5"},
-    {file = "soupsieve-1.9.5.tar.gz", hash = "sha256:e2c1c5dee4a1c36bcb790e0fabd5492d874b8ebd4617622c4f6a731701060dda"},
+    {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.4.0-py3-none-any.whl", hash = "sha256:e5c2d1efc4067ff83609a200c731db6c656fdfd26144ac8b50755d6c72515453"},
-    {file = "spdx_license_list-0.4.0.tar.gz", hash = "sha256:f8b5eeda2a1c88d8ce15f6324d5a6128a462932a2e55b032f017ac9a0e61f1c7"},
+    {file = "spdx_license_list-0.5.0-py3-none-any.whl", hash = "sha256:65c9f598dee3249d529300eb08800f8bf3d0d902868669146ada65192ecd0507"},
+    {file = "spdx_license_list-0.5.0.tar.gz", hash = "sha256:40cd53ff16401bab7059e6d1ef61839196b12079929a2763a50145d3b6949bc1"},
 ]
 sphinx = [
-    {file = "Sphinx-3.0.3-py3-none-any.whl", hash = "sha256:f5505d74cf9592f3b997380f9bdb2d2d0320ed74dd69691e3ee0644b956b8d83"},
-    {file = "Sphinx-3.0.3.tar.gz", hash = "sha256:62edfd92d955b868d6c124c0942eba966d54b5f3dcb4ded39e65f74abac3f572"},
+    {file = "Sphinx-3.0.4-py3-none-any.whl", hash = "sha256:779a519adbd3a70fc7c468af08c5e74829868b0a5b34587b33340e010291856c"},
+    {file = "Sphinx-3.0.4.tar.gz", hash = "sha256:ea64df287958ee5aac46be7ac2b7277305b0381d213728c3a49d8bb9b8415807"},
 ]
 sphinx-autodoc-typehints = [
     {file = "sphinx-autodoc-typehints-1.10.3.tar.gz", hash = "sha256:a6b3180167479aca2c4d1ed3b5cb044a70a76cccd6b38662d39288ebd9f0dff0"},
@@ -2974,16 +2954,15 @@ text-unidecode = [
     {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"},
 ]
 toml = [
-    {file = "toml-0.10.0-py2.7.egg", hash = "sha256:f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"},
-    {file = "toml-0.10.0-py2.py3-none-any.whl", hash = "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"},
-    {file = "toml-0.10.0.tar.gz", hash = "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c"},
+    {file = "toml-0.10.1-py2.py3-none-any.whl", hash = "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"},
+    {file = "toml-0.10.1.tar.gz", hash = "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f"},
 ]
 tqdm = [
-    {file = "tqdm-4.46.0-py2.py3-none-any.whl", hash = "sha256:acdafb20f51637ca3954150d0405ff1a7edde0ff19e38fb99a80a66210d2a28f"},
-    {file = "tqdm-4.46.0.tar.gz", hash = "sha256:4733c4a10d0f2a4d098d801464bdaf5240c7dadd2a7fde4ee93b0a0efd9fb25e"},
+    {file = "tqdm-4.46.1-py2.py3-none-any.whl", hash = "sha256:07c06493f1403c1380b630ae3dcbe5ae62abcf369a93bbc052502279f189ab8c"},
+    {file = "tqdm-4.46.1.tar.gz", hash = "sha256:cd140979c2bebd2311dfb14781d8f19bd5a9debb92dcab9f6ef899c987fcf71f"},
 ]
 twilio = [
-    {file = "twilio-6.39.0.tar.gz", hash = "sha256:7ef6ad19251fee6a41f1184e97b4fcb62f4a8c0e6f4b78797e40e9c92aed006d"},
+    {file = "twilio-6.41.0.tar.gz", hash = "sha256:7c6329118583852bb06a2065dd2987a012310e5dfd834ef821d736b059bd1c74"},
 ]
 typed-ast = [
     {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"},
@@ -3022,8 +3001,8 @@ vine = [
     {file = "vine-1.3.0.tar.gz", hash = "sha256:133ee6d7a9016f177ddeaf191c1f58421a1dcc6ee9a42c58b34bed40e1d2cd87"},
 ]
 wcwidth = [
-    {file = "wcwidth-0.1.9-py2.py3-none-any.whl", hash = "sha256:cafe2186b3c009a04067022ce1dcd79cb38d8d65ee4f4791b8888d6599d1bbe1"},
-    {file = "wcwidth-0.1.9.tar.gz", hash = "sha256:ee73862862a156bf77ff92b09034fc4825dd3af9cf81bc5b360668d425f3c5f1"},
+    {file = "wcwidth-0.2.3-py2.py3-none-any.whl", hash = "sha256:980fbf4f3c196c0f329cdcd1e84c554d6a211f18e252e525a0cf4223154a41d6"},
+    {file = "wcwidth-0.2.3.tar.gz", hash = "sha256:edbc2b718b4db6cdf393eefe3a420183947d6aa312505ce6754516f458ff8830"},
 ]
 webencodings = [
     {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"},
diff --git a/pyproject.toml b/pyproject.toml
index 42cb1190059bb2ee1a73f8cb02a039a1a25102ba..79e4ab7fcc7701a8553f78b4e54171fbe7999140 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -72,16 +72,16 @@ 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-otp = "0.9.0"
+django-otp = "0.9.1"
 django-colorfield = "^0.3.0"
 django-bleach = "^0.6.1"
 django-guardian = "^2.2.0"
 rules = "^2.2"
 django-cache-memoize = "^0.1.6"
 django-haystack = {version="3.0b1", allow-prereleases = true}
-celery-haystack = {version="^0.7.0", optional=true}
+celery-haystack = {version="^0.10.0", optional=true}
 django-dbbackup = "^3.3.0"
-spdx-license-list = "^0.4.0"
+spdx-license-list = "^0.5.0"
 license-expression = "^1.2"
 django-reversion = "^3.0.7"
 django-favicon-plus-reloaded = "^1.0.4"
@@ -111,7 +111,7 @@ flake8-builtins = "^1.4.1"
 flake8-docstrings = "^1.5.0"
 flake8-rst-docstrings = "^0.0.13"
 black = "^19.10b0"
-flake8-black = "^0.1.1"
+flake8-black = "^0.2.0"
 isort = "^4.3.21"
 flake8-isort = "^3.0.0"
 pytest-cov = "^2.8.1"