From f63c1fe541b7e7e8d4a16b08dabe00b448729136 Mon Sep 17 00:00:00 2001
From: Jonathan Weth <git@jonathanweth.de>
Date: Thu, 20 Feb 2020 16:33:57 +0100
Subject: [PATCH] Include graphical announcement form

---
 aleksis/core/forms.py                         | 76 ++++++++++++++++++-
 aleksis/core/menus.py                         |  9 +++
 .../templates/core/announcement/form.html     | 33 ++++++++
 .../templates/core/announcement/list.html     | 48 ++++++++++++
 aleksis/core/urls.py                          |  3 +
 aleksis/core/views.py                         | 45 +++++++++++
 6 files changed, 213 insertions(+), 1 deletion(-)
 create mode 100644 aleksis/core/templates/core/announcement/form.html
 create mode 100644 aleksis/core/templates/core/announcement/list.html

diff --git a/aleksis/core/forms.py b/aleksis/core/forms.py
index cef045d86..1ca40c66c 100644
--- a/aleksis/core/forms.py
+++ b/aleksis/core/forms.py
@@ -1,10 +1,15 @@
+from datetime import time
+
 from django import forms
 from django.contrib.auth import get_user_model
+from django.core.exceptions import ValidationError
+from django.utils import timezone
 from django.utils.translation import ugettext_lazy as _
 
 from django_select2.forms import ModelSelect2MultipleWidget, Select2Widget
+from material import Layout, Fieldset, Row
 
-from .models import Group, Person, School, SchoolTerm
+from .models import Group, Person, School, SchoolTerm, Announcement
 
 
 class PersonAccountForm(forms.ModelForm):
@@ -132,3 +137,72 @@ class EditTermForm(forms.ModelForm):
     class Meta:
         model = SchoolTerm
         fields = ["caption", "date_start", "date_end"]
+
+
+class AnnouncementForm(forms.ModelForm):
+    valid_from = forms.DateTimeField(required=False)
+    valid_until = forms.DateTimeField(required=False)
+
+    valid_from_date = forms.DateField(label=_("Date"))
+    valid_from_time = forms.TimeField(label=_("Time"))
+
+    valid_until_date = forms.DateField(label=_("Date"))
+    valid_until_time = forms.TimeField(label=_("Time"))
+
+    layout = Layout(
+        Fieldset(
+            _("From when until when should the announcement be displayed?"),
+            Row("valid_from_date", "valid_from_time", "valid_until_date", "valid_until_time"),
+        ),
+        Fieldset(_("Write your announcement:"), "title", "description"),
+    )
+
+    @classmethod
+    def get_initial(cls):
+        return {
+            "valid_from_date": timezone.datetime.now(),
+            "valid_from_time": time(0,0),
+            "valid_until_date": timezone.datetime.now(),
+            "valid_until_time": time(23, 59)
+        }
+
+    def clean(self):
+        data = super().clean()
+
+        from_date = data["valid_from_date"]
+        from_time = data["valid_from_time"]
+        until_date = data["valid_until_date"]
+        until_time = data["valid_until_time"]
+
+        valid_from = timezone.datetime.combine(from_date, from_time)
+        valid_until = timezone.datetime.combine(until_date, until_time)
+
+        if valid_until < timezone.datetime.now():
+            raise ValidationError(
+                _("You are not allowed to create announcements which are only valid in the past.")
+            )
+        elif valid_from > valid_until:
+            raise ValidationError(
+                _("The from date and time must be earlier then the until date and time.")
+            )
+
+        data["valid_from"] = valid_from
+        data["valid_until"] = valid_until
+
+        return data
+
+    def save(self, _ = False):
+        a = self.instance if self.instance is not None else Announcement()
+
+        a.valid_from = self.cleaned_data["valid_from"]
+        a.valid_until = self.cleaned_data["valid_until"]
+        a.title = self.cleaned_data["title"]
+        a.description = self.cleaned_data["description"]
+
+        a.save()
+
+        return a
+
+    class Meta:
+        model = Announcement
+        exclude = []
diff --git a/aleksis/core/menus.py b/aleksis/core/menus.py
index 1c41dbb70..0fdada99f 100644
--- a/aleksis/core/menus.py
+++ b/aleksis/core/menus.py
@@ -57,6 +57,15 @@ MENUS = {
                 "menu_generator.validators.is_superuser",
             ],
             "submenu": [
+                {
+                    "name": _("Announcements"),
+                    "url": "announcements",
+                    "icon": "announcement",
+                    "validators": [
+                        "menu_generator.validators.is_authenticated",
+                        "menu_generator.validators.is_superuser",
+                    ],
+                },
                 {
                     "name": _("Data management"),
                     "url": "data_management",
diff --git a/aleksis/core/templates/core/announcement/form.html b/aleksis/core/templates/core/announcement/form.html
new file mode 100644
index 000000000..0cd31cdef
--- /dev/null
+++ b/aleksis/core/templates/core/announcement/form.html
@@ -0,0 +1,33 @@
+{# -*- engine:django -*- #}
+
+{% extends "core/base.html" %}
+
+{% load i18n material_form %}
+
+
+{% block browser_title %}
+  {% if mode == "edit" %}
+    {% blocktrans %}Edit announcement{% endblocktrans %}
+  {% else %}
+    {% blocktrans %}Publish announcement{% endblocktrans %}
+  {% endif %}
+{% endblock %}
+{% block page_title %}
+  {% if mode == "edit" %}
+    {% blocktrans %}Edit announcement{% endblocktrans %}
+  {% else %}
+    {% blocktrans %}Publish new announcement{% endblocktrans %}
+  {% endif %}
+{% endblock %}
+
+{% block content %}
+  <form action="" method="post">
+    {% csrf_token %}
+    {% form form=form %}{% endform %}
+
+    <button type="submit" class="btn green waves-effect waves-light">
+      <i class="material-icons left">save</i>
+      {% trans "Save und publish announcement" %}
+    </button>
+  </form>
+{% endblock %}
diff --git a/aleksis/core/templates/core/announcement/list.html b/aleksis/core/templates/core/announcement/list.html
new file mode 100644
index 000000000..e3fe5e4a0
--- /dev/null
+++ b/aleksis/core/templates/core/announcement/list.html
@@ -0,0 +1,48 @@
+{# -*- engine:django -*- #}
+
+{% extends "core/base.html" %}
+
+{% load i18n %}
+
+{% block browser_title %}{% blocktrans %}Announcements{% endblocktrans %}{% endblock %}
+{% block page_title %}{% blocktrans %}Announcements{% endblocktrans %}{% endblock %}
+
+{% block content %}
+  <a class="btn green waves-effect waves-light" href="{% url "add_announcement" %}">
+    <i class="material-icons left">add</i>
+    {% trans "Publish new announcement" %}
+  </a>
+  <table class="highlight">
+    <thead>
+    <tr>
+      <th>{% trans "Title" %}</th>
+      <th>{% trans "Valid from" %}</th>
+      <th>{% trans "Valid until" %}</th>
+      <th>{% trans "Recipients" %}</th>
+      <th>{% trans "Actions" %}</th>
+    </tr>
+    </thead>
+    <tbody>
+    {% for announcement in announcements %}
+      <tr>
+        <td>{{ announcement.title }}</td>
+        <td>{{ announcement.valid_from }}</td>
+        <td>{{ announcement.valid_until }}</td>
+        <td>{{ announcement.recipients.all|join:", " }}</td>
+        <td>
+          <a class="btn-flat waves-effect waves-orange orange-text" href="{% url "edit_announcement" announcement.id %}">
+            <i class="material-icons left">edit</i>
+            {% trans "Edit" %}
+          </a>
+        </td>
+      </tr>
+    {% empty %}
+      <tr>
+        <td colspan="4">
+          <p class="flow-text">{% trans "There are no announcements." %}</p>
+        </td>
+      </tr>
+    {% endfor %}
+    </tbody>
+  </table>
+{% endblock %}
diff --git a/aleksis/core/urls.py b/aleksis/core/urls.py
index 6f1d4275e..894ffcfc2 100644
--- a/aleksis/core/urls.py
+++ b/aleksis/core/urls.py
@@ -35,6 +35,9 @@ urlpatterns = [
     path("group/<int:id_>/edit", views.edit_group, name="edit_group_by_id"),
     path("", views.index, name="index"),
     path("notifications/mark-read/<int:id_>", views.notification_mark_read, name="notification_mark_read"),
+    path("announcements/", views.announcements, name="announcements"),
+    path("announcement/create/", views.announcement_form, name="add_announcement"),
+    path("announcement/edit/<int:pk>/", views.announcement_form, name="edit_announcement"),
     path("maintenance-mode/", include("maintenance_mode.urls")),
     path("impersonate/", include("impersonate.urls")),
     path("__i18n__/", include("django.conf.urls.i18n")),
diff --git a/aleksis/core/views.py b/aleksis/core/views.py
index 98db80da4..4008d1896 100644
--- a/aleksis/core/views.py
+++ b/aleksis/core/views.py
@@ -15,6 +15,7 @@ from .forms import (
     EditSchoolForm,
     EditTermForm,
     PersonsAccountsFormSet,
+    AnnouncementForm,
 )
 from .models import Activity, Group, Notification, Person, School, DashboardWidget, Announcement
 from .tables import GroupsTable, PersonsTable
@@ -271,3 +272,47 @@ def notification_mark_read(request: HttpRequest, id_: int) -> HttpResponse:
         raise PermissionDenied(_("You are not allowed to mark notifications from other users as read!"))
 
     return redirect("index")
+
+
+@admin_required
+def announcements(request: HttpRequest) -> HttpResponse:
+    context = {}
+
+    # Get all persons
+    announcements = Announcement.objects.all()
+    context["announcements"] = announcements
+
+    return render(request, "core/announcement/list.html", context)
+
+
+@admin_required
+def announcement_form(request: HttpRequest, pk: Optional[int] = None) -> HttpResponse:
+    context = {}
+
+    if pk:
+        announcement = get_object_or_404(Announcement, pk=pk)
+        form = AnnouncementForm(
+            request.POST or None,
+            instance=announcement,
+            initial={
+                "valid_from_date": announcement.valid_from.date(),
+                "valid_from_time": announcement.valid_from.time(),
+                "valid_until_date": announcement.valid_until.date(),
+                "valid_until_time": announcement.valid_until.time()
+            }
+        )
+        context["mode"] = "edit"
+    else:
+        form = AnnouncementForm(request.POST or None, initial=AnnouncementForm.get_initial())
+        context["mode"] = "add"
+
+    if request.method == "POST":
+        if form.is_valid():
+            form.save()
+
+            messages.success(request, _("The announcement has been saved."))
+            return redirect("announcements")
+
+    context["form"] = form
+
+    return render(request, "core/announcement/form.html", context)
-- 
GitLab