From 8c51597c29c4cb957e7a236f4283deebec583f2f Mon Sep 17 00:00:00 2001 From: moanos Date: Tue, 6 Aug 2024 19:28:03 +0200 Subject: [PATCH] feat: Add permission control to edit and add-to actions --- src/fellchensammlung/baker_recipes.py | 1 - ...reated_by_adoptionnotice_owner_and_more.py | 45 +++++++++++++++++++ src/fellchensammlung/models.py | 10 ++++- src/fellchensammlung/views.py | 42 +++++++++++++---- 4 files changed, 86 insertions(+), 12 deletions(-) create mode 100644 src/fellchensammlung/migrations/0004_rename_created_by_adoptionnotice_owner_and_more.py diff --git a/src/fellchensammlung/baker_recipes.py b/src/fellchensammlung/baker_recipes.py index a4e338b..fd3eea7 100644 --- a/src/fellchensammlung/baker_recipes.py +++ b/src/fellchensammlung/baker_recipes.py @@ -1,4 +1,3 @@ -from django.contrib.auth.models import User from model_bakery.recipe import Recipe, seq from fellchensammlung.models import * diff --git a/src/fellchensammlung/migrations/0004_rename_created_by_adoptionnotice_owner_and_more.py b/src/fellchensammlung/migrations/0004_rename_created_by_adoptionnotice_owner_and_more.py new file mode 100644 index 0000000..2deb183 --- /dev/null +++ b/src/fellchensammlung/migrations/0004_rename_created_by_adoptionnotice_owner_and_more.py @@ -0,0 +1,45 @@ +# Generated by Django 5.0.6 on 2024-08-06 17:23 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("fellchensammlung", "0003_subscriptions"), + ] + + operations = [ + migrations.RenameField( + model_name="adoptionnotice", + old_name="created_by", + new_name="owner", + ), + migrations.RenameField( + model_name="subscriptions", + old_name="user", + new_name="owner", + ), + migrations.AddField( + model_name="animal", + name="owner", + field=models.ForeignKey( + default=1, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + preserve_default=False, + ), + migrations.AddField( + model_name="image", + name="owner", + field=models.ForeignKey( + default=1, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + preserve_default=False, + ), + ] diff --git a/src/fellchensammlung/models.py b/src/fellchensammlung/models.py index 5bfc894..eedd361 100644 --- a/src/fellchensammlung/models.py +++ b/src/fellchensammlung/models.py @@ -74,10 +74,15 @@ class User(AbstractUser): def get_num_unread_notifications(self): return BaseNotification.objects.filter(user=self,read=False).count() + @property + def owner(self): + return self + class Image(models.Model): image = models.ImageField(upload_to='images') alt_text = models.TextField(max_length=2000) + owner = models.ForeignKey(User, on_delete=models.CASCADE) def __str__(self): return self.alt_text @@ -164,7 +169,7 @@ class AdoptionNotice(models.Model): photos = models.ManyToManyField(Image, blank=True) location_string = models.CharField(max_length=200, verbose_name=_("Ortsangabe")) location = models.ForeignKey(Location, blank=True, null=True, on_delete=models.SET_NULL, ) - created_by = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=_('Creator')) + owner = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=_('Creator')) @property def animals(self): @@ -328,6 +333,7 @@ class Animal(models.Model): photos = models.ManyToManyField(Image, blank=True) sex = models.CharField(max_length=20, choices=SEX_CHOICES, ) adoption_notice = models.ForeignKey(AdoptionNotice, on_delete=models.CASCADE) + owner = models.ForeignKey(User, on_delete=models.CASCADE) def __str__(self): return f"{self.name}" @@ -566,6 +572,6 @@ class CommentNotification(BaseNotification): class Subscriptions(models.Model): - user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=_('Nutzer*in')) + owner = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=_('Nutzer*in')) adoption_notice = models.ForeignKey(AdoptionNotice, on_delete=models.CASCADE, verbose_name=_('AdoptionNotice')) created_at = models.DateTimeField(auto_now_add=True) diff --git a/src/fellchensammlung/views.py b/src/fellchensammlung/views.py index b8c36b1..92f5f1c 100644 --- a/src/fellchensammlung/views.py +++ b/src/fellchensammlung/views.py @@ -22,8 +22,18 @@ from .tools.metrics import gather_metrics_data from django.contrib.auth.decorators import user_passes_test -def user_is_mod_or_above(user): - return user.is_authenticated and user.trust_level > User.TRUST_LEVEL[User.MODERATOR] +def user_is_trust_level_or_above(user, trust_level=User.MODERATOR): + return user.is_authenticated and user.trust_level >= User.TRUST_LEVEL[trust_level] + + +def user_is_owner_or_trust_level(user, django_object, trust_level=User.MODERATOR): + return user.is_authenticated and ( + user.trust_level == User.TRUST_LEVEL[trust_level] or django_object.owner == user) + + +def fail_if_user_not_owner_or_trust_level(user, django_object, trust_level=User.MODERATOR): + if not user_is_owner_or_trust_level(user, django_object, trust_level): + raise PermissionDenied def index(request): @@ -68,7 +78,8 @@ def adoption_notice_detail(request, adoption_notice_id): comment_instance.save() # Auto-subscribe user to adoption notice - subscription, created = Subscriptions.objects.get_or_create(adoption_notice=adoption_notice, user=request.user) + subscription, created = Subscriptions.objects.get_or_create(adoption_notice=adoption_notice, + user=request.user) subscription.save() # Notify users that a comment was added @@ -94,6 +105,7 @@ def adoption_notice_edit(request, adoption_notice_id): Form to update adoption notices """ adoption_notice = AdoptionNotice.objects.get(pk=adoption_notice_id) + fail_if_user_not_owner_or_trust_level(request.user, adoption_notice) if request.method == 'POST': form = AdoptionNoticeForm(request.POST, instance=adoption_notice) @@ -148,12 +160,14 @@ def add_adoption_notice(request): # Set correct status if request.user.trust_level >= User.TRUST_LEVEL[User.COORDINATOR]: status = AdoptionNoticeStatus.objects.create(major_status=AdoptionNoticeStatus.ACTIVE, - minor_status=AdoptionNoticeStatus.MINOR_STATUS_CHOICES[AdoptionNoticeStatus.ACTIVE]["searching"], + minor_status=AdoptionNoticeStatus.MINOR_STATUS_CHOICES[ + AdoptionNoticeStatus.ACTIVE]["searching"], adoption_notice=instance) status.save() else: status = AdoptionNoticeStatus.objects.create(major_status=AdoptionNoticeStatus.AWAITING_ACTION, - minor_status=AdoptionNoticeStatus.MINOR_STATUS_CHOICES[AdoptionNoticeStatus.AWAITING_ACTION][ + minor_status=AdoptionNoticeStatus.MINOR_STATUS_CHOICES[ + AdoptionNoticeStatus.AWAITING_ACTION][ "waiting_for_review"], adoption_notice=instance) status.save() @@ -166,6 +180,9 @@ def add_adoption_notice(request): @login_required def adoption_notice_add_animal(request, adoption_notice_id): + # Only users that are mods or owners of the adoption notice are allowed to add to it + adoption_notice = AdoptionNotice.objects.get(pk=adoption_notice_id) + fail_if_user_not_owner_or_trust_level(request.user, adoption_notice) if request.method == 'POST': form = AnimalFormWithDateWidget(request.POST, request.FILES) @@ -187,6 +204,8 @@ def adoption_notice_add_animal(request, adoption_notice_id): @login_required def add_photo_to_animal(request, animal_id): animal = Animal.objects.get(id=animal_id) + # Only users that are mods or owners of the animal are allowed to add to it + fail_if_user_not_owner_or_trust_level(request.user, animal) if request.method == 'POST': form = ImageForm(request.POST, request.FILES) @@ -206,6 +225,8 @@ def add_photo_to_animal(request, animal_id): @login_required def add_photo_to_adoption_notice(request, adoption_notice_id): adoption_notice = AdoptionNotice.objects.get(id=adoption_notice_id) + # Only users that are mods or owners of the adoption notice are allowed to add to it + fail_if_user_not_owner_or_trust_level(request.user, adoption_notice) if request.method == 'POST': form = ImageForm(request.POST, request.FILES) @@ -229,6 +250,8 @@ def animal_edit(request, animal_id): * Updating an Animal """ animal = Animal.objects.get(pk=animal_id) + # Only users that are mods or owners of the animal are allowed to edit it + fail_if_user_not_owner_or_trust_level(request.user, animal) if request.method == 'POST': form = AnimalForm(request.POST, instance=animal) @@ -323,11 +346,13 @@ def report_detail_success(request, report_id): @login_required def user_detail(request, user_id): + user = User.objects.get(id=user_id) + # Only users that are mods or owners of the user are allowed to view + fail_if_user_not_owner_or_trust_level(request.user, user) if request.method == "POST": action = request.POST.get("action") if action == "notification_mark_read": notification_id = request.POST.get("notification_id") - print(notification_id) notification = CommentNotification.objects.get(pk=notification_id) notification.read = True notification.save() @@ -337,14 +362,13 @@ def user_detail(request, user_id): notification.read = True notification.save() - user = User.objects.get(id=user_id) context = {"user": user, - "adoption_notices": AdoptionNotice.objects.filter(created_by=user), + "adoption_notices": AdoptionNotice.objects.filter(owner=user), "notifications": CommentNotification.objects.filter(user=user, read=False)} return render(request, 'fellchensammlung/details/detail-user.html', context=context) -@user_passes_test(user_is_mod_or_above) +@user_passes_test(user_is_trust_level_or_above) def modqueue(request): open_reports = Report.objects.filter(status=Report.WAITING) context = {"reports": open_reports}