We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Implement display options for Duration type
## Problem
The Duration Mathesar type supports the following display options:
- Min unit
- Max unit
- Whether to show unit labels

We will combine this into a single `format` string.

## Solution
(1) We should store these display options in the following format in the `display_options` field of the corresponding column.
```
{
  "format": "HH:mm:ss.SSS"
}
```

(2) We should also validate these so that:
- Only columns of this type can have these display options. They should not be able to be set if the column is of a different type.
- `format` should be a valid JavaScript duration format.

(3) If the column type is changed, the display options should be deleted.

(4) We should add supported display options to the `types` endpoint.

## Additional Context
- [Design of Duration type options on Figma](https://www.figma.com/proto/Uaf1ntcldzK2U41Jhw6vS2/Mathesar-MVP?page-id=4260%3A37440&node-id=4270%3A41231&viewport=324%2C48%2C0.29&scaling=contain&starting-point-node-id=4270%3A41231&show-proto-sidebar=1)
- Blocked by #658
- #392 

--- 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/display_options.py`
Content:
```
1 from mathesar.database.types import MathesarTypeIdentifier
2 
3 DISPLAY_OPTIONS_BY_TYPE_IDENTIFIER = {
4     MathesarTypeIdentifier.BOOLEAN.value:
5         {
6             "options": [{"name": "input", "type": "string",
7                          "enum": ['dropdown', 'checkbox']},
8                         {'name': "custom_labels", "type": "object",
9                          "items": [{"name": "TRUE", "type": "string"},
10                                    {'name': "FALSE", "type": "string"}]}]
11 
12         },
13     MathesarTypeIdentifier.NUMBER.value:
14         {
15             "options": [{"name": "show_as_percentage", "type": "boolean"},
16                         {"name": "locale", "type": "string"}]
17         },
18     MathesarTypeIdentifier.DATETIME.value:
19         {
20             "options": [{"name": "format", "type": "string"}]
21         }
22 }
23
```

Path: `mathesar/api/serializers/shared_serializers.py`
Content:
```
1 from abc import ABC, abstractmethod
2 
3 import arrow
4 from django.core.exceptions import ImproperlyConfigured
5 from rest_framework import serializers
6 
7 from mathesar.api.exceptions.mixins import MathesarErrorMessageMixin
8 from mathesar.database.types import MathesarTypeIdentifier, get_mathesar_type_from_db_type
9 
10 
11 class ReadOnlyPolymorphicSerializerMappingMixin:
12     """
13     This serializer mixin is helpful in serializing polymorphic models,
14     by switching to correct serializer based on the mapping field value.
15     """
16 
17     def __new__(cls, *args, **kwargs):
18         if cls.serializers_mapping is None:
19             raise ImproperlyConfigured(
20                 '`{cls}` is missing a '
21                 '`{cls}.model_serializer_mapping` attribute'.format(cls=cls.__name__)
22             )
23         return super().__new__(cls, *args, **kwargs)
24 
25     def __init__(self, *args, **kwargs):
26         super().__init__(*args, **kwargs)
27         self.serializers_cls_mapping = {}
28         serializers_mapping = self.serializers_mapping
29         self.serializers_mapping = {}
30         for identifier, serializer_cls in serializers_mapping.items():
31             if callable(serializer_cls):
32                 serializer = serializer_cls(*args, **kwargs)
33                 serializer.parent = self
34             else:
35                 serializer = serializer_cls
36             self.serializers_mapping[identifier] = serializer
37             self.serializers_cls_mapping[identifier] = serializer_cls
38 
39     def to_representation(self, instance):
40         serializer = self.serializers_mapping.get(self.get_mapping_field(), None)
41         if serializer is not None:
42             self.__class__ = self.serializers_cls_mapping.get(self.get_mapping_field())
43             return serializer.to_representation(instance)
44         else:
45             raise Exception(f"Cannot find a matching serializer for the specified type {self.get_mapping_field()}")
46 
47     def get_mapping_field(self):
48         mapping_field = getattr(self, "mapping_field", None)
49         if mapping_field is None:
50             raise Exception(
51                 "Add a `mapping_field` to be used as a identifier"
52                 "or override this method to return a identifier to identify a proper serializer"
53             )
54         return mapping_field
55 
56 
57 class ReadWritePolymorphicSerializerMappingMixin(ReadOnlyPolymorphicSerializerMappingMixin):
58     def to_internal_value(self, data):
59         serializer = self.serializers_mapping.get(self.get_mapping_field())
60         if serializer is not None:
61             self.__class__ = self.serializers_cls_mapping.get(self.get_mapping_field())
62             return serializer.to_internal_value(data=data)
63         else:
64             raise Exception(f"Cannot find a matching serializer for the specified type {self.get_mapping_field()}")
65 
66 
67 class MonkeyPatchPartial:
68     """
69     Work around bug #3847 in djangorestframework by monkey-patching the partial
70     attribute of the root serializer during the call to validate_empty_values.
71     https://github.com/encode/django-rest-framework/issues/3847
72     """
73 
74     def __init__(self, root):
75         self._root = root
76 
77     def __enter__(self):
78         self._old = getattr(self._root, 'partial')
79         setattr(self._root, 'partial', False)
80 
81     def __exit__(self, *args):
82         setattr(self._root, 'partial', self._old)
83 
84 
85 class OverrideRootPartialMixin:
86     """
87     This mixin is used to convert a serializer into a partial serializer,
88     based on the serializer `partial` property rather than the parent's `partial` property.
89     Refer to the issue
90         https://github.com/encode/django-rest-framework/issues/3847
91     """
92 
93     def run_validation(self, *args, **kwargs):
94         if not self.partial:
95             with MonkeyPatchPartial(self.root):
96                 return super().run_validation(*args, **kwargs)
97         return super().run_validation(*args, **kwargs)
98 
99 
100 class CustomBooleanLabelSerializer(MathesarErrorMessageMixin, serializers.Serializer):
101     TRUE = serializers.CharField()
102     FALSE = serializers.CharField()
103 
104 
105 DISPLAY_OPTIONS_SERIALIZER_MAPPING_KEY = 'mathesar_type'
106 
107 
108 class BooleanDisplayOptionSerializer(MathesarErrorMessageMixin, OverrideRootPartialMixin, serializers.Serializer):
109     input = serializers.ChoiceField(choices=[("dropdown", 1), ("checkbox", 2)])
110     custom_labels = CustomBooleanLabelSerializer(required=False)
111 
112 
113 class NumberDisplayOptionSerializer(MathesarErrorMessageMixin, OverrideRootPartialMixin, serializers.Serializer):
114     show_as_percentage = serializers.BooleanField(default=False)
115     locale = serializers.CharField(required=False)
116 
117 
118 class AbstractDateTimeFormatValidator(ABC):
119     requires_context = True
120 
121     def __init__(self):
122         pass
123 
124     def __call__(self, value, serializer_field):
125         self.date_format_validator(value, serializer_field)
126 
127     def date_format_validator(self, value, serializer_field):
128         try:
129             timestamp_with_tz_obj = arrow.get('2013-09-30T15:34:00.000-07:00')
130             parsed_datetime_str = timestamp_with_tz_obj.format(value)
131             datetime_object = arrow.get(parsed_datetime_str, value)
132         except ValueError:
133             raise serializers.ValidationError(f"{value} is not a valid format used for parsing a datetime.")
134         else:
135             self.validate(datetime_object, value, serializer_field)
136 
137     @abstractmethod
138     def validate(self, datetime_obj, display_format, serializer_field):
139         pass
140 
141 
142 class TimestampWithTimeZoneFormatValidator(AbstractDateTimeFormatValidator):
143 
144     def validate(self, datetime_obj, display_format, serializer_field):
145         pass
146 
147 
148 class TimestampWithoutTimeZoneFormatValidator(AbstractDateTimeFormatValidator):
149 
150     def validate(self, datetime_obj, display_format, serializer_field):
151         if 'z' in display_format.lower():
152             raise serializers.ValidationError(
153                 "Timestamp without timezone column cannot contain timezone display format"
154             )
155 
156 
157 class DateFormatValidator(AbstractDateTimeFormatValidator):
158 
159     def validate(self, datetime_obj, display_format, serializer_field):
160         date_obj = arrow.get('2013-09-30')
161         if datetime_obj.time() != date_obj.time():
162             raise serializers.ValidationError("Date column cannot contain time or timezone display format")
163 
164 
165 class TimeWithTimeZoneFormatValidator(AbstractDateTimeFormatValidator):
166 
167     def validate(self, datetime_obj, display_format, serializer_field):
168         time_only_format = 'HH:mm:ssZZ'
169         time_str = arrow.get('2013-09-30T15:34:00.000-07:00').format(time_only_format)
170         parsed_time_str = arrow.get(time_str, time_only_format)
171         if parsed_time_str.date() != datetime_obj.date():
172             raise serializers.ValidationError("Time column cannot contain date display format")
173 
174 
175 class TimeWithoutTimeZoneFormatValidator(TimeWithTimeZoneFormatValidator):
176 
177     def validate(self, datetime_obj, display_format, serializer_field):
178         if 'z' in display_format.lower():
179             raise serializers.ValidationError("Time without timezone column cannot contain timezone display format")
180         return super().validate(datetime_obj, display_format, serializer_field)
181 
182 
183 class DateDisplayOptionSerializer(MathesarErrorMessageMixin, OverrideRootPartialMixin, serializers.Serializer):
184     format = serializers.CharField(validators=[DateFormatValidator()])
185 
186 
187 class TimestampWithoutTimezoneDisplayOptionSerializer(
188     MathesarErrorMessageMixin,
189     OverrideRootPartialMixin,
190     serializers.Serializer
191 ):
192     format = serializers.CharField(validators=[TimestampWithoutTimeZoneFormatValidator()])
193 
194 
195 class TimestampWithTimezoneDisplayOptionSerializer(
196     MathesarErrorMessageMixin,
197     OverrideRootPartialMixin,
198     serializers.Serializer
199 ):
200     format = serializers.CharField(validators=[TimestampWithTimeZoneFormatValidator()])
201 
202 
203 class TimeWithTimezoneDisplayOptionSerializer(
204     MathesarErrorMessageMixin,
205     OverrideRootPartialMixin,
206     serializers.Serializer
207 ):
208     format = serializers.CharField(validators=[TimeWithTimeZoneFormatValidator()])
209 
210 
211 class TimeWithoutTimezoneDisplayOptionSerializer(
212     MathesarErrorMessageMixin,
213     OverrideRootPartialMixin,
214     serializers.Serializer
215 ):
216     format = serializers.CharField(validators=[TimeWithoutTimeZoneFormatValidator()])
217 
218 
219 class DisplayOptionsMappingSerializer(
220     MathesarErrorMessageMixin,
221     ReadWritePolymorphicSerializerMappingMixin,
222     serializers.Serializer
223 ):
224     serializers_mapping = {
225         MathesarTypeIdentifier.BOOLEAN.value: BooleanDisplayOptionSerializer,
226         MathesarTypeIdentifier.NUMBER.value: NumberDisplayOptionSerializer,
227         ('timestamp with time zone',
228          MathesarTypeIdentifier.DATETIME.value): TimestampWithTimezoneDisplayOptionSerializer,
229         ('timestamp without time zone',
230          MathesarTypeIdentifier.DATETIME.value): TimestampWithoutTimezoneDisplayOptionSerializer,
231         ('date', MathesarTypeIdentifier.DATETIME.value): DateDisplayOptionSerializer,
232         ('time with time zone', MathesarTypeIdentifier.DATETIME.value): TimeWithTimezoneDisplayOptionSerializer,
233         ('time without time zone', MathesarTypeIdentifier.DATETIME.value): TimeWithoutTimezoneDisplayOptionSerializer,
234     }
235 
236     def get_mapping_field(self):
237         mathesar_type = get_mathesar_type_from_db_type(self.context[DISPLAY_OPTIONS_SERIALIZER_MAPPING_KEY])
238         if mathesar_type == MathesarTypeIdentifier.DATETIME.value:
239             return self.context[DISPLAY_OPTIONS_SERIALIZER_MAPPING_KEY].lower(), mathesar_type
240         else:
241             return mathesar_type
242
```
--- 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/display_options.py b/mathesar/api/display_options.py
--- a/mathesar/api/display_options.py
+++ b/mathesar/api/display_options.py
@@ -16,6 +16,10 @@
                         {"name": "locale", "type": "string"}]
         },
     MathesarTypeIdentifier.DATETIME.value:
+        {
+            "options": [{"name": "format", "type": "string"}]
+        },
+    MathesarTypeIdentifier.DURATION.value:
         {
             "options": [{"name": "format", "type": "string"}]
         }
diff --git a/mathesar/api/serializers/shared_serializers.py b/mathesar/api/serializers/shared_serializers.py
--- a/mathesar/api/serializers/shared_serializers.py
+++ b/mathesar/api/serializers/shared_serializers.py
@@ -165,7 +165,7 @@
 class TimeWithTimeZoneFormatValidator(AbstractDateTimeFormatValidator):
 
     def validate(self, datetime_obj, display_format, serializer_field):
-        time_only_format = 'HH:mm:ssZZ'
+        time_only_format = 'HH:mm:ss.SSSZZ'
         time_str = arrow.get('2013-09-30T15:34:00.000-07:00').format(time_only_format)
         parsed_time_str = arrow.get(time_str, time_only_format)
         if parsed_time_str.date() != datetime_obj.date():
@@ -180,6 +180,15 @@
         return super().validate(datetime_obj, display_format, serializer_field)
 
 
+class DurationFormatValidator(AbstractDateTimeFormatValidator):
+
+    def validate(self, datetime_obj, display_format, serializer_field):
+        if 'z' in display_format.lower():
+            raise serializers.ValidationError(
+                "Duration column cannot contain timezone display format"
+            )
+
+
 class DateDisplayOptionSerializer(MathesarErrorMessageMixin, OverrideRootPartialMixin, serializers.Serializer):
     format = serializers.CharField(validators=[DateFormatValidator()])
 
@@ -216,6 +225,10 @@
     format = serializers.CharField(validators=[TimeWithoutTimeZoneFormatValidator()])
 
 
+class DurationDisplayOptionSerializer(MathesarErrorMessageMixin, OverrideRootPartialMixin, serializers.Serializer):
+    format = serializers.CharField(validators=[DurationFormatValidator()])
+
+
 class DisplayOptionsMappingSerializer(
     MathesarErrorMessageMixin,
     ReadWritePolymorphicSerializerMappingMixin,
@@ -231,6 +244,7 @@
         ('date', MathesarTypeIdentifier.DATETIME.value): DateDisplayOptionSerializer,
         ('time with time zone', MathesarTypeIdentifier.DATETIME.value): TimeWithTimezoneDisplayOptionSerializer,
         ('time without time zone', MathesarTypeIdentifier.DATETIME.value): TimeWithoutTimezoneDisplayOptionSerializer,
+        MathesarTypeIdentifier.DURATION.value: DurationDisplayOptionSerializer,
     }
 
     def get_mapping_field(self):

{"golden_diff": "diff --git a/mathesar/api/display_options.py b/mathesar/api/display_options.py\n--- a/mathesar/api/display_options.py\n+++ b/mathesar/api/display_options.py\n@@ -16,6 +16,10 @@\n                         {\"name\": \"locale\", \"type\": \"string\"}]\n         },\n     MathesarTypeIdentifier.DATETIME.value:\n+        {\n+            \"options\": [{\"name\": \"format\", \"type\": \"string\"}]\n+        },\n+    MathesarTypeIdentifier.DURATION.value:\n         {\n             \"options\": [{\"name\": \"format\", \"type\": \"string\"}]\n         }\ndiff --git a/mathesar/api/serializers/shared_serializers.py b/mathesar/api/serializers/shared_serializers.py\n--- a/mathesar/api/serializers/shared_serializers.py\n+++ b/mathesar/api/serializers/shared_serializers.py\n@@ -165,7 +165,7 @@\n class TimeWithTimeZoneFormatValidator(AbstractDateTimeFormatValidator):\n \n     def validate(self, datetime_obj, display_format, serializer_field):\n-        time_only_format = 'HH:mm:ssZZ'\n+        time_only_format = 'HH:mm:ss.SSSZZ'\n         time_str = arrow.get('2013-09-30T15:34:00.000-07:00').format(time_only_format)\n         parsed_time_str = arrow.get(time_str, time_only_format)\n         if parsed_time_str.date() != datetime_obj.date():\n@@ -180,6 +180,15 @@\n         return super().validate(datetime_obj, display_format, serializer_field)\n \n \n+class DurationFormatValidator(AbstractDateTimeFormatValidator):\n+\n+    def validate(self, datetime_obj, display_format, serializer_field):\n+        if 'z' in display_format.lower():\n+            raise serializers.ValidationError(\n+                \"Duration column cannot contain timezone display format\"\n+            )\n+\n+\n class DateDisplayOptionSerializer(MathesarErrorMessageMixin, OverrideRootPartialMixin, serializers.Serializer):\n     format = serializers.CharField(validators=[DateFormatValidator()])\n \n@@ -216,6 +225,10 @@\n     format = serializers.CharField(validators=[TimeWithoutTimeZoneFormatValidator()])\n \n \n+class DurationDisplayOptionSerializer(MathesarErrorMessageMixin, OverrideRootPartialMixin, serializers.Serializer):\n+    format = serializers.CharField(validators=[DurationFormatValidator()])\n+\n+\n class DisplayOptionsMappingSerializer(\n     MathesarErrorMessageMixin,\n     ReadWritePolymorphicSerializerMappingMixin,\n@@ -231,6 +244,7 @@\n         ('date', MathesarTypeIdentifier.DATETIME.value): DateDisplayOptionSerializer,\n         ('time with time zone', MathesarTypeIdentifier.DATETIME.value): TimeWithTimezoneDisplayOptionSerializer,\n         ('time without time zone', MathesarTypeIdentifier.DATETIME.value): TimeWithoutTimezoneDisplayOptionSerializer,\n+        MathesarTypeIdentifier.DURATION.value: DurationDisplayOptionSerializer,\n     }\n \n     def get_mapping_field(self):\n", "issue": "Implement display options for Duration type\n## Problem\r\nThe Duration Mathesar type supports the following display options:\r\n- Min unit\r\n- Max unit\r\n- Whether to show unit labels\r\n\r\nWe will combine this into a single `format` string.\r\n\r\n## Solution\r\n(1) We should store these display options in the following format in the `display_options` field of the corresponding column.\r\n```\r\n{\r\n  \"format\": \"HH:mm:ss.SSS\"\r\n}\r\n```\r\n\r\n(2) We should also validate these so that:\r\n- Only columns of this type can have these display options. They should not be able to be set if the column is of a different type.\r\n- `format` should be a valid JavaScript duration format.\r\n\r\n(3) If the column type is changed, the display options should be deleted.\r\n\r\n(4) We should add supported display options to the `types` endpoint.\r\n\r\n## Additional Context\r\n- [Design of Duration type options on Figma](https://www.figma.com/proto/Uaf1ntcldzK2U41Jhw6vS2/Mathesar-MVP?page-id=4260%3A37440&node-id=4270%3A41231&viewport=324%2C48%2C0.29&scaling=contain&starting-point-node-id=4270%3A41231&show-proto-sidebar=1)\r\n- Blocked by #658\r\n- #392 \n", "before_files": [{"content": "from mathesar.database.types import MathesarTypeIdentifier\n\nDISPLAY_OPTIONS_BY_TYPE_IDENTIFIER = {\n    MathesarTypeIdentifier.BOOLEAN.value:\n        {\n            \"options\": [{\"name\": \"input\", \"type\": \"string\",\n                         \"enum\": ['dropdown', 'checkbox']},\n                        {'name': \"custom_labels\", \"type\": \"object\",\n                         \"items\": [{\"name\": \"TRUE\", \"type\": \"string\"},\n                                   {'name': \"FALSE\", \"type\": \"string\"}]}]\n\n        },\n    MathesarTypeIdentifier.NUMBER.value:\n        {\n            \"options\": [{\"name\": \"show_as_percentage\", \"type\": \"boolean\"},\n                        {\"name\": \"locale\", \"type\": \"string\"}]\n        },\n    MathesarTypeIdentifier.DATETIME.value:\n        {\n            \"options\": [{\"name\": \"format\", \"type\": \"string\"}]\n        }\n}\n", "path": "mathesar/api/display_options.py"}, {"content": "from abc import ABC, abstractmethod\n\nimport arrow\nfrom django.core.exceptions import ImproperlyConfigured\nfrom rest_framework import serializers\n\nfrom mathesar.api.exceptions.mixins import MathesarErrorMessageMixin\nfrom mathesar.database.types import MathesarTypeIdentifier, get_mathesar_type_from_db_type\n\n\nclass ReadOnlyPolymorphicSerializerMappingMixin:\n    \"\"\"\n    This serializer mixin is helpful in serializing polymorphic models,\n    by switching to correct serializer based on the mapping field value.\n    \"\"\"\n\n    def __new__(cls, *args, **kwargs):\n        if cls.serializers_mapping is None:\n            raise ImproperlyConfigured(\n                '`{cls}` is missing a '\n                '`{cls}.model_serializer_mapping` attribute'.format(cls=cls.__name__)\n            )\n        return super().__new__(cls, *args, **kwargs)\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.serializers_cls_mapping = {}\n        serializers_mapping = self.serializers_mapping\n        self.serializers_mapping = {}\n        for identifier, serializer_cls in serializers_mapping.items():\n            if callable(serializer_cls):\n                serializer = serializer_cls(*args, **kwargs)\n                serializer.parent = self\n            else:\n                serializer = serializer_cls\n            self.serializers_mapping[identifier] = serializer\n            self.serializers_cls_mapping[identifier] = serializer_cls\n\n    def to_representation(self, instance):\n        serializer = self.serializers_mapping.get(self.get_mapping_field(), None)\n        if serializer is not None:\n            self.__class__ = self.serializers_cls_mapping.get(self.get_mapping_field())\n            return serializer.to_representation(instance)\n        else:\n            raise Exception(f\"Cannot find a matching serializer for the specified type {self.get_mapping_field()}\")\n\n    def get_mapping_field(self):\n        mapping_field = getattr(self, \"mapping_field\", None)\n        if mapping_field is None:\n            raise Exception(\n                \"Add a `mapping_field` to be used as a identifier\"\n                \"or override this method to return a identifier to identify a proper serializer\"\n            )\n        return mapping_field\n\n\nclass ReadWritePolymorphicSerializerMappingMixin(ReadOnlyPolymorphicSerializerMappingMixin):\n    def to_internal_value(self, data):\n        serializer = self.serializers_mapping.get(self.get_mapping_field())\n        if serializer is not None:\n            self.__class__ = self.serializers_cls_mapping.get(self.get_mapping_field())\n            return serializer.to_internal_value(data=data)\n        else:\n            raise Exception(f\"Cannot find a matching serializer for the specified type {self.get_mapping_field()}\")\n\n\nclass MonkeyPatchPartial:\n    \"\"\"\n    Work around bug #3847 in djangorestframework by monkey-patching the partial\n    attribute of the root serializer during the call to validate_empty_values.\n    https://github.com/encode/django-rest-framework/issues/3847\n    \"\"\"\n\n    def __init__(self, root):\n        self._root = root\n\n    def __enter__(self):\n        self._old = getattr(self._root, 'partial')\n        setattr(self._root, 'partial', False)\n\n    def __exit__(self, *args):\n        setattr(self._root, 'partial', self._old)\n\n\nclass OverrideRootPartialMixin:\n    \"\"\"\n    This mixin is used to convert a serializer into a partial serializer,\n    based on the serializer `partial` property rather than the parent's `partial` property.\n    Refer to the issue\n        https://github.com/encode/django-rest-framework/issues/3847\n    \"\"\"\n\n    def run_validation(self, *args, **kwargs):\n        if not self.partial:\n            with MonkeyPatchPartial(self.root):\n                return super().run_validation(*args, **kwargs)\n        return super().run_validation(*args, **kwargs)\n\n\nclass CustomBooleanLabelSerializer(MathesarErrorMessageMixin, serializers.Serializer):\n    TRUE = serializers.CharField()\n    FALSE = serializers.CharField()\n\n\nDISPLAY_OPTIONS_SERIALIZER_MAPPING_KEY = 'mathesar_type'\n\n\nclass BooleanDisplayOptionSerializer(MathesarErrorMessageMixin, OverrideRootPartialMixin, serializers.Serializer):\n    input = serializers.ChoiceField(choices=[(\"dropdown\", 1), (\"checkbox\", 2)])\n    custom_labels = CustomBooleanLabelSerializer(required=False)\n\n\nclass NumberDisplayOptionSerializer(MathesarErrorMessageMixin, OverrideRootPartialMixin, serializers.Serializer):\n    show_as_percentage = serializers.BooleanField(default=False)\n    locale = serializers.CharField(required=False)\n\n\nclass AbstractDateTimeFormatValidator(ABC):\n    requires_context = True\n\n    def __init__(self):\n        pass\n\n    def __call__(self, value, serializer_field):\n        self.date_format_validator(value, serializer_field)\n\n    def date_format_validator(self, value, serializer_field):\n        try:\n            timestamp_with_tz_obj = arrow.get('2013-09-30T15:34:00.000-07:00')\n            parsed_datetime_str = timestamp_with_tz_obj.format(value)\n            datetime_object = arrow.get(parsed_datetime_str, value)\n        except ValueError:\n            raise serializers.ValidationError(f\"{value} is not a valid format used for parsing a datetime.\")\n        else:\n            self.validate(datetime_object, value, serializer_field)\n\n    @abstractmethod\n    def validate(self, datetime_obj, display_format, serializer_field):\n        pass\n\n\nclass TimestampWithTimeZoneFormatValidator(AbstractDateTimeFormatValidator):\n\n    def validate(self, datetime_obj, display_format, serializer_field):\n        pass\n\n\nclass TimestampWithoutTimeZoneFormatValidator(AbstractDateTimeFormatValidator):\n\n    def validate(self, datetime_obj, display_format, serializer_field):\n        if 'z' in display_format.lower():\n            raise serializers.ValidationError(\n                \"Timestamp without timezone column cannot contain timezone display format\"\n            )\n\n\nclass DateFormatValidator(AbstractDateTimeFormatValidator):\n\n    def validate(self, datetime_obj, display_format, serializer_field):\n        date_obj = arrow.get('2013-09-30')\n        if datetime_obj.time() != date_obj.time():\n            raise serializers.ValidationError(\"Date column cannot contain time or timezone display format\")\n\n\nclass TimeWithTimeZoneFormatValidator(AbstractDateTimeFormatValidator):\n\n    def validate(self, datetime_obj, display_format, serializer_field):\n        time_only_format = 'HH:mm:ssZZ'\n        time_str = arrow.get('2013-09-30T15:34:00.000-07:00').format(time_only_format)\n        parsed_time_str = arrow.get(time_str, time_only_format)\n        if parsed_time_str.date() != datetime_obj.date():\n            raise serializers.ValidationError(\"Time column cannot contain date display format\")\n\n\nclass TimeWithoutTimeZoneFormatValidator(TimeWithTimeZoneFormatValidator):\n\n    def validate(self, datetime_obj, display_format, serializer_field):\n        if 'z' in display_format.lower():\n            raise serializers.ValidationError(\"Time without timezone column cannot contain timezone display format\")\n        return super().validate(datetime_obj, display_format, serializer_field)\n\n\nclass DateDisplayOptionSerializer(MathesarErrorMessageMixin, OverrideRootPartialMixin, serializers.Serializer):\n    format = serializers.CharField(validators=[DateFormatValidator()])\n\n\nclass TimestampWithoutTimezoneDisplayOptionSerializer(\n    MathesarErrorMessageMixin,\n    OverrideRootPartialMixin,\n    serializers.Serializer\n):\n    format = serializers.CharField(validators=[TimestampWithoutTimeZoneFormatValidator()])\n\n\nclass TimestampWithTimezoneDisplayOptionSerializer(\n    MathesarErrorMessageMixin,\n    OverrideRootPartialMixin,\n    serializers.Serializer\n):\n    format = serializers.CharField(validators=[TimestampWithTimeZoneFormatValidator()])\n\n\nclass TimeWithTimezoneDisplayOptionSerializer(\n    MathesarErrorMessageMixin,\n    OverrideRootPartialMixin,\n    serializers.Serializer\n):\n    format = serializers.CharField(validators=[TimeWithTimeZoneFormatValidator()])\n\n\nclass TimeWithoutTimezoneDisplayOptionSerializer(\n    MathesarErrorMessageMixin,\n    OverrideRootPartialMixin,\n    serializers.Serializer\n):\n    format = serializers.CharField(validators=[TimeWithoutTimeZoneFormatValidator()])\n\n\nclass DisplayOptionsMappingSerializer(\n    MathesarErrorMessageMixin,\n    ReadWritePolymorphicSerializerMappingMixin,\n    serializers.Serializer\n):\n    serializers_mapping = {\n        MathesarTypeIdentifier.BOOLEAN.value: BooleanDisplayOptionSerializer,\n        MathesarTypeIdentifier.NUMBER.value: NumberDisplayOptionSerializer,\n        ('timestamp with time zone',\n         MathesarTypeIdentifier.DATETIME.value): TimestampWithTimezoneDisplayOptionSerializer,\n        ('timestamp without time zone',\n         MathesarTypeIdentifier.DATETIME.value): TimestampWithoutTimezoneDisplayOptionSerializer,\n        ('date', MathesarTypeIdentifier.DATETIME.value): DateDisplayOptionSerializer,\n        ('time with time zone', MathesarTypeIdentifier.DATETIME.value): TimeWithTimezoneDisplayOptionSerializer,\n        ('time without time zone', MathesarTypeIdentifier.DATETIME.value): TimeWithoutTimezoneDisplayOptionSerializer,\n    }\n\n    def get_mapping_field(self):\n        mathesar_type = get_mathesar_type_from_db_type(self.context[DISPLAY_OPTIONS_SERIALIZER_MAPPING_KEY])\n        if mathesar_type == MathesarTypeIdentifier.DATETIME.value:\n            return self.context[DISPLAY_OPTIONS_SERIALIZER_MAPPING_KEY].lower(), mathesar_type\n        else:\n            return mathesar_type\n", "path": "mathesar/api/serializers/shared_serializers.py"}], "after_files": [{"content": "from mathesar.database.types import MathesarTypeIdentifier\n\nDISPLAY_OPTIONS_BY_TYPE_IDENTIFIER = {\n    MathesarTypeIdentifier.BOOLEAN.value:\n        {\n            \"options\": [{\"name\": \"input\", \"type\": \"string\",\n                         \"enum\": ['dropdown', 'checkbox']},\n                        {'name': \"custom_labels\", \"type\": \"object\",\n                         \"items\": [{\"name\": \"TRUE\", \"type\": \"string\"},\n                                   {'name': \"FALSE\", \"type\": \"string\"}]}]\n\n        },\n    MathesarTypeIdentifier.NUMBER.value:\n        {\n            \"options\": [{\"name\": \"show_as_percentage\", \"type\": \"boolean\"},\n                        {\"name\": \"locale\", \"type\": \"string\"}]\n        },\n    MathesarTypeIdentifier.DATETIME.value:\n        {\n            \"options\": [{\"name\": \"format\", \"type\": \"string\"}]\n        },\n    MathesarTypeIdentifier.DURATION.value:\n        {\n            \"options\": [{\"name\": \"format\", \"type\": \"string\"}]\n        }\n}\n", "path": "mathesar/api/display_options.py"}, {"content": "from abc import ABC, abstractmethod\n\nimport arrow\nfrom django.core.exceptions import ImproperlyConfigured\nfrom rest_framework import serializers\n\nfrom mathesar.api.exceptions.mixins import MathesarErrorMessageMixin\nfrom mathesar.database.types import MathesarTypeIdentifier, get_mathesar_type_from_db_type\n\n\nclass ReadOnlyPolymorphicSerializerMappingMixin:\n    \"\"\"\n    This serializer mixin is helpful in serializing polymorphic models,\n    by switching to correct serializer based on the mapping field value.\n    \"\"\"\n\n    def __new__(cls, *args, **kwargs):\n        if cls.serializers_mapping is None:\n            raise ImproperlyConfigured(\n                '`{cls}` is missing a '\n                '`{cls}.model_serializer_mapping` attribute'.format(cls=cls.__name__)\n            )\n        return super().__new__(cls, *args, **kwargs)\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.serializers_cls_mapping = {}\n        serializers_mapping = self.serializers_mapping\n        self.serializers_mapping = {}\n        for identifier, serializer_cls in serializers_mapping.items():\n            if callable(serializer_cls):\n                serializer = serializer_cls(*args, **kwargs)\n                serializer.parent = self\n            else:\n                serializer = serializer_cls\n            self.serializers_mapping[identifier] = serializer\n            self.serializers_cls_mapping[identifier] = serializer_cls\n\n    def to_representation(self, instance):\n        serializer = self.serializers_mapping.get(self.get_mapping_field(), None)\n        if serializer is not None:\n            self.__class__ = self.serializers_cls_mapping.get(self.get_mapping_field())\n            return serializer.to_representation(instance)\n        else:\n            raise Exception(f\"Cannot find a matching serializer for the specified type {self.get_mapping_field()}\")\n\n    def get_mapping_field(self):\n        mapping_field = getattr(self, \"mapping_field\", None)\n        if mapping_field is None:\n            raise Exception(\n                \"Add a `mapping_field` to be used as a identifier\"\n                \"or override this method to return a identifier to identify a proper serializer\"\n            )\n        return mapping_field\n\n\nclass ReadWritePolymorphicSerializerMappingMixin(ReadOnlyPolymorphicSerializerMappingMixin):\n    def to_internal_value(self, data):\n        serializer = self.serializers_mapping.get(self.get_mapping_field())\n        if serializer is not None:\n            self.__class__ = self.serializers_cls_mapping.get(self.get_mapping_field())\n            return serializer.to_internal_value(data=data)\n        else:\n            raise Exception(f\"Cannot find a matching serializer for the specified type {self.get_mapping_field()}\")\n\n\nclass MonkeyPatchPartial:\n    \"\"\"\n    Work around bug #3847 in djangorestframework by monkey-patching the partial\n    attribute of the root serializer during the call to validate_empty_values.\n    https://github.com/encode/django-rest-framework/issues/3847\n    \"\"\"\n\n    def __init__(self, root):\n        self._root = root\n\n    def __enter__(self):\n        self._old = getattr(self._root, 'partial')\n        setattr(self._root, 'partial', False)\n\n    def __exit__(self, *args):\n        setattr(self._root, 'partial', self._old)\n\n\nclass OverrideRootPartialMixin:\n    \"\"\"\n    This mixin is used to convert a serializer into a partial serializer,\n    based on the serializer `partial` property rather than the parent's `partial` property.\n    Refer to the issue\n        https://github.com/encode/django-rest-framework/issues/3847\n    \"\"\"\n\n    def run_validation(self, *args, **kwargs):\n        if not self.partial:\n            with MonkeyPatchPartial(self.root):\n                return super().run_validation(*args, **kwargs)\n        return super().run_validation(*args, **kwargs)\n\n\nclass CustomBooleanLabelSerializer(MathesarErrorMessageMixin, serializers.Serializer):\n    TRUE = serializers.CharField()\n    FALSE = serializers.CharField()\n\n\nDISPLAY_OPTIONS_SERIALIZER_MAPPING_KEY = 'mathesar_type'\n\n\nclass BooleanDisplayOptionSerializer(MathesarErrorMessageMixin, OverrideRootPartialMixin, serializers.Serializer):\n    input = serializers.ChoiceField(choices=[(\"dropdown\", 1), (\"checkbox\", 2)])\n    custom_labels = CustomBooleanLabelSerializer(required=False)\n\n\nclass NumberDisplayOptionSerializer(MathesarErrorMessageMixin, OverrideRootPartialMixin, serializers.Serializer):\n    show_as_percentage = serializers.BooleanField(default=False)\n    locale = serializers.CharField(required=False)\n\n\nclass AbstractDateTimeFormatValidator(ABC):\n    requires_context = True\n\n    def __init__(self):\n        pass\n\n    def __call__(self, value, serializer_field):\n        self.date_format_validator(value, serializer_field)\n\n    def date_format_validator(self, value, serializer_field):\n        try:\n            timestamp_with_tz_obj = arrow.get('2013-09-30T15:34:00.000-07:00')\n            parsed_datetime_str = timestamp_with_tz_obj.format(value)\n            datetime_object = arrow.get(parsed_datetime_str, value)\n        except ValueError:\n            raise serializers.ValidationError(f\"{value} is not a valid format used for parsing a datetime.\")\n        else:\n            self.validate(datetime_object, value, serializer_field)\n\n    @abstractmethod\n    def validate(self, datetime_obj, display_format, serializer_field):\n        pass\n\n\nclass TimestampWithTimeZoneFormatValidator(AbstractDateTimeFormatValidator):\n\n    def validate(self, datetime_obj, display_format, serializer_field):\n        pass\n\n\nclass TimestampWithoutTimeZoneFormatValidator(AbstractDateTimeFormatValidator):\n\n    def validate(self, datetime_obj, display_format, serializer_field):\n        if 'z' in display_format.lower():\n            raise serializers.ValidationError(\n                \"Timestamp without timezone column cannot contain timezone display format\"\n            )\n\n\nclass DateFormatValidator(AbstractDateTimeFormatValidator):\n\n    def validate(self, datetime_obj, display_format, serializer_field):\n        date_obj = arrow.get('2013-09-30')\n        if datetime_obj.time() != date_obj.time():\n            raise serializers.ValidationError(\"Date column cannot contain time or timezone display format\")\n\n\nclass TimeWithTimeZoneFormatValidator(AbstractDateTimeFormatValidator):\n\n    def validate(self, datetime_obj, display_format, serializer_field):\n        time_only_format = 'HH:mm:ss.SSSZZ'\n        time_str = arrow.get('2013-09-30T15:34:00.000-07:00').format(time_only_format)\n        parsed_time_str = arrow.get(time_str, time_only_format)\n        if parsed_time_str.date() != datetime_obj.date():\n            raise serializers.ValidationError(\"Time column cannot contain date display format\")\n\n\nclass TimeWithoutTimeZoneFormatValidator(TimeWithTimeZoneFormatValidator):\n\n    def validate(self, datetime_obj, display_format, serializer_field):\n        if 'z' in display_format.lower():\n            raise serializers.ValidationError(\"Time without timezone column cannot contain timezone display format\")\n        return super().validate(datetime_obj, display_format, serializer_field)\n\n\nclass DurationFormatValidator(AbstractDateTimeFormatValidator):\n\n    def validate(self, datetime_obj, display_format, serializer_field):\n        if 'z' in display_format.lower():\n            raise serializers.ValidationError(\n                \"Duration column cannot contain timezone display format\"\n            )\n\n\nclass DateDisplayOptionSerializer(MathesarErrorMessageMixin, OverrideRootPartialMixin, serializers.Serializer):\n    format = serializers.CharField(validators=[DateFormatValidator()])\n\n\nclass TimestampWithoutTimezoneDisplayOptionSerializer(\n    MathesarErrorMessageMixin,\n    OverrideRootPartialMixin,\n    serializers.Serializer\n):\n    format = serializers.CharField(validators=[TimestampWithoutTimeZoneFormatValidator()])\n\n\nclass TimestampWithTimezoneDisplayOptionSerializer(\n    MathesarErrorMessageMixin,\n    OverrideRootPartialMixin,\n    serializers.Serializer\n):\n    format = serializers.CharField(validators=[TimestampWithTimeZoneFormatValidator()])\n\n\nclass TimeWithTimezoneDisplayOptionSerializer(\n    MathesarErrorMessageMixin,\n    OverrideRootPartialMixin,\n    serializers.Serializer\n):\n    format = serializers.CharField(validators=[TimeWithTimeZoneFormatValidator()])\n\n\nclass TimeWithoutTimezoneDisplayOptionSerializer(\n    MathesarErrorMessageMixin,\n    OverrideRootPartialMixin,\n    serializers.Serializer\n):\n    format = serializers.CharField(validators=[TimeWithoutTimeZoneFormatValidator()])\n\n\nclass DurationDisplayOptionSerializer(MathesarErrorMessageMixin, OverrideRootPartialMixin, serializers.Serializer):\n    format = serializers.CharField(validators=[DurationFormatValidator()])\n\n\nclass DisplayOptionsMappingSerializer(\n    MathesarErrorMessageMixin,\n    ReadWritePolymorphicSerializerMappingMixin,\n    serializers.Serializer\n):\n    serializers_mapping = {\n        MathesarTypeIdentifier.BOOLEAN.value: BooleanDisplayOptionSerializer,\n        MathesarTypeIdentifier.NUMBER.value: NumberDisplayOptionSerializer,\n        ('timestamp with time zone',\n         MathesarTypeIdentifier.DATETIME.value): TimestampWithTimezoneDisplayOptionSerializer,\n        ('timestamp without time zone',\n         MathesarTypeIdentifier.DATETIME.value): TimestampWithoutTimezoneDisplayOptionSerializer,\n        ('date', MathesarTypeIdentifier.DATETIME.value): DateDisplayOptionSerializer,\n        ('time with time zone', MathesarTypeIdentifier.DATETIME.value): TimeWithTimezoneDisplayOptionSerializer,\n        ('time without time zone', MathesarTypeIdentifier.DATETIME.value): TimeWithoutTimezoneDisplayOptionSerializer,\n        MathesarTypeIdentifier.DURATION.value: DurationDisplayOptionSerializer,\n    }\n\n    def get_mapping_field(self):\n        mathesar_type = get_mathesar_type_from_db_type(self.context[DISPLAY_OPTIONS_SERIALIZER_MAPPING_KEY])\n        if mathesar_type == MathesarTypeIdentifier.DATETIME.value:\n            return self.context[DISPLAY_OPTIONS_SERIALIZER_MAPPING_KEY].lower(), mathesar_type\n        else:\n            return mathesar_type\n", "path": "mathesar/api/serializers/shared_serializers.py"}]}