refactor sas moderation view permission

This commit is contained in:
imperosol
2026-02-21 18:33:55 +01:00
parent 52759764a1
commit 84ed180c1e
4 changed files with 239 additions and 252 deletions

View File

@@ -109,15 +109,14 @@ interface ViewerConfig {
/** id of the first picture to load on the page */ /** id of the first picture to load on the page */
firstPictureId: number; firstPictureId: number;
/** if the user is sas admin */ /** if the user is sas admin */
userIsSasAdmin: boolean; userCanModerate: boolean;
} }
/** /**
* Load user picture page with a nice download bar * Load user picture page with a nice download bar
**/ **/
exportToHtml("loadViewer", (config: ViewerConfig) => { document.addEventListener("alpine:init", () => {
document.addEventListener("alpine:init", () => { Alpine.data("picture_viewer", (config: ViewerConfig) => ({
Alpine.data("picture_viewer", () => ({
/** /**
* All the pictures that can be displayed on this picture viewer * All the pictures that can be displayed on this picture viewer
**/ **/
@@ -208,8 +207,7 @@ exportToHtml("loadViewer", (config: ViewerConfig) => {
} }
this.pushstate = History.Replace; this.pushstate = History.Replace;
this.currentPicture = this.pictures.find( this.currentPicture = this.pictures.find(
(i: PictureSchema) => (i: PictureSchema) => i.id === Number.parseInt(event.state.sasPictureId, 10),
i.id === Number.parseInt(event.state.sasPictureId, 10),
); );
}); });
this.pushstate = History.Replace; /* Avoid first url push */ this.pushstate = History.Replace; /* Avoid first url push */
@@ -231,11 +229,7 @@ exportToHtml("loadViewer", (config: ViewerConfig) => {
url: this.currentPicture.sas_url, url: this.currentPicture.sas_url,
}; };
if (this.pushstate === History.Replace) { if (this.pushstate === History.Replace) {
window.history.replaceState( window.history.replaceState(updateArgs.data, updateArgs.unused, updateArgs.url);
updateArgs.data,
updateArgs.unused,
updateArgs.url,
);
this.pushstate = History.Push; this.pushstate = History.Push;
} else { } else {
window.history.pushState(updateArgs.data, updateArgs.unused, updateArgs.url); window.history.pushState(updateArgs.data, updateArgs.unused, updateArgs.url);
@@ -251,7 +245,7 @@ exportToHtml("loadViewer", (config: ViewerConfig) => {
this.nextPicture?.preload(); this.nextPicture?.preload();
this.previousPicture?.preload(); this.previousPicture?.preload();
}); });
if (this.currentPicture.asked_for_removal && config.userIsSasAdmin) { if (this.currentPicture.asked_for_removal && config.userCanModerate) {
await Promise.all([ await Promise.all([
this.currentPicture.loadIdentifications(), this.currentPicture.loadIdentifications(),
this.currentPicture.loadModeration(), this.currentPicture.loadModeration(),
@@ -317,7 +311,7 @@ exportToHtml("loadViewer", (config: ViewerConfig) => {
* Check if an identification can be removed by the currently logged user * Check if an identification can be removed by the currently logged user
*/ */
canBeRemoved(identification: IdentifiedUserSchema): boolean { canBeRemoved(identification: IdentifiedUserSchema): boolean {
return config.userIsSasAdmin || identification.user.id === config.userId; return config.userCanModerate || identification.user.id === config.userId;
}, },
/** /**
@@ -336,5 +330,4 @@ exportToHtml("loadViewer", (config: ViewerConfig) => {
} }
}, },
})); }));
});
}); });

View File

@@ -17,10 +17,8 @@
{% from "sas/macros.jinja" import print_path %} {% from "sas/macros.jinja" import print_path %}
{% set user_is_sas_admin = user.is_root or user.is_in_group(pk = settings.SITH_GROUP_SAS_ADMIN_ID) %}
{% block content %} {% block content %}
<main x-data="picture_viewer"> <main x-data="picture_viewer(config)">
<code> <code>
<a href="{{ url('sas:main') }}">SAS</a> / {{ print_path(album) }} <span x-text="currentPicture.name"></span> <a href="{{ url('sas:main') }}">SAS</a> / {{ print_path(album) }} <span x-text="currentPicture.name"></span>
</code> </code>
@@ -50,15 +48,13 @@
It will be hidden to other users until it has been moderated. It will be hidden to other users until it has been moderated.
{% endtrans %} {% endtrans %}
</p> </p>
{% if user_is_sas_admin %} {% if user.has_perm("sas.moderate_sasfile") %}
<template x-if="currentPicture.asked_for_removal"> <template x-if="currentPicture.asked_for_removal">
<div> <div>
<h5>{% trans %}The following issues have been raised:{% endtrans %}</h5> <h5>{% trans %}The following issues have been raised:{% endtrans %}</h5>
<template x-for="req in (currentPicture.moderationRequests ?? [])" :key="req.id"> <template x-for="req in (currentPicture.moderationRequests ?? [])" :key="req.id">
<div> <div>
<h6 <h6 x-text="`${req.author.first_name} ${req.author.last_name}`"></h6>
x-text="`${req.author.first_name} ${req.author.last_name}`"
></h6>
<i x-text="Intl.DateTimeFormat( <i x-text="Intl.DateTimeFormat(
'{{ LANGUAGE_CODE }}', '{{ LANGUAGE_CODE }}',
{dateStyle: 'long', timeStyle: 'short'} {dateStyle: 'long', timeStyle: 'short'}
@@ -70,7 +66,7 @@
</template> </template>
{% endif %} {% endif %}
</div> </div>
{% if user_is_sas_admin %} {% if user.has_perm("sas.moderate_sasfile") %}
<div class="alert-aside"> <div class="alert-aside">
<button class="btn btn-blue" @click="moderatePicture()"> <button class="btn btn-blue" @click="moderatePicture()">
{% trans %}Moderate{% endtrans %} {% trans %}Moderate{% endtrans %}
@@ -204,16 +200,13 @@
{% endblock %} {% endblock %}
{% block script %} {% block script %}
{{ super() }}
<script> <script>
window.addEventListener("DOMContentLoaded", () => { const config = {
loadViewer({ albumId: {{ album.id }},
albumId: {{ album.id }} ,
albumUrl: "{{ album.get_absolute_url() }}", albumUrl: "{{ album.get_absolute_url() }}",
firstPictureId: {{ picture.id }}, {# id of the first picture to show after page load #} firstPictureId: {{ picture.id }}, {# id of the first picture to show after page load #}
userId: {{ user.id }}, userId: {{ user.id }},
userIsSasAdmin: {{ user_is_sas_admin|tojson }} userCanModerate: {{ user.has_perm("sas.moderate_sasfile")|tojson }}
}); }
})
</script> </script>
{% endblock %} {% endblock %}

View File

@@ -161,16 +161,22 @@ class TestSasModeration(TestCase):
assert len(res.context_data["pictures"]) == 1 assert len(res.context_data["pictures"]) == 1
assert res.context_data["pictures"][0] == self.to_moderate assert res.context_data["pictures"][0] == self.to_moderate
res = self.client.post(
reverse("sas:moderation"),
data={"album_id": self.to_moderate.id, "picture_id": self.to_moderate.id},
)
def test_moderation_page_forbidden(self): def test_moderation_page_forbidden(self):
self.client.force_login(self.simple_user) self.client.force_login(self.simple_user)
res = self.client.get(reverse("sas:moderation")) res = self.client.get(reverse("sas:moderation"))
assert res.status_code == 403 assert res.status_code == 403
def test_moderate_album(self):
self.client.force_login(self.moderator)
url = reverse("sas:moderation")
album = baker.make(
Album, is_moderated=False, parent_id=settings.SITH_SAS_ROOT_DIR_ID
)
res = self.client.post(url, data={"album_id": album.id, "moderate": ""})
assertRedirects(res, url)
album.refresh_from_db()
assert album.is_moderated
def test_moderate_picture(self): def test_moderate_picture(self):
self.client.force_login(self.moderator) self.client.force_login(self.moderator)
res = self.client.get( res = self.client.get(

View File

@@ -15,10 +15,10 @@
from typing import Any from typing import Any
from django.conf import settings from django.conf import settings
from django.core.exceptions import PermissionDenied from django.contrib.auth.mixins import PermissionRequiredMixin
from django.db.models import Count, OuterRef, Subquery from django.db.models import Count, OuterRef, Subquery
from django.http import Http404, HttpResponseRedirect from django.http import Http404, HttpResponseRedirect
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse from django.urls import reverse
from django.utils.safestring import SafeString from django.utils.safestring import SafeString
from django.views.generic import CreateView, DetailView, TemplateView from django.views.generic import CreateView, DetailView, TemplateView
@@ -191,18 +191,13 @@ class UserPicturesView(UserTabsMixin, CanViewMixin, DetailView):
# Admin views # Admin views
class ModerationView(TemplateView): class ModerationView(PermissionRequiredMixin, TemplateView):
template_name = "sas/moderation.jinja" template_name = "sas/moderation.jinja"
permission_required = "sas.moderate_sasfile"
def get(self, request, *args, **kwargs):
if request.user.is_in_group(pk=settings.SITH_GROUP_SAS_ADMIN_ID):
return super().get(request, *args, **kwargs)
raise PermissionDenied
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
if "album_id" not in request.POST: if "album_id" not in request.POST:
raise Http404 raise Http404
if request.user.is_in_group(pk=settings.SITH_GROUP_SAS_ADMIN_ID):
album = get_object_or_404(Album, pk=request.POST["album_id"]) album = get_object_or_404(Album, pk=request.POST["album_id"])
if "moderate" in request.POST: if "moderate" in request.POST:
album.moderator = request.user album.moderator = request.user
@@ -210,7 +205,7 @@ class ModerationView(TemplateView):
album.save() album.save()
elif "delete" in request.POST: elif "delete" in request.POST:
album.delete() album.delete()
return super().get(request, *args, **kwargs) return redirect(self.request.path)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs = super().get_context_data(**kwargs) kwargs = super().get_context_data(**kwargs)