We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Custom model delete method deprecated: after django upgrade
**expected behaviour:** Custom deletion logic should move to form_valid() for django > 4

**behaviour:** During the upgrade, we missed updating one of the custom deletion methods in dashboard/mixins.py

`https://github.com/liqd/adhocracy4/blob/main/adhocracy4/dashboard/mixins.py#L132`

**Comment/Question:** *Is this easy to do? What ideas do you have? Would this be a story?* 
It is easy to fix.

![image](https://github.com/liqd/adhocracy4/assets/5695572/a6467657-8383-442b-89dc-16d58aa6a84b)



--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `adhocracy4/dashboard/mixins.py`
Content:
```
1 from copy import deepcopy
2 
3 from django.apps import apps
4 from django.conf import settings
5 from django.contrib import messages
6 from django.core.exceptions import PermissionDenied
7 from django.shortcuts import get_object_or_404
8 from django.shortcuts import redirect
9 from django.urls import NoReverseMatch
10 from django.utils import timezone
11 from django.utils.translation import gettext_lazy as _
12 from django.views.generic import base
13 from django.views.generic import edit
14 
15 from adhocracy4.projects import models as project_models
16 from adhocracy4.rules import mixins as rules_mixins
17 
18 from . import components
19 from . import get_project_dashboard
20 from . import signals
21 
22 Organisation = apps.get_model(settings.A4_ORGANISATIONS_MODEL)
23 
24 
25 class DashboardBaseMixin(rules_mixins.PermissionRequiredMixin):
26     organisation_lookup_field = "slug"
27     organisation_url_kwarg = "organisation_slug"
28 
29     @property
30     def organisation(self):
31         if self.organisation_url_kwarg and self.organisation_url_kwarg in self.kwargs:
32             lookup = {
33                 self.organisation_lookup_field: self.kwargs[self.organisation_url_kwarg]
34             }
35             return get_object_or_404(Organisation, **lookup)
36 
37         return self.project.organisation
38 
39     @property
40     def other_organisations_of_user(self):
41         user = self.request.user
42         if self.organisation:
43             initiator_orgs = user.organisation_set.all()
44             if hasattr(Organisation, "groups") and user.groups.all():
45                 user_groups = user.groups.all().values_list("id", flat=True)
46                 group_orgs = Organisation.objects.filter(groups__in=user_groups)
47                 orgs = initiator_orgs | group_orgs
48                 return orgs.distinct().exclude(pk=self.organisation.pk)
49             return initiator_orgs.exclude(pk=self.organisation.pk)
50         else:
51             return None
52 
53     def get_permission_object(self):
54         raise NotImplementedError("Set permission object.")
55 
56     def get_success_url(self):
57         return self.request.path
58 
59 
60 class BlueprintMixin:
61     @property
62     def blueprint(self):
63         from .blueprints import get_blueprints
64 
65         return dict(get_blueprints())[self.blueprint_key]
66 
67     @property
68     def blueprint_key(self):
69         return self.kwargs["blueprint_slug"]
70 
71 
72 class DashboardComponentMixin(base.ContextMixin):
73     """Set the menu_item and add dashboard information to the context data.
74 
75     Assumes self.project, self.module and self.component are set.
76     """
77 
78     menu_item = "project"
79     component = None
80 
81     def get_context_data(self, **kwargs):
82         context = super().get_context_data(**kwargs)
83 
84         # Workaround Djangos update behavior:
85         # All fields from the POST data will be set on the view.object model
86         # instance, regardless of validation errors.
87         # Thus it is not reliable to check on empty fields on the view.object
88         # but it has to be ensured that the model reflects the database.
89         project = deepcopy(self.project)
90         if project:
91             project.refresh_from_db()
92 
93         dashboard = get_project_dashboard(project)
94 
95         context["dashboard_menu"] = dashboard.get_menu(self.module, self.component)
96 
97         num_valid, num_required = dashboard.get_progress()
98         project_num_valid, project_num_required = dashboard.get_project_progress()
99         project_is_complete = project_num_valid == project_num_required
100         context["project_progress"] = {
101             "valid": num_valid,
102             "required": num_required,
103             "project_is_complete": project_is_complete,
104         }
105 
106         return context
107 
108 
109 class DashboardComponentFormSignalMixin(edit.FormMixin):
110     def form_valid(self, form):
111         response = super().form_valid(form)
112 
113         component = self.component
114         if component.identifier in components.projects:
115             signals.project_component_updated.send(
116                 sender=component.__class__,
117                 project=self.project,
118                 component=component,
119                 user=self.request.user,
120             )
121         else:
122             signals.module_component_updated.send(
123                 sender=component.__class__,
124                 module=self.module,
125                 component=component,
126                 user=self.request.user,
127             )
128         return response
129 
130 
131 class DashboardComponentDeleteSignalMixin(edit.DeletionMixin):
132     def delete(self, request, *args, **kwargs):
133         # Project and module have to be stored before delete is called as
134         # they may rely on the still existing db object.
135         project = self.project
136         module = self.module
137 
138         response = super().delete(request, *args, **kwargs)
139 
140         component = self.component
141         if component.identifier in components.projects:
142             signals.project_component_updated.send(
143                 sender=component.__class__,
144                 project=project,
145                 component=component,
146                 user=self.request.user,
147             )
148         else:
149             signals.module_component_updated.send(
150                 sender=component.__class__,
151                 module=module,
152                 component=component,
153                 user=self.request.user,
154             )
155         return response
156 
157 
158 class DashboardProjectDuplicateMixin:
159     def post(self, request, *args, **kwargs):
160         if "duplicate" in request.POST:
161             pk = int(request.POST["project_pk"])
162             project = get_object_or_404(project_models.Project, pk=pk)
163             can_add = request.user.has_perm("a4projects.add_project", project)
164 
165             if not can_add:
166                 raise PermissionDenied()
167 
168             project_clone = deepcopy(project)
169             project_clone.pk = None
170             if project_clone.tile_image:
171                 project_clone.tile_image.save(
172                     project.tile_image.name, project.tile_image, False
173                 )
174             if project_clone.image:
175                 project_clone.image.save(project.image.name, project.image, False)
176             project_clone.created = timezone.now()
177             project_clone.is_draft = True
178             project_clone.is_archived = False
179             project_clone.save()
180             signals.project_created.send(
181                 sender=None, project=project_clone, user=self.request.user
182             )
183 
184             for moderator in project.moderators.all():
185                 project_clone.moderators.add(moderator)
186 
187             for module in project.module_set.all():
188                 module_clone = deepcopy(module)
189                 module_clone.project = project_clone
190                 module_clone.pk = None
191                 module_clone.save()
192                 signals.module_created.send(
193                     sender=None, module=module_clone, user=self.request.user
194                 )
195 
196                 for phase in module.phase_set.all():
197                     phase_clone = deepcopy(phase)
198                     phase_clone.module = module_clone
199                     phase_clone.pk = None
200                     phase_clone.save()
201 
202                 settings_instance = module.settings_instance
203                 if settings_instance:
204                     settings_instance_clone = deepcopy(settings_instance)
205                     settings_instance_clone.pk = None
206                     settings_instance_clone.module = module_clone
207                     settings_instance_clone.save()
208 
209             messages.success(request, _("Project successfully duplicated."))
210 
211             try:
212                 org_slug = project_clone.organisation.slug
213                 return redirect(
214                     "a4dashboard:project-edit",
215                     organisation_slug=org_slug,
216                     project_slug=project_clone.slug,
217                 )
218             except NoReverseMatch:
219                 return redirect(
220                     "a4dashboard:project-edit", project_slug=project_clone.slug
221                 )
222         else:
223             return super().post(request, *args, **kwargs)
224
```
--- 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/adhocracy4/dashboard/mixins.py b/adhocracy4/dashboard/mixins.py
--- a/adhocracy4/dashboard/mixins.py
+++ b/adhocracy4/dashboard/mixins.py
@@ -129,13 +129,13 @@
 
 
 class DashboardComponentDeleteSignalMixin(edit.DeletionMixin):
-    def delete(self, request, *args, **kwargs):
+    def form_valid(self, request, *args, **kwargs):
         # Project and module have to be stored before delete is called as
         # they may rely on the still existing db object.
         project = self.project
         module = self.module
 
-        response = super().delete(request, *args, **kwargs)
+        response = super().form_valid(request, *args, **kwargs)
 
         component = self.component
         if component.identifier in components.projects:

{"golden_diff": "diff --git a/adhocracy4/dashboard/mixins.py b/adhocracy4/dashboard/mixins.py\n--- a/adhocracy4/dashboard/mixins.py\n+++ b/adhocracy4/dashboard/mixins.py\n@@ -129,13 +129,13 @@\n \n \n class DashboardComponentDeleteSignalMixin(edit.DeletionMixin):\n-    def delete(self, request, *args, **kwargs):\n+    def form_valid(self, request, *args, **kwargs):\n         # Project and module have to be stored before delete is called as\n         # they may rely on the still existing db object.\n         project = self.project\n         module = self.module\n \n-        response = super().delete(request, *args, **kwargs)\n+        response = super().form_valid(request, *args, **kwargs)\n \n         component = self.component\n         if component.identifier in components.projects:\n", "issue": "Custom model delete method deprecated: after django upgrade\n**expected behaviour:** Custom deletion logic should move to form_valid() for django > 4\r\n\r\n**behaviour:** During the upgrade, we missed updating one of the custom deletion methods in dashboard/mixins.py\r\n\r\n`https://github.com/liqd/adhocracy4/blob/main/adhocracy4/dashboard/mixins.py#L132`\r\n\r\n**Comment/Question:** *Is this easy to do? What ideas do you have? Would this be a story?* \r\nIt is easy to fix.\r\n\r\n![image](https://github.com/liqd/adhocracy4/assets/5695572/a6467657-8383-442b-89dc-16d58aa6a84b)\r\n\r\n\n", "before_files": [{"content": "from copy import deepcopy\n\nfrom django.apps import apps\nfrom django.conf import settings\nfrom django.contrib import messages\nfrom django.core.exceptions import PermissionDenied\nfrom django.shortcuts import get_object_or_404\nfrom django.shortcuts import redirect\nfrom django.urls import NoReverseMatch\nfrom django.utils import timezone\nfrom django.utils.translation import gettext_lazy as _\nfrom django.views.generic import base\nfrom django.views.generic import edit\n\nfrom adhocracy4.projects import models as project_models\nfrom adhocracy4.rules import mixins as rules_mixins\n\nfrom . import components\nfrom . import get_project_dashboard\nfrom . import signals\n\nOrganisation = apps.get_model(settings.A4_ORGANISATIONS_MODEL)\n\n\nclass DashboardBaseMixin(rules_mixins.PermissionRequiredMixin):\n    organisation_lookup_field = \"slug\"\n    organisation_url_kwarg = \"organisation_slug\"\n\n    @property\n    def organisation(self):\n        if self.organisation_url_kwarg and self.organisation_url_kwarg in self.kwargs:\n            lookup = {\n                self.organisation_lookup_field: self.kwargs[self.organisation_url_kwarg]\n            }\n            return get_object_or_404(Organisation, **lookup)\n\n        return self.project.organisation\n\n    @property\n    def other_organisations_of_user(self):\n        user = self.request.user\n        if self.organisation:\n            initiator_orgs = user.organisation_set.all()\n            if hasattr(Organisation, \"groups\") and user.groups.all():\n                user_groups = user.groups.all().values_list(\"id\", flat=True)\n                group_orgs = Organisation.objects.filter(groups__in=user_groups)\n                orgs = initiator_orgs | group_orgs\n                return orgs.distinct().exclude(pk=self.organisation.pk)\n            return initiator_orgs.exclude(pk=self.organisation.pk)\n        else:\n            return None\n\n    def get_permission_object(self):\n        raise NotImplementedError(\"Set permission object.\")\n\n    def get_success_url(self):\n        return self.request.path\n\n\nclass BlueprintMixin:\n    @property\n    def blueprint(self):\n        from .blueprints import get_blueprints\n\n        return dict(get_blueprints())[self.blueprint_key]\n\n    @property\n    def blueprint_key(self):\n        return self.kwargs[\"blueprint_slug\"]\n\n\nclass DashboardComponentMixin(base.ContextMixin):\n    \"\"\"Set the menu_item and add dashboard information to the context data.\n\n    Assumes self.project, self.module and self.component are set.\n    \"\"\"\n\n    menu_item = \"project\"\n    component = None\n\n    def get_context_data(self, **kwargs):\n        context = super().get_context_data(**kwargs)\n\n        # Workaround Djangos update behavior:\n        # All fields from the POST data will be set on the view.object model\n        # instance, regardless of validation errors.\n        # Thus it is not reliable to check on empty fields on the view.object\n        # but it has to be ensured that the model reflects the database.\n        project = deepcopy(self.project)\n        if project:\n            project.refresh_from_db()\n\n        dashboard = get_project_dashboard(project)\n\n        context[\"dashboard_menu\"] = dashboard.get_menu(self.module, self.component)\n\n        num_valid, num_required = dashboard.get_progress()\n        project_num_valid, project_num_required = dashboard.get_project_progress()\n        project_is_complete = project_num_valid == project_num_required\n        context[\"project_progress\"] = {\n            \"valid\": num_valid,\n            \"required\": num_required,\n            \"project_is_complete\": project_is_complete,\n        }\n\n        return context\n\n\nclass DashboardComponentFormSignalMixin(edit.FormMixin):\n    def form_valid(self, form):\n        response = super().form_valid(form)\n\n        component = self.component\n        if component.identifier in components.projects:\n            signals.project_component_updated.send(\n                sender=component.__class__,\n                project=self.project,\n                component=component,\n                user=self.request.user,\n            )\n        else:\n            signals.module_component_updated.send(\n                sender=component.__class__,\n                module=self.module,\n                component=component,\n                user=self.request.user,\n            )\n        return response\n\n\nclass DashboardComponentDeleteSignalMixin(edit.DeletionMixin):\n    def delete(self, request, *args, **kwargs):\n        # Project and module have to be stored before delete is called as\n        # they may rely on the still existing db object.\n        project = self.project\n        module = self.module\n\n        response = super().delete(request, *args, **kwargs)\n\n        component = self.component\n        if component.identifier in components.projects:\n            signals.project_component_updated.send(\n                sender=component.__class__,\n                project=project,\n                component=component,\n                user=self.request.user,\n            )\n        else:\n            signals.module_component_updated.send(\n                sender=component.__class__,\n                module=module,\n                component=component,\n                user=self.request.user,\n            )\n        return response\n\n\nclass DashboardProjectDuplicateMixin:\n    def post(self, request, *args, **kwargs):\n        if \"duplicate\" in request.POST:\n            pk = int(request.POST[\"project_pk\"])\n            project = get_object_or_404(project_models.Project, pk=pk)\n            can_add = request.user.has_perm(\"a4projects.add_project\", project)\n\n            if not can_add:\n                raise PermissionDenied()\n\n            project_clone = deepcopy(project)\n            project_clone.pk = None\n            if project_clone.tile_image:\n                project_clone.tile_image.save(\n                    project.tile_image.name, project.tile_image, False\n                )\n            if project_clone.image:\n                project_clone.image.save(project.image.name, project.image, False)\n            project_clone.created = timezone.now()\n            project_clone.is_draft = True\n            project_clone.is_archived = False\n            project_clone.save()\n            signals.project_created.send(\n                sender=None, project=project_clone, user=self.request.user\n            )\n\n            for moderator in project.moderators.all():\n                project_clone.moderators.add(moderator)\n\n            for module in project.module_set.all():\n                module_clone = deepcopy(module)\n                module_clone.project = project_clone\n                module_clone.pk = None\n                module_clone.save()\n                signals.module_created.send(\n                    sender=None, module=module_clone, user=self.request.user\n                )\n\n                for phase in module.phase_set.all():\n                    phase_clone = deepcopy(phase)\n                    phase_clone.module = module_clone\n                    phase_clone.pk = None\n                    phase_clone.save()\n\n                settings_instance = module.settings_instance\n                if settings_instance:\n                    settings_instance_clone = deepcopy(settings_instance)\n                    settings_instance_clone.pk = None\n                    settings_instance_clone.module = module_clone\n                    settings_instance_clone.save()\n\n            messages.success(request, _(\"Project successfully duplicated.\"))\n\n            try:\n                org_slug = project_clone.organisation.slug\n                return redirect(\n                    \"a4dashboard:project-edit\",\n                    organisation_slug=org_slug,\n                    project_slug=project_clone.slug,\n                )\n            except NoReverseMatch:\n                return redirect(\n                    \"a4dashboard:project-edit\", project_slug=project_clone.slug\n                )\n        else:\n            return super().post(request, *args, **kwargs)\n", "path": "adhocracy4/dashboard/mixins.py"}], "after_files": [{"content": "from copy import deepcopy\n\nfrom django.apps import apps\nfrom django.conf import settings\nfrom django.contrib import messages\nfrom django.core.exceptions import PermissionDenied\nfrom django.shortcuts import get_object_or_404\nfrom django.shortcuts import redirect\nfrom django.urls import NoReverseMatch\nfrom django.utils import timezone\nfrom django.utils.translation import gettext_lazy as _\nfrom django.views.generic import base\nfrom django.views.generic import edit\n\nfrom adhocracy4.projects import models as project_models\nfrom adhocracy4.rules import mixins as rules_mixins\n\nfrom . import components\nfrom . import get_project_dashboard\nfrom . import signals\n\nOrganisation = apps.get_model(settings.A4_ORGANISATIONS_MODEL)\n\n\nclass DashboardBaseMixin(rules_mixins.PermissionRequiredMixin):\n    organisation_lookup_field = \"slug\"\n    organisation_url_kwarg = \"organisation_slug\"\n\n    @property\n    def organisation(self):\n        if self.organisation_url_kwarg and self.organisation_url_kwarg in self.kwargs:\n            lookup = {\n                self.organisation_lookup_field: self.kwargs[self.organisation_url_kwarg]\n            }\n            return get_object_or_404(Organisation, **lookup)\n\n        return self.project.organisation\n\n    @property\n    def other_organisations_of_user(self):\n        user = self.request.user\n        if self.organisation:\n            initiator_orgs = user.organisation_set.all()\n            if hasattr(Organisation, \"groups\") and user.groups.all():\n                user_groups = user.groups.all().values_list(\"id\", flat=True)\n                group_orgs = Organisation.objects.filter(groups__in=user_groups)\n                orgs = initiator_orgs | group_orgs\n                return orgs.distinct().exclude(pk=self.organisation.pk)\n            return initiator_orgs.exclude(pk=self.organisation.pk)\n        else:\n            return None\n\n    def get_permission_object(self):\n        raise NotImplementedError(\"Set permission object.\")\n\n    def get_success_url(self):\n        return self.request.path\n\n\nclass BlueprintMixin:\n    @property\n    def blueprint(self):\n        from .blueprints import get_blueprints\n\n        return dict(get_blueprints())[self.blueprint_key]\n\n    @property\n    def blueprint_key(self):\n        return self.kwargs[\"blueprint_slug\"]\n\n\nclass DashboardComponentMixin(base.ContextMixin):\n    \"\"\"Set the menu_item and add dashboard information to the context data.\n\n    Assumes self.project, self.module and self.component are set.\n    \"\"\"\n\n    menu_item = \"project\"\n    component = None\n\n    def get_context_data(self, **kwargs):\n        context = super().get_context_data(**kwargs)\n\n        # Workaround Djangos update behavior:\n        # All fields from the POST data will be set on the view.object model\n        # instance, regardless of validation errors.\n        # Thus it is not reliable to check on empty fields on the view.object\n        # but it has to be ensured that the model reflects the database.\n        project = deepcopy(self.project)\n        if project:\n            project.refresh_from_db()\n\n        dashboard = get_project_dashboard(project)\n\n        context[\"dashboard_menu\"] = dashboard.get_menu(self.module, self.component)\n\n        num_valid, num_required = dashboard.get_progress()\n        project_num_valid, project_num_required = dashboard.get_project_progress()\n        project_is_complete = project_num_valid == project_num_required\n        context[\"project_progress\"] = {\n            \"valid\": num_valid,\n            \"required\": num_required,\n            \"project_is_complete\": project_is_complete,\n        }\n\n        return context\n\n\nclass DashboardComponentFormSignalMixin(edit.FormMixin):\n    def form_valid(self, form):\n        response = super().form_valid(form)\n\n        component = self.component\n        if component.identifier in components.projects:\n            signals.project_component_updated.send(\n                sender=component.__class__,\n                project=self.project,\n                component=component,\n                user=self.request.user,\n            )\n        else:\n            signals.module_component_updated.send(\n                sender=component.__class__,\n                module=self.module,\n                component=component,\n                user=self.request.user,\n            )\n        return response\n\n\nclass DashboardComponentDeleteSignalMixin(edit.DeletionMixin):\n    def form_valid(self, request, *args, **kwargs):\n        # Project and module have to be stored before delete is called as\n        # they may rely on the still existing db object.\n        project = self.project\n        module = self.module\n\n        response = super().form_valid(request, *args, **kwargs)\n\n        component = self.component\n        if component.identifier in components.projects:\n            signals.project_component_updated.send(\n                sender=component.__class__,\n                project=project,\n                component=component,\n                user=self.request.user,\n            )\n        else:\n            signals.module_component_updated.send(\n                sender=component.__class__,\n                module=module,\n                component=component,\n                user=self.request.user,\n            )\n        return response\n\n\nclass DashboardProjectDuplicateMixin:\n    def post(self, request, *args, **kwargs):\n        if \"duplicate\" in request.POST:\n            pk = int(request.POST[\"project_pk\"])\n            project = get_object_or_404(project_models.Project, pk=pk)\n            can_add = request.user.has_perm(\"a4projects.add_project\", project)\n\n            if not can_add:\n                raise PermissionDenied()\n\n            project_clone = deepcopy(project)\n            project_clone.pk = None\n            if project_clone.tile_image:\n                project_clone.tile_image.save(\n                    project.tile_image.name, project.tile_image, False\n                )\n            if project_clone.image:\n                project_clone.image.save(project.image.name, project.image, False)\n            project_clone.created = timezone.now()\n            project_clone.is_draft = True\n            project_clone.is_archived = False\n            project_clone.save()\n            signals.project_created.send(\n                sender=None, project=project_clone, user=self.request.user\n            )\n\n            for moderator in project.moderators.all():\n                project_clone.moderators.add(moderator)\n\n            for module in project.module_set.all():\n                module_clone = deepcopy(module)\n                module_clone.project = project_clone\n                module_clone.pk = None\n                module_clone.save()\n                signals.module_created.send(\n                    sender=None, module=module_clone, user=self.request.user\n                )\n\n                for phase in module.phase_set.all():\n                    phase_clone = deepcopy(phase)\n                    phase_clone.module = module_clone\n                    phase_clone.pk = None\n                    phase_clone.save()\n\n                settings_instance = module.settings_instance\n                if settings_instance:\n                    settings_instance_clone = deepcopy(settings_instance)\n                    settings_instance_clone.pk = None\n                    settings_instance_clone.module = module_clone\n                    settings_instance_clone.save()\n\n            messages.success(request, _(\"Project successfully duplicated.\"))\n\n            try:\n                org_slug = project_clone.organisation.slug\n                return redirect(\n                    \"a4dashboard:project-edit\",\n                    organisation_slug=org_slug,\n                    project_slug=project_clone.slug,\n                )\n            except NoReverseMatch:\n                return redirect(\n                    \"a4dashboard:project-edit\", project_slug=project_clone.slug\n                )\n        else:\n            return super().post(request, *args, **kwargs)\n", "path": "adhocracy4/dashboard/mixins.py"}]}