Add MODEL_SERIALIZER_FIELD_MAPPING settings

This commit is contained in:
Stanislav Khlud 2024-08-21 09:55:29 +07:00
parent f113ab6b68
commit 760921a9bb
No known key found for this signature in database
GPG Key ID: 4A9896D700E79EB7
4 changed files with 115 additions and 43 deletions

View File

@ -143,6 +143,17 @@ Default: `ordering`
--- ---
## Serializer settings
#### MODEL_SERIALIZER_FIELD_MAPPING
Extra field mapping used to extend or override mapping of django db fields to serializer fields which is used by
ModelSerializer to set up fields for serializer.
Default: `{}`
---
## Versioning settings ## Versioning settings
#### DEFAULT_VERSION #### DEFAULT_VERSION

View File

@ -894,13 +894,35 @@ class ModelSerializer(Serializer):
* A set of default validators are automatically populated. * A set of default validators are automatically populated.
* Default `.create()` and `.update()` implementations are provided. * Default `.create()` and `.update()` implementations are provided.
"""
serializer_related_field = PrimaryKeyRelatedField
serializer_related_to_field = SlugRelatedField
serializer_url_field = HyperlinkedIdentityField
serializer_choice_field = ChoiceField
# The field name for hyperlinked identity fields. Defaults to 'url'.
# You can modify this using the API setting.
#
# Note that if you instead need modify this on a per-serializer basis,
# you'll also need to ensure you update the `create` method on any generic
# views, to correctly handle the 'Location' response header for
# "HTTP 201 Created" responses.
url_field_name = None
@property
def serializer_field_mapping(self):
"""Get mapping of django model field to serializer field.
The process of automatically determining a set of serializer fields The process of automatically determining a set of serializer fields
based on the model fields is reasonably complex, but you almost certainly based on the model fields is reasonably complex, but you almost certainly
don't need to dig into the implementation. don't need to dig into the implementation.
If the `ModelSerializer` class *doesn't* generate the set of fields that If the `ModelSerializer` class *doesn't* generate the set of fields that
you need you should either declare the extra/differing fields explicitly on you need you should either extend serializer_field_mapping with
the serializer class, or simply use a `Serializer` class. the extra/differing fields explicitly, or simply use a `Serializer`
class.
""" """
serializer_field_mapping = { serializer_field_mapping = {
models.AutoField: IntegerField, models.AutoField: IntegerField,
@ -936,19 +958,12 @@ class ModelSerializer(Serializer):
serializer_field_mapping[postgres_fields.HStoreField] = HStoreField serializer_field_mapping[postgres_fields.HStoreField] = HStoreField
serializer_field_mapping[postgres_fields.ArrayField] = ListField serializer_field_mapping[postgres_fields.ArrayField] = ListField
serializer_field_mapping[postgres_fields.JSONField] = JSONField serializer_field_mapping[postgres_fields.JSONField] = JSONField
serializer_related_field = PrimaryKeyRelatedField for (
serializer_related_to_field = SlugRelatedField model_field,
serializer_url_field = HyperlinkedIdentityField serializer_field,
serializer_choice_field = ChoiceField ) in api_settings.MODEL_SERIALIZER_FIELD_MAPPING.items():
serializer_field_mapping[model_field] = serializer_field
# The field name for hyperlinked identity fields. Defaults to 'url'. return serializer_field_mapping
# You can modify this using the API setting.
#
# Note that if you instead need modify this on a per-serializer basis,
# you'll also need to ensure you update the `create` method on any generic
# views, to correctly handle the 'Location' response header for
# "HTTP 201 Created" responses.
url_field_name = None
# Default `create` and `update` behavior... # Default `create` and `update` behavior...
def create(self, validated_data): def create(self, validated_data):

View File

@ -126,6 +126,9 @@ DEFAULTS = {
'retrieve': 'read', 'retrieve': 'read',
'destroy': 'delete' 'destroy': 'delete'
}, },
# Serializers
'MODEL_SERIALIZER_FIELD_MAPPING': {}
} }
@ -147,7 +150,8 @@ IMPORT_STRINGS = [
'UNAUTHENTICATED_USER', 'UNAUTHENTICATED_USER',
'UNAUTHENTICATED_TOKEN', 'UNAUTHENTICATED_TOKEN',
'VIEW_NAME_FUNCTION', 'VIEW_NAME_FUNCTION',
'VIEW_DESCRIPTION_FUNCTION' 'VIEW_DESCRIPTION_FUNCTION',
'MODEL_SERIALIZER_FIELD_MAPPING',
] ]
@ -168,6 +172,16 @@ def perform_import(val, setting_name):
return import_from_string(val, setting_name) return import_from_string(val, setting_name)
elif isinstance(val, (list, tuple)): elif isinstance(val, (list, tuple)):
return [import_from_string(item, setting_name) for item in val] return [import_from_string(item, setting_name) for item in val]
elif isinstance(val, (dict)):
return {
import_from_string(
key,
setting_name,
): import_from_string(
value,
setting_name,
) for key, value in val.items()
}
return val return val

View File

@ -21,7 +21,7 @@ from django.core.validators import (
from django.db import models from django.db import models
from django.db.models.signals import m2m_changed from django.db.models.signals import m2m_changed
from django.dispatch import receiver from django.dispatch import receiver
from django.test import TestCase from django.test import TestCase, override_settings
from rest_framework import serializers from rest_framework import serializers
from rest_framework.compat import postgres_fields from rest_framework.compat import postgres_fields
@ -43,6 +43,12 @@ class CustomField(models.Field):
pass pass
class CustomCharFieldField(serializers.CharField):
"""
A custom serializer field simply for testing purposes.
"""
class OneFieldModel(models.Model): class OneFieldModel(models.Model):
char_field = models.CharField(max_length=100) char_field = models.CharField(max_length=100)
@ -194,6 +200,32 @@ class TestRegularFieldMappings(TestCase):
custom_field = ModelField\(model_field=<tests.test_model_serializer.CustomField: custom_field>\) custom_field = ModelField\(model_field=<tests.test_model_serializer.CustomField: custom_field>\)
file_path_field = FilePathField\(path=%r\) file_path_field = FilePathField\(path=%r\)
""" % tempfile.gettempdir()) """ % tempfile.gettempdir())
print(expected)
assert re.search(expected, repr(TestSerializer())) is not None
@override_settings(
REST_FRAMEWORK={
'MODEL_SERIALIZER_FIELD_MAPPING': {
'django.db.models.CharField': 'tests.test_model_serializer.CustomCharFieldField',
}
},
)
def test_custom_fields(self):
"""
If MODEL_SERIALIZER_FIELD_MAPPING is set than model fields should map
to their equivalent serializer fields.
"""
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = RegularFieldsModel
fields = (
"char_field",
)
expected = dedent(r"""
TestSerializer\(\):
char_field = CustomCharFieldField\(max_length=100\)
""")
assert re.search(expected, repr(TestSerializer())) is not None assert re.search(expected, repr(TestSerializer())) is not None
def test_field_options(self): def test_field_options(self):