We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Filter working hours by type
As a planner, I want to filter the working hours by event type or manual working hours.

--- 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/views/workinghour.py`
Content:
```
1 import datetime
2 from collections import Counter
3 from datetime import date
4 from itertools import chain
5 from typing import Optional
6 
7 from django import forms
8 from django.contrib import messages
9 from django.contrib.auth.mixins import LoginRequiredMixin
10 from django.contrib.auth.models import Group
11 from django.contrib.messages.views import SuccessMessageMixin
12 from django.db.models import DurationField, ExpressionWrapper, F, Sum
13 from django.shortcuts import get_object_or_404, redirect
14 from django.urls import reverse
15 from django.utils.functional import cached_property
16 from django.utils.translation import gettext as _
17 from django.views.generic import DeleteView, DetailView, FormView, TemplateView, UpdateView
18 from guardian.shortcuts import get_objects_for_user
19 
20 from ephios.core.forms.users import WorkingHourRequestForm
21 from ephios.core.models import LocalParticipation, UserProfile, WorkingHours
22 from ephios.extra.mixins import CustomPermissionRequiredMixin
23 from ephios.extra.widgets import CustomDateInput
24 
25 
26 class CanGrantMixin:
27     @cached_property
28     def can_grant(self):
29         """
30         Return whether the current request user can grant
31         working hours to the target user (which might be themselves).
32         """
33         if self.request.user.is_anonymous:
34             return False
35         can_grant_for_groups = get_objects_for_user(
36             self.request.user, "decide_workinghours_for_group", klass=Group
37         )
38         return self._get_target_user().groups.filter(id__in=can_grant_for_groups).exists()
39 
40     def _get_target_user(self):
41         """Return the user whose working hours are being managed."""
42         return get_object_or_404(UserProfile, pk=self.kwargs["pk"])
43 
44 
45 class DateFilterForm(forms.Form):
46     start = forms.DateField(required=False, label=_("From"), widget=CustomDateInput)
47     end = forms.DateField(required=False, label=_("To"), widget=CustomDateInput)
48 
49 
50 class WorkingHourOverview(CustomPermissionRequiredMixin, TemplateView):
51     template_name = "core/workinghours_list.html"
52     permission_required = "core.view_userprofile"
53 
54     def _get_working_hours_stats(self, start: Optional[date], end: Optional[date]):
55         participations = (
56             LocalParticipation.objects.filter(
57                 state=LocalParticipation.States.CONFIRMED,
58                 start_time__date__gte=start,
59                 end_time__date__lte=end,
60             )
61             .annotate(
62                 duration=ExpressionWrapper(
63                     (F("end_time") - F("start_time")),
64                     output_field=DurationField(),
65                 ),
66             )
67             .annotate(hour_sum=Sum("duration"))
68             .values_list("user__pk", "user__display_name", "hour_sum")
69         )
70         workinghours = (
71             WorkingHours.objects.filter(date__gte=start, date__lte=end)
72             .annotate(hour_sum=Sum("hours"))
73             .values_list("user__pk", "user__display_name", "hour_sum")
74         )
75         result = {}
76         c = Counter()
77         for user_pk, display_name, hours in chain(participations, workinghours):
78             current_sum = (
79                 hours.total_seconds() / (60 * 60)
80                 if isinstance(hours, datetime.timedelta)
81                 else float(hours)
82             )
83             c[user_pk] += current_sum
84             result[user_pk] = {
85                 "pk": user_pk,
86                 "display_name": display_name,
87                 "hours": c[user_pk],
88             }
89         return sorted(result.values(), key=lambda x: x["hours"], reverse=True)
90 
91     def get_context_data(self, **kwargs):
92         filter_form = DateFilterForm(
93             self.request.GET
94             or {
95                 # intitial data for initial page laod
96                 # (does not use `initial` cause that only works with unbound forms)
97                 "start": date.today().replace(month=1, day=1),
98                 "end": date.today().replace(month=12, day=31),
99             }
100         )
101         filter_form.is_valid()
102         kwargs["filter_form"] = filter_form
103         kwargs["users"] = self._get_working_hours_stats(
104             start=filter_form.cleaned_data.get("start") or date.min,  # start/end are not required
105             end=filter_form.cleaned_data.get("end") or date.max,
106         )
107         kwargs["can_grant_for"] = set(
108             get_objects_for_user(self.request.user, "decide_workinghours_for_group", klass=Group)
109         )
110         kwargs["groups_by_user"] = {
111             profile.pk: set(profile.groups.all())
112             for profile in UserProfile.objects.all().prefetch_related("groups")
113         }
114         return super().get_context_data(**kwargs)
115 
116 
117 class OwnWorkingHourView(LoginRequiredMixin, DetailView):
118     template_name = "core/userprofile_workinghours.html"
119 
120     def get_object(self, queryset=None):
121         return self.request.user
122 
123     def get_context_data(self, **kwargs):
124         kwargs["own_profile"] = True
125         grant_ids = get_objects_for_user(
126             self.request.user, "decide_workinghours_for_group", klass=Group
127         ).values_list("id", flat=True)
128         kwargs["can_grant"] = self.request.user.groups.filter(id__in=grant_ids).exists()
129         return super().get_context_data(**kwargs)
130 
131 
132 class UserProfileWorkingHourView(CanGrantMixin, CustomPermissionRequiredMixin, DetailView):
133     model = UserProfile
134     permission_required = "core.view_userprofile"
135     template_name = "core/userprofile_workinghours.html"
136 
137     def get_context_data(self, **kwargs):
138         kwargs["can_grant"] = self.can_grant
139         return super().get_context_data(**kwargs)
140 
141 
142 class WorkingHourRequestView(LoginRequiredMixin, FormView):
143     form_class = WorkingHourRequestForm
144     template_name = "core/workinghours_form.html"
145 
146     def get_form_kwargs(self):
147         kwargs = super().get_form_kwargs()
148         kwargs["user"] = self.request.user
149         return kwargs
150 
151     def form_valid(self, form):
152         form.create_consequence()
153         messages.success(self.request, _("Your request has been submitted."))
154         return redirect(reverse("core:workinghours_own"))
155 
156 
157 class WorkingHourCreateView(CanGrantMixin, CustomPermissionRequiredMixin, WorkingHourRequestView):
158     def has_permission(self):
159         return self.can_grant
160 
161     def get_form_kwargs(self):
162         kwargs = super().get_form_kwargs()
163         kwargs["can_grant"] = True
164         kwargs["user"] = self._get_target_user()
165         return kwargs
166 
167     def form_valid(self, form):
168         workinghour = form.save(commit=False)
169         workinghour.user = form.user
170         workinghour.save()
171         messages.success(self.request, _("Working hours have been added."))
172         return redirect(reverse("core:workinghours_list"))
173 
174 
175 class WorkingHourUpdateView(CanGrantMixin, CustomPermissionRequiredMixin, UpdateView):
176     model = WorkingHours
177     form_class = WorkingHourRequestForm
178 
179     def has_permission(self):
180         return self.can_grant
181 
182     def get_success_url(self):
183         return reverse("core:workinghours_detail", kwargs={"pk": self.object.user.pk})
184 
185     def get_form_kwargs(self):
186         kwargs = super().get_form_kwargs()
187         kwargs["user"] = self.object.user
188         kwargs["can_grant"] = True
189         return kwargs
190 
191     def _get_target_user(self):
192         return get_object_or_404(WorkingHours, pk=self.kwargs["pk"]).user
193 
194 
195 class WorkingHourDeleteView(
196     CanGrantMixin, CustomPermissionRequiredMixin, SuccessMessageMixin, DeleteView
197 ):
198     permission_required = "core.decide_workinghours_for_group"
199     model = WorkingHours
200     success_message = _("Working hours have been deleted.")
201 
202     def _get_target_user(self):
203         return get_object_or_404(WorkingHours, pk=self.kwargs["pk"]).user
204 
205     def has_permission(self):
206         return self.can_grant
207 
208     def get_success_url(self):
209         return reverse("core:workinghours_detail", kwargs={"pk": self.object.user.pk})
210
```
--- 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/views/workinghour.py b/ephios/core/views/workinghour.py
--- a/ephios/core/views/workinghour.py
+++ b/ephios/core/views/workinghour.py
@@ -15,10 +15,11 @@
 from django.utils.functional import cached_property
 from django.utils.translation import gettext as _
 from django.views.generic import DeleteView, DetailView, FormView, TemplateView, UpdateView
+from django_select2.forms import Select2Widget
 from guardian.shortcuts import get_objects_for_user
 
 from ephios.core.forms.users import WorkingHourRequestForm
-from ephios.core.models import LocalParticipation, UserProfile, WorkingHours
+from ephios.core.models import EventType, LocalParticipation, UserProfile, WorkingHours
 from ephios.extra.mixins import CustomPermissionRequiredMixin
 from ephios.extra.widgets import CustomDateInput
 
@@ -42,23 +43,43 @@
         return get_object_or_404(UserProfile, pk=self.kwargs["pk"])
 
 
-class DateFilterForm(forms.Form):
+class WorkingHourFilterForm(forms.Form):
     start = forms.DateField(required=False, label=_("From"), widget=CustomDateInput)
     end = forms.DateField(required=False, label=_("To"), widget=CustomDateInput)
+    type = forms.ModelChoiceField(
+        label=_("Event type"),
+        queryset=EventType.objects.all(),
+        required=False,
+        widget=Select2Widget(
+            attrs={
+                "data-placeholder": _("Event type"),
+                "classes": "w-auto",
+            }
+        ),
+    )
 
 
 class WorkingHourOverview(CustomPermissionRequiredMixin, TemplateView):
     template_name = "core/workinghours_list.html"
     permission_required = "core.view_userprofile"
 
-    def _get_working_hours_stats(self, start: Optional[date], end: Optional[date]):
-        participations = (
-            LocalParticipation.objects.filter(
-                state=LocalParticipation.States.CONFIRMED,
-                start_time__date__gte=start,
-                end_time__date__lte=end,
+    def _get_working_hours_stats(self, start: date, end: date, eventtype: Optional[EventType]):
+        participations = LocalParticipation.objects.filter(
+            state=LocalParticipation.States.CONFIRMED,
+            start_time__date__gte=start,
+            end_time__date__lte=end,
+        )
+        workinghours = {}
+        if eventtype is not None:
+            participations = participations.filter(shift__event__type=eventtype)
+        else:
+            workinghours = (
+                WorkingHours.objects.filter(date__gte=start, date__lte=end)
+                .annotate(hour_sum=Sum("hours"))
+                .values_list("user__pk", "user__display_name", "hour_sum")
             )
-            .annotate(
+        participations = (
+            participations.annotate(
                 duration=ExpressionWrapper(
                     (F("end_time") - F("start_time")),
                     output_field=DurationField(),
@@ -67,11 +88,7 @@
             .annotate(hour_sum=Sum("duration"))
             .values_list("user__pk", "user__display_name", "hour_sum")
         )
-        workinghours = (
-            WorkingHours.objects.filter(date__gte=start, date__lte=end)
-            .annotate(hour_sum=Sum("hours"))
-            .values_list("user__pk", "user__display_name", "hour_sum")
-        )
+
         result = {}
         c = Counter()
         for user_pk, display_name, hours in chain(participations, workinghours):
@@ -89,7 +106,7 @@
         return sorted(result.values(), key=lambda x: x["hours"], reverse=True)
 
     def get_context_data(self, **kwargs):
-        filter_form = DateFilterForm(
+        filter_form = WorkingHourFilterForm(
             self.request.GET
             or {
                 # intitial data for initial page laod
@@ -103,6 +120,7 @@
         kwargs["users"] = self._get_working_hours_stats(
             start=filter_form.cleaned_data.get("start") or date.min,  # start/end are not required
             end=filter_form.cleaned_data.get("end") or date.max,
+            eventtype=filter_form.cleaned_data.get("type"),
         )
         kwargs["can_grant_for"] = set(
             get_objects_for_user(self.request.user, "decide_workinghours_for_group", klass=Group)

{"golden_diff": "diff --git a/ephios/core/views/workinghour.py b/ephios/core/views/workinghour.py\n--- a/ephios/core/views/workinghour.py\n+++ b/ephios/core/views/workinghour.py\n@@ -15,10 +15,11 @@\n from django.utils.functional import cached_property\n from django.utils.translation import gettext as _\n from django.views.generic import DeleteView, DetailView, FormView, TemplateView, UpdateView\n+from django_select2.forms import Select2Widget\n from guardian.shortcuts import get_objects_for_user\n \n from ephios.core.forms.users import WorkingHourRequestForm\n-from ephios.core.models import LocalParticipation, UserProfile, WorkingHours\n+from ephios.core.models import EventType, LocalParticipation, UserProfile, WorkingHours\n from ephios.extra.mixins import CustomPermissionRequiredMixin\n from ephios.extra.widgets import CustomDateInput\n \n@@ -42,23 +43,43 @@\n         return get_object_or_404(UserProfile, pk=self.kwargs[\"pk\"])\n \n \n-class DateFilterForm(forms.Form):\n+class WorkingHourFilterForm(forms.Form):\n     start = forms.DateField(required=False, label=_(\"From\"), widget=CustomDateInput)\n     end = forms.DateField(required=False, label=_(\"To\"), widget=CustomDateInput)\n+    type = forms.ModelChoiceField(\n+        label=_(\"Event type\"),\n+        queryset=EventType.objects.all(),\n+        required=False,\n+        widget=Select2Widget(\n+            attrs={\n+                \"data-placeholder\": _(\"Event type\"),\n+                \"classes\": \"w-auto\",\n+            }\n+        ),\n+    )\n \n \n class WorkingHourOverview(CustomPermissionRequiredMixin, TemplateView):\n     template_name = \"core/workinghours_list.html\"\n     permission_required = \"core.view_userprofile\"\n \n-    def _get_working_hours_stats(self, start: Optional[date], end: Optional[date]):\n-        participations = (\n-            LocalParticipation.objects.filter(\n-                state=LocalParticipation.States.CONFIRMED,\n-                start_time__date__gte=start,\n-                end_time__date__lte=end,\n+    def _get_working_hours_stats(self, start: date, end: date, eventtype: Optional[EventType]):\n+        participations = LocalParticipation.objects.filter(\n+            state=LocalParticipation.States.CONFIRMED,\n+            start_time__date__gte=start,\n+            end_time__date__lte=end,\n+        )\n+        workinghours = {}\n+        if eventtype is not None:\n+            participations = participations.filter(shift__event__type=eventtype)\n+        else:\n+            workinghours = (\n+                WorkingHours.objects.filter(date__gte=start, date__lte=end)\n+                .annotate(hour_sum=Sum(\"hours\"))\n+                .values_list(\"user__pk\", \"user__display_name\", \"hour_sum\")\n             )\n-            .annotate(\n+        participations = (\n+            participations.annotate(\n                 duration=ExpressionWrapper(\n                     (F(\"end_time\") - F(\"start_time\")),\n                     output_field=DurationField(),\n@@ -67,11 +88,7 @@\n             .annotate(hour_sum=Sum(\"duration\"))\n             .values_list(\"user__pk\", \"user__display_name\", \"hour_sum\")\n         )\n-        workinghours = (\n-            WorkingHours.objects.filter(date__gte=start, date__lte=end)\n-            .annotate(hour_sum=Sum(\"hours\"))\n-            .values_list(\"user__pk\", \"user__display_name\", \"hour_sum\")\n-        )\n+\n         result = {}\n         c = Counter()\n         for user_pk, display_name, hours in chain(participations, workinghours):\n@@ -89,7 +106,7 @@\n         return sorted(result.values(), key=lambda x: x[\"hours\"], reverse=True)\n \n     def get_context_data(self, **kwargs):\n-        filter_form = DateFilterForm(\n+        filter_form = WorkingHourFilterForm(\n             self.request.GET\n             or {\n                 # intitial data for initial page laod\n@@ -103,6 +120,7 @@\n         kwargs[\"users\"] = self._get_working_hours_stats(\n             start=filter_form.cleaned_data.get(\"start\") or date.min,  # start/end are not required\n             end=filter_form.cleaned_data.get(\"end\") or date.max,\n+            eventtype=filter_form.cleaned_data.get(\"type\"),\n         )\n         kwargs[\"can_grant_for\"] = set(\n             get_objects_for_user(self.request.user, \"decide_workinghours_for_group\", klass=Group)\n", "issue": "Filter working hours by type\nAs a planner, I want to filter the working hours by event type or manual working hours.\n", "before_files": [{"content": "import datetime\nfrom collections import Counter\nfrom datetime import date\nfrom itertools import chain\nfrom typing import Optional\n\nfrom django import forms\nfrom django.contrib import messages\nfrom django.contrib.auth.mixins import LoginRequiredMixin\nfrom django.contrib.auth.models import Group\nfrom django.contrib.messages.views import SuccessMessageMixin\nfrom django.db.models import DurationField, ExpressionWrapper, F, Sum\nfrom django.shortcuts import get_object_or_404, redirect\nfrom django.urls import reverse\nfrom django.utils.functional import cached_property\nfrom django.utils.translation import gettext as _\nfrom django.views.generic import DeleteView, DetailView, FormView, TemplateView, UpdateView\nfrom guardian.shortcuts import get_objects_for_user\n\nfrom ephios.core.forms.users import WorkingHourRequestForm\nfrom ephios.core.models import LocalParticipation, UserProfile, WorkingHours\nfrom ephios.extra.mixins import CustomPermissionRequiredMixin\nfrom ephios.extra.widgets import CustomDateInput\n\n\nclass CanGrantMixin:\n    @cached_property\n    def can_grant(self):\n        \"\"\"\n        Return whether the current request user can grant\n        working hours to the target user (which might be themselves).\n        \"\"\"\n        if self.request.user.is_anonymous:\n            return False\n        can_grant_for_groups = get_objects_for_user(\n            self.request.user, \"decide_workinghours_for_group\", klass=Group\n        )\n        return self._get_target_user().groups.filter(id__in=can_grant_for_groups).exists()\n\n    def _get_target_user(self):\n        \"\"\"Return the user whose working hours are being managed.\"\"\"\n        return get_object_or_404(UserProfile, pk=self.kwargs[\"pk\"])\n\n\nclass DateFilterForm(forms.Form):\n    start = forms.DateField(required=False, label=_(\"From\"), widget=CustomDateInput)\n    end = forms.DateField(required=False, label=_(\"To\"), widget=CustomDateInput)\n\n\nclass WorkingHourOverview(CustomPermissionRequiredMixin, TemplateView):\n    template_name = \"core/workinghours_list.html\"\n    permission_required = \"core.view_userprofile\"\n\n    def _get_working_hours_stats(self, start: Optional[date], end: Optional[date]):\n        participations = (\n            LocalParticipation.objects.filter(\n                state=LocalParticipation.States.CONFIRMED,\n                start_time__date__gte=start,\n                end_time__date__lte=end,\n            )\n            .annotate(\n                duration=ExpressionWrapper(\n                    (F(\"end_time\") - F(\"start_time\")),\n                    output_field=DurationField(),\n                ),\n            )\n            .annotate(hour_sum=Sum(\"duration\"))\n            .values_list(\"user__pk\", \"user__display_name\", \"hour_sum\")\n        )\n        workinghours = (\n            WorkingHours.objects.filter(date__gte=start, date__lte=end)\n            .annotate(hour_sum=Sum(\"hours\"))\n            .values_list(\"user__pk\", \"user__display_name\", \"hour_sum\")\n        )\n        result = {}\n        c = Counter()\n        for user_pk, display_name, hours in chain(participations, workinghours):\n            current_sum = (\n                hours.total_seconds() / (60 * 60)\n                if isinstance(hours, datetime.timedelta)\n                else float(hours)\n            )\n            c[user_pk] += current_sum\n            result[user_pk] = {\n                \"pk\": user_pk,\n                \"display_name\": display_name,\n                \"hours\": c[user_pk],\n            }\n        return sorted(result.values(), key=lambda x: x[\"hours\"], reverse=True)\n\n    def get_context_data(self, **kwargs):\n        filter_form = DateFilterForm(\n            self.request.GET\n            or {\n                # intitial data for initial page laod\n                # (does not use `initial` cause that only works with unbound forms)\n                \"start\": date.today().replace(month=1, day=1),\n                \"end\": date.today().replace(month=12, day=31),\n            }\n        )\n        filter_form.is_valid()\n        kwargs[\"filter_form\"] = filter_form\n        kwargs[\"users\"] = self._get_working_hours_stats(\n            start=filter_form.cleaned_data.get(\"start\") or date.min,  # start/end are not required\n            end=filter_form.cleaned_data.get(\"end\") or date.max,\n        )\n        kwargs[\"can_grant_for\"] = set(\n            get_objects_for_user(self.request.user, \"decide_workinghours_for_group\", klass=Group)\n        )\n        kwargs[\"groups_by_user\"] = {\n            profile.pk: set(profile.groups.all())\n            for profile in UserProfile.objects.all().prefetch_related(\"groups\")\n        }\n        return super().get_context_data(**kwargs)\n\n\nclass OwnWorkingHourView(LoginRequiredMixin, DetailView):\n    template_name = \"core/userprofile_workinghours.html\"\n\n    def get_object(self, queryset=None):\n        return self.request.user\n\n    def get_context_data(self, **kwargs):\n        kwargs[\"own_profile\"] = True\n        grant_ids = get_objects_for_user(\n            self.request.user, \"decide_workinghours_for_group\", klass=Group\n        ).values_list(\"id\", flat=True)\n        kwargs[\"can_grant\"] = self.request.user.groups.filter(id__in=grant_ids).exists()\n        return super().get_context_data(**kwargs)\n\n\nclass UserProfileWorkingHourView(CanGrantMixin, CustomPermissionRequiredMixin, DetailView):\n    model = UserProfile\n    permission_required = \"core.view_userprofile\"\n    template_name = \"core/userprofile_workinghours.html\"\n\n    def get_context_data(self, **kwargs):\n        kwargs[\"can_grant\"] = self.can_grant\n        return super().get_context_data(**kwargs)\n\n\nclass WorkingHourRequestView(LoginRequiredMixin, FormView):\n    form_class = WorkingHourRequestForm\n    template_name = \"core/workinghours_form.html\"\n\n    def get_form_kwargs(self):\n        kwargs = super().get_form_kwargs()\n        kwargs[\"user\"] = self.request.user\n        return kwargs\n\n    def form_valid(self, form):\n        form.create_consequence()\n        messages.success(self.request, _(\"Your request has been submitted.\"))\n        return redirect(reverse(\"core:workinghours_own\"))\n\n\nclass WorkingHourCreateView(CanGrantMixin, CustomPermissionRequiredMixin, WorkingHourRequestView):\n    def has_permission(self):\n        return self.can_grant\n\n    def get_form_kwargs(self):\n        kwargs = super().get_form_kwargs()\n        kwargs[\"can_grant\"] = True\n        kwargs[\"user\"] = self._get_target_user()\n        return kwargs\n\n    def form_valid(self, form):\n        workinghour = form.save(commit=False)\n        workinghour.user = form.user\n        workinghour.save()\n        messages.success(self.request, _(\"Working hours have been added.\"))\n        return redirect(reverse(\"core:workinghours_list\"))\n\n\nclass WorkingHourUpdateView(CanGrantMixin, CustomPermissionRequiredMixin, UpdateView):\n    model = WorkingHours\n    form_class = WorkingHourRequestForm\n\n    def has_permission(self):\n        return self.can_grant\n\n    def get_success_url(self):\n        return reverse(\"core:workinghours_detail\", kwargs={\"pk\": self.object.user.pk})\n\n    def get_form_kwargs(self):\n        kwargs = super().get_form_kwargs()\n        kwargs[\"user\"] = self.object.user\n        kwargs[\"can_grant\"] = True\n        return kwargs\n\n    def _get_target_user(self):\n        return get_object_or_404(WorkingHours, pk=self.kwargs[\"pk\"]).user\n\n\nclass WorkingHourDeleteView(\n    CanGrantMixin, CustomPermissionRequiredMixin, SuccessMessageMixin, DeleteView\n):\n    permission_required = \"core.decide_workinghours_for_group\"\n    model = WorkingHours\n    success_message = _(\"Working hours have been deleted.\")\n\n    def _get_target_user(self):\n        return get_object_or_404(WorkingHours, pk=self.kwargs[\"pk\"]).user\n\n    def has_permission(self):\n        return self.can_grant\n\n    def get_success_url(self):\n        return reverse(\"core:workinghours_detail\", kwargs={\"pk\": self.object.user.pk})\n", "path": "ephios/core/views/workinghour.py"}], "after_files": [{"content": "import datetime\nfrom collections import Counter\nfrom datetime import date\nfrom itertools import chain\nfrom typing import Optional\n\nfrom django import forms\nfrom django.contrib import messages\nfrom django.contrib.auth.mixins import LoginRequiredMixin\nfrom django.contrib.auth.models import Group\nfrom django.contrib.messages.views import SuccessMessageMixin\nfrom django.db.models import DurationField, ExpressionWrapper, F, Sum\nfrom django.shortcuts import get_object_or_404, redirect\nfrom django.urls import reverse\nfrom django.utils.functional import cached_property\nfrom django.utils.translation import gettext as _\nfrom django.views.generic import DeleteView, DetailView, FormView, TemplateView, UpdateView\nfrom django_select2.forms import Select2Widget\nfrom guardian.shortcuts import get_objects_for_user\n\nfrom ephios.core.forms.users import WorkingHourRequestForm\nfrom ephios.core.models import EventType, LocalParticipation, UserProfile, WorkingHours\nfrom ephios.extra.mixins import CustomPermissionRequiredMixin\nfrom ephios.extra.widgets import CustomDateInput\n\n\nclass CanGrantMixin:\n    @cached_property\n    def can_grant(self):\n        \"\"\"\n        Return whether the current request user can grant\n        working hours to the target user (which might be themselves).\n        \"\"\"\n        if self.request.user.is_anonymous:\n            return False\n        can_grant_for_groups = get_objects_for_user(\n            self.request.user, \"decide_workinghours_for_group\", klass=Group\n        )\n        return self._get_target_user().groups.filter(id__in=can_grant_for_groups).exists()\n\n    def _get_target_user(self):\n        \"\"\"Return the user whose working hours are being managed.\"\"\"\n        return get_object_or_404(UserProfile, pk=self.kwargs[\"pk\"])\n\n\nclass WorkingHourFilterForm(forms.Form):\n    start = forms.DateField(required=False, label=_(\"From\"), widget=CustomDateInput)\n    end = forms.DateField(required=False, label=_(\"To\"), widget=CustomDateInput)\n    type = forms.ModelChoiceField(\n        label=_(\"Event type\"),\n        queryset=EventType.objects.all(),\n        required=False,\n        widget=Select2Widget(\n            attrs={\n                \"data-placeholder\": _(\"Event type\"),\n                \"classes\": \"w-auto\",\n            }\n        ),\n    )\n\n\nclass WorkingHourOverview(CustomPermissionRequiredMixin, TemplateView):\n    template_name = \"core/workinghours_list.html\"\n    permission_required = \"core.view_userprofile\"\n\n    def _get_working_hours_stats(self, start: date, end: date, eventtype: Optional[EventType]):\n        participations = LocalParticipation.objects.filter(\n            state=LocalParticipation.States.CONFIRMED,\n            start_time__date__gte=start,\n            end_time__date__lte=end,\n        )\n        workinghours = {}\n        if eventtype is not None:\n            participations = participations.filter(shift__event__type=eventtype)\n        else:\n            workinghours = (\n                WorkingHours.objects.filter(date__gte=start, date__lte=end)\n                .annotate(hour_sum=Sum(\"hours\"))\n                .values_list(\"user__pk\", \"user__display_name\", \"hour_sum\")\n            )\n        participations = (\n            participations.annotate(\n                duration=ExpressionWrapper(\n                    (F(\"end_time\") - F(\"start_time\")),\n                    output_field=DurationField(),\n                ),\n            )\n            .annotate(hour_sum=Sum(\"duration\"))\n            .values_list(\"user__pk\", \"user__display_name\", \"hour_sum\")\n        )\n\n        result = {}\n        c = Counter()\n        for user_pk, display_name, hours in chain(participations, workinghours):\n            current_sum = (\n                hours.total_seconds() / (60 * 60)\n                if isinstance(hours, datetime.timedelta)\n                else float(hours)\n            )\n            c[user_pk] += current_sum\n            result[user_pk] = {\n                \"pk\": user_pk,\n                \"display_name\": display_name,\n                \"hours\": c[user_pk],\n            }\n        return sorted(result.values(), key=lambda x: x[\"hours\"], reverse=True)\n\n    def get_context_data(self, **kwargs):\n        filter_form = WorkingHourFilterForm(\n            self.request.GET\n            or {\n                # intitial data for initial page laod\n                # (does not use `initial` cause that only works with unbound forms)\n                \"start\": date.today().replace(month=1, day=1),\n                \"end\": date.today().replace(month=12, day=31),\n            }\n        )\n        filter_form.is_valid()\n        kwargs[\"filter_form\"] = filter_form\n        kwargs[\"users\"] = self._get_working_hours_stats(\n            start=filter_form.cleaned_data.get(\"start\") or date.min,  # start/end are not required\n            end=filter_form.cleaned_data.get(\"end\") or date.max,\n            eventtype=filter_form.cleaned_data.get(\"type\"),\n        )\n        kwargs[\"can_grant_for\"] = set(\n            get_objects_for_user(self.request.user, \"decide_workinghours_for_group\", klass=Group)\n        )\n        kwargs[\"groups_by_user\"] = {\n            profile.pk: set(profile.groups.all())\n            for profile in UserProfile.objects.all().prefetch_related(\"groups\")\n        }\n        return super().get_context_data(**kwargs)\n\n\nclass OwnWorkingHourView(LoginRequiredMixin, DetailView):\n    template_name = \"core/userprofile_workinghours.html\"\n\n    def get_object(self, queryset=None):\n        return self.request.user\n\n    def get_context_data(self, **kwargs):\n        kwargs[\"own_profile\"] = True\n        grant_ids = get_objects_for_user(\n            self.request.user, \"decide_workinghours_for_group\", klass=Group\n        ).values_list(\"id\", flat=True)\n        kwargs[\"can_grant\"] = self.request.user.groups.filter(id__in=grant_ids).exists()\n        return super().get_context_data(**kwargs)\n\n\nclass UserProfileWorkingHourView(CanGrantMixin, CustomPermissionRequiredMixin, DetailView):\n    model = UserProfile\n    permission_required = \"core.view_userprofile\"\n    template_name = \"core/userprofile_workinghours.html\"\n\n    def get_context_data(self, **kwargs):\n        kwargs[\"can_grant\"] = self.can_grant\n        return super().get_context_data(**kwargs)\n\n\nclass WorkingHourRequestView(LoginRequiredMixin, FormView):\n    form_class = WorkingHourRequestForm\n    template_name = \"core/workinghours_form.html\"\n\n    def get_form_kwargs(self):\n        kwargs = super().get_form_kwargs()\n        kwargs[\"user\"] = self.request.user\n        return kwargs\n\n    def form_valid(self, form):\n        form.create_consequence()\n        messages.success(self.request, _(\"Your request has been submitted.\"))\n        return redirect(reverse(\"core:workinghours_own\"))\n\n\nclass WorkingHourCreateView(CanGrantMixin, CustomPermissionRequiredMixin, WorkingHourRequestView):\n    def has_permission(self):\n        return self.can_grant\n\n    def get_form_kwargs(self):\n        kwargs = super().get_form_kwargs()\n        kwargs[\"can_grant\"] = True\n        kwargs[\"user\"] = self._get_target_user()\n        return kwargs\n\n    def form_valid(self, form):\n        workinghour = form.save(commit=False)\n        workinghour.user = form.user\n        workinghour.save()\n        messages.success(self.request, _(\"Working hours have been added.\"))\n        return redirect(reverse(\"core:workinghours_list\"))\n\n\nclass WorkingHourUpdateView(CanGrantMixin, CustomPermissionRequiredMixin, UpdateView):\n    model = WorkingHours\n    form_class = WorkingHourRequestForm\n\n    def has_permission(self):\n        return self.can_grant\n\n    def get_success_url(self):\n        return reverse(\"core:workinghours_detail\", kwargs={\"pk\": self.object.user.pk})\n\n    def get_form_kwargs(self):\n        kwargs = super().get_form_kwargs()\n        kwargs[\"user\"] = self.object.user\n        kwargs[\"can_grant\"] = True\n        return kwargs\n\n    def _get_target_user(self):\n        return get_object_or_404(WorkingHours, pk=self.kwargs[\"pk\"]).user\n\n\nclass WorkingHourDeleteView(\n    CanGrantMixin, CustomPermissionRequiredMixin, SuccessMessageMixin, DeleteView\n):\n    permission_required = \"core.decide_workinghours_for_group\"\n    model = WorkingHours\n    success_message = _(\"Working hours have been deleted.\")\n\n    def _get_target_user(self):\n        return get_object_or_404(WorkingHours, pk=self.kwargs[\"pk\"]).user\n\n    def has_permission(self):\n        return self.can_grant\n\n    def get_success_url(self):\n        return reverse(\"core:workinghours_detail\", kwargs={\"pk\": self.object.user.pk})\n", "path": "ephios/core/views/workinghour.py"}]}