diff --git a/aleksis/core/forms.py b/aleksis/core/forms.py index 1ca40c66cf414058c14964af03b32f2144c2a8b5..0daff5fd91fe2c45452025dd4d1e99bb7246953e 100644 --- a/aleksis/core/forms.py +++ b/aleksis/core/forms.py @@ -1,7 +1,9 @@ from datetime import time +from typing import Optional from django import forms from django.contrib.auth import get_user_model +from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError from django.utils import timezone from django.utils.translation import ugettext_lazy as _ @@ -9,7 +11,7 @@ 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, Announcement +from .models import Group, Person, School, SchoolTerm, Announcement, AnnouncementRecipient class PersonAccountForm(forms.ModelForm): @@ -149,26 +151,44 @@ class AnnouncementForm(forms.ModelForm): valid_until_date = forms.DateField(label=_("Date")) valid_until_time = forms.TimeField(label=_("Time")) + persons = forms.ModelMultipleChoiceField(Person.objects.all(), label=_("Persons"), required=False) + groups = forms.ModelMultipleChoiceField(Group.objects.all(), label=_("Groups"), required=False) + 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(_("Who should see the announcement?"), Row("groups", "persons")), 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 __init__(self, *args, **kwargs): + if "instance" not in kwargs: + kwargs["initial"] = { + "valid_from_date": timezone.datetime.now(), + "valid_from_time": time(0, 0), + "valid_until_date": timezone.datetime.now(), + "valid_until_time": time(23, 59), + } + else: + announcement = kwargs["instance"] + + # Fill special fields from given announcement instance + kwargs["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(), + "groups": announcement.get_recipients_for_model(Group), + "persons": announcement.get_recipients_for_model(Person), + } + super().__init__(*args, **kwargs) def clean(self): data = super().clean() + # Check date and time from_date = data["valid_from_date"] from_time = data["valid_from_time"] until_date = data["valid_until_date"] @@ -189,16 +209,31 @@ class AnnouncementForm(forms.ModelForm): data["valid_from"] = valid_from data["valid_until"] = valid_until + # Check recipients + if "groups" not in data and "persons" not in data: + raise ValidationError(_("You need at least one recipient.")) + + recipients = [] + recipients += data.get("groups", []) + recipients += data.get("persons", []) + + data["recipients"] = recipients + return data - def save(self, _ = False): + def save(self, _=False): + # Save announcement 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() + # Save recipients + a.recipients.all().delete() + for recipient in self.cleaned_data["recipients"]: + a.recipients.create(recipient=recipient) a.save() return a diff --git a/aleksis/core/models.py b/aleksis/core/models.py index 9f001cc714584c9c4694d44b5f53f72971f3f6b9..aef59f28a6d81af6a0a970bdc90eedfdfe05f501 100644 --- a/aleksis/core/models.py +++ b/aleksis/core/models.py @@ -330,6 +330,12 @@ class Announcement(ExtensibleModel): persons += recipient.persons return persons + def get_recipients_for_model(self, obj: Union[models.Model]) -> Sequence[models.Model]: + """ Get all recipients for this announcement with a special content type (provided through model) """ + + ct = ContentType.objects.get_for_model(obj) + return [r.recipient for r in self.recipients.filter(content_type=ct)] + def __str__(self): return self.title diff --git a/aleksis/core/templates/core/announcement/list.html b/aleksis/core/templates/core/announcement/list.html index e3fe5e4a0fc1ac6e1de1c7d023b72b11bd84cd7e..9f84617e255c78b7ee37e502c212e0704aa1da5e 100644 --- a/aleksis/core/templates/core/announcement/list.html +++ b/aleksis/core/templates/core/announcement/list.html @@ -30,16 +30,24 @@ <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 %}"> + <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> + <form action="{% url "delete_announcement" announcement.id %}" method="post"> + {% csrf_token %} + <button class="btn-flat waves-effect waves-re red-text" type="submit"> + <i class="material-icons left">delete</i> + {% trans "Delete" %} + </button> + </form> </td> </tr> {% empty %} <tr> - <td colspan="4"> - <p class="flow-text">{% trans "There are no announcements." %}</p> + <td colspan="5"> + <p class="flow-text center-align">{% trans "There are no announcements." %}</p> </td> </tr> {% endfor %} diff --git a/aleksis/core/urls.py b/aleksis/core/urls.py index 894ffcfc2d3bef8d8cd52d5d7c53400f12684c3a..e5320dd02f05a884a8891c7793305f8c0672d7fc 100644 --- a/aleksis/core/urls.py +++ b/aleksis/core/urls.py @@ -38,6 +38,7 @@ urlpatterns = [ 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("announcement/delete/<int:pk>/", views.delete_announcement, name="delete_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 4008d18960c15ac752b38fba33b79c4fdcf54c85..68773c76398542863bef6df3316ca3799a643e6a 100644 --- a/aleksis/core/views.py +++ b/aleksis/core/views.py @@ -293,17 +293,11 @@ def announcement_form(request: HttpRequest, pk: Optional[int] = None) -> HttpRes 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() - } + instance=announcement ) context["mode"] = "edit" else: - form = AnnouncementForm(request.POST or None, initial=AnnouncementForm.get_initial()) + form = AnnouncementForm(request.POST or None) context["mode"] = "add" if request.method == "POST": @@ -316,3 +310,13 @@ def announcement_form(request: HttpRequest, pk: Optional[int] = None) -> HttpRes context["form"] = form return render(request, "core/announcement/form.html", context) + + +@admin_required +def delete_announcement(request: HttpRequest, pk: int) -> HttpResponse: + if request.method == "POST": + announcement = get_object_or_404(Announcement, pk=pk) + announcement.delete() + messages.success(request, _("The announcement has been deleted.")) + + return redirect("announcements")