We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
No registration message no longer accepts links
### Describe the bug
Since #2458 the event registration status (specifically no_registration_message) doesn't support URLs any more on the website (app is unaltered I think). I aslo think the formatting that happens now might be a bit risky.

### How to reproduce
Make an event with URL in the no_registration message and view it.

### Expected behaviour
A nice link.

### Screenshots
<img width="627" alt="afbeelding" src="https://github.com/svthalia/concrexit/assets/41264528/66d61a15-0ceb-43bf-aa5a-a6536a02739d">




--- 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/events/admin/event.py`
Content:
```
1 """Registers admin interfaces for the event model."""
2 
3 from django.contrib import admin, messages
4 from django.template.defaultfilters import date as _date
5 from django.urls import path, reverse
6 from django.utils import timezone
7 from django.utils.html import format_html
8 from django.utils.translation import gettext_lazy as _
9 
10 from events import emails, models, services
11 from events.admin.filters import LectureYearFilter
12 from events.admin.forms import EventAdminForm, RegistrationInformationFieldForm
13 from events.admin.inlines import (
14     PizzaEventInline,
15     PromotionRequestInline,
16     RegistrationInformationFieldInline,
17 )
18 from events.admin.views import (
19     EventAdminDetails,
20     EventMarkPresentQR,
21     EventRegistrationsExport,
22 )
23 from utils.admin import DoNextModelAdmin
24 
25 
26 @admin.register(models.Event)
27 class EventAdmin(DoNextModelAdmin):
28     """Manage the events."""
29 
30     form = EventAdminForm
31 
32     inlines = (
33         RegistrationInformationFieldInline,
34         PizzaEventInline,
35         PromotionRequestInline,
36     )
37 
38     list_display = (
39         "overview_link",
40         "event_date",
41         "registration_date",
42         "num_participants",
43         "get_organisers",
44         "category",
45         "published",
46         "edit_link",
47     )
48     list_display_links = ("edit_link",)
49     list_filter = (LectureYearFilter, "start", "published", "category")
50     actions = ("make_published", "make_unpublished")
51     date_hierarchy = "start"
52     search_fields = ("title", "description")
53     prepopulated_fields = {
54         "map_location": ("location",),
55     }
56 
57     filter_horizontal = ("documents", "organisers")
58 
59     fieldsets = (
60         (
61             _("General"),
62             {
63                 "fields": (
64                     "title",
65                     "slug",
66                     "published",
67                     "organisers",
68                 )
69             },
70         ),
71         (
72             _("Detail"),
73             {
74                 "fields": (
75                     "category",
76                     "start",
77                     "end",
78                     "description",
79                     "caption",
80                     "location",
81                     "map_location",
82                     "show_map_location",
83                 ),
84                 "classes": ("collapse", "start-open"),
85             },
86         ),
87         (
88             _("Registrations"),
89             {
90                 "fields": (
91                     "price",
92                     "fine",
93                     "tpay_allowed",
94                     "max_participants",
95                     "registration_without_membership",
96                     "registration_start",
97                     "registration_end",
98                     "cancel_deadline",
99                     "send_cancel_email",
100                     "optional_registrations",
101                     "no_registration_message",
102                 ),
103                 "classes": ("collapse",),
104             },
105         ),
106         (
107             _("Extra"),
108             {"fields": ("documents", "shift"), "classes": ("collapse",)},
109         ),
110     )
111 
112     def get_queryset(self, request):
113         queryset = (
114             super()
115             .get_queryset(request)
116             .select_properties("participant_count")
117             .prefetch_related("organisers")
118         )
119         if not (
120             request.user.has_perm("events.override_organiser")
121             or request.user.has_perm("events.view_unpublished")
122         ):
123             queryset_published = queryset.filter(published=True)
124             queryset_unpublished = queryset.filter(
125                 published=False,
126                 organisers__in=list(
127                     request.member.get_member_groups().values_list("id", flat=True)
128                 ),
129             )
130             queryset = queryset_published | queryset_unpublished
131         return queryset
132 
133     def get_form(self, request, obj=None, change=False, **kwargs):
134         form = super().get_form(request, obj, change, **kwargs)
135         form.request = request
136         return form
137 
138     def overview_link(self, obj):
139         return format_html(
140             '<a href="{link}">{title}</a>',
141             link=reverse("admin:events_event_details", kwargs={"pk": obj.pk}),
142             title=obj.title,
143         )
144 
145     def has_delete_permission(self, request, obj=None):
146         """Only allow deleting an event if the user is an organiser."""
147         if obj is not None and not services.is_organiser(request.member, obj):
148             return False
149         return super().has_delete_permission(request, obj)
150 
151     def has_change_permission(self, request, obj=None):
152         """Only allow access to the change form if the user is an organiser."""
153         if obj is not None and not services.is_organiser(request.member, obj):
154             return False
155         return super().has_change_permission(request, obj)
156 
157     def event_date(self, obj):
158         event_date = timezone.make_naive(obj.start)
159         return _date(event_date, "l d b Y, G:i")
160 
161     event_date.short_description = _("Event Date")
162     event_date.admin_order_field = "start"
163 
164     def registration_date(self, obj):
165         if obj.registration_start is not None:
166             start_date = timezone.make_naive(obj.registration_start)
167         else:
168             start_date = obj.registration_start
169 
170         return _date(start_date, "l d b Y, G:i")
171 
172     registration_date.short_description = _("Registration Start")
173     registration_date.admin_order_field = "registration_start"
174 
175     def edit_link(self, obj):
176         return _("Edit")
177 
178     edit_link.short_description = ""
179 
180     def num_participants(self, obj):
181         """Pretty-print the number of participants."""
182         num = obj.participant_count  # prefetched aggregateproperty
183         if not obj.max_participants:
184             return f"{num}/∞"
185         return f"{num}/{obj.max_participants}"
186 
187     num_participants.short_description = _("Number of participants")
188 
189     def get_organisers(self, obj):
190         return ", ".join(str(o) for o in obj.organisers.all())
191 
192     get_organisers.short_description = _("Organisers")
193 
194     def make_published(self, request, queryset):
195         """Change the status of the event to published."""
196         self._change_published(request, queryset, True)
197 
198     make_published.short_description = _("Publish selected events")
199 
200     def make_unpublished(self, request, queryset):
201         """Change the status of the event to unpublished."""
202         self._change_published(request, queryset, False)
203 
204     make_unpublished.short_description = _("Unpublish selected events")
205 
206     @staticmethod
207     def _change_published(request, queryset, published):
208         if not request.user.is_superuser:
209             queryset = queryset.filter(
210                 organisers__in=request.member.get_member_groups()
211             )
212         queryset.update(published=published)
213 
214     def save_formset(self, request, form, formset, change):
215         """Save formsets with their order."""
216         formset.save()
217 
218         informationfield_forms = (
219             x
220             for x in formset.forms
221             if isinstance(x, RegistrationInformationFieldForm)
222             and "DELETE" not in x.changed_data
223         )
224         form.instance.set_registrationinformationfield_order(
225             [
226                 f.instance.pk
227                 for f in sorted(
228                     informationfield_forms,
229                     key=lambda x: (x.cleaned_data["order"], x.instance.pk),
230                 )
231             ]
232         )
233         form.instance.save()
234 
235     def save_model(self, request, obj, form, change):
236         if change and "max_participants" in form.changed_data:
237             prev = self.model.objects.get(id=obj.id)
238             prev_limit = prev.max_participants
239             self_limit = obj.max_participants
240             if prev_limit is None:
241                 prev_limit = prev.participant_count
242             if self_limit is None:
243                 self_limit = obj.participant_count
244 
245             if prev_limit < self_limit and prev_limit < obj.participant_count:
246                 diff = self_limit - prev_limit
247                 joiners = prev.queue[:diff]
248                 for registration in joiners:
249                     emails.notify_waiting(obj, registration)
250                 messages.info(
251                     request,
252                     "The maximum number of participants was increased. Any members that moved from the waiting list to the participants list have been notified.",
253                 )
254             elif self_limit < prev_limit and self_limit < obj.participant_count:
255                 diff = self_limit - prev_limit
256                 leavers = prev.registrations[self_limit:]
257                 address = map(lambda r: r.email, leavers)
258                 link = "mailto:?bcc=" + ",".join(address)
259                 messages.warning(
260                     request,
261                     format_html(
262                         "The maximum number of participants was decreased and some members moved to the waiting list. <a href='{}' style='text-decoration: underline;'>Use this link to send them an email.</a>",
263                         link,
264                     ),
265                 )
266         super().save_model(request, obj, form, change)
267 
268     def get_actions(self, request):
269         actions = super().get_actions(request)
270         if "delete_selected" in actions:
271             del actions["delete_selected"]
272         return actions
273 
274     def get_formsets_with_inlines(self, request, obj=None):
275         for inline in self.get_inline_instances(request, obj):
276             if self.has_change_permission(request, obj) or obj is None:
277                 yield inline.get_formset(request, obj), inline
278 
279     def get_urls(self):
280         urls = super().get_urls()
281         custom_urls = [
282             path(
283                 "<int:pk>/details/",
284                 self.admin_site.admin_view(EventAdminDetails.as_view()),
285                 name="events_event_details",
286             ),
287             path(
288                 "<int:pk>/export/",
289                 self.admin_site.admin_view(EventRegistrationsExport.as_view()),
290                 name="events_event_export",
291             ),
292             path(
293                 "<int:pk>/mark-present-qr/",
294                 self.admin_site.admin_view(EventMarkPresentQR.as_view()),
295                 name="events_event_mark_present_qr",
296             ),
297         ]
298         return custom_urls + urls
299
```
--- 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/events/admin/event.py b/website/events/admin/event.py
--- a/website/events/admin/event.py
+++ b/website/events/admin/event.py
@@ -98,7 +98,26 @@
                     "cancel_deadline",
                     "send_cancel_email",
                     "optional_registrations",
+                ),
+                "classes": ("collapse",),
+            },
+        ),
+        (
+            _("Registration status messages"),
+            {
+                "fields": (
                     "no_registration_message",
+                    "registration_msg_optional",
+                    "registration_msg_optional_registered",
+                    "registration_msg_registered",
+                    "registration_msg_open",
+                    "registration_msg_full",
+                    "registration_msg_waitinglist",
+                    "registration_msg_will_open",
+                    "registration_msg_expired",
+                    "registration_msg_cancelled",
+                    "registration_msg_cancelled_late",
+                    "registration_msg_cancelled_final",
                 ),
                 "classes": ("collapse",),
             },

{"golden_diff": "diff --git a/website/events/admin/event.py b/website/events/admin/event.py\n--- a/website/events/admin/event.py\n+++ b/website/events/admin/event.py\n@@ -98,7 +98,26 @@\n                     \"cancel_deadline\",\n                     \"send_cancel_email\",\n                     \"optional_registrations\",\n+                ),\n+                \"classes\": (\"collapse\",),\n+            },\n+        ),\n+        (\n+            _(\"Registration status messages\"),\n+            {\n+                \"fields\": (\n                     \"no_registration_message\",\n+                    \"registration_msg_optional\",\n+                    \"registration_msg_optional_registered\",\n+                    \"registration_msg_registered\",\n+                    \"registration_msg_open\",\n+                    \"registration_msg_full\",\n+                    \"registration_msg_waitinglist\",\n+                    \"registration_msg_will_open\",\n+                    \"registration_msg_expired\",\n+                    \"registration_msg_cancelled\",\n+                    \"registration_msg_cancelled_late\",\n+                    \"registration_msg_cancelled_final\",\n                 ),\n                 \"classes\": (\"collapse\",),\n             },\n", "issue": "No registration message no longer accepts links\n### Describe the bug\r\nSince #2458 the event registration status (specifically no_registration_message) doesn't support URLs any more on the website (app is unaltered I think). I aslo think the formatting that happens now might be a bit risky.\r\n\r\n### How to reproduce\r\nMake an event with URL in the no_registration message and view it.\r\n\r\n### Expected behaviour\r\nA nice link.\r\n\r\n### Screenshots\r\n<img width=\"627\" alt=\"afbeelding\" src=\"https://github.com/svthalia/concrexit/assets/41264528/66d61a15-0ceb-43bf-aa5a-a6536a02739d\">\r\n\r\n\r\n\n", "before_files": [{"content": "\"\"\"Registers admin interfaces for the event model.\"\"\"\n\nfrom django.contrib import admin, messages\nfrom django.template.defaultfilters import date as _date\nfrom django.urls import path, reverse\nfrom django.utils import timezone\nfrom django.utils.html import format_html\nfrom django.utils.translation import gettext_lazy as _\n\nfrom events import emails, models, services\nfrom events.admin.filters import LectureYearFilter\nfrom events.admin.forms import EventAdminForm, RegistrationInformationFieldForm\nfrom events.admin.inlines import (\n    PizzaEventInline,\n    PromotionRequestInline,\n    RegistrationInformationFieldInline,\n)\nfrom events.admin.views import (\n    EventAdminDetails,\n    EventMarkPresentQR,\n    EventRegistrationsExport,\n)\nfrom utils.admin import DoNextModelAdmin\n\n\n@admin.register(models.Event)\nclass EventAdmin(DoNextModelAdmin):\n    \"\"\"Manage the events.\"\"\"\n\n    form = EventAdminForm\n\n    inlines = (\n        RegistrationInformationFieldInline,\n        PizzaEventInline,\n        PromotionRequestInline,\n    )\n\n    list_display = (\n        \"overview_link\",\n        \"event_date\",\n        \"registration_date\",\n        \"num_participants\",\n        \"get_organisers\",\n        \"category\",\n        \"published\",\n        \"edit_link\",\n    )\n    list_display_links = (\"edit_link\",)\n    list_filter = (LectureYearFilter, \"start\", \"published\", \"category\")\n    actions = (\"make_published\", \"make_unpublished\")\n    date_hierarchy = \"start\"\n    search_fields = (\"title\", \"description\")\n    prepopulated_fields = {\n        \"map_location\": (\"location\",),\n    }\n\n    filter_horizontal = (\"documents\", \"organisers\")\n\n    fieldsets = (\n        (\n            _(\"General\"),\n            {\n                \"fields\": (\n                    \"title\",\n                    \"slug\",\n                    \"published\",\n                    \"organisers\",\n                )\n            },\n        ),\n        (\n            _(\"Detail\"),\n            {\n                \"fields\": (\n                    \"category\",\n                    \"start\",\n                    \"end\",\n                    \"description\",\n                    \"caption\",\n                    \"location\",\n                    \"map_location\",\n                    \"show_map_location\",\n                ),\n                \"classes\": (\"collapse\", \"start-open\"),\n            },\n        ),\n        (\n            _(\"Registrations\"),\n            {\n                \"fields\": (\n                    \"price\",\n                    \"fine\",\n                    \"tpay_allowed\",\n                    \"max_participants\",\n                    \"registration_without_membership\",\n                    \"registration_start\",\n                    \"registration_end\",\n                    \"cancel_deadline\",\n                    \"send_cancel_email\",\n                    \"optional_registrations\",\n                    \"no_registration_message\",\n                ),\n                \"classes\": (\"collapse\",),\n            },\n        ),\n        (\n            _(\"Extra\"),\n            {\"fields\": (\"documents\", \"shift\"), \"classes\": (\"collapse\",)},\n        ),\n    )\n\n    def get_queryset(self, request):\n        queryset = (\n            super()\n            .get_queryset(request)\n            .select_properties(\"participant_count\")\n            .prefetch_related(\"organisers\")\n        )\n        if not (\n            request.user.has_perm(\"events.override_organiser\")\n            or request.user.has_perm(\"events.view_unpublished\")\n        ):\n            queryset_published = queryset.filter(published=True)\n            queryset_unpublished = queryset.filter(\n                published=False,\n                organisers__in=list(\n                    request.member.get_member_groups().values_list(\"id\", flat=True)\n                ),\n            )\n            queryset = queryset_published | queryset_unpublished\n        return queryset\n\n    def get_form(self, request, obj=None, change=False, **kwargs):\n        form = super().get_form(request, obj, change, **kwargs)\n        form.request = request\n        return form\n\n    def overview_link(self, obj):\n        return format_html(\n            '<a href=\"{link}\">{title}</a>',\n            link=reverse(\"admin:events_event_details\", kwargs={\"pk\": obj.pk}),\n            title=obj.title,\n        )\n\n    def has_delete_permission(self, request, obj=None):\n        \"\"\"Only allow deleting an event if the user is an organiser.\"\"\"\n        if obj is not None and not services.is_organiser(request.member, obj):\n            return False\n        return super().has_delete_permission(request, obj)\n\n    def has_change_permission(self, request, obj=None):\n        \"\"\"Only allow access to the change form if the user is an organiser.\"\"\"\n        if obj is not None and not services.is_organiser(request.member, obj):\n            return False\n        return super().has_change_permission(request, obj)\n\n    def event_date(self, obj):\n        event_date = timezone.make_naive(obj.start)\n        return _date(event_date, \"l d b Y, G:i\")\n\n    event_date.short_description = _(\"Event Date\")\n    event_date.admin_order_field = \"start\"\n\n    def registration_date(self, obj):\n        if obj.registration_start is not None:\n            start_date = timezone.make_naive(obj.registration_start)\n        else:\n            start_date = obj.registration_start\n\n        return _date(start_date, \"l d b Y, G:i\")\n\n    registration_date.short_description = _(\"Registration Start\")\n    registration_date.admin_order_field = \"registration_start\"\n\n    def edit_link(self, obj):\n        return _(\"Edit\")\n\n    edit_link.short_description = \"\"\n\n    def num_participants(self, obj):\n        \"\"\"Pretty-print the number of participants.\"\"\"\n        num = obj.participant_count  # prefetched aggregateproperty\n        if not obj.max_participants:\n            return f\"{num}/\u221e\"\n        return f\"{num}/{obj.max_participants}\"\n\n    num_participants.short_description = _(\"Number of participants\")\n\n    def get_organisers(self, obj):\n        return \", \".join(str(o) for o in obj.organisers.all())\n\n    get_organisers.short_description = _(\"Organisers\")\n\n    def make_published(self, request, queryset):\n        \"\"\"Change the status of the event to published.\"\"\"\n        self._change_published(request, queryset, True)\n\n    make_published.short_description = _(\"Publish selected events\")\n\n    def make_unpublished(self, request, queryset):\n        \"\"\"Change the status of the event to unpublished.\"\"\"\n        self._change_published(request, queryset, False)\n\n    make_unpublished.short_description = _(\"Unpublish selected events\")\n\n    @staticmethod\n    def _change_published(request, queryset, published):\n        if not request.user.is_superuser:\n            queryset = queryset.filter(\n                organisers__in=request.member.get_member_groups()\n            )\n        queryset.update(published=published)\n\n    def save_formset(self, request, form, formset, change):\n        \"\"\"Save formsets with their order.\"\"\"\n        formset.save()\n\n        informationfield_forms = (\n            x\n            for x in formset.forms\n            if isinstance(x, RegistrationInformationFieldForm)\n            and \"DELETE\" not in x.changed_data\n        )\n        form.instance.set_registrationinformationfield_order(\n            [\n                f.instance.pk\n                for f in sorted(\n                    informationfield_forms,\n                    key=lambda x: (x.cleaned_data[\"order\"], x.instance.pk),\n                )\n            ]\n        )\n        form.instance.save()\n\n    def save_model(self, request, obj, form, change):\n        if change and \"max_participants\" in form.changed_data:\n            prev = self.model.objects.get(id=obj.id)\n            prev_limit = prev.max_participants\n            self_limit = obj.max_participants\n            if prev_limit is None:\n                prev_limit = prev.participant_count\n            if self_limit is None:\n                self_limit = obj.participant_count\n\n            if prev_limit < self_limit and prev_limit < obj.participant_count:\n                diff = self_limit - prev_limit\n                joiners = prev.queue[:diff]\n                for registration in joiners:\n                    emails.notify_waiting(obj, registration)\n                messages.info(\n                    request,\n                    \"The maximum number of participants was increased. Any members that moved from the waiting list to the participants list have been notified.\",\n                )\n            elif self_limit < prev_limit and self_limit < obj.participant_count:\n                diff = self_limit - prev_limit\n                leavers = prev.registrations[self_limit:]\n                address = map(lambda r: r.email, leavers)\n                link = \"mailto:?bcc=\" + \",\".join(address)\n                messages.warning(\n                    request,\n                    format_html(\n                        \"The maximum number of participants was decreased and some members moved to the waiting list. <a href='{}' style='text-decoration: underline;'>Use this link to send them an email.</a>\",\n                        link,\n                    ),\n                )\n        super().save_model(request, obj, form, change)\n\n    def get_actions(self, request):\n        actions = super().get_actions(request)\n        if \"delete_selected\" in actions:\n            del actions[\"delete_selected\"]\n        return actions\n\n    def get_formsets_with_inlines(self, request, obj=None):\n        for inline in self.get_inline_instances(request, obj):\n            if self.has_change_permission(request, obj) or obj is None:\n                yield inline.get_formset(request, obj), inline\n\n    def get_urls(self):\n        urls = super().get_urls()\n        custom_urls = [\n            path(\n                \"<int:pk>/details/\",\n                self.admin_site.admin_view(EventAdminDetails.as_view()),\n                name=\"events_event_details\",\n            ),\n            path(\n                \"<int:pk>/export/\",\n                self.admin_site.admin_view(EventRegistrationsExport.as_view()),\n                name=\"events_event_export\",\n            ),\n            path(\n                \"<int:pk>/mark-present-qr/\",\n                self.admin_site.admin_view(EventMarkPresentQR.as_view()),\n                name=\"events_event_mark_present_qr\",\n            ),\n        ]\n        return custom_urls + urls\n", "path": "website/events/admin/event.py"}], "after_files": [{"content": "\"\"\"Registers admin interfaces for the event model.\"\"\"\n\nfrom django.contrib import admin, messages\nfrom django.template.defaultfilters import date as _date\nfrom django.urls import path, reverse\nfrom django.utils import timezone\nfrom django.utils.html import format_html\nfrom django.utils.translation import gettext_lazy as _\n\nfrom events import emails, models, services\nfrom events.admin.filters import LectureYearFilter\nfrom events.admin.forms import EventAdminForm, RegistrationInformationFieldForm\nfrom events.admin.inlines import (\n    PizzaEventInline,\n    PromotionRequestInline,\n    RegistrationInformationFieldInline,\n)\nfrom events.admin.views import (\n    EventAdminDetails,\n    EventMarkPresentQR,\n    EventRegistrationsExport,\n)\nfrom utils.admin import DoNextModelAdmin\n\n\n@admin.register(models.Event)\nclass EventAdmin(DoNextModelAdmin):\n    \"\"\"Manage the events.\"\"\"\n\n    form = EventAdminForm\n\n    inlines = (\n        RegistrationInformationFieldInline,\n        PizzaEventInline,\n        PromotionRequestInline,\n    )\n\n    list_display = (\n        \"overview_link\",\n        \"event_date\",\n        \"registration_date\",\n        \"num_participants\",\n        \"get_organisers\",\n        \"category\",\n        \"published\",\n        \"edit_link\",\n    )\n    list_display_links = (\"edit_link\",)\n    list_filter = (LectureYearFilter, \"start\", \"published\", \"category\")\n    actions = (\"make_published\", \"make_unpublished\")\n    date_hierarchy = \"start\"\n    search_fields = (\"title\", \"description\")\n    prepopulated_fields = {\n        \"map_location\": (\"location\",),\n    }\n\n    filter_horizontal = (\"documents\", \"organisers\")\n\n    fieldsets = (\n        (\n            _(\"General\"),\n            {\n                \"fields\": (\n                    \"title\",\n                    \"slug\",\n                    \"published\",\n                    \"organisers\",\n                )\n            },\n        ),\n        (\n            _(\"Detail\"),\n            {\n                \"fields\": (\n                    \"category\",\n                    \"start\",\n                    \"end\",\n                    \"description\",\n                    \"caption\",\n                    \"location\",\n                    \"map_location\",\n                    \"show_map_location\",\n                ),\n                \"classes\": (\"collapse\", \"start-open\"),\n            },\n        ),\n        (\n            _(\"Registrations\"),\n            {\n                \"fields\": (\n                    \"price\",\n                    \"fine\",\n                    \"tpay_allowed\",\n                    \"max_participants\",\n                    \"registration_without_membership\",\n                    \"registration_start\",\n                    \"registration_end\",\n                    \"cancel_deadline\",\n                    \"send_cancel_email\",\n                    \"optional_registrations\",\n                ),\n                \"classes\": (\"collapse\",),\n            },\n        ),\n        (\n            _(\"Registration status messages\"),\n            {\n                \"fields\": (\n                    \"no_registration_message\",\n                    \"registration_msg_optional\",\n                    \"registration_msg_optional_registered\",\n                    \"registration_msg_registered\",\n                    \"registration_msg_open\",\n                    \"registration_msg_full\",\n                    \"registration_msg_waitinglist\",\n                    \"registration_msg_will_open\",\n                    \"registration_msg_expired\",\n                    \"registration_msg_cancelled\",\n                    \"registration_msg_cancelled_late\",\n                    \"registration_msg_cancelled_final\",\n                ),\n                \"classes\": (\"collapse\",),\n            },\n        ),\n        (\n            _(\"Extra\"),\n            {\"fields\": (\"documents\", \"shift\"), \"classes\": (\"collapse\",)},\n        ),\n    )\n\n    def get_queryset(self, request):\n        queryset = (\n            super()\n            .get_queryset(request)\n            .select_properties(\"participant_count\")\n            .prefetch_related(\"organisers\")\n        )\n        if not (\n            request.user.has_perm(\"events.override_organiser\")\n            or request.user.has_perm(\"events.view_unpublished\")\n        ):\n            queryset_published = queryset.filter(published=True)\n            queryset_unpublished = queryset.filter(\n                published=False,\n                organisers__in=list(\n                    request.member.get_member_groups().values_list(\"id\", flat=True)\n                ),\n            )\n            queryset = queryset_published | queryset_unpublished\n        return queryset\n\n    def get_form(self, request, obj=None, change=False, **kwargs):\n        form = super().get_form(request, obj, change, **kwargs)\n        form.request = request\n        return form\n\n    def overview_link(self, obj):\n        return format_html(\n            '<a href=\"{link}\">{title}</a>',\n            link=reverse(\"admin:events_event_details\", kwargs={\"pk\": obj.pk}),\n            title=obj.title,\n        )\n\n    def has_delete_permission(self, request, obj=None):\n        \"\"\"Only allow deleting an event if the user is an organiser.\"\"\"\n        if obj is not None and not services.is_organiser(request.member, obj):\n            return False\n        return super().has_delete_permission(request, obj)\n\n    def has_change_permission(self, request, obj=None):\n        \"\"\"Only allow access to the change form if the user is an organiser.\"\"\"\n        if obj is not None and not services.is_organiser(request.member, obj):\n            return False\n        return super().has_change_permission(request, obj)\n\n    def event_date(self, obj):\n        event_date = timezone.make_naive(obj.start)\n        return _date(event_date, \"l d b Y, G:i\")\n\n    event_date.short_description = _(\"Event Date\")\n    event_date.admin_order_field = \"start\"\n\n    def registration_date(self, obj):\n        if obj.registration_start is not None:\n            start_date = timezone.make_naive(obj.registration_start)\n        else:\n            start_date = obj.registration_start\n\n        return _date(start_date, \"l d b Y, G:i\")\n\n    registration_date.short_description = _(\"Registration Start\")\n    registration_date.admin_order_field = \"registration_start\"\n\n    def edit_link(self, obj):\n        return _(\"Edit\")\n\n    edit_link.short_description = \"\"\n\n    def num_participants(self, obj):\n        \"\"\"Pretty-print the number of participants.\"\"\"\n        num = obj.participant_count  # prefetched aggregateproperty\n        if not obj.max_participants:\n            return f\"{num}/\u221e\"\n        return f\"{num}/{obj.max_participants}\"\n\n    num_participants.short_description = _(\"Number of participants\")\n\n    def get_organisers(self, obj):\n        return \", \".join(str(o) for o in obj.organisers.all())\n\n    get_organisers.short_description = _(\"Organisers\")\n\n    def make_published(self, request, queryset):\n        \"\"\"Change the status of the event to published.\"\"\"\n        self._change_published(request, queryset, True)\n\n    make_published.short_description = _(\"Publish selected events\")\n\n    def make_unpublished(self, request, queryset):\n        \"\"\"Change the status of the event to unpublished.\"\"\"\n        self._change_published(request, queryset, False)\n\n    make_unpublished.short_description = _(\"Unpublish selected events\")\n\n    @staticmethod\n    def _change_published(request, queryset, published):\n        if not request.user.is_superuser:\n            queryset = queryset.filter(\n                organisers__in=request.member.get_member_groups()\n            )\n        queryset.update(published=published)\n\n    def save_formset(self, request, form, formset, change):\n        \"\"\"Save formsets with their order.\"\"\"\n        formset.save()\n\n        informationfield_forms = (\n            x\n            for x in formset.forms\n            if isinstance(x, RegistrationInformationFieldForm)\n            and \"DELETE\" not in x.changed_data\n        )\n        form.instance.set_registrationinformationfield_order(\n            [\n                f.instance.pk\n                for f in sorted(\n                    informationfield_forms,\n                    key=lambda x: (x.cleaned_data[\"order\"], x.instance.pk),\n                )\n            ]\n        )\n        form.instance.save()\n\n    def save_model(self, request, obj, form, change):\n        if change and \"max_participants\" in form.changed_data:\n            prev = self.model.objects.get(id=obj.id)\n            prev_limit = prev.max_participants\n            self_limit = obj.max_participants\n            if prev_limit is None:\n                prev_limit = prev.participant_count\n            if self_limit is None:\n                self_limit = obj.participant_count\n\n            if prev_limit < self_limit and prev_limit < obj.participant_count:\n                diff = self_limit - prev_limit\n                joiners = prev.queue[:diff]\n                for registration in joiners:\n                    emails.notify_waiting(obj, registration)\n                messages.info(\n                    request,\n                    \"The maximum number of participants was increased. Any members that moved from the waiting list to the participants list have been notified.\",\n                )\n            elif self_limit < prev_limit and self_limit < obj.participant_count:\n                diff = self_limit - prev_limit\n                leavers = prev.registrations[self_limit:]\n                address = map(lambda r: r.email, leavers)\n                link = \"mailto:?bcc=\" + \",\".join(address)\n                messages.warning(\n                    request,\n                    format_html(\n                        \"The maximum number of participants was decreased and some members moved to the waiting list. <a href='{}' style='text-decoration: underline;'>Use this link to send them an email.</a>\",\n                        link,\n                    ),\n                )\n        super().save_model(request, obj, form, change)\n\n    def get_actions(self, request):\n        actions = super().get_actions(request)\n        if \"delete_selected\" in actions:\n            del actions[\"delete_selected\"]\n        return actions\n\n    def get_formsets_with_inlines(self, request, obj=None):\n        for inline in self.get_inline_instances(request, obj):\n            if self.has_change_permission(request, obj) or obj is None:\n                yield inline.get_formset(request, obj), inline\n\n    def get_urls(self):\n        urls = super().get_urls()\n        custom_urls = [\n            path(\n                \"<int:pk>/details/\",\n                self.admin_site.admin_view(EventAdminDetails.as_view()),\n                name=\"events_event_details\",\n            ),\n            path(\n                \"<int:pk>/export/\",\n                self.admin_site.admin_view(EventRegistrationsExport.as_view()),\n                name=\"events_event_export\",\n            ),\n            path(\n                \"<int:pk>/mark-present-qr/\",\n                self.admin_site.admin_view(EventMarkPresentQR.as_view()),\n                name=\"events_event_mark_present_qr\",\n            ),\n        ]\n        return custom_urls + urls\n", "path": "website/events/admin/event.py"}]}