Skip to content

Commit

Permalink
Merge pull request #1392 from centerofci/default-polymorphic-serializer
Browse files Browse the repository at this point in the history
Default polymorphic serializer
  • Loading branch information
silentninja authored May 31, 2022
2 parents a73f496 + f828dd9 commit 11a0b77
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 54 deletions.
2 changes: 1 addition & 1 deletion mathesar/api/serializers/constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ class Meta:
}

def create(self, validated_data):
serializer = self.serializers_mapping.get(self.get_mapping_field(validated_data))
serializer = self.get_serializer_class(self.get_mapping_field(validated_data))
return serializer.create(validated_data)

def get_mapping_field(self, data):
Expand Down
68 changes: 37 additions & 31 deletions mathesar/api/serializers/shared_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class ReadOnlyPolymorphicSerializerMappingMixin:
This serializer mixin is helpful in serializing polymorphic models,
by switching to correct serializer based on the mapping field value.
"""
default_serializer = None

def __new__(cls, *args, **kwargs):
if cls.serializers_mapping is None:
Expand All @@ -19,22 +20,34 @@ def __new__(cls, *args, **kwargs):
)
return super().__new__(cls, *args, **kwargs)

def _init_serializer(self, serializer_cls, *args, **kwargs):
if callable(serializer_cls):
serializer = serializer_cls(*args, **kwargs)
serializer.parent = self
else:
serializer = serializer_cls
return serializer

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.serializers_cls_mapping = {}
serializers_mapping = self.serializers_mapping
self.serializers_mapping = {}
if self.default_serializer is not None:
self.default_serializer = self._init_serializer(self.default_serializer, *args, **kwargs)
for identifier, serializer_cls in serializers_mapping.items():
if callable(serializer_cls):
serializer = serializer_cls(*args, **kwargs)
serializer.parent = self
else:
serializer = serializer_cls
serializer = self._init_serializer(serializer_cls, *args, **kwargs)
self.serializers_mapping[identifier] = serializer
self.serializers_cls_mapping[identifier] = serializer_cls

def get_serializer_class(self, identifier):
if identifier in self.serializers_mapping:
return self.serializers_mapping.get(identifier)
else:
return self.default_serializer

def to_representation(self, instance):
serializer = self.serializers_mapping.get(self.get_mapping_field(instance), None)
serializer = self.get_serializer_class(self.get_mapping_field(instance))
if serializer is not None:
return serializer.to_representation(instance)
else:
Expand All @@ -52,7 +65,7 @@ def get_mapping_field(self, data):

class ReadWritePolymorphicSerializerMappingMixin(ReadOnlyPolymorphicSerializerMappingMixin):
def to_internal_value(self, data):
serializer = self.serializers_mapping.get(self.get_mapping_field(data))
serializer = self.get_serializer_class(self.get_mapping_field(data))
if serializer is not None:
return serializer.to_internal_value(data=data)
else:
Expand Down Expand Up @@ -104,6 +117,10 @@ def get_serializer_fields(self, data):
return self.serializers_mapping[self.get_mapping_field(data)].fields


class BaseDisplayOptionsSerializer(MathesarErrorMessageMixin, OverrideRootPartialMixin, serializers.Serializer):
show_fk_preview = serializers.BooleanField(default=True)


class CustomBooleanLabelSerializer(MathesarErrorMessageMixin, serializers.Serializer):
TRUE = serializers.CharField()
FALSE = serializers.CharField()
Expand All @@ -114,50 +131,38 @@ class CustomBooleanLabelSerializer(MathesarErrorMessageMixin, serializers.Serial
DISPLAY_OPTIONS_SERIALIZER_MAPPING_KEY = 'db_type'


class BooleanDisplayOptionSerializer(MathesarErrorMessageMixin, OverrideRootPartialMixin, serializers.Serializer):
class BooleanDisplayOptionSerializer(BaseDisplayOptionsSerializer):
input = serializers.ChoiceField(choices=[("dropdown", "dropdown"), ("checkbox", "checkbox")])
custom_labels = CustomBooleanLabelSerializer(required=False)


class AbstractNumberDisplayOptionSerializer(serializers.Serializer):
number_format = serializers.ChoiceField(required=False, allow_null=True, choices=['english', 'german', 'french', 'hindi', 'swiss'])
class AbstractNumberDisplayOptionSerializer(BaseDisplayOptionsSerializer):
number_format = serializers.ChoiceField(
required=False,
allow_null=True,
choices=['english', 'german', 'french', 'hindi', 'swiss']
)


class NumberDisplayOptionSerializer(
MathesarErrorMessageMixin,
OverrideRootPartialMixin,
AbstractNumberDisplayOptionSerializer
):
class NumberDisplayOptionSerializer(AbstractNumberDisplayOptionSerializer):
show_as_percentage = serializers.BooleanField(default=False)


class MoneyDisplayOptionSerializer(
MathesarErrorMessageMixin,
OverrideRootPartialMixin,
AbstractNumberDisplayOptionSerializer
):
class MoneyDisplayOptionSerializer(AbstractNumberDisplayOptionSerializer):
currency_symbol = serializers.CharField()
currency_symbol_location = serializers.ChoiceField(choices=['after-minus', 'end-with-space'])


class TimeFormatDisplayOptionSerializer(
MathesarErrorMessageMixin,
OverrideRootPartialMixin,
serializers.Serializer
):
class TimeFormatDisplayOptionSerializer(BaseDisplayOptionsSerializer):
format = serializers.CharField(max_length=255)


class DateTimeFormatDisplayOptionSerializer(
MathesarErrorMessageMixin,
OverrideRootPartialMixin,
serializers.Serializer
):
class DateTimeFormatDisplayOptionSerializer(BaseDisplayOptionsSerializer):
time_format = serializers.CharField(max_length=255)
date_format = serializers.CharField(max_length=255)


class DurationDisplayOptionSerializer(MathesarErrorMessageMixin, OverrideRootPartialMixin, serializers.Serializer):
class DurationDisplayOptionSerializer(BaseDisplayOptionsSerializer):
min = serializers.CharField(max_length=255)
max = serializers.CharField(max_length=255)
show_units = serializers.BooleanField()
Expand All @@ -178,6 +183,7 @@ class DisplayOptionsMappingSerializer(
UIType.DURATION: DurationDisplayOptionSerializer,
UIType.MONEY: MoneyDisplayOptionSerializer,
}
default_serializer = BaseDisplayOptionsSerializer

def get_mapping_field(self, _):
return self._get_ui_type_of_column_being_serialized()
Expand Down
46 changes: 24 additions & 22 deletions mathesar/tests/api/test_column_api_display_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ 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': {'input': "dropdown", "custom_labels": {"TRUE": "yes", "FALSE": "no"}}, 'show_fk_preview': True},
{'display_options': {'show_as_percentage': True, 'number_format': "english", 'show_fk_preview': True}},
{'display_options': None},
{},
{
Expand All @@ -37,12 +37,13 @@ def column_test_table_with_service_layer_options(patent_schema):
'currency_symbol': "HK $",
'number_format': "english",
'currency_symbol_location': 'after-minus'
}
},
'show_fk_preview': True
}
},
{'display_options': {'time_format': 'hh:mm', 'date_format': 'YYYY-MM-DD'}},
{'display_options': {'format': 'hh:mm'}},
{'display_options': {'format': 'YYYY-MM-DD'}},
{'display_options': {'time_format': 'hh:mm', 'date_format': 'YYYY-MM-DD', 'show_fk_preview': True}},
{'display_options': {'format': 'hh:mm', 'show_fk_preview': True}},
{'display_options': {'format': 'YYYY-MM-DD', 'show_fk_preview': True}},
]
db_table = SATable(
"anewtable",
Expand Down Expand Up @@ -71,57 +72,57 @@ def column_test_table_with_service_layer_options(patent_schema):
(
PostgresType.BOOLEAN,
{"input": "dropdown"},
{"input": "dropdown"}
{"input": "dropdown", 'show_fk_preview': True}
),
(
PostgresType.BOOLEAN,
{"input": "checkbox", "custom_labels": {"TRUE": "yes", "FALSE": "no"}},
{"input": "checkbox", "custom_labels": {"TRUE": "yes", "FALSE": "no"}}
{"input": "checkbox", "custom_labels": {"TRUE": "yes", "FALSE": "no"}, 'show_fk_preview': True}
),
(
PostgresType.DATE,
{'format': 'YYYY-MM-DD'},
{'format': 'YYYY-MM-DD'}
{'format': 'YYYY-MM-DD', 'show_fk_preview': True}
),
(
PostgresType.INTERVAL,
{'min': 's', 'max': 'h', 'show_units': True},
{'min': 's', 'max': 'h', 'show_units': True}
{'min': 's', 'max': 'h', 'show_units': True, 'show_fk_preview': True}
),
(
PostgresType.MONEY,
{'number_format': "english", 'currency_symbol': '$', 'currency_symbol_location': 'after-minus'},
{'currency_symbol': '$', 'currency_symbol_location': 'after-minus', 'number_format': "english"}
{'currency_symbol': '$', 'currency_symbol_location': 'after-minus', 'number_format': "english", 'show_fk_preview': True}
),
(
PostgresType.NUMERIC,
{"show_as_percentage": True, 'number_format': None},
{"show_as_percentage": True, 'number_format': None}
{"show_as_percentage": True, 'number_format': None, 'show_fk_preview': True}
),
(
PostgresType.NUMERIC,
{"show_as_percentage": True, 'number_format': "english"},
{"show_as_percentage": True, 'number_format': "english"}
{"show_as_percentage": True, 'number_format': "english", 'show_fk_preview': True}
),
(
PostgresType.TIMESTAMP_WITH_TIME_ZONE,
{'date_format': 'x', 'time_format': 'x'},
{'date_format': 'x', 'time_format': 'x'}
{'date_format': 'x', 'time_format': 'x', 'show_fk_preview': True}
),
(
PostgresType.TIMESTAMP_WITHOUT_TIME_ZONE,
{'date_format': 'x', 'time_format': 'x'},
{'date_format': 'x', 'time_format': 'x'}
{'date_format': 'x', 'time_format': 'x', 'show_fk_preview': True}
),
(
PostgresType.TIME_WITHOUT_TIME_ZONE,
{'format': 'hh:mm'},
{'format': 'hh:mm'}
{'format': 'hh:mm', 'show_fk_preview': True}
),
(
PostgresType.TIME_WITH_TIME_ZONE,
{'format': 'hh:mm Z'},
{'format': 'hh:mm Z'}
{'format': 'hh:mm Z', 'show_fk_preview': True}
),
]

Expand Down Expand Up @@ -230,7 +231,8 @@ def test_column_update_display_options(column_test_table_with_service_layer_opti
column_id = column.id
display_options = {
"input": "dropdown",
"custom_labels": {"TRUE": "yes", "FALSE": "no"}
"custom_labels": {"TRUE": "yes", "FALSE": "no"},
'show_fk_preview': False
}
column_data = {
'type': PostgresType.BOOLEAN.id,
Expand Down Expand Up @@ -317,11 +319,11 @@ def test_column_alter_same_type_display_options(


@pytest.mark.parametrize(
"display_options,type_options",
[[None, None], [{}, {}]]
"display_options,type_options, expected_display_options, expected_type_options",
[[None, None, None, None], [{}, {}, {'show_fk_preview': True}, {}]]
)
def test_column_update_type_with_display_and_type_options_as_null_or_empty_obj(
column_test_table, client, display_options, type_options
column_test_table, client, display_options, type_options, expected_display_options, expected_type_options
):
db_type_id = MathesarCustomType.URI.id
data = {
Expand All @@ -337,6 +339,6 @@ def test_column_update_type_with_display_and_type_options_as_null_or_empty_obj(
assert response.status_code == 200
response_json = response.json()
assert response_json["type"] == db_type_id
assert response_json["display_options"] == display_options
assert response_json["display_options"] == expected_display_options
# For some reason, type_options will reflect None, whether it was updated to None or to {}.
assert response_json["type_options"] is None

0 comments on commit 11a0b77

Please sign in to comment.