prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Allow people to delete albums without pushnotifications permissions
  ### Describe the bug
  The paparazcie cannot delete albums because they don't (didn't, I temporarily gave them permissions) have delete permissions on message and scheduledmessage and facedetectionphoto.
  
  ### How to reproduce
  Steps to reproduce the behaviour:
  1. Have delete_album and delete_photo permission but no other delete permissions.
  2. Try to delete an album
  3. Get error screen telling you you need some more permissions.
  
  ### Expected behaviour
  Cascade deletes are allowed when deleting an album or photo regardless of permissions on the related items.
  
  ### Additional context
  The delete permissions are needed only for related models that have a ModelAdmin registered in the admin site. Models without an admin are ignored already by default.
  
  Here the missing permissions are gathered: https://github.com/django/django/blob/7cc138a58f73c17f07cfaf459ef8e7677ac41ac0/django/contrib/admin/utils.py#LL147C8-L149C52. 
  
  We can probably drop them in `ModelAdmin.get_deleted_objects`.
  
  With splitting up some models (e.g. BlacklistedThabloidUser, etc.) there may be more admins that need something like this.
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `website/photos/admin.py`
  Content:
  ```
  1 from django.contrib import admin, messages
  2 from django.db.models import Count
  3 from django.dispatch import Signal
  4 from django.utils.translation import gettext_lazy as _
  5 
  6 from django_filepond_widget.fields import FilePondFile
  7 
  8 from .forms import AlbumForm
  9 from .models import Album, Like, Photo
  10 from .services import extract_archive, save_photo
  11 
  12 album_uploaded = Signal()
  13 
  14 
  15 @admin.register(Album)
  16 class AlbumAdmin(admin.ModelAdmin):
  17     """Model for Album admin page."""
  18 
  19     list_display = ("title", "date", "num_photos", "hidden", "shareable")
  20     fields = (
  21         "title",
  22         "slug",
  23         "date",
  24         "event",
  25         "hidden",
  26         "shareable",
  27         "album_archive",
  28         "_cover",
  29     )
  30     search_fields = ("title", "date")
  31     list_filter = ("hidden", "shareable")
  32     date_hierarchy = "date"
  33     prepopulated_fields = {
  34         "slug": (
  35             "date",
  36             "title",
  37         )
  38     }
  39     form = AlbumForm
  40 
  41     def get_queryset(self, request):
  42         """Get Albums and add the amount of photos as an annotation."""
  43         return Album.objects.annotate(photos_count=Count("photo"))
  44 
  45     def num_photos(self, obj):
  46         """Pretty-print the number of photos."""
  47         return obj.photos_count
  48 
  49     num_photos.short_description = _("Number of photos")
  50     num_photos.admin_order_field = "photos_count"
  51 
  52     def save_model(self, request, obj, form, change):
  53         """Save the new Album by extracting the archive."""
  54         super().save_model(request, obj, form, change)
  55 
  56         archive = form.cleaned_data.get("album_archive", None)
  57         if archive is not None:
  58             try:
  59                 extract_archive(request, obj, archive)
  60                 album_uploaded.send(sender=None, album=obj)
  61             except Exception as e:
  62                 raise e
  63             finally:
  64                 if isinstance(archive, FilePondFile):
  65                     archive.remove()
  66 
  67             messages.add_message(
  68                 request,
  69                 messages.WARNING,
  70                 _("Full-sized photos will not be saved on the Thalia-website."),
  71             )
  72 
  73 
  74 class LikeInline(admin.StackedInline):
  75     model = Like
  76     extra = 0
  77 
  78 
  79 @admin.register(Photo)
  80 class PhotoAdmin(admin.ModelAdmin):
  81     """Model for Photo admin page."""
  82 
  83     list_display = (
  84         "__str__",
  85         "album",
  86         "hidden",
  87         "num_likes",
  88     )
  89     search_fields = ("file",)
  90     list_filter = ("album", "hidden")
  91     exclude = ("_digest",)
  92 
  93     inlines = [
  94         LikeInline,
  95     ]
  96 
  97     def save_model(self, request, obj, form, change):
  98         """Save new Photo."""
  99         super().save_model(request, obj, form, change)
  100         if change and obj.original_file == obj.file.name:
  101             return
  102 
  103         if save_photo(obj, obj.file, obj.file.name):
  104             messages.add_message(
  105                 request,
  106                 messages.WARNING,
  107                 _("Full-sized photos will not be saved on the Thalia-website."),
  108             )
  109         else:
  110             messages.add_message(
  111                 request, messages.ERROR, _("This photo already exists in the album.")
  112             )
  113
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/website/photos/admin.py b/website/photos/admin.py
  --- a/website/photos/admin.py
  +++ b/website/photos/admin.py
  @@ -70,6 +70,18 @@
                   _("Full-sized photos will not be saved on the Thalia-website."),
               )
   
  +    def get_deleted_objects(self, objs, request):
  +        (
  +            deleted_objects,
  +            model_count,
  +            perms_needed,
  +            protected,
  +        ) = super().get_deleted_objects(objs, request)
  +
  +        # Drop any missing delete permissions. If the user has `delete_album` permission,
  +        # they should automatically be allowed to cascade e.g. related pushnotifications.
  +        return deleted_objects, model_count, set(), protected
  +
   
   class LikeInline(admin.StackedInline):
       model = Like
  @@ -94,6 +106,16 @@
           LikeInline,
       ]
   
  +    def get_deleted_objects(self, objs, request):
  +        (
  +            deleted_objects,
  +            model_count,
  +            perms_needed,
  +            protected,
  +        ) = super().get_deleted_objects(objs, request)
  +
  +        return deleted_objects, model_count, set(), protected
  +
       def save_model(self, request, obj, form, change):
           """Save new Photo."""
           super().save_model(request, obj, form, change)
  
verification_info: {"golden_diff": "diff --git a/website/photos/admin.py b/website/photos/admin.py\n--- a/website/photos/admin.py\n+++ b/website/photos/admin.py\n@@ -70,6 +70,18 @@\n                 _(\"Full-sized photos will not be saved on the Thalia-website.\"),\n             )\n \n+    def get_deleted_objects(self, objs, request):\n+        (\n+            deleted_objects,\n+            model_count,\n+            perms_needed,\n+            protected,\n+        ) = super().get_deleted_objects(objs, request)\n+\n+        # Drop any missing delete permissions. If the user has `delete_album` permission,\n+        # they should automatically be allowed to cascade e.g. related pushnotifications.\n+        return deleted_objects, model_count, set(), protected\n+\n \n class LikeInline(admin.StackedInline):\n     model = Like\n@@ -94,6 +106,16 @@\n         LikeInline,\n     ]\n \n+    def get_deleted_objects(self, objs, request):\n+        (\n+            deleted_objects,\n+            model_count,\n+            perms_needed,\n+            protected,\n+        ) = super().get_deleted_objects(objs, request)\n+\n+        return deleted_objects, model_count, set(), protected\n+\n     def save_model(self, request, obj, form, change):\n         \"\"\"Save new Photo.\"\"\"\n         super().save_model(request, obj, form, change)\n", "issue": "Allow people to delete albums without pushnotifications permissions\n### Describe the bug\r\nThe paparazcie cannot delete albums because they don't (didn't, I temporarily gave them permissions) have delete permissions on message and scheduledmessage and facedetectionphoto.\r\n\r\n### How to reproduce\r\nSteps to reproduce the behaviour:\r\n1. Have delete_album and delete_photo permission but no other delete permissions.\r\n2. Try to delete an album\r\n3. Get error screen telling you you need some more permissions.\r\n\r\n### Expected behaviour\r\nCascade deletes are allowed when deleting an album or photo regardless of permissions on the related items.\r\n\r\n### Additional context\r\nThe delete permissions are needed only for related models that have a ModelAdmin registered in the admin site. Models without an admin are ignored already by default.\r\n\r\nHere the missing permissions are gathered: https://github.com/django/django/blob/7cc138a58f73c17f07cfaf459ef8e7677ac41ac0/django/contrib/admin/utils.py#LL147C8-L149C52. \r\n\r\nWe can probably drop them in `ModelAdmin.get_deleted_objects`.\r\n\r\nWith splitting up some models (e.g. BlacklistedThabloidUser, etc.) there may be more admins that need something like this.\n", "before_files": [{"content": "from django.contrib import admin, messages\nfrom django.db.models import Count\nfrom django.dispatch import Signal\nfrom django.utils.translation import gettext_lazy as _\n\nfrom django_filepond_widget.fields import FilePondFile\n\nfrom .forms import AlbumForm\nfrom .models import Album, Like, Photo\nfrom .services import extract_archive, save_photo\n\nalbum_uploaded = Signal()\n\n\n@admin.register(Album)\nclass AlbumAdmin(admin.ModelAdmin):\n    \"\"\"Model for Album admin page.\"\"\"\n\n    list_display = (\"title\", \"date\", \"num_photos\", \"hidden\", \"shareable\")\n    fields = (\n        \"title\",\n        \"slug\",\n        \"date\",\n        \"event\",\n        \"hidden\",\n        \"shareable\",\n        \"album_archive\",\n        \"_cover\",\n    )\n    search_fields = (\"title\", \"date\")\n    list_filter = (\"hidden\", \"shareable\")\n    date_hierarchy = \"date\"\n    prepopulated_fields = {\n        \"slug\": (\n            \"date\",\n            \"title\",\n        )\n    }\n    form = AlbumForm\n\n    def get_queryset(self, request):\n        \"\"\"Get Albums and add the amount of photos as an annotation.\"\"\"\n        return Album.objects.annotate(photos_count=Count(\"photo\"))\n\n    def num_photos(self, obj):\n        \"\"\"Pretty-print the number of photos.\"\"\"\n        return obj.photos_count\n\n    num_photos.short_description = _(\"Number of photos\")\n    num_photos.admin_order_field = \"photos_count\"\n\n    def save_model(self, request, obj, form, change):\n        \"\"\"Save the new Album by extracting the archive.\"\"\"\n        super().save_model(request, obj, form, change)\n\n        archive = form.cleaned_data.get(\"album_archive\", None)\n        if archive is not None:\n            try:\n                extract_archive(request, obj, archive)\n                album_uploaded.send(sender=None, album=obj)\n            except Exception as e:\n                raise e\n            finally:\n                if isinstance(archive, FilePondFile):\n                    archive.remove()\n\n            messages.add_message(\n                request,\n                messages.WARNING,\n                _(\"Full-sized photos will not be saved on the Thalia-website.\"),\n            )\n\n\nclass LikeInline(admin.StackedInline):\n    model = Like\n    extra = 0\n\n\n@admin.register(Photo)\nclass PhotoAdmin(admin.ModelAdmin):\n    \"\"\"Model for Photo admin page.\"\"\"\n\n    list_display = (\n        \"__str__\",\n        \"album\",\n        \"hidden\",\n        \"num_likes\",\n    )\n    search_fields = (\"file\",)\n    list_filter = (\"album\", \"hidden\")\n    exclude = (\"_digest\",)\n\n    inlines = [\n        LikeInline,\n    ]\n\n    def save_model(self, request, obj, form, change):\n        \"\"\"Save new Photo.\"\"\"\n        super().save_model(request, obj, form, change)\n        if change and obj.original_file == obj.file.name:\n            return\n\n        if save_photo(obj, obj.file, obj.file.name):\n            messages.add_message(\n                request,\n                messages.WARNING,\n                _(\"Full-sized photos will not be saved on the Thalia-website.\"),\n            )\n        else:\n            messages.add_message(\n                request, messages.ERROR, _(\"This photo already exists in the album.\")\n            )\n", "path": "website/photos/admin.py"}], "after_files": [{"content": "from django.contrib import admin, messages\nfrom django.db.models import Count\nfrom django.dispatch import Signal\nfrom django.utils.translation import gettext_lazy as _\n\nfrom django_filepond_widget.fields import FilePondFile\n\nfrom .forms import AlbumForm\nfrom .models import Album, Like, Photo\nfrom .services import extract_archive, save_photo\n\nalbum_uploaded = Signal()\n\n\n@admin.register(Album)\nclass AlbumAdmin(admin.ModelAdmin):\n    \"\"\"Model for Album admin page.\"\"\"\n\n    list_display = (\"title\", \"date\", \"num_photos\", \"hidden\", \"shareable\")\n    fields = (\n        \"title\",\n        \"slug\",\n        \"date\",\n        \"event\",\n        \"hidden\",\n        \"shareable\",\n        \"album_archive\",\n        \"_cover\",\n    )\n    search_fields = (\"title\", \"date\")\n    list_filter = (\"hidden\", \"shareable\")\n    date_hierarchy = \"date\"\n    prepopulated_fields = {\n        \"slug\": (\n            \"date\",\n            \"title\",\n        )\n    }\n    form = AlbumForm\n\n    def get_queryset(self, request):\n        \"\"\"Get Albums and add the amount of photos as an annotation.\"\"\"\n        return Album.objects.annotate(photos_count=Count(\"photo\"))\n\n    def num_photos(self, obj):\n        \"\"\"Pretty-print the number of photos.\"\"\"\n        return obj.photos_count\n\n    num_photos.short_description = _(\"Number of photos\")\n    num_photos.admin_order_field = \"photos_count\"\n\n    def save_model(self, request, obj, form, change):\n        \"\"\"Save the new Album by extracting the archive.\"\"\"\n        super().save_model(request, obj, form, change)\n\n        archive = form.cleaned_data.get(\"album_archive\", None)\n        if archive is not None:\n            try:\n                extract_archive(request, obj, archive)\n                album_uploaded.send(sender=None, album=obj)\n            except Exception as e:\n                raise e\n            finally:\n                if isinstance(archive, FilePondFile):\n                    archive.remove()\n\n            messages.add_message(\n                request,\n                messages.WARNING,\n                _(\"Full-sized photos will not be saved on the Thalia-website.\"),\n            )\n\n    def get_deleted_objects(self, objs, request):\n        (\n            deleted_objects,\n            model_count,\n            perms_needed,\n            protected,\n        ) = super().get_deleted_objects(objs, request)\n\n        # Drop any missing delete permissions. If the user has `delete_album` permission,\n        # they should automatically be allowed to cascade e.g. related pushnotifications.\n        return deleted_objects, model_count, set(), protected\n\n\nclass LikeInline(admin.StackedInline):\n    model = Like\n    extra = 0\n\n\n@admin.register(Photo)\nclass PhotoAdmin(admin.ModelAdmin):\n    \"\"\"Model for Photo admin page.\"\"\"\n\n    list_display = (\n        \"__str__\",\n        \"album\",\n        \"hidden\",\n        \"num_likes\",\n    )\n    search_fields = (\"file\",)\n    list_filter = (\"album\", \"hidden\")\n    exclude = (\"_digest\",)\n\n    inlines = [\n        LikeInline,\n    ]\n\n    def get_deleted_objects(self, objs, request):\n        (\n            deleted_objects,\n            model_count,\n            perms_needed,\n            protected,\n        ) = super().get_deleted_objects(objs, request)\n\n        return deleted_objects, model_count, set(), protected\n\n    def save_model(self, request, obj, form, change):\n        \"\"\"Save new Photo.\"\"\"\n        super().save_model(request, obj, form, change)\n        if change and obj.original_file == obj.file.name:\n            return\n\n        if save_photo(obj, obj.file, obj.file.name):\n            messages.add_message(\n                request,\n                messages.WARNING,\n                _(\"Full-sized photos will not be saved on the Thalia-website.\"),\n            )\n        else:\n            messages.add_message(\n                request, messages.ERROR, _(\"This photo already exists in the album.\")\n            )\n", "path": "website/photos/admin.py"}]}