We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Allow users with management permissions to make events visible for all groups
Allow users with management permissions to make events visible for all groups by attributing the global permission instead of an object-level permission. Make sure that all views respect the global permission.
Also reword the label of the management checkbox on the GroupForm, as we are now using "ephios instance" as a label for the settings that require the is_staff flag

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `ephios/core/forms/users.py`
Content:
```
1 from crispy_forms.bootstrap import FormActions
2 from crispy_forms.helper import FormHelper
3 from crispy_forms.layout import Field, Fieldset, Layout, Submit
4 from django import forms
5 from django.contrib.auth.models import Group
6 from django.db.models import Q
7 from django.forms import (
8     CharField,
9     CheckboxSelectMultiple,
10     DateField,
11     DecimalField,
12     Form,
13     ModelForm,
14     ModelMultipleChoiceField,
15     MultipleChoiceField,
16     inlineformset_factory,
17 )
18 from django.urls import reverse
19 from django.utils.translation import gettext as _
20 from django_select2.forms import Select2MultipleWidget, Select2Widget
21 from guardian.shortcuts import assign_perm, get_objects_for_group, remove_perm
22 
23 from ephios.core.consequences import WorkingHoursConsequenceHandler
24 from ephios.core.models import QualificationGrant, UserProfile
25 from ephios.core.services.notifications.backends import enabled_notification_backends
26 from ephios.core.services.notifications.types import enabled_notification_types
27 from ephios.core.widgets import MultiUserProfileWidget
28 from ephios.extra.crispy import AbortLink
29 from ephios.extra.permissions import PermissionField, PermissionFormMixin
30 from ephios.extra.widgets import CustomDateInput
31 from ephios.modellogging.log import add_log_recorder
32 from ephios.modellogging.recorders import DerivedFieldsLogRecorder
33 
34 MANAGEMENT_PERMISSIONS = [
35     "auth.add_group",
36     "auth.change_group",
37     "auth.delete_group",
38     "auth.view_group",
39     "core.add_userprofile",
40     "core.change_userprofile",
41     "core.delete_userprofile",
42     "core.view_userprofile",
43     "core.view_event",
44     "core.add_event",
45     "core.change_event",
46     "core.delete_event",
47     "core.view_eventtype",
48     "core.add_eventtype",
49     "core.change_eventtype",
50     "core.delete_eventtype",
51     "core.view_qualification",
52     "core.add_qualification",
53     "core.change_qualification",
54     "core.delete_qualification",
55     "modellogging.view_logentry",
56 ]
57 
58 
59 def get_group_permission_log_fields(group):
60     # This lives here because it is closely related to the fields on GroupForm below
61     if not group.pk:
62         return {}
63     perms = set(group.permissions.values_list("codename", flat=True))
64 
65     return {
66         _("Can view past events"): "view_past_event" in perms,
67         _("Can add events"): "add_event" in perms,
68         _("Can edit users"): "change_userprofile" in perms,
69         _("Can manage ephios"): "change_group" in perms,
70         # force evaluation of querysets
71         _("Can publish events for groups"): set(
72             get_objects_for_group(group, "publish_event_for_group", klass=Group)
73         ),
74         _("Can decide working hours for groups"): set(
75             get_objects_for_group(group, "decide_workinghours_for_group", klass=Group)
76         ),
77     }
78 
79 
80 class GroupForm(PermissionFormMixin, ModelForm):
81     can_view_past_event = PermissionField(
82         label=_("Can view past events"), permissions=["core.view_past_event"], required=False
83     )
84 
85     is_planning_group = PermissionField(
86         label=_("Can add events"),
87         permissions=["core.add_event", "core.delete_event"],
88         required=False,
89     )
90     publish_event_for_group = ModelMultipleChoiceField(
91         label=_("Can publish events for groups"),
92         queryset=Group.objects.all(),
93         required=False,
94         help_text=_("Choose groups that this group can make events visible for."),
95         widget=Select2MultipleWidget,
96     )
97     decide_workinghours_for_group = ModelMultipleChoiceField(
98         label=_("Can decide working hours for groups"),
99         queryset=Group.objects.all(),
100         required=False,
101         help_text=_(
102             "Choose groups that the group you are currently editing can decide whether to grant working hours for."
103         ),
104         widget=Select2MultipleWidget,
105     )
106 
107     is_hr_group = PermissionField(
108         label=_("Can edit users"),
109         help_text=_(
110             "If checked, users in this group can view, add, edit and delete users. They can also manage group memberships for their own groups."
111         ),
112         permissions=[
113             "core.add_userprofile",
114             "core.change_userprofile",
115             "core.delete_userprofile",
116             "core.view_userprofile",
117         ],
118         required=False,
119     )
120     is_management_group = PermissionField(
121         label=_("Can manage ephios"),
122         help_text=_(
123             "If checked, users in this group can manage users, groups, all group memberships, eventtypes and qualifications"
124         ),
125         permissions=MANAGEMENT_PERMISSIONS,
126         required=False,
127     )
128 
129     users = ModelMultipleChoiceField(
130         label=_("Users"), queryset=UserProfile.objects.all(), widget=MultiUserProfileWidget
131     )
132 
133     class Meta:
134         model = Group
135         fields = ["name"]
136 
137     def __init__(self, **kwargs):
138         if (group := kwargs.get("instance", None)) is not None:
139             kwargs["initial"] = {
140                 "users": group.user_set.all(),
141                 "publish_event_for_group": get_objects_for_group(
142                     group, "publish_event_for_group", klass=Group
143                 ),
144                 "decide_workinghours_for_group": get_objects_for_group(
145                     group, "decide_workinghours_for_group", klass=Group
146                 ),
147                 **kwargs.get("initial", {}),
148             }
149             self.permission_target = group
150         super().__init__(**kwargs)
151         self.helper = FormHelper()
152         self.helper.layout = Layout(
153             Field("name"),
154             Field("users"),
155             Field("can_view_past_event"),
156             Fieldset(
157                 _("Management"),
158                 Field("is_hr_group", title="This permission is included with the management role."),
159                 "is_management_group",
160             ),
161             Fieldset(
162                 _("Planning"),
163                 Field(
164                     "is_planning_group",
165                     title="This permission is included with the management role.",
166                 ),
167                 Field("publish_event_for_group", wrapper_class="publish-select"),
168                 "decide_workinghours_for_group",
169             ),
170             FormActions(
171                 Submit("submit", _("Save"), css_class="float-end"),
172                 AbortLink(href=reverse("core:group_list")),
173             ),
174         )
175 
176     def save(self, commit=True):
177         add_log_recorder(self.instance, DerivedFieldsLogRecorder(get_group_permission_log_fields))
178         group = super().save(commit)
179 
180         group.user_set.set(self.cleaned_data["users"])
181 
182         remove_perm("publish_event_for_group", group, Group.objects.all())
183         if group.permissions.filter(codename="add_event").exists():
184             assign_perm(
185                 "publish_event_for_group", group, self.cleaned_data["publish_event_for_group"]
186             )
187 
188         if "decide_workinghours_for_group" in self.changed_data:
189             remove_perm("decide_workinghours_for_group", group, Group.objects.all())
190             assign_perm(
191                 "decide_workinghours_for_group",
192                 group,
193                 self.cleaned_data["decide_workinghours_for_group"],
194             )
195 
196         group.save()  # logging
197         return group
198 
199 
200 class UserProfileForm(ModelForm):
201     groups = ModelMultipleChoiceField(
202         label=_("Groups"),
203         queryset=Group.objects.all(),
204         widget=Select2MultipleWidget,
205         required=False,
206         disabled=True,  # explicitly enable for users with `change_group` permission
207     )
208 
209     def __init__(self, *args, **kwargs):
210         request = kwargs.pop("request")
211         super().__init__(*args, **kwargs)
212         self.locked_groups = set()
213         if request.user.has_perm("auth.change_group"):
214             self.fields["groups"].disabled = False
215         elif allowed_groups := request.user.groups:
216             self.fields["groups"].disabled = False
217             self.fields["groups"].queryset = allowed_groups
218             if self.instance.pk:
219                 self.locked_groups = set(self.instance.groups.exclude(id__in=allowed_groups.all()))
220             if self.locked_groups:
221                 self.fields["groups"].help_text = _(
222                     "The user is also member of <b>{groups}</b>, but you are not allowed to manage memberships for those groups."
223                 ).format(groups=", ".join((group.name for group in self.locked_groups)))
224 
225     field_order = [
226         "email",
227         "first_name",
228         "last_name",
229         "date_of_birth",
230         "phone",
231         "groups",
232         "is_active",
233     ]
234 
235     class Meta:
236         model = UserProfile
237         fields = ["email", "first_name", "last_name", "date_of_birth", "phone", "is_active"]
238         widgets = {"date_of_birth": CustomDateInput}
239         help_texts = {
240             "is_active": _("Inactive users cannot log in and do not get any notifications.")
241         }
242         labels = {"is_active": _("Active user")}
243 
244     def save(self, commit=True):
245         userprofile = super().save(commit)
246         userprofile.groups.set(
247             Group.objects.filter(
248                 Q(id__in=self.cleaned_data["groups"]) | Q(id__in=(g.id for g in self.locked_groups))
249             )
250         )
251         userprofile.save()
252         return userprofile
253 
254 
255 class QualificationGrantForm(ModelForm):
256     model = QualificationGrant
257 
258     class Meta:
259         fields = ["qualification", "expires"]
260         widgets = {"qualification": Select2Widget}
261 
262     def __init__(self, *args, **kwargs):
263         super().__init__(*args, **kwargs)
264         if hasattr(self, "instance") and self.instance.pk:
265             # Hide the field and simply display the qualification name in the template
266             self.fields["qualification"].disabled = True
267             self.fields["qualification"].widget = forms.HiddenInput()
268             self.fields["qualification"].title = self.instance.qualification.title
269 
270 
271 QualificationGrantFormset = inlineformset_factory(
272     UserProfile,
273     QualificationGrant,
274     form=QualificationGrantForm,
275     extra=0,
276 )
277 
278 
279 class QualificationGrantFormsetHelper(FormHelper):
280     def __init__(self, *args, **kwargs):
281         super().__init__(*args, **kwargs)
282         self.label_class = "col-md-4"
283         self.field_class = "col-md-8"
284 
285 
286 class WorkingHourRequestForm(Form):
287     when = DateField(widget=CustomDateInput, label=_("Date"))
288     hours = DecimalField(label=_("Hours of work"), min_value=0.5)
289     reason = CharField(label=_("Occasion"))
290 
291     def __init__(self, *args, **kwargs):
292         self.request = kwargs.pop("request")
293         super().__init__(*args, **kwargs)
294         self.helper = FormHelper(self)
295         self.helper.layout = Layout(
296             Field("when"),
297             Field("hours"),
298             Field("reason"),
299             FormActions(
300                 Submit("submit", _("Send"), css_class="float-end"),
301                 AbortLink(href=reverse("core:profile")),
302             ),
303         )
304 
305     def create_consequence(self):
306         WorkingHoursConsequenceHandler.create(
307             user=self.request.user,
308             when=self.cleaned_data["when"],
309             hours=float(self.cleaned_data["hours"]),
310             reason=self.cleaned_data["reason"],
311         )
312 
313 
314 class UserNotificationPreferenceForm(Form):
315     def __init__(self, *args, **kwargs):
316         self.user = kwargs.pop("user")
317         super().__init__(*args, **kwargs)
318 
319         preferences = self.user.preferences["notifications__notifications"]
320         for notification_type in enabled_notification_types():
321             if notification_type.unsubscribe_allowed:
322                 self.fields[notification_type.slug] = MultipleChoiceField(
323                     label=notification_type.title,
324                     choices=[
325                         (backend.slug, backend.title) for backend in enabled_notification_backends()
326                     ],
327                     initial=preferences.get(notification_type.slug, {}),
328                     widget=CheckboxSelectMultiple,
329                     required=False,
330                 )
331 
332     def update_preferences(self):
333         self.user.preferences["notifications__notifications"] = self.cleaned_data
334
```
--- 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/ephios/core/forms/users.py b/ephios/core/forms/users.py
--- a/ephios/core/forms/users.py
+++ b/ephios/core/forms/users.py
@@ -31,7 +31,7 @@
 from ephios.modellogging.log import add_log_recorder
 from ephios.modellogging.recorders import DerivedFieldsLogRecorder
 
-MANAGEMENT_PERMISSIONS = [
+CORE_MANAGEMENT_PERMISSIONS = [
     "auth.add_group",
     "auth.change_group",
     "auth.delete_group",
@@ -52,6 +52,7 @@
     "core.add_qualification",
     "core.change_qualification",
     "core.delete_qualification",
+    "auth.publish_event_for_group",
     "modellogging.view_logentry",
 ]
 
@@ -118,11 +119,11 @@
         required=False,
     )
     is_management_group = PermissionField(
-        label=_("Can manage ephios"),
+        label=_("Can manage permissions and qualifications"),
         help_text=_(
             "If checked, users in this group can manage users, groups, all group memberships, eventtypes and qualifications"
         ),
-        permissions=MANAGEMENT_PERMISSIONS,
+        permissions=CORE_MANAGEMENT_PERMISSIONS,
         required=False,
     )

{"golden_diff": "diff --git a/ephios/core/forms/users.py b/ephios/core/forms/users.py\n--- a/ephios/core/forms/users.py\n+++ b/ephios/core/forms/users.py\n@@ -31,7 +31,7 @@\n from ephios.modellogging.log import add_log_recorder\n from ephios.modellogging.recorders import DerivedFieldsLogRecorder\n \n-MANAGEMENT_PERMISSIONS = [\n+CORE_MANAGEMENT_PERMISSIONS = [\n     \"auth.add_group\",\n     \"auth.change_group\",\n     \"auth.delete_group\",\n@@ -52,6 +52,7 @@\n     \"core.add_qualification\",\n     \"core.change_qualification\",\n     \"core.delete_qualification\",\n+    \"auth.publish_event_for_group\",\n     \"modellogging.view_logentry\",\n ]\n \n@@ -118,11 +119,11 @@\n         required=False,\n     )\n     is_management_group = PermissionField(\n-        label=_(\"Can manage ephios\"),\n+        label=_(\"Can manage permissions and qualifications\"),\n         help_text=_(\n             \"If checked, users in this group can manage users, groups, all group memberships, eventtypes and qualifications\"\n         ),\n-        permissions=MANAGEMENT_PERMISSIONS,\n+        permissions=CORE_MANAGEMENT_PERMISSIONS,\n         required=False,\n     )\n", "issue": "Allow users with management permissions to make events visible for all groups\nAllow users with management permissions to make events visible for all groups by attributing the global permission instead of an object-level permission. Make sure that all views respect the global permission.\r\nAlso reword the label of the management checkbox on the GroupForm, as we are now using \"ephios instance\" as a label for the settings that require the is_staff flag\n", "before_files": [{"content": "from crispy_forms.bootstrap import FormActions\nfrom crispy_forms.helper import FormHelper\nfrom crispy_forms.layout import Field, Fieldset, Layout, Submit\nfrom django import forms\nfrom django.contrib.auth.models import Group\nfrom django.db.models import Q\nfrom django.forms import (\n    CharField,\n    CheckboxSelectMultiple,\n    DateField,\n    DecimalField,\n    Form,\n    ModelForm,\n    ModelMultipleChoiceField,\n    MultipleChoiceField,\n    inlineformset_factory,\n)\nfrom django.urls import reverse\nfrom django.utils.translation import gettext as _\nfrom django_select2.forms import Select2MultipleWidget, Select2Widget\nfrom guardian.shortcuts import assign_perm, get_objects_for_group, remove_perm\n\nfrom ephios.core.consequences import WorkingHoursConsequenceHandler\nfrom ephios.core.models import QualificationGrant, UserProfile\nfrom ephios.core.services.notifications.backends import enabled_notification_backends\nfrom ephios.core.services.notifications.types import enabled_notification_types\nfrom ephios.core.widgets import MultiUserProfileWidget\nfrom ephios.extra.crispy import AbortLink\nfrom ephios.extra.permissions import PermissionField, PermissionFormMixin\nfrom ephios.extra.widgets import CustomDateInput\nfrom ephios.modellogging.log import add_log_recorder\nfrom ephios.modellogging.recorders import DerivedFieldsLogRecorder\n\nMANAGEMENT_PERMISSIONS = [\n    \"auth.add_group\",\n    \"auth.change_group\",\n    \"auth.delete_group\",\n    \"auth.view_group\",\n    \"core.add_userprofile\",\n    \"core.change_userprofile\",\n    \"core.delete_userprofile\",\n    \"core.view_userprofile\",\n    \"core.view_event\",\n    \"core.add_event\",\n    \"core.change_event\",\n    \"core.delete_event\",\n    \"core.view_eventtype\",\n    \"core.add_eventtype\",\n    \"core.change_eventtype\",\n    \"core.delete_eventtype\",\n    \"core.view_qualification\",\n    \"core.add_qualification\",\n    \"core.change_qualification\",\n    \"core.delete_qualification\",\n    \"modellogging.view_logentry\",\n]\n\n\ndef get_group_permission_log_fields(group):\n    # This lives here because it is closely related to the fields on GroupForm below\n    if not group.pk:\n        return {}\n    perms = set(group.permissions.values_list(\"codename\", flat=True))\n\n    return {\n        _(\"Can view past events\"): \"view_past_event\" in perms,\n        _(\"Can add events\"): \"add_event\" in perms,\n        _(\"Can edit users\"): \"change_userprofile\" in perms,\n        _(\"Can manage ephios\"): \"change_group\" in perms,\n        # force evaluation of querysets\n        _(\"Can publish events for groups\"): set(\n            get_objects_for_group(group, \"publish_event_for_group\", klass=Group)\n        ),\n        _(\"Can decide working hours for groups\"): set(\n            get_objects_for_group(group, \"decide_workinghours_for_group\", klass=Group)\n        ),\n    }\n\n\nclass GroupForm(PermissionFormMixin, ModelForm):\n    can_view_past_event = PermissionField(\n        label=_(\"Can view past events\"), permissions=[\"core.view_past_event\"], required=False\n    )\n\n    is_planning_group = PermissionField(\n        label=_(\"Can add events\"),\n        permissions=[\"core.add_event\", \"core.delete_event\"],\n        required=False,\n    )\n    publish_event_for_group = ModelMultipleChoiceField(\n        label=_(\"Can publish events for groups\"),\n        queryset=Group.objects.all(),\n        required=False,\n        help_text=_(\"Choose groups that this group can make events visible for.\"),\n        widget=Select2MultipleWidget,\n    )\n    decide_workinghours_for_group = ModelMultipleChoiceField(\n        label=_(\"Can decide working hours for groups\"),\n        queryset=Group.objects.all(),\n        required=False,\n        help_text=_(\n            \"Choose groups that the group you are currently editing can decide whether to grant working hours for.\"\n        ),\n        widget=Select2MultipleWidget,\n    )\n\n    is_hr_group = PermissionField(\n        label=_(\"Can edit users\"),\n        help_text=_(\n            \"If checked, users in this group can view, add, edit and delete users. They can also manage group memberships for their own groups.\"\n        ),\n        permissions=[\n            \"core.add_userprofile\",\n            \"core.change_userprofile\",\n            \"core.delete_userprofile\",\n            \"core.view_userprofile\",\n        ],\n        required=False,\n    )\n    is_management_group = PermissionField(\n        label=_(\"Can manage ephios\"),\n        help_text=_(\n            \"If checked, users in this group can manage users, groups, all group memberships, eventtypes and qualifications\"\n        ),\n        permissions=MANAGEMENT_PERMISSIONS,\n        required=False,\n    )\n\n    users = ModelMultipleChoiceField(\n        label=_(\"Users\"), queryset=UserProfile.objects.all(), widget=MultiUserProfileWidget\n    )\n\n    class Meta:\n        model = Group\n        fields = [\"name\"]\n\n    def __init__(self, **kwargs):\n        if (group := kwargs.get(\"instance\", None)) is not None:\n            kwargs[\"initial\"] = {\n                \"users\": group.user_set.all(),\n                \"publish_event_for_group\": get_objects_for_group(\n                    group, \"publish_event_for_group\", klass=Group\n                ),\n                \"decide_workinghours_for_group\": get_objects_for_group(\n                    group, \"decide_workinghours_for_group\", klass=Group\n                ),\n                **kwargs.get(\"initial\", {}),\n            }\n            self.permission_target = group\n        super().__init__(**kwargs)\n        self.helper = FormHelper()\n        self.helper.layout = Layout(\n            Field(\"name\"),\n            Field(\"users\"),\n            Field(\"can_view_past_event\"),\n            Fieldset(\n                _(\"Management\"),\n                Field(\"is_hr_group\", title=\"This permission is included with the management role.\"),\n                \"is_management_group\",\n            ),\n            Fieldset(\n                _(\"Planning\"),\n                Field(\n                    \"is_planning_group\",\n                    title=\"This permission is included with the management role.\",\n                ),\n                Field(\"publish_event_for_group\", wrapper_class=\"publish-select\"),\n                \"decide_workinghours_for_group\",\n            ),\n            FormActions(\n                Submit(\"submit\", _(\"Save\"), css_class=\"float-end\"),\n                AbortLink(href=reverse(\"core:group_list\")),\n            ),\n        )\n\n    def save(self, commit=True):\n        add_log_recorder(self.instance, DerivedFieldsLogRecorder(get_group_permission_log_fields))\n        group = super().save(commit)\n\n        group.user_set.set(self.cleaned_data[\"users\"])\n\n        remove_perm(\"publish_event_for_group\", group, Group.objects.all())\n        if group.permissions.filter(codename=\"add_event\").exists():\n            assign_perm(\n                \"publish_event_for_group\", group, self.cleaned_data[\"publish_event_for_group\"]\n            )\n\n        if \"decide_workinghours_for_group\" in self.changed_data:\n            remove_perm(\"decide_workinghours_for_group\", group, Group.objects.all())\n            assign_perm(\n                \"decide_workinghours_for_group\",\n                group,\n                self.cleaned_data[\"decide_workinghours_for_group\"],\n            )\n\n        group.save()  # logging\n        return group\n\n\nclass UserProfileForm(ModelForm):\n    groups = ModelMultipleChoiceField(\n        label=_(\"Groups\"),\n        queryset=Group.objects.all(),\n        widget=Select2MultipleWidget,\n        required=False,\n        disabled=True,  # explicitly enable for users with `change_group` permission\n    )\n\n    def __init__(self, *args, **kwargs):\n        request = kwargs.pop(\"request\")\n        super().__init__(*args, **kwargs)\n        self.locked_groups = set()\n        if request.user.has_perm(\"auth.change_group\"):\n            self.fields[\"groups\"].disabled = False\n        elif allowed_groups := request.user.groups:\n            self.fields[\"groups\"].disabled = False\n            self.fields[\"groups\"].queryset = allowed_groups\n            if self.instance.pk:\n                self.locked_groups = set(self.instance.groups.exclude(id__in=allowed_groups.all()))\n            if self.locked_groups:\n                self.fields[\"groups\"].help_text = _(\n                    \"The user is also member of <b>{groups}</b>, but you are not allowed to manage memberships for those groups.\"\n                ).format(groups=\", \".join((group.name for group in self.locked_groups)))\n\n    field_order = [\n        \"email\",\n        \"first_name\",\n        \"last_name\",\n        \"date_of_birth\",\n        \"phone\",\n        \"groups\",\n        \"is_active\",\n    ]\n\n    class Meta:\n        model = UserProfile\n        fields = [\"email\", \"first_name\", \"last_name\", \"date_of_birth\", \"phone\", \"is_active\"]\n        widgets = {\"date_of_birth\": CustomDateInput}\n        help_texts = {\n            \"is_active\": _(\"Inactive users cannot log in and do not get any notifications.\")\n        }\n        labels = {\"is_active\": _(\"Active user\")}\n\n    def save(self, commit=True):\n        userprofile = super().save(commit)\n        userprofile.groups.set(\n            Group.objects.filter(\n                Q(id__in=self.cleaned_data[\"groups\"]) | Q(id__in=(g.id for g in self.locked_groups))\n            )\n        )\n        userprofile.save()\n        return userprofile\n\n\nclass QualificationGrantForm(ModelForm):\n    model = QualificationGrant\n\n    class Meta:\n        fields = [\"qualification\", \"expires\"]\n        widgets = {\"qualification\": Select2Widget}\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        if hasattr(self, \"instance\") and self.instance.pk:\n            # Hide the field and simply display the qualification name in the template\n            self.fields[\"qualification\"].disabled = True\n            self.fields[\"qualification\"].widget = forms.HiddenInput()\n            self.fields[\"qualification\"].title = self.instance.qualification.title\n\n\nQualificationGrantFormset = inlineformset_factory(\n    UserProfile,\n    QualificationGrant,\n    form=QualificationGrantForm,\n    extra=0,\n)\n\n\nclass QualificationGrantFormsetHelper(FormHelper):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.label_class = \"col-md-4\"\n        self.field_class = \"col-md-8\"\n\n\nclass WorkingHourRequestForm(Form):\n    when = DateField(widget=CustomDateInput, label=_(\"Date\"))\n    hours = DecimalField(label=_(\"Hours of work\"), min_value=0.5)\n    reason = CharField(label=_(\"Occasion\"))\n\n    def __init__(self, *args, **kwargs):\n        self.request = kwargs.pop(\"request\")\n        super().__init__(*args, **kwargs)\n        self.helper = FormHelper(self)\n        self.helper.layout = Layout(\n            Field(\"when\"),\n            Field(\"hours\"),\n            Field(\"reason\"),\n            FormActions(\n                Submit(\"submit\", _(\"Send\"), css_class=\"float-end\"),\n                AbortLink(href=reverse(\"core:profile\")),\n            ),\n        )\n\n    def create_consequence(self):\n        WorkingHoursConsequenceHandler.create(\n            user=self.request.user,\n            when=self.cleaned_data[\"when\"],\n            hours=float(self.cleaned_data[\"hours\"]),\n            reason=self.cleaned_data[\"reason\"],\n        )\n\n\nclass UserNotificationPreferenceForm(Form):\n    def __init__(self, *args, **kwargs):\n        self.user = kwargs.pop(\"user\")\n        super().__init__(*args, **kwargs)\n\n        preferences = self.user.preferences[\"notifications__notifications\"]\n        for notification_type in enabled_notification_types():\n            if notification_type.unsubscribe_allowed:\n                self.fields[notification_type.slug] = MultipleChoiceField(\n                    label=notification_type.title,\n                    choices=[\n                        (backend.slug, backend.title) for backend in enabled_notification_backends()\n                    ],\n                    initial=preferences.get(notification_type.slug, {}),\n                    widget=CheckboxSelectMultiple,\n                    required=False,\n                )\n\n    def update_preferences(self):\n        self.user.preferences[\"notifications__notifications\"] = self.cleaned_data\n", "path": "ephios/core/forms/users.py"}], "after_files": [{"content": "from crispy_forms.bootstrap import FormActions\nfrom crispy_forms.helper import FormHelper\nfrom crispy_forms.layout import Field, Fieldset, Layout, Submit\nfrom django import forms\nfrom django.contrib.auth.models import Group\nfrom django.db.models import Q\nfrom django.forms import (\n    CharField,\n    CheckboxSelectMultiple,\n    DateField,\n    DecimalField,\n    Form,\n    ModelForm,\n    ModelMultipleChoiceField,\n    MultipleChoiceField,\n    inlineformset_factory,\n)\nfrom django.urls import reverse\nfrom django.utils.translation import gettext as _\nfrom django_select2.forms import Select2MultipleWidget, Select2Widget\nfrom guardian.shortcuts import assign_perm, get_objects_for_group, remove_perm\n\nfrom ephios.core.consequences import WorkingHoursConsequenceHandler\nfrom ephios.core.models import QualificationGrant, UserProfile\nfrom ephios.core.services.notifications.backends import enabled_notification_backends\nfrom ephios.core.services.notifications.types import enabled_notification_types\nfrom ephios.core.widgets import MultiUserProfileWidget\nfrom ephios.extra.crispy import AbortLink\nfrom ephios.extra.permissions import PermissionField, PermissionFormMixin\nfrom ephios.extra.widgets import CustomDateInput\nfrom ephios.modellogging.log import add_log_recorder\nfrom ephios.modellogging.recorders import DerivedFieldsLogRecorder\n\nCORE_MANAGEMENT_PERMISSIONS = [\n    \"auth.add_group\",\n    \"auth.change_group\",\n    \"auth.delete_group\",\n    \"auth.view_group\",\n    \"core.add_userprofile\",\n    \"core.change_userprofile\",\n    \"core.delete_userprofile\",\n    \"core.view_userprofile\",\n    \"core.view_event\",\n    \"core.add_event\",\n    \"core.change_event\",\n    \"core.delete_event\",\n    \"core.view_eventtype\",\n    \"core.add_eventtype\",\n    \"core.change_eventtype\",\n    \"core.delete_eventtype\",\n    \"core.view_qualification\",\n    \"core.add_qualification\",\n    \"core.change_qualification\",\n    \"core.delete_qualification\",\n    \"auth.publish_event_for_group\",\n    \"modellogging.view_logentry\",\n]\n\n\ndef get_group_permission_log_fields(group):\n    # This lives here because it is closely related to the fields on GroupForm below\n    if not group.pk:\n        return {}\n    perms = set(group.permissions.values_list(\"codename\", flat=True))\n\n    return {\n        _(\"Can view past events\"): \"view_past_event\" in perms,\n        _(\"Can add events\"): \"add_event\" in perms,\n        _(\"Can edit users\"): \"change_userprofile\" in perms,\n        _(\"Can manage ephios\"): \"change_group\" in perms,\n        # force evaluation of querysets\n        _(\"Can publish events for groups\"): set(\n            get_objects_for_group(group, \"publish_event_for_group\", klass=Group)\n        ),\n        _(\"Can decide working hours for groups\"): set(\n            get_objects_for_group(group, \"decide_workinghours_for_group\", klass=Group)\n        ),\n    }\n\n\nclass GroupForm(PermissionFormMixin, ModelForm):\n    can_view_past_event = PermissionField(\n        label=_(\"Can view past events\"), permissions=[\"core.view_past_event\"], required=False\n    )\n\n    is_planning_group = PermissionField(\n        label=_(\"Can add events\"),\n        permissions=[\"core.add_event\", \"core.delete_event\"],\n        required=False,\n    )\n    publish_event_for_group = ModelMultipleChoiceField(\n        label=_(\"Can publish events for groups\"),\n        queryset=Group.objects.all(),\n        required=False,\n        help_text=_(\"Choose groups that this group can make events visible for.\"),\n        widget=Select2MultipleWidget,\n    )\n    decide_workinghours_for_group = ModelMultipleChoiceField(\n        label=_(\"Can decide working hours for groups\"),\n        queryset=Group.objects.all(),\n        required=False,\n        help_text=_(\n            \"Choose groups that the group you are currently editing can decide whether to grant working hours for.\"\n        ),\n        widget=Select2MultipleWidget,\n    )\n\n    is_hr_group = PermissionField(\n        label=_(\"Can edit users\"),\n        help_text=_(\n            \"If checked, users in this group can view, add, edit and delete users. They can also manage group memberships for their own groups.\"\n        ),\n        permissions=[\n            \"core.add_userprofile\",\n            \"core.change_userprofile\",\n            \"core.delete_userprofile\",\n            \"core.view_userprofile\",\n        ],\n        required=False,\n    )\n    is_management_group = PermissionField(\n        label=_(\"Can manage permissions and qualifications\"),\n        help_text=_(\n            \"If checked, users in this group can manage users, groups, all group memberships, eventtypes and qualifications\"\n        ),\n        permissions=CORE_MANAGEMENT_PERMISSIONS,\n        required=False,\n    )\n\n    users = ModelMultipleChoiceField(\n        label=_(\"Users\"), queryset=UserProfile.objects.all(), widget=MultiUserProfileWidget\n    )\n\n    class Meta:\n        model = Group\n        fields = [\"name\"]\n\n    def __init__(self, **kwargs):\n        if (group := kwargs.get(\"instance\", None)) is not None:\n            kwargs[\"initial\"] = {\n                \"users\": group.user_set.all(),\n                \"publish_event_for_group\": get_objects_for_group(\n                    group, \"publish_event_for_group\", klass=Group\n                ),\n                \"decide_workinghours_for_group\": get_objects_for_group(\n                    group, \"decide_workinghours_for_group\", klass=Group\n                ),\n                **kwargs.get(\"initial\", {}),\n            }\n            self.permission_target = group\n        super().__init__(**kwargs)\n        self.helper = FormHelper()\n        self.helper.layout = Layout(\n            Field(\"name\"),\n            Field(\"users\"),\n            Field(\"can_view_past_event\"),\n            Fieldset(\n                _(\"Management\"),\n                Field(\"is_hr_group\", title=\"This permission is included with the management role.\"),\n                \"is_management_group\",\n            ),\n            Fieldset(\n                _(\"Planning\"),\n                Field(\n                    \"is_planning_group\",\n                    title=\"This permission is included with the management role.\",\n                ),\n                Field(\"publish_event_for_group\", wrapper_class=\"publish-select\"),\n                \"decide_workinghours_for_group\",\n            ),\n            FormActions(\n                Submit(\"submit\", _(\"Save\"), css_class=\"float-end\"),\n                AbortLink(href=reverse(\"core:group_list\")),\n            ),\n        )\n\n    def save(self, commit=True):\n        add_log_recorder(self.instance, DerivedFieldsLogRecorder(get_group_permission_log_fields))\n        group = super().save(commit)\n\n        group.user_set.set(self.cleaned_data[\"users\"])\n\n        remove_perm(\"publish_event_for_group\", group, Group.objects.all())\n        if group.permissions.filter(codename=\"add_event\").exists():\n            assign_perm(\n                \"publish_event_for_group\", group, self.cleaned_data[\"publish_event_for_group\"]\n            )\n\n        if \"decide_workinghours_for_group\" in self.changed_data:\n            remove_perm(\"decide_workinghours_for_group\", group, Group.objects.all())\n            assign_perm(\n                \"decide_workinghours_for_group\",\n                group,\n                self.cleaned_data[\"decide_workinghours_for_group\"],\n            )\n\n        group.save()  # logging\n        return group\n\n\nclass UserProfileForm(ModelForm):\n    groups = ModelMultipleChoiceField(\n        label=_(\"Groups\"),\n        queryset=Group.objects.all(),\n        widget=Select2MultipleWidget,\n        required=False,\n        disabled=True,  # explicitly enable for users with `change_group` permission\n    )\n\n    def __init__(self, *args, **kwargs):\n        request = kwargs.pop(\"request\")\n        super().__init__(*args, **kwargs)\n        self.locked_groups = set()\n        if request.user.has_perm(\"auth.change_group\"):\n            self.fields[\"groups\"].disabled = False\n        elif allowed_groups := request.user.groups:\n            self.fields[\"groups\"].disabled = False\n            self.fields[\"groups\"].queryset = allowed_groups\n            if self.instance.pk:\n                self.locked_groups = set(self.instance.groups.exclude(id__in=allowed_groups.all()))\n            if self.locked_groups:\n                self.fields[\"groups\"].help_text = _(\n                    \"The user is also member of <b>{groups}</b>, but you are not allowed to manage memberships for those groups.\"\n                ).format(groups=\", \".join((group.name for group in self.locked_groups)))\n\n    field_order = [\n        \"email\",\n        \"first_name\",\n        \"last_name\",\n        \"date_of_birth\",\n        \"phone\",\n        \"groups\",\n        \"is_active\",\n    ]\n\n    class Meta:\n        model = UserProfile\n        fields = [\"email\", \"first_name\", \"last_name\", \"date_of_birth\", \"phone\", \"is_active\"]\n        widgets = {\"date_of_birth\": CustomDateInput}\n        help_texts = {\n            \"is_active\": _(\"Inactive users cannot log in and do not get any notifications.\")\n        }\n        labels = {\"is_active\": _(\"Active user\")}\n\n    def save(self, commit=True):\n        userprofile = super().save(commit)\n        userprofile.groups.set(\n            Group.objects.filter(\n                Q(id__in=self.cleaned_data[\"groups\"]) | Q(id__in=(g.id for g in self.locked_groups))\n            )\n        )\n        userprofile.save()\n        return userprofile\n\n\nclass QualificationGrantForm(ModelForm):\n    model = QualificationGrant\n\n    class Meta:\n        fields = [\"qualification\", \"expires\"]\n        widgets = {\"qualification\": Select2Widget}\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        if hasattr(self, \"instance\") and self.instance.pk:\n            # Hide the field and simply display the qualification name in the template\n            self.fields[\"qualification\"].disabled = True\n            self.fields[\"qualification\"].widget = forms.HiddenInput()\n            self.fields[\"qualification\"].title = self.instance.qualification.title\n\n\nQualificationGrantFormset = inlineformset_factory(\n    UserProfile,\n    QualificationGrant,\n    form=QualificationGrantForm,\n    extra=0,\n)\n\n\nclass QualificationGrantFormsetHelper(FormHelper):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.label_class = \"col-md-4\"\n        self.field_class = \"col-md-8\"\n\n\nclass WorkingHourRequestForm(Form):\n    when = DateField(widget=CustomDateInput, label=_(\"Date\"))\n    hours = DecimalField(label=_(\"Hours of work\"), min_value=0.5)\n    reason = CharField(label=_(\"Occasion\"))\n\n    def __init__(self, *args, **kwargs):\n        self.request = kwargs.pop(\"request\")\n        super().__init__(*args, **kwargs)\n        self.helper = FormHelper(self)\n        self.helper.layout = Layout(\n            Field(\"when\"),\n            Field(\"hours\"),\n            Field(\"reason\"),\n            FormActions(\n                Submit(\"submit\", _(\"Send\"), css_class=\"float-end\"),\n                AbortLink(href=reverse(\"core:profile\")),\n            ),\n        )\n\n    def create_consequence(self):\n        WorkingHoursConsequenceHandler.create(\n            user=self.request.user,\n            when=self.cleaned_data[\"when\"],\n            hours=float(self.cleaned_data[\"hours\"]),\n            reason=self.cleaned_data[\"reason\"],\n        )\n\n\nclass UserNotificationPreferenceForm(Form):\n    def __init__(self, *args, **kwargs):\n        self.user = kwargs.pop(\"user\")\n        super().__init__(*args, **kwargs)\n\n        preferences = self.user.preferences[\"notifications__notifications\"]\n        for notification_type in enabled_notification_types():\n            if notification_type.unsubscribe_allowed:\n                self.fields[notification_type.slug] = MultipleChoiceField(\n                    label=notification_type.title,\n                    choices=[\n                        (backend.slug, backend.title) for backend in enabled_notification_backends()\n                    ],\n                    initial=preferences.get(notification_type.slug, {}),\n                    widget=CheckboxSelectMultiple,\n                    required=False,\n                )\n\n    def update_preferences(self):\n        self.user.preferences[\"notifications__notifications\"] = self.cleaned_data\n", "path": "ephios/core/forms/users.py"}]}