We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Remove ObtainThaliaAuthToken view
### Describe the change
Remove https://github.com/svthalia/concrexit/blob/4b664220b406361245e153433288ffbcf9d6f4a9/website/members/views.py#L35


### Motivation
We don't use it 



--- 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/thaliawebsite/api/v1/urls.py`
Content:
```
1 from django.conf import settings
2 from django.urls import include, path
3 
4 from rest_framework.schemas import get_schema_view
5 
6 from members.views import ObtainThaliaAuthToken
7 from thaliawebsite.api.openapi import OAuthSchemaGenerator
8 
9 app_name = "thaliawebsite"
10 
11 urlpatterns = [
12     path("token-auth/", ObtainThaliaAuthToken.as_view()),
13     path("", include("activemembers.api.v1.urls")),
14     path("", include("announcements.api.v1.urls")),
15     path("", include("events.api.v1.urls")),
16     path("", include("members.api.v1.urls")),
17     path("", include("partners.api.v1.urls")),
18     path("", include("pizzas.api.v1.urls")),
19     path("", include("photos.api.v1.urls")),
20     path("", include("pushnotifications.api.v1.urls")),
21     path("", include("payments.api.v1.urls")),
22     path(
23         "schema",
24         get_schema_view(
25             title="API v1",
26             version=settings.SOURCE_COMMIT,
27             url="/api/v1/",
28             urlconf="thaliawebsite.api.v1.urls",
29             generator_class=OAuthSchemaGenerator,
30         ),
31         name="schema",
32     ),
33 ]
34
```

Path: `website/members/views.py`
Content:
```
1 """Views provided by the members package."""
2 import json
3 from datetime import date, datetime
4 
5 from django.contrib.auth.decorators import login_required
6 from django.contrib.messages.views import SuccessMessageMixin
7 from django.db.models import Q, QuerySet
8 from django.http import Http404, HttpResponse
9 from django.shortcuts import get_object_or_404
10 from django.template.response import TemplateResponse
11 from django.urls import reverse_lazy
12 from django.utils.decorators import method_decorator
13 from django.utils.translation import gettext_lazy as _
14 from django.views.generic import CreateView, DetailView, UpdateView
15 from django.views.generic.base import TemplateResponseMixin, TemplateView, View
16 
17 from rest_framework.authtoken.models import Token
18 from rest_framework.authtoken.views import ObtainAuthToken
19 from rest_framework.response import Response
20 
21 import activemembers.services as activemembers_services
22 import events.services as event_services
23 import pizzas.services
24 from members import emails, services
25 from members.decorators import membership_required
26 from members.models import EmailChange, Member, Membership, Profile
27 from thaliawebsite.views import PagedView
28 from utils.media.services import fetch_thumbnails_db
29 from utils.snippets import datetime_to_lectureyear
30 
31 from . import models
32 from .forms import ProfileForm
33 from .services import member_achievements, member_societies
34 
35 
36 class ObtainThaliaAuthToken(ObtainAuthToken):
37     """Custom override of the AuthToken view to force lowercase the username."""
38 
39     def post(self, request, *args, **kwargs) -> HttpResponse:
40         serializer = self.serializer_class(
41             data={
42                 "username": request.data.get("username").lower()
43                 if "username" in request.data
44                 else None,
45                 "password": request.data.get("password"),
46             },
47             context={"request": request},
48         )
49 
50         if not serializer.is_valid():
51             return Response({"error": "Unauthorized"}, status=401)
52 
53         user = serializer.validated_data["user"]
54         token, _ = Token.objects.get_or_create(user=user)
55         return Response({"token": token.key})
56 
57 
58 @method_decorator(login_required, "dispatch")
59 @method_decorator(membership_required, "dispatch")
60 class MembersIndex(PagedView):
61     """View that renders the members overview."""
62 
63     model = Member
64     paginate_by = 28
65     template_name = "members/index.html"
66     context_object_name = "members"
67     keywords = None
68     query_filter = ""
69     year_range = []
70 
71     def setup(self, request, *args, **kwargs) -> None:
72         super().setup(request, *args, **kwargs)
73         current_lectureyear = datetime_to_lectureyear(date.today())
74         self.year_range = list(
75             reversed(range(current_lectureyear - 5, current_lectureyear + 1))
76         )
77         self.keywords = request.GET.get("keywords", "").split() or None
78         self.query_filter = kwargs.get("filter", None)
79 
80     def get_queryset(self) -> QuerySet:
81         memberships_query = Q(until__gt=datetime.now()) | Q(until=None)
82         members_query = ~Q(id=None)
83 
84         if self.query_filter and self.query_filter.isdigit():
85             members_query &= Q(profile__starting_year=int(self.query_filter))
86             memberships_query &= Q(type=Membership.MEMBER)
87         elif self.query_filter == "older":
88             members_query &= Q(profile__starting_year__lt=self.year_range[-1])
89             memberships_query &= Q(type=Membership.MEMBER)
90         elif self.query_filter == "former":
91             # Filter out all current active memberships
92             memberships_query &= Q(type=Membership.MEMBER) | Q(type=Membership.HONORARY)
93             memberships = Membership.objects.filter(memberships_query)
94             members_query &= ~Q(pk__in=memberships.values("user__pk"))
95         # Members_query contains users that are not currently (honorary)member
96         elif self.query_filter == "benefactors":
97             memberships_query &= Q(type=Membership.BENEFACTOR)
98         elif self.query_filter == "honorary":
99             memberships_query = Q(until__gt=datetime.now().date()) | Q(until=None)
100             memberships_query &= Q(type=Membership.HONORARY)
101 
102         if self.keywords:
103             for key in self.keywords:
104                 # Works because relevant options all have `nick` in their key
105                 members_query &= (
106                     (
107                         Q(profile__nickname__icontains=key)
108                         & Q(profile__display_name_preference__contains="nick")
109                     )
110                     | Q(first_name__icontains=key)
111                     | Q(last_name__icontains=key)
112                     | Q(username__icontains=key)
113                 )
114 
115         if self.query_filter == "former":
116             memberships_query = Q(type=Membership.MEMBER) | Q(type=Membership.HONORARY)
117             memberships = Membership.objects.filter(memberships_query)
118             all_memberships = Membership.objects.all()
119             # Only keep members that were once members, or are legacy users
120             # that do not have any memberships at all
121             members_query &= Q(pk__in=memberships.values("user__pk")) | ~Q(
122                 pk__in=all_memberships.values("user__pk")
123             )
124         else:
125             memberships = Membership.objects.filter(memberships_query)
126             members_query &= Q(pk__in=memberships.values("user__pk"))
127         members = (
128             Member.objects.filter(members_query)
129             .order_by("first_name")
130             .select_related("profile")
131         )
132         return members
133 
134     def get_context_data(self, **kwargs) -> dict:
135         context = super().get_context_data(**kwargs)
136 
137         context.update(
138             {
139                 "filter": self.query_filter,
140                 "year_range": self.year_range,
141                 "keywords": self.keywords,
142             }
143         )
144 
145         fetch_thumbnails_db(
146             [x.profile.photo for x in context["object_list"] if x.profile.photo]
147         )
148 
149         return context
150 
151 
152 @method_decorator(login_required, "dispatch")
153 class ProfileDetailView(DetailView):
154     """View that renders a member's profile."""
155 
156     context_object_name = "member"
157     model = Member
158     template_name = "members/user/profile.html"
159 
160     def setup(self, request, *args, **kwargs) -> None:
161         if "pk" not in kwargs and request.member:
162             kwargs["pk"] = request.member.pk
163         super().setup(request, *args, **kwargs)
164 
165     def get_context_data(self, **kwargs) -> dict:
166         context = super().get_context_data(**kwargs)
167         member = context["member"]
168 
169         achievements = member_achievements(member)
170         societies = member_societies(member)
171 
172         membership = member.current_membership
173         membership_type = _("Unknown membership history")
174         if membership:
175             membership_type = membership.get_type_display()
176         elif member.has_been_honorary_member():
177             membership_type = _("Former honorary member")
178         elif member.has_been_member():
179             membership_type = _("Former member")
180         elif member.latest_membership:
181             membership_type = _("Former benefactor")
182 
183         context.update(
184             {
185                 "achievements": achievements,
186                 "societies": societies,
187                 "membership_type": membership_type,
188             }
189         )
190 
191         return context
192 
193 
194 @method_decorator(login_required, "dispatch")
195 class UserProfileUpdateView(SuccessMessageMixin, UpdateView):
196     """View that allows a user to update their profile."""
197 
198     template_name = "members/user/edit_profile.html"
199     model = Profile
200     form_class = ProfileForm
201     success_url = reverse_lazy("members:edit-profile")
202     success_message = _("Your profile has been updated successfully.")
203 
204     def get_object(self, queryset=None) -> Profile:
205         return get_object_or_404(models.Profile, user=self.request.user)
206 
207 
208 @method_decorator(login_required, "dispatch")
209 class StatisticsView(TemplateView):
210     """View that renders the statistics page."""
211 
212     template_name = "members/statistics.html"
213 
214     def get_context_data(self, **kwargs) -> dict:
215         context = super().get_context_data(**kwargs)
216         context.update(
217             {
218                 "total_members": models.Member.current_members.count(),
219                 "cohort_sizes": json.dumps(services.gen_stats_year()),
220                 "member_type_distribution": json.dumps(
221                     services.gen_stats_member_type()
222                 ),
223                 "committee_sizes": json.dumps(
224                     activemembers_services.generate_statistics()
225                 ),
226                 "event_categories": json.dumps(
227                     event_services.generate_category_statistics()
228                 ),
229                 "total_pizza_orders": json.dumps(
230                     pizzas.services.gen_stats_pizza_orders()
231                 ),
232                 "active_members": json.dumps(services.gen_stats_active_members()),
233             }
234         )
235 
236         return context
237 
238 
239 @method_decorator(login_required, name="dispatch")
240 class EmailChangeFormView(CreateView):
241     """View that renders the email change form."""
242 
243     model = EmailChange
244     fields = ["email", "member"]
245     template_name = "members/user/email_change.html"
246 
247     def get_initial(self) -> dict:
248         initial = super().get_initial()
249         initial["email"] = self.request.member.email
250         return initial
251 
252     def post(self, request, *args, **kwargs) -> HttpResponse:
253         request.POST = request.POST.dict()
254         request.POST["member"] = request.member.pk
255         return super().post(request, *args, **kwargs)
256 
257     def form_valid(self, form) -> HttpResponse:
258         change_request = form.save()
259         emails.send_email_change_confirmation_messages(change_request)
260         return TemplateResponse(
261             request=self.request, template="members/user/email_change_requested.html"
262         )
263 
264 
265 @method_decorator(login_required, name="dispatch")
266 class EmailChangeConfirmView(View, TemplateResponseMixin):
267     """View that renders an HTML template and confirms the old email address."""
268 
269     template_name = "members/user/email_change_confirmed.html"
270 
271     def get(self, request, *args, **kwargs) -> HttpResponse:
272         if not EmailChange.objects.filter(confirm_key=kwargs["key"]).exists():
273             raise Http404
274 
275         change_request = EmailChange.objects.get(confirm_key=kwargs["key"])
276 
277         services.confirm_email_change(change_request)
278 
279         return self.render_to_response({})
280 
281 
282 @method_decorator(login_required, name="dispatch")
283 class EmailChangeVerifyView(View, TemplateResponseMixin):
284     """View that renders an HTML template and verifies the new email address."""
285 
286     template_name = "members/user/email_change_verified.html"
287 
288     def get(self, request, *args, **kwargs) -> HttpResponse:
289         if not EmailChange.objects.filter(verify_key=kwargs["key"]).exists():
290             raise Http404
291 
292         change_request = EmailChange.objects.get(verify_key=kwargs["key"])
293 
294         services.verify_email_change(change_request)
295 
296         return self.render_to_response({})
297
```
--- 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/members/views.py b/website/members/views.py
--- a/website/members/views.py
+++ b/website/members/views.py
@@ -14,10 +14,6 @@
 from django.views.generic import CreateView, DetailView, UpdateView
 from django.views.generic.base import TemplateResponseMixin, TemplateView, View
 
-from rest_framework.authtoken.models import Token
-from rest_framework.authtoken.views import ObtainAuthToken
-from rest_framework.response import Response
-
 import activemembers.services as activemembers_services
 import events.services as event_services
 import pizzas.services
@@ -33,28 +29,6 @@
 from .services import member_achievements, member_societies
 
 
-class ObtainThaliaAuthToken(ObtainAuthToken):
-    """Custom override of the AuthToken view to force lowercase the username."""
-
-    def post(self, request, *args, **kwargs) -> HttpResponse:
-        serializer = self.serializer_class(
-            data={
-                "username": request.data.get("username").lower()
-                if "username" in request.data
-                else None,
-                "password": request.data.get("password"),
-            },
-            context={"request": request},
-        )
-
-        if not serializer.is_valid():
-            return Response({"error": "Unauthorized"}, status=401)
-
-        user = serializer.validated_data["user"]
-        token, _ = Token.objects.get_or_create(user=user)
-        return Response({"token": token.key})
-
-
 @method_decorator(login_required, "dispatch")
 @method_decorator(membership_required, "dispatch")
 class MembersIndex(PagedView):
diff --git a/website/thaliawebsite/api/v1/urls.py b/website/thaliawebsite/api/v1/urls.py
--- a/website/thaliawebsite/api/v1/urls.py
+++ b/website/thaliawebsite/api/v1/urls.py
@@ -3,13 +3,11 @@
 
 from rest_framework.schemas import get_schema_view
 
-from members.views import ObtainThaliaAuthToken
 from thaliawebsite.api.openapi import OAuthSchemaGenerator
 
 app_name = "thaliawebsite"
 
 urlpatterns = [
-    path("token-auth/", ObtainThaliaAuthToken.as_view()),
     path("", include("activemembers.api.v1.urls")),
     path("", include("announcements.api.v1.urls")),
     path("", include("events.api.v1.urls")),

{"golden_diff": "diff --git a/website/members/views.py b/website/members/views.py\n--- a/website/members/views.py\n+++ b/website/members/views.py\n@@ -14,10 +14,6 @@\n from django.views.generic import CreateView, DetailView, UpdateView\n from django.views.generic.base import TemplateResponseMixin, TemplateView, View\n \n-from rest_framework.authtoken.models import Token\n-from rest_framework.authtoken.views import ObtainAuthToken\n-from rest_framework.response import Response\n-\n import activemembers.services as activemembers_services\n import events.services as event_services\n import pizzas.services\n@@ -33,28 +29,6 @@\n from .services import member_achievements, member_societies\n \n \n-class ObtainThaliaAuthToken(ObtainAuthToken):\n-    \"\"\"Custom override of the AuthToken view to force lowercase the username.\"\"\"\n-\n-    def post(self, request, *args, **kwargs) -> HttpResponse:\n-        serializer = self.serializer_class(\n-            data={\n-                \"username\": request.data.get(\"username\").lower()\n-                if \"username\" in request.data\n-                else None,\n-                \"password\": request.data.get(\"password\"),\n-            },\n-            context={\"request\": request},\n-        )\n-\n-        if not serializer.is_valid():\n-            return Response({\"error\": \"Unauthorized\"}, status=401)\n-\n-        user = serializer.validated_data[\"user\"]\n-        token, _ = Token.objects.get_or_create(user=user)\n-        return Response({\"token\": token.key})\n-\n-\n @method_decorator(login_required, \"dispatch\")\n @method_decorator(membership_required, \"dispatch\")\n class MembersIndex(PagedView):\ndiff --git a/website/thaliawebsite/api/v1/urls.py b/website/thaliawebsite/api/v1/urls.py\n--- a/website/thaliawebsite/api/v1/urls.py\n+++ b/website/thaliawebsite/api/v1/urls.py\n@@ -3,13 +3,11 @@\n \n from rest_framework.schemas import get_schema_view\n \n-from members.views import ObtainThaliaAuthToken\n from thaliawebsite.api.openapi import OAuthSchemaGenerator\n \n app_name = \"thaliawebsite\"\n \n urlpatterns = [\n-    path(\"token-auth/\", ObtainThaliaAuthToken.as_view()),\n     path(\"\", include(\"activemembers.api.v1.urls\")),\n     path(\"\", include(\"announcements.api.v1.urls\")),\n     path(\"\", include(\"events.api.v1.urls\")),\n", "issue": "Remove ObtainThaliaAuthToken view\n### Describe the change\r\nRemove https://github.com/svthalia/concrexit/blob/4b664220b406361245e153433288ffbcf9d6f4a9/website/members/views.py#L35\r\n\r\n\r\n### Motivation\r\nWe don't use it \r\n\r\n\n", "before_files": [{"content": "from django.conf import settings\nfrom django.urls import include, path\n\nfrom rest_framework.schemas import get_schema_view\n\nfrom members.views import ObtainThaliaAuthToken\nfrom thaliawebsite.api.openapi import OAuthSchemaGenerator\n\napp_name = \"thaliawebsite\"\n\nurlpatterns = [\n    path(\"token-auth/\", ObtainThaliaAuthToken.as_view()),\n    path(\"\", include(\"activemembers.api.v1.urls\")),\n    path(\"\", include(\"announcements.api.v1.urls\")),\n    path(\"\", include(\"events.api.v1.urls\")),\n    path(\"\", include(\"members.api.v1.urls\")),\n    path(\"\", include(\"partners.api.v1.urls\")),\n    path(\"\", include(\"pizzas.api.v1.urls\")),\n    path(\"\", include(\"photos.api.v1.urls\")),\n    path(\"\", include(\"pushnotifications.api.v1.urls\")),\n    path(\"\", include(\"payments.api.v1.urls\")),\n    path(\n        \"schema\",\n        get_schema_view(\n            title=\"API v1\",\n            version=settings.SOURCE_COMMIT,\n            url=\"/api/v1/\",\n            urlconf=\"thaliawebsite.api.v1.urls\",\n            generator_class=OAuthSchemaGenerator,\n        ),\n        name=\"schema\",\n    ),\n]\n", "path": "website/thaliawebsite/api/v1/urls.py"}, {"content": "\"\"\"Views provided by the members package.\"\"\"\nimport json\nfrom datetime import date, datetime\n\nfrom django.contrib.auth.decorators import login_required\nfrom django.contrib.messages.views import SuccessMessageMixin\nfrom django.db.models import Q, QuerySet\nfrom django.http import Http404, HttpResponse\nfrom django.shortcuts import get_object_or_404\nfrom django.template.response import TemplateResponse\nfrom django.urls import reverse_lazy\nfrom django.utils.decorators import method_decorator\nfrom django.utils.translation import gettext_lazy as _\nfrom django.views.generic import CreateView, DetailView, UpdateView\nfrom django.views.generic.base import TemplateResponseMixin, TemplateView, View\n\nfrom rest_framework.authtoken.models import Token\nfrom rest_framework.authtoken.views import ObtainAuthToken\nfrom rest_framework.response import Response\n\nimport activemembers.services as activemembers_services\nimport events.services as event_services\nimport pizzas.services\nfrom members import emails, services\nfrom members.decorators import membership_required\nfrom members.models import EmailChange, Member, Membership, Profile\nfrom thaliawebsite.views import PagedView\nfrom utils.media.services import fetch_thumbnails_db\nfrom utils.snippets import datetime_to_lectureyear\n\nfrom . import models\nfrom .forms import ProfileForm\nfrom .services import member_achievements, member_societies\n\n\nclass ObtainThaliaAuthToken(ObtainAuthToken):\n    \"\"\"Custom override of the AuthToken view to force lowercase the username.\"\"\"\n\n    def post(self, request, *args, **kwargs) -> HttpResponse:\n        serializer = self.serializer_class(\n            data={\n                \"username\": request.data.get(\"username\").lower()\n                if \"username\" in request.data\n                else None,\n                \"password\": request.data.get(\"password\"),\n            },\n            context={\"request\": request},\n        )\n\n        if not serializer.is_valid():\n            return Response({\"error\": \"Unauthorized\"}, status=401)\n\n        user = serializer.validated_data[\"user\"]\n        token, _ = Token.objects.get_or_create(user=user)\n        return Response({\"token\": token.key})\n\n\n@method_decorator(login_required, \"dispatch\")\n@method_decorator(membership_required, \"dispatch\")\nclass MembersIndex(PagedView):\n    \"\"\"View that renders the members overview.\"\"\"\n\n    model = Member\n    paginate_by = 28\n    template_name = \"members/index.html\"\n    context_object_name = \"members\"\n    keywords = None\n    query_filter = \"\"\n    year_range = []\n\n    def setup(self, request, *args, **kwargs) -> None:\n        super().setup(request, *args, **kwargs)\n        current_lectureyear = datetime_to_lectureyear(date.today())\n        self.year_range = list(\n            reversed(range(current_lectureyear - 5, current_lectureyear + 1))\n        )\n        self.keywords = request.GET.get(\"keywords\", \"\").split() or None\n        self.query_filter = kwargs.get(\"filter\", None)\n\n    def get_queryset(self) -> QuerySet:\n        memberships_query = Q(until__gt=datetime.now()) | Q(until=None)\n        members_query = ~Q(id=None)\n\n        if self.query_filter and self.query_filter.isdigit():\n            members_query &= Q(profile__starting_year=int(self.query_filter))\n            memberships_query &= Q(type=Membership.MEMBER)\n        elif self.query_filter == \"older\":\n            members_query &= Q(profile__starting_year__lt=self.year_range[-1])\n            memberships_query &= Q(type=Membership.MEMBER)\n        elif self.query_filter == \"former\":\n            # Filter out all current active memberships\n            memberships_query &= Q(type=Membership.MEMBER) | Q(type=Membership.HONORARY)\n            memberships = Membership.objects.filter(memberships_query)\n            members_query &= ~Q(pk__in=memberships.values(\"user__pk\"))\n        # Members_query contains users that are not currently (honorary)member\n        elif self.query_filter == \"benefactors\":\n            memberships_query &= Q(type=Membership.BENEFACTOR)\n        elif self.query_filter == \"honorary\":\n            memberships_query = Q(until__gt=datetime.now().date()) | Q(until=None)\n            memberships_query &= Q(type=Membership.HONORARY)\n\n        if self.keywords:\n            for key in self.keywords:\n                # Works because relevant options all have `nick` in their key\n                members_query &= (\n                    (\n                        Q(profile__nickname__icontains=key)\n                        & Q(profile__display_name_preference__contains=\"nick\")\n                    )\n                    | Q(first_name__icontains=key)\n                    | Q(last_name__icontains=key)\n                    | Q(username__icontains=key)\n                )\n\n        if self.query_filter == \"former\":\n            memberships_query = Q(type=Membership.MEMBER) | Q(type=Membership.HONORARY)\n            memberships = Membership.objects.filter(memberships_query)\n            all_memberships = Membership.objects.all()\n            # Only keep members that were once members, or are legacy users\n            # that do not have any memberships at all\n            members_query &= Q(pk__in=memberships.values(\"user__pk\")) | ~Q(\n                pk__in=all_memberships.values(\"user__pk\")\n            )\n        else:\n            memberships = Membership.objects.filter(memberships_query)\n            members_query &= Q(pk__in=memberships.values(\"user__pk\"))\n        members = (\n            Member.objects.filter(members_query)\n            .order_by(\"first_name\")\n            .select_related(\"profile\")\n        )\n        return members\n\n    def get_context_data(self, **kwargs) -> dict:\n        context = super().get_context_data(**kwargs)\n\n        context.update(\n            {\n                \"filter\": self.query_filter,\n                \"year_range\": self.year_range,\n                \"keywords\": self.keywords,\n            }\n        )\n\n        fetch_thumbnails_db(\n            [x.profile.photo for x in context[\"object_list\"] if x.profile.photo]\n        )\n\n        return context\n\n\n@method_decorator(login_required, \"dispatch\")\nclass ProfileDetailView(DetailView):\n    \"\"\"View that renders a member's profile.\"\"\"\n\n    context_object_name = \"member\"\n    model = Member\n    template_name = \"members/user/profile.html\"\n\n    def setup(self, request, *args, **kwargs) -> None:\n        if \"pk\" not in kwargs and request.member:\n            kwargs[\"pk\"] = request.member.pk\n        super().setup(request, *args, **kwargs)\n\n    def get_context_data(self, **kwargs) -> dict:\n        context = super().get_context_data(**kwargs)\n        member = context[\"member\"]\n\n        achievements = member_achievements(member)\n        societies = member_societies(member)\n\n        membership = member.current_membership\n        membership_type = _(\"Unknown membership history\")\n        if membership:\n            membership_type = membership.get_type_display()\n        elif member.has_been_honorary_member():\n            membership_type = _(\"Former honorary member\")\n        elif member.has_been_member():\n            membership_type = _(\"Former member\")\n        elif member.latest_membership:\n            membership_type = _(\"Former benefactor\")\n\n        context.update(\n            {\n                \"achievements\": achievements,\n                \"societies\": societies,\n                \"membership_type\": membership_type,\n            }\n        )\n\n        return context\n\n\n@method_decorator(login_required, \"dispatch\")\nclass UserProfileUpdateView(SuccessMessageMixin, UpdateView):\n    \"\"\"View that allows a user to update their profile.\"\"\"\n\n    template_name = \"members/user/edit_profile.html\"\n    model = Profile\n    form_class = ProfileForm\n    success_url = reverse_lazy(\"members:edit-profile\")\n    success_message = _(\"Your profile has been updated successfully.\")\n\n    def get_object(self, queryset=None) -> Profile:\n        return get_object_or_404(models.Profile, user=self.request.user)\n\n\n@method_decorator(login_required, \"dispatch\")\nclass StatisticsView(TemplateView):\n    \"\"\"View that renders the statistics page.\"\"\"\n\n    template_name = \"members/statistics.html\"\n\n    def get_context_data(self, **kwargs) -> dict:\n        context = super().get_context_data(**kwargs)\n        context.update(\n            {\n                \"total_members\": models.Member.current_members.count(),\n                \"cohort_sizes\": json.dumps(services.gen_stats_year()),\n                \"member_type_distribution\": json.dumps(\n                    services.gen_stats_member_type()\n                ),\n                \"committee_sizes\": json.dumps(\n                    activemembers_services.generate_statistics()\n                ),\n                \"event_categories\": json.dumps(\n                    event_services.generate_category_statistics()\n                ),\n                \"total_pizza_orders\": json.dumps(\n                    pizzas.services.gen_stats_pizza_orders()\n                ),\n                \"active_members\": json.dumps(services.gen_stats_active_members()),\n            }\n        )\n\n        return context\n\n\n@method_decorator(login_required, name=\"dispatch\")\nclass EmailChangeFormView(CreateView):\n    \"\"\"View that renders the email change form.\"\"\"\n\n    model = EmailChange\n    fields = [\"email\", \"member\"]\n    template_name = \"members/user/email_change.html\"\n\n    def get_initial(self) -> dict:\n        initial = super().get_initial()\n        initial[\"email\"] = self.request.member.email\n        return initial\n\n    def post(self, request, *args, **kwargs) -> HttpResponse:\n        request.POST = request.POST.dict()\n        request.POST[\"member\"] = request.member.pk\n        return super().post(request, *args, **kwargs)\n\n    def form_valid(self, form) -> HttpResponse:\n        change_request = form.save()\n        emails.send_email_change_confirmation_messages(change_request)\n        return TemplateResponse(\n            request=self.request, template=\"members/user/email_change_requested.html\"\n        )\n\n\n@method_decorator(login_required, name=\"dispatch\")\nclass EmailChangeConfirmView(View, TemplateResponseMixin):\n    \"\"\"View that renders an HTML template and confirms the old email address.\"\"\"\n\n    template_name = \"members/user/email_change_confirmed.html\"\n\n    def get(self, request, *args, **kwargs) -> HttpResponse:\n        if not EmailChange.objects.filter(confirm_key=kwargs[\"key\"]).exists():\n            raise Http404\n\n        change_request = EmailChange.objects.get(confirm_key=kwargs[\"key\"])\n\n        services.confirm_email_change(change_request)\n\n        return self.render_to_response({})\n\n\n@method_decorator(login_required, name=\"dispatch\")\nclass EmailChangeVerifyView(View, TemplateResponseMixin):\n    \"\"\"View that renders an HTML template and verifies the new email address.\"\"\"\n\n    template_name = \"members/user/email_change_verified.html\"\n\n    def get(self, request, *args, **kwargs) -> HttpResponse:\n        if not EmailChange.objects.filter(verify_key=kwargs[\"key\"]).exists():\n            raise Http404\n\n        change_request = EmailChange.objects.get(verify_key=kwargs[\"key\"])\n\n        services.verify_email_change(change_request)\n\n        return self.render_to_response({})\n", "path": "website/members/views.py"}], "after_files": [{"content": "from django.conf import settings\nfrom django.urls import include, path\n\nfrom rest_framework.schemas import get_schema_view\n\nfrom thaliawebsite.api.openapi import OAuthSchemaGenerator\n\napp_name = \"thaliawebsite\"\n\nurlpatterns = [\n    path(\"\", include(\"activemembers.api.v1.urls\")),\n    path(\"\", include(\"announcements.api.v1.urls\")),\n    path(\"\", include(\"events.api.v1.urls\")),\n    path(\"\", include(\"members.api.v1.urls\")),\n    path(\"\", include(\"partners.api.v1.urls\")),\n    path(\"\", include(\"pizzas.api.v1.urls\")),\n    path(\"\", include(\"photos.api.v1.urls\")),\n    path(\"\", include(\"pushnotifications.api.v1.urls\")),\n    path(\"\", include(\"payments.api.v1.urls\")),\n    path(\n        \"schema\",\n        get_schema_view(\n            title=\"API v1\",\n            version=settings.SOURCE_COMMIT,\n            url=\"/api/v1/\",\n            urlconf=\"thaliawebsite.api.v1.urls\",\n            generator_class=OAuthSchemaGenerator,\n        ),\n        name=\"schema\",\n    ),\n]\n", "path": "website/thaliawebsite/api/v1/urls.py"}, {"content": "\"\"\"Views provided by the members package.\"\"\"\nimport json\nfrom datetime import date, datetime\n\nfrom django.contrib.auth.decorators import login_required\nfrom django.contrib.messages.views import SuccessMessageMixin\nfrom django.db.models import Q, QuerySet\nfrom django.http import Http404, HttpResponse\nfrom django.shortcuts import get_object_or_404\nfrom django.template.response import TemplateResponse\nfrom django.urls import reverse_lazy\nfrom django.utils.decorators import method_decorator\nfrom django.utils.translation import gettext_lazy as _\nfrom django.views.generic import CreateView, DetailView, UpdateView\nfrom django.views.generic.base import TemplateResponseMixin, TemplateView, View\n\nimport activemembers.services as activemembers_services\nimport events.services as event_services\nimport pizzas.services\nfrom members import emails, services\nfrom members.decorators import membership_required\nfrom members.models import EmailChange, Member, Membership, Profile\nfrom thaliawebsite.views import PagedView\nfrom utils.media.services import fetch_thumbnails_db\nfrom utils.snippets import datetime_to_lectureyear\n\nfrom . import models\nfrom .forms import ProfileForm\nfrom .services import member_achievements, member_societies\n\n\n@method_decorator(login_required, \"dispatch\")\n@method_decorator(membership_required, \"dispatch\")\nclass MembersIndex(PagedView):\n    \"\"\"View that renders the members overview.\"\"\"\n\n    model = Member\n    paginate_by = 28\n    template_name = \"members/index.html\"\n    context_object_name = \"members\"\n    keywords = None\n    query_filter = \"\"\n    year_range = []\n\n    def setup(self, request, *args, **kwargs) -> None:\n        super().setup(request, *args, **kwargs)\n        current_lectureyear = datetime_to_lectureyear(date.today())\n        self.year_range = list(\n            reversed(range(current_lectureyear - 5, current_lectureyear + 1))\n        )\n        self.keywords = request.GET.get(\"keywords\", \"\").split() or None\n        self.query_filter = kwargs.get(\"filter\", None)\n\n    def get_queryset(self) -> QuerySet:\n        memberships_query = Q(until__gt=datetime.now()) | Q(until=None)\n        members_query = ~Q(id=None)\n\n        if self.query_filter and self.query_filter.isdigit():\n            members_query &= Q(profile__starting_year=int(self.query_filter))\n            memberships_query &= Q(type=Membership.MEMBER)\n        elif self.query_filter == \"older\":\n            members_query &= Q(profile__starting_year__lt=self.year_range[-1])\n            memberships_query &= Q(type=Membership.MEMBER)\n        elif self.query_filter == \"former\":\n            # Filter out all current active memberships\n            memberships_query &= Q(type=Membership.MEMBER) | Q(type=Membership.HONORARY)\n            memberships = Membership.objects.filter(memberships_query)\n            members_query &= ~Q(pk__in=memberships.values(\"user__pk\"))\n        # Members_query contains users that are not currently (honorary)member\n        elif self.query_filter == \"benefactors\":\n            memberships_query &= Q(type=Membership.BENEFACTOR)\n        elif self.query_filter == \"honorary\":\n            memberships_query = Q(until__gt=datetime.now().date()) | Q(until=None)\n            memberships_query &= Q(type=Membership.HONORARY)\n\n        if self.keywords:\n            for key in self.keywords:\n                # Works because relevant options all have `nick` in their key\n                members_query &= (\n                    (\n                        Q(profile__nickname__icontains=key)\n                        & Q(profile__display_name_preference__contains=\"nick\")\n                    )\n                    | Q(first_name__icontains=key)\n                    | Q(last_name__icontains=key)\n                    | Q(username__icontains=key)\n                )\n\n        if self.query_filter == \"former\":\n            memberships_query = Q(type=Membership.MEMBER) | Q(type=Membership.HONORARY)\n            memberships = Membership.objects.filter(memberships_query)\n            all_memberships = Membership.objects.all()\n            # Only keep members that were once members, or are legacy users\n            # that do not have any memberships at all\n            members_query &= Q(pk__in=memberships.values(\"user__pk\")) | ~Q(\n                pk__in=all_memberships.values(\"user__pk\")\n            )\n        else:\n            memberships = Membership.objects.filter(memberships_query)\n            members_query &= Q(pk__in=memberships.values(\"user__pk\"))\n        members = (\n            Member.objects.filter(members_query)\n            .order_by(\"first_name\")\n            .select_related(\"profile\")\n        )\n        return members\n\n    def get_context_data(self, **kwargs) -> dict:\n        context = super().get_context_data(**kwargs)\n\n        context.update(\n            {\n                \"filter\": self.query_filter,\n                \"year_range\": self.year_range,\n                \"keywords\": self.keywords,\n            }\n        )\n\n        fetch_thumbnails_db(\n            [x.profile.photo for x in context[\"object_list\"] if x.profile.photo]\n        )\n\n        return context\n\n\n@method_decorator(login_required, \"dispatch\")\nclass ProfileDetailView(DetailView):\n    \"\"\"View that renders a member's profile.\"\"\"\n\n    context_object_name = \"member\"\n    model = Member\n    template_name = \"members/user/profile.html\"\n\n    def setup(self, request, *args, **kwargs) -> None:\n        if \"pk\" not in kwargs and request.member:\n            kwargs[\"pk\"] = request.member.pk\n        super().setup(request, *args, **kwargs)\n\n    def get_context_data(self, **kwargs) -> dict:\n        context = super().get_context_data(**kwargs)\n        member = context[\"member\"]\n\n        achievements = member_achievements(member)\n        societies = member_societies(member)\n\n        membership = member.current_membership\n        membership_type = _(\"Unknown membership history\")\n        if membership:\n            membership_type = membership.get_type_display()\n        elif member.has_been_honorary_member():\n            membership_type = _(\"Former honorary member\")\n        elif member.has_been_member():\n            membership_type = _(\"Former member\")\n        elif member.latest_membership:\n            membership_type = _(\"Former benefactor\")\n\n        context.update(\n            {\n                \"achievements\": achievements,\n                \"societies\": societies,\n                \"membership_type\": membership_type,\n            }\n        )\n\n        return context\n\n\n@method_decorator(login_required, \"dispatch\")\nclass UserProfileUpdateView(SuccessMessageMixin, UpdateView):\n    \"\"\"View that allows a user to update their profile.\"\"\"\n\n    template_name = \"members/user/edit_profile.html\"\n    model = Profile\n    form_class = ProfileForm\n    success_url = reverse_lazy(\"members:edit-profile\")\n    success_message = _(\"Your profile has been updated successfully.\")\n\n    def get_object(self, queryset=None) -> Profile:\n        return get_object_or_404(models.Profile, user=self.request.user)\n\n\n@method_decorator(login_required, \"dispatch\")\nclass StatisticsView(TemplateView):\n    \"\"\"View that renders the statistics page.\"\"\"\n\n    template_name = \"members/statistics.html\"\n\n    def get_context_data(self, **kwargs) -> dict:\n        context = super().get_context_data(**kwargs)\n        context.update(\n            {\n                \"total_members\": models.Member.current_members.count(),\n                \"cohort_sizes\": json.dumps(services.gen_stats_year()),\n                \"member_type_distribution\": json.dumps(\n                    services.gen_stats_member_type()\n                ),\n                \"committee_sizes\": json.dumps(\n                    activemembers_services.generate_statistics()\n                ),\n                \"event_categories\": json.dumps(\n                    event_services.generate_category_statistics()\n                ),\n                \"total_pizza_orders\": json.dumps(\n                    pizzas.services.gen_stats_pizza_orders()\n                ),\n                \"active_members\": json.dumps(services.gen_stats_active_members()),\n            }\n        )\n\n        return context\n\n\n@method_decorator(login_required, name=\"dispatch\")\nclass EmailChangeFormView(CreateView):\n    \"\"\"View that renders the email change form.\"\"\"\n\n    model = EmailChange\n    fields = [\"email\", \"member\"]\n    template_name = \"members/user/email_change.html\"\n\n    def get_initial(self) -> dict:\n        initial = super().get_initial()\n        initial[\"email\"] = self.request.member.email\n        return initial\n\n    def post(self, request, *args, **kwargs) -> HttpResponse:\n        request.POST = request.POST.dict()\n        request.POST[\"member\"] = request.member.pk\n        return super().post(request, *args, **kwargs)\n\n    def form_valid(self, form) -> HttpResponse:\n        change_request = form.save()\n        emails.send_email_change_confirmation_messages(change_request)\n        return TemplateResponse(\n            request=self.request, template=\"members/user/email_change_requested.html\"\n        )\n\n\n@method_decorator(login_required, name=\"dispatch\")\nclass EmailChangeConfirmView(View, TemplateResponseMixin):\n    \"\"\"View that renders an HTML template and confirms the old email address.\"\"\"\n\n    template_name = \"members/user/email_change_confirmed.html\"\n\n    def get(self, request, *args, **kwargs) -> HttpResponse:\n        if not EmailChange.objects.filter(confirm_key=kwargs[\"key\"]).exists():\n            raise Http404\n\n        change_request = EmailChange.objects.get(confirm_key=kwargs[\"key\"])\n\n        services.confirm_email_change(change_request)\n\n        return self.render_to_response({})\n\n\n@method_decorator(login_required, name=\"dispatch\")\nclass EmailChangeVerifyView(View, TemplateResponseMixin):\n    \"\"\"View that renders an HTML template and verifies the new email address.\"\"\"\n\n    template_name = \"members/user/email_change_verified.html\"\n\n    def get(self, request, *args, **kwargs) -> HttpResponse:\n        if not EmailChange.objects.filter(verify_key=kwargs[\"key\"]).exists():\n            raise Http404\n\n        change_request = EmailChange.objects.get(verify_key=kwargs[\"key\"])\n\n        services.verify_email_change(change_request)\n\n        return self.render_to_response({})\n", "path": "website/members/views.py"}]}