From 76bdc68cb90ef472c51f6d3c08015b36855bb415 Mon Sep 17 00:00:00 2001
From: Tom Teichler <tom.teichler@teckids.org>
Date: Wed, 19 Aug 2020 21:28:06 +0200
Subject: [PATCH] [Reformat] Make lint clean

---
 .gitlab-ci.yml                                |   7 +
 aleksis/apps/ticdesk/__init__.py              |   2 -
 aleksis/apps/ticdesk/filters.py               |  33 +-
 aleksis/apps/ticdesk/forms.py                 | 308 +++++++++++-------
 aleksis/apps/ticdesk/menus.py                 |  24 +-
 .../migrations/0007_auto_20200728_1145.py     | 129 ++++++--
 .../migrations/0008_auto_20200728_1352.py     |  10 +-
 .../migrations/0009_auto_20200728_1504.py     |  19 +-
 .../migrations/0010_auto_20200728_2017.py     |  48 ++-
 .../migrations/0011_auto_20200728_2119.py     |  20 +-
 .../migrations/0012_auto_20200729_1640.py     |  20 +-
 .../migrations/0013_auto_20200729_1738.py     |   8 +-
 .../migrations/0014_auto_20200731_1511.py     |  14 +-
 .../migrations/0015_auto_20200802_1340.py     |  19 +-
 ...16_eventfeedback_feedbackfeedbackaspect.py | 167 ++++++++--
 .../migrations/0017_auto_20200802_1507.py     |  29 +-
 .../migrations/0018_auto_20200802_1646.py     |  25 +-
 aleksis/apps/ticdesk/model_extensions.py      |  10 +-
 aleksis/apps/ticdesk/models.py                |  77 ++++-
 aleksis/apps/ticdesk/predicates.py            |   4 +-
 aleksis/apps/ticdesk/preferences.py           |   1 -
 aleksis/apps/ticdesk/rules.py                 |  28 +-
 aleksis/apps/ticdesk/settings.py              |   6 -
 aleksis/apps/ticdesk/tables.py                |  31 +-
 aleksis/apps/ticdesk/urls.py                  |  54 ++-
 aleksis/apps/ticdesk/util.py                  |  13 +-
 aleksis/apps/ticdesk/views.py                 | 168 ++++++----
 poetry.lock                                   |  64 ++--
 tox.ini                                       |  82 +++++
 29 files changed, 949 insertions(+), 471 deletions(-)
 create mode 100644 .gitlab-ci.yml
 create mode 100644 tox.ini

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..1224312
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,7 @@
+include:
+    - project: "AlekSIS/official/AlekSIS"
+      file: /ci/general.yml
+    - project: "AlekSIS/official/AlekSIS"
+      file: /ci/test.yml
+    - project: "AlekSIS/official/AlekSIS"
+      file: /ci/build_dist.yml
diff --git a/aleksis/apps/ticdesk/__init__.py b/aleksis/apps/ticdesk/__init__.py
index b775e19..4ba3a1e 100644
--- a/aleksis/apps/ticdesk/__init__.py
+++ b/aleksis/apps/ticdesk/__init__.py
@@ -1,3 +1 @@
-from ._version import __version__
-
 default_app_config = "aleksis.apps.ticdesk.apps.TICDeskConfig"
diff --git a/aleksis/apps/ticdesk/filters.py b/aleksis/apps/ticdesk/filters.py
index 6cd3acc..9656830 100644
--- a/aleksis/apps/ticdesk/filters.py
+++ b/aleksis/apps/ticdesk/filters.py
@@ -1,13 +1,14 @@
-from django_filters import FilterSet
-from material import Layout, Row
 from django.utils.translation import gettext_lazy as _
 
-from .models import EventRegistration, TeckidsMember, Voucher, FeedbackAspect
+from django_filters import FilterSet
+from material import Layout, Row
 
 from aleksis.core.filters import MultipleCharFilter
 
-class EventRegistrationFilter(FilterSet):
+from .models import EventRegistration, FeedbackAspect, TeckidsMember, Voucher
 
+
+class EventRegistrationFilter(FilterSet):
     class Meta:
         model = EventRegistration
         fields = ["person", "event", "accept_sepa", "date_registred"]
@@ -16,19 +17,15 @@ class EventRegistrationFilter(FilterSet):
         super().__init__(*args, **kwargs)
 
         self.form.layout = Layout(
-            Row("person", "event"),
-            Row("accept_sepa", "date_registred"),
+            Row("person", "event"), Row("accept_sepa", "date_registred"),
         )
 
 
 class TeckidsMemberFilter(FilterSet):
 
     name = MultipleCharFilter(
-        [
-            "person__first_name__icontains",
-            "person__last_name__icontains",
-        ],
-        label = _("Search by name"),
+        ["person__first_name__icontains", "person__last_name__icontains",],
+        label=_("Search by name"),
     )
 
     class Meta:
@@ -45,18 +42,12 @@ class TeckidsMemberFilter(FilterSet):
 
 class VoucherFilter(FilterSet):
     event = MultipleCharFilter(
-        [
-            "project__short_name__icontains",
-        ],
-        label = _("Search by event"),
+        ["project__short_name__icontains",], label=_("Search by event"),
     )
 
     name = MultipleCharFilter(
-        [
-            "person__first_name__icontains",
-            "person__last_name__icontains",
-        ],
-        label = _("Search by name"),
+        ["person__first_name__icontains", "person__last_name__icontains",],
+        label=_("Search by name"),
     )
 
     class Meta:
@@ -68,8 +59,8 @@ class VoucherFilter(FilterSet):
 
         self.form.layout = Layout(Row("event", "name"))
 
-class FeedbackAspectsFilter(FilterSet):
 
+class FeedbackAspectsFilter(FilterSet):
     class Meta:
         model = FeedbackAspect
         fields = ["aspect"]
diff --git a/aleksis/apps/ticdesk/forms.py b/aleksis/apps/ticdesk/forms.py
index 2ac0abb..d04ca81 100644
--- a/aleksis/apps/ticdesk/forms.py
+++ b/aleksis/apps/ticdesk/forms.py
@@ -2,23 +2,28 @@ import re
 from collections import OrderedDict
 
 from django import forms
-from django.apps import apps
-from django.core import validators
 from django.utils.translation import ugettext_lazy as _
-from django_select2.forms import ModelSelect2MultipleWidget, Select2Widget
 
 import phonenumbers
+from django_select2.forms import ModelSelect2MultipleWidget
 from django_starfield import Stars
-from localflavor.generic.countries.sepa import IBAN_SEPA_COUNTRIES
-from localflavor.generic.forms import IBANFormField
 from material import Fieldset, Layout, Row
 
-from .models import Voucher, TeckidsProject, MailAddress, SSHKey, PGPKey, EventRegistration, TeckidsMember, FeedbackAspect
-
-from aleksis.core.models import Person, Group
 from aleksis.core.mixins import ExtensibleForm
+from aleksis.core.models import Group, Person
 from aleksis.core.util.core_helpers import get_site_preferences
 
+from .models import (
+    EventRegistration,
+    FeedbackAspect,
+    MailAddress,
+    PGPKey,
+    SSHKey,
+    TeckidsMember,
+    TeckidsProject,
+    Voucher,
+)
+
 COMMENT_CHOICES = [
     ("first", _("Only first name")),
     ("first_age", _("First name and age")),
@@ -36,7 +41,8 @@ LICENCE_CHOICES = [
     (
         "CC-BY-SA-4.0+",
         _(
-            "Creative Commons with attribution and distribution only under the same conditions, 4.0 or later"
+            "Creative Commons with attribution and distribution only"
+            "under the same conditions, 4.0 or later"
         ),
     ),
 ]
@@ -82,6 +88,7 @@ def clean_phonenumber(field):
 
     return _clean_phonenumber
 
+
 class EventAdditionalSurveyForm(forms.Form):
     def __init__(self, project, *args, **kwargs):
         super().__init__(*args, **kwargs)
@@ -128,30 +135,25 @@ class EventAdditionalSurveyForm(forms.Form):
 
 
 class EventFeedbackForm(ExtensibleForm):
-
     class Meta:
-         model = FeedbackAspect
-         fields = []
+        model = FeedbackAspect
+        fields = []
 
     layout = Layout(
         Fieldset(
             _("Comments"),
             Row("comment_private", "comment_public", "comment_public_info"),
         ),
-        Fieldset(
-            _("Photos"),
-            Row("photos", "photos_licence"),
-        ),
-        Fieldset(
-            _("Feedback aspects"),
-        ),
+        Fieldset(_("Photos"), Row("photos", "photos_licence"),),
+        Fieldset(_("Feedback aspects"),),
     )
 
     comment_private = forms.CharField(
         required=False,
         label=_("Comment for the team"),
         help_text=_(
-            "This comment is for the team only. You can write down everything you would like to give us as feedback here."
+            "This comment is for the team only. You can write down everything you"
+            "would like to give us as feedback here."
         ),
         widget=forms.Textarea,
     )
@@ -160,7 +162,9 @@ class EventFeedbackForm(ExtensibleForm):
         required=False,
         label=_("Comment for the website"),
         help_text=_(
-            "This comment is for the report on our website. Tell in detail about what you experienced, what you liked, what you learned and everything else you can think of."
+            "This comment is for the report on our website. Tell in detail about what"
+            "you experienced, what you liked, what you learned and everything else"
+            "you can think of."
         ),
         widget=forms.Textarea,
     )
@@ -176,7 +180,8 @@ class EventFeedbackForm(ExtensibleForm):
         widget=forms.ClearableFileInput(attrs={"multiple": True}),
         required=False,
         help_text=_(
-            "If you want to contribute photos to the report, you can upload them here. You can select multiple files in most file selection dialogs with CTRL + click."
+            "If you want to contribute photos to the report, you can upload them here. You can"
+            "select multiple files in most file selection dialogs with CTRL + click."
         ),
     )
 
@@ -192,10 +197,7 @@ class EventFeedbackForm(ExtensibleForm):
         self._project = project
 
         for aspect in project.feedback_aspects.all():
-            field = forms.IntegerField(
-                widget=Stars,
-                required=False,
-            )
+            field = forms.IntegerField(widget=Stars, required=False,)
             self.fields[aspect.aspect] = field
             node = Fieldset(f"{aspect.aspect}", f"{aspect.aspect}")
             self.add_node_to_layout(node)
@@ -205,19 +207,9 @@ class EditEventForm(forms.ModelForm):
     """Form to create or edit an event."""
 
     layout = Layout(
-        Fieldset(
-            _("Base data"),
-            Row("short_name", "display_name"),
-            "description",
-        ),
-        Fieldset(
-            _("Persons"),
-            Row("members", "owners"),
-        ),
-        Fieldset(
-            _("Feedback aspects"),
-            "feedback_aspects",
-        ),
+        Fieldset(_("Base data"), Row("short_name", "display_name"), "description",),
+        Fieldset(_("Persons"), Row("members", "owners"),),
+        Fieldset(_("Feedback aspects"), "feedback_aspects",),
         Fieldset(
             _("Event settings"),
             Row("date_event", "date_registration", "date_retraction"),
@@ -226,7 +218,6 @@ class EditEventForm(forms.ModelForm):
             "published",
         ),
     )
-            
 
     class Meta:
         model = TeckidsProject
@@ -244,7 +235,8 @@ class EditEventForm(forms.ModelForm):
 
 MAIL_LOCAL_REGEX = r"^[a-z][a-z0-9._-]{1,29}$"
 MAIL_LOCAL_MESSAGE = _(
-    "The local part of the email address may only consist of lowercase letters, numbers, periods, hyphens and underscores and must begin with a letter!"
+    "The local part of the email address may only consist of lowercase letters, numbers,"
+    "periods, hyphens and underscores and must begin with a letter!"
 )
 
 
@@ -322,18 +314,9 @@ class RegisterEventForm(forms.ModelForm):
             Row("street", "housenumber"),
             Row("postal_code", "place"),
         ),
-        Fieldset(
-            _("Contact details"),
-            Row("mobile_number", "email"),
-        ),
-        Fieldset(
-            _("Personal data"),
-            Row("date_of_birth", "sex"),
-        ),
-        Fieldset(
-            _("School details"),
-            Row("school", "school_place", "school_class"),
-        ),
+        Fieldset(_("Contact details"), Row("mobile_number", "email"),),
+        Fieldset(_("Personal data"), Row("date_of_birth", "sex"),),
+        Fieldset(_("School details"), Row("school", "school_place", "school_class"),),
         Fieldset(
             _("Guardians personal data"),
             Row("guardian_first_name", "guardian_last_name"),
@@ -348,9 +331,7 @@ class RegisterEventForm(forms.ModelForm):
             Row("comment", "channel"),
         ),
         Fieldset(
-            _("Financial data"),
-            "voucher_code",
-            Row("iban", "donation", "accept_sepa"),
+            _("Financial data"), "voucher_code", Row("iban", "donation", "accept_sepa"),
         ),
         Fieldset(
             _("Declaration of consent"),
@@ -362,99 +343,144 @@ class RegisterEventForm(forms.ModelForm):
         model = EventRegistration
         exclude = ["date_registred", "voucher"]
         help_texts = {
-            "voucher": _("If you have a voucher for the event, enter the code here. It will be charged automatically."),
-            "donation": ("Our association would like to offer all children and young people the opportunity to participate in our events. Often, however, the family fee cannot be paid. We therefore have a budget from which we can promote participation after we have carefully examined the necessity and eligibility. We rely on donations for this budget. If you would like to donate a voluntary additional amount for this budget, please indicate this here. We do not permanently save whether and if so in what amount donations are made and also not within the association, e.g. passed on to leisure supervisors."),
-            "accept_sepa": _("Parents: I authorize the creditor Teckids e.V., Rochusstr. 2-4, 53123 Bonn with creditor ID DE70FZT00001497650, to collect the participant fee from my account once using the SEPA core direct debit. At the same time, I instruct my bank to redeem the SEPA core direct debit withdrawn from my account by Teckids e.V."),
-            "iban": _("If your parents want to pay by SEPA direct debit, please let them fill out this field."),
-            "accept_terms": _("Parents: My child filled out the registration form together with me, but myself, and I agree to the participation, the terms of use and the terms and conditions. I am aware that the registration is binding and that withdrawal is only possible in exceptional cases with a valid reason. In addition, I agree to pay the participation fee in advance and agree to the reimbursement guidelines mentioned above."),
-            "accept_data": _("I consent to the processing of my data as stated in the <a href='https://www.teckids.org/kleingedrucktes/nutzungsbedingungen/'> terms of use </a> and all the data provided is correct. If I am under the age of 16, my parents also agree to this and I can prove this on request (e.g. by making contact with my parents)."),
-            "accept_general_terms": _("I agree with the <a href='https://www.teckids.org/de/kleingedrucktes/agb-fur-freizeiten/'> AGB </a> and have read them."),
+            "voucher": _(
+                "If you have a voucher for the event, enter the code here."
+                "It will be charged automatically."
+            ),
+            "donation": (
+                "Our association would like to offer all children and young"
+                "people the opportunity to participate in our events. Often,"
+                "however, the family fee cannot be paid. We therefore have a"
+                "budget from which we can promote participation after we have"
+                "carefully examined the necessity and eligibility. We rely on"
+                "donations for this budget. If you would like to donate a voluntary"
+                "additional amount for this budget, please indicate this here. We do"
+                "not permanently save whether and if so in what amount donations are"
+                "made and also not within the association, e.g. passed on to leisure supervisors."
+            ),
+            "accept_sepa": _(
+                "Parents: I authorize the creditor Teckids e.V., Rochusstr. 2-4, 53123 Bonn with"
+                "creditor ID DE70FZT00001497650, to collect the participant fee from my account"
+                "once using the SEPA core direct debit. At the same time, I instruct my bank to"
+                "redeem the SEPA core direct debit withdrawn from my account by Teckids e.V."
+            ),
+            "iban": _(
+                "If your parents want to pay by SEPA direct debit,"
+                "please let them fill out this field."
+            ),
+            "accept_terms": _(
+                "Parents: My child filled out the registration form together with me, but myself,"
+                "and I agree to the participation, the terms of use and the terms and conditions."
+                "I am aware that the registration is binding and that withdrawal is only possible"
+                "in exceptional cases with a valid reason. In addition, I agree to pay the"
+                "participation fee in advance and agree to the reimbursement guidelines"
+                "mentioned above."
+            ),
+            "accept_data": _(
+                "I consent to the processing of my data as stated in the"
+                "<a href='https://www.teckids.org/kleingedrucktes/nutzungsbedingungen/'>"
+                "terms of use </a> and all the data provided is correct. If I am under the"
+                "age of 16, my parents also agree to this and I can prove this on request"
+                "(e.g. by making contact with my parents)."
+            ),
+            "accept_general_terms": _(
+                "I agree with the"
+                "<a href='https://www.teckids.org/de/kleingedrucktes/agb-fur-freizeiten/'>"
+                "AGB </a> and have read them."
+            ),
             "channel": _("How did you find out about the event?"),
         }
 
     guardian_first_name = forms.CharField(
-        label = _("Guardians first name"),
-        help_text = _("Please enter the first name of the legal guardian who will fill in the registration with you and who can be reached during the event in an emergency."),
+        label=_("Guardians first name"),
+        help_text=_(
+            "Please enter the first name of the legal guardian who will fill in the registration"
+            "with you and who can be reached during the event in an emergency."
+        ),
     )
 
     guardian_last_name = forms.CharField(
-        label = _("Guardians last name"),
-        help_text = _("Please enter the last name of the legal guardian who will fill in the registration with you and who can be reached during the event in an emergency."),
+        label=_("Guardians last name"),
+        help_text=_(
+            "Please enter the last name of the legal guardian who will fill in the registration"
+            "with you and who can be reached during the event in an emergency."
+        ),
     )
 
     guardian_mobile_number = forms.CharField(
-        label = _("Guardians mobile number"),
-        help_text = _("We need the mobile phone number for emergencies if we urgently need to reach your parents during the event."),
+        label=_("Guardians mobile number"),
+        help_text=_(
+            "We need the mobile phone number for emergencies if we"
+            "urgently need to reach your parents during the event."
+        ),
     )
 
-    guardian_email = forms.EmailField(
-        label = _("Guardians email address"),
-    )
+    guardian_email = forms.EmailField(label=_("Guardians email address"),)
 
     voucher_code = forms.CharField(
-        label = _("Voucher code"),
-        help_text = _("If you have a voucher code, type it in here."),
-        required = False,
+        label=_("Voucher code"),
+        help_text=_("If you have a voucher code, type it in here."),
+        required=False,
     )
 
-    street = forms.CharField(
-        label = _("Street"),
-    )
+    street = forms.CharField(label=_("Street"),)
 
-    housenumber = forms.CharField(
-        label = _("Housenumber"),
-    )
+    housenumber = forms.CharField(label=_("Housenumber"),)
 
-    postal_code = forms.CharField(
-        label = _("Postal code"),
-    )
+    postal_code = forms.CharField(label=_("Postal code"),)
 
-    place = forms.CharField(
-        label = _("Place"),
-    )
+    place = forms.CharField(label=_("Place"),)
 
     mobile_number = forms.CharField(
-        label = _("Mobile number"),
-        required = False,
-        help_text = _("Your mobile number helps us to reach you in an emergency during the event, e.g. if you are alone with your group at a conference or similar. If you don't have a cell phone, you can leave the field blank."),
-        validators = [is_phonenumber],
+        label=_("Mobile number"),
+        required=False,
+        help_text=_(
+            "Your mobile number helps us to reach you in an emergency during the event, e.g."
+            "if you are alone with your group at a conference or similar. If you don't have a"
+            "cell phone, you can leave the field blank."
+        ),
+        validators=[is_phonenumber],
     )
 
-    date_of_birth = forms.DateField(
-        label = _("Date of birth"),
-    )
+    date_of_birth = forms.DateField(label=_("Date of birth"),)
 
     sex = forms.ChoiceField(
-        label = _("Sex"),
-        help_text = _("For various reasons, e.g. because we have to keep gender segregation during the night for legal reasons, we need to know if you are a boy or a girl. With some names this is not always immediately recognizable, so we ask you to indicate it here."),
-        choices = Person.SEX_CHOICES,
-        initial = None,
+        label=_("Sex"),
+        help_text=_(
+            "For various reasons, e.g. because we have to keep gender segregation during the night"
+            "for legal reasons, we need to know if you are a boy or a girl. With some names this is"
+            "not always immediately recognizable, so we ask you to indicate it here."
+        ),
+        choices=Person.SEX_CHOICES,
+        initial=None,
     )
 
-    email = forms.EmailField(
-        label = _("Email address"),
-    )
+    email = forms.EmailField(label=_("Email address"),)
 
     school = forms.CharField(
-        label = _("School"),
-        help_text = _("Please enter the name of your school as exactly as it should be written."),
+        label=_("School"),
+        help_text=_(
+            "Please enter the name of your school as exactly as it should be written."
+        ),
     )
 
     school_place = forms.CharField(
-        label = _("School place"),
-        help_text = _("Enter the place (city) where your school is located (without a district)."),
+        label=_("School place"),
+        help_text=_(
+            "Enter the place (city) where your school is located (without a district)."
+        ),
     )
 
     school_class = forms.CharField(
-        label = _("School class"),
-        help_text = _("Please enter the class you are going to (e.g. 8a)."),
+        label=_("School class"),
+        help_text=_("Please enter the class you are going to (e.g. 8a)."),
     )
 
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
 
         self.fields["event"].disabled = True
-        self.fields["person"].disabled = True    
+        self.fields["person"].disabled = True
         self.fields["accept_terms"].required = True
         self.fields["accept_general_terms"].required = True
         self.fields["accept_data"].required = True
@@ -470,10 +496,7 @@ class EditTeckidsMemberForm(forms.ModelForm):
             Row("employee_number", "title"),
             Row("member_since", "member_until"),
         ),
-        Fieldset(
-            _("Financial information"),
-            Row("dues", "dues_sepa"),
-        ),
+        Fieldset(_("Financial information"), Row("dues", "dues_sepa"),),
     )
 
     class Meta:
@@ -481,7 +504,9 @@ class EditTeckidsMemberForm(forms.ModelForm):
         exclude = []
         help_texts = {
             "employee_number": _("Only change if you know what you're doing!"),
-            "title": _("Members title. For example: „Systemadministrator, Tutor Spieleprogrammieren“"),
+            "title": _(
+                "Members title. For example: „Systemadministrator, Tutor Spieleprogrammieren“"
+            ),
             "member_since": _("Start of membership"),
             "member_until": _("End of membership"),
             "dues": _("Yearly payed membership dues"),
@@ -499,9 +524,7 @@ class EditEventRegistrationForm(forms.ModelForm):
             Row("comment", "channel"),
         ),
         Fieldset(
-            _("Financial data"),
-            "voucher_code",
-            Row("iban", "donation", "accept_sepa"),
+            _("Financial data"), "voucher_code", Row("iban", "donation", "accept_sepa"),
         ),
         Fieldset(
             _("Declaration of consent"),
@@ -512,20 +535,57 @@ class EditEventRegistrationForm(forms.ModelForm):
     class Meta:
         model = EventRegistration
         help_texts = {
-            "voucher": _("If you have a voucher for the event, enter the code here. It will be charged automatically."),
-            "donation": ("Our association would like to offer all children and young people the opportunity to participate in our events. Often, however, the family fee cannot be paid. We therefore have a budget from which we can promote participation after we have carefully examined the necessity and eligibility. We rely on donations for this budget. If you would like to donate a voluntary additional amount for this budget, please indicate this here. We do not permanently save whether and if so in what amount donations are made and also not within the association, e.g. passed on to leisure supervisors."),
-            "accept_sepa": _("Parents: I authorize the creditor Teckids e.V., Rochusstr. 2-4, 53123 Bonn with creditor ID DE70FZT00001497650, to collect the participant fee from my account once using the SEPA core direct debit. At the same time, I instruct my bank to redeem the SEPA core direct debit withdrawn from my account by Teckids e.V."),
-            "iban": _("If your parents want to pay by SEPA direct debit, please let them fill out this field."),
-            "accept_terms": _("Parents: My child filled out the registration form together with me, but myself, and I agree to the participation, the terms of use and the terms and conditions. I am aware that the registration is binding and that withdrawal is only possible in exceptional cases with a valid reason. In addition, I agree to pay the participation fee in advance and agree to the reimbursement guidelines mentioned above."),
-            "accept_data": _("I consent to the processing of my data as stated in the <a href='https://www.teckids.org/kleingedrucktes/nutzungsbedingungen/'> terms of use </a> and all the data provided is correct. If I am under the age of 16, my parents also agree to this and I can prove this on request (e.g. by making contact with my parents)."),
-            "accept_general_terms": _("I agree with the <a href='https://www.teckids.org/de/kleingedrucktes/agb-fur-freizeiten/'> AGB </a> and have read them."),
+            "voucher": _(
+                "If you have a voucher for the event, enter the code here."
+                "It will be charged automatically."
+            ),
+            "donation": (
+                "Our association would like to offer all children and young"
+                "people the opportunity to participate in our events. Often,"
+                "however, the family fee cannot be paid. We therefore have a"
+                "budget from which we can promote participation after we have"
+                "carefully examined the necessity and eligibility. We rely on"
+                "donations for this budget. If you would like to donate a voluntary"
+                "additional amount for this budget, please indicate this here. We do not"
+                "permanently save whether and if so in what amount donations are made"
+                "and also not within the association, e.g. passed on to leisure supervisors."
+            ),
+            "accept_sepa": _(
+                "Parents: I authorize the creditor Teckids e.V., Rochusstr. 2-4, 53123 Bonn with"
+                "creditor ID DE70FZT00001497650, to collect the participant fee from my account"
+                "once using the SEPA core direct debit. At the same time, I instruct my bank"
+                "to redeem the SEPA core direct debit withdrawn from my account by Teckids e.V."
+            ),
+            "iban": _(
+                "If your parents want to pay by SEPA direct debit,"
+                "please let them fill out this field."
+            ),
+            "accept_terms": _(
+                "Parents: My child filled out the registration form together with me, but myself,"
+                "and I agree to the participation, the terms of use and the terms and conditions."
+                "I am aware that the registration is binding and that withdrawal is only possible"
+                "in exceptional cases with a valid reason. In addition, I agree to pay the"
+                "participation fee in advance and agree to the reimbursement"
+                "guidelines mentioned above."
+            ),
+            "accept_data": _(
+                "I consent to the processing of my data as stated in the"
+                "<a href='https://www.teckids.org/kleingedrucktes/nutzungsbedingungen/'>"
+                "terms of use </a> and all the data provided is correct. If I am under"
+                "the age of 16, my parents also agree to this and I can prove this on"
+                "request (e.g. by making contact with my parents)."
+            ),
+            "accept_general_terms": _(
+                "I agree with the"
+                "<a href='https://www.teckids.org/de/kleingedrucktes/agb-fur-freizeiten/'>"
+                "AGB </a> and have read them."
+            ),
             "channel": _("How did you find out about the event?"),
         }
         exclude = []
 
 
 class EditFeedbackAspectForm(forms.ModelForm):
-
     class Meta:
         model = FeedbackAspect
         exclude = []
diff --git a/aleksis/apps/ticdesk/menus.py b/aleksis/apps/ticdesk/menus.py
index 65688c1..14a51f2 100644
--- a/aleksis/apps/ticdesk/menus.py
+++ b/aleksis/apps/ticdesk/menus.py
@@ -12,16 +12,8 @@ MENUS = {
                 ("aleksis.core.util.predicates.permission_validator", "core.has_person")
             ],
             "submenu": [
-                {
-                    "name": _("Mail addresses"),
-                    "url": "manage_mail",
-                    "icon": "email",
-                },
-                {
-                    "name": _("SSH keys"),
-                    "url": "manage_ssh_keys",
-                    "icon": "vpn_key",
-                },
+                {"name": _("Mail addresses"), "url": "manage_mail", "icon": "email",},
+                {"name": _("SSH keys"), "url": "manage_ssh_keys", "icon": "vpn_key",},
                 {
                     "name": _("PGP keys"),
                     "url": "manage_pgp_keys",
@@ -146,16 +138,8 @@ MENUS = {
                 )
             ],
             "submenu": [
-                {
-                    "name": _("List members"),
-                    "icon": "list",
-                    "url": "members",
-                },
-                {
-                    "name": _("New member"),
-                    "icon": "add",
-                    "url": "create_member",
-                },
+                {"name": _("List members"), "icon": "list", "url": "members",},
+                {"name": _("New member"), "icon": "add", "url": "create_member",},
             ],
         },
     ],
diff --git a/aleksis/apps/ticdesk/migrations/0007_auto_20200728_1145.py b/aleksis/apps/ticdesk/migrations/0007_auto_20200728_1145.py
index 268fd55..e8a18d4 100644
--- a/aleksis/apps/ticdesk/migrations/0007_auto_20200728_1145.py
+++ b/aleksis/apps/ticdesk/migrations/0007_auto_20200728_1145.py
@@ -9,41 +9,118 @@ import django.db.models.deletion
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('core', '0004_auto_20200728_1145'),
-        ('sites', '0002_alter_domain_unique'),
-        ('ticdesk', '0006_teckidsmember'),
+        ("core", "0004_auto_20200728_1145"),
+        ("sites", "0002_alter_domain_unique"),
+        ("ticdesk", "0006_teckidsmember"),
     ]
 
     operations = [
         migrations.AlterModelOptions(
-            name='globalpermissions',
-            options={'managed': False, 'permissions': (('view_vouchers', 'Can view vouchers'), ('edit_vouchers', 'Can edit vouchers'), ('delete_vouchers', 'Can delete vouchers'), ('create_vouchers', 'Can create vouchers'), ('edit_events', 'Can edit events'), ('create_events', 'Can create events'), ('delete_events', 'Can delete events'), ('generate_lists', 'Can generate lists'))},
+            name="globalpermissions",
+            options={
+                "managed": False,
+                "permissions": (
+                    ("view_vouchers", "Can view vouchers"),
+                    ("edit_vouchers", "Can edit vouchers"),
+                    ("delete_vouchers", "Can delete vouchers"),
+                    ("create_vouchers", "Can create vouchers"),
+                    ("edit_events", "Can edit events"),
+                    ("create_events", "Can create events"),
+                    ("delete_events", "Can delete events"),
+                    ("generate_lists", "Can generate lists"),
+                ),
+            },
         ),
         migrations.CreateModel(
-            name='EventRegistration',
+            name="EventRegistration",
             fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('extended_data', django.contrib.postgres.fields.jsonb.JSONField(default=dict, editable=False)),
-                ('date_registred', models.DateTimeField(auto_now_add=True, verbose_name='Registration date')),
-                ('comment', models.TextField(verbose_name='Comment / remarks')),
-                ('channel', models.CharField(max_length=255, verbose_name='Channel')),
-                ('donation', models.PositiveIntegerField(verbose_name='Donation')),
-                ('accept_sepa', models.BooleanField(verbose_name='SEPA direct debit')),
-                ('iban', models.CharField(max_length=255, verbose_name='IBAN (for SEPA direct debit)')),
-                ('accept_terms', models.BooleanField(verbose_name='Delcaration of consent by parents')),
-                ('accept_data', models.BooleanField(verbose_name='Declaration of consent data protection')),
-                ('accept_general_terms', models.BooleanField(verbose_name='Declatation of consent terms and condition')),
-                ('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ticdesk.TeckidsProject', verbose_name='Event')),
-                ('person', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.Person', verbose_name='Person')),
-                ('site', models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.CASCADE, to='sites.Site')),
-                ('voucher', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ticdesk.Voucher', verbose_name='Voucher')),
+                (
+                    "id",
+                    models.AutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                (
+                    "extended_data",
+                    django.contrib.postgres.fields.jsonb.JSONField(
+                        default=dict, editable=False
+                    ),
+                ),
+                (
+                    "date_registred",
+                    models.DateTimeField(
+                        auto_now_add=True, verbose_name="Registration date"
+                    ),
+                ),
+                ("comment", models.TextField(verbose_name="Comment / remarks")),
+                ("channel", models.CharField(max_length=255, verbose_name="Channel")),
+                ("donation", models.PositiveIntegerField(verbose_name="Donation")),
+                ("accept_sepa", models.BooleanField(verbose_name="SEPA direct debit")),
+                (
+                    "iban",
+                    models.CharField(
+                        max_length=255, verbose_name="IBAN (for SEPA direct debit)"
+                    ),
+                ),
+                (
+                    "accept_terms",
+                    models.BooleanField(
+                        verbose_name="Delcaration of consent by parents"
+                    ),
+                ),
+                (
+                    "accept_data",
+                    models.BooleanField(
+                        verbose_name="Declaration of consent data protection"
+                    ),
+                ),
+                (
+                    "accept_general_terms",
+                    models.BooleanField(
+                        verbose_name="Declatation of consent terms and condition"
+                    ),
+                ),
+                (
+                    "event",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to="ticdesk.TeckidsProject",
+                        verbose_name="Event",
+                    ),
+                ),
+                (
+                    "person",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to="core.Person",
+                        verbose_name="Person",
+                    ),
+                ),
+                (
+                    "site",
+                    models.ForeignKey(
+                        default=1,
+                        editable=False,
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to="sites.Site",
+                    ),
+                ),
+                (
+                    "voucher",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to="ticdesk.Voucher",
+                        verbose_name="Voucher",
+                    ),
+                ),
             ],
             options={
-                'verbose_name': 'Registration',
-                'verbose_name_plural': 'Registrations',
+                "verbose_name": "Registration",
+                "verbose_name_plural": "Registrations",
             },
-            managers=[
-                ('objects', django.contrib.sites.managers.CurrentSiteManager()),
-            ],
+            managers=[("objects", django.contrib.sites.managers.CurrentSiteManager()),],
         ),
     ]
diff --git a/aleksis/apps/ticdesk/migrations/0008_auto_20200728_1352.py b/aleksis/apps/ticdesk/migrations/0008_auto_20200728_1352.py
index 1bdffab..22d621c 100644
--- a/aleksis/apps/ticdesk/migrations/0008_auto_20200728_1352.py
+++ b/aleksis/apps/ticdesk/migrations/0008_auto_20200728_1352.py
@@ -6,12 +6,16 @@ from django.db import migrations
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('ticdesk', '0007_auto_20200728_1145'),
+        ("ticdesk", "0007_auto_20200728_1145"),
     ]
 
     operations = [
         migrations.AlterModelOptions(
-            name='teckidsproject',
-            options={'ordering': ('date_event',), 'verbose_name': 'Teckids project', 'verbose_name_plural': 'Teckids projects'},
+            name="teckidsproject",
+            options={
+                "ordering": ("date_event",),
+                "verbose_name": "Teckids project",
+                "verbose_name_plural": "Teckids projects",
+            },
         ),
     ]
diff --git a/aleksis/apps/ticdesk/migrations/0009_auto_20200728_1504.py b/aleksis/apps/ticdesk/migrations/0009_auto_20200728_1504.py
index 9a9bedb..9db8982 100644
--- a/aleksis/apps/ticdesk/migrations/0009_auto_20200728_1504.py
+++ b/aleksis/apps/ticdesk/migrations/0009_auto_20200728_1504.py
@@ -6,13 +6,24 @@ from django.db import migrations, models
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('ticdesk', '0008_auto_20200728_1352'),
+        ("ticdesk", "0008_auto_20200728_1352"),
     ]
 
     operations = [
         migrations.AlterField(
-            model_name='eventregistration',
-            name='channel',
-            field=models.CharField(choices=[('none', 'No information'), ('internet', 'From internet'), ('school', 'From school'), ('friends', 'From friends'), ('parents', 'From parents'), ('newsletter', 'From newsletter')], max_length=255, verbose_name='Channel'),
+            model_name="eventregistration",
+            name="channel",
+            field=models.CharField(
+                choices=[
+                    ("none", "No information"),
+                    ("internet", "From internet"),
+                    ("school", "From school"),
+                    ("friends", "From friends"),
+                    ("parents", "From parents"),
+                    ("newsletter", "From newsletter"),
+                ],
+                max_length=255,
+                verbose_name="Channel",
+            ),
         ),
     ]
diff --git a/aleksis/apps/ticdesk/migrations/0010_auto_20200728_2017.py b/aleksis/apps/ticdesk/migrations/0010_auto_20200728_2017.py
index 5d6d59d..870b4a6 100644
--- a/aleksis/apps/ticdesk/migrations/0010_auto_20200728_2017.py
+++ b/aleksis/apps/ticdesk/migrations/0010_auto_20200728_2017.py
@@ -6,28 +6,50 @@ from django.db import migrations, models
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('ticdesk', '0009_auto_20200728_1504'),
+        ("ticdesk", "0009_auto_20200728_1504"),
     ]
 
     operations = [
         migrations.AlterField(
-            model_name='eventregistration',
-            name='channel',
-            field=models.CharField(blank=True, choices=[('none', 'No information'), ('internet', 'From internet'), ('school', 'From school'), ('friends', 'From friends'), ('parents', 'From parents'), ('newsletter', 'From newsletter')], max_length=255, null=True, verbose_name='Channel'),
+            model_name="eventregistration",
+            name="channel",
+            field=models.CharField(
+                blank=True,
+                choices=[
+                    ("none", "No information"),
+                    ("internet", "From internet"),
+                    ("school", "From school"),
+                    ("friends", "From friends"),
+                    ("parents", "From parents"),
+                    ("newsletter", "From newsletter"),
+                ],
+                max_length=255,
+                null=True,
+                verbose_name="Channel",
+            ),
         ),
         migrations.AlterField(
-            model_name='eventregistration',
-            name='comment',
-            field=models.TextField(blank=True, null=True, verbose_name='Comment / remarks'),
+            model_name="eventregistration",
+            name="comment",
+            field=models.TextField(
+                blank=True, null=True, verbose_name="Comment / remarks"
+            ),
         ),
         migrations.AlterField(
-            model_name='eventregistration',
-            name='donation',
-            field=models.PositiveIntegerField(blank=True, null=True, verbose_name='Donation'),
+            model_name="eventregistration",
+            name="donation",
+            field=models.PositiveIntegerField(
+                blank=True, null=True, verbose_name="Donation"
+            ),
         ),
         migrations.AlterField(
-            model_name='eventregistration',
-            name='iban',
-            field=models.CharField(blank=True, max_length=255, null=True, verbose_name='IBAN (for SEPA direct debit)'),
+            model_name="eventregistration",
+            name="iban",
+            field=models.CharField(
+                blank=True,
+                max_length=255,
+                null=True,
+                verbose_name="IBAN (for SEPA direct debit)",
+            ),
         ),
     ]
diff --git a/aleksis/apps/ticdesk/migrations/0011_auto_20200728_2119.py b/aleksis/apps/ticdesk/migrations/0011_auto_20200728_2119.py
index 8094746..fb89126 100644
--- a/aleksis/apps/ticdesk/migrations/0011_auto_20200728_2119.py
+++ b/aleksis/apps/ticdesk/migrations/0011_auto_20200728_2119.py
@@ -6,12 +6,26 @@ from django.db import migrations
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('ticdesk', '0010_auto_20200728_2017'),
+        ("ticdesk", "0010_auto_20200728_2017"),
     ]
 
     operations = [
         migrations.AlterModelOptions(
-            name='globalpermissions',
-            options={'managed': False, 'permissions': (('view_vouchers', 'Can view vouchers'), ('edit_vouchers', 'Can edit vouchers'), ('delete_vouchers', 'Can delete vouchers'), ('create_vouchers', 'Can create vouchers'), ('edit_events', 'Can edit events'), ('create_events', 'Can create events'), ('delete_events', 'Can delete events'), ('generate_lists', 'Can generate lists'), ('manage_registrations', 'Can manage registrations'), ('view_registrations', 'Can view registrations'))},
+            name="globalpermissions",
+            options={
+                "managed": False,
+                "permissions": (
+                    ("view_vouchers", "Can view vouchers"),
+                    ("edit_vouchers", "Can edit vouchers"),
+                    ("delete_vouchers", "Can delete vouchers"),
+                    ("create_vouchers", "Can create vouchers"),
+                    ("edit_events", "Can edit events"),
+                    ("create_events", "Can create events"),
+                    ("delete_events", "Can delete events"),
+                    ("generate_lists", "Can generate lists"),
+                    ("manage_registrations", "Can manage registrations"),
+                    ("view_registrations", "Can view registrations"),
+                ),
+            },
         ),
     ]
diff --git a/aleksis/apps/ticdesk/migrations/0012_auto_20200729_1640.py b/aleksis/apps/ticdesk/migrations/0012_auto_20200729_1640.py
index 843b361..7d14d61 100644
--- a/aleksis/apps/ticdesk/migrations/0012_auto_20200729_1640.py
+++ b/aleksis/apps/ticdesk/migrations/0012_auto_20200729_1640.py
@@ -6,18 +6,24 @@ from django.db import migrations, models
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('ticdesk', '0011_auto_20200728_2119'),
+        ("ticdesk", "0011_auto_20200728_2119"),
     ]
 
     operations = [
         migrations.AddField(
-            model_name='teckidsmember',
-            name='dues',
-            field=models.PositiveIntegerField(default=60, verbose_name='Membership dues in €'),
+            model_name="teckidsmember",
+            name="dues",
+            field=models.PositiveIntegerField(
+                default=60, verbose_name="Membership dues in €"
+            ),
         ),
         migrations.AddField(
-            model_name='teckidsmember',
-            name='dues_sepa',
-            field=models.DateField(blank=True, null=True, verbose_name='Membership dues SEPA direct debit mandate'),
+            model_name="teckidsmember",
+            name="dues_sepa",
+            field=models.DateField(
+                blank=True,
+                null=True,
+                verbose_name="Membership dues SEPA direct debit mandate",
+            ),
         ),
     ]
diff --git a/aleksis/apps/ticdesk/migrations/0013_auto_20200729_1738.py b/aleksis/apps/ticdesk/migrations/0013_auto_20200729_1738.py
index 6345fe0..638db84 100644
--- a/aleksis/apps/ticdesk/migrations/0013_auto_20200729_1738.py
+++ b/aleksis/apps/ticdesk/migrations/0013_auto_20200729_1738.py
@@ -6,13 +6,13 @@ from django.db import migrations, models
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('ticdesk', '0012_auto_20200729_1640'),
+        ("ticdesk", "0012_auto_20200729_1640"),
     ]
 
     operations = [
         migrations.AlterField(
-            model_name='teckidsmember',
-            name='member_until',
-            field=models.DateField(blank=True, null=True, verbose_name='Member until'),
+            model_name="teckidsmember",
+            name="member_until",
+            field=models.DateField(blank=True, null=True, verbose_name="Member until"),
         ),
     ]
diff --git a/aleksis/apps/ticdesk/migrations/0014_auto_20200731_1511.py b/aleksis/apps/ticdesk/migrations/0014_auto_20200731_1511.py
index cb42c81..0f619fc 100644
--- a/aleksis/apps/ticdesk/migrations/0014_auto_20200731_1511.py
+++ b/aleksis/apps/ticdesk/migrations/0014_auto_20200731_1511.py
@@ -7,13 +7,19 @@ import django.db.models.deletion
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('ticdesk', '0013_auto_20200729_1738'),
+        ("ticdesk", "0013_auto_20200729_1738"),
     ]
 
     operations = [
         migrations.AlterField(
-            model_name='eventregistration',
-            name='voucher',
-            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='ticdesk.Voucher', verbose_name='Voucher'),
+            model_name="eventregistration",
+            name="voucher",
+            field=models.ForeignKey(
+                blank=True,
+                null=True,
+                on_delete=django.db.models.deletion.CASCADE,
+                to="ticdesk.Voucher",
+                verbose_name="Voucher",
+            ),
         ),
     ]
diff --git a/aleksis/apps/ticdesk/migrations/0015_auto_20200802_1340.py b/aleksis/apps/ticdesk/migrations/0015_auto_20200802_1340.py
index b8aff0c..2b8e021 100644
--- a/aleksis/apps/ticdesk/migrations/0015_auto_20200802_1340.py
+++ b/aleksis/apps/ticdesk/migrations/0015_auto_20200802_1340.py
@@ -6,18 +6,19 @@ from django.db import migrations, models
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('sites', '0002_alter_domain_unique'),
-        ('ticdesk', '0014_auto_20200731_1511'),
+        ("sites", "0002_alter_domain_unique"),
+        ("ticdesk", "0014_auto_20200731_1511"),
     ]
 
     operations = [
-        migrations.RenameModel(
-            old_name='FeedbackAspects',
-            new_name='FeedbackAspect',
-        ),
+        migrations.RenameModel(old_name="FeedbackAspects", new_name="FeedbackAspect",),
         migrations.AddField(
-            model_name='teckidsproject',
-            name='feedback_aspects',
-            field=models.ManyToManyField(related_name='project', to='ticdesk.FeedbackAspect', verbose_name='Feedback aspects'),
+            model_name="teckidsproject",
+            name="feedback_aspects",
+            field=models.ManyToManyField(
+                related_name="project",
+                to="ticdesk.FeedbackAspect",
+                verbose_name="Feedback aspects",
+            ),
         ),
     ]
diff --git a/aleksis/apps/ticdesk/migrations/0016_eventfeedback_feedbackfeedbackaspect.py b/aleksis/apps/ticdesk/migrations/0016_eventfeedback_feedbackfeedbackaspect.py
index db9d9ec..f4cdbeb 100644
--- a/aleksis/apps/ticdesk/migrations/0016_eventfeedback_feedbackfeedbackaspect.py
+++ b/aleksis/apps/ticdesk/migrations/0016_eventfeedback_feedbackfeedbackaspect.py
@@ -9,50 +9,151 @@ import django.db.models.deletion
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('core', '0004_auto_20200728_1145'),
-        ('sites', '0002_alter_domain_unique'),
-        ('ticdesk', '0015_auto_20200802_1340'),
+        ("core", "0004_auto_20200728_1145"),
+        ("sites", "0002_alter_domain_unique"),
+        ("ticdesk", "0015_auto_20200802_1340"),
     ]
 
     operations = [
         migrations.CreateModel(
-            name='FeedbackFeedbackAspect',
+            name="FeedbackFeedbackAspect",
             fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('extended_data', django.contrib.postgres.fields.jsonb.JSONField(default=dict, editable=False)),
-                ('feedback', models.IntegerField()),
-                ('aspect', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ticdesk.FeedbackAspect')),
-                ('person', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.Person')),
-                ('site', models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.CASCADE, to='sites.Site')),
-            ],
-            options={
-                'abstract': False,
-            },
-            managers=[
-                ('objects', django.contrib.sites.managers.CurrentSiteManager()),
+                (
+                    "id",
+                    models.AutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                (
+                    "extended_data",
+                    django.contrib.postgres.fields.jsonb.JSONField(
+                        default=dict, editable=False
+                    ),
+                ),
+                ("feedback", models.IntegerField()),
+                (
+                    "aspect",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to="ticdesk.FeedbackAspect",
+                    ),
+                ),
+                (
+                    "person",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE, to="core.Person"
+                    ),
+                ),
+                (
+                    "site",
+                    models.ForeignKey(
+                        default=1,
+                        editable=False,
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to="sites.Site",
+                    ),
+                ),
             ],
+            options={"abstract": False,},
+            managers=[("objects", django.contrib.sites.managers.CurrentSiteManager()),],
         ),
         migrations.CreateModel(
-            name='EventFeedback',
+            name="EventFeedback",
             fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('extended_data', django.contrib.postgres.fields.jsonb.JSONField(default=dict, editable=False)),
-                ('comment_team', models.TextField(verbose_name='Comment for the team')),
-                ('comment_public', models.TextField(verbose_name='Comment for the website')),
-                ('comment_public_info', models.CharField(choices=[('first', 'Only first name'), ('first_age', 'First name and age'), ('first_last_age', 'First name, last name and age')], max_length=255, verbose_name='Comment info')),
-                ('photos', models.FileField(upload_to='feedback_photos/', verbose_name='Photos')),
-                ('photo_licence', models.CharField(choices=[('CC-BY-4.0+', 'Creative Commons with attribution, 4.0 or later'), ('CC-BY-SA-4.0+', 'Creative Commons with attribution and distribution only under the same conditions, 4.0 or later')], max_length=255, verbose_name='Photo licence')),
-                ('feedback', models.ManyToManyField(to='ticdesk.FeedbackFeedbackAspect')),
-                ('person', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='event_feedback', to='core.Person', verbose_name='Person')),
-                ('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='feedback', to='ticdesk.TeckidsProject', verbose_name='Project')),
-                ('site', models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.CASCADE, to='sites.Site')),
+                (
+                    "id",
+                    models.AutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                (
+                    "extended_data",
+                    django.contrib.postgres.fields.jsonb.JSONField(
+                        default=dict, editable=False
+                    ),
+                ),
+                ("comment_team", models.TextField(verbose_name="Comment for the team")),
+                (
+                    "comment_public",
+                    models.TextField(verbose_name="Comment for the website"),
+                ),
+                (
+                    "comment_public_info",
+                    models.CharField(
+                        choices=[
+                            ("first", "Only first name"),
+                            ("first_age", "First name and age"),
+                            ("first_last_age", "First name, last name and age"),
+                        ],
+                        max_length=255,
+                        verbose_name="Comment info",
+                    ),
+                ),
+                (
+                    "photos",
+                    models.FileField(
+                        upload_to="feedback_photos/", verbose_name="Photos"
+                    ),
+                ),
+                (
+                    "photo_licence",
+                    models.CharField(
+                        choices=[
+                            (
+                                "CC-BY-4.0+",
+                                "Creative Commons with attribution, 4.0 or later",
+                            ),
+                            (
+                                "CC-BY-SA-4.0+",
+                                "Creative Commons with attribution and distribution only under the same conditions, 4.0 or later",
+                            ),
+                        ],
+                        max_length=255,
+                        verbose_name="Photo licence",
+                    ),
+                ),
+                (
+                    "feedback",
+                    models.ManyToManyField(to="ticdesk.FeedbackFeedbackAspect"),
+                ),
+                (
+                    "person",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="event_feedback",
+                        to="core.Person",
+                        verbose_name="Person",
+                    ),
+                ),
+                (
+                    "project",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="feedback",
+                        to="ticdesk.TeckidsProject",
+                        verbose_name="Project",
+                    ),
+                ),
+                (
+                    "site",
+                    models.ForeignKey(
+                        default=1,
+                        editable=False,
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to="sites.Site",
+                    ),
+                ),
             ],
             options={
-                'verbose_name': 'Event feedback',
-                'verbose_name_plural': 'Event feedbacks',
+                "verbose_name": "Event feedback",
+                "verbose_name_plural": "Event feedbacks",
             },
-            managers=[
-                ('objects', django.contrib.sites.managers.CurrentSiteManager()),
-            ],
+            managers=[("objects", django.contrib.sites.managers.CurrentSiteManager()),],
         ),
     ]
diff --git a/aleksis/apps/ticdesk/migrations/0017_auto_20200802_1507.py b/aleksis/apps/ticdesk/migrations/0017_auto_20200802_1507.py
index 19f8163..52aa5ef 100644
--- a/aleksis/apps/ticdesk/migrations/0017_auto_20200802_1507.py
+++ b/aleksis/apps/ticdesk/migrations/0017_auto_20200802_1507.py
@@ -6,23 +6,32 @@ from django.db import migrations, models
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('ticdesk', '0016_eventfeedback_feedbackfeedbackaspect'),
+        ("ticdesk", "0016_eventfeedback_feedbackfeedbackaspect"),
     ]
 
     operations = [
         migrations.AlterField(
-            model_name='eventfeedback',
-            name='comment_public',
-            field=models.TextField(blank=True, null=True, verbose_name='Comment for the website'),
+            model_name="eventfeedback",
+            name="comment_public",
+            field=models.TextField(
+                blank=True, null=True, verbose_name="Comment for the website"
+            ),
         ),
         migrations.AlterField(
-            model_name='eventfeedback',
-            name='comment_team',
-            field=models.TextField(blank=True, null=True, verbose_name='Comment for the team'),
+            model_name="eventfeedback",
+            name="comment_team",
+            field=models.TextField(
+                blank=True, null=True, verbose_name="Comment for the team"
+            ),
         ),
         migrations.AlterField(
-            model_name='eventfeedback',
-            name='photos',
-            field=models.FileField(blank=True, null=True, upload_to='feedback_photos/', verbose_name='Photos'),
+            model_name="eventfeedback",
+            name="photos",
+            field=models.FileField(
+                blank=True,
+                null=True,
+                upload_to="feedback_photos/",
+                verbose_name="Photos",
+            ),
         ),
     ]
diff --git a/aleksis/apps/ticdesk/migrations/0018_auto_20200802_1646.py b/aleksis/apps/ticdesk/migrations/0018_auto_20200802_1646.py
index b625246..e7e0ebd 100644
--- a/aleksis/apps/ticdesk/migrations/0018_auto_20200802_1646.py
+++ b/aleksis/apps/ticdesk/migrations/0018_auto_20200802_1646.py
@@ -6,26 +6,13 @@ from django.db import migrations
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('ticdesk', '0017_auto_20200802_1507'),
+        ("ticdesk", "0017_auto_20200802_1507"),
     ]
 
     operations = [
-        migrations.RemoveField(
-            model_name='feedbackfeedbackaspect',
-            name='aspect',
-        ),
-        migrations.RemoveField(
-            model_name='feedbackfeedbackaspect',
-            name='person',
-        ),
-        migrations.RemoveField(
-            model_name='feedbackfeedbackaspect',
-            name='site',
-        ),
-        migrations.DeleteModel(
-            name='EventFeedback',
-        ),
-        migrations.DeleteModel(
-            name='FeedbackFeedbackAspect',
-        ),
+        migrations.RemoveField(model_name="feedbackfeedbackaspect", name="aspect",),
+        migrations.RemoveField(model_name="feedbackfeedbackaspect", name="person",),
+        migrations.RemoveField(model_name="feedbackfeedbackaspect", name="site",),
+        migrations.DeleteModel(name="EventFeedback",),
+        migrations.DeleteModel(name="FeedbackFeedbackAspect",),
     ]
diff --git a/aleksis/apps/ticdesk/model_extensions.py b/aleksis/apps/ticdesk/model_extensions.py
index a848c13..863b95e 100644
--- a/aleksis/apps/ticdesk/model_extensions.py
+++ b/aleksis/apps/ticdesk/model_extensions.py
@@ -1,22 +1,24 @@
-from django.utils import timezone
 from django.utils.translation import gettext_lazy as _
 
-from jsonstore import CharField, DateField, IntegerField
+from jsonstore import CharField
 
-from aleksis.core.models import Group, Person
+from aleksis.core.models import Person
 
 from .models import TeckidsMember
 
+
 @Person.property_
 def is_member_of(self, group):
     """ Check if person is member of a given group """
 
     return group in self.member_of.all()
 
+
 @Person.property_
 def is_member(self) -> bool:
     """Check if person is still a member."""
-    return (TeckidsMember.objects.filter(person=self).exists())
+    return TeckidsMember.objects.filter(person=self).exists()
+
 
 # Additional fields for persons
 Person.field(school=CharField(verbose_name=_("Name of school")))
diff --git a/aleksis/apps/ticdesk/models.py b/aleksis/apps/ticdesk/models.py
index f937d96..6c51171 100644
--- a/aleksis/apps/ticdesk/models.py
+++ b/aleksis/apps/ticdesk/models.py
@@ -3,7 +3,7 @@ from django.utils import timezone
 from django.utils.translation import gettext_lazy as _
 
 from aleksis.core.mixins import ExtensibleModel, PureDjangoModel
-from aleksis.core.models import Person, Group
+from aleksis.core.models import Person
 
 
 class TeckidsMember(ExtensibleModel):
@@ -11,11 +11,19 @@ class TeckidsMember(ExtensibleModel):
         unique=True, verbose_name=_("Employee number")
     )
     member_since = models.DateField(verbose_name=_("Member since"))
-    member_until = models.DateField(verbose_name=_("Member until"), blank=True, null=True)
+    member_until = models.DateField(
+        verbose_name=_("Member until"), blank=True, null=True
+    )
     title = models.TextField(verbose_name=_("Title / Role"))
 
-    dues = models.PositiveIntegerField(verbose_name=_("Membership dues in €"), default=60)
-    dues_sepa = models.DateField(verbose_name=_("Membership dues SEPA direct debit mandate"), blank=True, null=True)
+    dues = models.PositiveIntegerField(
+        verbose_name=_("Membership dues in €"), default=60
+    )
+    dues_sepa = models.DateField(
+        verbose_name=_("Membership dues SEPA direct debit mandate"),
+        blank=True,
+        null=True,
+    )
     person = models.ForeignKey(Person, on_delete=models.CASCADE)
 
     def __str__(self) -> str:
@@ -133,7 +141,9 @@ class TeckidsProject(ExtensibleModel):
 
     def clean(self):
         if not self.gid_number:
-            self.gid_number = TeckidsProject.objects.order_by("-gid_number")[0].gid_number + 1
+            self.gid_number = (
+                TeckidsProject.objects.order_by("-gid_number")[0].gid_number + 1
+            )
 
         super(TeckidsProject, self).clean()
 
@@ -241,6 +251,7 @@ class Voucher(ExtensibleModel):
     def __str__(self) -> str:
         return self.code
 
+
 class GlobalPermissions(models.Model, PureDjangoModel):
     class Meta:
         managed = False
@@ -257,6 +268,7 @@ class GlobalPermissions(models.Model, PureDjangoModel):
             ("view_registrations", _("Can view registrations")),
         )
 
+
 class EventRegistration(ExtensibleModel):
     class Meta:
         verbose_name = _("Registration")
@@ -271,21 +283,54 @@ class EventRegistration(ExtensibleModel):
         ("newsletter", _("From newsletter")),
     ]
 
-    event = models.ForeignKey(TeckidsProject, on_delete=models.CASCADE, verbose_name=_("Event"))
-    person = models.ForeignKey(Person, on_delete=models.CASCADE, verbose_name=_("Person"))
-    date_registred = models.DateTimeField(auto_now_add=True, verbose_name=_("Registration date"))
+    event = models.ForeignKey(
+        TeckidsProject, on_delete=models.CASCADE, verbose_name=_("Event")
+    )
+    person = models.ForeignKey(
+        Person, on_delete=models.CASCADE, verbose_name=_("Person")
+    )
+    date_registred = models.DateTimeField(
+        auto_now_add=True, verbose_name=_("Registration date")
+    )
 
-    comment = models.TextField(verbose_name=_("Comment / remarks"), blank=True, null=True)
-    channel = models.CharField(verbose_name=_("Channel"), max_length=255, choices=CHANNEL_CHOICES, blank=True, null=True)
-    voucher = models.ForeignKey(Voucher, on_delete=models.CASCADE, verbose_name=_("Voucher"), blank=True, null=True)
-    donation = models.PositiveIntegerField(verbose_name=_("Donation"), blank=True, null=True)
+    comment = models.TextField(
+        verbose_name=_("Comment / remarks"), blank=True, null=True
+    )
+    channel = models.CharField(
+        verbose_name=_("Channel"),
+        max_length=255,
+        choices=CHANNEL_CHOICES,
+        blank=True,
+        null=True,
+    )
+    voucher = models.ForeignKey(
+        Voucher,
+        on_delete=models.CASCADE,
+        verbose_name=_("Voucher"),
+        blank=True,
+        null=True,
+    )
+    donation = models.PositiveIntegerField(
+        verbose_name=_("Donation"), blank=True, null=True
+    )
 
     accept_sepa = models.BooleanField(verbose_name=_("SEPA direct debit"))
-    iban = models.CharField(max_length=255, verbose_name=_("IBAN (for SEPA direct debit)"), blank=True, null=True)
+    iban = models.CharField(
+        max_length=255,
+        verbose_name=_("IBAN (for SEPA direct debit)"),
+        blank=True,
+        null=True,
+    )
 
-    accept_terms = models.BooleanField(verbose_name=_("Delcaration of consent by parents"))
-    accept_data = models.BooleanField(verbose_name=_("Declaration of consent data protection"))
-    accept_general_terms = models.BooleanField(verbose_name=_("Declatation of consent terms and condition"))
+    accept_terms = models.BooleanField(
+        verbose_name=_("Delcaration of consent by parents")
+    )
+    accept_data = models.BooleanField(
+        verbose_name=_("Declaration of consent data protection")
+    )
+    accept_general_terms = models.BooleanField(
+        verbose_name=_("Declatation of consent terms and condition")
+    )
 
     def __str__(self) -> str:
         return f"{self.event}, {self.person.first_name} {self.person.last_name}"
diff --git a/aleksis/apps/ticdesk/predicates.py b/aleksis/apps/ticdesk/predicates.py
index a10b80b..fed88c5 100644
--- a/aleksis/apps/ticdesk/predicates.py
+++ b/aleksis/apps/ticdesk/predicates.py
@@ -5,7 +5,7 @@ from rules import predicate
 from aleksis.core.models import Group, Person
 from aleksis.core.util.predicates import check_object_permission
 
-from .models import Voucher, SSHKey, PGPKey
+from .models import PGPKey, SSHKey, Voucher
 
 User = get_user_model()
 
@@ -25,12 +25,14 @@ def see_owned_groups_members(user: User, person: Person) -> bool:
 
     return Person.member_of.filter(id__in=groups_list).exists()
 
+
 @predicate
 def is_own_voucher(user: User, voucher: Voucher) -> bool:
     """Predicate which checks if the voucher belongs to the user."""
 
     return voucher.person == user.person
 
+
 @predicate
 def is_own_ssh_key(user: User, ssh_key: SSHKey) -> bool:
     """Predicate which checks if the ssh_key belongs to the user."""
diff --git a/aleksis/apps/ticdesk/preferences.py b/aleksis/apps/ticdesk/preferences.py
index 51fa746..de1f87e 100644
--- a/aleksis/apps/ticdesk/preferences.py
+++ b/aleksis/apps/ticdesk/preferences.py
@@ -4,7 +4,6 @@ from dynamic_preferences.preferences import Section
 from dynamic_preferences.types import StringPreference
 
 from aleksis.core.registries import site_preferences_registry
-from aleksis.core.util.core_helpers import get_site_preferences
 
 ticdesk = Section("ticdesk")
 
diff --git a/aleksis/apps/ticdesk/rules.py b/aleksis/apps/ticdesk/rules.py
index f499cfb..b56d8c3 100644
--- a/aleksis/apps/ticdesk/rules.py
+++ b/aleksis/apps/ticdesk/rules.py
@@ -1,17 +1,23 @@
 from rules import add_perm
 
-from .models import Voucher, TeckidsProject, EventRegistration, TeckidsMember, FeedbackAspect
-from .predicates import see_group_by_grouptype, see_owned_groups_members, is_own_voucher, is_own_ssh_key, is_own_pgp_key
-
-from aleksis.core.models import Announcement, Group, Person
+from aleksis.core.models import Group
 from aleksis.core.util.predicates import (
     has_any_object,
-    has_object_perm,
     has_global_perm,
+    has_object_perm,
     has_person,
     is_group_member,
 )
 
+from .models import EventRegistration, FeedbackAspect, TeckidsMember, TeckidsProject, Voucher
+from .predicates import (
+    is_own_pgp_key,
+    is_own_ssh_key,
+    is_own_voucher,
+    see_group_by_grouptype,
+    see_owned_groups_members,
+)
+
 # View vouchers
 view_vouchers_predicate = has_person & (
     has_global_perm("ticdesk.view_vouchers")
@@ -113,21 +119,15 @@ manage_teckids_members_predicate = has_person & (
 add_perm("ticdesk.manage_teckids_members", manage_teckids_members_predicate)
 
 # Is own voucher?
-is_own_voucher_predicate = has_person & (
-    is_own_voucher
-)
+is_own_voucher_predicate = has_person & (is_own_voucher)
 add_perm("ticdesk.is_own_voucher", is_own_voucher_predicate)
 
 # Is own ssh key?
-is_own_ssh_key_predicate = has_person & (
-    is_own_ssh_key
-)
+is_own_ssh_key_predicate = has_person & (is_own_ssh_key)
 add_perm("ticdesk.is_own_ssh_key", is_own_ssh_key_predicate)
 
 # Is own pgp key?
-is_own_pgp_key_predicate = has_person & (
-    is_own_pgp_key
-)
+is_own_pgp_key_predicate = has_person & (is_own_pgp_key)
 add_perm("ticdesk.is_own_pgp_key", is_own_pgp_key_predicate)
 
 
diff --git a/aleksis/apps/ticdesk/settings.py b/aleksis/apps/ticdesk/settings.py
index e6c0ac1..9378041 100644
--- a/aleksis/apps/ticdesk/settings.py
+++ b/aleksis/apps/ticdesk/settings.py
@@ -1,9 +1,3 @@
-import os
-
-from django.utils.translation import gettext_lazy as _
-
-from ._version import __version__ as VERSION
-
 INSTALLED_APPS = [
     "django_extensions",
     "django_starfield",
diff --git a/aleksis/apps/ticdesk/tables.py b/aleksis/apps/ticdesk/tables.py
index 7b9d5ce..2e9fc94 100644
--- a/aleksis/apps/ticdesk/tables.py
+++ b/aleksis/apps/ticdesk/tables.py
@@ -14,7 +14,10 @@ class EventsTable(tables.Table):
     date_registration = tables.Column(verbose_name=_("Registration until"))
 
     short_name = tables.LinkColumn(
-        "register_event_by_id", args=[A("id")], verbose_name=_("Register"), text=_("Register")
+        "register_event_by_id",
+        args=[A("id")],
+        verbose_name=_("Register"),
+        text=_("Register"),
     )
 
 
@@ -26,7 +29,10 @@ class ParticipatedEventsTable(tables.Table):
     date_event = tables.Column(verbose_name=_("Date"))
 
     short_name = tables.LinkColumn(
-        "feedback_event_by_id", args=[A("id")], verbose_name=_("Feedback"), text=_("Feedback")
+        "feedback_event_by_id",
+        args=[A("id")],
+        verbose_name=_("Feedback"),
+        text=_("Feedback"),
     )
 
 
@@ -53,7 +59,10 @@ class VouchersTable(tables.Table):
     code = tables.Column(verbose_name=_("Code"))
     person = tables.Column(verbose_name=_("Person"))
     deleted = tables.LinkColumn(
-        "delete_voucher_by_id", args=[A("id")], verbose_name=_("Delete"), text=_("Delete")
+        "delete_voucher_by_id",
+        args=[A("id")],
+        verbose_name=_("Delete"),
+        text=_("Delete"),
     )
     edit = tables.LinkColumn(
         "edit_voucher_by_id", args=[A("id")], verbose_name=_("Edit"), text=_("Edit")
@@ -62,6 +71,7 @@ class VouchersTable(tables.Table):
         "print_voucher_by_id", args=[A("id")], verbose_name=_("Print"), text=_("Print")
     )
 
+
 class EventRegistrationsTable(tables.Table):
     class Meta:
         attrs = {"class": "responsive-table highlight"}
@@ -70,7 +80,10 @@ class EventRegistrationsTable(tables.Table):
     event = tables.Column()
     date_registred = tables.Column()
     view = tables.LinkColumn(
-        "registration_by_id", args=[A("id")], verbose_name=_("View registration"), text=_("View")
+        "registration_by_id",
+        args=[A("id")],
+        verbose_name=_("View registration"),
+        text=_("View"),
     )
 
 
@@ -83,7 +96,10 @@ class TeckidsMemberTable(tables.Table):
     member_since = tables.Column()
     member_until = tables.Column()
     edit = tables.LinkColumn(
-        "edit_member_by_id", args=[A("id")], verbose_name=_("Edit member"), text=_("Edit")
+        "edit_member_by_id",
+        args=[A("id")],
+        verbose_name=_("Edit member"),
+        text=_("Edit"),
     )
 
 
@@ -94,5 +110,8 @@ class FeedbackAspectsTable(tables.Table):
     aspect = tables.Column()
 
     edit = tables.LinkColumn(
-        "edit_feedback_aspect_by_id", args=[A("id")], verbose_name=_("Edit"), text=_("Edit")
+        "edit_feedback_aspect_by_id",
+        args=[A("id")],
+        verbose_name=_("Edit"),
+        text=_("Edit"),
     )
diff --git a/aleksis/apps/ticdesk/urls.py b/aleksis/apps/ticdesk/urls.py
index 511ebe4..ec5744f 100644
--- a/aleksis/apps/ticdesk/urls.py
+++ b/aleksis/apps/ticdesk/urls.py
@@ -3,7 +3,11 @@ from django.urls import path
 from . import views
 
 urlpatterns = [
-    path("event/<int:id_>/additional", views.register_event_additional, name="register_event_additional_by_id",),
+    path(
+        "event/<int:id_>/additional",
+        views.register_event_additional,
+        name="register_event_additional_by_id",
+    ),
     path("event/<int:id_>/edit", views.edit_event, name="edit_event_by_id"),
     path("event/<int:id_>/feedback", views.feedback_event, name="feedback_event_by_id"),
     path("event/<int:id_>/register", views.register_event, name="register_event_by_id"),
@@ -11,24 +15,54 @@ urlpatterns = [
     path("events/manage", views.manage_events, name="manage_events"),
     path("events/", views.events, name="events"),
     path("vouchers/create", views.edit_voucher, name="create_vouchers"),
-    path("vouchers/<int:id_>/delete", views.delete_voucher, name="delete_voucher_by_id"),
+    path(
+        "vouchers/<int:id_>/delete", views.delete_voucher, name="delete_voucher_by_id"
+    ),
     path("vouchers/<int:id_>/edit", views.edit_voucher, name="edit_voucher_by_id"),
     path("vouchers/<int:id_>/print", views.print_voucher, name="print_voucher_by_id"),
     path("vouchers/", views.vouchers, name="vouchers"),
     path("manage/mails", views.manage_mail, name="manage_mail"),
     path("manage/ssh_keys", views.manage_ssh_keys, name="manage_ssh_keys"),
     path("manage/pgp_keys", views.manage_pgp_keys, name="manage_pgp_keys"),
-    path("manage/ssh_keys/<int:id_>/delete", views.delete_sshkey, name="delete_sshkey_by_id"),
-    path("manage/pgp_keys/<int:id_>/delete", views.delete_pgpkey, name="delete_pgpkey_by_id"),
+    path(
+        "manage/ssh_keys/<int:id_>/delete",
+        views.delete_sshkey,
+        name="delete_sshkey_by_id",
+    ),
+    path(
+        "manage/pgp_keys/<int:id_>/delete",
+        views.delete_pgpkey,
+        name="delete_pgpkey_by_id",
+    ),
     path("event/lists/generate", views.generate_lists, name="generate_lists"),
     path("event/registrations/list", views.registrations, name="registrations"),
-    path("event/registrations/<int:id_>", views.registration, name="registration_by_id"),
-    path("event/registrations/<int:id_>/edit", views.edit_registration, name="edit_registration_by_id"),
-    path("event/registrations/<int:id_>/delete", views.delete_registration, name="delete_registration_by_id"),
+    path(
+        "event/registrations/<int:id_>", views.registration, name="registration_by_id"
+    ),
+    path(
+        "event/registrations/<int:id_>/edit",
+        views.edit_registration,
+        name="edit_registration_by_id",
+    ),
+    path(
+        "event/registrations/<int:id_>/delete",
+        views.delete_registration,
+        name="delete_registration_by_id",
+    ),
     path("members/list", views.members, name="members"),
     path("members/create", views.edit_member, name="create_member"),
     path("members/<int:id_>/edit", views.edit_member, name="edit_member_by_id"),
-    path("event/feedback_aspects/list", views.feedback_aspects, name="feedback_aspects"),
-    path("event/feedback_aspects/create", views.edit_feedback_aspect, name="create_feedback_aspect"),
-    path("event/feedback_aspects/<int:id_>/edit", views.edit_feedback_aspect, name="edit_feedback_aspect_by_id"),
+    path(
+        "event/feedback_aspects/list", views.feedback_aspects, name="feedback_aspects"
+    ),
+    path(
+        "event/feedback_aspects/create",
+        views.edit_feedback_aspect,
+        name="create_feedback_aspect",
+    ),
+    path(
+        "event/feedback_aspects/<int:id_>/edit",
+        views.edit_feedback_aspect,
+        name="edit_feedback_aspect_by_id",
+    ),
 ]
diff --git a/aleksis/apps/ticdesk/util.py b/aleksis/apps/ticdesk/util.py
index 50562ec..cfc51db 100644
--- a/aleksis/apps/ticdesk/util.py
+++ b/aleksis/apps/ticdesk/util.py
@@ -1,22 +1,13 @@
 import os
 import random
 import string
-import hashlib
-from base64 import b64encode
 from tempfile import mkstemp
 from textwrap import wrap
 
-from django.conf import settings
-from django.forms import fields
 from django import forms
-from django.utils.translation import ugettext as _
-
-import hashlib
-from base64 import b64encode
+from django.conf import settings
 
-import pexpect
 import requests
-from pam import pam
 
 
 def subscribe_mailinglist(listname, mail):
@@ -94,6 +85,6 @@ def upload_file_to_media_url(file, subdir="", prefix="upload_"):
 def generate_code():
     alphabet = string.ascii_uppercase + string.digits
     length = 8
-    code = ''.join(random.choice(alphabet) for _ in range(length))
+    code = "".join(random.choice(alphabet) for _ in range(length))
 
     return code
diff --git a/aleksis/apps/ticdesk/views.py b/aleksis/apps/ticdesk/views.py
index d5e0156..4d5ff12 100644
--- a/aleksis/apps/ticdesk/views.py
+++ b/aleksis/apps/ticdesk/views.py
@@ -3,63 +3,62 @@ import re
 from datetime import datetime
 from typing import Optional
 
-from django.apps import apps
 from django.contrib.auth.decorators import login_required
-from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
 from django.core.mail import EmailMessage
-from django.shortcuts import render, redirect
 from django.http import HttpRequest, HttpResponse
+from django.shortcuts import redirect, render
 from django.utils.translation import ugettext as _
-from django.views.decorators.debug import sensitive_post_parameters
 
+import pytz
 import reversion
+from django_tables2 import RequestConfig
 from rules.contrib.views import permission_required
-from templated_email import send_templated_mail
 
-import pytz
-from django_tables2 import RequestConfig
-from ldap import INVALID_SYNTAX
+from aleksis.core.models import Activity, Person
+from aleksis.core.util import messages
+from aleksis.core.util.core_helpers import objectgetter_optional
 
+from .filters import (
+    EventRegistrationFilter,
+    FeedbackAspectsFilter,
+    TeckidsMemberFilter,
+    VoucherFilter,
+)
 from .forms import (
-    EventAdditionalSurveyForm,
-    EventFeedbackForm,
-    RegisterEventForm,
-    EditVoucherForm,
     EditEventForm,
-    EditSSHKeyForm,
-    EditPGPKeyForm,
-    MailAddForm,
-    GenerateListForm,
-    EditTeckidsMemberForm,
     EditEventRegistrationForm,
     EditFeedbackAspectForm,
+    EditPGPKeyForm,
+    EditSSHKeyForm,
+    EditTeckidsMemberForm,
+    EditVoucherForm,
+    EventAdditionalSurveyForm,
+    EventFeedbackForm,
+    GenerateListForm,
+    MailAddForm,
+    RegisterEventForm,
 )
 from .models import (
-    RegistrationField,
-    TeckidsProject,
-    Voucher,
+    EventRegistration,
+    FeedbackAspect,
     MailAddress,
-    SSHKey,
     PGPKey,
-    EventRegistration,
+    RegistrationField,
+    SSHKey,
     TeckidsMember,
-    FeedbackAspect,
+    TeckidsProject,
+    Voucher,
 )
 from .tables import (
+    EventRegistrationsTable,
     EventsTable,
+    FeedbackAspectsTable,
     ManageEventsTable,
     ParticipatedEventsTable,
-    VouchersTable,
-    EventRegistrationsTable,
     TeckidsMemberTable,
-    FeedbackAspectsTable,
+    VouchersTable,
 )
-from .util import form_to_text_table, upload_file_to_media_url, generate_code
-from .filters import EventRegistrationFilter, TeckidsMemberFilter, VoucherFilter, FeedbackAspectsFilter
-
-from aleksis.core.util.core_helpers import objectgetter_optional
-from aleksis.core.util import messages
-from aleksis.core.models import Activity, Person
+from .util import form_to_text_table, generate_code, upload_file_to_media_url
 
 
 def events(request):
@@ -77,9 +76,7 @@ def events(request):
     if request.user.is_authenticated:
         # Get all events the person participated in.
         current_person = Person.objects.get(user__username=request.user.username)
-        participated_events = TeckidsProject.objects.filter(
-            members=current_person
-        )
+        participated_events = TeckidsProject.objects.filter(members=current_person)
 
         # Build table
         participated_events_table = ParticipatedEventsTable(participated_events)
@@ -115,12 +112,14 @@ def register_event(request, id_):
     }
 
     if current_person.guardians.first():
-        initial.update({
-            "guardian_first_name": current_person.guardians.first().first_name,
-            "guardian_last_name": current_person.guardians.first().last_name,
-            "guardian_mobile_number": current_person.guardians.first().mobile_number,
-            "guardian_email": current_person.guardians.first().email,
-        })
+        initial.update(
+            {
+                "guardian_first_name": current_person.guardians.first().first_name,
+                "guardian_last_name": current_person.guardians.first().last_name,
+                "guardian_mobile_number": current_person.guardians.first().mobile_number,
+                "guardian_email": current_person.guardians.first().email,
+            }
+        )
 
     register_form = RegisterEventForm(initial=initial)
 
@@ -149,9 +148,13 @@ def register_event(request, id_):
                 current_person.school = register_form.cleaned_data["school"]
                 current_person.school_class = register_form.cleaned_data["school_class"]
                 current_person.school_place = register_form.cleaned_data["school_place"]
-                current_person.mobile_number = register_form.cleaned_data["mobile_number"]
+                current_person.mobile_number = register_form.cleaned_data[
+                    "mobile_number"
+                ]
                 current_person.sex = register_form.cleaned_data["sex"]
-                current_person.date_of_birth = register_form.cleaned_data["date_of_birth"]
+                current_person.date_of_birth = register_form.cleaned_data[
+                    "date_of_birth"
+                ]
                 with reversion.create_revision():
                     current_person.save()
 
@@ -175,10 +178,10 @@ def register_event(request, id_):
                 or "guardian_email" in register_form.changed_data
             ):
                 guardian = Person.objects.get_or_create(
-                    first_name = register_form.cleaned_data["guardian_first_name"],
-                    last_name = register_form.cleaned_data["guardian_last_name"],
-                    mobile_number = register_form.cleaned_data["guardian_mobile_number"],
-                    email = register_form.cleaned_data["guardian_email"],
+                    first_name=register_form.cleaned_data["guardian_first_name"],
+                    last_name=register_form.cleaned_data["guardian_last_name"],
+                    mobile_number=register_form.cleaned_data["guardian_mobile_number"],
+                    email=register_form.cleaned_data["guardian_email"],
                 )
 
                 current_person.guardians.add(guardian[0])
@@ -193,7 +196,9 @@ def register_event(request, id_):
             with reversion.create_revision():
                 registration = register_form.save(commit=True)
             if "voucher_code" in register_form.changed_data:
-                voucher = Voucher.objects.get(code=register_form.cleaned_data["voucher_code"])
+                voucher = Voucher.objects.get(
+                    code=register_form.cleaned_data["voucher_code"]
+                )
                 if voucher:
                     registration.voucher = voucher
                     with reversion.create_revision():
@@ -207,9 +212,7 @@ def register_event(request, id_):
             message.to = [
                 "orga@teckids.org",
             ]
-            message.subject = _("New event:") % (
-                event,
-            )
+            message.subject = _("New event:") % (event,)
             message.extra_headers = {
                 "X-OTRS-CustomerUser": current_person.user.username,
             }
@@ -219,7 +222,14 @@ def register_event(request, id_):
             # Send message
             message.send()
 
-            messages.success(request, _("You have successfully registered for the event. Please give us up to two days to process your registration. You will then receive an email from us."))
+            messages.success(
+                request,
+                _(
+                    "You have successfully registered for the event. Please give us"
+                    "up to two days to process your registration. You will then"
+                    "receive an email from us."
+                ),
+            )
 
             act = Activity(
                 title=_("You registred for an event"),
@@ -325,12 +335,14 @@ def feedback_event(request, id_):
 
     if request.method == "POST":
         if feedback_form.is_valid():
-             # Handle photo uploads, if any
+            # Handle photo uploads, if any
             photo_urls = []
             for file in request.FILES.getlist("photos"):
-                url = upload_file_to_media_url(file, "ticdesk/teckids_projects/feedback/photos")
+                url = upload_file_to_media_url(
+                    file, "ticdesk/teckids_projects/feedback/photos"
+                )
                 photo_urls.append(url)
- 
+
             # Produce e-mail to registration queue
             message = EmailMessage()
 
@@ -452,7 +464,7 @@ def delete_voucher(request, id_):
 
     act = Activity(
         title=_("You deleted a voucher!"),
-        description=_("You deleted the voucher with ID %s" % current_voucher.id ),
+        description=_("You deleted the voucher with ID %s" % current_voucher.id),
         app="TIC-Desk",
         user=request.user.person,
     )
@@ -490,7 +502,10 @@ def edit_voucher(request: HttpRequest, id_: Optional[int] = None) -> HttpRespons
 
             act = Activity(
                 title=_("You have created a voucher."),
-                description=_("You have created a voucher for %s for %s" % (voucher.person, voucher.project)),
+                description=_(
+                    "You have created a voucher for %s for %s"
+                    % (voucher.person, voucher.project)
+                ),
                 app="TIC-Desk",
                 user=request.user.person,
             )
@@ -561,7 +576,9 @@ your Teckids-Team
 
             act = Activity(
                 title=_("You have added an email address"),
-                description=_("You have added the email address %s to your profile." % mail),
+                description=_(
+                    "You have added the email address %s to your profile." % mail
+                ),
                 app="TIC-Desk",
                 user=request.user.person,
             )
@@ -778,12 +795,15 @@ def registrations(request: HttpRequest) -> HttpResponse:
 
 
 @permission_required(
-    "ticdesk.view_registration", fn=objectgetter_optional(EventRegistration, "request.user.person", True)
+    "ticdesk.view_registration",
+    fn=objectgetter_optional(EventRegistration, "request.user.person", True),
 )
 def registration(request: HttpRequest, id_) -> HttpResponse:
     context = {}
 
-    registration = objectgetter_optional(EventRegistration, "request.user.person", True)(request, id_)
+    registration = objectgetter_optional(
+        EventRegistration, "request.user.person", True
+    )(request, id_)
 
     context["registration"] = registration
 
@@ -811,7 +831,8 @@ def members(request: HttpRequest) -> HttpResponse:
 
 
 @permission_required(
-    "ticdesk.manage_teckids_member", fn=objectgetter_optional(TeckidsMember, None, False)
+    "ticdesk.manage_teckids_member",
+    fn=objectgetter_optional(TeckidsMember, None, False),
 )
 def edit_member(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse:
     """View to edit or create a member."""
@@ -842,7 +863,8 @@ def edit_member(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse
 
 
 @permission_required(
-    "ticdesk.delete_registrations", fn=objectgetter_optional(EventRegistration, None, False)
+    "ticdesk.delete_registrations",
+    fn=objectgetter_optional(EventRegistration, None, False),
 )
 def delete_registration(request: HttpRequest, id_) -> HttpResponse:
     context = {}
@@ -862,14 +884,17 @@ def delete_registration(request: HttpRequest, id_) -> HttpResponse:
 
 
 @permission_required(
-    "ticdesk.manage_registrations", fn=objectgetter_optional(EventRegistration, None, False)
+    "ticdesk.manage_registrations",
+    fn=objectgetter_optional(EventRegistration, None, False),
 )
 def edit_registration(request: HttpRequest, id_) -> HttpResponse:
     context = {}
 
     registration = objectgetter_optional(EventRegistration, None, False)(request, id_)
 
-    edit_event_registration_form = EditEventRegistrationForm(request.POST or None, instance=registration)
+    edit_event_registration_form = EditEventRegistrationForm(
+        request.POST or None, instance=registration
+    )
 
     if request.method == "POST":
         if edit_event_registration_form.is_valid():
@@ -906,7 +931,9 @@ def feedback_aspects(request: HttpRequest) -> HttpResponse:
     feedback_aspects = FeedbackAspect.objects.all()
 
     # Get filter
-    feedback_aspects_filter = FeedbackAspectsFilter(request.GET, queryset=feedback_aspects)
+    feedback_aspects_filter = FeedbackAspectsFilter(
+        request.GET, queryset=feedback_aspects
+    )
     context["feedback_aspects_filter"] = feedback_aspects_filter
 
     # Build table
@@ -918,9 +945,12 @@ def feedback_aspects(request: HttpRequest) -> HttpResponse:
 
 
 @permission_required(
-    "ticdesk.edit_feedback_aspect", fn=objectgetter_optional(FeedbackAspect, None, False)
+    "ticdesk.edit_feedback_aspect",
+    fn=objectgetter_optional(FeedbackAspect, None, False),
 )
-def edit_feedback_aspect(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse:
+def edit_feedback_aspect(
+    request: HttpRequest, id_: Optional[int] = None
+) -> HttpResponse:
     """View to edit or create an feedback_aspect."""
     context = {}
 
@@ -929,7 +959,9 @@ def edit_feedback_aspect(request: HttpRequest, id_: Optional[int] = None) -> Htt
 
     if id_:
         # Edit form for existing feedback_aspect
-        edit_feedback_aspect_form = EditFeedbackAspectForm(request.POST or None, instance=feedback_aspect)
+        edit_feedback_aspect_form = EditFeedbackAspectForm(
+            request.POST or None, instance=feedback_aspect
+        )
     else:
         # Empty form to create a new feedback_aspect
         edit_feedback_aspect_form = EditFeedbackAspectForm(request.POST or None)
diff --git a/poetry.lock b/poetry.lock
index 12ee907..ee20fe9 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -244,10 +244,10 @@ description = "A high-level Python Web framework that encourages rapid developme
 name = "django"
 optional = false
 python-versions = ">=3.6"
-version = "3.0.8"
+version = "3.1"
 
 [package.dependencies]
-asgiref = ">=3.2,<4.0"
+asgiref = ">=3.2.10,<3.3.0"
 pytz = "*"
 sqlparse = ">=0.2.2"
 
@@ -373,11 +373,12 @@ category = "main"
 description = "Yet another Django audit log app, hopefully the simplest one."
 name = "django-easy-audit"
 optional = false
-python-versions = "*"
-version = "1.3.0a3"
+python-versions = ">=3.5"
+version = "1.3.0a5"
 
 [package.dependencies]
 beautifulsoup4 = "*"
+django = ">=2.2,<3.2"
 
 [[package]]
 category = "main"
@@ -486,7 +487,7 @@ description = "A Django utility application that returns client's real IP addres
 name = "django-ipware"
 optional = false
 python-versions = "*"
-version = "3.0.0"
+version = "3.0.1"
 
 [[package]]
 category = "main"
@@ -547,7 +548,7 @@ description = "Material design for django forms and admin"
 name = "django-material"
 optional = false
 python-versions = "*"
-version = "1.6.7"
+version = "1.7.0"
 
 [package.dependencies]
 six = "*"
@@ -831,8 +832,8 @@ category = "main"
 description = "Faker is a Python package that generates fake data for you."
 name = "faker"
 optional = false
-python-versions = ">=3.4"
-version = "4.1.1"
+python-versions = ">=3.5"
+version = "4.1.2"
 
 [package.dependencies]
 python-dateutil = ">=2.4"
@@ -943,7 +944,7 @@ description = "Python version of Google's common library for parsing, formatting
 name = "phonenumbers"
 optional = false
 python-versions = "*"
-version = "8.12.7"
+version = "8.12.8"
 
 [[package]]
 category = "main"
@@ -1185,7 +1186,7 @@ description = "A simple tool/library for working with SPDX license definitions."
 name = "spdx-license-list"
 optional = false
 python-versions = "*"
-version = "0.5.0"
+version = "0.5.1"
 
 [[package]]
 category = "main"
@@ -1225,7 +1226,7 @@ description = "Fast, Extensible Progress Meter"
 name = "tqdm"
 optional = false
 python-versions = ">=2.6, !=3.0.*, !=3.1.*"
-version = "4.48.0"
+version = "4.48.2"
 
 [package.extras]
 dev = ["py-make (>=0.1.0)", "twine", "argopt", "pydoc-markdown"]
@@ -1236,7 +1237,7 @@ description = "Twilio API client and TwiML generator"
 name = "twilio"
 optional = false
 python-versions = "*"
-version = "6.44.1"
+version = "6.44.2"
 
 [package.dependencies]
 PyJWT = ">=1.4.2"
@@ -1274,11 +1275,10 @@ description = "A library for verifying YubiKey OTP tokens, both locally and thro
 name = "yubiotp"
 optional = false
 python-versions = "*"
-version = "0.2.2.post1"
+version = "1.0.0.post1"
 
 [package.dependencies]
 pycryptodome = "*"
-six = "*"
 
 [metadata]
 content-hash = "ffa383cf197c6943613647026c3ed301d09a7522dee39765fb4a01bb894c988f"
@@ -1346,8 +1346,8 @@ decorator = [
     {file = "decorator-4.4.2.tar.gz", hash = "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"},
 ]
 django = [
-    {file = "Django-3.0.8-py3-none-any.whl", hash = "sha256:5457fc953ec560c5521b41fad9e6734a4668b7ba205832191bbdff40ec61073c"},
-    {file = "Django-3.0.8.tar.gz", hash = "sha256:31a5fbbea5fc71c99e288ec0b2f00302a0a92c44b13ede80b73a6a4d6d205582"},
+    {file = "Django-3.1-py3-none-any.whl", hash = "sha256:1a63f5bb6ff4d7c42f62a519edc2adbb37f9b78068a5a862beff858b68e3dc8b"},
+    {file = "Django-3.1.tar.gz", hash = "sha256:2d390268a13c655c97e0e2ede9d117007996db692c1bb93eabebd4fb7ea7012b"},
 ]
 django-any-js = [
     {file = "django-any-js-1.0.3.post0.tar.gz", hash = "sha256:1da88b44b861b0f54f6b8ea0eb4c7c4fa1a5772e9a4320532cd4e0871a4e23f7"},
@@ -1388,8 +1388,8 @@ django-dynamic-preferences = [
     {file = "django_dynamic_preferences-1.10-py2.py3-none-any.whl", hash = "sha256:d5852c720c1989a67d87669035e11f6c033e7a507de6ec9bd28941cba24a2dc4"},
 ]
 django-easy-audit = [
-    {file = "django-easy-audit-1.3.0a3.tar.gz", hash = "sha256:80e9ed3f11d8927ecd5fa7e03e3aeebd87729d6feba60f9b26b554237dbe3508"},
-    {file = "django_easy_audit-1.3.0a3-py3-none-any.whl", hash = "sha256:2b3d067d81392bb18d2b6bb09e67ce4367e73f767d3f4e067af010d496dc657c"},
+    {file = "django-easy-audit-1.3.0a5.tar.gz", hash = "sha256:bb6c0291c360fe305d5cdbbc02d9bdec82885788a675871f80ec1b2b0fd6d443"},
+    {file = "django_easy_audit-1.3.0a5-py3-none-any.whl", hash = "sha256:01f66d19b15b19c377754848157f87dc78fe88cd1ed9bd7314d24438fb95504a"},
 ]
 django-favicon-plus-reloaded = [
     {file = "django-favicon-plus-reloaded-1.0.4.tar.gz", hash = "sha256:90c761c636a338e6e9fb1d086649d82095085f92cff816c9cf074607f28c85a5"},
@@ -1427,7 +1427,7 @@ django-impersonate = [
     {file = "django-impersonate-1.5.1.tar.gz", hash = "sha256:7c786ffaa7a5dd430f9277b53a64676c470b684eee5aa52c3b483298860d09b4"},
 ]
 django-ipware = [
-    {file = "django-ipware-3.0.0.tar.gz", hash = "sha256:161605eb011439550dd3ee496d0e999720b13f01952be25ea9e88982fbe48e83"},
+    {file = "django-ipware-3.0.1.tar.gz", hash = "sha256:73a640a5bff00aa7503a35e92e462001cfabb07d73d649c262f117423beee953"},
 ]
 django-js-asset = [
     {file = "django-js-asset-1.2.2.tar.gz", hash = "sha256:c163ae80d2e0b22d8fb598047cd0dcef31f81830e127cfecae278ad574167260"},
@@ -1448,8 +1448,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.7.tar.gz", hash = "sha256:3cc68b34348634f019bf529f3e0b99b1474ab36ec9b50040f5e557b5b65add1d"},
-    {file = "django_material-1.6.7-py2.py3-none-any.whl", hash = "sha256:9da268532c92c270b512d9610c9723a07dbfea06db98434dac8aa1dd2910778f"},
+    {file = "django-material-1.7.0.tar.gz", hash = "sha256:8d8e76605aa77de3d37d8ae4db1835479e484f1530485fd6b66535244f64131b"},
+    {file = "django_material-1.7.0-py2.py3-none-any.whl", hash = "sha256:ff36170e400158a22f9b675e1c07d42267bf1a92122518f68db0c2bf3410096d"},
 ]
 django-menu-generator = [
     {file = "django-menu-generator-1.0.4.tar.gz", hash = "sha256:ce71a5055c16933c8aff64fb36c21e5cf8b6d505733aceed1252f8b99369a378"},
@@ -1523,8 +1523,8 @@ easy-thumbnails = [
     {file = "easy-thumbnails-2.7.tar.gz", hash = "sha256:e4e7a0dd4001f56bfd4058428f2c91eafe27d33ef3b8b33ac4e013b159b9ff91"},
 ]
 faker = [
-    {file = "Faker-4.1.1-py3-none-any.whl", hash = "sha256:1290f589648bc470b8d98fff1fdff773fe3f46b4ca2cac73ac74668b12cf008e"},
-    {file = "Faker-4.1.1.tar.gz", hash = "sha256:c006b3664c270a2cfd4785c5e41ff263d48101c4e920b5961cf9c237131d8418"},
+    {file = "Faker-4.1.2-py3-none-any.whl", hash = "sha256:bc4b8c908dfcd84e4fe5d9fa2e52fbe17546515fb8f126909b98c47badf05658"},
+    {file = "Faker-4.1.2.tar.gz", hash = "sha256:ff188c416864e3f7d8becd8f9ee683a4b4101a2a2d2bcdcb3e84bb1bdd06eaae"},
 ]
 funcsigs = [
     {file = "funcsigs-1.0.2-py2.py3-none-any.whl", hash = "sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca"},
@@ -1575,8 +1575,8 @@ pexpect = [
     {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"},
 ]
 phonenumbers = [
-    {file = "phonenumbers-8.12.7-py2.py3-none-any.whl", hash = "sha256:772d69e620f85bb089d27c4e2bbf718c49ad327459accf9463ac65dbab67695c"},
-    {file = "phonenumbers-8.12.7.tar.gz", hash = "sha256:652c418f8e97c8438f227a524ddf8d7d325c4a47e4924ce865b827c24ec3194d"},
+    {file = "phonenumbers-8.12.8-py2.py3-none-any.whl", hash = "sha256:991504a61ea37d14f49f4f30f361e348ff6e6a3306db33ee87ae21b2e55aa0e6"},
+    {file = "phonenumbers-8.12.8.tar.gz", hash = "sha256:3a8b8f7b60ea00b83ca588b5ca57e48d9a3c223aa42f1a3d5fa88566424216c3"},
 ]
 pillow = [
     {file = "Pillow-7.2.0-cp35-cp35m-macosx_10_10_intel.whl", hash = "sha256:1ca594126d3c4def54babee699c055a913efb01e106c309fa6b04405d474d5ae"},
@@ -1749,8 +1749,8 @@ soupsieve = [
     {file = "soupsieve-1.9.6.tar.gz", hash = "sha256:7985bacc98c34923a439967c1a602dc4f1e15f923b6fcf02344184f86cc7efaa"},
 ]
 spdx-license-list = [
-    {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"},
+    {file = "spdx_license_list-0.5.1-py3-none-any.whl", hash = "sha256:32f1401e0077b46ba8b3d9c648b6503ef1d49c41aab51aa13816be2dde3b4a13"},
+    {file = "spdx_license_list-0.5.1.tar.gz", hash = "sha256:64cb5de37724c64cdeccafa2ae68667ff8ccdb7b688f51c1c2be82d7ebe3a112"},
 ]
 sqlparse = [
     {file = "sqlparse-0.3.1-py2.py3-none-any.whl", hash = "sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e"},
@@ -1768,11 +1768,11 @@ toml = [
     {file = "toml-0.10.1.tar.gz", hash = "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f"},
 ]
 tqdm = [
-    {file = "tqdm-4.48.0-py2.py3-none-any.whl", hash = "sha256:fcb7cb5b729b60a27f300b15c1ffd4744f080fb483b88f31dc8654b082cc8ea5"},
-    {file = "tqdm-4.48.0.tar.gz", hash = "sha256:6baa75a88582b1db6d34ce4690da5501d2a1cb65c34664840a456b2c9f794d29"},
+    {file = "tqdm-4.48.2-py2.py3-none-any.whl", hash = "sha256:1a336d2b829be50e46b84668691e0a2719f26c97c62846298dd5ae2937e4d5cf"},
+    {file = "tqdm-4.48.2.tar.gz", hash = "sha256:564d632ea2b9cb52979f7956e093e831c28d441c11751682f84c86fc46e4fd21"},
 ]
 twilio = [
-    {file = "twilio-6.44.1.tar.gz", hash = "sha256:a84b6a4e8f6c739cf3fa429033ad522d07c78e3788800e68fe5195d83db4f03a"},
+    {file = "twilio-6.44.2.tar.gz", hash = "sha256:db0c1ed249ad672007cbe7109e8a2cbe30c4ed660beba740976baa32f0a8574a"},
 ]
 urllib3 = [
     {file = "urllib3-1.25.10-py2.py3-none-any.whl", hash = "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"},
@@ -1783,6 +1783,6 @@ webencodings = [
     {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"},
 ]
 yubiotp = [
-    {file = "YubiOTP-0.2.2.post1-py2.py3-none-any.whl", hash = "sha256:7e281801b24678f4bda855ce8ab975a7688a912f5a6cb22b6c2b16263a93cbd2"},
-    {file = "YubiOTP-0.2.2.post1.tar.gz", hash = "sha256:de83b1560226e38b5923f6ab919f962c8c2abb7c722104cb45b2b6db2ac86e40"},
+    {file = "YubiOTP-1.0.0.post1-py2.py3-none-any.whl", hash = "sha256:7ad57011866e0bc6c6d179ffbc3926fcc0e82d410178a6d01ba4da0f88332878"},
+    {file = "YubiOTP-1.0.0.post1.tar.gz", hash = "sha256:c13825f7b76a69afb92f19521f4dea9f5031d70f45123b505dc2e0ac03132065"},
 ]
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..6d8918e
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,82 @@
+[tox]
+skipsdist = True
+skip_missing_interpreters = true
+envlist = py37,py38
+
+[testenv]
+whitelist_externals = poetry
+		      sudo
+skip_install = true
+envdir = {toxworkdir}/globalenv
+commands_pre =
+     - poetry install
+commands =
+    - poetry run pytest --cov=. {posargs} aleksis/
+
+[testenv:selenium]
+setenv =
+    TEST_SCREENSHOT_PATH = {env:TEST_SCREENSHOT_PATH:.tox/screenshots}
+    TEST_SELENIUM_HUB = {env:TEST_SELENIUM_HUB:http://127.0.0.1:4444/wd/hub}
+    TEST_SELENIUM_BROWSERS = {env:TEST_SELENIUM_BROWSERS:chrome,firefox}
+    TEST_HOST = {env:TEST_HOST:172.17.0.1}
+
+[testenv:lint]
+commands =
+    - poetry run black --check --diff aleksis/
+    - poetry run isort -c --diff --stdout -rc aleksis/
+    poetry run flake8 {posargs} aleksis/
+
+[testenv:security]
+commands =
+    poetry show --no-dev
+    poetry run safety check --full-report
+
+[testenv:build]
+commands_pre =
+commands = poetry build
+
+[testenv:docs]
+commands = poetry run make -C docs/ html {posargs}
+
+[testenv:reformat]
+commands =
+    poetry run isort -rc aleksis/
+    poetry run black aleksis/
+
+[flake8]
+max_line_length = 100
+exclude = migrations,tests
+ignore = BLK100,E203,E231,W503,D100,D101,D102,D103,D104,D105,D106,D107,RST215,RST214,F821,F841,S106,T100,T101,DJ05,F634
+
+[isort]
+line_length = 100
+multi_line_output = 3
+include_trailing_comma = 1
+default_section = THIRDPARTY
+known_first_party = aleksis
+known_django = django
+skip = migrations
+sections = FUTURE,STDLIB,DJANGO,THIRDPARTY,FIRSTPARTY,LOCALFOLDER
+
+[mypy]
+plugins = mypy_django_plugin.main
+python_version = 3.8
+platform = linux
+show_column_numbers = True
+follow_imports = skip
+ignore_missing_imports = True
+cache_dir = /dev/null
+
+[mypy.plugins.django-stubs]
+django_settings_module = aleksis.core.settings
+
+[pytest]
+DJANGO_SETTINGS_MODULE = aleksis.core.settings
+junit_family = legacy
+
+[coverage:run]
+omit =
+    */migrations/*
+    */tests/*
+    .tox/*
+    manage.py
-- 
GitLab