【问题标题】:Django Rest Framework categories and childs in one model一个模型中的 Django Rest Framework 类别和子项
【发布时间】:2018-12-03 05:46:29
【问题描述】:

我有一个非常简单(第一眼)的问题。案例 - 一个产品可以在多个地方(商店)销售,每个产品都可以在一个商店中以不同的类别和子类别表示(这就是为什么类别通过 ForeignKey 与 Assortment 链接两次)。 所以这里是 My Assortment 模型,有几个 FK。

class Assortment(models.Model):

    category = models.ForeignKey('category.Category', null=True, blank=True, default=None,related_name='assortment_child')
    parent_category = models.ForeignKey('category.Category', null=True, blank=True, default=None,related_name='assortment_parent')
    product = models.ForeignKey(Product)
    shop = models.ForeignKey(Shop)

视图,基于rest_framework.generics.ListAPIView

class InstitutionTreeCategories(generics.ListAPIView):
    """Resource to get shop's tree of categories."""

    serializer_class = serializers.InstitutionCategoriesSerializer

    def get_queryset(self):
        shop = self.get_shop()
        return Category.objects.filter(assortment_parent__shop=shop).distinct()

最后是序列化器

class CategoryListSerializer(serializers.ModelSerializer):

    class Meta:
        """Meta class."""

        model = Category
        fields = ('id', 'name', 'image')


class CategoriesTreeSerializer(CategoryListSerializer):

    # childs = CategoryListSerializer(many=True, source='assortment_child__parent_category')
    childs = serializers.SerializerMethodField()

    class Meta(CategoryListSerializer.Meta):
        """Meta class."""

        fields = ('id', 'name', 'image', 'childs')

    def get_childs(self, obj):
        qs = Category.objects.filter(assortment_child__parent_category=obj.id).distinct()
        return CategoryListSerializer(qs, many=True, context=self.context).data

而且我需要使用我的 API 为一家商店显示类别树。 但问题是 - 如果我使用 serializer.SerializerMethodField - 它可以工作,但是查询太多(对于每个父类别)。我试图通过我的'CategoryListSerializer' 使用'source' 选项来避免它,因为我做不到。每次,我都会得到 - 'Category' object has no attribute assortment_child__parent_category。在我尝试过的外壳模型中

In [8]: cat.assortment_parent.values('category').distinct()
Out[8]: (0.003) SELECT DISTINCT "marketplace_assortment"."category_id" FROM "marketplace_assortment" WHERE "marketplace_assortment"."parent_category_id" = 4 LIMIT 21; args=(4,)
<AssortmentQuerySet [{'category': 3}]>

所以 - 类别对象有这个属性,当然有,我用了一个 get_childs 方法。所以问题是 - 我如何将它与 serializer.ModelSerializer 及其源选项一起使用? (当然使用 select_related 方法和 queryset,避免过多的查询)。

【问题讨论】:

    标签: django django-rest-framework parent-child django-queryset


    【解决方案1】:

    您需要将 prefetch_related 与序列化方法字段一起使用

    序列化器:

    class CategoriesTreeSerializer(CategoryListSerializer):
    
        children = serializers.SerializerMethodField()
    
        class Meta(CategoryListSerializer.Meta):
    
            fields = (
                'id', 
                'name', 
                'image', 
                'children'
                )
    
        def get_children(self, obj):
            children = set()
            for assortment in obj.assortment_parent.all():
                children.add(assortment.category)
            serializer = CategoryListSerializer(list(children), many=True)
            return serializer.data
    

    你的 get queryset 方法:

    def get_queryset(self):
        shop = self.get_shop()
        return (Category.objects.filter(assortment_parent__shop=shop)
               .prefetch_related(Prefetch('assortment_parent', queryset=Assortment.objects.all().select_related('category')
               .distinct())
    

    【讨论】:

      【解决方案2】:

      我遇到了类似的问题,我发现的最佳解决方案是进行一些手动处理以获得所需的树表示。 所以首先我们为 shop 获取所有 Assortment,然后手动构建树。

      我们来看例子。

      def get_categories_tree(assortments, context):
          assortments = assortments.select_related('category', 'parent_category')
          parent_categories_dict = OrderedDict()
      
          for assortment in assortments:
              parent = assortment.parent_category
              # Each parent category will appear in parent_categories_dict only once
              # and it will accumulate list of child categories
              if parent not in parent_categories_dict:
                  parent_data = CategoryListSerializer(instance=parent, context=context).data
                  parent_categories_dict[parent] = parent_data
                  parent_categories_dict[parent]['childs'] = []
      
              child = assortment.category
              child_data = CategoryListSerializer(instance=child, context=context).data
              parent_categories_dict[parent]['childs'].append(child_data)
      
          # convert to list as we don't need the keys already - they were used only for matching
          parent_categories_list = list(parent_categories_dict.values())
          return parent_categories_list
      
      
      class InstitutionTreeCategories(generics.ListAPIView):
          def list(self, request, *args, **kwargs):
              shop = self.get_shop()
              assortments = Assortment.objects.filter(shop=shop)
              context = self.get_serializer_context()
              categories_tree = get_categories_tree(assortments, context)
              return Response(categories_tree)
      

      全部在单个数据库查询中。

      这里的问题是categoryparent_category 之间没有明确的关系。如果您在Category 中使用Assortment 定义ManyToManyField 作为through 中间模型,您将获得Django 可以理解的访问权限,因此您只需在Category 上使用属性childs。但是,这仍然会返回所有子项(如果您的 source 示例有效,也会发生同样的情况)类别,忽略 shop,因此必须做一些聪明的 Prefetch 才能获得正确的结果。但我相信手动“加入”更简单。

      【讨论】:

        【解决方案3】:

        按来源选项,您应该使用. in 而不是__

        childs = CategoryListSerializer(many=True, source='assortment_child.parent_category')
        

        但您仍然会有很多疑问,要修复它,您应该使用prefetch-related

        def get_queryset(self):
            shop = self.get_shop()
            qs = Category.objects.filter(assortment_parent__shop=shop).all()
            return qs.prefetch_related('assortment_child').distinct()
        

        更多详情请阅读how-can-i-optimize-queries-django-rest-framework

        【讨论】:

        • 不幸的是,这也不起作用,异常 - `'RelatedManager' 对象没有属性'parent_category'。`
        • 没有什么可显示的,所有其他模型都很简单,产品就像一个id、名称、描述和一些图像,对于商店来说也是如此。类别 - id、名称、图像。唯一重要的 - category 和 parent_category FKs 以及 Assortment 模型的相关名称。
        • @koles.web 我仍在研究您的问题的好答案。如果我找不到它,我会做赏金。
        猜你喜欢
        • 1970-01-01
        • 2013-11-11
        • 2014-05-13
        • 2020-08-15
        • 1970-01-01
        • 2014-01-09
        • 2020-02-16
        • 2014-08-20
        • 2019-12-10
        相关资源
        最近更新 更多