diff --git a/.gitmodules b/.gitmodules
index 0b1ff289423a93278ac8d274890a2b5ae0788e1a..9c04a74362075f927208cfbca8332da1d405e755 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -8,8 +8,13 @@
 	ignore = untracked
 [submodule "apps/official/AlekSIS-App-DashboardFeeds"]
 	path = apps/official/AlekSIS-App-DashboardFeeds
-	url = https://edugit.org/AlekSIS/Official/AlekSIS-App-DashboardFeeds.git
+	url = https://edugit.org/AlekSIS/Official/AlekSIS-App-DashboardFeeds
 	ignore = untracked
 [submodule "apps/official/AlekSIS-App-LDAP"]
 	path = apps/official/AlekSIS-App-LDAP
 	url = https://edugit.org/AlekSIS/official/AlekSIS-App-LDAP
+	ignore = untracked
+[submodule "apps/official/AlekSIS-App-Untis"]
+	path = apps/official/AlekSIS-App-Untis
+	url = https://edugit.org/AlekSIS/official/AlekSIS-App-Untis
+	ignore = untracked
diff --git a/README.rst b/README.rst
index 36ba5325a73f06493b8c3a862188516c17039a17..5c08b69d1ac8d24ab74c7cdbf74a9e4b8e161d6e 100644
--- a/README.rst
+++ b/README.rst
@@ -37,8 +37,8 @@ Licence
 
   Copyright © 2017, 2018, 2019, 2020 Jonathan Weth <wethjo@katharineum.de>
   Copyright © 2017, 2018, 2019 Frank Poetzsch-Heffter <p-h@katharineum.de>
+  Copyright © 2018, 2019, 2020 Julian Leucker <leuckeju@katharineum.de>
   Copyright © 2018, 2019, 2020 Hangzhi Yu <yuha@katharineum.de>
-  Copyright © 2018, 2019 Julian Leucker <leuckeju@katharineum.de>
   Copyright © 2019, 2020 Dominik George <dominik.george@teckids.org>
   Copyright © 2019, 2020 mirabilos <thorsten.glaser@teckids.org>
   Copyright © 2019, 2020 Tom Teichler <tom.teichler@teckids.org>
diff --git a/aleksis/core/__init__.py b/aleksis/core/__init__.py
index 21a49f0785f3977d9a265059a32ec957c9e1eb3c..587b829c611913af41c69f2734a48235956ff787 100644
--- a/aleksis/core/__init__.py
+++ b/aleksis/core/__init__.py
@@ -1,5 +1,7 @@
 import pkg_resources
 
+from django.utils.translation import gettext_lazy as _
+
 try:
     from .celery import app as celery_app
 except ModuleNotFoundError:
diff --git a/aleksis/core/apps.py b/aleksis/core/apps.py
index 13e3b3ddcf1083e1cff0f8c5e3461103bb12f4b2..648d063a378f30fddbbece6474c3b84a5a65bed6 100644
--- a/aleksis/core/apps.py
+++ b/aleksis/core/apps.py
@@ -13,6 +13,20 @@ class CoreConfig(AppConfig):
     name = "aleksis.core"
     verbose_name = "AlekSIS — The Free School Information System"
 
+    urls = {
+        "Repository": "https://edugit.org/AlekSIS/official/AlekSIS/",
+    }
+    licence = "EUPL-1.2+"
+    copyright = (
+        ([2017, 2018, 2019, 2020], "Jonathan Weth", "wethjo@katharineum.de"),
+        ([2017, 2018, 2019], "Frank Poetzsch-Heffter", "p-h@katharineum.de"),
+        ([2018, 2019, 2020], "Julian Leucker", "leuckeju@katharineum.de"),
+        ([2018, 2019, 2020], "Hangzhi Yu", "yuha@katharineum.de"),
+        ([2019, 2020], "Dominik George", "dominik.george@teckids.org"),
+        ([2019, 2020], "mirabilos", "thorsten.glaser@teckids.org"),
+        ([2019, 2020], "Tom Teichler", "tom.teichler@teckids.org"),
+    )
+
     def config_updated(self, *args, **kwargs) -> None:
         clean_scss()
 
diff --git a/aleksis/core/menus.py b/aleksis/core/menus.py
index 18cf14ee8d465fdd05b809c21af7faf90bb26a38..33cb12c3bd734f6ffd21c72ac29dfc9feb1a7254 100644
--- a/aleksis/core/menus.py
+++ b/aleksis/core/menus.py
@@ -50,7 +50,10 @@ MENUS = {
                     "name": _("Me"),
                     "url": "person",
                     "icon": "insert_emoticon",
-                    "validators": ["menu_generator.validators.is_authenticated"],
+                    "validators": [
+                        "menu_generator.validators.is_authenticated",
+                        "aleksis.core.util.core_helpers.has_person",
+                    ],
                 },
             ],
         },
diff --git a/aleksis/core/settings.py b/aleksis/core/settings.py
index 463ec58009375ee57562957e8e4a45dd2bb041b5..35758dca54ab27e5a82a7fa5e962e1e4231ef372 100644
--- a/aleksis/core/settings.py
+++ b/aleksis/core/settings.py
@@ -1,7 +1,9 @@
 import os
 import sys
 from glob import glob
+from importlib import import_module
 
+from django.apps import apps
 from django.utils.translation import gettext_lazy as _
 from calendarweek.django import i18n_day_name_choices_lazy
 
diff --git a/aleksis/core/static/js/main.js b/aleksis/core/static/js/main.js
index 403837da22d351d8e97ed3cf6c27c79e30514497..b625c105f4e0e8a51b581104049bd1d76bcbc200 100644
--- a/aleksis/core/static/js/main.js
+++ b/aleksis/core/static/js/main.js
@@ -66,7 +66,7 @@ $(document).ready( function () {
     });
 
     // Initialise auto-completion for search bar
-    window.autocomplete = new Autocomplete({});
+    window.autocomplete = new Autocomplete({minimum_length: 2});
     window.autocomplete.setup();
 
     // Initialize text collapsibles [MAT, own work]
diff --git a/aleksis/core/static/js/search.js b/aleksis/core/static/js/search.js
index 7943d2ebfaa07123bf0174b24c055884ceb0bd36..ea1dfb25b061f73a8a74c40edad3e3c8d073278e 100644
--- a/aleksis/core/static/js/search.js
+++ b/aleksis/core/static/js/search.js
@@ -35,9 +35,20 @@ Autocomplete.prototype.setup = function () {
         self.query_box.trigger("keydown");
     });
 
+    this.query_box.keyup(function () {
+        var query = self.query_box.val();
+
+        if (query.length < self.minimum_length) {
+            $("#search-results").remove();
+            return true;
+        }
+
+        self.fetch(query);
+        return true;
+    });
+
     // Watch the input box.
     this.query_box.keydown(function (e) {
-        var query = self.query_box.val();
 
         if (e.which === 38) { // Keypress Up
             if (!self.selected_element) {
@@ -69,14 +80,6 @@ Autocomplete.prototype.setup = function () {
             e.preventDefault();
             window.location.href = self.selected_element.attr("href");
         }
-
-        if (query.length < self.minimum_length) {
-            $("#search-results").remove();
-            return true;
-        }
-
-        self.fetch(query);
-        return true;
     });
 
     // // On selecting a result, remove result box
diff --git a/aleksis/core/static/style.scss b/aleksis/core/static/style.scss
index e1edc75561fb4fe57568f1b148b8ab0f955c1b58..10a00caa35ef329b248b7be2f927cd2714ebd244 100644
--- a/aleksis/core/static/style.scss
+++ b/aleksis/core/static/style.scss
@@ -189,6 +189,7 @@ a.collection-item.search-item {
 
 div#search-results {
   position: absolute;
+  margin-top: -10px;
   width: 100%;
 }
 
diff --git a/aleksis/core/templates/core/about.html b/aleksis/core/templates/core/about.html
new file mode 100644
index 0000000000000000000000000000000000000000..5b9097f8e294b099151d90dc91fbb583bd9e6dc9
--- /dev/null
+++ b/aleksis/core/templates/core/about.html
@@ -0,0 +1,122 @@
+{# -*- engine:django -*- #}
+{% extends "core/base.html" %}
+{% load i18n %}
+
+
+{% block browser_title %}{% blocktrans %}About AlekSIS{% endblocktrans %}{% endblock %}
+{% block page_title %}{% blocktrans %}AlekSIS – The Free School Information System{% endblocktrans %}{% endblock %}
+
+{% block content %}
+
+  <div class="row">
+    <div class="col s12">
+      <div class="card">
+        <div class="card-content">
+          <span class="card-title">{% blocktrans %}About AlekSIS{% endblocktrans %}</span>
+          <p>
+            {% blocktrans %}
+              This platform is powered by AlekSIS, a web-based school information system (SIS) which can be used
+              to manage and/or publish organisational subjects of educational institutions. AlekSIS is free software and
+              can be used by everyone.
+            {% endblocktrans %}
+          </p>
+        </div>
+        <div class="card-action">
+          <a class="" href="https://aleksis.org/">{% trans "Website of AlekSIS" %}</a>
+          <a class="" href="https://edugit.org/AlekSIS/">{% trans "Source code" %}</a>
+        </div>
+      </div>
+    </div>
+  </div>
+  <div class="row">
+    <div class="col s12">
+      <div class="card">
+        <div class="card-content">
+          <span class="card-title">{% trans "Licence information" %}</span>
+          <p>
+            {% blocktrans %}
+              The core and the official apps of AlekSIS are licenced under the EUPL, version 1.2 or later. For licence
+              information from third-party apps, if installed, see directly at the respective components below. The
+              licences are marked like this:
+            {% endblocktrans %}
+          </p>
+          <br/>
+          <p>
+            <span class="chip green white-text">{% trans "Free/Open Source Licence" %}</span>
+            <span class="chip orange white-text">{% trans "Other Licence" %}</span>
+          </p>
+        </div>
+        <div class="card-action">
+          <a href="https://eupl.eu">{% trans "Full licence text" %}</a>
+          <a href="https://joinup.ec.europa.eu/collection/eupl/guidelines-users-and-developers">{% trans "More information about the EUPL" %}</a>
+        </div>
+      </div>
+    </div>
+  </div>
+
+  <div class="row">
+    {% for app_config in app_configs %}
+      <div class="col s12 m12 l6">
+        <div class="card " id="{{ app_config.name }}">
+          <div class="card-content">
+            {% if app_config.get_licence.1.isFsfLibre %}
+              <span class="chip green white-text right">Free Software</span>
+            {% elif app_config.get_licence.1.isOsiApproved %}
+              <span class="chip green white-text right">Open Source</span>
+            {% endif %}
+
+            <span class="card-title">{{ app_config.get_name }} <small>{{ app_config.get_version }}</small></span>
+
+            {% if app_config.get_copyright %}
+              <p>
+                {% for holder in app_config.get_copyright %}
+                  Copyright © {{ holder.0 }}
+
+                  {% if holder.2 %}
+                    <a href="mailto:{{ holder.2 }}">{{ holder.1 }}</a>
+                  {% else %}
+                    {{ holder.1 }}
+                  {% endif %}
+
+                  <br/>
+                {% endfor %}
+              </p>
+              <br/>
+            {% endif %}
+
+            {% if app_config.get_licence %}
+              {% with licence=app_config.get_licence %}
+                <p>
+                  {% blocktrans with licence=licence.0 %}
+                    This app is licenced under {{ licence }}.
+                  {% endblocktrans %}
+                </p>
+                <br/>
+                <p>
+                  {% for l in licence.2 %}
+                    <a class="chip white-text {% if l.isOsiApproved or l.isFsfLibre %}green{% else %}orange{% endif %}"
+                       href="{{ l.url }}">
+                      {{ l.name }}
+                    </a>
+                  {% endfor %}
+                </p>
+              {% endwith %}
+            {% endif %}
+          </div>
+          {% if app_config.get_urls %}
+            <div class="card-action">
+              {% for url_name, url in app_config.get_urls.items %}
+                <a href="{{ url }}">{{ url_name }}</a>
+              {% endfor %}
+            </div>
+          {% endif %}
+        </div>
+      </div>
+      {% if forloop.counter|divisibleby:2 %}
+        </div>
+        <div class="row">
+      {% endif %}
+    {% endfor %}
+    </div>
+
+{% endblock %}
diff --git a/aleksis/core/templates/core/base.html b/aleksis/core/templates/core/base.html
index 8ac5ed74a195be76a558ae30e780d493ae6c2ecf..fe536557737d05a04c93d9bfbedfcc63f1bbe60b 100644
--- a/aleksis/core/templates/core/base.html
+++ b/aleksis/core/templates/core/base.html
@@ -146,8 +146,8 @@
   <div class="footer-copyright">
     <div class="container">
       <div class="left">
-        <a class="blue-text text-lighten-4" href="https://aleksis.org/">
-          AlekSIS — The Free School Information System
+        <a class="blue-text text-lighten-4" href="{% url "about_aleksis" %}">
+          {% trans "About AlekSIS — The Free School Information System" %}
         </a>
         © The AlekSIS Team
       </div>
diff --git a/aleksis/core/templates/search/search.html b/aleksis/core/templates/search/search.html
index babbd70d945f26cbf777ae46ca0add15a24e442d..d463f1569793168d6644c80a1dc63fe60cb06234 100644
--- a/aleksis/core/templates/search/search.html
+++ b/aleksis/core/templates/search/search.html
@@ -14,7 +14,6 @@
     <input type="text" name="{{ form.q.name }}" id="{{ form.q.id }}" value="{% firstof form.q.value "" %}"
            placeholder="{% trans "Search Term" %}">
 
-    <h6>{{ form.models.label }}</h6>
     <div>
       {% for group, items in form.models|select_options %}
         {% for choice, value, selected in items %}
@@ -28,10 +27,12 @@
       {% endfor %}
     </div>
 
-    <button type="submit" class="btn waves-effect waves-light green">
-      <i class="material-icons left">search</i>
-      {% blocktrans %}Search{% endblocktrans %}
-    </button>
+    <p>
+      <button type="submit" class="btn waves-effect waves-light green">
+        <i class="material-icons left">search</i>
+        {% blocktrans %}Search{% endblocktrans %}
+      </button>
+    </p>
 
     <h5>{% trans "Results" %}</h5>
 
@@ -44,7 +45,9 @@
           </a>
         {% empty %}
           <li class="collection-item">
-            {% trans "No search results could be found to your search" %}
+            <p class="flow-text">
+              {% trans "No search results could be found to your search." %}
+            </p>
           </li>
         {% endfor %}
       </div>
@@ -81,11 +84,13 @@
         </ul>
       {% endif %}
     {% else %}
-      <div class="collection">
+      <ul class="collection">
         <li class="collection-item">
-          {% trans "Please enter a search term above" %}
+          <p class="flow-text">
+            {% trans "Please enter a search term above." %}
+          </p>
         </li>
-      </div>
+      </ul>
     {% endif %}
 
 
diff --git a/aleksis/core/urls.py b/aleksis/core/urls.py
index 4bb7d10fbbaa6774d5f5beed3a1d7e034f020260..5de76472c91da9243edfa13c57c5ce29a7782e08 100644
--- a/aleksis/core/urls.py
+++ b/aleksis/core/urls.py
@@ -16,6 +16,7 @@ from . import views
 urlpatterns = [
     path("", include("pwa.urls"), name="pwa"),
     path("offline/", views.offline, name="offline"),
+    path("about/", views.about, name="about_aleksis"),
     path("admin/", admin.site.urls),
     path("data_management/", views.data_management, name="data_management"),
     path("status/", views.system_status, name="system_status"),
diff --git a/aleksis/core/util/apps.py b/aleksis/core/util/apps.py
index 7eef538ae05265d3492bda331eaf75875229a4ae..dbf6159252c0b20aee9fcc422fb21360c7fbc1e0 100644
--- a/aleksis/core/util/apps.py
+++ b/aleksis/core/util/apps.py
@@ -1,5 +1,5 @@
 from importlib import import_module
-from typing import Any, List, Optional, Tuple
+from typing import Any, List, Optional, Tuple, Sequence
 
 import django.apps
 from django.contrib.auth.signals import user_logged_in, user_logged_out
@@ -7,6 +7,10 @@ from django.db.models.signals import post_migrate, pre_migrate
 from django.http import HttpRequest
 
 from constance.signals import config_updated
+from license_expression import Licensing, LicenseSymbol
+from spdx_license_list import LICENSES
+
+from .core_helpers import copyright_years
 
 
 class AppConfig(django.apps.AppConfig):
@@ -41,6 +45,87 @@ class AppConfig(django.apps.AppConfig):
             # ImportErrors are non-fatal because checks are optional.
             pass
 
+    @classmethod
+    def get_name(cls):
+        return getattr(cls, "verbose_name", cls.name)
+        # TODO Try getting from distribution if not set
+
+    @classmethod
+    def get_version(cls):
+        try:
+            from .. import __version__  # noqa
+        except ImportError:
+            __version__ = None
+
+        return getattr(cls, "version", __version__)
+
+    @classmethod
+    def get_licence(cls) -> Tuple:
+        licence = getattr(cls, "licence", None)
+
+        default_dict = {
+            'isDeprecatedLicenseId': False,
+            'isFsfLibre': False,
+            'isOsiApproved': False,
+            'licenseId': 'unknown',
+            'name': 'Unknown Licence',
+            'referenceNumber': -1,
+            'url': '',
+        }
+
+        if licence:
+            licensing = Licensing(LICENSES.keys())
+            parsed = licensing.parse(licence).simplify()
+            readable = parsed.render_as_readable()
+
+            flags = {
+                "isFsfLibre": True,
+                "isOsiApproved": True,
+            }
+
+            licence_dicts = []
+
+            for symbol in parsed.symbols:
+                licence_dict = LICENSES.get(symbol.key.rstrip("+"), None)
+
+                if licence_dict is None:
+                    licence_dict = default_dict
+                else:
+                    licence_dict["url"] = "https://spdx.org/licenses/{}.html".format(licence_dict["licenseId"])
+
+                flags["isFsfLibre"] = flags["isFsfLibre"] and licence_dict["isFsfLibre"]
+                flags["isOsiApproved"] = flags["isOsiApproved"] and licence_dict["isOsiApproved"]
+
+                licence_dicts.append(licence_dict)
+
+            return (readable, flags, licence_dicts)
+        else:
+            return ("Unknown", [default_dict])
+
+    @classmethod
+    def get_urls(cls):
+        return getattr(cls, "urls", {})
+        # TODO Try getting from distribution if not set
+
+    @classmethod
+    def get_copyright(cls) -> Sequence[Tuple[str, str, str]]:
+        copyrights = getattr(cls, "copyright", tuple())
+
+        copyrights_processed = []
+
+        for copyright in copyrights:
+            copyrights_processed.append(
+                (
+                    copyright[0] if isinstance(copyright[0], str) else copyright_years(copyright[0]),
+                    copyright[1],
+                    copyright[2],
+                )
+            )
+
+        return copyrights_processed
+
+        # TODO Try getting from distribution if not set
+
     def config_updated(
         self,
         key: Optional[str] = "",
diff --git a/aleksis/core/util/core_helpers.py b/aleksis/core/util/core_helpers.py
index 9f91154c30913ae14eff1332b5f6bea3207bfa86..13558233068626c8f288b70624b2646840826819 100644
--- a/aleksis/core/util/core_helpers.py
+++ b/aleksis/core/util/core_helpers.py
@@ -1,8 +1,10 @@
 from datetime import datetime, timedelta
+from itertools import groupby
+from operator import itemgetter
 import os
 import pkgutil
 from importlib import import_module
-from typing import Any, Callable, Sequence, Union
+from typing import Any, Callable, Sequence, Union, List
 from uuid import uuid4
 
 from django.conf import settings
@@ -12,6 +14,18 @@ from django.utils import timezone
 from django.utils.functional import lazy
 
 
+def copyright_years(years: Sequence[int], seperator: str = ", ", joiner: str = "–") -> str:
+    """ Takes a sequence of integegers and produces a string with ranges
+
+    >>> copyright_years([1999, 2000, 2001, 2005, 2007, 2008, 2009])
+    '1999–2001, 2005, 2007–2009'
+    """
+
+    ranges = [list(map(itemgetter(1), group)) for _, group in groupby(enumerate(years), lambda e: e[1]-e[0])]
+    years_strs = [str(range_[0]) if len(range_) == 1 else joiner.join([str(range_[0]), str(range_[-1])]) for range_ in ranges]
+
+    return seperator.join(years_strs)
+
 def dt_show_toolbar(request: HttpRequest) -> bool:
     from debug_toolbar.middleware import show_toolbar  # noqa
 
diff --git a/aleksis/core/views.py b/aleksis/core/views.py
index d566ce328f7fbe5aa73d3261dfcaeecfe0aa4846..0f38a8a0412d7d0702ad05242710ec7c187c49a1 100644
--- a/aleksis/core/views.py
+++ b/aleksis/core/views.py
@@ -1,5 +1,7 @@
+from importlib import import_module
 from typing import Optional
 
+from django.apps import apps
 from django.contrib.auth.decorators import login_required
 from django.core.exceptions import PermissionDenied
 from django.http import Http404, HttpRequest, HttpResponse
@@ -24,6 +26,7 @@ from .forms import (
 from .models import Activity, Group, Notification, Person, School, DashboardWidget, Announcement
 from .tables import GroupsTable, PersonsTable
 from .util import messages
+from .util.apps import AppConfig
 
 
 @permission_required("core.view_dashboard")
@@ -54,6 +57,14 @@ def offline(request):
     return render(request, "core/offline.html")
 
 
+def about(request):
+    context = {}
+
+    context["app_configs"] = list(filter(lambda a: isinstance(a, AppConfig), apps.get_app_configs()))
+
+    return render(request, "core/about.html", context)
+
+
 @permission_required("core.view_persons")
 def persons(request: HttpRequest) -> HttpResponse:
     context = {}
diff --git a/apps/official/AlekSIS-App-Chronos b/apps/official/AlekSIS-App-Chronos
index 6b05dfc0613ff143fa292aa67eb349c90439d7ee..6833571a02b8df133ea0f3b99f75e93da0c50fc9 160000
--- a/apps/official/AlekSIS-App-Chronos
+++ b/apps/official/AlekSIS-App-Chronos
@@ -1 +1 @@
-Subproject commit 6b05dfc0613ff143fa292aa67eb349c90439d7ee
+Subproject commit 6833571a02b8df133ea0f3b99f75e93da0c50fc9
diff --git a/apps/official/AlekSIS-App-DashboardFeeds b/apps/official/AlekSIS-App-DashboardFeeds
index 894dc89ac4fd4f6c33b51020288eabe9b6169256..dc6d80c09193910bf27533ac7d6877839fb62c50 160000
--- a/apps/official/AlekSIS-App-DashboardFeeds
+++ b/apps/official/AlekSIS-App-DashboardFeeds
@@ -1 +1 @@
-Subproject commit 894dc89ac4fd4f6c33b51020288eabe9b6169256
+Subproject commit dc6d80c09193910bf27533ac7d6877839fb62c50
diff --git a/apps/official/AlekSIS-App-Exlibris b/apps/official/AlekSIS-App-Exlibris
index 736a7ea53be4e0fa7de7bf3dd66cd47965e36bed..ab4e3f2d03bba8b4c2b6ad90d08106f423ae32e4 160000
--- a/apps/official/AlekSIS-App-Exlibris
+++ b/apps/official/AlekSIS-App-Exlibris
@@ -1 +1 @@
-Subproject commit 736a7ea53be4e0fa7de7bf3dd66cd47965e36bed
+Subproject commit ab4e3f2d03bba8b4c2b6ad90d08106f423ae32e4
diff --git a/apps/official/AlekSIS-App-LDAP b/apps/official/AlekSIS-App-LDAP
index c0bc552d9a7e04560b54c261b5e8d690958d2968..f99aa641eb5cdeaf5421e5297813cbcb3c55eb6c 160000
--- a/apps/official/AlekSIS-App-LDAP
+++ b/apps/official/AlekSIS-App-LDAP
@@ -1 +1 @@
-Subproject commit c0bc552d9a7e04560b54c261b5e8d690958d2968
+Subproject commit f99aa641eb5cdeaf5421e5297813cbcb3c55eb6c
diff --git a/apps/official/AlekSIS-App-Untis b/apps/official/AlekSIS-App-Untis
new file mode 160000
index 0000000000000000000000000000000000000000..a567021ee5e66657b3374a76865bab1ee9bdbc33
--- /dev/null
+++ b/apps/official/AlekSIS-App-Untis
@@ -0,0 +1 @@
+Subproject commit a567021ee5e66657b3374a76865bab1ee9bdbc33
diff --git a/poetry.lock b/poetry.lock
index 591849c3413c4492ebc30c577f706574f7c8470a..6893b47254efa4705507f421317319d5a99d8e70 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -140,6 +140,14 @@ version = "3.1.4"
 six = ">=1.9.0"
 webencodings = "*"
 
+[[package]]
+category = "main"
+description = "Define boolean algebras, create and parse boolean expressions and create custom boolean DSL."
+name = "boolean.py"
+optional = false
+python-versions = "*"
+version = "3.7"
+
 [[package]]
 category = "main"
 description = "Utilities for working with calendar weeks in Python and Django"
@@ -207,6 +215,17 @@ yaml = ["PyYAML (>=3.10)"]
 zookeeper = ["kazoo (>=1.3.1)"]
 zstd = ["zstandard"]
 
+[[package]]
+category = "main"
+description = "An app for integrating Celery with Haystack."
+name = "celery-haystack"
+optional = true
+python-versions = "*"
+version = "0.3.1"
+
+[package.dependencies]
+django-appconf = ">=0.2.1"
+
 [[package]]
 category = "main"
 description = "Python package for providing Mozilla's CA Bundle."
@@ -437,6 +456,19 @@ version = "*"
 database = ["django-picklefield"]
 redis = ["redis"]
 
+[[package]]
+category = "main"
+description = "Management commands to help backup and restore a project database and media"
+name = "django-dbbackup"
+optional = false
+python-versions = "*"
+version = "3.3.0"
+
+[package.dependencies]
+Django = ">=1.5"
+pytz = "*"
+six = "*"
+
 [[package]]
 category = "main"
 description = "A configurable set of panels that display various debug information about the current request/response."
@@ -497,6 +529,17 @@ django-bulk-update = ">=2.2.0"
 six = "*"
 tqdm = ">=4.23.4"
 
+[[package]]
+category = "main"
+description = "Pluggable search for Django."
+name = "django-haystack"
+optional = false
+python-versions = "*"
+version = "3.0b1"
+
+[package.dependencies]
+Django = ">=2.2"
+
 [[package]]
 category = "main"
 description = "A reusable app for cropping images easily and non-destructively in Django"
@@ -580,7 +623,7 @@ description = "An implementation of memoization technique for Django."
 name = "django-memoize"
 optional = false
 python-versions = "*"
-version = "2.2.1"
+version = "2.3.0"
 
 [package.dependencies]
 django = "*"
@@ -1179,6 +1222,17 @@ version = "0.19.4"
 [package.dependencies]
 six = "*"
 
+[[package]]
+category = "main"
+description = "license-expression is small utility library to parse, compare, simplify and normalize license expressions (such as SPDX license expressions) using boolean logic."
+name = "license-expression"
+optional = false
+python-versions = "*"
+version = "1.2"
+
+[package.dependencies]
+"boolean.py" = ">=3.6,<4.0.0"
+
 [[package]]
 category = "dev"
 description = "Safely add untrusted strings to HTML/XML markup."
@@ -1261,7 +1315,7 @@ description = "PostgreSQL interface library"
 name = "pg8000"
 optional = false
 python-versions = ">=3.5"
-version = "1.15.1"
+version = "1.15.2"
 
 [package.dependencies]
 scramp = "1.1.1"
@@ -1515,7 +1569,7 @@ description = "Add .env support to your django/flask apps in development and dep
 name = "python-dotenv"
 optional = false
 python-versions = "*"
-version = "0.12.0"
+version = "0.13.0"
 
 [package.extras]
 cli = ["click (>=5.0)"]
@@ -1691,6 +1745,14 @@ optional = false
 python-versions = "*"
 version = "1.9.5"
 
+[[package]]
+category = "main"
+description = "A simple tool/library for working with SPDX license definitions."
+name = "spdx-license-list"
+optional = false
+python-versions = "*"
+version = "0.4.0"
+
 [[package]]
 category = "dev"
 description = "Python documentation generator"
@@ -1932,11 +1994,11 @@ description = "HTTP library with thread-safe connection pooling, file post, and
 name = "urllib3"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
-version = "1.25.8"
+version = "1.25.9"
 
 [package.extras]
 brotli = ["brotlipy (>=0.6.0)"]
-secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
+secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)", "ipaddress"]
 socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"]
 
 [[package]]
@@ -1953,7 +2015,7 @@ description = "Virtual Python Environment builder"
 name = "virtualenv"
 optional = false
 python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
-version = "20.0.17"
+version = "20.0.18"
 
 [package.dependencies]
 appdirs = ">=1.4.3,<2"
@@ -2007,11 +2069,11 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
 testing = ["jaraco.itertools", "func-timeout"]
 
 [extras]
-celery = ["Celery", "django-celery-results", "django-celery-beat", "django-celery-email"]
+celery = ["Celery", "django-celery-results", "django-celery-beat", "django-celery-email", "celery-haystack"]
 ldap = ["django-auth-ldap"]
 
 [metadata]
-content-hash = "8d698d1e20a7a28bb0f91312abb432565a30e9345f39552a2c2933eb1b4f8072"
+content-hash = "afd2f4b69870b913a740e8247dbe3618d40e0b51294203c5e883b9f72427bd71"
 python-versions = "^3.7"
 
 [metadata.files]
@@ -2064,6 +2126,10 @@ bleach = [
     {file = "bleach-3.1.4-py2.py3-none-any.whl", hash = "sha256:cc8da25076a1fe56c3ac63671e2194458e0c4d9c7becfd52ca251650d517903c"},
     {file = "bleach-3.1.4.tar.gz", hash = "sha256:e78e426105ac07026ba098f04de8abe9b6e3e98b5befbf89b51a5ef0a4292b03"},
 ]
+"boolean.py" = [
+    {file = "boolean.py-3.7-py2.py3-none-any.whl", hash = "sha256:82ae181f9c85cb5c893a5a4daba9f24d60b538a7dd27fd0c6752a77eba4fbeff"},
+    {file = "boolean.py-3.7.tar.gz", hash = "sha256:bd19b412435611ecc712603d0fd7d0e280e24698e7a6e3d5f610473870c5dd1e"},
+]
 calendarweek = [
     {file = "calendarweek-0.4.5-py3-none-any.whl", hash = "sha256:b35fcc087073969d017cede62a7295bcd714a1304bcb4c4e2b0f23acb0265fb1"},
     {file = "calendarweek-0.4.5.tar.gz", hash = "sha256:5b1788ca435022f9348fc81a718974e51dd85d080f9aa3dad717df70a1bc6e1f"},
@@ -2072,6 +2138,9 @@ celery = [
     {file = "celery-4.4.2-py2.py3-none-any.whl", hash = "sha256:5b4b37e276033fe47575107a2775469f0b721646a08c96ec2c61531e4fe45f2a"},
     {file = "celery-4.4.2.tar.gz", hash = "sha256:108a0bf9018a871620936c33a3ee9f6336a89f8ef0a0f567a9001f4aa361415f"},
 ]
+celery-haystack = [
+    {file = "celery-haystack-0.3.1.tar.gz", hash = "sha256:49992712e67b1f39afd294dca6ba2820f2d262b3137ad14cb0c57a05fa218725"},
+]
 certifi = [
     {file = "certifi-2020.4.5.1-py2.py3-none-any.whl", hash = "sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304"},
     {file = "certifi-2020.4.5.1.tar.gz", hash = "sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519"},
@@ -2181,6 +2250,9 @@ django-colorfield = [
 django-constance = [
     {file = "django-constance-2.6.0.tar.gz", hash = "sha256:12d827f9d5552ee39884fb6fb356f231f32b1ab8958acc715e3d1a6ecf913653"},
 ]
+django-dbbackup = [
+    {file = "django-dbbackup-3.3.0.tar.gz", hash = "sha256:bb109735cae98b64ad084e5b461b7aca2d7b39992f10c9ed9435e3ebb6fb76c8"},
+]
 django-debug-toolbar = [
     {file = "django-debug-toolbar-2.2.tar.gz", hash = "sha256:eabbefe89881bbe4ca7c980ff102e3c35c8e8ad6eb725041f538988f2f39a943"},
     {file = "django_debug_toolbar-2.2-py3-none-any.whl", hash = "sha256:ff94725e7aae74b133d0599b9bf89bd4eb8f5d2c964106e61d11750228c8774c"},
@@ -2201,6 +2273,10 @@ django-hattori = [
     {file = "django-hattori-0.2.1.tar.gz", hash = "sha256:6953d40881317252f19f62c4e7fe8058924b852c7498bc42beb7bc4d268c252c"},
     {file = "django_hattori-0.2.1-py2.py3-none-any.whl", hash = "sha256:e529ed7af8fc34a0169c797c477672b687a205a56f3f5206f90c260acb83b7ac"},
 ]
+django-haystack = [
+    {file = "django-haystack-3.0b1.tar.gz", hash = "sha256:9dba64f5c76cf147ac382d4a4a270f30d30a45a3a7a1738a9d05c96d18777c07"},
+    {file = "django_haystack-3.0b1-py3-none-any.whl", hash = "sha256:b83705e1cf8141cd1755fc6683ac65fea4e1281f4b4306bc9224af96495b0df3"},
+]
 django-image-cropping = [
     {file = "django-image-cropping-1.4.0.tar.gz", hash = "sha256:6cc4a6bd8901e69b710caceea29b942fdb202da26626313cd9271ae989a83a52"},
     {file = "django_image_cropping-1.4.0-py3-none-any.whl", hash = "sha256:fe6a139c6d5dfc480f2a1d4e7e3e928d5edaefc898e17be66bc5f73140762ad9"},
@@ -2231,7 +2307,7 @@ django-material = [
     {file = "django_material-1.6.3-py2.py3-none-any.whl", hash = "sha256:502dc88c2f61f190fdc401666e83b47da00cbda98477af6ed8b7d43944ce6407"},
 ]
 django-memoize = [
-    {file = "django-memoize-2.2.1.tar.gz", hash = "sha256:7428e19cb17009ea51120420101a239d83bdf4064e5c72ca8c3dfa9656033f24"},
+    {file = "django-memoize-2.3.0.tar.gz", hash = "sha256:85decffbef7d38ffc569dc96527f598e6677bbc01ce29adf722b051da7efd4be"},
 ]
 django-menu-generator = [
     {file = "django-menu-generator-1.0.4.tar.gz", hash = "sha256:ce71a5055c16933c8aff64fb36c21e5cf8b6d505733aceed1252f8b99369a378"},
@@ -2419,6 +2495,10 @@ libsass = [
     {file = "libsass-0.19.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6a51393d75f6e3c812785b0fa0b7d67c54258c28011921f204643b55f7355ec0"},
     {file = "libsass-0.19.4.tar.gz", hash = "sha256:8b5b6d1a7c4ea1d954e0982b04474cc076286493f6af2d0a13c2e950fbe0be95"},
 ]
+license-expression = [
+    {file = "license-expression-1.2.tar.gz", hash = "sha256:7960e1dfdf20d127e75ead931476f2b5c7556df05b117a73880b22ade17d1abc"},
+    {file = "license_expression-1.2-py2.py3-none-any.whl", hash = "sha256:6d97906380cecfc758a77f6d38c6760f2afade7e83d2b8295e234fe21f486fb8"},
+]
 markupsafe = [
     {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"},
     {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"},
@@ -2495,8 +2575,8 @@ pbr = [
     {file = "pbr-5.4.5.tar.gz", hash = "sha256:07f558fece33b05caf857474a366dfcc00562bca13dd8b47b2b3e22d9f9bf55c"},
 ]
 pg8000 = [
-    {file = "pg8000-1.15.1-py3-none-any.whl", hash = "sha256:1f17917c8e2580581f4f689ed5e6eefef4ba873e7a9550d04afcb07fd43be83d"},
-    {file = "pg8000-1.15.1.tar.gz", hash = "sha256:01033498ffb27e780f6fb9ec877655ad97ceb26a50efdd77ca3b39ab2271f37c"},
+    {file = "pg8000-1.15.2-py3-none-any.whl", hash = "sha256:2bfdd03c2623302af655ef089a958ff329b2035c9d9aea406b5e4dac9c007524"},
+    {file = "pg8000-1.15.2.tar.gz", hash = "sha256:eb42ba62fbc048c91d5cf1ac729e0ea4ee329cc526bddafed4e7a8aa6b57fbbb"},
 ]
 phonenumbers = [
     {file = "phonenumbers-8.12.1-py2.py3-none-any.whl", hash = "sha256:bebf881ef0e775b93062fbd107bf164b5baef877a7b8f702e93a9a5d24ae4065"},
@@ -2637,8 +2717,8 @@ python-dateutil = [
     {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"},
 ]
 python-dotenv = [
-    {file = "python-dotenv-0.12.0.tar.gz", hash = "sha256:92b3123fb2d58a284f76cc92bfe4ee6c502c32ded73e8b051c4f6afc8b6751ed"},
-    {file = "python_dotenv-0.12.0-py2.py3-none-any.whl", hash = "sha256:81822227f771e0cab235a2939f0f265954ac4763cafd806d845801c863bf372f"},
+    {file = "python-dotenv-0.13.0.tar.gz", hash = "sha256:3b9909bc96b0edc6b01586e1eed05e71174ef4e04c71da5786370cebea53ad74"},
+    {file = "python_dotenv-0.13.0-py2.py3-none-any.whl", hash = "sha256:25c0ff1a3e12f4bde8d592cc254ab075cfe734fc5dd989036716fd17ee7e5ec7"},
 ]
 python-ldap = [
     {file = "python-ldap-3.2.0.tar.gz", hash = "sha256:7d1c4b15375a533564aad3d3deade789221e450052b21ebb9720fb822eccdb8e"},
@@ -2730,6 +2810,10 @@ soupsieve = [
     {file = "soupsieve-1.9.5-py2.py3-none-any.whl", hash = "sha256:bdb0d917b03a1369ce964056fc195cfdff8819c40de04695a80bc813c3cfa1f5"},
     {file = "soupsieve-1.9.5.tar.gz", hash = "sha256:e2c1c5dee4a1c36bcb790e0fabd5492d874b8ebd4617622c4f6a731701060dda"},
 ]
+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"},
+]
 sphinx = [
     {file = "Sphinx-2.4.4-py3-none-any.whl", hash = "sha256:fc312670b56cb54920d6cc2ced455a22a547910de10b3142276495ced49231cb"},
     {file = "Sphinx-2.4.4.tar.gz", hash = "sha256:b4c750d546ab6d7e05bdff6ac24db8ae3e8b8253a3569b754e445110a0a12b66"},
@@ -2831,16 +2915,16 @@ typing-extensions = [
     {file = "typing_extensions-3.7.4.2.tar.gz", hash = "sha256:79ee589a3caca649a9bfd2a8de4709837400dfa00b6cc81962a1e6a1815969ae"},
 ]
 urllib3 = [
-    {file = "urllib3-1.25.8-py2.py3-none-any.whl", hash = "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc"},
-    {file = "urllib3-1.25.8.tar.gz", hash = "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"},
+    {file = "urllib3-1.25.9-py2.py3-none-any.whl", hash = "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"},
+    {file = "urllib3-1.25.9.tar.gz", hash = "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527"},
 ]
 vine = [
     {file = "vine-1.3.0-py2.py3-none-any.whl", hash = "sha256:ea4947cc56d1fd6f2095c8d543ee25dad966f78692528e68b4fada11ba3f98af"},
     {file = "vine-1.3.0.tar.gz", hash = "sha256:133ee6d7a9016f177ddeaf191c1f58421a1dcc6ee9a42c58b34bed40e1d2cd87"},
 ]
 virtualenv = [
-    {file = "virtualenv-20.0.17-py2.py3-none-any.whl", hash = "sha256:00cfe8605fb97f5a59d52baab78e6070e72c12ca64f51151695407cc0eb8a431"},
-    {file = "virtualenv-20.0.17.tar.gz", hash = "sha256:c8364ec469084046c779c9a11ae6340094e8a0bf1d844330fc55c1cefe67c172"},
+    {file = "virtualenv-20.0.18-py2.py3-none-any.whl", hash = "sha256:5021396e8f03d0d002a770da90e31e61159684db2859d0ba4850fbea752aa675"},
+    {file = "virtualenv-20.0.18.tar.gz", hash = "sha256:ac53ade75ca189bc97b6c1d9ec0f1a50efe33cbf178ae09452dcd9fd309013c1"},
 ]
 virtualenv-clone = [
     {file = "virtualenv-clone-0.5.4.tar.gz", hash = "sha256:665e48dd54c84b98b71a657acb49104c54e7652bce9c1c4f6c6976ed4c827a29"},
diff --git a/pyproject.toml b/pyproject.toml
index cc3967bcd613a1616e9cfef5e6eb7c0f21ce3588..7ebc0ca15eb5fdbdb8061c7316ce915434e79ffb 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -71,8 +71,11 @@ django-memoize = "^2.2.1"
 django-guardian = "^2.2.0"
 rules = "^2.2"
 django-haystack = "^3.0"
+django-haystack = {version="3.0b1", allows-prereleases = true}
 celery-haystack = {version="^0.3.1", optional=true}
 django-dbbackup = "^3.3.0"
+spdx-license-list = "^0.4.0"
+license-expression = "^1.2"
 
 [tool.poetry.extras]
 ldap = ["django-auth-ldap"]