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()

```

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)

{"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"}]}