From 2bbcbb26225ad3b5b95d209af49e868503ba5efb Mon Sep 17 00:00:00 2001
From: Tom Teichler <t.teichler@babiel.com>
Date: Tue, 30 Nov 2021 16:57:01 +0100
Subject: [PATCH] Implement frontend

---
 aleksis/apps/paweljong/apps.py                |   4 +-
 aleksis/apps/paweljong/filters.py             |  54 ++
 aleksis/apps/paweljong/forms.py               | 528 ++++++++++++++++
 aleksis/apps/paweljong/menus.py               | 116 +++-
 aleksis/apps/paweljong/model_extensions.py    |  10 +
 aleksis/apps/paweljong/predicates.py          |  38 ++
 aleksis/apps/paweljong/preferences.py         |  35 ++
 aleksis/apps/paweljong/rules.py               | 112 ++++
 aleksis/apps/paweljong/tables.py              | 101 ++++
 .../paweljong/templates/paweljong/empty.html  |   8 -
 .../templates/paweljong/event/edit.html       |  22 +
 .../templates/paweljong/event/feedback.html   |  46 ++
 .../templates/paweljong/event/list.html       |  23 +
 .../templates/paweljong/event/manage.html     |  15 +
 .../templates/paweljong/event/register.html   | 116 ++++
 .../paweljong/event/register_additional.html  |  55 ++
 .../paweljong/event_registration/full.html    | 194 ++++++
 .../paweljong/event_registration/list.html    |  25 +
 .../paweljong/feedback_aspect/edit.html       |  13 +
 .../paweljong/feedback_aspect/list.html       |  26 +
 .../templates/paweljong/print/corona.html     |  40 ++
 .../paweljong/print/list_attendance.html      |  80 +++
 .../paweljong/print/list_participants.html    |  53 ++
 .../templates/paweljong/print/list_sign.html  |  35 ++
 .../templates/paweljong/print/manage.html     |  14 +
 .../templates/paweljong/print/voucher.html    |  20 +
 .../templates/paweljong/register.html         |  17 +
 .../templates/paweljong/voucher/edit.html     |  13 +
 .../templates/paweljong/voucher/list.html     |  25 +
 .../templated_email/event_created.email       |  74 +++
 .../templated_email/event_feedback.html       |  69 +++
 .../templated_email/event_registred.email     |  92 +++
 aleksis/apps/paweljong/urls.py                |  36 +-
 aleksis/apps/paweljong/util.py                |  86 +++
 aleksis/apps/paweljong/views.py               | 571 +++++++++++++++++-
 35 files changed, 2744 insertions(+), 22 deletions(-)
 create mode 100644 aleksis/apps/paweljong/filters.py
 create mode 100644 aleksis/apps/paweljong/forms.py
 create mode 100644 aleksis/apps/paweljong/model_extensions.py
 create mode 100644 aleksis/apps/paweljong/predicates.py
 create mode 100644 aleksis/apps/paweljong/preferences.py
 create mode 100644 aleksis/apps/paweljong/rules.py
 create mode 100644 aleksis/apps/paweljong/tables.py
 delete mode 100644 aleksis/apps/paweljong/templates/paweljong/empty.html
 create mode 100644 aleksis/apps/paweljong/templates/paweljong/event/edit.html
 create mode 100644 aleksis/apps/paweljong/templates/paweljong/event/feedback.html
 create mode 100644 aleksis/apps/paweljong/templates/paweljong/event/list.html
 create mode 100644 aleksis/apps/paweljong/templates/paweljong/event/manage.html
 create mode 100644 aleksis/apps/paweljong/templates/paweljong/event/register.html
 create mode 100644 aleksis/apps/paweljong/templates/paweljong/event/register_additional.html
 create mode 100644 aleksis/apps/paweljong/templates/paweljong/event_registration/full.html
 create mode 100644 aleksis/apps/paweljong/templates/paweljong/event_registration/list.html
 create mode 100644 aleksis/apps/paweljong/templates/paweljong/feedback_aspect/edit.html
 create mode 100644 aleksis/apps/paweljong/templates/paweljong/feedback_aspect/list.html
 create mode 100644 aleksis/apps/paweljong/templates/paweljong/print/corona.html
 create mode 100644 aleksis/apps/paweljong/templates/paweljong/print/list_attendance.html
 create mode 100644 aleksis/apps/paweljong/templates/paweljong/print/list_participants.html
 create mode 100644 aleksis/apps/paweljong/templates/paweljong/print/list_sign.html
 create mode 100644 aleksis/apps/paweljong/templates/paweljong/print/manage.html
 create mode 100644 aleksis/apps/paweljong/templates/paweljong/print/voucher.html
 create mode 100644 aleksis/apps/paweljong/templates/paweljong/register.html
 create mode 100644 aleksis/apps/paweljong/templates/paweljong/voucher/edit.html
 create mode 100644 aleksis/apps/paweljong/templates/paweljong/voucher/list.html
 create mode 100644 aleksis/apps/paweljong/templates/templated_email/event_created.email
 create mode 100644 aleksis/apps/paweljong/templates/templated_email/event_feedback.html
 create mode 100644 aleksis/apps/paweljong/templates/templated_email/event_registred.email
 create mode 100644 aleksis/apps/paweljong/util.py

diff --git a/aleksis/apps/paweljong/apps.py b/aleksis/apps/paweljong/apps.py
index 2b8d76a..561fcf4 100644
--- a/aleksis/apps/paweljong/apps.py
+++ b/aleksis/apps/paweljong/apps.py
@@ -7,7 +7,7 @@ class DefaultConfig(AppConfig):
     dist_name = "AlekSIS-App-Paweljong"
 
     urls = {
-        "Repository": "https://edugit.org/Teckids/hacknfun//AlekSIS-App-Paweljong",
+        "Repository": "https://edugit.org//hacknfun//AlekSIS-App-Paweljong",
     }
     licence = "EUPL-1.2+"
-    copyright_info = (([2021], "Dominik George", "dominik.george@teckids.org"),)
+    copyright_info = (([2021], "Dominik George", "dominik.george@.org"),)
diff --git a/aleksis/apps/paweljong/filters.py b/aleksis/apps/paweljong/filters.py
new file mode 100644
index 0000000..f4b44df
--- /dev/null
+++ b/aleksis/apps/paweljong/filters.py
@@ -0,0 +1,54 @@
+from django.utils.translation import gettext_lazy as _
+
+from django_filters import FilterSet
+from material import Layout, Row
+
+from aleksis.core.filters import MultipleCharFilter
+
+from .models import EventRegistration, FeedbackAspect, Voucher
+
+
+class EventRegistrationFilter(FilterSet):
+    class Meta:
+        model = EventRegistration
+        fields = ["person", "event", "accept_sepa", "date_registred"]
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+
+        self.form.layout = Layout(
+            Row("person", "event"),
+            Row("accept_sepa", "date_registred"),
+        )
+
+
+class VoucherFilter(FilterSet):
+    event = MultipleCharFilter(
+        [
+            "event__short_name__icontains",
+        ],
+        label=_("Search by event"),
+    )
+
+    name = MultipleCharFilter(
+        [
+            "person__first_name__icontains",
+            "person__last_name__icontains",
+        ],
+        label=_("Search by name"),
+    )
+
+    class Meta:
+        model = Voucher
+        fields = []
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+
+        self.form.layout = Layout(Row("event", "name"))
+
+
+class FeedbackAspectsFilter(FilterSet):
+    class Meta:
+        model = FeedbackAspect
+        fields = ["aspect"]
diff --git a/aleksis/apps/paweljong/forms.py b/aleksis/apps/paweljong/forms.py
new file mode 100644
index 0000000..0fe993f
--- /dev/null
+++ b/aleksis/apps/paweljong/forms.py
@@ -0,0 +1,528 @@
+import re
+from collections import OrderedDict
+
+from django import forms
+from django.utils.translation import ugettext_lazy as _
+
+import phonenumbers
+from django_select2.forms import ModelSelect2MultipleWidget, ModelSelect2Widget
+from django_starfield import Stars
+from material import Fieldset, Layout, Row
+
+from aleksis.core.forms import AccountRegisterForm
+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 Event, EventRegistration, FeedbackAspect, Voucher
+
+COMMENT_CHOICES = [
+    ("first", _("Only first name")),
+    ("first_age", _("First name and age")),
+    ("first_last_age", _("First name, last name and age")),
+]
+
+TEMPLATE_CHOICES = [
+    ("list_sign", _("Signature list")),
+    ("list_participants", _("Participants list")),
+    ("corona", _("Corona attendance list")),
+]
+
+LICENCE_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"
+        ),
+    ),
+]
+
+NEWSLETTER_CHOICES = get_site_preferences()["paweljong__newsletter_choices"].split(",")
+
+
+def is_phonenumber(number):
+    try:
+        phonenumbers.parse(number, "DE")
+    except BaseException:
+        raise forms.ValidationError(_("%s is not a valid phone number") % number)
+
+
+def is_valid_voucher_for(event_cn):
+    def _is_valid_voucher(code):
+        if Voucher.objects.filter(code=code, event_cn=event_cn, used=False).count() == 0:
+            raise forms.ValidationError(_("The voucher is invalid!"))
+
+    return _is_valid_voucher
+
+
+def clean_phonenumber(field):
+    def _clean_phonenumber(obj):
+        value = obj.cleaned_data[field]
+
+        if value:
+            pn = phonenumbers.parse(value, "DE")
+            value = phonenumbers.format_number(pn, phonenumbers.PhoneNumberFormat.E164)
+
+        return value
+
+    return _clean_phonenumber
+
+
+class EventAdditionalSurveyForm(forms.Form):
+    def __init__(self, event, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.add_fields_from_ldap(event)
+
+    def add_fields_from_ldap(self, event, before=None):
+        new_fields = OrderedDict()
+
+        for field in event.registration_fields:
+            label, help_text, *choices = field.split("|")
+            var = re.sub(r"[^A-Za-z0-9]|^(?=\d)", "_", label)
+
+            if choices:
+                choices_map = [
+                    (re.sub(r"[^A-Za-z0-9]|^(?=\d)", "_", choice), choice) for choice in choices
+                ]
+                field_attr = forms.ChoiceField(
+                    label=label,
+                    help_text=help_text,
+                    choices=choices_map,
+                    required=False,
+                )
+            else:
+                field_attr = forms.CharField(label=label, help_text=help_text, required=False)
+
+            new_fields[var] = field_attr
+
+        if before:
+            before_field_index = list(self.fields.keys()).index(before)
+            field_names = list(self.fields.keys())
+            new_field_order = (
+                field_names[: before_field_index - 1]
+                + list(new_fields.keys())
+                + field_names[before_field_index - 1 :]
+            )
+
+        self.fields.update(new_fields)
+
+        if before:
+            self.order_fields(new_field_order)
+
+
+class EventFeedbackForm(ExtensibleForm):
+    class Meta:
+        model = FeedbackAspect
+        fields = []
+
+    layout = Layout(
+        Fieldset(
+            _("Comments"),
+            Row("comment_private", "comment_public", "comment_public_info"),
+        ),
+        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."
+        ),
+        widget=forms.Textarea,
+    )
+
+    comment_public = forms.CharField(
+        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."
+        ),
+        widget=forms.Textarea,
+    )
+
+    comment_public_info = forms.ChoiceField(
+        label=_("Information in the comment"),
+        choices=COMMENT_CHOICES,
+        help_text=_("What information would you like to use to publish your comment?"),
+    )
+
+    photos = forms.ImageField(
+        label=_("Photos"),
+        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."
+        ),
+    )
+
+    photos_licence = forms.ChoiceField(
+        label=_("Photo licence"),
+        choices=LICENCE_CHOICES,
+        required=False,
+        help_text=_("If you upload photos, choose a license here."),
+    )
+
+    def __init__(self, event, *args, **kwargs):
+        super(EventFeedbackForm, self).__init__(*args, **kwargs)
+        self._event = event
+
+        for aspect in event.feedback_aspects.all():
+            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)
+
+
+class EditEventForm(forms.ModelForm):
+    """Form to create or edit an event."""
+
+    layout = Layout(
+        Fieldset(
+            _("Base data"),
+            "group",
+            Row("display_name", "description"),
+            Row("place", "published"),
+            Fieldset(_("Date data"), Row("date_event", "date_registration", "date_retraction")),
+            Fieldset(_("Event details"), Row("cost", "max_participants")),
+            Fieldset(_("Feedback aspects"), "feedback_aspects"),
+        ),
+    )
+
+    class Meta:
+        model = Event
+        exclude = []
+        widgets = {
+            "group": ModelSelect2Widget(
+                search_fields=["name__icontains", "short_name__icontains"],
+                attrs={"data-minimum-input-length": 0, "class": "browser-default"},
+            ),
+            "feedback_aspects": ModelSelect2MultipleWidget(
+                search_fields=["aspect__icontains"],
+                attrs={"data-minimum-input-length": 0, "class": "browser-default"},
+            ),
+        }
+
+
+class EditVoucherForm(forms.ModelForm):
+    """Form to edit and create vouchers."""
+
+    class Meta:
+        model = Voucher
+        exclude = ["code", "used_person_uid", "used", "deleted"]
+        help_texts = {
+            "event": _("Event the voucher is valid for."),
+            "person": _("Person the voucher is valid for."),
+            "discount": _("Voucher discount."),
+        }
+
+
+class GenerateListForm(forms.Form):
+    """Form to create a list of participants of a group."""
+
+    group = forms.ModelChoiceField(
+        label=_("Group"),
+        queryset=Group.objects.all(),
+        help_text=_("Select group to generate list."),
+    )
+
+    template = forms.ChoiceField(
+        label=_("Template"),
+        choices=TEMPLATE_CHOICES,
+        help_text=_("Select template to generate list."),
+    )
+
+    landscape = forms.BooleanField(
+        label=_("Landscape"),
+        help_text=_("Select if output should be in landscape."),
+    )
+
+
+class RegisterEventForm(forms.ModelForm):
+    """Form to register for an event."""
+
+    layout = Layout(
+        Fieldset(
+            _("Address data"),
+            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(
+            _("Guardians personal data"),
+            Row("guardian_first_name", "guardian_last_name"),
+        ),
+        Fieldset(
+            _("Guardians contact details"),
+            Row("guardian_email", "guardian_mobile_number"),
+        ),
+        Fieldset(
+            _("General event information"),
+            Row("event", "person"),
+            Row("comment", "channel"),
+        ),
+        Fieldset(
+            _("Financial data"),
+            "voucher_code",
+            Row("iban", "donation", "accept_sepa"),
+        ),
+        Fieldset(
+            _("Declaration of consent"),
+            Row("accept_terms", "accept_data", "accept_general_terms"),
+        ),
+    )
+
+    class Meta:
+        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  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  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"
+                "terms of use 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" "AGB 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."
+        ),
+    )
+
+    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."
+        ),
+    )
+
+    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."
+        ),
+    )
+
+    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,
+    )
+
+    street = forms.CharField(
+        label=_("Street"),
+    )
+
+    housenumber = forms.CharField(
+        label=_("Housenumber"),
+    )
+
+    postal_code = forms.CharField(
+        label=_("Postal code"),
+    )
+
+    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],
+    )
+
+    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,
+    )
+
+    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."),
+    )
+
+    school_place = forms.CharField(
+        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)."),
+    )
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+
+        self.fields["event"].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
+
+
+class EditEventRegistrationForm(forms.ModelForm):
+
+    layout = Layout(
+        Fieldset(
+            _("General event information"),
+            Row("event", "person"),
+            Row("comment", "channel"),
+        ),
+        Fieldset(
+            _("Financial data"),
+            "voucher_code",
+            Row("iban", "donation", "accept_sepa"),
+        ),
+        Fieldset(
+            _("Declaration of consent"),
+            Row("accept_terms", "accept_data", "accept_general_terms"),
+        ),
+    )
+
+    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  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  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"
+                "terms of use 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" "AGB and have read them."),
+            "channel": _("How did you find out about the event?"),
+        }
+        exclude = []
+
+
+class EditFeedbackAspectForm(forms.ModelForm):
+    class Meta:
+        model = FeedbackAspect
+        exclude = []
+
+
+date_of_birth = forms.DateField(label=_("Date of birth"))
+extend_register_form = Fieldset(date_of_birth)
+AccountRegisterForm.add_node_to_layout(extend_register_form)
diff --git a/aleksis/apps/paweljong/menus.py b/aleksis/apps/paweljong/menus.py
index 5872a6a..073566e 100644
--- a/aleksis/apps/paweljong/menus.py
+++ b/aleksis/apps/paweljong/menus.py
@@ -1,16 +1,116 @@
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext_lazy as _
 
 MENUS = {
     "NAV_MENU_CORE": [
         {
-            "name": _("Paweljong"),
-            "url": "empty",
+            "name": _("Events"),
+            "url": "events",
+            "icon": "event",
+        },
+        {
+            "name": _("Vouchers"),
+            "url": "#",
+            "icon": "confirmation_number",
             "root": True,
             "validators": [
-                "menu_generator.validators.is_authenticated",
-                "aleksis.core.util.core_helpers.has_person",
+                (
+                    "aleksis.core.util.predicates.permission_validator",
+                    "paweljong.view_vouchers_rule",
+                )
+            ],
+            "submenu": [
+                {
+                    "name": _("Voucher overview"),
+                    "url": "vouchers",
+                    "icon": "confirmation_number",
+                    "validators": [
+                        (
+                            "aleksis.core.util.predicates.permission_validator",
+                            "paweljong.change_vouchers_rule",
+                        )
+                    ],
+                },
+                {
+                    "name": _("Create voucher"),
+                    "url": "create_vouchers",
+                    "icon": "post_add",
+                    "validator": [
+                        (
+                            "aleksis.core.util.predicates.permission_validator",
+                            "paweljong.create_vouchers_rule",
+                        )
+                    ],
+                },
+            ],
+        },
+        {
+            "name": _("Event management"),
+            "url": "#",
+            "icon": "event_note",
+            "root": True,
+            "validators": [
+                (
+                    "aleksis.core.util.predicates.permission_validator",
+                    "paweljong.change_events_rule",
+                )
+            ],
+            "submenu": [
+                {
+                    "name": _("Create event"),
+                    "url": "create_event",
+                    "icon": "event_available",
+                    "validators": [
+                        (
+                            "aleksis.core.util.predicates.permission_validator",
+                            "paweljong.create_events_rule",
+                        )
+                    ],
+                },
+                {
+                    "name": _("Manage feedback aspects"),
+                    "url": "feedback_aspects",
+                    "icon": "rate_review",
+                    "validators": [
+                        (
+                            "aleksis.core.util.predicates.permission_validator",
+                            "paweljong.view_feedback_aspects_rule",
+                        )
+                    ],
+                },
+                {
+                    "name": _("Generate participant list"),
+                    "url": "generate_lists",
+                    "icon": "format_list_numbered",
+                    "validators": [
+                        (
+                            "aleksis.core.util.predicates.permission_validator",
+                            "paweljong.generate_lists_rule",
+                        )
+                    ],
+                },
+                {
+                    "name": _("Manage upcoming events"),
+                    "url": "manage_events",
+                    "icon": "change",
+                    "validators": [
+                        (
+                            "aleksis.core.util.predicates.permission_validator",
+                            "paweljong.change_events_rule",
+                        )
+                    ],
+                },
+                {
+                    "name": _("Manage registrations"),
+                    "url": "registrations",
+                    "icon": "how_to_reg",
+                    "validators": [
+                        (
+                            "aleksis.core.util.predicates.permission_validator",
+                            "paweljong.view_registrations_rule",
+                        )
+                    ],
+                },
             ],
-            "submenu": [],
-        }
-    ]
+        },
+    ],
 }
diff --git a/aleksis/apps/paweljong/model_extensions.py b/aleksis/apps/paweljong/model_extensions.py
new file mode 100644
index 0000000..09f8924
--- /dev/null
+++ b/aleksis/apps/paweljong/model_extensions.py
@@ -0,0 +1,10 @@
+from django.utils.translation import gettext_lazy as _
+
+from jsonstore import CharField
+
+from aleksis.core.models import Person
+
+# Additional fields for persons
+Person.field(school=CharField(verbose_name=_("Name of school")))
+Person.field(school_class=CharField(verbose_name=_("School class")))
+Person.field(school_place=CharField(verbose_name=_("Place of the school")))
diff --git a/aleksis/apps/paweljong/predicates.py b/aleksis/apps/paweljong/predicates.py
new file mode 100644
index 0000000..7288954
--- /dev/null
+++ b/aleksis/apps/paweljong/predicates.py
@@ -0,0 +1,38 @@
+from django.contrib.auth import get_user_model
+
+from rules import predicate
+
+from aleksis.core.models import Group, Person
+from aleksis.core.util.predicates import check_object_permission
+
+from .models import EventRegistration, Voucher
+
+User = get_user_model()
+
+
+@predicate
+def see_group_by_grouptype(user: User, group: Group) -> bool:
+    """Predicate which checks if the user is allowed to see the groups GroupType."""
+    grouptype = group.group_type
+
+    return check_object_permission(user, "core.view_grouptype", grouptype)
+
+
+@predicate
+def see_owned_groups_members(user: User, person: Person) -> bool:
+    """Owners of groups can see their members."""
+    groups_list = user.person.owner_of.all().values_list("id", flat=True)
+
+    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_registration(user: User, registration: EventRegistration) -> bool:
+    """Predicate which checks if the registration belongs to the user."""
+    return registration.person == user.person
diff --git a/aleksis/apps/paweljong/preferences.py b/aleksis/apps/paweljong/preferences.py
new file mode 100644
index 0000000..d48689d
--- /dev/null
+++ b/aleksis/apps/paweljong/preferences.py
@@ -0,0 +1,35 @@
+from django.utils.translation import gettext_lazy as _
+
+from dynamic_preferences.preferences import Section
+from dynamic_preferences.types import StringPreference
+
+from aleksis.core.registries import site_preferences_registry
+
+paweljong = Section("paweljong")
+
+
+@site_preferences_registry.register
+class NewsletterChoices(StringPreference):
+    section = paweljong
+    name = "newsletter_choices"
+    default = ""
+    required = False
+    verbose_name = _("Newsletter choices (comma-seperated)")
+
+
+@site_preferences_registry.register
+class WWSPostUrl(StringPreference):
+    section = paweljong
+    name = "wws_post_url"
+    default = ""
+    required = False
+    verbose_name = _("POST url for Sympa")
+
+
+@site_preferences_registry.register
+class ChannelChoices(StringPreference):
+    section = paweljong
+    name = "channel_choices"
+    default = ""
+    requred = False
+    verbose_name = _("Channel choices")
diff --git a/aleksis/apps/paweljong/rules.py b/aleksis/apps/paweljong/rules.py
new file mode 100644
index 0000000..50723ee
--- /dev/null
+++ b/aleksis/apps/paweljong/rules.py
@@ -0,0 +1,112 @@
+import rules
+
+from aleksis.core.models import Group
+from aleksis.core.util.predicates import (
+    has_any_object,
+    has_global_perm,
+    has_object_perm,
+    has_person,
+    is_group_member,
+)
+
+from .models import Event, EventRegistration, FeedbackAspect, Voucher
+from .predicates import (
+    is_own_registration,
+    is_own_voucher,
+    see_group_by_grouptype,
+    see_owned_groups_members,
+)
+
+# View vouchers
+view_vouchers_predicate = has_person & (
+    has_global_perm("paweljong.view_voucher") | has_any_object("paweljong.view_voucher", Voucher)
+)
+rules.add_perm("paweljong.view_vouchers_rule", view_vouchers_predicate)
+
+# Edit vouchers
+change_vouchers_predicate = has_person & (
+    has_global_perm("paweljong.change_voucher")
+    | has_any_object("paweljong.change_voucher", Voucher)
+)
+rules.add_perm("paweljong.change_vouchers_rule", change_vouchers_predicate)
+
+
+# Delete vouchers
+delete_vouchers_predicate = has_person & (
+    has_global_perm("paweljong.delete_voucher")
+    | has_any_object("paweljong.delete_voucher", Voucher)
+)
+rules.add_perm("paweljong.delete_vouchers_rule", delete_vouchers_predicate)
+
+# Create vouchers
+create_vouchers_predicate = has_person & (
+    has_global_perm("paweljong.create_voucher")
+    | has_any_object("paweljong.create_voucher", Voucher)
+)
+rules.add_perm("paweljong.create_vouchers_rule", create_vouchers_predicate)
+
+# Edit events
+change_events_predicate = has_person & (
+    has_global_perm("paweljong.change_event") | has_any_object("paweljong.change_event", Event)
+)
+rules.add_perm("paweljong.change_events_rule", change_events_predicate)
+
+
+# Delete events
+delete_events_predicate = has_person & (
+    has_global_perm("paweljong.delete_event") | has_any_object("paweljong.delete_event", Event)
+)
+rules.add_perm("paweljong.delete_events_rule", delete_events_predicate)
+
+# Create events
+create_events_predicate = has_person & (
+    has_global_perm("paweljong.create_event") | has_any_object("paweljong.create_event", Event)
+)
+rules.add_perm("paweljong.create_events_rule", create_events_predicate)
+
+# Allowed to see group
+may_see_group_predicate = has_person & (
+    is_group_member | has_any_object("core.view_group", Group) | see_group_by_grouptype
+)
+rules.add_perm("paweljong.may_see_group_rule", may_see_group_predicate)
+
+may_see_person_predicate = has_person & (
+    see_owned_groups_members | has_object_perm("core.view_person")
+)
+rules.add_perm("paweljong.see_person_rule", may_see_person_predicate)
+
+# View registrations
+view_registrations_predicate = has_person & (
+    has_global_perm("paweljong.view_eventregistration")
+    | has_any_object("paweljong.view_eventregistration", EventRegistration)
+)
+rules.add_perm("paweljong.view_registrations_rule", view_registrations_predicate)
+
+
+# Manage registrations
+manage_registrations_predicate = has_person & (
+    has_global_perm("paweljong.manage_registration")
+    | is_own_registration
+    | has_any_object("paweljong.manage_registration", EventRegistration)
+)
+rules.add_perm("paweljong.manage_registrations_rule", manage_registrations_predicate)
+
+# Delete registrations
+delete_registrations_predicate = has_person & (
+    has_global_perm("paweljong.delete_eventregistration")
+    | has_any_object("paweljong.delete_eventregistration", EventRegistration)
+)
+rules.add_perm("paweljong.delete_registrations_rule", delete_registrations_predicate)
+
+
+# Is own voucher?
+is_own_voucher_predicate = has_person & (is_own_voucher)
+rules.add_perm("paweljong.is_own_voucher_rule", is_own_voucher_predicate)
+
+
+# View feedback aspects
+view_feedback_aspects_predicate = has_person & (
+    has_global_perm("paweljong.view_feedback_aspect")
+    | has_any_object("paweljong.view_feedback_aspect", FeedbackAspect)
+)
+rules.add_perm("paweljong.view_feedback_aspects_rule", view_feedback_aspects_predicate)
diff --git a/aleksis/apps/paweljong/tables.py b/aleksis/apps/paweljong/tables.py
new file mode 100644
index 0000000..60aa6c7
--- /dev/null
+++ b/aleksis/apps/paweljong/tables.py
@@ -0,0 +1,101 @@
+from django.utils.translation import ugettext_lazy as _
+
+import django_tables2 as tables
+from django_tables2.utils import A
+
+
+class EventsTable(tables.Table):
+    class Meta:
+        attrs = {"class": "responsive-table highlight"}
+
+    display_name = tables.Column(verbose_name=_("Event"))
+    date_event = tables.Column(verbose_name=_("Date"))
+    max_participants = tables.Column(verbose_name=_("Max. participants"))
+    date_registration = tables.Column(verbose_name=_("Registration until"))
+
+    short_name = tables.LinkColumn(
+        "register_event_by_id",
+        args=[A("id")],
+        verbose_name=_("Register"),
+        text=_("Register"),
+    )
+
+
+class ParticipatedEventsTable(tables.Table):
+    class Meta:
+        attrs = {"class": "responsive-table highlight"}
+
+    display_name = tables.Column(verbose_name=_("Event"))
+    date_event = tables.Column(verbose_name=_("Date"))
+
+    short_name = tables.LinkColumn(
+        "feedback_event_by_id",
+        args=[A("id")],
+        verbose_name=_("Feedback"),
+        text=_("Feedback"),
+    )
+
+
+class ManageEventsTable(tables.Table):
+    class Meta:
+        attrs = {"class": "responsive-table highlight"}
+
+    display_name = tables.Column(verbose_name=_("Event"))
+    date_event = tables.Column(verbose_name=_("Date"))
+    max_participants = tables.Column(verbose_name=_("Max. participants"))
+    date_registration = tables.Column(verbose_name=_("Registration until"))
+
+    short_name = tables.LinkColumn(
+        "edit_event_by_id", args=[A("id")], verbose_name=_("Edit"), text=_("Edit")
+    )
+
+
+class VouchersTable(tables.Table):
+    class Meta:
+        attrs = {"class": "responsive-table highlight"}
+
+    event = tables.Column(verbose_name=_("Event"))
+    discount = tables.Column(verbose_name=_("Amount"))
+    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"),
+    )
+    edit = tables.LinkColumn(
+        "edit_voucher_by_id", args=[A("id")], verbose_name=_("Edit"), text=_("Edit")
+    )
+    print_voucher = tables.LinkColumn(
+        "print_voucher_by_id", args=[A("id")], verbose_name=_("Print"), text=_("Print")
+    )
+
+
+class EventRegistrationsTable(tables.Table):
+    class Meta:
+        attrs = {"class": "responsive-table highlight"}
+
+    person = tables.Column()
+    event = tables.Column()
+    date_registred = tables.Column()
+    view = tables.LinkColumn(
+        "registration_by_id",
+        args=[A("id")],
+        verbose_name=_("View registration"),
+        text=_("View"),
+    )
+
+
+class FeedbackAspectsTable(tables.Table):
+    class Meta:
+        attrs = {"class": "responsive-table highlight"}
+
+    aspect = tables.Column()
+
+    edit = tables.LinkColumn(
+        "edit_feedback_aspect_by_id",
+        args=[A("id")],
+        verbose_name=_("Edit"),
+        text=_("Edit"),
+    )
diff --git a/aleksis/apps/paweljong/templates/paweljong/empty.html b/aleksis/apps/paweljong/templates/paweljong/empty.html
deleted file mode 100644
index 67dc22d..0000000
--- a/aleksis/apps/paweljong/templates/paweljong/empty.html
+++ /dev/null
@@ -1,8 +0,0 @@
-{% extends 'core/base.html' %}
-{% load i18n %}
-
-{% block content %}
-  <p class="flow-text">
-   {% blocktrans %}Paweljong (Camp/Event management){% endblocktrans %}
-  </p>
-{% endblock %}
diff --git a/aleksis/apps/paweljong/templates/paweljong/event/edit.html b/aleksis/apps/paweljong/templates/paweljong/event/edit.html
new file mode 100644
index 0000000..61d7275
--- /dev/null
+++ b/aleksis/apps/paweljong/templates/paweljong/event/edit.html
@@ -0,0 +1,22 @@
+{% extends "core/base.html" %}
+{% load material_form i18n any_js %}
+
+{% block page_title %}{% blocktrans %}Edit event{% endblocktrans %}{% endblock %}
+{% block browser_title %}{% blocktrans %}Edit event{% endblocktrans %}{% endblock %}
+
+{% block extra_head %}
+    {{ edit_event_form.media.css }}
+    {% include_css "select2-materialize" %}
+{% endblock %}
+
+{% block content %}
+
+  <form method="post">
+    {% csrf_token %}
+    {% form form=edit_event_form %}{% form %}
+    {% include "core/partials/save_button.html" %}
+  </form>
+  {% include_js "select2-materialize" %}
+  {{ edit_event_form.media.js }}
+
+{% endblock %}
diff --git a/aleksis/apps/paweljong/templates/paweljong/event/feedback.html b/aleksis/apps/paweljong/templates/paweljong/event/feedback.html
new file mode 100644
index 0000000..d3bb5a2
--- /dev/null
+++ b/aleksis/apps/paweljong/templates/paweljong/event/feedback.html
@@ -0,0 +1,46 @@
+{% extends "core/base.html" %}
+{% load material_form i18n %}
+
+{% block page_title %}{% blocktrans %}Feedback on an event{% endblocktrans %}{% endblock %}
+{% block browser_title %}{% blocktrans %}Feedback on an event{% endblocktrans %}{% endblock %}
+
+{% block content %}
+ <h5>
+  {% blocktrans %}Feedback on{% endblocktrans %} {{ event.display_name }}
+ </h5>
+
+ <div class="row">
+  <div class="col s12 m12">
+   <div class="card info">
+    <div class="card-content">
+     <span class="card-title">{% blocktrans %}How does the feedback work?{% endblocktrans %}</span>
+     <p>
+      {% blocktrans %}
+       The feedback consists of three parts.
+      {% endblocktrans %}
+     </p>
+     <ul>
+      <li>
+       {% blocktrans %}Evaluation of the program points from 1 (not good) to 5 (very good){% endblocktrans %}
+      </li>
+      <li>
+       {% blocktrans %}Private commentary where you can tell us what you liked, what didn't, what we should do differently, etc.{% endblocktrans %}
+      </li>
+      <li>
+       {% blocktrans %}Comment for the report, which helps us and other children to learn as much as possible about the event. What you have experienced yourself is very interesting for the next participants!{% endblocktrans %}
+      </li>
+     </ul>
+     <p>
+      {% blocktrans %}Your feedback will be emailed to us. We may contact you again.{% endblocktrans %}
+     </p>
+    </div>
+   </div>
+  </div>
+ </div>
+
+  <form method="post" enctype="multiform/formdata">
+    {% csrf_token %}
+    {% form form=feedback_form %}{% endform %}
+    {% include "core/partials/save_button.html" %}
+  </form>
+{% endblock %}
diff --git a/aleksis/apps/paweljong/templates/paweljong/event/list.html b/aleksis/apps/paweljong/templates/paweljong/event/list.html
new file mode 100644
index 0000000..55322b9
--- /dev/null
+++ b/aleksis/apps/paweljong/templates/paweljong/event/list.html
@@ -0,0 +1,23 @@
+{% extends "core/base.html" %}
+{% load i18n %}
+{% load render_table from django_tables2 %}
+
+{% block page_title %}{% blocktrans %}Events{% endblocktrans %}{% endblock %}
+{% block browser_title %}{% blocktrans %}Events{% endblocktrans %}{% endblock %}
+
+{% block content %}
+ <h5>
+  {% blocktrans %}Upcoming events{% endblocktrans %}
+ </h5>
+
+ {% render_table events_table %}
+
+ {% if participated_events_table %}
+   <h5>
+    {% blocktrans %}Events you've taken part{% endblocktrans %}
+   </h5>
+
+   {% render_table participated_events_table %}
+ {% endif %}
+
+{% endblock %}
diff --git a/aleksis/apps/paweljong/templates/paweljong/event/manage.html b/aleksis/apps/paweljong/templates/paweljong/event/manage.html
new file mode 100644
index 0000000..3ecb263
--- /dev/null
+++ b/aleksis/apps/paweljong/templates/paweljong/event/manage.html
@@ -0,0 +1,15 @@
+{% extends "core/base.html" %}
+{% load i18n %}
+{% load render_table from django_tables2 %}
+
+{% block page_title %}{% blocktrans %}Manage events{% endblocktrans %}{% endblock %}
+{% block browser_title %}{% blocktrans %}Manage events{% endblocktrans %}{% endblock %}
+
+{% block content %}
+ <h5>
+  {% blocktrans %}Upcoming events{% endblocktrans %}
+ </h5>
+
+ {% render_table object_list %}
+
+{% endblock %}
diff --git a/aleksis/apps/paweljong/templates/paweljong/event/register.html b/aleksis/apps/paweljong/templates/paweljong/event/register.html
new file mode 100644
index 0000000..0613fb7
--- /dev/null
+++ b/aleksis/apps/paweljong/templates/paweljong/event/register.html
@@ -0,0 +1,116 @@
+{% extends "core/base.html" %}
+{% load material_form i18n %}
+
+{% block page_title %}{% blocktrans %}Event registration{% endblocktrans %}{% endblock %}
+{% block browser_title %}{% blocktrans %}Event registration{% endblocktrans %}{% endblock %}
+
+{% block content %}
+ <h5>
+  {% blocktrans %}Registration for{% endblocktrans %} {{ event.display_name }}
+ </h5>
+
+ <div class="row">
+  <div class="col s12 m6">
+   <div class="card info">
+    <div class="card-content">
+     <span class="card-title">{% blocktrans %}Information for parents{% endblocktrans %}</span>
+     <p>
+      {% blocktrans %}
+       Please go through the registration with your child.  It is important
+       to us to train important skills in dealing with the Internet and
+       responsibility in general with the registration.
+      {% endblocktrans %}
+     </p>
+    </div>
+    <div class="card-content">
+     <span class="card-title">{% blocktrans %}Information for children{% endblocktrans %}</span>
+     <p>
+      {% blocktrans %}
+       Please read everything carefully with your parents and then send your
+       registration together with them!
+      {% endblocktrans %}
+     </p>
+    </div>    
+   </div>
+  </div>
+  <div class="col s12 m6">
+   <div class="card info">
+    <div class="card-content">
+     <span class="card-title">{% blocktrans %}Information about the event{% endblocktrans %}</span>
+     <p>
+      {% blocktrans %}
+       Please read the
+      {% endblocktrans %}
+      
+      <a href="{{ event.website }}">
+       {% blocktrans %}
+        website of the event
+       {% endblocktrans %}
+      </a>
+      
+      {% blocktrans %}
+       carefully together with your parents.
+      {% endblocktrans %}
+     </p>
+     <ul>
+      <li>
+       {% blocktrans %}
+        The event will take place from
+       {% endblocktrans %}
+
+       {{ event.date_event }}
+
+       {% blocktrans %}
+       at
+       {% endblocktrans %}
+
+       {{ event.l }}
+
+      </li>
+      <li>
+       {% blocktrans %}
+        Registration is possible until
+       {% endblocktrans %}
+
+       {{ event.date_registration }};
+
+       {% blocktrans %}
+        the number of participants is limited to
+       {% endblocktrans %}
+       
+       {{ event.max_participants }}
+
+       {% blocktrans %}
+        participants
+       {% endblocktrans %}.
+      </li>
+      <li>
+       {% blocktrans %}
+        The participation fee is
+       {% endblocktrans %}
+
+       {{ event.cost }} €
+
+       {% blocktrans %}
+        and must be paid in advance and within 7 days after receipt of the
+        invoice.  Cancellation with reimbursement is possible until
+       {% endblocktrans %}
+
+       {{ event.date_retraction }}
+
+       {% blocktrans %}
+        and only for good reason.
+       {% endblocktrans %}
+      </li>
+     </ul>
+    </div>
+   </div>
+  </div>
+ </div>
+
+ <form method="post">
+   {% csrf_token %}
+   {% form form=register_form %}{% endform %}
+   {% include "core/partials/save_button.html" %}
+ </form>
+{% endblock %}
diff --git a/aleksis/apps/paweljong/templates/paweljong/event/register_additional.html b/aleksis/apps/paweljong/templates/paweljong/event/register_additional.html
new file mode 100644
index 0000000..c89b1d3
--- /dev/null
+++ b/aleksis/apps/paweljong/templates/paweljong/event/register_additional.html
@@ -0,0 +1,55 @@
+{% extends "ticdesk/base_with_info_sidebar.html" %}
+{% load  i18n %}
+
+{% block page_title %}{% blocktrans %}Zusätzliche Angaben zur Veranstaltungsanmeldung{% endblocktrans %}{% endblock %}
+
+{% block content_info_children %}
+ {% blocktrans %}
+  Bitte lies dir mit deinen Eltern gemeinsam alles genau durch und schicke
+  deine Anmeldung dann mit ihnen gemeinsam ab!
+ {% endblocktrans %}
+{% endblock %}
+
+{% block content_info_parents %}
+ {% blocktrans %}
+  Bitte gehen Sie die Anmeldung mit Ihrem Kind gemeinsam durch. Uns ist es
+  wichtig, schon mit der Anmeldung wichtige Kompetenzen im Umgang mit dem
+  Internet und Verantwortung im Allgemeinen zu trainieren.
+ {% endblocktrans %}
+{% endblock %}
+
+{% block content %}
+ <h5>
+  {% blocktrans %}Zusätzliche Angaben zu{% endblocktrans %} {{ event.display_name }}
+ </h5>
+
+ {% if error %}
+  <div class="alert alert-danger">
+   <div class="alert-heading">
+    {% blocktrans %}Fehler{% endblocktrans %}
+   </div>
+   <hr>
+    <p>
+     {{ error }}
+    </p>
+  </div>
+ {% elif success %}
+  <div class="alert alert-success">
+   <div class="alert-heading">
+    {% blocktrans %}Anmeldung erfolgreich{% endblocktrans %}
+   </div>
+   <hr>
+    <p>
+     {% blocktrans %}
+      Du hast deien Angaben erfolgreich eingesendet.
+     {% endblocktrans %}
+    </p>
+  </div>
+ {% else %}
+  <form method="post">
+   {% csrf_token %}
+   {% bootstrap_form register_form %}
+   <input type="submit" value="{% blocktrans %}Absenden{% endblocktrans %}" />
+  </form>
+ {% endif %}
+{% endblock %}
diff --git a/aleksis/apps/paweljong/templates/paweljong/event_registration/full.html b/aleksis/apps/paweljong/templates/paweljong/event_registration/full.html
new file mode 100644
index 0000000..f6a95d2
--- /dev/null
+++ b/aleksis/apps/paweljong/templates/paweljong/event_registration/full.html
@@ -0,0 +1,194 @@
+{# -*- engine:django -*- #}
+
+{% extends "core/base.html" %}
+
+{% load i18n static rules material_form %}
+{% load render_table from django_tables2 %}
+
+{% block browser_title %}Event registration {{ registration }}{% endblock %}
+
+{% block content %}
+  <h4>{{ registration }} </h4>
+
+  {% has_perm 'ticdesk.manage_registration' user registration as can_manage_registration %}
+  {% has_perm 'ticdesk.delete_registration' user registration as can_delete_registration %}
+
+  {% if can_manage_registration or can_manage_registration_preferences or can_delete_registration %}
+    <p>
+      {% if can_manage_registration %}
+        <a href="{% url 'edit_registration_by_id' registration.id %}" class="btn waves-effect waves-light">
+          <i class="material-icons left">edit</i>
+          {% trans "Edit" %}
+        </a>
+      {% endif %}
+
+      {% if can_delete_registration %}
+        <a href="{% url 'delete_registration_by_id' registration.id %}" class="btn waves-effect waves-light red">
+          <i class="material-icons left">delete</i>
+          {% trans "Delete" %}
+        </a>
+      {% endif %}
+
+    </p>
+  {% endif %}
+
+  <h5>{% blocktrans %}Contact details{% endblocktrans %}</h5>
+  <div class="row">
+    <div class="col s12 m4">
+      {% has_perm 'core.view_photo' user registration.person as can_view_photo %}
+      {% if registration.person.photo and can_view_photo %}
+        <img class="person-img" src="{{ registration.person.photo.url }}"
+             alt="{{ registration.person.first_name }} {{ registration.person.last_name }}"/>
+      {% else %}
+        <img class="person-img" src="{% static 'img/fallback.png' %}"
+             alt="{{ registration.person.first_name }} {{ registration.person.last_name }}"/>
+      {% endif %}
+    </div>
+    <div class="col s12 m8">
+      <table class="responsive-table highlight">
+        <tr>
+          <td rowspan="6">
+
+          </td>
+          <td>
+            <i class="material-icons small">person</i>
+          </td>
+          <td>{{ registration.person.first_name }}</td>
+          <td>{{ registration.person.additional_name }}</td>
+          <td>{{ registration.person.last_name }}</td>
+        </tr>
+        <tr>
+          <td>
+            <i class="material-icons small">face</i>
+          </td>
+          <td colspan="3">{{ registration.person.get_sex_display }}</td>
+        </tr>
+        {% has_perm 'core.view_address' user registration.person as can_view_address %}
+        {% if can_view_address %}
+          <tr>
+            <td>
+              <i class="material-icons small">home</i>
+            </td>
+            <td colspan="2">{{ registration.person.street }} {{ registration.person.housenumber }}</td>
+            <td colspan="2">{{ registration.person.postal_code }} {{ registration.person.place }}</td>
+          </tr>
+        {% endif %}
+        {% has_perm 'core.view_contact_details' user registration.person as can_view_contact_details %}
+        {% if can_view_contact_details %}
+          <tr>
+            <td>
+              <i class="material-icons small">phone</i>
+            </td>
+            <td>{{ registration.person.phone_number }}</td>
+            <td>{{ registration.person.mobile_number }}</td>
+          </tr>
+          <tr>
+            <td>
+              <i class="material-icons small">email</i>
+            </td>
+            <td colspan="3">{{ registration.person.email }}</td>
+          </tr>
+        {% endif %}
+        {% has_perm 'core.view_personal_details' user registration.person as can_view_personal_details %}
+        {% if can_view_personal_details %}
+          <tr>
+            <td>
+              <i class="material-icons small">cake</i>
+            </td>
+            <td colspan="3">{{ registration.person.date_of_birth|date }}</td>
+          </tr>
+        {% endif %}
+      </table>
+    </div>
+    <div class="col s12 m12">
+      <h5>{% trans "Registration information" %}</h5>
+      <table>
+        <tr>
+          <td>
+            <i class="material-icons small">local_activity</a>
+          </td>
+          <td colspan="3"><a href="{% url 'edit_event_by_id' registration.event.id %}">{{ registration.event }}</a></td>
+        </tr>
+        <tr>
+          <td>
+            <i class="material-icons small">redeem</a>
+          </td>
+          <td colspan="3">{{ registration.donation }}</td>
+        </tr>
+        <tr>
+          <td>
+            <i class="material-icons small">payment</a>
+          </td>
+          <td>{% trans "SEPA direct debit" %}: {{ registration.accept_sepa }}</td>
+          {% if registration.accept_sepa %}
+            <td>{% trans "IBAN" %}: {{ registration.iban }}</td>
+          {% endif %}
+        </tr>
+      </table>
+      </p>
+    </div>
+  </div>
+
+  {% if registration.person.guardians.all and can_view_personal_details %}
+  <h5>{% trans "Guardians / Parents "%}</h5>
+  {% for person in registration.person.guardians.all %}
+    <div class="col s12 m8">
+      <table class="responsive-table highlight">
+        <tr>
+          <td rowspan="6">
+
+          </td>
+          <td>
+            <i class="material-icons small">person</i>
+          </td>
+          <td>{{ person.first_name }}</td>
+          <td>{{ person.additional_name }}</td>
+          <td>{{ person.last_name }}</td>
+        </tr>
+        <tr>
+          <td>
+            <i class="material-icons small">face</i>
+          </td>
+          <td colspan="3">{{ person.get_sex_display }}</td>
+        </tr>
+        {% has_perm 'core.view_address' user person as can_view_address %}
+        {% if can_view_address %}
+          <tr>
+            <td>
+              <i class="material-icons small">home</i>
+            </td>
+            <td colspan="2">{{ person.street }} {{ person.housenumber }}</td>
+            <td colspan="2">{{ person.postal_code }} {{ person.place }}</td>
+          </tr>
+        {% endif %}
+        {% has_perm 'core.view_contact_details' user person as can_view_contact_details %}
+        {% if can_view_contact_details %}
+          <tr>
+            <td>
+              <i class="material-icons small">phone</i>
+            </td>
+            <td>{{ person.phone_number }}</td>
+            <td>{{ person.mobile_number }}</td>
+          </tr>
+          <tr>
+            <td>
+              <i class="material-icons small">email</i>
+            </td>
+            <td colspan="3">{{ person.email }}</td>
+          </tr>
+        {% endif %}
+        {% has_perm 'core.view_personal_details' user person as can_view_personal_details %}
+        {% if can_view_personal_details %}
+          <tr>
+            <td>
+              <i class="material-icons small">cake</i>
+            </td>
+            <td colspan="3">{{ person.date_of_birth|date }}</td>
+          </tr>
+        {% endif %}
+      </table>
+    </div>
+  {% endfor %}
+  {% endif %}
+
+{% endblock %}
diff --git a/aleksis/apps/paweljong/templates/paweljong/event_registration/list.html b/aleksis/apps/paweljong/templates/paweljong/event_registration/list.html
new file mode 100644
index 0000000..cb9184d
--- /dev/null
+++ b/aleksis/apps/paweljong/templates/paweljong/event_registration/list.html
@@ -0,0 +1,25 @@
+{% extends "core/base.html" %}
+{% load material_form i18n %}
+
+{% load render_table from django_tables2 %}
+
+{% block page_title %}{% blocktrans %}Registrations{% endblocktrans %}{% endblock %}
+{% block browser_title %}{% blocktrans %}Registrations{% endblocktrans %}{% endblock %}
+
+{% block content %}
+
+  <h5>{% trans "Filter registrations" %}</h5>
+  <form method="get">
+    {% form form=registrations_filter.form %}{% endform %}
+    {% trans "Search" as caption %}
+    {% include "core/partials/save_button.html" with caption=caption icon="search" %}
+    <button type="reset" class="btn red waves-effect waves-light">
+      <i class="material-icons left">clear</i>
+      {% trans "Clear" %}
+    </button>
+  </form>
+
+  <h5>{% trans "Selected registrations" %}</h5>
+  {% render_table registrations_table %}
+
+{% endblock %}
diff --git a/aleksis/apps/paweljong/templates/paweljong/feedback_aspect/edit.html b/aleksis/apps/paweljong/templates/paweljong/feedback_aspect/edit.html
new file mode 100644
index 0000000..96f73df
--- /dev/null
+++ b/aleksis/apps/paweljong/templates/paweljong/feedback_aspect/edit.html
@@ -0,0 +1,13 @@
+{% extends "core/base.html" %}
+{% load material_form i18n %}
+
+{% block page_title %}{% blocktrans %}Edit feedback aspect{% endblocktrans %}{% endblock %}
+{% block browser_title %}{% blocktrans %}Edit feedback aspect{% endblocktrans %}{% endblock %}
+
+{% block content %}
+  <form method="post">
+    {% csrf_token %}
+    {% form form=edit_feedback_aspect_form %}{% endform %}
+    {% include "core/partials/save_button.html" %}
+  </form>
+{% endblock %}
diff --git a/aleksis/apps/paweljong/templates/paweljong/feedback_aspect/list.html b/aleksis/apps/paweljong/templates/paweljong/feedback_aspect/list.html
new file mode 100644
index 0000000..8b02215
--- /dev/null
+++ b/aleksis/apps/paweljong/templates/paweljong/feedback_aspect/list.html
@@ -0,0 +1,26 @@
+{% extends "core/base.html" %}
+{% load material_form i18n %}
+
+{% load render_table from django_tables2 %}
+
+{% block page_title %}{% blocktrans %}Feedback aspects{% endblocktrans %}{% endblock %}
+{% block browser_title %}{% blocktrans %}Feedback aspects{% endblocktrans %}{% endblock %}
+
+{% block content %}
+
+  <h5>{% trans "Filter feedback_aspects" %}</h5>
+  <form method="get">
+    {% form form=feedback_aspects_filter.form %}{% endform %}
+    {% trans "Search" as caption %}
+    {% include "core/partials/save_button.html" with caption=caption icon="search" %}
+    <button type="reset" class="btn red waves-effect waves-light">
+      <i class="material-icons left">clear</i>
+      {% trans "Clear" %}
+    </button>
+    <a class="btn colour-primary waves-effect waves-light" href="{% url 'create_feedback_aspect' %}">{% trans "Create feedback aspect" %}</a>
+  </form>
+
+  <h5>{% blocktrans %}Selected feedback aspects{% endblocktrans %}</h5>
+    {% render_table feedback_aspects_table %}
+
+{% endblock %}
diff --git a/aleksis/apps/paweljong/templates/paweljong/print/corona.html b/aleksis/apps/paweljong/templates/paweljong/print/corona.html
new file mode 100644
index 0000000..5f06adf
--- /dev/null
+++ b/aleksis/apps/paweljong/templates/paweljong/print/corona.html
@@ -0,0 +1,40 @@
+{% extends "core/base_print.html" %}
+{% load i18n static %}
+
+{% block page_title %}
+    {{ group }} — {% trans "Corona" %}
+{% endblock %}
+
+{% block extra_head %}
+    <link rel="stylesheet" href="{% static 'ticdesk.css' %}">
+{% endblock %}
+
+{% block browser_title %}{{ group }} — {% trans "Corona" %}{% endblock %}
+
+{% block content %}
+
+    <table>
+        <thead>
+            <tr>
+                <th class="table-print">{% trans "Last name" %}</th>
+                <th class="table-print">{% trans "First name" %}</th>
+                <th class="table-print">{% trans "Address" %}</th>
+                <th class="table-print">{% trans "Mobile number" %}</th>
+                <th class="table-print">{% trans "Arrival" %}</th>
+                <th class="table-print">{% trans "Departure" %}</th>
+            </tr>
+        </thead>
+        <tbody>
+            {% for person in group.members.all %}
+                    <td class="table-print">{{ person.last_name }}</td>
+                    <td class="table-print">{{ person.first_name }}</td>
+                    <td class="table-print">{{ person.street }} {{ person.housenumber }}, {{ person.postal_code }} {{ person.place }}</td>
+                    <td class="table-print">{{ person.mobile_number }}</td>
+                    <td class="table-print"></td>
+                    <td class="table-print"></td>
+                </tr>
+            {% endfor %}
+        </tbody>
+    </table>
+
+{% endblock %}
diff --git a/aleksis/apps/paweljong/templates/paweljong/print/list_attendance.html b/aleksis/apps/paweljong/templates/paweljong/print/list_attendance.html
new file mode 100644
index 0000000..0064515
--- /dev/null
+++ b/aleksis/apps/paweljong/templates/paweljong/print/list_attendance.html
@@ -0,0 +1,80 @@
+{% extends "core/base_print.html" %}
+{% load i18n static %}
+
+{% block page_title %}
+    {{ group }} — {% trans "attendance list" %}
+{% endblock %}
+
+{% block extra_head %}
+    <link rel="stylesheet" href="{% static 'ticdesk.css' %}">
+{% endblock %}
+
+{% block browser_title %}{{ group }} — {% trans "attendance list" %}{% endblock %}
+
+{% block content %}
+
+    <table>
+        <thead>
+            <tr>
+                <th class="table-print">{% trans "Last name" %}</th>
+                <th class="table-print">{% trans "First name" %}</th>
+                <th class="table-print"></th>
+                <th class="table-print"></th>
+                <th class="table-print"></th>
+                <th class="table-print"></th>
+                <th class="table-print"></th>
+                <th class="table-print"></th>
+                <th class="table-print"></th>
+                <th class="table-print"></th>
+                <th class="table-print"></th>
+                <th class="table-print"></th>
+                <th class="table-print"></th>
+                <th class="table-print"></th>
+                <th class="table-print"></th>
+                <th class="table-print"></th>
+                <th class="table-print"></th>
+                <th class="table-print"></th>
+                <th class="table-print"></th>
+                <th class="table-print"></th>
+                <th class="table-print"></th>
+                <th class="table-print"></th>
+                <th class="table-print"></th>
+                <th class="table-print"></th>
+                <th class="table-print"></th>
+                <th class="table-print"></th>
+            </tr>
+        </thead>
+        <tbody>
+            {% for person in group.members.all %}
+                    <td class="table-print">{{ person.last_name }}</td>
+                    <td class="table-print">{{ person.first_name }}</td>
+                    <th class="table-print"></th>
+                    <th class="table-print"></th>
+                    <th class="table-print"></th>
+                    <th class="table-print"></th>
+                    <th class="table-print"></th>
+                    <th class="table-print"></th>
+                    <th class="table-print"></th>
+                    <th class="table-print"></th>
+                    <th class="table-print"></th>
+                    <th class="table-print"></th>
+                    <th class="table-print"></th>
+                    <th class="table-print"></th>
+                    <th class="table-print"></th>
+                    <th class="table-print"></th>
+                    <th class="table-print"></th>
+                    <th class="table-print"></th>
+                    <th class="table-print"></th>
+                    <th class="table-print"></th>
+                    <th class="table-print"></th>
+                    <th class="table-print"></th>
+                    <th class="table-print"></th>
+                    <th class="table-print"></th>
+                    <th class="table-print"></th>
+                    <th class="table-print"></th>
+                </tr>
+            {% endfor %}
+        </tbody>
+    </table>
+
+{% endblock %}
diff --git a/aleksis/apps/paweljong/templates/paweljong/print/list_participants.html b/aleksis/apps/paweljong/templates/paweljong/print/list_participants.html
new file mode 100644
index 0000000..3ab8aa8
--- /dev/null
+++ b/aleksis/apps/paweljong/templates/paweljong/print/list_participants.html
@@ -0,0 +1,53 @@
+{% extends "core/base_print.html" %}
+{% load i18n static %}
+
+{% block page_title %}
+    {{ group }} — {% trans "participant list" %}
+{% endblock %}
+
+{% block extra_head %}
+    <link rel="stylesheet" href="{% static 'css/ticdesk/ticdesk.css' %}">
+{% endblock %}
+
+{% block browser_title %}{{ group }} — {% trans "participant list" %}{% endblock %}
+
+{% block content %}
+
+    <table>
+        <thead>
+            <tr>
+                <th class="table-print">{% trans "Photo" %}</th>
+                <th class="table-print">{% trans "Last name" %}</th>
+                <th class="table-print">{% trans "First name" %}</th>
+                <th class="table-print">{% trans "Date of birth" %}</th>
+                <th class="table-print">{% trans "Mobile number" %}</th>
+                <th class="table-print">{% trans "School" %}</th>
+                <th class="table-print">{% trans "Class" %}</th>
+            </tr>
+        </thead>
+        <tbody>
+            {% for person in group.members.all %}
+                <tr>
+                    {% if person.photo %}
+                        <td class="table-print">
+                            <img class="person-photo-print" src="{{ person.photo.url }}"
+                                alt="{{ registration.person.first_name }} {{ registration.person.last_name }}"/>
+                        </td>
+                    {% else %}
+                        <td class="table-print">
+                            <img class="person-photo-print" src="{% static 'img/fallback.png' %}"
+                            alt="{{ registration.person.first_name }} {{ registration.person.last_name }}"/>
+                        </td>
+                    {% endif %}
+                    <td class="table-print">{{ person.last_name }}</td>
+                    <td class="table-print">{{ person.first_name }}</td>
+                    <td class="table-print">{{ person.date_of_birth }}</td>
+                    <td class="table-print">{{ person.mobile_number }}</td>
+                    <td class="table-print">{{ person.school }}</td>
+                    <td class="table-print">{{ person.school_class }}</td>
+                </tr>
+            {% endfor %}
+        </tbody>
+    </table>
+
+{% endblock %}
diff --git a/aleksis/apps/paweljong/templates/paweljong/print/list_sign.html b/aleksis/apps/paweljong/templates/paweljong/print/list_sign.html
new file mode 100644
index 0000000..499105a
--- /dev/null
+++ b/aleksis/apps/paweljong/templates/paweljong/print/list_sign.html
@@ -0,0 +1,35 @@
+{% extends "core/base_print.html" %}
+{% load i18n static %}
+
+{% block page_title %}
+    {{ group }} — {% trans "signature list" %}
+{% endblock %}
+
+{% block extra_head %}
+    <link rel="stylesheet" href="{% static 'ticdesk.css' %}">
+{% endblock %}
+
+{% block browser_title %}{{ group }} — {% trans "signature list" %}{% endblock %}
+
+{% block content %}
+
+    <p>{% trans "Meeting"%}: ________________________________________ </p>
+    <table>
+        <thead>
+            <tr>
+                <th class="table-print">{% trans "Last name" %}</th>
+                <th class="table-print">{% trans "First name" %}</th>
+                <th class="table-print">{% trans "Signature" %}</th>
+            </tr>
+        </thead>
+        <tbody>
+            {% for person in group.members.all %}
+                    <td class="table-print">{{ person.last_name }}</td>
+                    <td class="table-print">{{ person.first_name }}</td>
+                    <td class="table-print"></td>
+                </tr>
+            {% endfor %}
+        </tbody>
+    </table>
+
+{% endblock %}
diff --git a/aleksis/apps/paweljong/templates/paweljong/print/manage.html b/aleksis/apps/paweljong/templates/paweljong/print/manage.html
new file mode 100644
index 0000000..01ddaad
--- /dev/null
+++ b/aleksis/apps/paweljong/templates/paweljong/print/manage.html
@@ -0,0 +1,14 @@
+{% extends "core/base.html" %}
+{% load material_form i18n %}
+
+{% block page_title %}{% blocktrans %}Generate list{% endblocktrans %}{% endblock %}
+{% block browser_title %}{% blocktrans %}Generate list{% endblocktrans %}{% endblock %}
+
+{% block content %}
+  <form method="post">
+    {% csrf_token %}
+    {% form form=generate_list_form %}{% endform %}
+    {% trans "Print" as caption %}
+    {% include "core/partials/save_button.html" with caption=caption icon=print%}
+  </form>
+{% endblock %}
diff --git a/aleksis/apps/paweljong/templates/paweljong/print/voucher.html b/aleksis/apps/paweljong/templates/paweljong/print/voucher.html
new file mode 100644
index 0000000..2102cd4
--- /dev/null
+++ b/aleksis/apps/paweljong/templates/paweljong/print/voucher.html
@@ -0,0 +1,20 @@
+{% extends "core/base_print.html" %}
+{% load i18n static %}
+
+{% block page_title %}
+    {% trans "Voucher for "%} {{ voucher.event }}
+{% endblock %}
+
+{% block browser_title %}{% trans "Voucher for "%} {{ voucher.event }}{% endblock %}
+
+{% block content %}
+
+    <div class="center-align">
+        <img src="{% firstof request.site.preferences.theme__logo.url aleksis_banner %}" alt="Logo" class="max-size-600 center">
+        <h3>{{ voucher.event }}<h3>
+        <h4>{{ voucher.code }}</h4>
+        <p>{% trans "Voucher for" %} {{ voucher.person.first_name }} {{ voucher.person.last_name }} {% trans "to visit event" %} {{ voucher.event }} {% trans "on" %} {{ voucher.event.date_event }} {% trans "at" %} {{ voucher.event.place }}!</p>
+        <p>{% trans "To use the voucher, register for the event " %} <a href="{% url 'register_event_by_id' voucher.event.id %}">{% trans "here" %}</a></p>
+    </div>
+
+{% endblock %}
diff --git a/aleksis/apps/paweljong/templates/paweljong/register.html b/aleksis/apps/paweljong/templates/paweljong/register.html
new file mode 100644
index 0000000..dc9d7ff
--- /dev/null
+++ b/aleksis/apps/paweljong/templates/paweljong/register.html
@@ -0,0 +1,17 @@
+{% extends "core/base.html" %}
+{% load material_form i18n %}
+
+{% block page_title %}{% blocktrans %}Registration{% endblocktrans %}{% endblock %}
+{% block browser_title %}{% blocktrans %}Registration{% endblocktrans %}{% endblock %}
+
+{% block content %}
+ <h5>
+  {% blocktrans %}Registration{% endblocktrans %} {{ event.display_name }}
+ </h5>
+
+ <form method="post">
+   {% csrf_token %}
+   {% form form=register_form %}{% endform %}
+   {% include "core/partials/save_button.html" %}
+ </form>
+{% endblock %}
diff --git a/aleksis/apps/paweljong/templates/paweljong/voucher/edit.html b/aleksis/apps/paweljong/templates/paweljong/voucher/edit.html
new file mode 100644
index 0000000..7b9e0ec
--- /dev/null
+++ b/aleksis/apps/paweljong/templates/paweljong/voucher/edit.html
@@ -0,0 +1,13 @@
+{% extends "core/base.html" %}
+{% load material_form i18n %}
+
+{% block page_title %}{% blocktrans %}Edit voucher{% endblocktrans %}{% endblock %}
+{% block browser_title %}{% blocktrans %}Edit voucher{% endblocktrans %}{% endblock %}
+
+{% block content %}
+  <form method="post">
+    {% csrf_token %}
+    {% form form=edit_voucher_form %}{% endform %}
+    {% include "core/partials/save_button.html" %}
+  </form>
+{% endblock %}
diff --git a/aleksis/apps/paweljong/templates/paweljong/voucher/list.html b/aleksis/apps/paweljong/templates/paweljong/voucher/list.html
new file mode 100644
index 0000000..18d789b
--- /dev/null
+++ b/aleksis/apps/paweljong/templates/paweljong/voucher/list.html
@@ -0,0 +1,25 @@
+{% extends "core/base.html" %}
+{% load material_form i18n %}
+
+{% load render_table from django_tables2 %}
+
+{% block page_title %}{% blocktrans %}Vouchers{% endblocktrans %}{% endblock %}
+{% block browser_title %}{% blocktrans %}Vouchers{% endblocktrans %}{% endblock %}
+
+{% block content %}
+
+  <h5>{% trans "Filter vouchers" %}</h5>
+  <form method="get">
+    {% form form=vouchers_filter.form %}{% endform %}
+    {% trans "Search" as caption %}
+    {% include "core/partials/save_button.html" with caption=caption icon="search" %}
+    <button type="reset" class="btn red waves-effect waves-light">
+      <i class="material-icons left">clear</i>
+      {% trans "Clear" %}
+    </button>
+  </form>
+
+  <h5>{% blocktrans %}Selected vouchers{% endblocktrans %}</h5>
+    {% render_table vouchers_table %}
+
+{% endblock %}
diff --git a/aleksis/apps/paweljong/templates/templated_email/event_created.email b/aleksis/apps/paweljong/templates/templated_email/event_created.email
new file mode 100644
index 0000000..d82ac83
--- /dev/null
+++ b/aleksis/apps/paweljong/templates/templated_email/event_created.email
@@ -0,0 +1,74 @@
+{% load i18n %}
+
+{% block subject %} {% trans "New event" %} {{ new_event }} {% endblock %}
+
+{% block plain %}
+    {% blocktrans with new_event=new_event %} An event was created: {{ new_event }}{% endblocktrans %}
+
+    * {% trans "Group" %}: {{ new_event.group }}
+    * {% trans "Description" %}: {{ new_event.description }}
+    * {% trans "Published" %}: {{ new_event.published }}
+    * {% trans "Place" %}: {{ new_event.place }}
+    * {% trans "Date of event" %}: {{ new_event.date_event }}
+    * {% trans "Registration deadline" %}: {{ new_event.date_registration }}
+    * {% trans "Retraction deadline" %}: {{ new_event.date_retraction }}
+    * {% trans "Cost" %}: {{ new_event.cost }}
+    * {% trans "Max. participants" %}: {{ new_event.max_participants }}
+    * {% trans "Owners" %}:
+    {% for owner in new_event.group.owners.all %}
+        * {{ owner }}
+    {% endfor %}
+    * {% trans "Feedback aspects" %}:
+    {% for aspect in new_event.feedback_aspects.all %}
+         * {{ aspect }}
+    {% endfor %}
+
+    {% blocktrans with sender=person %}
+        The event was created by {{ sender }}
+    {% endblocktrans %}
+
+    {% trans "Your AlekSIS team" %}
+{% endblock %}
+
+{% block html %}
+<main>
+    <p>{% blocktrans with new_event=new_event %} An event was created: {{ new_event }}{% endblocktrans %}</p>
+
+    <blockquote>
+        <h5>{{ new_event }}</h5>
+        <ul>
+            <li> {% trans "Group" %}: {{ new_event.group }}</li>
+            <li> {% trans "Description" %}: {{ new_event.description }}</li>
+            <li> {% trans "Published" %}: {{ new_event.published }}</li>
+            <li> {% trans "Place" %}: {{ new_event.place }}</li>
+            <li> {% trans "Date of event" %}: {{ new_event.date_event }}</li>
+            <li> {% trans "Registration deadline" %}: {{ new_event.date_registration }}</li>
+            <li> {% trans "Retraction deadline" %}: {{ new_event.date_retraction }}</li>
+            <li> {% trans "Cost" %}: {{ new_event.cost }}</li>
+            <li> {% trans "Max. participants" %}: {{ new_event.max_participants }}</li>
+            <li> {% trans "Owners" %}</li>
+            <ul>
+            {% for owner in new_event.group.owners.all %}
+                <li> {{ owner }}</li>
+            {% endfor %}
+	    </ul>
+            <li> {% trans "Feedback aspects" %}</li>
+            <ul>
+            {% for aspect in new_event.feedback_aspects.all %}
+                 <li> {{ aspect }}</li>
+            {% endfor %}
+            </ul>
+	</ul>
+    </blockquote>
+
+    <p>
+    {% blocktrans with sender=person %}
+        The event was created by {{ sender }}
+    {% endblocktrans %}
+    </p>
+
+    <p>
+        <i>{% trans "Your AlekSIS team" %}</i>
+    </p>
+</main>
+{% endblock %}
diff --git a/aleksis/apps/paweljong/templates/templated_email/event_feedback.html b/aleksis/apps/paweljong/templates/templated_email/event_feedback.html
new file mode 100644
index 0000000..9d0e555
--- /dev/null
+++ b/aleksis/apps/paweljong/templates/templated_email/event_feedback.html
@@ -0,0 +1,69 @@
+{% load i18n %}
+
+{% block subject %} {% trans "New feedback on" %} {{ feedback.event }} {% endblock %}
+
+{% block plain %}
+    {% blocktrans with feedback.event=feedback.event %}New feedback on: {{ feedback.event }}{% endblocktrans %}
+
+    *** {% trans "Event" %} *****
+
+    * {% trans "Event" %}: {{ feedback.event }}
+
+    **** {% trans "Person " %} ****
+
+    * {% trans "Name" %}: {{ feedback.person }}
+    * {% trans "Date of birth" %}: {{ feedback.person.date_of_birth }}
+    * {% trans "Email address" %}: {{ feedback.person.email }}
+
+    **** {% trans "Comments" %} ****
+
+    * {% trans "Private comment" %}: {{ feedback.comment_private }}
+    * {% trans "Public comment" %}: {{ feedback.comment_public }}
+    * {% trans "Public comment info" %}: {{ feedback.comment_public_info }}
+
+    **** {% trans 'Photos' %} ****
+    
+    {% for photo in feedback.photos %}
+        * {{ photo }}
+    {% endfor %}
+    * {% trans "Photo licence" %}: {{ feedback.photos_licence }}
+
+
+    {% trans "Your AlekSIS team" %}
+{% endblock %}
+
+{% block html %}
+<main>
+    <p>{% blocktrans with new_feedback.event=new_feedback.event %}New feedback.event: {{ feedback.event}}{% endblocktrans %}</p>
+
+    <blockquote>
+        <h5>{% trans "Event" %}</h5>
+            <ul>
+                <li>{% trans "Event" %}: {{ feedback.event.feedback.event }}</li>
+            </ul>
+        <h5>{% trans "Person" %</h5>
+        <ul>
+            <li>{% trans "Name" %}: {{ feedback.event.person }}</li>
+            <li>{% trans "Date of birth" %}: {{ feedback.event.date_of_birth }}</li>
+            <li>{% trans "Email address" %}: {{ feedback.event.person.email }}</li>
+        </ul>
+        <h5>{% trans "Comments" %}</h5>
+        <ul>
+            <li>{% trans "Private comment" %}: {{ feedback.comment_private }}</li>
+            <li>{% trans "Public comment" %}: {{ feedback.comment_public }}</li>
+            <li>{% trans "Public comment info" %}: {{ feedback.comment_public_info }}</li>
+        </ul>
+        <h5>{% trans "Photos" %}</h5>
+        {% for photo in feedback.photos %}
+          <img src="{{ photo }}" alt="Feedback photo"></img>
+        {% endfor %}
+        <ul>
+          <li>{% trans "Photo licence" %}: {{ feedback.photos_licence }}</li>
+        </ul>
+    </blockquote>
+
+    <p>
+        <i>{% trans "Your AlekSIS team" %}</i>
+    </p>
+</main>
+{% endblock %}
diff --git a/aleksis/apps/paweljong/templates/templated_email/event_registred.email b/aleksis/apps/paweljong/templates/templated_email/event_registred.email
new file mode 100644
index 0000000..da83ae3
--- /dev/null
+++ b/aleksis/apps/paweljong/templates/templated_email/event_registred.email
@@ -0,0 +1,92 @@
+{% load i18n %}
+
+{% block subject %} {% trans "New registration" %} {{ registration }} {% endblock %}
+
+{% block plain %}
+    {% blocktrans with new_event=new_event %}New registration: {{ event }}{% endblocktrans %}
+
+    *** {% trans "Event" %} *****
+
+    * {% trans "Event" %}: {{ registration.event }}
+
+    **** {% trans "Person " %} ****
+
+    * {% trans "Name" %}: {{ registration.person }}
+    * {% trans "Date of birth" %}: {{ registration.date_of_birth }}
+    * {% trans "Email address" %}: {{ registration.person.email }}
+    * {% trans "Mobile number" %}: {{ registration.person.mobile_number }}
+    * {% trans "Street" %}: {{ registration.person.street }}
+    * {% trans "Housenumber" %}: {{ registration.person.housenumber }}
+    * {% trans "Postal code" %}: {{ registration.person.postal_code }}
+    * {% trans "Place" %}: {{ registration.person.place }}
+
+    **** {% trans "Guardian" %} ****
+
+    * {% trans "Name" %}: {{ registration.person.guardians.first }}
+    * {% trans "Email address" %}: {{ registration.person.guardians.first.email }}
+    * {% trans "Mobile number" %}: {{ registration.person.guardians.first.mobile_number }}
+
+    **** {% trans "Financial details" %}
+
+    * {% trans "Donation" %}: {{ registration.donation }}
+    {% if registration.iban %}
+        * {% trans "Accept SEPA" %}: {{ registration.accept_sepa }}
+        * {% trans "IBAN" %}: {{ registration.iban }}
+    {% endif %}
+
+    **** {% trans "Declarations" %} ****
+
+    * {% trans "Consent by parents" %}: {{ registration.accept_terms }}
+    * {% trans "Data protection" %}: {{ registration.accept_data }}
+    * {% trans "Terms and conditions" %}: {{ registration.accept_general_terms }}
+
+    {% trans "Your AlekSIS team" %}
+{% endblock %}
+
+{% block html %}
+<main>
+    <p>{% blocktrans with new_event=new_event %}New registration: {{ registration}}{% endblocktrans %}</p>
+
+    <blockquote>
+        <h5>{% trans "Event" %}</h5>
+            <ul>
+                <li>{% trans "Event" %}: {{ registration.event }}</li>
+            </ul>
+        <h5>{% trans "Person " %</h5>
+        <ul>
+            <li>{% trans "Name" %}: {{ registration.person }}</li>
+            <li>{% trans "Date of birth" %}: {{ registration.date_of_birth }}</li>
+            <li>{% trans "Email address" %}: {{ registration.person.email }}</li>
+            <li>{% trans "Mobile number" %}: {{ registration.person.mobile_number }}</li>
+            <li>{% trans "Street" %}: {{ registration.person.street }}</li>
+            <li>{% trans "Housenumber" %}: {{ registration.person.housenumber }}</li>
+            <li>{% trans "Postal code" %}: {{ registration.person.postal_code }}</li>
+            <li>{% trans "Place" %}: {{ registration.person.place }}</li>
+        </ul>
+        <h5>{% trans "Guardian" %}</h5>
+        <ul>
+	    <li>{% trans "Name" %}: {{ registration.person.guardians.first }}</li>
+	    <li>{% trans "Email address" %}: {{ registration.person.guardians.first.email }}</li>
+	    <li>{% trans "Mobile number" %}: {{ registration.person.guardians.first.mobile_number }}</li>
+	</ul>
+	<h5>{% trans "Financial details" %}</h5>
+	<ul>
+	    <li>{% trans "Donation" %}: {{ registration.donation }}</li>
+	    {% if registration.iban %}
+	        <li>{% trans "Accept SEPA" %}: {{ registration.accept_sepa }}</li>
+	        <li>{% trans "IBAN" %}: {{ registration.iban }}</li>
+	    {% endif %}
+	</ul>
+	<h5>{% trans "Declarations" %}</h5>
+	<ul>
+	    <li>{% trans "Consent by parents" %}: {{ registration.accept_terms }}</li>
+	    <li>{% trans "Data protection" %}: {{ registration.accept_data }}</li>
+	    <li>{% trans "Terms and conditions" %}: {{ registration.accept_general_terms }}</li>
+	</ul>
+    </blockquote>
+
+    <p>
+        <i>{% trans "Your AlekSIS team" %}</i>
+    </p>
+</main>
+{% endblock %}
diff --git a/aleksis/apps/paweljong/urls.py b/aleksis/apps/paweljong/urls.py
index 3545de1..bad236b 100644
--- a/aleksis/apps/paweljong/urls.py
+++ b/aleksis/apps/paweljong/urls.py
@@ -3,5 +3,39 @@ from django.urls import path
 from . import views
 
 urlpatterns = [
-    path("empty", views.empty, name="empty"),
+    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"),
+    path("events/create", views.edit_event, name="create_event"),
+    path("events/manage", views.ManageEvents.as_view(), 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_>/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("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/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/paweljong/util.py b/aleksis/apps/paweljong/util.py
new file mode 100644
index 0000000..ade0bd4
--- /dev/null
+++ b/aleksis/apps/paweljong/util.py
@@ -0,0 +1,86 @@
+import os
+import random
+import string
+from tempfile import mkstemp
+from textwrap import wrap
+
+from django import forms
+from django.conf import settings
+
+import requests
+
+
+def subscribe_mailinglist(listname, mail):
+    form_data = {
+        "email": mail,
+        "list": listname,
+        "action": "subrequest",
+        "via_subrequest": 1,
+    }
+    return requests.post(get_site_preferences()["paweljong__wws_post_url"], data=form_data)
+
+
+def form_to_text_table(form, width=74, sep=" | "):
+    output_list = []
+
+    for field_name, field in form.fields.items():
+        # Determine field value depending on field type
+        if isinstance(field, forms.ModelMultipleChoiceField):
+            value = "\n".join([choice.__str__() for choice in form.cleaned_data[field_name]])
+        elif isinstance(field, forms.ModelChoiceField):
+            value = dict(field.choices)[form.cleaned_data[field_name]]
+        else:
+            value = form.cleaned_data[field_name]
+        value = str(value)
+
+        # Store in output list
+        output_list.append((field.label, value))
+
+    # Determine maximum field widths
+    max_label = max([len(_[0]) for _ in output_list])
+    max_value = width - len(sep) - max_label
+
+    # Generate result text
+    res = []
+    for label, value in output_list:
+        # Wrap value to lines
+        lines = wrap(value, max_value)
+
+        if lines:
+            # Output first line with label name
+            res.append("%s%s%s" % (label.rjust(max_label), sep, lines.pop(0)))
+
+            # Output following lines without label, if any
+            for line in lines:
+                res.append("%s%s%s" % (" " * max_label, sep, line))
+        else:
+            # Output empty label row
+            res.append("%s%s" % (label.rjust(max_label), sep))
+
+    # Build text block and return
+    return "\n".join(res)
+
+
+def upload_file_to_media_url(file, subdir="", prefix="upload_"):
+    fileext = os.path.splitext(file.name)[-1]
+
+    dest_abs = os.path.join(settings.MEDIA_ROOT, subdir)
+    dest_fd, dest_path = mkstemp(prefix=prefix, suffix=fileext, dir=dest_abs)
+
+    os.close(dest_fd)
+    with open(dest_path, "wb+") as dest_file:
+        for chunk in file.chunks():
+            dest_file.write(chunk)
+
+    basename = os.path.basename(dest_path)
+    url = "%s/%s/%s" % (settings.MEDIA_URL, subdir, basename)
+
+    return url
+
+
+def generate_code():
+    alphabet = string.ascii_uppercase + string.digits
+    length = 8
+    code = "".join(random.choice(alphabet) for _ in range(length))  # noqa
+
+    return code
diff --git a/aleksis/apps/paweljong/views.py b/aleksis/apps/paweljong/views.py
index 6cef4ad..515bef9 100644
--- a/aleksis/apps/paweljong/views.py
+++ b/aleksis/apps/paweljong/views.py
@@ -1,10 +1,575 @@
+from typing import Optional
+
 from django.contrib.auth.decorators import login_required
 from django.http import HttpRequest, HttpResponse
-from django.shortcuts import render
+from django.shortcuts import redirect, render
+from django.utils import timezone
+from django.utils.translation import ugettext as _
+from django.views.generic.list import ListView
+
+import reversion
+from django_tables2 import RequestConfig
+from reversion.views import create_revision
+from rules.contrib.views import PermissionRequiredMixin, permission_required
+from templated_email import send_templated_mail
+
+from aleksis.core.models import Activity, Person
+from aleksis.core.util import messages
+from aleksis.core.util.core_helpers import lazy_preference, objectgetter_optional
+
+from .filters import EventRegistrationFilter, FeedbackAspectsFilter, VoucherFilter
+from .forms import (
+    EditEventForm,
+    EditEventRegistrationForm,
+    EditFeedbackAspectForm,
+    EditVoucherForm,
+    EventFeedbackForm,
+    GenerateListForm,
+    RegisterEventForm,
+)
+from .models import Event, EventRegistration, FeedbackAspect, Voucher
+from .tables import (
+    EventRegistrationsTable,
+    EventsTable,
+    FeedbackAspectsTable,
+    ParticipatedEventsTable,
+    VouchersTable,
+)
+from .util import generate_code
+
+
+def events(request):
+    context = {}
+
+    # Get all upcoming events
+    now = timezone.datetime.today()
+    events = Event.objects.filter(date_event__gte=now, published=True)
+
+    # Build table
+    events_table = EventsTable(events)
+    RequestConfig(request).configure(events_table)
+    context["events_table"] = events_table
+
+    if request.user.is_authenticated:
+        # Get all events the person participated in.
+        current_person = Person.objects.get(user__username=request.user.username)
+        participated_events = Event.objects.filter(group__members=current_person)
 
+        # Build table
+        participated_events_table = ParticipatedEventsTable(participated_events)
+        RequestConfig(request).configure(participated_events_table)
+        context["participated_events_table"] = participated_events_table
 
+    return render(request, "paweljong/_event/list.html", context)
+
+
+@create_revision
 @login_required
-def empty(request: HttpRequest) -> HttpResponse:
+def register_event(request, id_):
+    context = {}
+
+    # Get current person and event
+    event = Event.objects.get(id=id_)
+    context["event"] = event
+
+    initial = {
+        "person": request.user.person,
+        "event": event,
+        "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 request.user.person.guardians.first():
+        initial.update(
+            {
+                "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,
+            }
+        )
+
+    register_form = RegisterEventForm(initial=initial)
+
+    # Produce error if registration is not possible
+    if not event.can_register(request):
+        messages.error(request, _("Registration is no longer possible"))
+        return redirect("events")
+
+    # Check whether person is already a member of the event
+    if request.user.person in event.group.members.all():
+        messages.success(request, _("You are already registred."))
+        return redirect("events")
+
+    if request.method == "POST":
+        register_form = RegisterEventForm(request.POST, initial=initial)
+        if register_form.is_valid():
+            # Update person information if changed
+            if (
+                "school" in register_form.changed_data
+                or "school_class" in register_form.changed_data
+                or "school_place" in register_form.changed_data
+                or "mobile" in register_form.changed_data
+                or "sex" in register_form.changed_data
+                or "date_of_birth" in register_form.changed_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"]
+                request.user.person.sex = register_form.cleaned_data["sex"]
+                request.user.person.date_of_birth = register_form.cleaned_data["date_of_birth"]
+
+                request.user.person.save()
+
+            # Store postal address in database
+            if (
+                "postal_code" in register_form.changed_data
+                or "place" in register_form.changed_data
+                or "street" in register_form.changed_data
+            ):
+
+                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
+                or "guardian_last_name" in register_form.changed_data
+                or "guardian_mobile_number" in register_form.changed_data
+                or "guardian_email" in register_form.changed_data
+            ):
+                guardian = Person.objects.get_or_create(
+                    defaults={
+                        "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"],
+                )
+
+                request.user.person.guardians.add(guardian[0])
+                request.user.person.save()
+
+            # Add the current person to the event
+            event.add_member(request.user.person)
+            event.save()
+
+            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"])
+                if voucher:
+                    registration.voucher = voucher
+                    with reversion.create_revision():
+                        registration.save()
+                else:
+                    messages.error(request, _("You entered an invalid voucher code!"))
+
+            send_templated_mail(
+                template_name="event_registred",
+                from_email=lazy_preference("mail", "address"),
+                recipient_list=["orga@.org"],
+                headers={
+                    "reply_to": [
+                        request.person.email,
+                        request.person.guardians.first().email,
+                    ],
+                },
+                context=context,
+            )
+
+            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"),
+                description=_("You registred for the event %s" % event.display_name),
+                app="TIC-Desk",
+                user=request.user.person,
+            )
+            act.save()
+
+    context["register_form"] = register_form
+
+    return render(request, "paweljong/_event/register.html", context)
+
+
+@login_required
+def feedback_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
+
+    # Prepare the form
+    initial = {
+        "event": event,
+        "person": current_person,
+    }
+
+    feedback_form = EventFeedbackForm(event, initial=initial)
+
+    # Check whether person is a member of the event
+    if current_person not in event.group.members.all():
+        return redirect("events")
+        messages.error(request, _("You did not take part in this event."))
+
+    if request.method == "POST":
+        if feedback_form.is_valid():
+            feedback = feedback_form.save(commit=True)
+            context["feedback"] = feedback
+            # Handle photo uploads, if any
+
+            send_templated_mail(
+                template_name="event_feedback",
+                from_email=lazy_preference("mail", "address"),
+                recipient_list=["verein@.org"],
+                headers={
+                    "reply_to": [
+                        request.person.email,
+                    ],
+                },
+                context=context,
+            )
+
+            # Set success
+            messages.success(request, _("Feedback successfully submitted."))
+            return redirect("events")
+
+            act = Activity(
+                title=_("You submitted feedback"),
+                description=_("You submitted feedback for %s" % event.display_name),
+                app="TIC-Desk",
+                user=request.user.person,
+            )
+            act.save()
+
+    context["feedback_form"] = feedback_form
+
+    return render(request, "paweljong/_event/feedback.html", context)
+
+
+@permission_required("paweljong.change_event", fn=objectgetter_optional(Event, None, False))
+def edit_event(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse:
+    """View to edit or create an event."""
     context = {}
 
-    return render(request, "paweljong/empty.html", context)
+    event = objectgetter_optional(Event, None, False)(request, id_)
+    context["event"] = event
+
+    if event:
+        # Edit form for existing event
+        edit_event_form = EditEventForm(request.POST or None, instance=event)
+    else:
+        # Empty form to create a new event
+        edit_event_form = EditEventForm(request.POST or None)
+
+    if request.method == "POST":
+        if edit_event_form.is_valid():
+            with reversion.create_revision():
+                new_event = edit_event_form.save()
+                context["new_event"] = new_event
+                context["person"] = request.user.person
+                send_templated_mail(
+                    template_name="event_created",
+                    from_email=lazy_preference("mail", "address"),
+                    recipient_list=["orga@.org"],
+                    context=context,
+                )
+            messages.success(request, _("The event has been saved."))
+
+            return redirect("events")
+
+    context["edit_event_form"] = edit_event_form
+
+    return render(request, "paweljong/_event/edit.html", context)
+
+
+class ManageEvents(ListView, PermissionRequiredMixin):
+    """View listing upcoming events."""
+
+    template_name = "paweljong/_event/manage.html"
+    permission_required = "paweljong.change_events"
+    context = {}
+
+    def get_queryset(self):
+        now = timezone.datetime.today()
+        return Event.objects.filter(date_event__gte=now)
+
+
+@permission_required("paweljong.view_vouchers")
+def vouchers(request):
+    context = {}
+
+    # Get all unused vouchers
+    vouchers = Voucher.objects.filter(used=False, deleted=False)
+
+    # Get filter
+    vouchers_filter = VoucherFilter(request.GET, queryset=vouchers)
+    context["vouchers_filter"] = vouchers_filter
+
+    # Build table
+    vouchers_table = VouchersTable(vouchers_filter.qs)
+    RequestConfig(request).configure(vouchers_table)
+    context["vouchers_table"] = vouchers_table
+
+    return render(request, "paweljong/voucher/list.html", context)
+
+
+@permission_required("paweljong.delete_voucher")
+def delete_voucher(request, id_):
+    context = {}
+
+    current_voucher = Voucher.objects.get(id=id_)
+    current_voucher.deleted = True
+    with reversion.create_revision():
+        current_voucher.save()
+
+    act = Activity(
+        title=_("You deleted a voucher!"),
+        description=_("You deleted the voucher with ID %s" % current_voucher.id),
+        app="TIC-Desk",
+        user=request.user.person,
+    )
+    act.save()
+
+    messages.success(request, _("The voucher was successfully deleted."))
+
+    return redirect("vouchers")
+
+
+@permission_required("paweljong.change_voucher", fn=objectgetter_optional(Voucher, None, False))
+def edit_voucher(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse:
+    """View to edit or create a voucher."""
+    context = {}
+
+    voucher = objectgetter_optional(Voucher, None, False)(request, id_)
+    context["voucher"] = voucher
+
+    if id_:
+        # Edit form for existing voucher
+        edit_voucher_form = EditVoucherForm(request.POST or None, instance=voucher)
+    else:
+        # Empty form to create a new voucher
+        edit_voucher_form = EditVoucherForm(request.POST or None)
+
+    if request.method == "POST":
+        if edit_voucher_form.is_valid():
+            voucher = edit_voucher_form.save(commit=True)
+            if not voucher.code:
+                voucher.code = generate_code()
+            with reversion.create_revision():
+                voucher.save()
+
+            act = Activity(
+                title=_("You have created a voucher."),
+                description=_(
+                    "You have created a voucher for %s for %s" % (voucher.person, voucher.event)
+                ),
+                app="TIC-Desk",
+                user=request.user.person,
+            )
+            act.save()
+
+            messages.success(request, _("The voucher has been saved."))
+
+            return redirect("vouchers")
+
+    context["edit_voucher_form"] = edit_voucher_form
+
+    return render(request, "paweljong/voucher/edit.html", context)
+
+
+@permission_required("paweljong.generate_lists")
+def generate_lists(request: HttpRequest) -> HttpResponse:
+    context = {}
+
+    generate_list_form = GenerateListForm()
+
+    if request.method == "POST":
+        generate_list_form = GenerateListForm(request.POST)
+        if generate_list_form.is_valid():
+            context["group"] = generate_list_form.cleaned_data["group"]
+            template = generate_list_form.cleaned_data["template"]
+            context["landscape"] = generate_list_form.cleaned_data["landscape"]
+
+            return render(request, "paweljong/print/%s.html" % (template), context)
+
+    context["generate_list_form"] = generate_list_form
+
+    return render(request, "paweljong/print/manage.html", context)
+
+
+@permission_required("paweljong.view_registrations")
+def registrations(request: HttpRequest) -> HttpResponse:
+    """List view listing all registrations."""
+    context = {}
+
+    # Get all registrations
+    registrations = EventRegistration.objects.all()
+
+    # Get filter
+    registrations_filter = EventRegistrationFilter(request.GET, queryset=registrations)
+    context["registrations_filter"] = registrations_filter
+
+    # Build table
+    registrations_table = EventRegistrationsTable(registrations_filter.qs)
+    RequestConfig(request).configure(registrations_table)
+    context["registrations_table"] = registrations_table
+
+    return render(request, "paweljong/event_registration/list.html", context)
+
+
+@permission_required(
+    "paweljong.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_
+    )
+
+    context["registration"] = registration
+
+    return render(request, "paweljong/event_registration/full.html", context)
+
+
+@permission_required(
+    "paweljong.delete_registrations",
+    fn=objectgetter_optional(EventRegistration, None, False),
+)
+def delete_registration(request: HttpRequest, id_) -> HttpResponse:
+    context = {}
+
+    registration = EventRegistration.objects.get(id=id_)
+
+    person = registration.person
+
+    registration.event.remove_member(registration.person)
+    registration.event.save()
+
+    registration.delete()
+
+    messages.success(request, _("Registration was successfully deleted."))
+
+    return redirect("registrations")
+
+
+@permission_required(
+    "paweljong.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
+    )
+
+    if request.method == "POST":
+        if edit_event_registration_form.is_valid():
+            with reversion.create_revision():
+                edit_event_registration_form.save(commit=True)
+
+            messages.success(request, _("The registration has been saved."))
+
+            return redirect("registration")
+
+    context["edit_event_registration_form"] = edit_event_registration_form
+
+    return render(request, "paweljong/event_registration/edit.html", context)
+
+
+@permission_required("paweljong.is_own_voucher", fn=objectgetter_optional(Voucher, None, False))
+def print_voucher(request: HttpRequest, id_) -> HttpResponse:
+    context = {}
+
+    voucher = Voucher.objects.get(id=id_)
+    context["voucher"] = voucher
+
+    return render(request, "paweljong/print/voucher.html", context)
+
+
+@permission_required("paweljong.view_feedback_aspects")
+def feedback_aspects(request: HttpRequest) -> HttpResponse:
+    """List view listing all feedback_aspects."""
+    context = {}
+
+    # Get all feedback_aspects
+    feedback_aspects = FeedbackAspect.objects.all()
+
+    # Get filter
+    feedback_aspects_filter = FeedbackAspectsFilter(request.GET, queryset=feedback_aspects)
+    context["feedback_aspects_filter"] = feedback_aspects_filter
+
+    # Build table
+    feedback_aspects_table = FeedbackAspectsTable(feedback_aspects_filter.qs)
+    RequestConfig(request).configure(feedback_aspects_table)
+    context["feedback_aspects_table"] = feedback_aspects_table
+
+    return render(request, "paweljong/feedback_aspect/list.html", context)
+
+
+@permission_required(
+    "paweljong.change_feedback_aspect",
+    fn=objectgetter_optional(FeedbackAspect, None, False),
+)
+def edit_feedback_aspect(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse:
+    """View to edit or create an feedback_aspect."""
+    context = {}
+
+    feedback_aspect = objectgetter_optional(FeedbackAspect, None, False)(request, id_)
+    context["feedback_aspect"] = feedback_aspect
+
+    if id_:
+        # Edit form for existing 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)
+
+    if request.method == "POST":
+        if edit_feedback_aspect_form.is_valid():
+            with reversion.create_revision():
+                aspect = edit_feedback_aspect_form.save(commit=True)
+
+                act = Activity(
+                    title=_("You have create a feedback aspect."),
+                    description=_("You have created the feedback aspect: %s" % aspect),
+                    app="TIC-Desk",
+                    user=request.user.person,
+                )
+                act.save()
+
+            messages.success(request, _("The feedback aspect has been saved."))
+
+            return redirect("feedback_aspects")
+
+    context["edit_feedback_aspect_form"] = edit_feedback_aspect_form
+
+    return render(request, "paweljong/feedback_aspect/edit.html", context)
-- 
GitLab