This commit is contained in:
Berg Lucas 2025-04-03 16:06:47 +09:00 committed by GitHub
commit 7d1da75656
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 85 additions and 1 deletions

View File

@ -580,6 +580,22 @@ This option is a dictionary, mapping field names to a dictionary of keyword argu
Please keep in mind that, if the field has already been explicitly declared on the serializer class, then the `extra_kwargs` option will be ignored.
It is also possible to create new serializer fields from any related model fields using the `extra_kwargs` option. For example:
class UserProfile(models.Model):
birthdate = models.DateField()
user = models.ForeignKey(User, on_delete=models.CASCADE)
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = ['date_of_birth', 'first_name', 'last_name']
extra_kwargs = {
'date_of_birth': {'source': 'birthdate'},
'first_name': {'source': 'user.first_name'},
'last_name': {'source': 'user.last_name'}
}
## Relational fields
When serializing model instances, there are a number of different ways you might choose to represent relationships. The default representation for `ModelSerializer` is to use the primary keys of the related instances.

View File

@ -1101,9 +1101,36 @@ class ModelSerializer(Serializer):
if source == '*':
source = field_name
# Get the right model and info for source with attributes
source_attrs = source.split('.')
source_info = info
source_model = model
if len(source_attrs) > 1:
attr_info = info
attr_model = model
for attr in source_attrs[:-1]:
if attr not in attr_info.relations:
break
attr_model = attr_info.relations[attr].related_model
attr_info = model_meta.get_field_info(attr_model)
else:
attr = source_attrs[-1]
if (
attr in attr_info.fields_and_pk
or attr in attr_info.relations
or hasattr(attr_model, attr)
or attr == self.url_field_name
):
source = attr
source_info = attr_info
source_model = attr_model
# Determine the serializer field class and keyword arguments.
field_class, field_kwargs = self.build_field(
source, info, model, depth
source, source_info, source_model, depth
)
# Include any kwargs defined in `Meta.extra_kwargs`

View File

@ -13,6 +13,7 @@ import sys
import tempfile
import pytest
from django.contrib.auth.models import User
from django.core.exceptions import ImproperlyConfigured
from django.core.serializers.json import DjangoJSONEncoder
from django.core.validators import (
@ -731,6 +732,46 @@ class TestRelationalFieldMappings(TestCase):
""")
self.assertEqual(repr(TestSerializer()), expected)
def test_source_with_attributes(self):
class UserProfile(models.Model):
age = models.IntegerField()
birthdate = models.DateField()
user = models.ForeignKey(User, on_delete=models.CASCADE)
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = ('username', 'email', 'first_name', 'last_name', 'age', 'birthdate')
extra_kwargs = {
'username': {
'source': 'user.username',
},
'email': {
'source': 'user.email',
},
'first_name': {
'source': 'user.first_name',
},
'last_name': {
'source': 'user.last_name',
}
}
# In Django 3.0, the maximum length of first_name is 30, whereas it is 150
# in later versions, so we can't hard-code the value in the expected variable.
max_length = User.first_name.field.max_length
expected = dedent(f"""
UserProfileSerializer():
username = CharField(help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, source='user.username', validators=[<django.contrib.auth.validators.UnicodeUsernameValidator object>, <UniqueValidator(queryset=User.objects.all())>])
email = EmailField(allow_blank=True, label='Email address', max_length=254, required=False, source='user.email')
first_name = CharField(allow_blank=True, max_length={max_length}, required=False, source='user.first_name')
last_name = CharField(allow_blank=True, max_length=150, required=False, source='user.last_name')
age = IntegerField()
birthdate = DateField()
""")
self.assertEqual(repr(UserProfileSerializer()), expected)
class DisplayValueTargetModel(models.Model):
name = models.CharField(max_length=100)