From 90aa5833f4ff86cfe2104ba266522757eae3eaac Mon Sep 17 00:00:00 2001
From: Tom Teichler <tom.teichler@teckids.org>
Date: Wed, 23 Sep 2020 19:59:36 +0100
Subject: [PATCH] Clean up code

---
 .gitignore                                    |   4 +-
 aleksis/apps/ticdesk/forms.py                 |  45 +----
 aleksis/apps/ticdesk/models.py                |  96 ++---------
 aleksis/apps/ticdesk/preferences.py           |  32 +---
 .../edit.html                                 |   0
 .../feedback.html                             |   0
 .../list.html                                 |   0
 .../manage.html                               |   0
 .../register.html                             |   0
 .../register_additional.html                  |   0
 aleksis/apps/ticdesk/views.py                 | 155 +++++-------------
 poetry.lock                                   |  14 +-
 pyproject.toml                                |   1 +
 13 files changed, 78 insertions(+), 269 deletions(-)
 rename aleksis/apps/ticdesk/templates/ticdesk/{teckids_project => teckids_event}/edit.html (100%)
 rename aleksis/apps/ticdesk/templates/ticdesk/{teckids_project => teckids_event}/feedback.html (100%)
 rename aleksis/apps/ticdesk/templates/ticdesk/{teckids_project => teckids_event}/list.html (100%)
 rename aleksis/apps/ticdesk/templates/ticdesk/{teckids_project => teckids_event}/manage.html (100%)
 rename aleksis/apps/ticdesk/templates/ticdesk/{teckids_project => teckids_event}/register.html (100%)
 rename aleksis/apps/ticdesk/templates/ticdesk/{teckids_project => teckids_event}/register_additional.html (100%)

diff --git a/.gitignore b/.gitignore
index 4fd8861..dfb3016 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,6 +42,7 @@ local_settings.py
 env/
 venv/
 ENV/
+.tox/
 
 # Editors
 *~
@@ -50,6 +51,3 @@ DEADJOE
 
 # Database
 db.sqlite3
-
-# Compiled CSS files
-*.css
diff --git a/aleksis/apps/ticdesk/forms.py b/aleksis/apps/ticdesk/forms.py
index bae09c7..4258080 100644
--- a/aleksis/apps/ticdesk/forms.py
+++ b/aleksis/apps/ticdesk/forms.py
@@ -44,14 +44,6 @@ LICENCE_CHOICES = [
     ),
 ]
 
-DISALLOWED_UIDS = get_site_preferences()["ticdesk__disallowed_uids"].split(",")
-
-DISALLOWED_LOCAL_PARTS = get_site_preferences()[
-    "ticdesk__disallowed_local_parts"
-].split(",") + get_site_preferences()["ticdesk__disallowed_uids"].split(",")
-
-MAIL_DOMAINS = tuple(get_site_preferences()["ticdesk__mail_domains"].split(","))
-
 NEWSLETTER_CHOICES = get_site_preferences()["ticdesk__newsletter_choices"].split(",")
 
 
@@ -204,44 +196,13 @@ 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(
-            _("Event settings"),
-            Row("date_event", "date_registration", "date_retraction"),
-            Row("cost", "max_participants"),
-            "place",
-            "published",
-        ),
+        Fieldset(_("Base data"), Row("group", "description", Row("place"), "published"),
+        Fieldset(_("Date data"), Row("date_event", "date_registration", "date_retraction"),
+        Fieldset(_("Feedback aspects"), "feedback_aspects"),
     )
 
     class Meta:
         model = TeckidsEvent
-        exclude = ["gid_number"]
-        widgets = {
-            "owners": ModelSelect2MultipleWidget(
-                search_fields=[
-                    "first_name__icontains",
-                    "last_name__icontains",
-                    "short_name__icontains",
-                ]
-            ),
-        }
-
-
-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!"
-)
-
-
-def is_mail_taken(mail):
-    return (
-        bool(Person.objects.filter(email=mail))
-        or mail.split("@")[0] in DISALLOWED_LOCAL_PARTS
-    )
 
 
 class EditVoucherForm(forms.ModelForm):
diff --git a/aleksis/apps/ticdesk/models.py b/aleksis/apps/ticdesk/models.py
index 53f85f2..b54c4c0 100644
--- a/aleksis/apps/ticdesk/models.py
+++ b/aleksis/apps/ticdesk/models.py
@@ -2,6 +2,8 @@ from django.db import models
 from django.utils import timezone
 from django.utils.translation import gettext_lazy as _
 
+from django_iban.fields import IBANField
+
 from aleksis.core.mixins import ExtensibleModel, PureDjangoModel
 from aleksis.core.models import Person
 
@@ -57,27 +59,18 @@ class TeckidsEvent(ExtensibleModel):
         verbose_name = _("Teckids event")
         verbose_name_plural = _("Teckids events")
 
-    # Group name details
-    short_name = models.CharField(max_length=50)
-    display_name = models.CharField(max_length=100, verbose_name=_("Display name"))
+    # Event details
+    group = models.ForeignKey(Group, on_delete=models.CASCADE, verbose_name=_("Group"), related_name="event")
     description = models.CharField(max_length=500, verbose_name=_("Description"))
-    gid_number = models.IntegerField(verbose_name=_("GID number"))
     published = models.BooleanField(default=False, verbose_name=_("Publish"))
-
-    # Members of this group
-    members = models.ManyToManyField(
-        Person, verbose_name=_("Members"), related_name="member"
-    )
-    owners = models.ManyToManyField(
-        Person, verbose_name=_("Owners"), related_name="owner"
-    )
-
     place = models.CharField(max_length=50, verbose_name="Place")
 
+    # Date details
     date_event = models.DateTimeField(verbose_name=_("Date of event"))
     date_registration = models.DateTimeField(verbose_name=_("Registration deadline"))
     date_retraction = models.DateTimeField(verbose_name=_("Retraction deadline"))
 
+    # Other details
     cost = models.IntegerField(verbose_name=_("Cost in €"))
     max_participants = models.IntegerField(verbose_name=_("Max participants"))
 
@@ -88,18 +81,6 @@ class TeckidsEvent(ExtensibleModel):
     def __str__(self) -> str:
         return self.display_name
 
-    def clean(self):
-        if not self.gid_number:
-            self.gid_number = (
-                TeckidsEvent.objects.order_by("-gid_number")[0].gid_number + 1
-            )
-
-        super(TeckidsEvent, self).clean()
-
-    def save(self, *args, **kwargs):
-        self.clean()
-        super(TeckidsEvent, self).save(*args, **kwargs)
-
     def can_register(self, request=None):
         now = timezone.now()
 
@@ -112,7 +93,7 @@ class TeckidsEvent(ExtensibleModel):
             ):
                 return True
 
-        if self.members.count() >= self.max_participants:
+        if self.group.members.count() >= self.max_participants:
             return False
 
         if self.date_registration:
@@ -121,56 +102,15 @@ class TeckidsEvent(ExtensibleModel):
 
     @property
     def booked_percentage(self):
-        return self.members.count() / self.max_participants * 100
-
-    def sync_members(self):
-        # Ensure member_uids are in sync mith members
-        self.member_uids = []
-        for member_dn in self.members:
-            # Rely on RDN being correctly set, for performance reasons
-            rdn = member_dn.split(",")[0]
-            rdn_parts = rdn.split("+")
-
-            # Find the uid part of the RDN
-            for rdn_part in rdn_parts:
-                if rdn_part.startswith("uid="):
-                    # Add to uids list and continue with next entry
-                    uid = rdn_part.split("=")[1]
-                    self.member_uids.append(uid)
-                    break
-
-    def add_member(self, person):
-        # Add person both to groupOfNames and posixGroup part
-        if person not in self.members.all():
-            self.members.add(person)
+        return self.group.members.count() / self.max_participants * 100
 
     @property
     def members_persons(self):
-        return self._get_objects("members", Person)
+        return self.group.members.all()
 
     @property
     def owners_persons(self):
-        return self._get_objects("owners", Person)
-
-
-class RegistrationField(ExtensibleModel):
-    class Meta:
-        unique_together = ("event", "person", "title")
-
-    event = models.ForeignKey(
-        TeckidsEvent,
-        related_name="registration_field",
-        verbose_name=_("Project"),
-        on_delete=models.CASCADE,
-    )
-    person = models.ForeignKey(
-        Person,
-        related_name="registration_field",
-        verbose_name=_("Person"),
-        on_delete=models.CASCADE,
-    )
-    title = models.CharField(max_length=30, verbose_name=_("Title"))
-    content = models.TextField(verbose_name=_("Content"))
+        return self.group.owners.all()
 
 
 class Voucher(ExtensibleModel):
@@ -223,15 +163,6 @@ class EventRegistration(ExtensibleModel):
         verbose_name = _("Registration")
         verbose_name_plural = _("Registrations")
 
-    CHANNEL_CHOICES = [
-        ("none", _("No information")),
-        ("internet", _("From internet")),
-        ("school", _("From school")),
-        ("friends", _("From friends")),
-        ("parents", _("From parents")),
-        ("newsletter", _("From newsletter")),
-    ]
-
     event = models.ForeignKey(
         TeckidsEvent, on_delete=models.CASCADE, verbose_name=_("Event")
     )
@@ -248,7 +179,6 @@ class EventRegistration(ExtensibleModel):
     channel = models.CharField(
         verbose_name=_("Channel"),
         max_length=255,
-        choices=CHANNEL_CHOICES,
         blank=True,
         null=True,
     )
@@ -264,11 +194,9 @@ class EventRegistration(ExtensibleModel):
     )
 
     accept_sepa = models.BooleanField(verbose_name=_("SEPA direct debit"))
-    iban = models.CharField(
-        max_length=255,
+    iban = models.IBANField(
         verbose_name=_("IBAN (for SEPA direct debit)"),
-        blank=True,
-        null=True,
+        enforce_database_constraint=True,
     )
 
     accept_terms = models.BooleanField(
diff --git a/aleksis/apps/ticdesk/preferences.py b/aleksis/apps/ticdesk/preferences.py
index de1f87e..cc7feb2 100644
--- a/aleksis/apps/ticdesk/preferences.py
+++ b/aleksis/apps/ticdesk/preferences.py
@@ -8,15 +8,6 @@ from aleksis.core.registries import site_preferences_registry
 ticdesk = Section("ticdesk")
 
 
-@site_preferences_registry.register
-class MailDomains(StringPreference):
-    section = ticdesk
-    name = "mail_domains"
-    default = ""
-    required = False
-    verbose_name = _("Available mail domains (comma-seperated)")
-
-
 @site_preferences_registry.register
 class NewsletterChoices(StringPreference):
     section = ticdesk
@@ -27,27 +18,18 @@ class NewsletterChoices(StringPreference):
 
 
 @site_preferences_registry.register
-class DisallowedUids(StringPreference):
-    section = ticdesk
-    name = "disallowed_uids"
-    default = ""
-    required = False
-    verbose_name = _("Disallowed uids (comma-seperated)")
-
-
-@site_preferences_registry.register
-class DisallowedLocalParts(StringPreference):
+class WWSPostUrl(StringPreference):
     section = ticdesk
-    name = "disallowed_local_parts"
+    name = "wws_post_url"
     default = ""
     required = False
-    verbose_name = _("Disallowed mail local parts (comma-seperated)")
+    verbose_name = _("POST url for Sympa")
 
 
 @site_preferences_registry.register
-class WWSPostUrl(StringPreference):
+class ChannelChoices(StringPreference):
     section = ticdesk
-    name = "wws_post_url"
+    name = "channel_choices"
     default = ""
-    required = False
-    verbose_name = _("POST url for Sympa")
+    requred = False
+    verbose_name = _("Channel choices")
diff --git a/aleksis/apps/ticdesk/templates/ticdesk/teckids_project/edit.html b/aleksis/apps/ticdesk/templates/ticdesk/teckids_event/edit.html
similarity index 100%
rename from aleksis/apps/ticdesk/templates/ticdesk/teckids_project/edit.html
rename to aleksis/apps/ticdesk/templates/ticdesk/teckids_event/edit.html
diff --git a/aleksis/apps/ticdesk/templates/ticdesk/teckids_project/feedback.html b/aleksis/apps/ticdesk/templates/ticdesk/teckids_event/feedback.html
similarity index 100%
rename from aleksis/apps/ticdesk/templates/ticdesk/teckids_project/feedback.html
rename to aleksis/apps/ticdesk/templates/ticdesk/teckids_event/feedback.html
diff --git a/aleksis/apps/ticdesk/templates/ticdesk/teckids_project/list.html b/aleksis/apps/ticdesk/templates/ticdesk/teckids_event/list.html
similarity index 100%
rename from aleksis/apps/ticdesk/templates/ticdesk/teckids_project/list.html
rename to aleksis/apps/ticdesk/templates/ticdesk/teckids_event/list.html
diff --git a/aleksis/apps/ticdesk/templates/ticdesk/teckids_project/manage.html b/aleksis/apps/ticdesk/templates/ticdesk/teckids_event/manage.html
similarity index 100%
rename from aleksis/apps/ticdesk/templates/ticdesk/teckids_project/manage.html
rename to aleksis/apps/ticdesk/templates/ticdesk/teckids_event/manage.html
diff --git a/aleksis/apps/ticdesk/templates/ticdesk/teckids_project/register.html b/aleksis/apps/ticdesk/templates/ticdesk/teckids_event/register.html
similarity index 100%
rename from aleksis/apps/ticdesk/templates/ticdesk/teckids_project/register.html
rename to aleksis/apps/ticdesk/templates/ticdesk/teckids_event/register.html
diff --git a/aleksis/apps/ticdesk/templates/ticdesk/teckids_project/register_additional.html b/aleksis/apps/ticdesk/templates/ticdesk/teckids_event/register_additional.html
similarity index 100%
rename from aleksis/apps/ticdesk/templates/ticdesk/teckids_project/register_additional.html
rename to aleksis/apps/ticdesk/templates/ticdesk/teckids_event/register_additional.html
diff --git a/aleksis/apps/ticdesk/views.py b/aleksis/apps/ticdesk/views.py
index 054af97..909973c 100644
--- a/aleksis/apps/ticdesk/views.py
+++ b/aleksis/apps/ticdesk/views.py
@@ -79,39 +79,38 @@ def events(request):
 
     return render(request, "ticdesk/teckids_event/list.html", context)
 
-
-@login_required
+@reversion.register()
+@person_required
 def register_event(request, id_):
     context = {}
 
     # Get current person and event
-    current_person = Person.objects.get(user__username=request.user.username)
     event = event.objects.get(id=id_)
     context["event"] = event
 
     initial = {
-        "person": current_person,
+        "person": request.user.person,
         "event": event,
-        "school": current_person.school,
-        "school_place": current_person.school_place,
-        "school_class": current_person.school_class,
-        "mobile_number": current_person.mobile_number,
-        "email": current_person.email,
-        "street": current_person.street,
-        "place": current_person.place,
-        "housenumber": current_person.housenumber,
-        "sex": current_person.sex,
-        "date_of_birth": current_person.date_of_birth,
-        "postal_code": current_person.postal_code,
+        "school": request.user.person.school,
+        "school_place": request.user.person.school_place,
+        "school_class": request.user.person.school_class,
+        "mobile_number": request.user.person.mobile_number,
+        "email": request.user.person.email,
+        "street": request.user.person.street,
+        "place": request.user.person.place,
+        "housenumber": request.user.person.housenumber,
+        "sex": request.user.person.sex,
+        "date_of_birth": request.user.person.date_of_birth,
+        "postal_code": request.user.person.postal_code,
     }
 
-    if current_person.guardians.first():
+    if request.user.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,
+                "guardian_first_name": request.user.person.guardians.first().first_name,
+                "guardian_last_name": request.user.person.guardians.first().last_name,
+                "guardian_mobile_number": request.user.person.guardians.first().mobile_number,
+                "guardian_email": request.user.person.guardians.first().email,
             }
         )
 
@@ -123,7 +122,7 @@ def register_event(request, id_):
         return redirect("events")
 
     # Check whether person is already a member of the event
-    if current_person in event.members.all():
+    if request.user.person in event.group.members.all():
         messages.success(request, _("You are already registred."))
         return redirect("events")
 
@@ -139,18 +138,18 @@ def register_event(request, id_):
                 or "sex" in register_form.changed_data
                 or "date_of_birth" in register_form.changed_data,
             ):
-                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[
+                request.user.person.school = register_form.cleaned_data["school"]
+                request.user.person.school_class = register_form.cleaned_data["school_class"]
+                request.user.person.school_place = register_form.cleaned_data["school_place"]
+                request.user.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[
+                request.user.person.sex = register_form.cleaned_data["sex"]
+                request.user.person.date_of_birth = register_form.cleaned_data[
                     "date_of_birth"
                 ]
-                with reversion.create_revision():
-                    current_person.save()
+
+                request.user.person.save()
 
             # Store postal address in database
             if (
@@ -159,11 +158,11 @@ def register_event(request, id_):
                 or "street" in register_form.changed_data
             ):
 
-                current_person.street = register_form.cleaned_data["steet"]
-                current_person.postal_code = register_form.cleaned_data["postal_code"]
-                current_person.place = register_form.cleaned_data["place"]
-                with reversion.create_revision():
-                    current_person.save()
+                request.user.person.street = register_form.cleaned_data["steet"]
+                request.user.person.postal_code = register_form.cleaned_data["postal_code"]
+                request.user.person.place = register_form.cleaned_data["place"]
+
+                request.user.person.save()
 
             if (
                 "guardian_first_name" in register_form.changed_data
@@ -180,17 +179,14 @@ def register_event(request, id_):
                     email=register_form.cleaned_data["guardian_email"],
                 )
 
-                current_person.guardians.add(guardian[0])
-                with reversion.create_revision():
-                    current_person.save()
+                request.user.person.guardians.add(guardian[0])
+                request.user.person.save()
 
             # Add the current person to the event
-            event.add_member(current_person)
-            with reversion.create_revision():
-                event.save()
+            event.add_member(request.user.person)
+            event.save()
 
-            with reversion.create_revision():
-                registration = register_form.save(commit=True)
+            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"]
@@ -204,13 +200,13 @@ def register_event(request, id_):
 
             # Produce e-mail
             message = EmailMessage()
-            message.reply_to = (current_person.mail,)
+            message.reply_to = (request.user.person.mail,)
             message.to = [
                 "orga@teckids.org",
             ]
             message.subject = _("New event:") % (event,)
             message.extra_headers = {
-                "X-OTRS-CustomerUser": current_person.user.username,
+                "X-OTRS-CustomerUser": request.user.person.user.username,
             }
             message.body = ""
             message.body += form_to_text_table(edit_event_form, 78)
@@ -240,73 +236,6 @@ def register_event(request, id_):
     return render(request, "ticdesk/teckids_event/register.html", context)
 
 
-@login_required
-def register_event_additional(request, cn):
-    context = {}
-
-    # Get current person and event
-    current_person = Person.objects.get(user__username=request.user.username)
-    event = event.objects.get(cn=cn)
-    context["event"] = event
-
-    register_form = EventAdditionalSurveyForm(event)
-
-    # Check whether person is already a member of the event
-    if current_person not in event.members:
-        return redirect("events")
-        messages.error(request, _("You are not registered for this event."))
-
-    if request.method == "POST":
-        register_form = EventAdditionalSurveyForm(event, request.POST)
-        if register_form.is_valid():
-            # Store additional registration fields
-            for field in event.registration_fields:
-                label, help_text, *choices = field.split("|")
-                var = re.sub(r"[^A-Za-z0-9]|^(?=\d)", "_", label)
-
-                field = RegistrationField.objects.get_or_create(
-                    event=event, person=current_person, title=label
-                )[0]
-
-                field.content = register_form.cleaned_data[var]
-                with reversion.create_revision():
-                    field.save()
-
-            # Produce e-mail to registration queue
-            message = EmailMessage()
-            message.reply_to = (current_person.mail,)
-            message.to = [
-                "anmeldung@teckids.org",
-            ]
-            message.subject = _("Additional informaion: %s from %s") % (
-                event.display_name,
-                current_person.cn,
-            )
-            message.extra_headers = {
-                "X-OTRS-DynamicField-TeckidsEvent": event.display_name,
-                "X-OTRS-CustomerUser": current_person.user.username,
-            }
-            message.body = ""
-            message.body += form_to_text_table(register_form, 78)
-
-            # Attach raw form data as attachment
-            message.attach(
-                "register_form.json",
-                json.dumps(register_form.cleaned_data, indent=4, default=str),
-                "application/json",
-            )
-
-            # Send message
-            message.send()
-
-            # Set success
-            context["success"] = True
-
-    context["register_form"] = register_form
-
-    return render(request, "ticdesk/teckids_event/register_additional.html", context)
-
-
 @login_required
 def feedback_event(request, id_):
     context = {}
@@ -325,7 +254,7 @@ def feedback_event(request, id_):
     feedback_form = EventFeedbackForm(event, initial=initial)
 
     # Check whether person is a member of the event
-    if current_person not in event.members.all():
+    if current_person not in event.group.members.all():
         return redirect("events")
         messages.error(request, _("You did not take part in this event."))
 
@@ -388,10 +317,8 @@ def edit_event(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse:
     """View to edit or create an event."""
     context = {}
 
-    current_person = request.user.person
     event = objectgetter_optional(event, None, False)(request, id_)
     context["event"] = event
-    context["person"] = current_person
 
     if id_:
         # Edit form for existing event
diff --git a/poetry.lock b/poetry.lock
index 0670b94..311057b 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -85,6 +85,7 @@ ldap = ["django-auth-ldap (^2.2)"]
 reference = "7f1a59aaaef8cde8007afcb0742ac9934f4115b5"
 type = "git"
 url = "https://edugit.org/AlekSIS/official/AlekSIS"
+
 [[package]]
 category = "dev"
 description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
@@ -552,6 +553,14 @@ version = "3.14.1"
 [package.dependencies]
 django = ">=1.11"
 
+[[package]]
+category = "main"
+description = "IBAN field for django with validation and optional postgresql in database constraint checking"
+name = "django-iban-field"
+optional = false
+python-versions = "*"
+version = "0.8"
+
 [[package]]
 category = "main"
 description = "A reusable app for cropping images easily and non-destructively in Django"
@@ -2134,7 +2143,7 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
 testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"]
 
 [metadata]
-content-hash = "4a2973c045b427cd99b71aa5d77484f4b42cce7c141e6af4f1bac57ecdac22f7"
+content-hash = "014c8ff1cf6f39c409543d9a021dfa3514a8fb2f92d32ade9b648b1c81989e92"
 python-versions = "^3.7"
 
 [metadata.files]
@@ -2336,6 +2345,9 @@ django-health-check = [
     {file = "django-health-check-3.14.1.tar.gz", hash = "sha256:08706f3b7a36b1690381779880b357c8d26d0b05109425eb891febd08993bbf1"},
     {file = "django_health_check-3.14.1-py2.py3-none-any.whl", hash = "sha256:19a136e2da4bad473e29e0f12f866f33d80b3d778b959290e09617738539c0bc"},
 ]
+django-iban-field = [
+    {file = "django_iban_field-0.8-py2.py3-none-any.whl", hash = "sha256:9d11eacb49b939702aa169aa0a3c9880970ed087c236279c32c26f86c7e10092"},
+]
 django-image-cropping = [
     {file = "django-image-cropping-1.5.0.tar.gz", hash = "sha256:59744e8df88db7e46e37b526fc715fdde665d9efa345922745f50411a6dadb3f"},
     {file = "django_image_cropping-1.5.0-py3-none-any.whl", hash = "sha256:81dbcabb6421c5a1e88fac9d96f336d6109a23dcb8fa6c678329d3688c9973c4"},
diff --git a/pyproject.toml b/pyproject.toml
index 174dee5..c1f4034 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -31,6 +31,7 @@ python-dateutil = "^2.8.1"
 python-pam = "^1.8.4"
 python-resize-image = "^1.1.19"
 redis-collections = "^0.8.0"
+django-iban-field = "^0.8"
 
 [tool.poetry.dev-dependencies]
 sphinx = "^3.0"
-- 
GitLab