Repository: mathesar-foundation/mathesar

Issue:
KeyError thrown and Incorrect display_options present when altering a column type from TEXT to DATE
## Description
When patching a column from `TEXT` to `DATE`,
* Irrelevant `display_option "show_as_percentage"` is present on the column
* Further requests to `columns/` and `tables/` endpoints, throws a 500 with keyError.

## Reproduction
* Create a TEXT column
* PATCH it to DATE. (Alternatively, change type to DATE on the frontend).
   ```
  {
    type: "DATE",
    display_options: {},
    type_options: {},
  }
   ```
* Notice that the table fails to loads on the frontend.
* Requests to `columns/` and `tables/` endpoints result in a 500 with,
   > Got KeyError when attempting to get a value for field `format` on serializer `TimeFormatDisplayOptionSerializer`.\nThe serializer field might be named incorrectly and not match any attribute or key on the `dict` instance.\nOriginal exception text was: 'format'.
* Check the preloaded tables list by executing the following on the developer tools:
  ```javascript
  JSON.parse(document.querySelector('#common-data').textContent).tables
  ```
* Notice that incorrect fields are present in the display_options of the modified column: 
   ```javascript
  {
    id: 34,
    name: "Date Shared",
    type: "DATE",
    type_options :null,
    display_options:{
      show_as_percentage: false
    }
  }
   ```

## Expected behavior
* `display_options` field should not contain invalid entries.
* The 500 with keyError should not be thrown.


Discussion:

Resolving patch:
diff --git a/mathesar/api/exceptions/mixins.py b/mathesar/api/exceptions/mixins.py
--- a/mathesar/api/exceptions/mixins.py
+++ b/mathesar/api/exceptions/mixins.py
@@ -27,7 +27,7 @@ def build_pretty_errors(self, errors, serializer=None):
                 else:
                     pretty.extend(self.get_non_field_error_entries(errors[error_type]))
             else:
-                field = self.fields.fields[error_type]
+                field = self.get_serializer_fields().fields[error_type]
                 if isinstance(field, Serializer) and type(errors[error_type]) == dict:
                     field.initial_data = self.initial_data[error_type]
                     child_errors = field.build_pretty_errors(errors[error_type])
@@ -43,6 +43,9 @@ def build_pretty_errors(self, errors, serializer=None):
             return pretty
         return []
 
+    def get_serializer_fields(self):
+        return self.fields
+
     def _run_validator(self, validator, field, message):
         """
         This method build on top of `_run_validator` method of the superclass
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
@@ -36,7 +36,6 @@ def __init__(self, *args, **kwargs):
     def to_representation(self, instance):
         serializer = self.serializers_mapping.get(self.get_mapping_field(), None)
         if serializer is not None:
-            self.__class__ = self.serializers_cls_mapping.get(self.get_mapping_field())
             return serializer.to_representation(instance)
         else:
             raise Exception(f"Cannot find a matching serializer for the specified type {self.get_mapping_field()}")
@@ -55,7 +54,6 @@ class ReadWritePolymorphicSerializerMappingMixin(ReadOnlyPolymorphicSerializerMa
     def to_internal_value(self, data):
         serializer = self.serializers_mapping.get(self.get_mapping_field())
         if serializer is not None:
-            self.__class__ = self.serializers_cls_mapping.get(self.get_mapping_field())
             return serializer.to_internal_value(data=data)
         else:
             raise Exception(f"Cannot find a matching serializer for the specified type {self.get_mapping_field()}")
@@ -94,6 +92,11 @@ def run_validation(self, *args, **kwargs):
         return super().run_validation(*args, **kwargs)
 
 
+class MathesarPolymorphicErrorMixin(MathesarErrorMessageMixin):
+    def get_serializer_fields(self):
+        return self.serializers_mapping[self.get_mapping_field()].fields
+
+
 class CustomBooleanLabelSerializer(MathesarErrorMessageMixin, serializers.Serializer):
     TRUE = serializers.CharField()
     FALSE = serializers.CharField()
@@ -134,8 +137,8 @@ class DurationDisplayOptionSerializer(MathesarErrorMessageMixin, OverrideRootPar
 
 
 class DisplayOptionsMappingSerializer(
-    MathesarErrorMessageMixin,
     OverrideRootPartialMixin,
+    MathesarPolymorphicErrorMixin,
     ReadWritePolymorphicSerializerMappingMixin,
     serializers.Serializer
 ):


Test patch:
diff --git a/mathesar/tests/api/test_column_api.py b/mathesar/tests/api/test_column_api.py
--- a/mathesar/tests/api/test_column_api.py
+++ b/mathesar/tests/api/test_column_api.py
@@ -64,7 +64,7 @@ def column_test_table_with_service_layer_options(patent_schema):
     column_data_list = [{},
                         {'display_options': {'input': "dropdown", "custom_labels": {"TRUE": "yes", "FALSE": "no"}}},
                         {'display_options': {'show_as_percentage': True, 'number_format': "english"}},
-                        {},
+                        {'display_options': None},
                         {},
                         {},
                         {'display_options': {'format': 'YYYY-MM-DD hh:mm'}}]
@@ -497,6 +497,23 @@ def test_column_update_type_invalid_display_options(column_test_table_with_servi
     assert response.status_code == 400
 
 
+def test_column_update_type_get_all_columns(column_test_table_with_service_layer_options, client):
+    cache.clear()
+    table, columns = column_test_table_with_service_layer_options
+    colum_name = "mycolumn2"
+    column = _get_columns_by_name(table, [colum_name])[0]
+    column_id = column.id
+    display_options_data = {'type': 'BOOLEAN'}
+    client.patch(
+        f"/api/db/v0/tables/{table.id}/columns/{column_id}/",
+        display_options_data,
+    )
+    new_columns_response = client.get(
+        f"/api/db/v0/tables/{table.id}/columns/"
+    )
+    assert new_columns_response.status_code == 200
+
+
 def test_column_display_options_type_on_reflection(column_test_table,
                                                    client, engine):
     cache.clear()