django休息框架 - 你如何压扁嵌套数据?

时间:2014-01-27 13:05:35

标签: django django-rest-framework

我有一个管理多对多关系的“通过”模型,我希望能够将“直通”模型和目标模型作为平面数据返回,而不是将目标模型嵌套。

因此,对于带有通过的多对多的标准示例,请说这些是模型,

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数据中,以便它是平的而不是嵌套的?

...希望一切都有道理。

5 个答案:

答案 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'))