我有一个管理多对多关系的“通过”模型,我希望能够将“直通”模型和目标模型作为平面数据返回,而不是将目标模型嵌套。
因此,对于带有通过的多对多的标准示例,请说这些是模型,
class Person(models.Model):
first_name = models.CharField(max_length=128)
last_name = models.CharField(max_length=128)
favourite_food = models.CharField(max_length=128)
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
class Membership(models.Model):
person = models.ForeignKey(Person)
group = models.ForeignKey(Group)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
因此,目前返回会员资格的序列化工具是,
class MembershipSerializer(serializers.HyperlinkedModelSerializer):
person = PersonSerializer()
class Meta:
model = Membership
fields = ('id', 'url', 'group', 'date_joined', 'invite_reason', 'person')
class PersonSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Person
fields = ('first_name', 'last_name', 'favourite_food')
因此,当我使用MembershipSerializer检索成员资格模型时,我得到了这个json,
{
'id':1,
'url':'http://cheeselovers.com/api/member/1/'
'group':'http://cheeselovers.com/api/group/1/'
'date_joined': '2014-01-24T16:33:40.781Z',
'invite_reason': 'loves cheese',
'person':{
'first_name':'Barry',
'last_name':'CheeseLover',
'favourite_food': 'cheese'
}
}
但我要归还的是这个,
{
'id':1,
'url':'http://cheeselovers.com/api/member/1/'
'group':'http://cheeselovers.com/api/group/1/'
'date_joined': '2014-01-24T16:33:40.781Z',
'invite_reason': 'loves cheese',
'first_name':'Barry',
'last_name':'CheeseLover',
'favourite_food': 'cheese'
}
现在我意识到我可以通过将MembershipSerializer更改为此来简单地完成此任务,
class MembershipSerializer(serializers.HyperlinkedModelSerializer):
first_name = serializers.Field(source='person.first_name')
last_name = serializers.Field(source='person.last_name')
favourite_food = serializers.Field(source='person.favourite_food')
class Meta:
model = Membership
fields = ('id', 'url', 'group', 'date_joined', 'invite_reason', 'first_name', 'last_name', 'favourite_food')
但是,目标模型我有10个属性,中间'通'模型只有只读道具,所以我已经有一个功能的目标模型序列化器,在创建中间模型时使用。
能够重复使用它感觉更干,因此如果目标模型上的任何内容发生更改,我只需要对其序列化程序进行更改,因为这些更改将反映在中介的序列化程序返回的数据中
那么我有没有办法从PersonSerializer获取数据并将其添加到Membership数据中,以便它是平的而不是嵌套的?
...希望一切都有道理。
答案 0 :(得分:20)
这是基于James答案的方法,但是对于更新版本的Django Rest Framework以及对读写的支持(仅更新嵌套字段,添加创建应该很容易,请参阅DRF的文档。)< / p>
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = ('phone', 'some', 'other', 'fields')
class UserDetailsSerializer(serializers.ModelSerializer):
"""User model with Profile. Handled as a single object, profile is flattened."""
profile = ProfileSerializer()
class Meta:
model = User
fields = ('username', 'email', 'profile')
read_only_fields = ('email', )
def to_representation(self, obj):
"""Move fields from profile to user representation."""
representation = super().to_representation(obj)
profile_representation = representation.pop('profile')
for key in profile_representation:
representation[key] = profile_representation[key]
return representation
def to_internal_value(self, data):
"""Move fields related to profile to their own profile dictionary."""
profile_internal = {}
for key in ProfileSerializer.Meta.fields:
if key in data:
profile_internal[key] = data.pop(key)
internal = super().to_internal_value(data)
internal['profile'] = profile_internal
return internal
def update(self, instance, validated_data):
"""Update user and profile. Assumes there is a profile for every user."""
profile_data = validated_data.pop('profile')
super().update(instance, validated_data)
profile = instance.profile
for attr, value in profile_data.items():
setattr(profile, attr, value)
profile.save()
return instance
答案 1 :(得分:9)
我不相信这是最简单的方法,但我想出的解决方案是覆盖MembershipSerializer的to_native方法,然后手动创建并调用PersonSerializer的to_native方法并将两个结果字典合并在一起< / p>
class MembershipSerializer(serializers.HyperlinkedModelSerializer):
def to_native(self, obj):
ret = super(MembershipSerializer, self).to_native(obj)
p_serializer = PersonSerializer(obj.person, context=self.context)
p_ret = p_serializer.to_native(obj.person)
for key in p_ret:
ret[key] = p_ret[key]
return ret
class Meta:
model = Membership
fields = ('id', 'url', 'group', 'date_joined', 'invite_reason', 'person')
字典既是SortedDict的子类,也是SortedDict的子类。我不确定是否有一个显式方法来合并保留顺序的两个方法,所以我只是使用了一个循环。
答案 2 :(得分:8)
詹姆斯&#39;答案是我最终使用的。由于我有几个使用此方法的序列化程序,我将其转换为mixin:
class FlattenMixin(object):
"""Flatens the specified related objects in this representation"""
def to_representation(self, obj):
assert hasattr(self.Meta, 'flatten'), (
'Class {serializer_class} missing "Meta.flatten" attribute'.format(
serializer_class=self.__class__.__name__
)
)
# Get the current object representation
rep = super(FlattenMixin, self).to_representation(obj)
# Iterate the specified related objects with their serializer
for field, serializer_class in self.Meta.flatten:
serializer = serializer_class(context = self.context)
objrep = serializer.to_representation(getattr(obj, field))
#Include their fields, prefixed, in the current representation
for key in objrep:
rep[field + "__" + key] = objrep[key]
return rep
这样,您可以执行以下操作:
class MembershipSerializer(FlattenMixin, serializers.HyperlinkedModelSerializer):
class Meta:
model = Membership
fields = ('id', 'url', 'group', 'date_joined', 'invite_reason')
flatten = [ ('person', PersonSerializer) ]
答案 3 :(得分:4)
我没有使用HyperlinkedModelSerializer
进行尝试,但使用ModelSerializer
,您可以制作支持flatten
选项的自定义序列化程序类。
class CustomModelSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
self.flatten = kwargs.pop('flatten', False)
super(CustomModelSerializer, self).__init__(*args, **kwargs)
def get_fields(self):
fields = super(CustomModelSerializer, self).get_fields()
for field_name, field in fields.items():
if getattr(field, 'flatten', False):
del fields[field_name]
for nested_field_name, nested_field in field.fields.iteritems():
nested_field.source = (field_name + '.' +
(nested_field.source or nested_field_name))
fields[nested_field_name] = nested_field
return fields
用法:
class MembershipSerializer(CustomModelSerializer):
person = PersonSerializer(flatten=True)
class Meta:
model = Membership
fields = ('person', ...)
class PersonSerializer(CustomModelSerializer):
class Meta:
model = Person
fields = (...)
答案 4 :(得分:0)
结合ekuusela的答案和DRF文档中的this example,您还可以控制要显示的字段(来自嵌套对象)。
您的序列化器看起来像这样
class UserDetailsSerializer(serializers.ModelSerializer):
"""User model with Profile. Handled as a single object, profile is flattened."""
profile = ProfileSerializer()
def __init__(self, *args, **kwargs):
self.allow_fields = kwargs.pop('fields', None)
super(ProfileSerializer, self).__init__(*args, **kwargs)
class Meta:
model = User
fields = ('username', 'email', 'profile')
def to_representation(self, instance):
representation = super().to_representation(instance)
profile_representation = representation.pop('profile')
representation.update(profile_representation)
if self.allow_fields is not None:
# Drop any fields that are not specified in the `fields` argument.
allowed = set(self.allow_fields)
existing = set(representation)
for field_name in existing - allowed:
representation.pop(field_name)
return representation
您将实例化序列化器,就好像它只是一个单一模型
serializer = UserDetailsSerializer(user, fields=('username', 'email','profile_field1', 'profile_field2'))