Django Rest Framework категорий и детей в одной модели - PullRequest
0 голосов
/ 24 июня 2018

У меня очень простая (на первый взгляд) проблема.Случай - товар может быть продан в нескольких местах (магазинах), и каждый товар может быть представлен в одном магазине с разными категориями и подкатегориями (поэтому категории, связанные через ForeignKey с Ассортиментом дважды).Итак, вот модель «Мой ассортимент» с несколькими 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)

View, основанная на 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 - он работает, но слишком много запросов (для каждой родительской категории).Я пытался избежать этого, используя опцию 'source' с моим 'CategoryListSerializer', но не могу этого сделать.Каждый раз я получаю - '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, чтобы избежать лишних запросов).

Ответы [ 3 ]

0 голосов
/ 03 июля 2018

У меня была похожая проблема, и лучшее решение, которое я нашел, - это выполнить ручную обработку, чтобы получить желаемое представление дерева. Итак, сначала мы выбираем все 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)

Все в одном запросе к БД.

Проблема в том, что между category и parent_category нет явной связи. Если вы определите ManyToManyField в Category, используя Assortment в качестве промежуточной модели through, вы получите доступ, который может понять Django, так что вы просто используете атрибут childs в Category, например. Однако это все равно вернет все дочерние категории (то же самое произойдет, если ваш пример source работает), игнорируя shop, поэтому для достижения правильных результатов нужно будет сделать несколько умных Prefetch. Но я считаю, что ручное "соединение" проще.

0 голосов
/ 04 июля 2018

вам нужно использовать 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:

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())
0 голосов
/ 24 июня 2018

по варианту источника, вы должны использовать . вместо __:

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()

более подробно вы можете прочитать в как можно оптимизировать запросы-django-rest-framework

...