From aeeefc92f7f4fe212168ab149b9a8628f3f23eca Mon Sep 17 00:00:00 2001
From: Tom Teichler <tom.teichler@teckids.org>
Date: Sun, 6 Mar 2022 21:07:53 +0100
Subject: [PATCH] Implement registration states

---
 aleksis/apps/paweljong/forms.py               | 29 +++++++------
 aleksis/apps/paweljong/menus.py               | 11 +++++
 .../migrations/0015_registrationstate.py      | 33 +++++++++++++++
 .../0016_eventregistration_states.py          | 18 ++++++++
 aleksis/apps/paweljong/models.py              | 12 ++++++
 aleksis/apps/paweljong/tables.py              | 29 +++++++++++++
 .../paweljong/event_registration/edit.html    | 22 ++++++++++
 .../paweljong/registration_state/chip.html    | 19 +++++++++
 .../paweljong/registration_state/create.html  | 19 +++++++++
 .../paweljong/registration_state/edit.html    | 18 ++++++++
 .../paweljong/registration_state/list.html    | 14 +++++++
 aleksis/apps/paweljong/urls.py                | 15 +++++++
 aleksis/apps/paweljong/views.py               | 41 +++++++++++++++++--
 13 files changed, 262 insertions(+), 18 deletions(-)
 create mode 100644 aleksis/apps/paweljong/migrations/0015_registrationstate.py
 create mode 100644 aleksis/apps/paweljong/migrations/0016_eventregistration_states.py
 create mode 100644 aleksis/apps/paweljong/templates/paweljong/event_registration/edit.html
 create mode 100644 aleksis/apps/paweljong/templates/paweljong/registration_state/chip.html
 create mode 100644 aleksis/apps/paweljong/templates/paweljong/registration_state/create.html
 create mode 100644 aleksis/apps/paweljong/templates/paweljong/registration_state/edit.html
 create mode 100644 aleksis/apps/paweljong/templates/paweljong/registration_state/list.html

diff --git a/aleksis/apps/paweljong/forms.py b/aleksis/apps/paweljong/forms.py
index 8a51d71..d710f90 100644
--- a/aleksis/apps/paweljong/forms.py
+++ b/aleksis/apps/paweljong/forms.py
@@ -11,7 +11,7 @@ from phonenumber_field.formfields import PhoneNumberField
 from aleksis.core.mixins import ExtensibleForm
 from aleksis.core.models import Group, Person
 
-from .models import Event, EventRegistration, InfoMailing, Terms, Voucher
+from .models import Event, EventRegistration, InfoMailing, Terms, Voucher, RegistrationState
 
 COMMENT_CHOICES = [
     ("first", _("Only first name")),
@@ -404,20 +404,13 @@ class RegisterEventConsent(ExtensibleForm):
 class EditEventRegistrationForm(forms.ModelForm):
 
     layout = Layout(
-        Fieldset(
-            _("General event information"),
-            Row("event", "person"),
-            Row("comment"),
-        ),
-        Fieldset(
-            _("Financial data"),
-            "voucher_code",
-            Row("iban", "donation", "accept_sepa"),
-        ),
-        Fieldset(
-            _("Declaration of consent"),
-            Row("accept_terms", "accept_data", "accept_general_terms"),
-        ),
+        Row("event", "person"),
+        Row("comment", "medical_information"),
+        "voucher",
+        Row("iban", "donation", "accept_sepa"),
+        "accepted_terms",
+        Row("school", "school_class", "school_place"),
+        "states",
     )
 
     class Meta:
@@ -490,3 +483,9 @@ class RegistrationNotificationForm(forms.ModelForm):
     class Meta:
         model = InfoMailing
         exclude = ["sent_to", "active"]
+
+
+class RegistrationStatesForm(forms.ModelForm):
+    class Meta:
+        model = RegistrationState
+        exclude = []
diff --git a/aleksis/apps/paweljong/menus.py b/aleksis/apps/paweljong/menus.py
index afe3056..149cd54 100644
--- a/aleksis/apps/paweljong/menus.py
+++ b/aleksis/apps/paweljong/menus.py
@@ -44,6 +44,17 @@ MENUS = {
                         )
                     ],
                 },
+                {
+                    "name": _("Registration states"),
+                    "url": "registration_states",
+                    "icon": "list",
+                    "validators": [
+                        (
+                            "aleksis.core.util.predicates.permission_validator",
+                            "paweljong.view_registration_states_rule",
+                        )
+                    ],
+                },
                 {
                     "name": _("Info mailings"),
                     "url": "info_mailings",
diff --git a/aleksis/apps/paweljong/migrations/0015_registrationstate.py b/aleksis/apps/paweljong/migrations/0015_registrationstate.py
new file mode 100644
index 0000000..6375c85
--- /dev/null
+++ b/aleksis/apps/paweljong/migrations/0015_registrationstate.py
@@ -0,0 +1,33 @@
+# Generated by Django 3.2.12 on 2022-03-06 20:09
+
+import colorfield.fields
+import django.contrib.sites.managers
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('sites', '0002_alter_domain_unique'),
+        ('paweljong', '0014_move_sent_to_to_through'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='RegistrationState',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('extended_data', models.JSONField(default=dict, editable=False)),
+                ('name', models.CharField(max_length=255, verbose_name='Name of State')),
+                ('colour', colorfield.fields.ColorField(blank=True, default='', image_field=None, max_length=18, samples=None, verbose_name='Colour')),
+                ('site', models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.CASCADE, to='sites.site')),
+            ],
+            options={
+                'abstract': False,
+            },
+            managers=[
+                ('objects', django.contrib.sites.managers.CurrentSiteManager()),
+            ],
+        ),
+    ]
diff --git a/aleksis/apps/paweljong/migrations/0016_eventregistration_states.py b/aleksis/apps/paweljong/migrations/0016_eventregistration_states.py
new file mode 100644
index 0000000..28e9964
--- /dev/null
+++ b/aleksis/apps/paweljong/migrations/0016_eventregistration_states.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.2.12 on 2022-03-06 20:30
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('paweljong', '0015_registrationstate'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='eventregistration',
+            name='states',
+            field=models.ManyToManyField(related_name='registrations', to='paweljong.RegistrationState', verbose_name='States'),
+        ),
+    ]
diff --git a/aleksis/apps/paweljong/models.py b/aleksis/apps/paweljong/models.py
index 976b744..251dace 100644
--- a/aleksis/apps/paweljong/models.py
+++ b/aleksis/apps/paweljong/models.py
@@ -8,6 +8,7 @@ from django.utils.translation import gettext_lazy as _
 
 from ckeditor.fields import RichTextField
 from django_iban.fields import IBANField
+from colorfield.fields import ColorField
 
 from aleksis.core.mixins import ExtensibleModel
 from aleksis.core.models import Group, Person
@@ -15,6 +16,15 @@ from aleksis.core.util.core_helpers import generate_random_code, get_site_prefer
 from aleksis.core.util.email import send_email
 
 
+class RegistrationState(ExtensibleModel):
+
+    name = models.CharField(verbose_name=_("Name of State"), max_length=255)
+    colour = ColorField(blank=True, verbose_name=_("Colour"))
+
+    def __str__(self) -> str:
+        return self.name
+
+
 class Terms(ExtensibleModel):
     title = models.CharField(max_length=255, verbose_name=_("Title"))
     term = RichTextField(verbose_name=_("Term"))
@@ -261,6 +271,8 @@ class EventRegistration(ExtensibleModel):
         blank=True,
     )
 
+    states = models.ManyToManyField(RegistrationState, verbose_name=_("States"), related_name="registrations")
+
     def __str__(self) -> str:
         return f"{self.event}, {self.person.first_name} {self.person.last_name}"
 
diff --git a/aleksis/apps/paweljong/tables.py b/aleksis/apps/paweljong/tables.py
index 2e7ac69..8795e17 100644
--- a/aleksis/apps/paweljong/tables.py
+++ b/aleksis/apps/paweljong/tables.py
@@ -1,3 +1,4 @@
+from django.template.loader import render_to_string
 from django.utils.translation import ugettext_lazy as _
 
 import django_tables2 as tables
@@ -56,13 +57,23 @@ class EventRegistrationsTable(tables.Table):
     person = tables.Column()
     event = tables.Column()
     date_registered = tables.Column()
+    states = tables.Column()
     view = tables.LinkColumn(
         "registration_by_pk",
         args=[A("id")],
         verbose_name=_("View registration"),
         text=_("View"),
     )
+    edit = tables.LinkColumn(
+        "edit_registration_by_pk",
+        args=[A("pk")],
+        verbose_name=_("Edit"),
+        text=_("Edit"),
+    )
 
+    def render_states(self, value, record):
+        context = dict(states=value.all())
+        return render_to_string("paweljong/registration_state/chip.html", context)
 
 class TermsTable(tables.Table):
     class Meta:
@@ -96,3 +107,21 @@ class InfoMailingsTable(tables.Table):
         verbose_name=_("Delete"),
         text=_("Delete"),
     )
+
+
+class RegistrationStatesTable(tables.Table):
+    class Meta:
+        attrs = {"class": "responsive-table highlight"}
+
+    name = tables.Column()
+
+    edit = tables.LinkColumn(
+        "edit_registration_state_by_pk",
+        args=[A("id")],
+        verbose_name=_("Edit"),
+        text=_("Edit"),
+    )
+
+    def render_name(self, value, record):
+        context = dict(state=record)
+        return render_to_string("paweljong/registration_state/chip.html", context)
diff --git a/aleksis/apps/paweljong/templates/paweljong/event_registration/edit.html b/aleksis/apps/paweljong/templates/paweljong/event_registration/edit.html
new file mode 100644
index 0000000..4238fe2
--- /dev/null
+++ b/aleksis/apps/paweljong/templates/paweljong/event_registration/edit.html
@@ -0,0 +1,22 @@
+{% extends "core/base.html" %}
+{% load material_form i18n any_js %}
+
+{% block page_title %}{% blocktrans %}Edit registration{% endblocktrans %}{% endblock %}
+{% block browser_title %}{% blocktrans %}Edit registration{% endblocktrans %}{% endblock %}
+
+{% block extra_head %}
+    {{ form.media.css }}
+    {% include_css "select2-materialize" %}
+{% endblock %}
+
+{% block content %}
+
+  <form method="post">
+    {% csrf_token %}
+    {% form form=form %}{% form %}
+    {% include "core/partials/save_button.html" %}
+  </form>
+  {% include_js "select2-materialize" %}
+  {{ form.media.js }}
+
+{% endblock %}
diff --git a/aleksis/apps/paweljong/templates/paweljong/registration_state/chip.html b/aleksis/apps/paweljong/templates/paweljong/registration_state/chip.html
new file mode 100644
index 0000000..91aa0ef
--- /dev/null
+++ b/aleksis/apps/paweljong/templates/paweljong/registration_state/chip.html
@@ -0,0 +1,19 @@
+{# -*- engine:django -*- #}
+
+{% if states %}
+  {% for state in states %}
+    <div class="chip white-text" style="background-color: {{ state.colour|default:"black" }};">
+      {{ state }}
+      {% if small %}
+        <small>({{ small }})</small>
+      {% endif %}
+    </div>
+  {% endfor %}
+{% else %}
+  <div class="chip white-text" style="background-color: {{ state.colour|default:"black" }};">
+    {{ state.name }}
+    {% if small %}
+      <small>({{ small }})</small>
+    {% endif %}
+  </div>
+{% endif %}
diff --git a/aleksis/apps/paweljong/templates/paweljong/registration_state/create.html b/aleksis/apps/paweljong/templates/paweljong/registration_state/create.html
new file mode 100644
index 0000000..d8215a9
--- /dev/null
+++ b/aleksis/apps/paweljong/templates/paweljong/registration_state/create.html
@@ -0,0 +1,19 @@
+{% extends "core/base.html" %}
+{% load material_form i18n %}
+
+{% block page_title %}{% blocktrans %}Create registration state{% endblocktrans %}{% endblock %}
+{% block browser_title %}{% blocktrans %}Create registration state{% endblocktrans %}{% endblock %}
+
+{% block extra_head %}
+    {{ form.media.css }}
+{% endblock %}
+
+{% block content %}
+  <form method="post">
+    {% csrf_token %}
+    {% form form=form %}{% endform %}
+    {% include "core/partials/save_button.html" %}
+  </form>
+
+  {{ form.media.js }}
+{% endblock %}
diff --git a/aleksis/apps/paweljong/templates/paweljong/registration_state/edit.html b/aleksis/apps/paweljong/templates/paweljong/registration_state/edit.html
new file mode 100644
index 0000000..da8ec67
--- /dev/null
+++ b/aleksis/apps/paweljong/templates/paweljong/registration_state/edit.html
@@ -0,0 +1,18 @@
+{% extends "core/base.html" %}
+{% load material_form i18n %}
+
+{% block page_title %}{% blocktrans %}Edit registration state{% endblocktrans %}{% endblock %}
+{% block browser_title %}{% blocktrans %}Edit registration state{% endblocktrans %}{% endblock %}
+
+{% block extra_head %}
+    {{ form.media.css }}
+{% endblock %}
+
+{% block content %}
+  <form method="post">
+    {% csrf_token %}
+    {% form form=form %}{% endform %}
+    {% include "core/partials/save_button.html" %}
+  </form>
+  {{ form.media.js }}
+{% endblock %}
diff --git a/aleksis/apps/paweljong/templates/paweljong/registration_state/list.html b/aleksis/apps/paweljong/templates/paweljong/registration_state/list.html
new file mode 100644
index 0000000..4077190
--- /dev/null
+++ b/aleksis/apps/paweljong/templates/paweljong/registration_state/list.html
@@ -0,0 +1,14 @@
+{% extends "core/base.html" %}
+{% load material_form i18n %}
+
+{% load render_table from django_tables2 %}
+
+{% block page_title %}{% blocktrans %}Registration states{% endblocktrans %}{% endblock %}
+{% block browser_title %}{% blocktrans %}Registration states{% endblocktrans %}{% endblock %}
+
+{% block content %}
+
+    <a class="btn colour-primary waves-effect waves-light" href="{% url 'create_registration_state' %}">{% trans "Create registration state" %}</a>
+    {% render_table table %}
+
+{% endblock %}
diff --git a/aleksis/apps/paweljong/urls.py b/aleksis/apps/paweljong/urls.py
index d2a43fd..5568600 100644
--- a/aleksis/apps/paweljong/urls.py
+++ b/aleksis/apps/paweljong/urls.py
@@ -107,6 +107,21 @@ urlpatterns = [
         views.TermEditView.as_view(),
         name="edit_term_by_pk",
     ),
+    path(
+        "event/registrations/states/list",
+        views.RegistrationStateListView.as_view(),
+        name="registration_states",
+    ),
+    path(
+        "event/registrations/states/create",
+        views.RegistrationStateCreateView.as_view(),
+        name="create_registration_state",
+    ),
+    path(
+        "event/registrations/states/<int:pk>/edit",
+        views.RegistrationStateEditView.as_view(),
+        name="edit_registration_state_by_pk",
+    ),
     path(
         "info_mailings/list",
         views.InfoMailingListView.as_view(),
diff --git a/aleksis/apps/paweljong/views.py b/aleksis/apps/paweljong/views.py
index 9786d31..a5251d9 100644
--- a/aleksis/apps/paweljong/views.py
+++ b/aleksis/apps/paweljong/views.py
@@ -37,14 +37,16 @@ from .forms import (
     EditVoucherForm,
     GenerateListForm,
     RegistrationNotificationForm,
+    RegistrationStatesForm,
 )
-from .models import Event, EventRegistration, InfoMailing, Terms, Voucher
+from .models import Event, EventRegistration, InfoMailing, Terms, Voucher, RegistrationState
 from .tables import (
     EventRegistrationsTable,
     InfoMailingsTable,
     ManageEventsTable,
     TermsTable,
     VouchersTable,
+    RegistrationStatesTable,
 )
 
 User = get_user_model()
@@ -160,7 +162,7 @@ class EventRegistrationCreateView(PermissionRequiredMixin, AdvancedCreateView):
     form_class = EditEventRegistrationForm
     permission_required = "paweljong.manage_registration"
     template_name = "paweljong/event_registration/create.html"
-    success_url = reverse_lazy("event_registrations")
+    success_url = reverse_lazy("registrations")
     success_message = _("The event registration has been created.")
 
 
@@ -172,7 +174,7 @@ class EventRegistrationEditView(PermissionRequiredMixin, AdvancedEditView):
     form_class = EditEventRegistrationForm
     permission_required = "paweljong.manage_eventregistration"
     template_name = "paweljong/event_registration/edit.html"
-    success_url = reverse_lazy("event_registrations")
+    success_url = reverse_lazy("registrations")
     success_message = _("The event registration has been saved.")
 
 
@@ -860,3 +862,36 @@ class SendMailFromRegistration(PermissionRequiredMixin, FormView):
         )
 
         return super().form_valid(self)
+
+
+class RegistrationStateListView(PermissionRequiredMixin, SingleTableView):
+    """Table of all terms."""
+
+    model = RegistrationState
+    table_class = RegistrationStatesTable
+    permission_required = "paweljong.view_registration_states"
+    template_name = "paweljong/registration_state/list.html"
+
+
+@method_decorator(never_cache, name="dispatch")
+class RegistrationStateCreateView(PermissionRequiredMixin, AdvancedCreateView):
+    """Create view for terms."""
+
+    model = RegistrationState
+    form_class = RegistrationStatesForm
+    permission_required = "paweljong.add_registration_states"
+    template_name = "paweljong/registration_state/create.html"
+    success_url = reverse_lazy("registration_states")
+    success_message = _("The term has been created.")
+
+
+@method_decorator(never_cache, name="dispatch")
+class RegistrationStateEditView(PermissionRequiredMixin, AdvancedEditView):
+    """Edit view for terms."""
+
+    model = RegistrationState
+    form_class = RegistrationStatesForm
+    permission_required = "paweljong.edit_registration_states"
+    template_name = "paweljong/registration_state/edit.html"
+    success_url = reverse_lazy("registration_states")
+    success_message = _("The term has been saved.")
-- 
GitLab