104

我有看起来像这样的模型:

class Category(models.Model):
    parentCategory = models.ForeignKey('self', blank=True, null=True, related_name='subcategories')
    name = models.CharField(max_length=200)
    description = models.CharField(max_length=500)

我设法使用序列化程序获得所有类别的平面 json 表示:

class CategorySerializer(serializers.HyperlinkedModelSerializer):
    parentCategory = serializers.PrimaryKeyRelatedField()
    subcategories = serializers.ManyRelatedField()

    class Meta:
        model = Category
        fields = ('parentCategory', 'name', 'description', 'subcategories')

现在我想做的是让子类别列表具有子类别的内联 json 表示,而不是它们的 id。我将如何使用 django-rest-framework 来做到这一点?我试图在文档中找到它,但它似乎不完整。

4

11 回答 11

79

不要使用 ManyRelatedField,而是使用嵌套的序列化器作为您的字段:

class SubCategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = Category
        fields = ('name', 'description')

class CategorySerializer(serializers.ModelSerializer):
    parentCategory = serializers.PrimaryKeyRelatedField()
    subcategories = serializers.SubCategorySerializer()

    class Meta:
        model = Category
        fields = ('parentCategory', 'name', 'description', 'subcategories')

如果您想处理任意嵌套的字段,您应该查看文档的自定义默认字段部分。您目前不能直接将序列化程序声明为自身的字段,但您可以使用这些方法来覆盖默认使用的字段。

class CategorySerializer(serializers.ModelSerializer):
    parentCategory = serializers.PrimaryKeyRelatedField()

    class Meta:
        model = Category
        fields = ('parentCategory', 'name', 'description', 'subcategories')

        def get_related_field(self, model_field):
            # Handles initializing the `subcategories` field
            return CategorySerializer()

实际上,正如您所指出的,上述内容并不完全正确。这有点小技巧,但您可以尝试在序列化程序已经声明后添加该字段。

class CategorySerializer(serializers.ModelSerializer):
    parentCategory = serializers.PrimaryKeyRelatedField()

    class Meta:
        model = Category
        fields = ('parentCategory', 'name', 'description', 'subcategories')

CategorySerializer.base_fields['subcategories'] = CategorySerializer()

声明递归关系的机制是需要添加的。


编辑:请注意,现在有一个第三方包可以专门处理这种用例。请参阅djangorestframework-recursive

于 2012-11-14T10:40:19.367 回答
62

@wjin 的解决方案对我来说非常有用,直到我升级到 Django REST framework 3.0.0,它弃用了 to_native。这是我的 DRF 3.0 解决方案,稍作修改。

假设您有一个具有自引用字段的模型,例如名为“回复”的属性中的线程注释。您有此评论线程的树表示,并且您想要序列化该树

首先,定义可重用的 RecursiveField 类

class RecursiveField(serializers.Serializer):
    def to_representation(self, value):
        serializer = self.parent.parent.__class__(value, context=self.context)
        return serializer.data

然后,对于您的序列化程序,使用 RecursiveField 序列化“回复”的值

class CommentSerializer(serializers.Serializer):
    replies = RecursiveField(many=True)

    class Meta:
        model = Comment
        fields = ('replies, ....)

Easy peasy,您只需要 4 行代码即可获得可重用的解决方案。

注意:如果您的数据结构比树更复杂,例如有 向无环图(FANCY!),那么您可以尝试@wjin 的包——请参阅他的解决方案。但是对于基于 MPTTModel 的树,我对此解决方案没有任何问题。

于 2014-12-01T20:33:38.073 回答
59

另一个适用于 Django REST Framework 3.3.2 的选项:

class CategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = Category
        fields = ('id', 'name', 'parentid', 'subcategories')

    def get_fields(self):
        fields = super(CategorySerializer, self).get_fields()
        fields['subcategories'] = CategorySerializer(many=True)
        return fields
于 2016-03-09T16:53:04.167 回答
32

Late to the game here, but here's my solution. Let's say I'm serializing a Blah, with multiple children also of type Blah.

    class RecursiveField(serializers.Serializer):
        def to_native(self, value):
            return self.parent.to_native(value)

Using this field I can serialize my recursively-defined objects that have many child-objects

    class BlahSerializer(serializers.Serializer):
        name = serializers.Field()
        child_blahs = RecursiveField(many=True)

I wrote a recursive field for DRF3.0 and packaged it for pip https://pypi.python.org/pypi/djangorestframework-recursive/

于 2014-06-14T04:12:29.637 回答
19

我能够使用serializers.SerializerMethodField. 我不确定这是否是最好的方法,但对我有用:

class CategorySerializer(serializers.ModelSerializer):

    subcategories = serializers.SerializerMethodField(
        read_only=True, method_name="get_child_categories")

    class Meta:
        model = Category
        fields = [
            'name',
            'category_id',
            'subcategories',
        ]

    def get_child_categories(self, obj):
        """ self referral field """
        serializer = CategorySerializer(
            instance=obj.subcategories_set.all(),
            many=True
        )
        return serializer.data
于 2016-03-14T18:05:31.240 回答
10

另一种选择是在序列化模型的视图中递归。这是一个例子:

class DepartmentSerializer(ModelSerializer):
    class Meta:
        model = models.Department


class DepartmentViewSet(ModelViewSet):
    model = models.Department
    serializer_class = DepartmentSerializer

    def serialize_tree(self, queryset):
        for obj in queryset:
            data = self.get_serializer(obj).data
            data['children'] = self.serialize_tree(obj.children.all())
            yield data

    def list(self, request):
        queryset = self.get_queryset().filter(level=0)
        data = self.serialize_tree(queryset)
        return Response(data)

    def retrieve(self, request, pk=None):
        self.object = self.get_object()
        data = self.serialize_tree([self.object])
        return Response(data)
于 2013-10-11T15:57:02.760 回答
8

我最近遇到了同样的问题,并提出了一个迄今为止似乎有效的解决方案,即使对于任意深度也是如此。解决方案是对 Tom Christie 的一个小修改:

class CategorySerializer(serializers.ModelSerializer):
    parentCategory = serializers.PrimaryKeyRelatedField()

    def convert_object(self, obj):
        #Add any self-referencing fields here (if not already done)
        if not self.fields.has_key('subcategories'):
            self.fields['subcategories'] = CategorySerializer()      
        return super(CategorySerializer,self).convert_object(obj) 

    class Meta:
        model = Category
        #do NOT include self-referencing fields here
        #fields = ('parentCategory', 'name', 'description', 'subcategories')
        fields = ('parentCategory', 'name', 'description')
#This is not needed
#CategorySerializer.base_fields['subcategories'] = CategorySerializer()

我不确定它是否可以在任何情况下可靠地工作,但......

于 2013-04-05T19:14:10.897 回答
6

我想我会加入的乐趣!

通过wjinMark Chackerian,我创建了一个更通用的解决方案,它适用于直接树状模型和具有直通模型的树结构。我不确定这是否属于它自己的答案,但我想我不妨把它放在某个地方。我包括了一个 max_depth 选项,它可以防止无限递归,在最深的层次上,孩子被表示为 URLS(如果你不希望它不是 url,那就是最后的 else 子句)。

from rest_framework.reverse import reverse
from rest_framework import serializers

class RecursiveField(serializers.Serializer):
    """
    Can be used as a field within another serializer,
    to produce nested-recursive relationships. Works with
    through models, and limited and/or arbitrarily deep trees.
    """
    def __init__(self, **kwargs):
        self._recurse_through = kwargs.pop('through_serializer', None)
        self._recurse_max = kwargs.pop('max_depth', None)
        self._recurse_view = kwargs.pop('reverse_name', None)
        self._recurse_attr = kwargs.pop('reverse_attr', None)
        self._recurse_many = kwargs.pop('many', False)

        super(RecursiveField, self).__init__(**kwargs)

    def to_representation(self, value):
        parent = self.parent
        if isinstance(parent, serializers.ListSerializer):
            parent = parent.parent

        lvl = getattr(parent, '_recurse_lvl', 1)
        max_lvl = self._recurse_max or getattr(parent, '_recurse_max', None)

        # Defined within RecursiveField(through_serializer=A)
        serializer_class = self._recurse_through
        is_through = has_through = True

        # Informed by previous serializer (for through m2m)
        if not serializer_class:
            is_through = False
            serializer_class = getattr(parent, '_recurse_next', None)

        # Introspected for cases without through models.
        if not serializer_class:
            has_through = False
            serializer_class = parent.__class__

        if is_through or not max_lvl or lvl <= max_lvl: 
            serializer = serializer_class(
                value, many=self._recurse_many, context=self.context)

            # Propagate hereditary attributes.
            serializer._recurse_lvl = lvl + is_through or not has_through
            serializer._recurse_max = max_lvl

            if is_through:
                # Delay using parent serializer till next lvl.
                serializer._recurse_next = parent.__class__

            return serializer.data
        else:
            view = self._recurse_view or self.context['request'].resolver_match.url_name
            attr = self._recurse_attr or 'id'
            return reverse(view, args=[getattr(value, attr)],
                           request=self.context['request'])
于 2015-04-15T13:26:22.500 回答
6

这是对适用于 drf 3.0.5 和 django 2.7.4 的 caipirginka 解决方案的改编:

class CategorySerializer(serializers.ModelSerializer):

    def to_representation(self, obj):
        #Add any self-referencing fields here (if not already done)
        if 'branches' not in self.fields:
            self.fields['subcategories'] = CategorySerializer(obj, many=True)      
        return super(CategorySerializer, self).to_representation(obj) 

    class Meta:
        model = Category
        fields = ('id', 'description', 'parentCategory')

请注意,第 6 行中的 CategorySerializer 是使用对象和 many=True 属性调用的。

于 2015-03-09T16:28:00.583 回答
4

此解决方案与此处发布的其他解决方案几乎相似,但在根级别的子级重复问题方面略有不同(如果您认为这是一个问题)。举个例子

class RecursiveSerializer(serializers.Serializer):
    def to_representation(self, value):
        serializer = self.parent.parent.__class__(value, context=self.context)
        return serializer.data

class CategoryListSerializer(ModelSerializer):
    sub_category = RecursiveSerializer(many=True, read_only=True)

    class Meta:
        model = Category
        fields = (
            'name',
            'slug',
            'parent', 
            'sub_category'
    )

如果你有这个观点

class CategoryListAPIView(ListAPIView):
    queryset = Category.objects.all()
    serializer_class = CategoryListSerializer

这将产生以下结果,

[
{
    "name": "parent category",
    "slug": "parent-category",
    "parent": null,
    "sub_category": [
        {
            "name": "child category",
            "slug": "child-category",
            "parent": 20,  
            "sub_category": []
        }
    ]
},
{
    "name": "child category",
    "slug": "child-category",
    "parent": 20,
    "sub_category": []
}
]

这里的parent categoryhas achild category和 json 表示正是我们想要的。

child category但是您可以看到在根级别有重复。

正如一些人在上面发布的答案的评论部分中询问我们如何在根级别停止这种子重复,只需过滤您parent=None的查询集,如下所示

class CategoryListAPIView(ListAPIView):
    queryset = Category.objects.filter(parent=None)
    serializer_class = CategoryListSerializer

它将解决问题。

注意:这个答案可能与问题没有直接关系,但问题在某种程度上是相关的。这种使用方法也RecursiveSerializer很昂贵。如果您使用其他倾向于性能的选项,那就更好了。

于 2020-05-01T01:08:27.007 回答
4

使用 Django REST framework 3.3.1,我需要以下代码来将子类别添加到类别中:

模型.py

class Category(models.Model):

    id = models.AutoField(
        primary_key=True
    )

    name = models.CharField(
        max_length=45, 
        blank=False, 
        null=False
    )

    parentid = models.ForeignKey(
        'self',
        related_name='subcategories',
        blank=True,
        null=True
    )

    class Meta:
        db_table = 'Categories'

序列化程序.py

class SubcategorySerializer(serializers.ModelSerializer):

    class Meta:
        model = Category
        fields = ('id', 'name', 'parentid')


class CategorySerializer(serializers.ModelSerializer):
    subcategories = SubcategorySerializer(many=True, read_only=True)

    class Meta:
        model = Category
        fields = ('id', 'name', 'parentid', 'subcategories')
于 2016-01-22T14:17:36.430 回答