We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Internal Server Error when searching with partial dates in Find a Record
## Description
<!-- A clear and concise description of what the bug is. -->
Sometimes when partially entering the date in the Record Search Field, it randomly throws internal server errors.
Sometimes it throws the intended `InvalidDateFormatAPIException` but other times it `UnboundLocalError`.

Here is a recording of the behaviour:

https://github.com/mathesar-foundation/mathesar/assets/31622972/6e7885c2-9d19-473a-86e0-8c63c5e68c0f


Also, it would be good UI/UX to show the user the date is invalid. In case when the user has just put an incorrect date time format and the server returns a 400 BAD REQUEST InvalidDateFormatAPIException. The user won't know as the table still shows the loading state.
<img width="1700" alt="image" src="https://github.com/mathesar-foundation/mathesar/assets/31622972/78fac439-c326-47ae-8189-08dc61963d75">


## Expected behavior
<!-- A clear and concise description of what you expected to happen. -->
- Server should not throw 500 internal server error and always throw InvalidDateFormatAPIException in case of wrong date format.
- User should be conveyed if their input date is incorrect.

## To Reproduce
<!-- How can we recreate this bug? Please try to provide a Minimal, Complete, and Verifiable (http://stackoverflow.com/help/mcve) example if code-related. -->

## Environment
 - OS: macOS
 - Browser: Chrome
 - Other info:

## Additional context
<!-- Add any other context about the problem or screenshots here. -->


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `mathesar/api/db/viewsets/records.py`
Content:
```
1 from psycopg2.errors import ForeignKeyViolation, InvalidDatetimeFormat
2 from rest_access_policy import AccessViewSetMixin
3 from rest_framework import status, viewsets
4 from rest_framework.exceptions import NotFound, MethodNotAllowed
5 from rest_framework.renderers import BrowsableAPIRenderer
6 from rest_framework.response import Response
7 from rest_framework.permissions import IsAuthenticatedOrReadOnly
8 from sqlalchemy.exc import IntegrityError, DataError
9 
10 from mathesar.api.db.permissions.records import RecordAccessPolicy
11 from mathesar.api.exceptions.error_codes import ErrorCodes
12 import mathesar.api.exceptions.database_exceptions.exceptions as database_api_exceptions
13 import mathesar.api.exceptions.generic_exceptions.base_exceptions as generic_api_exceptions
14 from db.functions.exceptions import (
15     BadDBFunctionFormat, ReferencedColumnsDontExist, UnknownDBFunctionID,
16 )
17 from db.records.exceptions import (
18     BadGroupFormat, GroupFieldNotFound, InvalidGroupType, UndefinedFunction,
19     BadSortFormat, SortFieldNotFound
20 )
21 from mathesar.api.pagination import TableLimitOffsetPagination
22 from mathesar.api.serializers.records import RecordListParameterSerializer, RecordSerializer
23 from mathesar.api.utils import get_table_or_404
24 from mathesar.functions.operations.convert import rewrite_db_function_spec_column_ids_to_names
25 from mathesar.models.base import Table
26 from mathesar.utils.json import MathesarJSONRenderer
27 
28 
29 class RecordViewSet(AccessViewSetMixin, viewsets.ViewSet):
30     permission_classes = [IsAuthenticatedOrReadOnly]
31     access_policy = RecordAccessPolicy
32 
33     # There is no 'update' method.
34     # We're not supporting PUT requests because there aren't a lot of use cases
35     # where the entire record needs to be replaced, PATCH suffices for updates.
36     def get_queryset(self):
37         return Table.objects.all().order_by('-created_at')
38 
39     renderer_classes = [MathesarJSONRenderer, BrowsableAPIRenderer]
40 
41     # For filter parameter formatting, see:
42     # db/functions/operations/deserialize.py::get_db_function_from_ma_function_spec function doc>
43     # For sorting parameter formatting, see:
44     # https://github.com/centerofci/sqlalchemy-filters#sort-format
45     def list(self, request, table_pk=None):
46         paginator = TableLimitOffsetPagination()
47 
48         serializer = RecordListParameterSerializer(data=request.GET)
49         serializer.is_valid(raise_exception=True)
50         table = get_table_or_404(table_pk)
51 
52         filter_unprocessed = serializer.validated_data['filter']
53         order_by = serializer.validated_data['order_by']
54         grouping = serializer.validated_data['grouping']
55         search_fuzzy = serializer.validated_data['search_fuzzy']
56         filter_processed = None
57         column_names_to_ids = table.get_column_name_id_bidirectional_map()
58         column_ids_to_names = column_names_to_ids.inverse
59         if filter_unprocessed:
60             filter_processed = rewrite_db_function_spec_column_ids_to_names(
61                 column_ids_to_names=column_ids_to_names,
62                 spec=filter_unprocessed,
63             )
64         # Replace column id value used in the `field` property with column name
65         name_converted_group_by = None
66         if grouping:
67             group_by_columns_names = [column_ids_to_names[column_id] for column_id in grouping['columns']]
68             name_converted_group_by = {**grouping, 'columns': group_by_columns_names}
69         name_converted_order_by = [{**column, 'field': column_ids_to_names[column['field']]} for column in order_by]
70         name_converted_search = [{**column, 'column': column_ids_to_names[column['field']]} for column in search_fuzzy]
71 
72         try:
73             records = paginator.paginate_queryset(
74                 self.get_queryset(), request, table, column_names_to_ids,
75                 filters=filter_processed,
76                 order_by=name_converted_order_by,
77                 grouping=name_converted_group_by,
78                 search=name_converted_search,
79                 duplicate_only=serializer.validated_data['duplicate_only']
80             )
81         except (BadDBFunctionFormat, UnknownDBFunctionID, ReferencedColumnsDontExist) as e:
82             raise database_api_exceptions.BadFilterAPIException(
83                 e,
84                 field='filters',
85                 status_code=status.HTTP_400_BAD_REQUEST
86             )
87         except (BadSortFormat, SortFieldNotFound) as e:
88             raise database_api_exceptions.BadSortAPIException(
89                 e,
90                 field='order_by',
91                 status_code=status.HTTP_400_BAD_REQUEST
92             )
93         except (BadGroupFormat, GroupFieldNotFound, InvalidGroupType) as e:
94             raise database_api_exceptions.BadGroupAPIException(
95                 e,
96                 field='grouping',
97                 status_code=status.HTTP_400_BAD_REQUEST
98             )
99         except UndefinedFunction as e:
100             raise database_api_exceptions.UndefinedFunctionAPIException(
101                 e,
102                 details=e.args[0],
103                 status_code=status.HTTP_400_BAD_REQUEST
104             )
105         except DataError as e:
106             if isinstance(e.orig, InvalidDatetimeFormat):
107                 raise database_api_exceptions.InvalidDateFormatAPIException(
108                     e,
109                     status_code=status.HTTP_400_BAD_REQUEST,
110                 )
111 
112         serializer = RecordSerializer(
113             records,
114             many=True,
115             context=self.get_serializer_context(table)
116         )
117         return paginator.get_paginated_response(serializer.data)
118 
119     def retrieve(self, request, pk=None, table_pk=None):
120         table = get_table_or_404(table_pk)
121         # TODO refactor to use serializer for more DRY response logic
122         paginator = TableLimitOffsetPagination()
123         record_filters = {
124             "equal": [
125                 {"column_name": [table.primary_key_column_name]},
126                 {"literal": [pk]}
127             ]
128         }
129         column_names_to_ids = table.get_column_name_id_bidirectional_map()
130         records = paginator.paginate_queryset(
131             table,
132             request,
133             table,
134             column_names_to_ids,
135             filters=record_filters
136         )
137         if not records:
138             raise NotFound
139         serializer = RecordSerializer(
140             records,
141             many=True,
142             context=self.get_serializer_context(table)
143         )
144         return paginator.get_paginated_response(serializer.data)
145 
146     def create(self, request, table_pk=None):
147         table = get_table_or_404(table_pk)
148         primary_key_column_name = None
149         try:
150             primary_key_column_name = table.primary_key_column_name
151         except AssertionError:
152             raise generic_api_exceptions.MethodNotAllowedAPIException(
153                 MethodNotAllowed,
154                 error_code=ErrorCodes.MethodNotAllowed.value,
155                 message="You cannot insert into tables without a primary key"
156             )
157         serializer = RecordSerializer(data=request.data, context=self.get_serializer_context(table))
158         serializer.is_valid(raise_exception=True)
159         serializer.save()
160         # TODO refactor to use serializer for more DRY response logic
161         column_name_id_map = table.get_column_name_id_bidirectional_map()
162         table_pk_column_id = column_name_id_map[primary_key_column_name]
163         pk_value = serializer.data[table_pk_column_id]
164         paginator = TableLimitOffsetPagination()
165         record_filters = {
166             "equal": [
167                 {"column_name": [primary_key_column_name]},
168                 {"literal": [pk_value]}
169             ]
170         }
171         column_names_to_ids = table.get_column_name_id_bidirectional_map()
172         records = paginator.paginate_queryset(
173             table,
174             request,
175             table,
176             column_names_to_ids,
177             filters=record_filters
178         )
179         serializer = RecordSerializer(
180             records,
181             many=True,
182             context=self.get_serializer_context(table)
183         )
184         response = paginator.get_paginated_response(serializer.data)
185         response.status_code = status.HTTP_201_CREATED
186         return response
187 
188     def partial_update(self, request, pk=None, table_pk=None):
189         table = get_table_or_404(table_pk)
190         serializer = RecordSerializer(
191             {'id': pk},
192             data=request.data,
193             context=self.get_serializer_context(table),
194             partial=True
195         )
196         serializer.is_valid(raise_exception=True)
197         serializer.save()
198         # TODO refactor to use serializer for more DRY response logic
199         paginator = TableLimitOffsetPagination()
200         record_filters = {
201             "equal": [
202                 {"column_name": [table.primary_key_column_name]},
203                 {"literal": [pk]}
204             ]
205         }
206         column_names_to_ids = table.get_column_name_id_bidirectional_map()
207         records = paginator.paginate_queryset(
208             table,
209             request,
210             table,
211             column_names_to_ids,
212             filters=record_filters
213         )
214         serializer = RecordSerializer(
215             records,
216             many=True,
217             context=self.get_serializer_context(table)
218         )
219         return paginator.get_paginated_response(serializer.data)
220 
221     def destroy(self, request, pk=None, table_pk=None):
222         table = get_table_or_404(table_pk)
223         if table.get_record(pk) is None:
224             raise generic_api_exceptions.NotFoundAPIException(
225                 NotFound,
226                 error_code=ErrorCodes.RecordNotFound.value,
227                 message="Record doesn't exist"
228             )
229         try:
230             table.delete_record(pk)
231         except IntegrityError as e:
232             if isinstance(e.orig, ForeignKeyViolation):
233                 raise database_api_exceptions.ForeignKeyViolationAPIException(
234                     e,
235                     status_code=status.HTTP_400_BAD_REQUEST,
236                     referent_table=table,
237                 )
238 
239         return Response(status=status.HTTP_204_NO_CONTENT)
240 
241     def get_serializer_context(self, table):
242         columns_map = table.get_column_name_id_bidirectional_map()
243         context = {'columns_map': columns_map, 'table': table}
244         return context
245
```
--- 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/mathesar/api/db/viewsets/records.py b/mathesar/api/db/viewsets/records.py
--- a/mathesar/api/db/viewsets/records.py
+++ b/mathesar/api/db/viewsets/records.py
@@ -1,4 +1,4 @@
-from psycopg2.errors import ForeignKeyViolation, InvalidDatetimeFormat
+from psycopg2.errors import ForeignKeyViolation, InvalidDatetimeFormat, DatetimeFieldOverflow
 from rest_access_policy import AccessViewSetMixin
 from rest_framework import status, viewsets
 from rest_framework.exceptions import NotFound, MethodNotAllowed
@@ -108,6 +108,16 @@
                     e,
                     status_code=status.HTTP_400_BAD_REQUEST,
                 )
+            elif isinstance(e.orig, DatetimeFieldOverflow):
+                raise database_api_exceptions.InvalidDateAPIException(
+                    e,
+                    status_code=status.HTTP_400_BAD_REQUEST,
+                )
+            else:
+                raise database_api_exceptions.MathesarAPIException(
+                    e,
+                    status_code=status.HTTP_400_BAD_REQUEST
+                )
 
         serializer = RecordSerializer(
             records,

{"golden_diff": "diff --git a/mathesar/api/db/viewsets/records.py b/mathesar/api/db/viewsets/records.py\n--- a/mathesar/api/db/viewsets/records.py\n+++ b/mathesar/api/db/viewsets/records.py\n@@ -1,4 +1,4 @@\n-from psycopg2.errors import ForeignKeyViolation, InvalidDatetimeFormat\n+from psycopg2.errors import ForeignKeyViolation, InvalidDatetimeFormat, DatetimeFieldOverflow\n from rest_access_policy import AccessViewSetMixin\n from rest_framework import status, viewsets\n from rest_framework.exceptions import NotFound, MethodNotAllowed\n@@ -108,6 +108,16 @@\n                     e,\n                     status_code=status.HTTP_400_BAD_REQUEST,\n                 )\n+            elif isinstance(e.orig, DatetimeFieldOverflow):\n+                raise database_api_exceptions.InvalidDateAPIException(\n+                    e,\n+                    status_code=status.HTTP_400_BAD_REQUEST,\n+                )\n+            else:\n+                raise database_api_exceptions.MathesarAPIException(\n+                    e,\n+                    status_code=status.HTTP_400_BAD_REQUEST\n+                )\n \n         serializer = RecordSerializer(\n             records,\n", "issue": "Internal Server Error when searching with partial dates in Find a Record\n## Description\r\n<!-- A clear and concise description of what the bug is. -->\r\nSometimes when partially entering the date in the Record Search Field, it randomly throws internal server errors.\r\nSometimes it throws the intended `InvalidDateFormatAPIException` but other times it `UnboundLocalError`.\r\n\r\nHere is a recording of the behaviour:\r\n\r\nhttps://github.com/mathesar-foundation/mathesar/assets/31622972/6e7885c2-9d19-473a-86e0-8c63c5e68c0f\r\n\r\n\r\nAlso, it would be good UI/UX to show the user the date is invalid. In case when the user has just put an incorrect date time format and the server returns a 400 BAD REQUEST InvalidDateFormatAPIException. The user won't know as the table still shows the loading state.\r\n<img width=\"1700\" alt=\"image\" src=\"https://github.com/mathesar-foundation/mathesar/assets/31622972/78fac439-c326-47ae-8189-08dc61963d75\">\r\n\r\n\r\n## Expected behavior\r\n<!-- A clear and concise description of what you expected to happen. -->\r\n- Server should not throw 500 internal server error and always throw InvalidDateFormatAPIException in case of wrong date format.\r\n- User should be conveyed if their input date is incorrect.\r\n\r\n## To Reproduce\r\n<!-- How can we recreate this bug? Please try to provide a Minimal, Complete, and Verifiable (http://stackoverflow.com/help/mcve) example if code-related. -->\r\n\r\n## Environment\r\n - OS: macOS\r\n - Browser: Chrome\r\n - Other info:\r\n\r\n## Additional context\r\n<!-- Add any other context about the problem or screenshots here. -->\r\n\n", "before_files": [{"content": "from psycopg2.errors import ForeignKeyViolation, InvalidDatetimeFormat\nfrom rest_access_policy import AccessViewSetMixin\nfrom rest_framework import status, viewsets\nfrom rest_framework.exceptions import NotFound, MethodNotAllowed\nfrom rest_framework.renderers import BrowsableAPIRenderer\nfrom rest_framework.response import Response\nfrom rest_framework.permissions import IsAuthenticatedOrReadOnly\nfrom sqlalchemy.exc import IntegrityError, DataError\n\nfrom mathesar.api.db.permissions.records import RecordAccessPolicy\nfrom mathesar.api.exceptions.error_codes import ErrorCodes\nimport mathesar.api.exceptions.database_exceptions.exceptions as database_api_exceptions\nimport mathesar.api.exceptions.generic_exceptions.base_exceptions as generic_api_exceptions\nfrom db.functions.exceptions import (\n    BadDBFunctionFormat, ReferencedColumnsDontExist, UnknownDBFunctionID,\n)\nfrom db.records.exceptions import (\n    BadGroupFormat, GroupFieldNotFound, InvalidGroupType, UndefinedFunction,\n    BadSortFormat, SortFieldNotFound\n)\nfrom mathesar.api.pagination import TableLimitOffsetPagination\nfrom mathesar.api.serializers.records import RecordListParameterSerializer, RecordSerializer\nfrom mathesar.api.utils import get_table_or_404\nfrom mathesar.functions.operations.convert import rewrite_db_function_spec_column_ids_to_names\nfrom mathesar.models.base import Table\nfrom mathesar.utils.json import MathesarJSONRenderer\n\n\nclass RecordViewSet(AccessViewSetMixin, viewsets.ViewSet):\n    permission_classes = [IsAuthenticatedOrReadOnly]\n    access_policy = RecordAccessPolicy\n\n    # There is no 'update' method.\n    # We're not supporting PUT requests because there aren't a lot of use cases\n    # where the entire record needs to be replaced, PATCH suffices for updates.\n    def get_queryset(self):\n        return Table.objects.all().order_by('-created_at')\n\n    renderer_classes = [MathesarJSONRenderer, BrowsableAPIRenderer]\n\n    # For filter parameter formatting, see:\n    # db/functions/operations/deserialize.py::get_db_function_from_ma_function_spec function doc>\n    # For sorting parameter formatting, see:\n    # https://github.com/centerofci/sqlalchemy-filters#sort-format\n    def list(self, request, table_pk=None):\n        paginator = TableLimitOffsetPagination()\n\n        serializer = RecordListParameterSerializer(data=request.GET)\n        serializer.is_valid(raise_exception=True)\n        table = get_table_or_404(table_pk)\n\n        filter_unprocessed = serializer.validated_data['filter']\n        order_by = serializer.validated_data['order_by']\n        grouping = serializer.validated_data['grouping']\n        search_fuzzy = serializer.validated_data['search_fuzzy']\n        filter_processed = None\n        column_names_to_ids = table.get_column_name_id_bidirectional_map()\n        column_ids_to_names = column_names_to_ids.inverse\n        if filter_unprocessed:\n            filter_processed = rewrite_db_function_spec_column_ids_to_names(\n                column_ids_to_names=column_ids_to_names,\n                spec=filter_unprocessed,\n            )\n        # Replace column id value used in the `field` property with column name\n        name_converted_group_by = None\n        if grouping:\n            group_by_columns_names = [column_ids_to_names[column_id] for column_id in grouping['columns']]\n            name_converted_group_by = {**grouping, 'columns': group_by_columns_names}\n        name_converted_order_by = [{**column, 'field': column_ids_to_names[column['field']]} for column in order_by]\n        name_converted_search = [{**column, 'column': column_ids_to_names[column['field']]} for column in search_fuzzy]\n\n        try:\n            records = paginator.paginate_queryset(\n                self.get_queryset(), request, table, column_names_to_ids,\n                filters=filter_processed,\n                order_by=name_converted_order_by,\n                grouping=name_converted_group_by,\n                search=name_converted_search,\n                duplicate_only=serializer.validated_data['duplicate_only']\n            )\n        except (BadDBFunctionFormat, UnknownDBFunctionID, ReferencedColumnsDontExist) as e:\n            raise database_api_exceptions.BadFilterAPIException(\n                e,\n                field='filters',\n                status_code=status.HTTP_400_BAD_REQUEST\n            )\n        except (BadSortFormat, SortFieldNotFound) as e:\n            raise database_api_exceptions.BadSortAPIException(\n                e,\n                field='order_by',\n                status_code=status.HTTP_400_BAD_REQUEST\n            )\n        except (BadGroupFormat, GroupFieldNotFound, InvalidGroupType) as e:\n            raise database_api_exceptions.BadGroupAPIException(\n                e,\n                field='grouping',\n                status_code=status.HTTP_400_BAD_REQUEST\n            )\n        except UndefinedFunction as e:\n            raise database_api_exceptions.UndefinedFunctionAPIException(\n                e,\n                details=e.args[0],\n                status_code=status.HTTP_400_BAD_REQUEST\n            )\n        except DataError as e:\n            if isinstance(e.orig, InvalidDatetimeFormat):\n                raise database_api_exceptions.InvalidDateFormatAPIException(\n                    e,\n                    status_code=status.HTTP_400_BAD_REQUEST,\n                )\n\n        serializer = RecordSerializer(\n            records,\n            many=True,\n            context=self.get_serializer_context(table)\n        )\n        return paginator.get_paginated_response(serializer.data)\n\n    def retrieve(self, request, pk=None, table_pk=None):\n        table = get_table_or_404(table_pk)\n        # TODO refactor to use serializer for more DRY response logic\n        paginator = TableLimitOffsetPagination()\n        record_filters = {\n            \"equal\": [\n                {\"column_name\": [table.primary_key_column_name]},\n                {\"literal\": [pk]}\n            ]\n        }\n        column_names_to_ids = table.get_column_name_id_bidirectional_map()\n        records = paginator.paginate_queryset(\n            table,\n            request,\n            table,\n            column_names_to_ids,\n            filters=record_filters\n        )\n        if not records:\n            raise NotFound\n        serializer = RecordSerializer(\n            records,\n            many=True,\n            context=self.get_serializer_context(table)\n        )\n        return paginator.get_paginated_response(serializer.data)\n\n    def create(self, request, table_pk=None):\n        table = get_table_or_404(table_pk)\n        primary_key_column_name = None\n        try:\n            primary_key_column_name = table.primary_key_column_name\n        except AssertionError:\n            raise generic_api_exceptions.MethodNotAllowedAPIException(\n                MethodNotAllowed,\n                error_code=ErrorCodes.MethodNotAllowed.value,\n                message=\"You cannot insert into tables without a primary key\"\n            )\n        serializer = RecordSerializer(data=request.data, context=self.get_serializer_context(table))\n        serializer.is_valid(raise_exception=True)\n        serializer.save()\n        # TODO refactor to use serializer for more DRY response logic\n        column_name_id_map = table.get_column_name_id_bidirectional_map()\n        table_pk_column_id = column_name_id_map[primary_key_column_name]\n        pk_value = serializer.data[table_pk_column_id]\n        paginator = TableLimitOffsetPagination()\n        record_filters = {\n            \"equal\": [\n                {\"column_name\": [primary_key_column_name]},\n                {\"literal\": [pk_value]}\n            ]\n        }\n        column_names_to_ids = table.get_column_name_id_bidirectional_map()\n        records = paginator.paginate_queryset(\n            table,\n            request,\n            table,\n            column_names_to_ids,\n            filters=record_filters\n        )\n        serializer = RecordSerializer(\n            records,\n            many=True,\n            context=self.get_serializer_context(table)\n        )\n        response = paginator.get_paginated_response(serializer.data)\n        response.status_code = status.HTTP_201_CREATED\n        return response\n\n    def partial_update(self, request, pk=None, table_pk=None):\n        table = get_table_or_404(table_pk)\n        serializer = RecordSerializer(\n            {'id': pk},\n            data=request.data,\n            context=self.get_serializer_context(table),\n            partial=True\n        )\n        serializer.is_valid(raise_exception=True)\n        serializer.save()\n        # TODO refactor to use serializer for more DRY response logic\n        paginator = TableLimitOffsetPagination()\n        record_filters = {\n            \"equal\": [\n                {\"column_name\": [table.primary_key_column_name]},\n                {\"literal\": [pk]}\n            ]\n        }\n        column_names_to_ids = table.get_column_name_id_bidirectional_map()\n        records = paginator.paginate_queryset(\n            table,\n            request,\n            table,\n            column_names_to_ids,\n            filters=record_filters\n        )\n        serializer = RecordSerializer(\n            records,\n            many=True,\n            context=self.get_serializer_context(table)\n        )\n        return paginator.get_paginated_response(serializer.data)\n\n    def destroy(self, request, pk=None, table_pk=None):\n        table = get_table_or_404(table_pk)\n        if table.get_record(pk) is None:\n            raise generic_api_exceptions.NotFoundAPIException(\n                NotFound,\n                error_code=ErrorCodes.RecordNotFound.value,\n                message=\"Record doesn't exist\"\n            )\n        try:\n            table.delete_record(pk)\n        except IntegrityError as e:\n            if isinstance(e.orig, ForeignKeyViolation):\n                raise database_api_exceptions.ForeignKeyViolationAPIException(\n                    e,\n                    status_code=status.HTTP_400_BAD_REQUEST,\n                    referent_table=table,\n                )\n\n        return Response(status=status.HTTP_204_NO_CONTENT)\n\n    def get_serializer_context(self, table):\n        columns_map = table.get_column_name_id_bidirectional_map()\n        context = {'columns_map': columns_map, 'table': table}\n        return context\n", "path": "mathesar/api/db/viewsets/records.py"}], "after_files": [{"content": "from psycopg2.errors import ForeignKeyViolation, InvalidDatetimeFormat, DatetimeFieldOverflow\nfrom rest_access_policy import AccessViewSetMixin\nfrom rest_framework import status, viewsets\nfrom rest_framework.exceptions import NotFound, MethodNotAllowed\nfrom rest_framework.renderers import BrowsableAPIRenderer\nfrom rest_framework.response import Response\nfrom rest_framework.permissions import IsAuthenticatedOrReadOnly\nfrom sqlalchemy.exc import IntegrityError, DataError\n\nfrom mathesar.api.db.permissions.records import RecordAccessPolicy\nfrom mathesar.api.exceptions.error_codes import ErrorCodes\nimport mathesar.api.exceptions.database_exceptions.exceptions as database_api_exceptions\nimport mathesar.api.exceptions.generic_exceptions.base_exceptions as generic_api_exceptions\nfrom db.functions.exceptions import (\n    BadDBFunctionFormat, ReferencedColumnsDontExist, UnknownDBFunctionID,\n)\nfrom db.records.exceptions import (\n    BadGroupFormat, GroupFieldNotFound, InvalidGroupType, UndefinedFunction,\n    BadSortFormat, SortFieldNotFound\n)\nfrom mathesar.api.pagination import TableLimitOffsetPagination\nfrom mathesar.api.serializers.records import RecordListParameterSerializer, RecordSerializer\nfrom mathesar.api.utils import get_table_or_404\nfrom mathesar.functions.operations.convert import rewrite_db_function_spec_column_ids_to_names\nfrom mathesar.models.base import Table\nfrom mathesar.utils.json import MathesarJSONRenderer\n\n\nclass RecordViewSet(AccessViewSetMixin, viewsets.ViewSet):\n    permission_classes = [IsAuthenticatedOrReadOnly]\n    access_policy = RecordAccessPolicy\n\n    # There is no 'update' method.\n    # We're not supporting PUT requests because there aren't a lot of use cases\n    # where the entire record needs to be replaced, PATCH suffices for updates.\n    def get_queryset(self):\n        return Table.objects.all().order_by('-created_at')\n\n    renderer_classes = [MathesarJSONRenderer, BrowsableAPIRenderer]\n\n    # For filter parameter formatting, see:\n    # db/functions/operations/deserialize.py::get_db_function_from_ma_function_spec function doc>\n    # For sorting parameter formatting, see:\n    # https://github.com/centerofci/sqlalchemy-filters#sort-format\n    def list(self, request, table_pk=None):\n        paginator = TableLimitOffsetPagination()\n\n        serializer = RecordListParameterSerializer(data=request.GET)\n        serializer.is_valid(raise_exception=True)\n        table = get_table_or_404(table_pk)\n\n        filter_unprocessed = serializer.validated_data['filter']\n        order_by = serializer.validated_data['order_by']\n        grouping = serializer.validated_data['grouping']\n        search_fuzzy = serializer.validated_data['search_fuzzy']\n        filter_processed = None\n        column_names_to_ids = table.get_column_name_id_bidirectional_map()\n        column_ids_to_names = column_names_to_ids.inverse\n        if filter_unprocessed:\n            filter_processed = rewrite_db_function_spec_column_ids_to_names(\n                column_ids_to_names=column_ids_to_names,\n                spec=filter_unprocessed,\n            )\n        # Replace column id value used in the `field` property with column name\n        name_converted_group_by = None\n        if grouping:\n            group_by_columns_names = [column_ids_to_names[column_id] for column_id in grouping['columns']]\n            name_converted_group_by = {**grouping, 'columns': group_by_columns_names}\n        name_converted_order_by = [{**column, 'field': column_ids_to_names[column['field']]} for column in order_by]\n        name_converted_search = [{**column, 'column': column_ids_to_names[column['field']]} for column in search_fuzzy]\n\n        try:\n            records = paginator.paginate_queryset(\n                self.get_queryset(), request, table, column_names_to_ids,\n                filters=filter_processed,\n                order_by=name_converted_order_by,\n                grouping=name_converted_group_by,\n                search=name_converted_search,\n                duplicate_only=serializer.validated_data['duplicate_only']\n            )\n        except (BadDBFunctionFormat, UnknownDBFunctionID, ReferencedColumnsDontExist) as e:\n            raise database_api_exceptions.BadFilterAPIException(\n                e,\n                field='filters',\n                status_code=status.HTTP_400_BAD_REQUEST\n            )\n        except (BadSortFormat, SortFieldNotFound) as e:\n            raise database_api_exceptions.BadSortAPIException(\n                e,\n                field='order_by',\n                status_code=status.HTTP_400_BAD_REQUEST\n            )\n        except (BadGroupFormat, GroupFieldNotFound, InvalidGroupType) as e:\n            raise database_api_exceptions.BadGroupAPIException(\n                e,\n                field='grouping',\n                status_code=status.HTTP_400_BAD_REQUEST\n            )\n        except UndefinedFunction as e:\n            raise database_api_exceptions.UndefinedFunctionAPIException(\n                e,\n                details=e.args[0],\n                status_code=status.HTTP_400_BAD_REQUEST\n            )\n        except DataError as e:\n            if isinstance(e.orig, InvalidDatetimeFormat):\n                raise database_api_exceptions.InvalidDateFormatAPIException(\n                    e,\n                    status_code=status.HTTP_400_BAD_REQUEST,\n                )\n            elif isinstance(e.orig, DatetimeFieldOverflow):\n                raise database_api_exceptions.InvalidDateAPIException(\n                    e,\n                    status_code=status.HTTP_400_BAD_REQUEST,\n                )\n            else:\n                raise database_api_exceptions.MathesarAPIException(\n                    e,\n                    status_code=status.HTTP_400_BAD_REQUEST\n                )\n\n        serializer = RecordSerializer(\n            records,\n            many=True,\n            context=self.get_serializer_context(table)\n        )\n        return paginator.get_paginated_response(serializer.data)\n\n    def retrieve(self, request, pk=None, table_pk=None):\n        table = get_table_or_404(table_pk)\n        # TODO refactor to use serializer for more DRY response logic\n        paginator = TableLimitOffsetPagination()\n        record_filters = {\n            \"equal\": [\n                {\"column_name\": [table.primary_key_column_name]},\n                {\"literal\": [pk]}\n            ]\n        }\n        column_names_to_ids = table.get_column_name_id_bidirectional_map()\n        records = paginator.paginate_queryset(\n            table,\n            request,\n            table,\n            column_names_to_ids,\n            filters=record_filters\n        )\n        if not records:\n            raise NotFound\n        serializer = RecordSerializer(\n            records,\n            many=True,\n            context=self.get_serializer_context(table)\n        )\n        return paginator.get_paginated_response(serializer.data)\n\n    def create(self, request, table_pk=None):\n        table = get_table_or_404(table_pk)\n        primary_key_column_name = None\n        try:\n            primary_key_column_name = table.primary_key_column_name\n        except AssertionError:\n            raise generic_api_exceptions.MethodNotAllowedAPIException(\n                MethodNotAllowed,\n                error_code=ErrorCodes.MethodNotAllowed.value,\n                message=\"You cannot insert into tables without a primary key\"\n            )\n        serializer = RecordSerializer(data=request.data, context=self.get_serializer_context(table))\n        serializer.is_valid(raise_exception=True)\n        serializer.save()\n        # TODO refactor to use serializer for more DRY response logic\n        column_name_id_map = table.get_column_name_id_bidirectional_map()\n        table_pk_column_id = column_name_id_map[primary_key_column_name]\n        pk_value = serializer.data[table_pk_column_id]\n        paginator = TableLimitOffsetPagination()\n        record_filters = {\n            \"equal\": [\n                {\"column_name\": [primary_key_column_name]},\n                {\"literal\": [pk_value]}\n            ]\n        }\n        column_names_to_ids = table.get_column_name_id_bidirectional_map()\n        records = paginator.paginate_queryset(\n            table,\n            request,\n            table,\n            column_names_to_ids,\n            filters=record_filters\n        )\n        serializer = RecordSerializer(\n            records,\n            many=True,\n            context=self.get_serializer_context(table)\n        )\n        response = paginator.get_paginated_response(serializer.data)\n        response.status_code = status.HTTP_201_CREATED\n        return response\n\n    def partial_update(self, request, pk=None, table_pk=None):\n        table = get_table_or_404(table_pk)\n        serializer = RecordSerializer(\n            {'id': pk},\n            data=request.data,\n            context=self.get_serializer_context(table),\n            partial=True\n        )\n        serializer.is_valid(raise_exception=True)\n        serializer.save()\n        # TODO refactor to use serializer for more DRY response logic\n        paginator = TableLimitOffsetPagination()\n        record_filters = {\n            \"equal\": [\n                {\"column_name\": [table.primary_key_column_name]},\n                {\"literal\": [pk]}\n            ]\n        }\n        column_names_to_ids = table.get_column_name_id_bidirectional_map()\n        records = paginator.paginate_queryset(\n            table,\n            request,\n            table,\n            column_names_to_ids,\n            filters=record_filters\n        )\n        serializer = RecordSerializer(\n            records,\n            many=True,\n            context=self.get_serializer_context(table)\n        )\n        return paginator.get_paginated_response(serializer.data)\n\n    def destroy(self, request, pk=None, table_pk=None):\n        table = get_table_or_404(table_pk)\n        if table.get_record(pk) is None:\n            raise generic_api_exceptions.NotFoundAPIException(\n                NotFound,\n                error_code=ErrorCodes.RecordNotFound.value,\n                message=\"Record doesn't exist\"\n            )\n        try:\n            table.delete_record(pk)\n        except IntegrityError as e:\n            if isinstance(e.orig, ForeignKeyViolation):\n                raise database_api_exceptions.ForeignKeyViolationAPIException(\n                    e,\n                    status_code=status.HTTP_400_BAD_REQUEST,\n                    referent_table=table,\n                )\n\n        return Response(status=status.HTTP_204_NO_CONTENT)\n\n    def get_serializer_context(self, table):\n        columns_map = table.get_column_name_id_bidirectional_map()\n        context = {'columns_map': columns_map, 'table': table}\n        return context\n", "path": "mathesar/api/db/viewsets/records.py"}]}