We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Allow the user to hide grouping separators for Number and Money
## Current behavior

- Look at this table of numbers. The right-most column is storing years as in integer, which I think will be common. But they are displayed with grouping separators. Ugh. That looks terrible!

    ![image](https://user-images.githubusercontent.com/42411/165846061-9648a4bd-2aac-4074-851b-882c77aa8bea.png)

- There is currently no way for the user to disable the display of grouping separators.

## Desired behavior

- The user can use display options to enable/disable grouping separators.

## Proposal

### Schema

- Use the `use_grouping` property as [spec'ed in this comment](https://github.com/centerofci/mathesar/discussions/1243#discussioncomment-2519195).

### UX

- Add a new form field labeled "Use Grouping Separators", with a dropdown input having options "Yes", "No", and "Auto". Display this new field before the already-existing "Format" field.

    For integers, the default is "No".

    For non-integers, and Money, the default is "Yes".

- Modify the "Format" form field to behave as follows:

    - If we have an **integer with grouping separators**, then present the following options:

        | option    | value |
        | --------- | ------- |
        | Use browser locale | `null` |
        | 1,234,567 | `'english'` |
        | 1.234.567 | `'german'`  |
        | 1 234 567 | `'french'`  |
        | 12,34,567 | `'hindi'`   |
        | 1'234'567 | `'swiss'`   |

    - If we have an **integer without grouping separators**, then don't show the "format" option, because it's irrelevant. Keep value as `null`.

    - If we have a **non-integer with grouping separators**, then present the following options:

        | option    | value |
        | --------- | ------- |
        | Use browser locale | `null` |
        | 1,234,567.89 | `'english'` |
        | 1.234.567,89 | `'german'`  |
        | 1 234 567,89 | `'french'`  |
        | 12,34,567.89 | `'hindi'`   |
        | 1'234'567.89 | `'swiss'`   |

    - If we have a **non-integer without grouping separators**, then present the following options:

        | option    | value |
        | --------- | ------- |
        | Use browser locale | `null` |
        | 1234.56 | `'english'` |
        | 1234,56 | `'german'`  |

- When changing the "Use Grouping Separators" field from "No" to "Yes", we set a more specific value for the "Format" field, using the browser locale to resolve ambiguity. For example, if the user has configured `format` to `'german'` (because they just wanted a comma decimal separator), but their locale is `fr-FR`, then we change `'german'` to `'french'`, which also has a comma decimal separator but is more likely to match their desired grouping separator. We can use the browser locale to determine the format option by formatting a number and inspecting it. As a further edge case, if the user has configured `format` to `'german'`, but their locale is `en-US` (which is _not_ a more specific form of `'german'`), then we'd leave the value untouched.



--- 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 import json
2 from mathesar.database.types import UIType
3 from lazydict import LazyDictionary
4 
5 
6 def _money_display_options_schema():
7     with open("currency_info.json", "r") as info_file:
8         currency_info = json.load(info_file)
9     currency_codes = list(currency_info.keys())
10     return {
11         "options": [
12             {"name": "currency_code", "type": "string", "enum": currency_codes},
13             {
14                 "name": "currency_details",
15                 "type": "object",
16                 "items": [
17                     {"name": "symbol", "type": "string"},
18                     {"name": "symbol_location", "type": "number", "enum": [1, -1]},
19                     {"name": "decimal_symbol", "type": "string", "enum": [",", "."]},
20                     {"name": "digit_grouping", "type": "array"},
21                     {"name": "digit_symbol", "type": "string", "enum": [",", ".", " "]}
22                 ]
23             }]
24     }
25 
26 
27 DISPLAY_OPTIONS_BY_UI_TYPE = LazyDictionary(
28     {
29         UIType.BOOLEAN:
30         {
31             "options": [
32                 {
33                     "name": "input", "type": "string",
34                     "enum": ['dropdown', 'checkbox']
35                 },
36                 {
37                     'name': "custom_labels", "type": "object",
38                     "items": [
39                         {"name": "TRUE", "type": "string"},
40                         {'name': "FALSE", "type": "string"}
41                     ]
42                 }
43             ]
44 
45         },
46         UIType.NUMBER:
47         {
48             "options": [
49                 {
50                     "name": "show_as_percentage",
51                     "type": "string",
52                     "enum": ['dropdown', 'checkbox']
53                 },
54                 {
55                     "name": "locale",
56                     "type": "string"
57                 }
58             ]
59         },
60         UIType.DATETIME:
61         {
62             "options": [{"name": "format", "type": "string"}]
63         },
64         UIType.TIME:
65         {
66             "options": [{"name": "format", "type": "string"}]
67         },
68         UIType.DATE:
69         {
70             "options": [{"name": "format", "type": "string"}]
71         },
72         UIType.DURATION:
73         {
74             "options": [
75                 {"name": "min", "type": "string"},
76                 {"name": "max", "type": "string"},
77                 {"name": "show_units", "type": "boolean"},
78             ]
79         },
80         # NOTE: below callable will be evaluated lazily by LazyDictionary
81         UIType.MONEY: _money_display_options_schema,
82     }
83 )
84
```

Path: `mathesar/api/serializers/shared_serializers.py`
Content:
```
1 from django.core.exceptions import ImproperlyConfigured
2 from rest_framework import serializers
3 
4 from mathesar.api.exceptions.mixins import MathesarErrorMessageMixin
5 from mathesar.database.types import UIType, get_ui_type_from_db_type
6 
7 
8 class ReadOnlyPolymorphicSerializerMappingMixin:
9     """
10     This serializer mixin is helpful in serializing polymorphic models,
11     by switching to correct serializer based on the mapping field value.
12     """
13     default_serializer = None
14 
15     def __new__(cls, *args, **kwargs):
16         if cls.serializers_mapping is None:
17             raise ImproperlyConfigured(
18                 '`{cls}` is missing a '
19                 '`{cls}.model_serializer_mapping` attribute'.format(cls=cls.__name__)
20             )
21         return super().__new__(cls, *args, **kwargs)
22 
23     def _init_serializer(self, serializer_cls, *args, **kwargs):
24         if callable(serializer_cls):
25             serializer = serializer_cls(*args, **kwargs)
26             serializer.parent = self
27         else:
28             serializer = serializer_cls
29         return serializer
30 
31     def __init__(self, *args, **kwargs):
32         super().__init__(*args, **kwargs)
33         self.serializers_cls_mapping = {}
34         serializers_mapping = self.serializers_mapping
35         self.serializers_mapping = {}
36         if self.default_serializer is not None:
37             self.default_serializer = self._init_serializer(self.default_serializer, *args, **kwargs)
38         for identifier, serializer_cls in serializers_mapping.items():
39             serializer = self._init_serializer(serializer_cls, *args, **kwargs)
40             self.serializers_mapping[identifier] = serializer
41             self.serializers_cls_mapping[identifier] = serializer_cls
42 
43     def get_serializer_class(self, identifier):
44         if identifier in self.serializers_mapping:
45             return self.serializers_mapping.get(identifier)
46         else:
47             return self.default_serializer
48 
49     def to_representation(self, instance):
50         serializer = self.get_serializer_class(self.get_mapping_field(instance))
51         if serializer is not None:
52             return serializer.to_representation(instance)
53         else:
54             return instance
55 
56     def get_mapping_field(self, data):
57         mapping_field = getattr(self, "mapping_field", None)
58         if mapping_field is None:
59             raise Exception(
60                 "Add a `mapping_field` to be used as a identifier"
61                 "or override this method to return a identifier to identify a proper serializer"
62             )
63         return mapping_field
64 
65 
66 class ReadWritePolymorphicSerializerMappingMixin(ReadOnlyPolymorphicSerializerMappingMixin):
67     def to_internal_value(self, data):
68         serializer = self.get_serializer_class(self.get_mapping_field(data))
69         if serializer is not None:
70             return serializer.to_internal_value(data=data)
71         else:
72             data = {}
73             return data
74 
75     def validate(self, attrs):
76         serializer = self.serializers_mapping.get(self.get_mapping_field(attrs))
77         if serializer is not None:
78             return serializer.validate(attrs)
79         return {}
80 
81 
82 class MonkeyPatchPartial:
83     """
84     Work around bug #3847 in djangorestframework by monkey-patching the partial
85     attribute of the root serializer during the call to validate_empty_values.
86     https://github.com/encode/django-rest-framework/issues/3847
87     """
88 
89     def __init__(self, root):
90         self._root = root
91 
92     def __enter__(self):
93         self._old = getattr(self._root, 'partial')
94         setattr(self._root, 'partial', False)
95 
96     def __exit__(self, *args):
97         setattr(self._root, 'partial', self._old)
98 
99 
100 class OverrideRootPartialMixin:
101     """
102     This mixin is used to convert a serializer into a partial serializer,
103     based on the serializer `partial` property rather than the parent's `partial` property.
104     Refer to the issue
105         https://github.com/encode/django-rest-framework/issues/3847
106     """
107 
108     def run_validation(self, *args, **kwargs):
109         if not self.partial:
110             with MonkeyPatchPartial(self.root):
111                 return super().run_validation(*args, **kwargs)
112         return super().run_validation(*args, **kwargs)
113 
114 
115 class MathesarPolymorphicErrorMixin(MathesarErrorMessageMixin):
116     def get_serializer_fields(self, data):
117         return self.serializers_mapping[self.get_mapping_field(data)].fields
118 
119 
120 class BaseDisplayOptionsSerializer(MathesarErrorMessageMixin, OverrideRootPartialMixin, serializers.Serializer):
121     show_fk_preview = serializers.BooleanField(default=True)
122 
123 
124 class CustomBooleanLabelSerializer(MathesarErrorMessageMixin, serializers.Serializer):
125     TRUE = serializers.CharField()
126     FALSE = serializers.CharField()
127 
128 
129 # This is the key which will determine which display options serializer is used. Its value is
130 # supposed to be the column's DB type (a DatabaseType instance).
131 DISPLAY_OPTIONS_SERIALIZER_MAPPING_KEY = 'db_type'
132 
133 
134 class BooleanDisplayOptionSerializer(BaseDisplayOptionsSerializer):
135     input = serializers.ChoiceField(choices=[("dropdown", "dropdown"), ("checkbox", "checkbox")])
136     custom_labels = CustomBooleanLabelSerializer(required=False)
137 
138 
139 class AbstractNumberDisplayOptionSerializer(BaseDisplayOptionsSerializer):
140     number_format = serializers.ChoiceField(
141         required=False,
142         allow_null=True,
143         choices=['english', 'german', 'french', 'hindi', 'swiss']
144     )
145 
146 
147 class NumberDisplayOptionSerializer(AbstractNumberDisplayOptionSerializer):
148     show_as_percentage = serializers.BooleanField(default=False)
149 
150 
151 class MoneyDisplayOptionSerializer(AbstractNumberDisplayOptionSerializer):
152     currency_symbol = serializers.CharField()
153     currency_symbol_location = serializers.ChoiceField(choices=['after-minus', 'end-with-space'])
154 
155 
156 class TimeFormatDisplayOptionSerializer(BaseDisplayOptionsSerializer):
157     format = serializers.CharField(max_length=255)
158 
159 
160 class DateTimeFormatDisplayOptionSerializer(BaseDisplayOptionsSerializer):
161     time_format = serializers.CharField(max_length=255)
162     date_format = serializers.CharField(max_length=255)
163 
164 
165 class DurationDisplayOptionSerializer(BaseDisplayOptionsSerializer):
166     min = serializers.CharField(max_length=255)
167     max = serializers.CharField(max_length=255)
168     show_units = serializers.BooleanField()
169 
170 
171 class DisplayOptionsMappingSerializer(
172     OverrideRootPartialMixin,
173     MathesarPolymorphicErrorMixin,
174     ReadWritePolymorphicSerializerMappingMixin,
175     serializers.Serializer
176 ):
177     serializers_mapping = {
178         UIType.BOOLEAN: BooleanDisplayOptionSerializer,
179         UIType.NUMBER: NumberDisplayOptionSerializer,
180         UIType.DATETIME: DateTimeFormatDisplayOptionSerializer,
181         UIType.DATE: TimeFormatDisplayOptionSerializer,
182         UIType.TIME: TimeFormatDisplayOptionSerializer,
183         UIType.DURATION: DurationDisplayOptionSerializer,
184         UIType.MONEY: MoneyDisplayOptionSerializer,
185     }
186     default_serializer = BaseDisplayOptionsSerializer
187 
188     def get_mapping_field(self, _):
189         return self._get_ui_type_of_column_being_serialized()
190 
191     def _get_ui_type_of_column_being_serialized(self):
192         db_type = self.context[DISPLAY_OPTIONS_SERIALIZER_MAPPING_KEY]
193         ui_type = get_ui_type_from_db_type(db_type)
194         return ui_type
195
```
--- 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
@@ -51,6 +51,11 @@
                     "type": "string",
                     "enum": ['dropdown', 'checkbox']
                 },
+                {
+                    "name": "use_grouping",
+                    "type": "string",
+                    "enum": ['true', 'false', 'auto']
+                },
                 {
                     "name": "locale",
                     "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
@@ -143,6 +143,18 @@
         choices=['english', 'german', 'french', 'hindi', 'swiss']
     )
 
+    use_grouping = serializers.ChoiceField(required=False, choices=['true', 'false', 'auto'], default='auto')
+    """
+    The choices here correspond to the options available for the `useGrouping`
+    property within the [Intl API][1]. True and False are encoded as strings
+    instead of booleans to maintain consistency with the Intl API and to keep
+    the type consistent. We did considering using an optional boolean but
+    decided a string would be better, especially if we want to support other
+    options eventually, like "min2".
+
+    [1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat
+    """
+
 
 class NumberDisplayOptionSerializer(AbstractNumberDisplayOptionSerializer):
     show_as_percentage = serializers.BooleanField(default=False)

{"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@@ -51,6 +51,11 @@\n                     \"type\": \"string\",\n                     \"enum\": ['dropdown', 'checkbox']\n                 },\n+                {\n+                    \"name\": \"use_grouping\",\n+                    \"type\": \"string\",\n+                    \"enum\": ['true', 'false', 'auto']\n+                },\n                 {\n                     \"name\": \"locale\",\n                     \"type\": \"string\"\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@@ -143,6 +143,18 @@\n         choices=['english', 'german', 'french', 'hindi', 'swiss']\n     )\n \n+    use_grouping = serializers.ChoiceField(required=False, choices=['true', 'false', 'auto'], default='auto')\n+    \"\"\"\n+    The choices here correspond to the options available for the `useGrouping`\n+    property within the [Intl API][1]. True and False are encoded as strings\n+    instead of booleans to maintain consistency with the Intl API and to keep\n+    the type consistent. We did considering using an optional boolean but\n+    decided a string would be better, especially if we want to support other\n+    options eventually, like \"min2\".\n+\n+    [1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat\n+    \"\"\"\n+\n \n class NumberDisplayOptionSerializer(AbstractNumberDisplayOptionSerializer):\n     show_as_percentage = serializers.BooleanField(default=False)\n", "issue": "Allow the user to hide grouping separators for Number and Money\n## Current behavior\r\n\r\n- Look at this table of numbers. The right-most column is storing years as in integer, which I think will be common. But they are displayed with grouping separators. Ugh. That looks terrible!\r\n\r\n    ![image](https://user-images.githubusercontent.com/42411/165846061-9648a4bd-2aac-4074-851b-882c77aa8bea.png)\r\n\r\n- There is currently no way for the user to disable the display of grouping separators.\r\n\r\n## Desired behavior\r\n\r\n- The user can use display options to enable/disable grouping separators.\r\n\r\n## Proposal\r\n\r\n### Schema\r\n\r\n- Use the `use_grouping` property as [spec'ed in this comment](https://github.com/centerofci/mathesar/discussions/1243#discussioncomment-2519195).\r\n\r\n### UX\r\n\r\n- Add a new form field labeled \"Use Grouping Separators\", with a dropdown input having options \"Yes\", \"No\", and \"Auto\". Display this new field before the already-existing \"Format\" field.\r\n\r\n    For integers, the default is \"No\".\r\n\r\n    For non-integers, and Money, the default is \"Yes\".\r\n\r\n- Modify the \"Format\" form field to behave as follows:\r\n\r\n    - If we have an **integer with grouping separators**, then present the following options:\r\n\r\n        | option    | value |\r\n        | --------- | ------- |\r\n        | Use browser locale | `null` |\r\n        | 1,234,567 | `'english'` |\r\n        | 1.234.567 | `'german'`  |\r\n        | 1 234 567 | `'french'`  |\r\n        | 12,34,567 | `'hindi'`   |\r\n        | 1'234'567 | `'swiss'`   |\r\n\r\n    - If we have an **integer without grouping separators**, then don't show the \"format\" option, because it's irrelevant. Keep value as `null`.\r\n\r\n    - If we have a **non-integer with grouping separators**, then present the following options:\r\n\r\n        | option    | value |\r\n        | --------- | ------- |\r\n        | Use browser locale | `null` |\r\n        | 1,234,567.89 | `'english'` |\r\n        | 1.234.567,89 | `'german'`  |\r\n        | 1 234 567,89 | `'french'`  |\r\n        | 12,34,567.89 | `'hindi'`   |\r\n        | 1'234'567.89 | `'swiss'`   |\r\n\r\n    - If we have a **non-integer without grouping separators**, then present the following options:\r\n\r\n        | option    | value |\r\n        | --------- | ------- |\r\n        | Use browser locale | `null` |\r\n        | 1234.56 | `'english'` |\r\n        | 1234,56 | `'german'`  |\r\n\r\n- When changing the \"Use Grouping Separators\" field from \"No\" to \"Yes\", we set a more specific value for the \"Format\" field, using the browser locale to resolve ambiguity. For example, if the user has configured `format` to `'german'` (because they just wanted a comma decimal separator), but their locale is `fr-FR`, then we change `'german'` to `'french'`, which also has a comma decimal separator but is more likely to match their desired grouping separator. We can use the browser locale to determine the format option by formatting a number and inspecting it. As a further edge case, if the user has configured `format` to `'german'`, but their locale is `en-US` (which is _not_ a more specific form of `'german'`), then we'd leave the value untouched.\r\n\r\n\n", "before_files": [{"content": "import json\nfrom mathesar.database.types import UIType\nfrom lazydict import LazyDictionary\n\n\ndef _money_display_options_schema():\n    with open(\"currency_info.json\", \"r\") as info_file:\n        currency_info = json.load(info_file)\n    currency_codes = list(currency_info.keys())\n    return {\n        \"options\": [\n            {\"name\": \"currency_code\", \"type\": \"string\", \"enum\": currency_codes},\n            {\n                \"name\": \"currency_details\",\n                \"type\": \"object\",\n                \"items\": [\n                    {\"name\": \"symbol\", \"type\": \"string\"},\n                    {\"name\": \"symbol_location\", \"type\": \"number\", \"enum\": [1, -1]},\n                    {\"name\": \"decimal_symbol\", \"type\": \"string\", \"enum\": [\",\", \".\"]},\n                    {\"name\": \"digit_grouping\", \"type\": \"array\"},\n                    {\"name\": \"digit_symbol\", \"type\": \"string\", \"enum\": [\",\", \".\", \" \"]}\n                ]\n            }]\n    }\n\n\nDISPLAY_OPTIONS_BY_UI_TYPE = LazyDictionary(\n    {\n        UIType.BOOLEAN:\n        {\n            \"options\": [\n                {\n                    \"name\": \"input\", \"type\": \"string\",\n                    \"enum\": ['dropdown', 'checkbox']\n                },\n                {\n                    'name': \"custom_labels\", \"type\": \"object\",\n                    \"items\": [\n                        {\"name\": \"TRUE\", \"type\": \"string\"},\n                        {'name': \"FALSE\", \"type\": \"string\"}\n                    ]\n                }\n            ]\n\n        },\n        UIType.NUMBER:\n        {\n            \"options\": [\n                {\n                    \"name\": \"show_as_percentage\",\n                    \"type\": \"string\",\n                    \"enum\": ['dropdown', 'checkbox']\n                },\n                {\n                    \"name\": \"locale\",\n                    \"type\": \"string\"\n                }\n            ]\n        },\n        UIType.DATETIME:\n        {\n            \"options\": [{\"name\": \"format\", \"type\": \"string\"}]\n        },\n        UIType.TIME:\n        {\n            \"options\": [{\"name\": \"format\", \"type\": \"string\"}]\n        },\n        UIType.DATE:\n        {\n            \"options\": [{\"name\": \"format\", \"type\": \"string\"}]\n        },\n        UIType.DURATION:\n        {\n            \"options\": [\n                {\"name\": \"min\", \"type\": \"string\"},\n                {\"name\": \"max\", \"type\": \"string\"},\n                {\"name\": \"show_units\", \"type\": \"boolean\"},\n            ]\n        },\n        # NOTE: below callable will be evaluated lazily by LazyDictionary\n        UIType.MONEY: _money_display_options_schema,\n    }\n)\n", "path": "mathesar/api/display_options.py"}, {"content": "from django.core.exceptions import ImproperlyConfigured\nfrom rest_framework import serializers\n\nfrom mathesar.api.exceptions.mixins import MathesarErrorMessageMixin\nfrom mathesar.database.types import UIType, get_ui_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    default_serializer = None\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_serializer(self, serializer_cls, *args, **kwargs):\n        if callable(serializer_cls):\n            serializer = serializer_cls(*args, **kwargs)\n            serializer.parent = self\n        else:\n            serializer = serializer_cls\n        return serializer\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        if self.default_serializer is not None:\n            self.default_serializer = self._init_serializer(self.default_serializer, *args, **kwargs)\n        for identifier, serializer_cls in serializers_mapping.items():\n            serializer = self._init_serializer(serializer_cls, *args, **kwargs)\n            self.serializers_mapping[identifier] = serializer\n            self.serializers_cls_mapping[identifier] = serializer_cls\n\n    def get_serializer_class(self, identifier):\n        if identifier in self.serializers_mapping:\n            return self.serializers_mapping.get(identifier)\n        else:\n            return self.default_serializer\n\n    def to_representation(self, instance):\n        serializer = self.get_serializer_class(self.get_mapping_field(instance))\n        if serializer is not None:\n            return serializer.to_representation(instance)\n        else:\n            return instance\n\n    def get_mapping_field(self, data):\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.get_serializer_class(self.get_mapping_field(data))\n        if serializer is not None:\n            return serializer.to_internal_value(data=data)\n        else:\n            data = {}\n            return data\n\n    def validate(self, attrs):\n        serializer = self.serializers_mapping.get(self.get_mapping_field(attrs))\n        if serializer is not None:\n            return serializer.validate(attrs)\n        return {}\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 MathesarPolymorphicErrorMixin(MathesarErrorMessageMixin):\n    def get_serializer_fields(self, data):\n        return self.serializers_mapping[self.get_mapping_field(data)].fields\n\n\nclass BaseDisplayOptionsSerializer(MathesarErrorMessageMixin, OverrideRootPartialMixin, serializers.Serializer):\n    show_fk_preview = serializers.BooleanField(default=True)\n\n\nclass CustomBooleanLabelSerializer(MathesarErrorMessageMixin, serializers.Serializer):\n    TRUE = serializers.CharField()\n    FALSE = serializers.CharField()\n\n\n# This is the key which will determine which display options serializer is used. Its value is\n# supposed to be the column's DB type (a DatabaseType instance).\nDISPLAY_OPTIONS_SERIALIZER_MAPPING_KEY = 'db_type'\n\n\nclass BooleanDisplayOptionSerializer(BaseDisplayOptionsSerializer):\n    input = serializers.ChoiceField(choices=[(\"dropdown\", \"dropdown\"), (\"checkbox\", \"checkbox\")])\n    custom_labels = CustomBooleanLabelSerializer(required=False)\n\n\nclass AbstractNumberDisplayOptionSerializer(BaseDisplayOptionsSerializer):\n    number_format = serializers.ChoiceField(\n        required=False,\n        allow_null=True,\n        choices=['english', 'german', 'french', 'hindi', 'swiss']\n    )\n\n\nclass NumberDisplayOptionSerializer(AbstractNumberDisplayOptionSerializer):\n    show_as_percentage = serializers.BooleanField(default=False)\n\n\nclass MoneyDisplayOptionSerializer(AbstractNumberDisplayOptionSerializer):\n    currency_symbol = serializers.CharField()\n    currency_symbol_location = serializers.ChoiceField(choices=['after-minus', 'end-with-space'])\n\n\nclass TimeFormatDisplayOptionSerializer(BaseDisplayOptionsSerializer):\n    format = serializers.CharField(max_length=255)\n\n\nclass DateTimeFormatDisplayOptionSerializer(BaseDisplayOptionsSerializer):\n    time_format = serializers.CharField(max_length=255)\n    date_format = serializers.CharField(max_length=255)\n\n\nclass DurationDisplayOptionSerializer(BaseDisplayOptionsSerializer):\n    min = serializers.CharField(max_length=255)\n    max = serializers.CharField(max_length=255)\n    show_units = serializers.BooleanField()\n\n\nclass DisplayOptionsMappingSerializer(\n    OverrideRootPartialMixin,\n    MathesarPolymorphicErrorMixin,\n    ReadWritePolymorphicSerializerMappingMixin,\n    serializers.Serializer\n):\n    serializers_mapping = {\n        UIType.BOOLEAN: BooleanDisplayOptionSerializer,\n        UIType.NUMBER: NumberDisplayOptionSerializer,\n        UIType.DATETIME: DateTimeFormatDisplayOptionSerializer,\n        UIType.DATE: TimeFormatDisplayOptionSerializer,\n        UIType.TIME: TimeFormatDisplayOptionSerializer,\n        UIType.DURATION: DurationDisplayOptionSerializer,\n        UIType.MONEY: MoneyDisplayOptionSerializer,\n    }\n    default_serializer = BaseDisplayOptionsSerializer\n\n    def get_mapping_field(self, _):\n        return self._get_ui_type_of_column_being_serialized()\n\n    def _get_ui_type_of_column_being_serialized(self):\n        db_type = self.context[DISPLAY_OPTIONS_SERIALIZER_MAPPING_KEY]\n        ui_type = get_ui_type_from_db_type(db_type)\n        return ui_type\n", "path": "mathesar/api/serializers/shared_serializers.py"}], "after_files": [{"content": "import json\nfrom mathesar.database.types import UIType\nfrom lazydict import LazyDictionary\n\n\ndef _money_display_options_schema():\n    with open(\"currency_info.json\", \"r\") as info_file:\n        currency_info = json.load(info_file)\n    currency_codes = list(currency_info.keys())\n    return {\n        \"options\": [\n            {\"name\": \"currency_code\", \"type\": \"string\", \"enum\": currency_codes},\n            {\n                \"name\": \"currency_details\",\n                \"type\": \"object\",\n                \"items\": [\n                    {\"name\": \"symbol\", \"type\": \"string\"},\n                    {\"name\": \"symbol_location\", \"type\": \"number\", \"enum\": [1, -1]},\n                    {\"name\": \"decimal_symbol\", \"type\": \"string\", \"enum\": [\",\", \".\"]},\n                    {\"name\": \"digit_grouping\", \"type\": \"array\"},\n                    {\"name\": \"digit_symbol\", \"type\": \"string\", \"enum\": [\",\", \".\", \" \"]}\n                ]\n            }]\n    }\n\n\nDISPLAY_OPTIONS_BY_UI_TYPE = LazyDictionary(\n    {\n        UIType.BOOLEAN:\n        {\n            \"options\": [\n                {\n                    \"name\": \"input\", \"type\": \"string\",\n                    \"enum\": ['dropdown', 'checkbox']\n                },\n                {\n                    'name': \"custom_labels\", \"type\": \"object\",\n                    \"items\": [\n                        {\"name\": \"TRUE\", \"type\": \"string\"},\n                        {'name': \"FALSE\", \"type\": \"string\"}\n                    ]\n                }\n            ]\n\n        },\n        UIType.NUMBER:\n        {\n            \"options\": [\n                {\n                    \"name\": \"show_as_percentage\",\n                    \"type\": \"string\",\n                    \"enum\": ['dropdown', 'checkbox']\n                },\n                {\n                    \"name\": \"use_grouping\",\n                    \"type\": \"string\",\n                    \"enum\": ['true', 'false', 'auto']\n                },\n                {\n                    \"name\": \"locale\",\n                    \"type\": \"string\"\n                }\n            ]\n        },\n        UIType.DATETIME:\n        {\n            \"options\": [{\"name\": \"format\", \"type\": \"string\"}]\n        },\n        UIType.TIME:\n        {\n            \"options\": [{\"name\": \"format\", \"type\": \"string\"}]\n        },\n        UIType.DATE:\n        {\n            \"options\": [{\"name\": \"format\", \"type\": \"string\"}]\n        },\n        UIType.DURATION:\n        {\n            \"options\": [\n                {\"name\": \"min\", \"type\": \"string\"},\n                {\"name\": \"max\", \"type\": \"string\"},\n                {\"name\": \"show_units\", \"type\": \"boolean\"},\n            ]\n        },\n        # NOTE: below callable will be evaluated lazily by LazyDictionary\n        UIType.MONEY: _money_display_options_schema,\n    }\n)\n", "path": "mathesar/api/display_options.py"}, {"content": "from django.core.exceptions import ImproperlyConfigured\nfrom rest_framework import serializers\n\nfrom mathesar.api.exceptions.mixins import MathesarErrorMessageMixin\nfrom mathesar.database.types import UIType, get_ui_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    default_serializer = None\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_serializer(self, serializer_cls, *args, **kwargs):\n        if callable(serializer_cls):\n            serializer = serializer_cls(*args, **kwargs)\n            serializer.parent = self\n        else:\n            serializer = serializer_cls\n        return serializer\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        if self.default_serializer is not None:\n            self.default_serializer = self._init_serializer(self.default_serializer, *args, **kwargs)\n        for identifier, serializer_cls in serializers_mapping.items():\n            serializer = self._init_serializer(serializer_cls, *args, **kwargs)\n            self.serializers_mapping[identifier] = serializer\n            self.serializers_cls_mapping[identifier] = serializer_cls\n\n    def get_serializer_class(self, identifier):\n        if identifier in self.serializers_mapping:\n            return self.serializers_mapping.get(identifier)\n        else:\n            return self.default_serializer\n\n    def to_representation(self, instance):\n        serializer = self.get_serializer_class(self.get_mapping_field(instance))\n        if serializer is not None:\n            return serializer.to_representation(instance)\n        else:\n            return instance\n\n    def get_mapping_field(self, data):\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.get_serializer_class(self.get_mapping_field(data))\n        if serializer is not None:\n            return serializer.to_internal_value(data=data)\n        else:\n            data = {}\n            return data\n\n    def validate(self, attrs):\n        serializer = self.serializers_mapping.get(self.get_mapping_field(attrs))\n        if serializer is not None:\n            return serializer.validate(attrs)\n        return {}\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 MathesarPolymorphicErrorMixin(MathesarErrorMessageMixin):\n    def get_serializer_fields(self, data):\n        return self.serializers_mapping[self.get_mapping_field(data)].fields\n\n\nclass BaseDisplayOptionsSerializer(MathesarErrorMessageMixin, OverrideRootPartialMixin, serializers.Serializer):\n    show_fk_preview = serializers.BooleanField(default=True)\n\n\nclass CustomBooleanLabelSerializer(MathesarErrorMessageMixin, serializers.Serializer):\n    TRUE = serializers.CharField()\n    FALSE = serializers.CharField()\n\n\n# This is the key which will determine which display options serializer is used. Its value is\n# supposed to be the column's DB type (a DatabaseType instance).\nDISPLAY_OPTIONS_SERIALIZER_MAPPING_KEY = 'db_type'\n\n\nclass BooleanDisplayOptionSerializer(BaseDisplayOptionsSerializer):\n    input = serializers.ChoiceField(choices=[(\"dropdown\", \"dropdown\"), (\"checkbox\", \"checkbox\")])\n    custom_labels = CustomBooleanLabelSerializer(required=False)\n\n\nclass AbstractNumberDisplayOptionSerializer(BaseDisplayOptionsSerializer):\n    number_format = serializers.ChoiceField(\n        required=False,\n        allow_null=True,\n        choices=['english', 'german', 'french', 'hindi', 'swiss']\n    )\n\n    use_grouping = serializers.ChoiceField(required=False, choices=['true', 'false', 'auto'], default='auto')\n    \"\"\"\n    The choices here correspond to the options available for the `useGrouping`\n    property within the [Intl API][1]. True and False are encoded as strings\n    instead of booleans to maintain consistency with the Intl API and to keep\n    the type consistent. We did considering using an optional boolean but\n    decided a string would be better, especially if we want to support other\n    options eventually, like \"min2\".\n\n    [1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat\n    \"\"\"\n\n\nclass NumberDisplayOptionSerializer(AbstractNumberDisplayOptionSerializer):\n    show_as_percentage = serializers.BooleanField(default=False)\n\n\nclass MoneyDisplayOptionSerializer(AbstractNumberDisplayOptionSerializer):\n    currency_symbol = serializers.CharField()\n    currency_symbol_location = serializers.ChoiceField(choices=['after-minus', 'end-with-space'])\n\n\nclass TimeFormatDisplayOptionSerializer(BaseDisplayOptionsSerializer):\n    format = serializers.CharField(max_length=255)\n\n\nclass DateTimeFormatDisplayOptionSerializer(BaseDisplayOptionsSerializer):\n    time_format = serializers.CharField(max_length=255)\n    date_format = serializers.CharField(max_length=255)\n\n\nclass DurationDisplayOptionSerializer(BaseDisplayOptionsSerializer):\n    min = serializers.CharField(max_length=255)\n    max = serializers.CharField(max_length=255)\n    show_units = serializers.BooleanField()\n\n\nclass DisplayOptionsMappingSerializer(\n    OverrideRootPartialMixin,\n    MathesarPolymorphicErrorMixin,\n    ReadWritePolymorphicSerializerMappingMixin,\n    serializers.Serializer\n):\n    serializers_mapping = {\n        UIType.BOOLEAN: BooleanDisplayOptionSerializer,\n        UIType.NUMBER: NumberDisplayOptionSerializer,\n        UIType.DATETIME: DateTimeFormatDisplayOptionSerializer,\n        UIType.DATE: TimeFormatDisplayOptionSerializer,\n        UIType.TIME: TimeFormatDisplayOptionSerializer,\n        UIType.DURATION: DurationDisplayOptionSerializer,\n        UIType.MONEY: MoneyDisplayOptionSerializer,\n    }\n    default_serializer = BaseDisplayOptionsSerializer\n\n    def get_mapping_field(self, _):\n        return self._get_ui_type_of_column_being_serialized()\n\n    def _get_ui_type_of_column_being_serialized(self):\n        db_type = self.context[DISPLAY_OPTIONS_SERIALIZER_MAPPING_KEY]\n        ui_type = get_ui_type_from_db_type(db_type)\n        return ui_type\n", "path": "mathesar/api/serializers/shared_serializers.py"}]}