We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Add an endpoint to fetch user information from a username.
We'd need this endpoint to be able to implement features requested in [this](https://github.com/python-discord/bot/issues/1757) bot issue.

```py
from django.shortcuts import get_object_or_404

    @action(
        detail=False,
        methods=["GET"],
        url_path=r"username/(?P<name>[^#]{,32})#(?P<discriminator>[0-9]{4})"
    )
    def get_by_username(self, request, name, discriminator):
        user = get_object_or_404(User, name=name, discriminator=discriminator)
        return Response(UserSerializer(user).data, status=status.HTTP_200_OK)
```

This addition to the user view set seems to make GET requests to URLs like `bot/users/username/Qwerty#0001` work , but there might be a more ideal way to do this.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `pydis_site/apps/api/viewsets/bot/user.py`
Content:
```
1 import typing
2 from collections import OrderedDict
3 
4 from django.db.models import Q
5 from rest_framework import status
6 from rest_framework.decorators import action
7 from rest_framework.pagination import PageNumberPagination
8 from rest_framework.request import Request
9 from rest_framework.response import Response
10 from rest_framework.serializers import ModelSerializer
11 from rest_framework.viewsets import ModelViewSet
12 
13 from pydis_site.apps.api.models.bot.infraction import Infraction
14 from pydis_site.apps.api.models.bot.metricity import Metricity, NotFoundError
15 from pydis_site.apps.api.models.bot.user import User
16 from pydis_site.apps.api.serializers import UserSerializer
17 
18 
19 class UserListPagination(PageNumberPagination):
20     """Custom pagination class for the User Model."""
21 
22     page_size = 2500
23     page_size_query_param = "page_size"
24 
25     def get_next_page_number(self) -> typing.Optional[int]:
26         """Get the next page number."""
27         if not self.page.has_next():
28             return None
29         page_number = self.page.next_page_number()
30         return page_number
31 
32     def get_previous_page_number(self) -> typing.Optional[int]:
33         """Get the previous page number."""
34         if not self.page.has_previous():
35             return None
36 
37         page_number = self.page.previous_page_number()
38         return page_number
39 
40     def get_paginated_response(self, data: list) -> Response:
41         """Override method to send modified response."""
42         return Response(OrderedDict([
43             ('count', self.page.paginator.count),
44             ('next_page_no', self.get_next_page_number()),
45             ('previous_page_no', self.get_previous_page_number()),
46             ('results', data)
47         ]))
48 
49 
50 class UserViewSet(ModelViewSet):
51     """
52     View providing CRUD operations on Discord users through the bot.
53 
54     ## Routes
55     ### GET /bot/users
56     Returns all users currently known with pagination.
57 
58     #### Response format
59     >>> {
60     ...     'count': 95000,
61     ...     'next_page_no': "2",
62     ...     'previous_page_no': None,
63     ...     'results': [
64     ...      {
65     ...         'id': 409107086526644234,
66     ...         'name': "Python",
67     ...         'discriminator': 4329,
68     ...         'roles': [
69     ...             352427296948486144,
70     ...             270988689419665409,
71     ...             277546923144249364,
72     ...             458226699344019457
73     ...         ],
74     ...         'in_guild': True
75     ...     },
76     ...     ]
77     ... }
78 
79     #### Optional Query Parameters
80     - page_size: number of Users in one page, defaults to 10,000
81     - page: page number
82 
83     #### Status codes
84     - 200: returned on success
85 
86     ### GET /bot/users/<snowflake:int>
87     Gets a single user by ID.
88 
89     #### Response format
90     >>> {
91     ...     'id': 409107086526644234,
92     ...     'name': "Python",
93     ...     'discriminator': 4329,
94     ...     'roles': [
95     ...         352427296948486144,
96     ...         270988689419665409,
97     ...         277546923144249364,
98     ...         458226699344019457
99     ...     ],
100     ...     'in_guild': True
101     ... }
102 
103     #### Status codes
104     - 200: returned on success
105     - 404: if a user with the given `snowflake` could not be found
106 
107     ### GET /bot/users/<snowflake:int>/metricity_data
108     Gets metricity data for a single user by ID.
109 
110     #### Response format
111     >>> {
112     ...    "joined_at": "2020-10-06T21:54:23.540766",
113     ...    "total_messages": 2,
114     ...    "voice_banned": False,
115     ...    "activity_blocks": 1
116     ...}
117 
118     #### Status codes
119     - 200: returned on success
120     - 404: if a user with the given `snowflake` could not be found
121 
122     ### GET /bot/users/<snowflake:int>/metricity_review_data
123     Gets metricity data for a single user's review by ID.
124 
125     #### Response format
126     >>> {
127     ...     'joined_at': '2020-08-26T08:09:43.507000',
128     ...     'top_channel_activity': [['off-topic', 15],
129     ...                              ['talent-pool', 4],
130     ...                              ['defcon', 2]],
131     ...     'total_messages': 22
132     ... }
133 
134     #### Status codes
135     - 200: returned on success
136     - 404: if a user with the given `snowflake` could not be found
137 
138     ### POST /bot/users
139     Adds a single or multiple new users.
140     The roles attached to the user(s) must be roles known by the site.
141     Users that already exist in the database will be skipped.
142 
143     #### Request body
144     >>> {
145     ...     'id': int,
146     ...     'name': str,
147     ...     'discriminator': int,
148     ...     'roles': List[int],
149     ...     'in_guild': bool
150     ... }
151 
152     Alternatively, request users can be POSTed as a list of above objects,
153     in which case multiple users will be created at once. In this case,
154     the response is an empty list.
155 
156     #### Status codes
157     - 201: returned on success
158     - 400: if one of the given roles does not exist, or one of the given fields is invalid
159     - 400: if multiple user objects with the same id are given
160 
161     ### PUT /bot/users/<snowflake:int>
162     Update the user with the given `snowflake`.
163     All fields in the request body are required.
164 
165     #### Request body
166     >>> {
167     ...     'id': int,
168     ...     'name': str,
169     ...     'discriminator': int,
170     ...     'roles': List[int],
171     ...     'in_guild': bool
172     ... }
173 
174     #### Status codes
175     - 200: returned on success
176     - 400: if the request body was invalid, see response body for details
177     - 404: if the user with the given `snowflake` could not be found
178 
179     ### PATCH /bot/users/<snowflake:int>
180     Update the user with the given `snowflake`.
181     All fields in the request body are optional.
182 
183     #### Request body
184     >>> {
185     ...     'id': int,
186     ...     'name': str,
187     ...     'discriminator': int,
188     ...     'roles': List[int],
189     ...     'in_guild': bool
190     ... }
191 
192     #### Status codes
193     - 200: returned on success
194     - 400: if the request body was invalid, see response body for details
195     - 404: if the user with the given `snowflake` could not be found
196 
197     ### BULK PATCH /bot/users/bulk_patch
198     Update users with the given `ids` and `details`.
199     `id` field and at least one other field is mandatory.
200 
201     #### Request body
202     >>> [
203     ...     {
204     ...         'id': int,
205     ...         'name': str,
206     ...         'discriminator': int,
207     ...         'roles': List[int],
208     ...         'in_guild': bool
209     ...     },
210     ...     {
211     ...         'id': int,
212     ...         'name': str,
213     ...         'discriminator': int,
214     ...         'roles': List[int],
215     ...         'in_guild': bool
216     ...     },
217     ... ]
218 
219     #### Status codes
220     - 200: returned on success
221     - 400: if the request body was invalid, see response body for details
222     - 400: if multiple user objects with the same id are given
223     - 404: if the user with the given id does not exist
224 
225     ### DELETE /bot/users/<snowflake:int>
226     Deletes the user with the given `snowflake`.
227 
228     #### Status codes
229     - 204: returned on success
230     - 404: if a user with the given `snowflake` does not exist
231     """
232 
233     serializer_class = UserSerializer
234     queryset = User.objects.all().order_by("id")
235     pagination_class = UserListPagination
236 
237     def get_serializer(self, *args, **kwargs) -> ModelSerializer:
238         """Set Serializer many attribute to True if request body contains a list."""
239         if isinstance(kwargs.get('data', {}), list):
240             kwargs['many'] = True
241 
242         return super().get_serializer(*args, **kwargs)
243 
244     @action(detail=False, methods=["PATCH"], name='user-bulk-patch')
245     def bulk_patch(self, request: Request) -> Response:
246         """Update multiple User objects in a single request."""
247         serializer = self.get_serializer(
248             instance=self.get_queryset(),
249             data=request.data,
250             many=True,
251             partial=True
252         )
253 
254         serializer.is_valid(raise_exception=True)
255         serializer.save()
256 
257         return Response(serializer.data, status=status.HTTP_200_OK)
258 
259     @action(detail=True)
260     def metricity_data(self, request: Request, pk: str = None) -> Response:
261         """Request handler for metricity_data endpoint."""
262         user = self.get_object()
263 
264         has_voice_infraction = Infraction.objects.filter(
265             Q(user__id=user.id, active=True),
266             Q(type="voice_ban") | Q(type="voice_mute")
267         ).exists()
268 
269         with Metricity() as metricity:
270             try:
271                 data = metricity.user(user.id)
272 
273                 data["total_messages"] = metricity.total_messages(user.id)
274                 data["activity_blocks"] = metricity.total_message_blocks(user.id)
275 
276                 data["voice_gate_blocked"] = has_voice_infraction
277                 return Response(data, status=status.HTTP_200_OK)
278             except NotFoundError:
279                 return Response(dict(detail="User not found in metricity"),
280                                 status=status.HTTP_404_NOT_FOUND)
281 
282     @action(detail=True)
283     def metricity_review_data(self, request: Request, pk: str = None) -> Response:
284         """Request handler for metricity_review_data endpoint."""
285         user = self.get_object()
286 
287         with Metricity() as metricity:
288             try:
289                 data = metricity.user(user.id)
290                 data["total_messages"] = metricity.total_messages(user.id)
291                 data["top_channel_activity"] = metricity.top_channel_activity(user.id)
292                 return Response(data, status=status.HTTP_200_OK)
293             except NotFoundError:
294                 return Response(dict(detail="User not found in metricity"),
295                                 status=status.HTTP_404_NOT_FOUND)
296
```
--- 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/pydis_site/apps/api/viewsets/bot/user.py b/pydis_site/apps/api/viewsets/bot/user.py
--- a/pydis_site/apps/api/viewsets/bot/user.py
+++ b/pydis_site/apps/api/viewsets/bot/user.py
@@ -2,6 +2,7 @@
 from collections import OrderedDict
 
 from django.db.models import Q
+from django_filters.rest_framework import DjangoFilterBackend
 from rest_framework import status
 from rest_framework.decorators import action
 from rest_framework.pagination import PageNumberPagination
@@ -77,6 +78,8 @@
     ... }
 
     #### Optional Query Parameters
+    - username: username to search for
+    - discriminator: discriminator to search for
     - page_size: number of Users in one page, defaults to 10,000
     - page: page number
 
@@ -233,6 +236,8 @@
     serializer_class = UserSerializer
     queryset = User.objects.all().order_by("id")
     pagination_class = UserListPagination
+    filter_backends = (DjangoFilterBackend,)
+    filter_fields = ('name', 'discriminator')
 
     def get_serializer(self, *args, **kwargs) -> ModelSerializer:
         """Set Serializer many attribute to True if request body contains a list."""

{"golden_diff": "diff --git a/pydis_site/apps/api/viewsets/bot/user.py b/pydis_site/apps/api/viewsets/bot/user.py\n--- a/pydis_site/apps/api/viewsets/bot/user.py\n+++ b/pydis_site/apps/api/viewsets/bot/user.py\n@@ -2,6 +2,7 @@\n from collections import OrderedDict\n \n from django.db.models import Q\n+from django_filters.rest_framework import DjangoFilterBackend\n from rest_framework import status\n from rest_framework.decorators import action\n from rest_framework.pagination import PageNumberPagination\n@@ -77,6 +78,8 @@\n     ... }\n \n     #### Optional Query Parameters\n+    - username: username to search for\n+    - discriminator: discriminator to search for\n     - page_size: number of Users in one page, defaults to 10,000\n     - page: page number\n \n@@ -233,6 +236,8 @@\n     serializer_class = UserSerializer\n     queryset = User.objects.all().order_by(\"id\")\n     pagination_class = UserListPagination\n+    filter_backends = (DjangoFilterBackend,)\n+    filter_fields = ('name', 'discriminator')\n \n     def get_serializer(self, *args, **kwargs) -> ModelSerializer:\n         \"\"\"Set Serializer many attribute to True if request body contains a list.\"\"\"\n", "issue": "Add an endpoint to fetch user information from a username.\nWe'd need this endpoint to be able to implement features requested in [this](https://github.com/python-discord/bot/issues/1757) bot issue.\r\n\r\n```py\r\nfrom django.shortcuts import get_object_or_404\r\n\r\n    @action(\r\n        detail=False,\r\n        methods=[\"GET\"],\r\n        url_path=r\"username/(?P<name>[^#]{,32})#(?P<discriminator>[0-9]{4})\"\r\n    )\r\n    def get_by_username(self, request, name, discriminator):\r\n        user = get_object_or_404(User, name=name, discriminator=discriminator)\r\n        return Response(UserSerializer(user).data, status=status.HTTP_200_OK)\r\n```\r\n\r\nThis addition to the user view set seems to make GET requests to URLs like `bot/users/username/Qwerty#0001` work , but there might be a more ideal way to do this.\n", "before_files": [{"content": "import typing\nfrom collections import OrderedDict\n\nfrom django.db.models import Q\nfrom rest_framework import status\nfrom rest_framework.decorators import action\nfrom rest_framework.pagination import PageNumberPagination\nfrom rest_framework.request import Request\nfrom rest_framework.response import Response\nfrom rest_framework.serializers import ModelSerializer\nfrom rest_framework.viewsets import ModelViewSet\n\nfrom pydis_site.apps.api.models.bot.infraction import Infraction\nfrom pydis_site.apps.api.models.bot.metricity import Metricity, NotFoundError\nfrom pydis_site.apps.api.models.bot.user import User\nfrom pydis_site.apps.api.serializers import UserSerializer\n\n\nclass UserListPagination(PageNumberPagination):\n    \"\"\"Custom pagination class for the User Model.\"\"\"\n\n    page_size = 2500\n    page_size_query_param = \"page_size\"\n\n    def get_next_page_number(self) -> typing.Optional[int]:\n        \"\"\"Get the next page number.\"\"\"\n        if not self.page.has_next():\n            return None\n        page_number = self.page.next_page_number()\n        return page_number\n\n    def get_previous_page_number(self) -> typing.Optional[int]:\n        \"\"\"Get the previous page number.\"\"\"\n        if not self.page.has_previous():\n            return None\n\n        page_number = self.page.previous_page_number()\n        return page_number\n\n    def get_paginated_response(self, data: list) -> Response:\n        \"\"\"Override method to send modified response.\"\"\"\n        return Response(OrderedDict([\n            ('count', self.page.paginator.count),\n            ('next_page_no', self.get_next_page_number()),\n            ('previous_page_no', self.get_previous_page_number()),\n            ('results', data)\n        ]))\n\n\nclass UserViewSet(ModelViewSet):\n    \"\"\"\n    View providing CRUD operations on Discord users through the bot.\n\n    ## Routes\n    ### GET /bot/users\n    Returns all users currently known with pagination.\n\n    #### Response format\n    >>> {\n    ...     'count': 95000,\n    ...     'next_page_no': \"2\",\n    ...     'previous_page_no': None,\n    ...     'results': [\n    ...      {\n    ...         'id': 409107086526644234,\n    ...         'name': \"Python\",\n    ...         'discriminator': 4329,\n    ...         'roles': [\n    ...             352427296948486144,\n    ...             270988689419665409,\n    ...             277546923144249364,\n    ...             458226699344019457\n    ...         ],\n    ...         'in_guild': True\n    ...     },\n    ...     ]\n    ... }\n\n    #### Optional Query Parameters\n    - page_size: number of Users in one page, defaults to 10,000\n    - page: page number\n\n    #### Status codes\n    - 200: returned on success\n\n    ### GET /bot/users/<snowflake:int>\n    Gets a single user by ID.\n\n    #### Response format\n    >>> {\n    ...     'id': 409107086526644234,\n    ...     'name': \"Python\",\n    ...     'discriminator': 4329,\n    ...     'roles': [\n    ...         352427296948486144,\n    ...         270988689419665409,\n    ...         277546923144249364,\n    ...         458226699344019457\n    ...     ],\n    ...     'in_guild': True\n    ... }\n\n    #### Status codes\n    - 200: returned on success\n    - 404: if a user with the given `snowflake` could not be found\n\n    ### GET /bot/users/<snowflake:int>/metricity_data\n    Gets metricity data for a single user by ID.\n\n    #### Response format\n    >>> {\n    ...    \"joined_at\": \"2020-10-06T21:54:23.540766\",\n    ...    \"total_messages\": 2,\n    ...    \"voice_banned\": False,\n    ...    \"activity_blocks\": 1\n    ...}\n\n    #### Status codes\n    - 200: returned on success\n    - 404: if a user with the given `snowflake` could not be found\n\n    ### GET /bot/users/<snowflake:int>/metricity_review_data\n    Gets metricity data for a single user's review by ID.\n\n    #### Response format\n    >>> {\n    ...     'joined_at': '2020-08-26T08:09:43.507000',\n    ...     'top_channel_activity': [['off-topic', 15],\n    ...                              ['talent-pool', 4],\n    ...                              ['defcon', 2]],\n    ...     'total_messages': 22\n    ... }\n\n    #### Status codes\n    - 200: returned on success\n    - 404: if a user with the given `snowflake` could not be found\n\n    ### POST /bot/users\n    Adds a single or multiple new users.\n    The roles attached to the user(s) must be roles known by the site.\n    Users that already exist in the database will be skipped.\n\n    #### Request body\n    >>> {\n    ...     'id': int,\n    ...     'name': str,\n    ...     'discriminator': int,\n    ...     'roles': List[int],\n    ...     'in_guild': bool\n    ... }\n\n    Alternatively, request users can be POSTed as a list of above objects,\n    in which case multiple users will be created at once. In this case,\n    the response is an empty list.\n\n    #### Status codes\n    - 201: returned on success\n    - 400: if one of the given roles does not exist, or one of the given fields is invalid\n    - 400: if multiple user objects with the same id are given\n\n    ### PUT /bot/users/<snowflake:int>\n    Update the user with the given `snowflake`.\n    All fields in the request body are required.\n\n    #### Request body\n    >>> {\n    ...     'id': int,\n    ...     'name': str,\n    ...     'discriminator': int,\n    ...     'roles': List[int],\n    ...     'in_guild': bool\n    ... }\n\n    #### Status codes\n    - 200: returned on success\n    - 400: if the request body was invalid, see response body for details\n    - 404: if the user with the given `snowflake` could not be found\n\n    ### PATCH /bot/users/<snowflake:int>\n    Update the user with the given `snowflake`.\n    All fields in the request body are optional.\n\n    #### Request body\n    >>> {\n    ...     'id': int,\n    ...     'name': str,\n    ...     'discriminator': int,\n    ...     'roles': List[int],\n    ...     'in_guild': bool\n    ... }\n\n    #### Status codes\n    - 200: returned on success\n    - 400: if the request body was invalid, see response body for details\n    - 404: if the user with the given `snowflake` could not be found\n\n    ### BULK PATCH /bot/users/bulk_patch\n    Update users with the given `ids` and `details`.\n    `id` field and at least one other field is mandatory.\n\n    #### Request body\n    >>> [\n    ...     {\n    ...         'id': int,\n    ...         'name': str,\n    ...         'discriminator': int,\n    ...         'roles': List[int],\n    ...         'in_guild': bool\n    ...     },\n    ...     {\n    ...         'id': int,\n    ...         'name': str,\n    ...         'discriminator': int,\n    ...         'roles': List[int],\n    ...         'in_guild': bool\n    ...     },\n    ... ]\n\n    #### Status codes\n    - 200: returned on success\n    - 400: if the request body was invalid, see response body for details\n    - 400: if multiple user objects with the same id are given\n    - 404: if the user with the given id does not exist\n\n    ### DELETE /bot/users/<snowflake:int>\n    Deletes the user with the given `snowflake`.\n\n    #### Status codes\n    - 204: returned on success\n    - 404: if a user with the given `snowflake` does not exist\n    \"\"\"\n\n    serializer_class = UserSerializer\n    queryset = User.objects.all().order_by(\"id\")\n    pagination_class = UserListPagination\n\n    def get_serializer(self, *args, **kwargs) -> ModelSerializer:\n        \"\"\"Set Serializer many attribute to True if request body contains a list.\"\"\"\n        if isinstance(kwargs.get('data', {}), list):\n            kwargs['many'] = True\n\n        return super().get_serializer(*args, **kwargs)\n\n    @action(detail=False, methods=[\"PATCH\"], name='user-bulk-patch')\n    def bulk_patch(self, request: Request) -> Response:\n        \"\"\"Update multiple User objects in a single request.\"\"\"\n        serializer = self.get_serializer(\n            instance=self.get_queryset(),\n            data=request.data,\n            many=True,\n            partial=True\n        )\n\n        serializer.is_valid(raise_exception=True)\n        serializer.save()\n\n        return Response(serializer.data, status=status.HTTP_200_OK)\n\n    @action(detail=True)\n    def metricity_data(self, request: Request, pk: str = None) -> Response:\n        \"\"\"Request handler for metricity_data endpoint.\"\"\"\n        user = self.get_object()\n\n        has_voice_infraction = Infraction.objects.filter(\n            Q(user__id=user.id, active=True),\n            Q(type=\"voice_ban\") | Q(type=\"voice_mute\")\n        ).exists()\n\n        with Metricity() as metricity:\n            try:\n                data = metricity.user(user.id)\n\n                data[\"total_messages\"] = metricity.total_messages(user.id)\n                data[\"activity_blocks\"] = metricity.total_message_blocks(user.id)\n\n                data[\"voice_gate_blocked\"] = has_voice_infraction\n                return Response(data, status=status.HTTP_200_OK)\n            except NotFoundError:\n                return Response(dict(detail=\"User not found in metricity\"),\n                                status=status.HTTP_404_NOT_FOUND)\n\n    @action(detail=True)\n    def metricity_review_data(self, request: Request, pk: str = None) -> Response:\n        \"\"\"Request handler for metricity_review_data endpoint.\"\"\"\n        user = self.get_object()\n\n        with Metricity() as metricity:\n            try:\n                data = metricity.user(user.id)\n                data[\"total_messages\"] = metricity.total_messages(user.id)\n                data[\"top_channel_activity\"] = metricity.top_channel_activity(user.id)\n                return Response(data, status=status.HTTP_200_OK)\n            except NotFoundError:\n                return Response(dict(detail=\"User not found in metricity\"),\n                                status=status.HTTP_404_NOT_FOUND)\n", "path": "pydis_site/apps/api/viewsets/bot/user.py"}], "after_files": [{"content": "import typing\nfrom collections import OrderedDict\n\nfrom django.db.models import Q\nfrom django_filters.rest_framework import DjangoFilterBackend\nfrom rest_framework import status\nfrom rest_framework.decorators import action\nfrom rest_framework.pagination import PageNumberPagination\nfrom rest_framework.request import Request\nfrom rest_framework.response import Response\nfrom rest_framework.serializers import ModelSerializer\nfrom rest_framework.viewsets import ModelViewSet\n\nfrom pydis_site.apps.api.models.bot.infraction import Infraction\nfrom pydis_site.apps.api.models.bot.metricity import Metricity, NotFoundError\nfrom pydis_site.apps.api.models.bot.user import User\nfrom pydis_site.apps.api.serializers import UserSerializer\n\n\nclass UserListPagination(PageNumberPagination):\n    \"\"\"Custom pagination class for the User Model.\"\"\"\n\n    page_size = 2500\n    page_size_query_param = \"page_size\"\n\n    def get_next_page_number(self) -> typing.Optional[int]:\n        \"\"\"Get the next page number.\"\"\"\n        if not self.page.has_next():\n            return None\n        page_number = self.page.next_page_number()\n        return page_number\n\n    def get_previous_page_number(self) -> typing.Optional[int]:\n        \"\"\"Get the previous page number.\"\"\"\n        if not self.page.has_previous():\n            return None\n\n        page_number = self.page.previous_page_number()\n        return page_number\n\n    def get_paginated_response(self, data: list) -> Response:\n        \"\"\"Override method to send modified response.\"\"\"\n        return Response(OrderedDict([\n            ('count', self.page.paginator.count),\n            ('next_page_no', self.get_next_page_number()),\n            ('previous_page_no', self.get_previous_page_number()),\n            ('results', data)\n        ]))\n\n\nclass UserViewSet(ModelViewSet):\n    \"\"\"\n    View providing CRUD operations on Discord users through the bot.\n\n    ## Routes\n    ### GET /bot/users\n    Returns all users currently known with pagination.\n\n    #### Response format\n    >>> {\n    ...     'count': 95000,\n    ...     'next_page_no': \"2\",\n    ...     'previous_page_no': None,\n    ...     'results': [\n    ...      {\n    ...         'id': 409107086526644234,\n    ...         'name': \"Python\",\n    ...         'discriminator': 4329,\n    ...         'roles': [\n    ...             352427296948486144,\n    ...             270988689419665409,\n    ...             277546923144249364,\n    ...             458226699344019457\n    ...         ],\n    ...         'in_guild': True\n    ...     },\n    ...     ]\n    ... }\n\n    #### Optional Query Parameters\n    - username: username to search for\n    - discriminator: discriminator to search for\n    - page_size: number of Users in one page, defaults to 10,000\n    - page: page number\n\n    #### Status codes\n    - 200: returned on success\n\n    ### GET /bot/users/<snowflake:int>\n    Gets a single user by ID.\n\n    #### Response format\n    >>> {\n    ...     'id': 409107086526644234,\n    ...     'name': \"Python\",\n    ...     'discriminator': 4329,\n    ...     'roles': [\n    ...         352427296948486144,\n    ...         270988689419665409,\n    ...         277546923144249364,\n    ...         458226699344019457\n    ...     ],\n    ...     'in_guild': True\n    ... }\n\n    #### Status codes\n    - 200: returned on success\n    - 404: if a user with the given `snowflake` could not be found\n\n    ### GET /bot/users/<snowflake:int>/metricity_data\n    Gets metricity data for a single user by ID.\n\n    #### Response format\n    >>> {\n    ...    \"joined_at\": \"2020-10-06T21:54:23.540766\",\n    ...    \"total_messages\": 2,\n    ...    \"voice_banned\": False,\n    ...    \"activity_blocks\": 1\n    ...}\n\n    #### Status codes\n    - 200: returned on success\n    - 404: if a user with the given `snowflake` could not be found\n\n    ### GET /bot/users/<snowflake:int>/metricity_review_data\n    Gets metricity data for a single user's review by ID.\n\n    #### Response format\n    >>> {\n    ...     'joined_at': '2020-08-26T08:09:43.507000',\n    ...     'top_channel_activity': [['off-topic', 15],\n    ...                              ['talent-pool', 4],\n    ...                              ['defcon', 2]],\n    ...     'total_messages': 22\n    ... }\n\n    #### Status codes\n    - 200: returned on success\n    - 404: if a user with the given `snowflake` could not be found\n\n    ### POST /bot/users\n    Adds a single or multiple new users.\n    The roles attached to the user(s) must be roles known by the site.\n    Users that already exist in the database will be skipped.\n\n    #### Request body\n    >>> {\n    ...     'id': int,\n    ...     'name': str,\n    ...     'discriminator': int,\n    ...     'roles': List[int],\n    ...     'in_guild': bool\n    ... }\n\n    Alternatively, request users can be POSTed as a list of above objects,\n    in which case multiple users will be created at once. In this case,\n    the response is an empty list.\n\n    #### Status codes\n    - 201: returned on success\n    - 400: if one of the given roles does not exist, or one of the given fields is invalid\n    - 400: if multiple user objects with the same id are given\n\n    ### PUT /bot/users/<snowflake:int>\n    Update the user with the given `snowflake`.\n    All fields in the request body are required.\n\n    #### Request body\n    >>> {\n    ...     'id': int,\n    ...     'name': str,\n    ...     'discriminator': int,\n    ...     'roles': List[int],\n    ...     'in_guild': bool\n    ... }\n\n    #### Status codes\n    - 200: returned on success\n    - 400: if the request body was invalid, see response body for details\n    - 404: if the user with the given `snowflake` could not be found\n\n    ### PATCH /bot/users/<snowflake:int>\n    Update the user with the given `snowflake`.\n    All fields in the request body are optional.\n\n    #### Request body\n    >>> {\n    ...     'id': int,\n    ...     'name': str,\n    ...     'discriminator': int,\n    ...     'roles': List[int],\n    ...     'in_guild': bool\n    ... }\n\n    #### Status codes\n    - 200: returned on success\n    - 400: if the request body was invalid, see response body for details\n    - 404: if the user with the given `snowflake` could not be found\n\n    ### BULK PATCH /bot/users/bulk_patch\n    Update users with the given `ids` and `details`.\n    `id` field and at least one other field is mandatory.\n\n    #### Request body\n    >>> [\n    ...     {\n    ...         'id': int,\n    ...         'name': str,\n    ...         'discriminator': int,\n    ...         'roles': List[int],\n    ...         'in_guild': bool\n    ...     },\n    ...     {\n    ...         'id': int,\n    ...         'name': str,\n    ...         'discriminator': int,\n    ...         'roles': List[int],\n    ...         'in_guild': bool\n    ...     },\n    ... ]\n\n    #### Status codes\n    - 200: returned on success\n    - 400: if the request body was invalid, see response body for details\n    - 400: if multiple user objects with the same id are given\n    - 404: if the user with the given id does not exist\n\n    ### DELETE /bot/users/<snowflake:int>\n    Deletes the user with the given `snowflake`.\n\n    #### Status codes\n    - 204: returned on success\n    - 404: if a user with the given `snowflake` does not exist\n    \"\"\"\n\n    serializer_class = UserSerializer\n    queryset = User.objects.all().order_by(\"id\")\n    pagination_class = UserListPagination\n    filter_backends = (DjangoFilterBackend,)\n    filter_fields = ('name', 'discriminator')\n\n    def get_serializer(self, *args, **kwargs) -> ModelSerializer:\n        \"\"\"Set Serializer many attribute to True if request body contains a list.\"\"\"\n        if isinstance(kwargs.get('data', {}), list):\n            kwargs['many'] = True\n\n        return super().get_serializer(*args, **kwargs)\n\n    @action(detail=False, methods=[\"PATCH\"], name='user-bulk-patch')\n    def bulk_patch(self, request: Request) -> Response:\n        \"\"\"Update multiple User objects in a single request.\"\"\"\n        serializer = self.get_serializer(\n            instance=self.get_queryset(),\n            data=request.data,\n            many=True,\n            partial=True\n        )\n\n        serializer.is_valid(raise_exception=True)\n        serializer.save()\n\n        return Response(serializer.data, status=status.HTTP_200_OK)\n\n    @action(detail=True)\n    def metricity_data(self, request: Request, pk: str = None) -> Response:\n        \"\"\"Request handler for metricity_data endpoint.\"\"\"\n        user = self.get_object()\n\n        has_voice_infraction = Infraction.objects.filter(\n            Q(user__id=user.id, active=True),\n            Q(type=\"voice_ban\") | Q(type=\"voice_mute\")\n        ).exists()\n\n        with Metricity() as metricity:\n            try:\n                data = metricity.user(user.id)\n\n                data[\"total_messages\"] = metricity.total_messages(user.id)\n                data[\"activity_blocks\"] = metricity.total_message_blocks(user.id)\n\n                data[\"voice_gate_blocked\"] = has_voice_infraction\n                return Response(data, status=status.HTTP_200_OK)\n            except NotFoundError:\n                return Response(dict(detail=\"User not found in metricity\"),\n                                status=status.HTTP_404_NOT_FOUND)\n\n    @action(detail=True)\n    def metricity_review_data(self, request: Request, pk: str = None) -> Response:\n        \"\"\"Request handler for metricity_review_data endpoint.\"\"\"\n        user = self.get_object()\n\n        with Metricity() as metricity:\n            try:\n                data = metricity.user(user.id)\n                data[\"total_messages\"] = metricity.total_messages(user.id)\n                data[\"top_channel_activity\"] = metricity.top_channel_activity(user.id)\n                return Response(data, status=status.HTTP_200_OK)\n            except NotFoundError:\n                return Response(dict(detail=\"User not found in metricity\"),\n                                status=status.HTTP_404_NOT_FOUND)\n", "path": "pydis_site/apps/api/viewsets/bot/user.py"}]}