prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Members API doesn't list all members
  ### Describe the bug
  Luko, pk=23 on staging, is listed as member on the website, but is not a member according to the API
  
  ### How to reproduce
  No idea.
  check https://staging.thalia.nu/members/profile/23 vs https://staging.thalia.nu/api/v2/members/23/
  
  ### Expected behaviour
  These urls should agree on the memberness of Luko
  
  
  
  --- 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/members/api/v2/views.py`
  Content:
  ```
  1 """API views of the activemembers app."""
  2 
  3 from django.shortcuts import get_object_or_404
  4 
  5 from oauth2_provider.contrib.rest_framework import IsAuthenticatedOrTokenHasScope
  6 from rest_framework import filters as framework_filters
  7 from rest_framework.generics import ListAPIView, RetrieveAPIView, UpdateAPIView
  8 
  9 from members.api.v2 import filters
  10 from members.api.v2.serializers.member import (
  11     MemberCurrentSerializer,
  12     MemberListSerializer,
  13     MemberSerializer,
  14 )
  15 from members.models import Member
  16 from thaliawebsite.api.openapi import OAuthAutoSchema
  17 from thaliawebsite.api.v2.permissions import IsAuthenticatedOrTokenHasScopeForMethod
  18 from utils.media.services import fetch_thumbnails_db
  19 
  20 
  21 class MemberListView(ListAPIView):
  22     """Returns an overview of all members."""
  23 
  24     serializer_class = MemberListSerializer
  25     queryset = (
  26         Member.current_members.all()
  27         .select_related("profile")
  28         .prefetch_related("membership_set")
  29     )
  30 
  31     def get_serializer(self, *args, **kwargs):
  32         if len(args) > 0:
  33             members = args[0]
  34             fetch_thumbnails_db([member.profile.photo for member in members])
  35         return super().get_serializer(*args, **kwargs)
  36 
  37     permission_classes = [
  38         IsAuthenticatedOrTokenHasScope,
  39     ]
  40     required_scopes = ["members:read"]
  41     filter_backends = (
  42         framework_filters.OrderingFilter,
  43         framework_filters.SearchFilter,
  44         filters.MembershipTypeFilter,
  45         filters.StartingYearFilter,
  46     )
  47     ordering_fields = ("first_name", "last_name", "username")
  48     search_fields = (
  49         "profile__nickname",
  50         "profile__starting_year",
  51         "first_name",
  52         "last_name",
  53         "username",
  54     )
  55 
  56 
  57 class MemberDetailView(RetrieveAPIView):
  58     """Returns details of a member."""
  59 
  60     serializer_class = MemberSerializer
  61     queryset = Member.current_members.all()
  62     permission_classes = [
  63         IsAuthenticatedOrTokenHasScope,
  64     ]
  65     required_scopes = ["members:read"]
  66 
  67 
  68 class MemberCurrentView(MemberDetailView, UpdateAPIView):
  69     """Returns details of the authenticated member."""
  70 
  71     serializer_class = MemberCurrentSerializer
  72     schema = OAuthAutoSchema(operation_id_base="CurrentMember")
  73     permission_classes = [
  74         IsAuthenticatedOrTokenHasScopeForMethod,
  75     ]
  76     required_scopes_per_method = {
  77         "GET": ["profile:read"],
  78         "PATCH": ["profile:write"],
  79         "PUT": ["profile:write"],
  80     }
  81 
  82     def get_object(self):
  83         return get_object_or_404(Member, pk=self.request.user.pk)
  84
  ```
  
  Path: `website/members/api/v2/filters.py`
  Content:
  ```
  1 from rest_framework import filters
  2 
  3 from members.models import Membership
  4 
  5 
  6 class StartingYearFilter(filters.BaseFilterBackend):
  7     """Allows you to filter by starting year."""
  8 
  9     def filter_queryset(self, request, queryset, view):
  10         starting_year = request.query_params.get("starting_year", None)
  11 
  12         if starting_year:
  13             queryset = queryset.filter(profile__starting_year=starting_year)
  14 
  15         return queryset
  16 
  17     def get_schema_operation_parameters(self, view):
  18         return [
  19             {
  20                 "name": "starting_year",
  21                 "required": False,
  22                 "in": "query",
  23                 "description": "Filter by starting year",
  24                 "schema": {
  25                     "type": "number",
  26                 },
  27             }
  28         ]
  29 
  30 
  31 class MembershipTypeFilter(filters.BaseFilterBackend):
  32     """Allows you to filter by membership type."""
  33 
  34     def filter_queryset(self, request, queryset, view):
  35         membership_type = request.query_params.get("membership_type", None)
  36 
  37         if membership_type:
  38             memberships = Membership.objects.filter(type=membership_type)
  39             queryset = queryset.filter(pk__in=memberships.values("user__pk"))
  40 
  41         return queryset
  42 
  43     def get_schema_operation_parameters(self, view):
  44         return [
  45             {
  46                 "name": "membership_type",
  47                 "required": False,
  48                 "in": "query",
  49                 "description": "Filter by membership type",
  50                 "schema": {
  51                     "type": "string",
  52                 },
  53             }
  54         ]
  55
  ```
  --- 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()
  
  ```
  
golden_diff: diff --git a/website/members/api/v2/filters.py b/website/members/api/v2/filters.py
  --- a/website/members/api/v2/filters.py
  +++ b/website/members/api/v2/filters.py
  @@ -1,4 +1,9 @@
  -from rest_framework import filters
  +from datetime import datetime
  +
  +from django.db.models import Q
  +from django.utils import timezone
  +
  +from rest_framework import filters, serializers
   
   from members.models import Membership
   
  @@ -28,6 +33,62 @@
           ]
   
   
  +class FormerMemberFilter(filters.BaseFilterBackend):
  +    def filter_queryset(self, request, queryset, view):
  +        former = request.query_params.get("former", "false")
  +
  +        if former == "false":
  +            # Filter out former members
  +            return (
  +                queryset.exclude(membership=None)
  +                .filter(
  +                    Q(membership__until__isnull=True)
  +                    | Q(membership__until__gt=timezone.now().date())
  +                )
  +                .distinct()
  +            )
  +        elif former == "true":
  +            # Filter out current members
  +
  +            memberships_query = Q(until__gt=datetime.now()) | Q(until=None)
  +            members_query = ~Q(id=None)
  +
  +            # Filter out all current active memberships
  +            memberships_query &= Q(type=Membership.MEMBER) | Q(type=Membership.HONORARY)
  +            memberships = Membership.objects.filter(memberships_query)
  +            members_query &= ~Q(pk__in=memberships.values("user__pk"))
  +
  +            memberships_query = Q(type=Membership.MEMBER) | Q(type=Membership.HONORARY)
  +            memberships = Membership.objects.filter(memberships_query)
  +            all_memberships = Membership.objects.all()
  +            # Only keep members that were once members, or are legacy users
  +            # that do not have any memberships at all
  +            members_query &= Q(pk__in=memberships.values("user__pk")) | ~Q(
  +                pk__in=all_memberships.values("user__pk")
  +            )
  +
  +            return queryset.filter(members_query)
  +        elif former == "any":
  +            # Include both former and current members
  +            return queryset
  +        else:
  +            raise serializers.ValidationError("invalid former parameter")
  +
  +    def get_schema_operation_parameters(self, view):
  +        return [
  +            {
  +                "name": "former",
  +                "required": False,
  +                "in": "query",
  +                "description": "Include former members or only former members",
  +                "schema": {
  +                    "type": "string",
  +                    "enum": ["true", "false", "any"],
  +                },
  +            }
  +        ]
  +
  +
   class MembershipTypeFilter(filters.BaseFilterBackend):
       """Allows you to filter by membership type."""
   
  diff --git a/website/members/api/v2/views.py b/website/members/api/v2/views.py
  --- a/website/members/api/v2/views.py
  +++ b/website/members/api/v2/views.py
  @@ -23,7 +23,7 @@
   
       serializer_class = MemberListSerializer
       queryset = (
  -        Member.current_members.all()
  +        Member.objects.all()
           .select_related("profile")
           .prefetch_related("membership_set")
       )
  @@ -43,6 +43,7 @@
           framework_filters.SearchFilter,
           filters.MembershipTypeFilter,
           filters.StartingYearFilter,
  +        filters.FormerMemberFilter,
       )
       ordering_fields = ("first_name", "last_name", "username")
       search_fields = (
  @@ -58,7 +59,7 @@
       """Returns details of a member."""
   
       serializer_class = MemberSerializer
  -    queryset = Member.current_members.all()
  +    queryset = Member.objects.all()
       permission_classes = [
           IsAuthenticatedOrTokenHasScope,
       ]
  
verification_info: {"golden_diff": "diff --git a/website/members/api/v2/filters.py b/website/members/api/v2/filters.py\n--- a/website/members/api/v2/filters.py\n+++ b/website/members/api/v2/filters.py\n@@ -1,4 +1,9 @@\n-from rest_framework import filters\n+from datetime import datetime\n+\n+from django.db.models import Q\n+from django.utils import timezone\n+\n+from rest_framework import filters, serializers\n \n from members.models import Membership\n \n@@ -28,6 +33,62 @@\n         ]\n \n \n+class FormerMemberFilter(filters.BaseFilterBackend):\n+    def filter_queryset(self, request, queryset, view):\n+        former = request.query_params.get(\"former\", \"false\")\n+\n+        if former == \"false\":\n+            # Filter out former members\n+            return (\n+                queryset.exclude(membership=None)\n+                .filter(\n+                    Q(membership__until__isnull=True)\n+                    | Q(membership__until__gt=timezone.now().date())\n+                )\n+                .distinct()\n+            )\n+        elif former == \"true\":\n+            # Filter out current members\n+\n+            memberships_query = Q(until__gt=datetime.now()) | Q(until=None)\n+            members_query = ~Q(id=None)\n+\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+\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+\n+            return queryset.filter(members_query)\n+        elif former == \"any\":\n+            # Include both former and current members\n+            return queryset\n+        else:\n+            raise serializers.ValidationError(\"invalid former parameter\")\n+\n+    def get_schema_operation_parameters(self, view):\n+        return [\n+            {\n+                \"name\": \"former\",\n+                \"required\": False,\n+                \"in\": \"query\",\n+                \"description\": \"Include former members or only former members\",\n+                \"schema\": {\n+                    \"type\": \"string\",\n+                    \"enum\": [\"true\", \"false\", \"any\"],\n+                },\n+            }\n+        ]\n+\n+\n class MembershipTypeFilter(filters.BaseFilterBackend):\n     \"\"\"Allows you to filter by membership type.\"\"\"\n \ndiff --git a/website/members/api/v2/views.py b/website/members/api/v2/views.py\n--- a/website/members/api/v2/views.py\n+++ b/website/members/api/v2/views.py\n@@ -23,7 +23,7 @@\n \n     serializer_class = MemberListSerializer\n     queryset = (\n-        Member.current_members.all()\n+        Member.objects.all()\n         .select_related(\"profile\")\n         .prefetch_related(\"membership_set\")\n     )\n@@ -43,6 +43,7 @@\n         framework_filters.SearchFilter,\n         filters.MembershipTypeFilter,\n         filters.StartingYearFilter,\n+        filters.FormerMemberFilter,\n     )\n     ordering_fields = (\"first_name\", \"last_name\", \"username\")\n     search_fields = (\n@@ -58,7 +59,7 @@\n     \"\"\"Returns details of a member.\"\"\"\n \n     serializer_class = MemberSerializer\n-    queryset = Member.current_members.all()\n+    queryset = Member.objects.all()\n     permission_classes = [\n         IsAuthenticatedOrTokenHasScope,\n     ]\n", "issue": "Members API doesn't list all members\n### Describe the bug\r\nLuko, pk=23 on staging, is listed as member on the website, but is not a member according to the API\r\n\r\n### How to reproduce\r\nNo idea.\r\ncheck https://staging.thalia.nu/members/profile/23 vs https://staging.thalia.nu/api/v2/members/23/\r\n\r\n### Expected behaviour\r\nThese urls should agree on the memberness of Luko\r\n\r\n\n", "before_files": [{"content": "\"\"\"API views of the activemembers app.\"\"\"\n\nfrom django.shortcuts import get_object_or_404\n\nfrom oauth2_provider.contrib.rest_framework import IsAuthenticatedOrTokenHasScope\nfrom rest_framework import filters as framework_filters\nfrom rest_framework.generics import ListAPIView, RetrieveAPIView, UpdateAPIView\n\nfrom members.api.v2 import filters\nfrom members.api.v2.serializers.member import (\n    MemberCurrentSerializer,\n    MemberListSerializer,\n    MemberSerializer,\n)\nfrom members.models import Member\nfrom thaliawebsite.api.openapi import OAuthAutoSchema\nfrom thaliawebsite.api.v2.permissions import IsAuthenticatedOrTokenHasScopeForMethod\nfrom utils.media.services import fetch_thumbnails_db\n\n\nclass MemberListView(ListAPIView):\n    \"\"\"Returns an overview of all members.\"\"\"\n\n    serializer_class = MemberListSerializer\n    queryset = (\n        Member.current_members.all()\n        .select_related(\"profile\")\n        .prefetch_related(\"membership_set\")\n    )\n\n    def get_serializer(self, *args, **kwargs):\n        if len(args) > 0:\n            members = args[0]\n            fetch_thumbnails_db([member.profile.photo for member in members])\n        return super().get_serializer(*args, **kwargs)\n\n    permission_classes = [\n        IsAuthenticatedOrTokenHasScope,\n    ]\n    required_scopes = [\"members:read\"]\n    filter_backends = (\n        framework_filters.OrderingFilter,\n        framework_filters.SearchFilter,\n        filters.MembershipTypeFilter,\n        filters.StartingYearFilter,\n    )\n    ordering_fields = (\"first_name\", \"last_name\", \"username\")\n    search_fields = (\n        \"profile__nickname\",\n        \"profile__starting_year\",\n        \"first_name\",\n        \"last_name\",\n        \"username\",\n    )\n\n\nclass MemberDetailView(RetrieveAPIView):\n    \"\"\"Returns details of a member.\"\"\"\n\n    serializer_class = MemberSerializer\n    queryset = Member.current_members.all()\n    permission_classes = [\n        IsAuthenticatedOrTokenHasScope,\n    ]\n    required_scopes = [\"members:read\"]\n\n\nclass MemberCurrentView(MemberDetailView, UpdateAPIView):\n    \"\"\"Returns details of the authenticated member.\"\"\"\n\n    serializer_class = MemberCurrentSerializer\n    schema = OAuthAutoSchema(operation_id_base=\"CurrentMember\")\n    permission_classes = [\n        IsAuthenticatedOrTokenHasScopeForMethod,\n    ]\n    required_scopes_per_method = {\n        \"GET\": [\"profile:read\"],\n        \"PATCH\": [\"profile:write\"],\n        \"PUT\": [\"profile:write\"],\n    }\n\n    def get_object(self):\n        return get_object_or_404(Member, pk=self.request.user.pk)\n", "path": "website/members/api/v2/views.py"}, {"content": "from rest_framework import filters\n\nfrom members.models import Membership\n\n\nclass StartingYearFilter(filters.BaseFilterBackend):\n    \"\"\"Allows you to filter by starting year.\"\"\"\n\n    def filter_queryset(self, request, queryset, view):\n        starting_year = request.query_params.get(\"starting_year\", None)\n\n        if starting_year:\n            queryset = queryset.filter(profile__starting_year=starting_year)\n\n        return queryset\n\n    def get_schema_operation_parameters(self, view):\n        return [\n            {\n                \"name\": \"starting_year\",\n                \"required\": False,\n                \"in\": \"query\",\n                \"description\": \"Filter by starting year\",\n                \"schema\": {\n                    \"type\": \"number\",\n                },\n            }\n        ]\n\n\nclass MembershipTypeFilter(filters.BaseFilterBackend):\n    \"\"\"Allows you to filter by membership type.\"\"\"\n\n    def filter_queryset(self, request, queryset, view):\n        membership_type = request.query_params.get(\"membership_type\", None)\n\n        if membership_type:\n            memberships = Membership.objects.filter(type=membership_type)\n            queryset = queryset.filter(pk__in=memberships.values(\"user__pk\"))\n\n        return queryset\n\n    def get_schema_operation_parameters(self, view):\n        return [\n            {\n                \"name\": \"membership_type\",\n                \"required\": False,\n                \"in\": \"query\",\n                \"description\": \"Filter by membership type\",\n                \"schema\": {\n                    \"type\": \"string\",\n                },\n            }\n        ]\n", "path": "website/members/api/v2/filters.py"}], "after_files": [{"content": "\"\"\"API views of the activemembers app.\"\"\"\n\nfrom django.shortcuts import get_object_or_404\n\nfrom oauth2_provider.contrib.rest_framework import IsAuthenticatedOrTokenHasScope\nfrom rest_framework import filters as framework_filters\nfrom rest_framework.generics import ListAPIView, RetrieveAPIView, UpdateAPIView\n\nfrom members.api.v2 import filters\nfrom members.api.v2.serializers.member import (\n    MemberCurrentSerializer,\n    MemberListSerializer,\n    MemberSerializer,\n)\nfrom members.models import Member\nfrom thaliawebsite.api.openapi import OAuthAutoSchema\nfrom thaliawebsite.api.v2.permissions import IsAuthenticatedOrTokenHasScopeForMethod\nfrom utils.media.services import fetch_thumbnails_db\n\n\nclass MemberListView(ListAPIView):\n    \"\"\"Returns an overview of all members.\"\"\"\n\n    serializer_class = MemberListSerializer\n    queryset = (\n        Member.objects.all()\n        .select_related(\"profile\")\n        .prefetch_related(\"membership_set\")\n    )\n\n    def get_serializer(self, *args, **kwargs):\n        if len(args) > 0:\n            members = args[0]\n            fetch_thumbnails_db([member.profile.photo for member in members])\n        return super().get_serializer(*args, **kwargs)\n\n    permission_classes = [\n        IsAuthenticatedOrTokenHasScope,\n    ]\n    required_scopes = [\"members:read\"]\n    filter_backends = (\n        framework_filters.OrderingFilter,\n        framework_filters.SearchFilter,\n        filters.MembershipTypeFilter,\n        filters.StartingYearFilter,\n        filters.FormerMemberFilter,\n    )\n    ordering_fields = (\"first_name\", \"last_name\", \"username\")\n    search_fields = (\n        \"profile__nickname\",\n        \"profile__starting_year\",\n        \"first_name\",\n        \"last_name\",\n        \"username\",\n    )\n\n\nclass MemberDetailView(RetrieveAPIView):\n    \"\"\"Returns details of a member.\"\"\"\n\n    serializer_class = MemberSerializer\n    queryset = Member.objects.all()\n    permission_classes = [\n        IsAuthenticatedOrTokenHasScope,\n    ]\n    required_scopes = [\"members:read\"]\n\n\nclass MemberCurrentView(MemberDetailView, UpdateAPIView):\n    \"\"\"Returns details of the authenticated member.\"\"\"\n\n    serializer_class = MemberCurrentSerializer\n    schema = OAuthAutoSchema(operation_id_base=\"CurrentMember\")\n    permission_classes = [\n        IsAuthenticatedOrTokenHasScopeForMethod,\n    ]\n    required_scopes_per_method = {\n        \"GET\": [\"profile:read\"],\n        \"PATCH\": [\"profile:write\"],\n        \"PUT\": [\"profile:write\"],\n    }\n\n    def get_object(self):\n        return get_object_or_404(Member, pk=self.request.user.pk)\n", "path": "website/members/api/v2/views.py"}, {"content": "from datetime import datetime\n\nfrom django.db.models import Q\nfrom django.utils import timezone\n\nfrom rest_framework import filters, serializers\n\nfrom members.models import Membership\n\n\nclass StartingYearFilter(filters.BaseFilterBackend):\n    \"\"\"Allows you to filter by starting year.\"\"\"\n\n    def filter_queryset(self, request, queryset, view):\n        starting_year = request.query_params.get(\"starting_year\", None)\n\n        if starting_year:\n            queryset = queryset.filter(profile__starting_year=starting_year)\n\n        return queryset\n\n    def get_schema_operation_parameters(self, view):\n        return [\n            {\n                \"name\": \"starting_year\",\n                \"required\": False,\n                \"in\": \"query\",\n                \"description\": \"Filter by starting year\",\n                \"schema\": {\n                    \"type\": \"number\",\n                },\n            }\n        ]\n\n\nclass FormerMemberFilter(filters.BaseFilterBackend):\n    def filter_queryset(self, request, queryset, view):\n        former = request.query_params.get(\"former\", \"false\")\n\n        if former == \"false\":\n            # Filter out former members\n            return (\n                queryset.exclude(membership=None)\n                .filter(\n                    Q(membership__until__isnull=True)\n                    | Q(membership__until__gt=timezone.now().date())\n                )\n                .distinct()\n            )\n        elif former == \"true\":\n            # Filter out current members\n\n            memberships_query = Q(until__gt=datetime.now()) | Q(until=None)\n            members_query = ~Q(id=None)\n\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\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\n            return queryset.filter(members_query)\n        elif former == \"any\":\n            # Include both former and current members\n            return queryset\n        else:\n            raise serializers.ValidationError(\"invalid former parameter\")\n\n    def get_schema_operation_parameters(self, view):\n        return [\n            {\n                \"name\": \"former\",\n                \"required\": False,\n                \"in\": \"query\",\n                \"description\": \"Include former members or only former members\",\n                \"schema\": {\n                    \"type\": \"string\",\n                    \"enum\": [\"true\", \"false\", \"any\"],\n                },\n            }\n        ]\n\n\nclass MembershipTypeFilter(filters.BaseFilterBackend):\n    \"\"\"Allows you to filter by membership type.\"\"\"\n\n    def filter_queryset(self, request, queryset, view):\n        membership_type = request.query_params.get(\"membership_type\", None)\n\n        if membership_type:\n            memberships = Membership.objects.filter(type=membership_type)\n            queryset = queryset.filter(pk__in=memberships.values(\"user__pk\"))\n\n        return queryset\n\n    def get_schema_operation_parameters(self, view):\n        return [\n            {\n                \"name\": \"membership_type\",\n                \"required\": False,\n                \"in\": \"query\",\n                \"description\": \"Filter by membership type\",\n                \"schema\": {\n                    \"type\": \"string\",\n                },\n            }\n        ]\n", "path": "website/members/api/v2/filters.py"}]}