diff --git a/aleksis/core/rules.py b/aleksis/core/rules.py index 2a0889262e349d0479f4d55f03e8941b5f274bb7..4ebbaee0bc302de092b4f0676eb107254602a547 100644 --- a/aleksis/core/rules.py +++ b/aleksis/core/rules.py @@ -8,6 +8,7 @@ from .util.predicates import ( is_current_person, has_object_perm, is_group_owner, + is_notification_recipient, ) @@ -104,6 +105,10 @@ add_perm("core.manage_school", manage_school_predicate) manage_data_predicate = has_person & has_global_perm("core.manage_data") add_perm("core.manage_data", manage_data_predicate) +# Mark notification as read +mark_notification_as_read_predicate = has_person & is_notification_recipient +add_perm("core.mark_notification_as_read", mark_notification_as_read_predicate) + # View announcements view_announcements_predicate = has_person & ( has_global_perm("core.view_announcement") | has_any_object("core.view_announcement", Announcement) diff --git a/aleksis/core/util/core_helpers.py b/aleksis/core/util/core_helpers.py index 3c624f8537aa2b7d1ae2dc089c050f769e55ab08..9c2119a83c71645ced8e67124e8e601dfb31213e 100644 --- a/aleksis/core/util/core_helpers.py +++ b/aleksis/core/util/core_helpers.py @@ -4,7 +4,7 @@ from operator import itemgetter import os import pkgutil from importlib import import_module -from typing import Any, Callable, Sequence, Union, List +from typing import Any, Callable, Sequence, Union, List, Optional from uuid import uuid4 from django.conf import settings @@ -192,30 +192,13 @@ def now_tomorrow() -> datetime: return timezone.now() + timedelta(days=1) -def get_person_by_pk(request: HttpRequest, id_: Optional[int] = None): - """ Get a person by its ID, defaulting to person in request's user """ +def objectgetter_optional(model: Model, default: Optional[Any] = None, default_eval: bool = False) -> Callable[[HttpRequest, Optional[int]], Model]: + """ Get an object by pk, defaulting to None """ - from ..models import Person # noqa - - if id_: - return get_object_or_404(Person, pk=id_) - else: - return request.user.person - - -def get_group_by_pk(request: HttpRequest, id_: Optional[int] = None) -> Group: - """ Get a group by its ID, defaulting to None """ - - if id_: - return get_object_or_404(Group, id=id_) - - return None - - -def get_announcement_by_pk(request: HttpRequest, id_: Optional[int] = None): - """ Get an announcement by its ID; defaulting to None """ - - if id_: - return get_object_or_404(Announcement, pk=pk) + def get_object(request: HttpRequest, id_: Optional[int] = None) -> Model: + if id_ is not None: + return get_object_or_404(model, pk=id_) + else: + return eval(default) if default_eval else default - return None + return get_object diff --git a/aleksis/core/util/predicates.py b/aleksis/core/util/predicates.py index 396fdbf3a643e96c75394581a67aa98874a13f64..bdfa1c0cdede47436cb3b967be819cc2db0cbd4e 100644 --- a/aleksis/core/util/predicates.py +++ b/aleksis/core/util/predicates.py @@ -90,3 +90,9 @@ def is_group_owner(user: User, group: Group) -> bool: return group.owners.filter(owners=user.person).exists() + +@predicate +def is_notification_recipient(user: User, obj: Model) -> bool: + """ Predicate which checks whether the recipient of the notification a user wants to mark read is this user """ + + return user == obj.recipient.user diff --git a/aleksis/core/views.py b/aleksis/core/views.py index f005233c4808d58f747461d32288c923aaa342ba..2cdd5284e8bddf2b284f3f9aa516d8c1ee1c6004 100644 --- a/aleksis/core/views.py +++ b/aleksis/core/views.py @@ -33,7 +33,7 @@ from .registries import site_preferences_registry, group_preferences_registry, p from .tables import GroupsTable, PersonsTable from .util import messages from .util.apps import AppConfig -from .util.core_helpers import get_announcement_by_pk, get_group_by_pk, get_person_by_pk +from .util.core_helpers import objectgetter_optional @permission_required("core.view_dashboard") @@ -97,13 +97,13 @@ def persons(request: HttpRequest) -> HttpResponse: return render(request, "core/persons.html", context) -@permission_required("core.view_person", fn=get_person_by_pk) +@permission_required("core.view_person", fn=objectgetter_optional(Person, "request.user.person", True)) def person(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse: """ Detail view for one person; defaulting to logged-in person """ context = {} - person = get_person_by_pk(request, id_) + person = objectgetter_optional(Person, "request.user.person", True)(request, id_) context["person"] = person # Get groups where person is member of @@ -117,13 +117,13 @@ def person(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse: return render(request, "core/person_full.html", context) -@permission_required("core.view_group", fn=get_group_by_pk) +@permission_required("core.view_group", fn=objectgetter_optional(Group, None, False)) def group(request: HttpRequest, id_: int) -> HttpResponse: """ Detail view for one group """ context = {} - group = get_group_by_pk(request, id_) + group = objectgetter_optional(Group, None, False)(request, id_) context["group"] = group # Get group @@ -224,13 +224,13 @@ def groups_child_groups(request: HttpRequest) -> HttpResponse: return render(request, "core/groups_child_groups.html", context) -@permission_required("core.edit_person", fn=get_person_by_pk) +@permission_required("core.edit_person", fn=objectgetter_optional(Person, "request.user.person", True)) def edit_person(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse: """ Edit view for a single person, defaulting to logged-in person """ context = {} - person = get_person_by_pk(request, id_) + person = objectgetter_optional(Person, "request.user.person", True)(request, id_) context["person"] = person edit_person_form = EditPersonForm(request.POST or None, request.FILES or None, instance=person) @@ -255,13 +255,13 @@ def get_group_by_id(request: HttpRequest, id_: Optional[int] = None): return None -@permission_required("core.edit_group", fn=get_group_by_pk) +@permission_required("core.edit_group", fn=objectgetter_optional(Group, None, False)) def edit_group(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse: """ View to edit or create a group """ context = {} - group = get_group_by_pk(request, id_) + group = objectgetter_optional(Group, None, False)(request, id_) context["group"] = group if id_: @@ -301,18 +301,16 @@ def system_status(request: HttpRequest) -> HttpResponse: return render(request, "core/system_status.html", context) +@permission_required("core.mark_notification_as_read", fn=objectgetter_optional(Notification, None, False)) def notification_mark_read(request: HttpRequest, id_: int) -> HttpResponse: """ Mark a notification read """ context = {} - notification = get_object_or_404(Notification, pk=id_) + notification = objectgetter_optional(Notification, None, False)(request, id_) - if notification.recipient.user == request.user: - notification.read = True - notification.save() - else: - raise PermissionDenied(_("You are not allowed to mark notifications from other users as read!")) + notification.read = True + notification.save() # Redirect to dashboard as this is only used from there if JavaScript is unavailable return redirect("index") @@ -331,13 +329,13 @@ def announcements(request: HttpRequest) -> HttpResponse: return render(request, "core/announcement/list.html", context) -@permission_required("core.create_or_edit_announcement", fn=get_announcement_by_pk) -def announcement_form(request: HttpRequest, pk: Optional[int] = None) -> HttpResponse: +@permission_required("core.create_or_edit_announcement", fn=objectgetter_optional(Announcement, None, False)) +def announcement_form(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse: """ View to create or edit an announcement """ context = {} - announcement = get_announcement_by_pk(request, pk) + announcement = objectgetter_optional(Announcement, None, False)(request, id_) if announcement: # Edit form for existing announcement @@ -363,12 +361,12 @@ def announcement_form(request: HttpRequest, pk: Optional[int] = None) -> HttpRes return render(request, "core/announcement/form.html", context) -@permission_required("core.delete_announcement", fn=get_announcement_by_pk) -def delete_announcement(request: HttpRequest, pk: int) -> HttpResponse: +@permission_required("core.delete_announcement", fn=objectgetter_optional(Announcement, None, False)) +def delete_announcement(request: HttpRequest, id_: int) -> HttpResponse: """ View to delete an announcement """ if request.method == "POST": - announcement = get_announcement_by_pk(request, pk) + announcement = objectgetter_optional(Announcement, None, False)(request, id_) announcement.delete() messages.success(request, _("The announcement has been deleted."))