diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 67055c96..df24f703 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -616,7 +616,7 @@ msgstr "No" #: counter/templates/counter/last_ops.jinja:20 #: counter/templates/counter/last_ops.jinja:45 #: counter/templates/counter/refilling_list.jinja:16 -#: rootplace/templates/rootplace/logs.jinja:12 sas/views.py:310 +#: rootplace/templates/rootplace/logs.jinja:12 sas/forms.py:90 #: trombi/templates/trombi/user_profile.jinja:40 msgid "Date" msgstr "Date" @@ -997,7 +997,7 @@ msgstr "Vous ne pouvez pas ajouter deux fois le même utilisateur" msgid "You should specify a role" msgstr "Vous devez choisir un rôle" -#: club/forms.py:283 sas/views.py:52 sas/views.py:176 +#: club/forms.py:283 sas/views.py:58 sas/views.py:177 msgid "You do not have the permission to do that" msgstr "Vous n'avez pas la permission de faire cela" @@ -1613,7 +1613,7 @@ msgstr "Résumé" #: com/templates/com/news_admin_list.jinja:252 #: com/templates/com/news_admin_list.jinja:289 #: com/templates/com/weekmail.jinja:17 com/templates/com/weekmail.jinja:46 -#: forum/templates/forum/forum.jinja:55 +#: forum/templates/forum/forum.jinja:55 sas/models.py:298 msgid "Author" msgstr "Auteur" @@ -2246,7 +2246,7 @@ msgstr "avoir une notification pour chaque click" msgid "get a notification for every refilling" msgstr "avoir une notification pour chaque rechargement" -#: core/models.py:914 sas/forms.py:86 +#: core/models.py:914 sas/forms.py:89 msgid "file name" msgstr "nom du fichier" @@ -2582,6 +2582,7 @@ msgstr "Confirmation" #: core/templates/core/delete_confirm.jinja:20 #: core/templates/core/file_delete_confirm.jinja:14 #: counter/templates/counter/counter_click.jinja:121 +#: sas/templates/sas/ask_picture_removal.jinja:20 msgid "Cancel" msgstr "Annuler" @@ -3496,12 +3497,12 @@ msgid "Error creating folder %(folder_name)s: %(msg)s" msgstr "Erreur de création du dossier %(folder_name)s : %(msg)s" #: core/views/files.py:153 core/views/forms.py:277 core/views/forms.py:284 -#: sas/forms.py:57 +#: sas/forms.py:60 #, python-format msgid "Error uploading file %(file_name)s: %(msg)s" msgstr "Erreur d'envoi du fichier %(file_name)s : %(msg)s" -#: core/views/files.py:228 sas/forms.py:90 +#: core/views/files.py:228 sas/forms.py:93 msgid "Apply rights recursively" msgstr "Appliquer les droits récursivement" @@ -3996,7 +3997,7 @@ msgstr "Ce n'est pas un UID de carte étudiante valide" #: counter/templates/counter/invoices_call.jinja:16 #: launderette/templates/launderette/launderette_admin.jinja:35 #: launderette/templates/launderette/launderette_click.jinja:13 -#: sas/templates/sas/picture.jinja:141 +#: sas/templates/sas/picture.jinja:143 #: subscription/templates/subscription/stats.jinja:19 msgid "Go" msgstr "Valider" @@ -5099,7 +5100,7 @@ msgstr "non noté" msgid "UV comment moderation" msgstr "Modération des commentaires d'UV" -#: pedagogy/templates/pedagogy/moderation.jinja:14 +#: pedagogy/templates/pedagogy/moderation.jinja:14 sas/models.py:309 msgid "Reason" msgstr "Raison" @@ -5267,27 +5268,47 @@ msgstr "Utilisateur qui sera supprimé" msgid "User to be selected" msgstr "Utilisateur à sélectionner" -#: sas/forms.py:13 +#: sas/forms.py:16 msgid "Add a new album" msgstr "Ajouter un nouvel album" -#: sas/forms.py:16 +#: sas/forms.py:19 msgid "Upload images" msgstr "Envoyer les images" -#: sas/forms.py:34 +#: sas/forms.py:37 #, python-format msgid "Error creating album %(album)s: %(msg)s" msgstr "Erreur de création de l'album %(album)s : %(msg)s" -#: sas/forms.py:69 trombi/templates/trombi/detail.jinja:15 +#: sas/forms.py:72 trombi/templates/trombi/detail.jinja:15 msgid "Add user" msgstr "Ajouter une personne" -#: sas/models.py:282 +#: sas/forms.py:117 +msgid "You already requested moderation for this picture." +msgstr "Vous avez déjà déposé une demande de retrait pour cette photo." + +#: sas/models.py:280 msgid "picture" msgstr "photo" +#: sas/models.py:304 +msgid "Picture" +msgstr "Photo" + +#: sas/models.py:311 +msgid "Why do you want this image to be removed ?" +msgstr "Pourquoi voulez-vous retirer cette image ?" + +#: sas/models.py:315 +msgid "Picture moderation request" +msgstr "Demande de modération de photo" + +#: sas/models.py:316 +msgid "Picture moderation requests" +msgstr "Demandes de modération de photo" + #: sas/templates/sas/album.jinja:9 #: sas/templates/sas/ask_picture_removal.jinja:4 sas/templates/sas/main.jinja:8 #: sas/templates/sas/main.jinja:17 sas/templates/sas/picture.jinja:12 @@ -5306,6 +5327,14 @@ msgstr "Envoyer" msgid "Template generation time: " msgstr "Temps de génération du template : " +#: sas/templates/sas/ask_picture_removal.jinja:9 +msgid "Image removal request" +msgstr "Demande de retrait d'image" + +#: sas/templates/sas/ask_picture_removal.jinja:25 +msgid "Request removal" +msgstr "Demander le retrait" + #: sas/templates/sas/main.jinja:20 msgid "You must be logged in to see the SAS." msgstr "Vous devez être connecté pour voir les photos." @@ -5339,7 +5368,7 @@ msgstr "" msgid "HD version" msgstr "Version HD" -#: sas/templates/sas/picture.jinja:98 +#: sas/templates/sas/picture.jinja:99 msgid "Ask for removal" msgstr "Demander le retrait" @@ -5347,7 +5376,7 @@ msgstr "Demander le retrait" msgid "Previous picture" msgstr "Image précédente" -#: sas/templates/sas/picture.jinja:137 +#: sas/templates/sas/picture.jinja:139 msgid "People" msgstr "Personne(s)" diff --git a/sas/admin.py b/sas/admin.py index f2845ad3..ac980341 100644 --- a/sas/admin.py +++ b/sas/admin.py @@ -15,7 +15,7 @@ from django.contrib import admin -from sas.models import Album, PeoplePictureRelation, Picture +from sas.models import Album, PeoplePictureRelation, Picture, PictureModerationRequest @admin.register(Picture) @@ -31,4 +31,15 @@ class PeoplePictureRelationAdmin(admin.ModelAdmin): autocomplete_fields = ("picture", "user") -admin.site.register(Album) +@admin.register(Album) +class AlbumAdmin(admin.ModelAdmin): + list_display = ("name", "parent", "date", "owner", "is_moderated") + search_fields = ("name",) + autocomplete_fields = ("owner", "parent", "edit_groups", "view_groups") + + +@admin.register(PictureModerationRequest) +class PictureModerationRequestAdmin(admin.ModelAdmin): + list_display = ("author", "picture", "created_at") + search_fields = ("author", "picture") + autocomplete_fields = ("author", "picture") diff --git a/sas/forms.py b/sas/forms.py index 4750dab9..6569e92a 100644 --- a/sas/forms.py +++ b/sas/forms.py @@ -1,11 +1,14 @@ +from typing import Any + from ajax_select import make_ajax_field from ajax_select.fields import AutoCompleteSelectMultipleField from django import forms from django.utils.translation import gettext_lazy as _ +from core.models import User from core.views import MultipleImageField from core.views.forms import SelectDate -from sas.models import Album, PeoplePictureRelation, Picture +from sas.models import Album, PeoplePictureRelation, Picture, PictureModerationRequest class SASForm(forms.Form): @@ -88,3 +91,34 @@ class AlbumEditForm(forms.ModelForm): parent = make_ajax_field(Album, "parent", "files", help_text="") edit_groups = make_ajax_field(Album, "edit_groups", "groups", help_text="") recursive = forms.BooleanField(label=_("Apply rights recursively"), required=False) + + +class PictureModerationRequestForm(forms.ModelForm): + """Form to create a PictureModerationRequest. + + The form only manages the reason field, + because the author and the picture are set in the view. + """ + + class Meta: + model = PictureModerationRequest + fields = ["reason"] + + def __init__(self, *args, user: User, picture: Picture, **kwargs): + super().__init__(*args, **kwargs) + self.user = user + self.picture = picture + + def clean(self) -> dict[str, Any]: + if PictureModerationRequest.objects.filter( + author=self.user, picture=self.picture + ).exists(): + raise forms.ValidationError( + _("You already requested moderation for this picture.") + ) + return super().clean() + + def save(self, *, commit=True) -> PictureModerationRequest: + self.instance.author = self.user + self.instance.picture = self.picture + return super().save(commit) diff --git a/sas/migrations/0004_picturemoderationrequest_and_more.py b/sas/migrations/0004_picturemoderationrequest_and_more.py new file mode 100644 index 00000000..e07b925d --- /dev/null +++ b/sas/migrations/0004_picturemoderationrequest_and_more.py @@ -0,0 +1,68 @@ +# Generated by Django 4.2.16 on 2024-10-10 20:44 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("sas", "0003_sasfile"), + ] + + operations = [ + migrations.CreateModel( + name="PictureModerationRequest", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ( + "reason", + models.TextField( + default="", + help_text="Why do you want this image to be removed ?", + verbose_name="Reason", + ), + ), + ], + options={ + "verbose_name": "Picture moderation request", + "verbose_name_plural": "Picture moderation requests", + }, + ), + migrations.AddField( + model_name="picturemoderationrequest", + name="author", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="moderation_requests", + to=settings.AUTH_USER_MODEL, + verbose_name="Author", + ), + ), + migrations.AddField( + model_name="picturemoderationrequest", + name="picture", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="moderation_requests", + to="sas.picture", + verbose_name="Picture", + ), + ), + migrations.AddConstraint( + model_name="picturemoderationrequest", + constraint=models.UniqueConstraint( + fields=("author", "picture"), name="one_request_per_user_per_picture" + ), + ), + ] diff --git a/sas/models.py b/sas/models.py index 43f26ccd..82bc87f2 100644 --- a/sas/models.py +++ b/sas/models.py @@ -273,16 +273,12 @@ class PeoplePictureRelation(models.Model): User, verbose_name=_("user"), related_name="pictures", - null=False, - blank=False, on_delete=models.CASCADE, ) picture = models.ForeignKey( Picture, verbose_name=_("picture"), related_name="people", - null=False, - blank=False, on_delete=models.CASCADE, ) @@ -290,4 +286,39 @@ class PeoplePictureRelation(models.Model): unique_together = ["user", "picture"] def __str__(self): - return self.user.get_display_name() + " - " + str(self.picture) + return f"Moderation request by {self.user.get_short_name()} - {self.picture}" + + +class PictureModerationRequest(models.Model): + """A request to remove a Picture from the SAS.""" + + created_at = models.DateTimeField(auto_now_add=True) + author = models.ForeignKey( + User, + verbose_name=_("Author"), + related_name="moderation_requests", + on_delete=models.CASCADE, + ) + picture = models.ForeignKey( + Picture, + verbose_name=_("Picture"), + related_name="moderation_requests", + on_delete=models.CASCADE, + ) + reason = models.TextField( + verbose_name=_("Reason"), + default="", + help_text=_("Why do you want this image to be removed ?"), + ) + + class Meta: + verbose_name = _("Picture moderation request") + verbose_name_plural = _("Picture moderation requests") + constraints = [ + models.UniqueConstraint( + fields=["author", "picture"], name="one_request_per_user_per_picture" + ) + ] + + def __str__(self): + return f"Moderation request by {self.author.get_short_name()}" diff --git a/sas/templates/sas/ask_picture_removal.jinja b/sas/templates/sas/ask_picture_removal.jinja new file mode 100644 index 00000000..26c345a0 --- /dev/null +++ b/sas/templates/sas/ask_picture_removal.jinja @@ -0,0 +1,28 @@ +{% extends "core/base.jinja" %} + +{% block title %} + {% trans %}SAS{% endtrans %} +{% endblock %} + + +{% block content %} +