【问题标题】:Displaying Django subcategories in category and products in each category json as Json Child将每个类别json中的类别和产品中的Django子类别显示为Json Child
【发布时间】:2018-11-13 00:36:17
【问题描述】:

您好,在我实现 Django oscar 的 Django oscar 项目中。我能够实现用于查看类别和显示它们的自定义 API。 API 现在的问题是一个类别的子类别在我的 API 视图中显示为类别,我希望它们位于一个数组中,表明它们是子类别。我的分类代码如下

customapi 序列化程序类

class CategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = Category
        fields = ('id', 'numchild', 'name', 'description', 'image', 'slug')

观看次数

class CategoryList(generics.ListAPIView):
    queryset = Category.objects.all()
    serializer_class = CategorySerializer


class CategoryDetail(generics.RetrieveAPIView):
    queryset = Category.objects.all()
    serializer_class = CategorySerializer

customapi/urls.py

url(r'^caty/$', CategoryList.as_view(), name='category-list'),
url(r'^caty/(?P<category_slug>[\w-]+(/[\w-]+)*)_(?P<pk>\d+)/$',
        CategoryDetail.as_view(), name='category'),

Json

[
    {
        "id": 2,
        "path": "0001",
        "depth": 1,
        "numchild": 4,
        "name": "Clothes",
        "description": "<p>Beautiful Clothes</p>",
        "image": null,
        "slug": "clothes"
    },
    {
        "id": 8,
        "path": "00010001",
        "depth": 2,
        "numchild": 0,
        "name": "c",
        "description": "",
        "image": null,
        "slug": "c"
    },
    {
        "id": 7,
        "path": "00010002",
        "depth": 2,
        "numchild": 0,
        "name": "b",
        "description": "",
        "image": null,
        "slug": "b"
    },
    {
        "id": 6,
        "path": "00010003",
        "depth": 2,
        "numchild": 0,
        "name": "a",
        "description": "",
        "image": null,
        "slug": "a"
    },
    {
        "id": 5,
        "path": "00010004",
        "depth": 2,
        "numchild": 0,
        "name": "MsWears",
        "description": "",
        "image": null,
        "slug": "mswears"
    },]

请注意,numchild 是第一个的 4,这表示它是父类别,其余的是子类别。

Django-oscar 模型中的子类别是这样渲染的

class AbstractCategory(MP_Node):
    """
    A product category. Merely used for navigational purposes; has no effects on business logic.

    Uses Django-treebeard.
    """
    name = models.CharField(_('Name'), max_length=255, db_index=True)
    description = models.TextField(_('Description'), blank=True)
    image = models.ImageField(_('Image'), upload_to='categories', blank=True,
                              null=True, max_length=255)
    slug = SlugField(_('Slug'), max_length=255, db_index=True)

    _slug_separator = '/'
    _full_name_separator = ' > '

    def __str__(self):
        return self.full_name

    @property
    def full_name(self):
        """
        Returns a string representation of the category and it's ancestors,
        e.g. 'Books > Non-fiction > Essential programming'.

        It's rarely used in Oscar's codebase, but used to be stored as a
        CharField and is hence kept for backward compatibility. It's also sufficiently useful to keep around.
        """
        names = [category.name for category in self.get_ancestors_and_self()]
        return self._full_name_separator.join(names)

    @property
    def full_slug(self):
        """
        Returns a string of this category's slug concatenated with the slugs
        of it's ancestors, e.g. 'books/non-fiction/essential-programming'.

        Oscar used to store this as in the 'slug' model field, but this field
        has been re-purposed to only store this category's slug and to not
        include it's ancestors' slugs.
        """
        slugs = [category.slug for category in self.get_ancestors_and_self()]
        return self._slug_separator.join(slugs)

    def generate_slug(self):
        """
        Generates a slug for a category. This makes no attempt at generating a unique slug.
        """
        return slugify(self.name)

    def ensure_slug_uniqueness(self):
        """
        Ensures that the category's slug is unique amongst its siblings.
        This is inefficient and probably not thread-safe.
        """
        unique_slug = self.slug
        siblings = self.get_siblings().exclude(pk=self.pk)
        next_num = 2
        while siblings.filter(slug=unique_slug).exists():
            unique_slug = '{slug}_{end}'.format(slug=self.slug, end=next_num)
            next_num += 1

        if unique_slug != self.slug:
            self.slug = unique_slug
            self.save()

    def save(self, *args, **kwargs):
        """
        Oscar traditionally auto-generated slugs from names. As that is often convenient, we still do so if a slug is not supplied through other means. If you want to control slug creation, just create instances with a slug already set, or expose a field on the appropriate forms.
        """
        if self.slug:
            # Slug was supplied. Hands off!
            super(AbstractCategory, self).save(*args, **kwargs)
        else:
            self.slug = self.generate_slug()
            super(AbstractCategory, self).save(*args, **kwargs)
            # We auto-generated a slug, so we need to make sure that it's
            # unique. As we need to be able to inspect the category's siblings
            # for that, we need to wait until the instance is saved. We
            # update the slug and save again if necessary.
            self.ensure_slug_uniqueness()

    def get_ancestors_and_self(self):
        """
        Gets ancestors and includes itself. Use treebeard's get_ancestors
        if you don't want to include the category itself. It's a separate function as it's commonly used in templates.
        """
        return list(self.get_ancestors()) + [self]

    def get_descendants_and_self(self):
        """
        Gets descendants and includes itself. Use treebeard's get_descendants
        if you don't want to include the category itself. It's a separate function as it's commonly used in templates.
        """
        return list(self.get_descendants()) + [self]

    def get_absolute_url(self):
        """
        Our URL scheme means we have to look up the category's ancestors. As that is a bit more expensive, we cache the generated URL. That is
        safe even for a stale cache, as the default implementation of
        ProductCategoryView does the lookup via primary key anyway. But if you change that logic, you'll have to reconsider the caching approach.
        """
        current_locale = get_language()
        cache_key = 'CATEGORY_URL_%s_%s' % (current_locale, self.pk)
        url = cache.get(cache_key)
        if not url:
            url = reverse(
                'catalogue:category',
                kwargs={'category_slug': self.full_slug, 'pk': self.pk})
            cache.set(cache_key, url)
        return url

    class Meta:
        abstract = True
        app_label = 'catalogue'
        ordering = ['path']
        verbose_name = _('Category')
        verbose_name_plural = _('Categories')

    def has_children(self):
        return self.get_num_children() > 0

    def get_num_children(self):
        return self.get_children().count() 

当一个类别被选中时,对应的 JSON so 是这样的

 {
        "url": "http://127.0.0.1:8000/nativapi/products/16/",
        "id": 16,
        "title": "Deall",
        "images": [],
        "price": {
            "currency": "NGN",
            "excl_tax": "1000.00",
            "incl_tax": "1000.00",
            "tax": "0.00"
        },
        "availability": "http://127.0.0.1:8000/nativapi/products/16/availability/"
    },
    {
        "url": "http://127.0.0.1:8000/nativapi/products/13/",
        "id": 13,
        "title": "ada",
        "images": [
            {
                "id": 8,
                "original": "http://127.0.0.1:8000/media/images/products/2018/05/f3.jpg",
                "caption": "",
                "display_order": 0,
                "date_created": "2018-05-26T17:24:34.762848Z",
                "product": 13
            },]

这意味着只退回该类别下的产品。如果一个类别有一个数字,如果是孩子,孩子的数字应该作为一个对象数组返回。

【问题讨论】:

  • 您能否举例说明所需的类别 API 输出?因为根据要求,这是一个非常动态的问题,解决方案会根据需求的级别而有所不同。
  • 嗨,我已更新问题以在选择类别超链接时显示所需的输出
  • 您(更新后的)所需输出是 products json,而不是 nested categories(这是最初的询问)。请澄清你到底想要什么。
  • 我在我的更新中声明了这一点。我添加的产品模型是退货

标签: django django-rest-framework django-oscar


【解决方案1】:

我建议将特定类别的数据分开(在详细信息页面中)并且只使用产品 API。

要获取某个类别下的产品,您可以执行以下操作 -

views.py

from django.shortcuts import get_object_or_404
from oscar.core.loading import get_model
from rest_framework import generics
from oscarapi.serializers import ProductsSerializer


Category = get_model('catalogue', 'Category')
Product = get_model('catalogue', 'Product')


class CategoryProductsView(generics.ListAPIView):
    serializer_class = ProductsSerializer

    def get_queryset(self):
        cat_id = self.kwargs.get('pk', None)
        if cat_id is not None:
            category = get_object_or_404(Category, id=cat_id)
            return Product.objects.filter(
                categories__path__startswith=category.path).all()
        else:
            return Product.objects.none()

urls.py

from views import CategoryProductsView

urlpatterns = [
    ...
    url(r'^caty/(?P<pk>[0-9]+)/products/$', CategoryProducts.as_view(), name='category-products'),
    ...
]

由于我们使用的是categories__path__startswith,我们将获得该类别下的所有产品,包括给定类别的子类别下的产品,依此类推。

更新

至于您想要列出的子类别,您只需添加一个SerializerMethodField() 即可为您执行此操作。我建议获取子类别的 id 列表,以便进一步获取该子类别的详细信息,因为它是 id(从现有类别列表中简单查找)

serializers.py

from oscarapi.utils import OscarModelSerializer
from rest_framework import serializers


class CategorySerializer(OscarModelSerializer):
    subcategories = serializers.SerializerMethodField()

    class Meta:
        model = Category
        fields = ('id', 'numchild', 'name', 'description', 'image', 'slug',
                  'path', 'depth', 'subcategories')

    def get_subcategories(self, obj):
        return Category.objects.filter(path__startswith=obj.path,
                                       depth=obj.depth+1
                              ).values_list('id', flat=True)

样本输出

"results": [
    {
        "id": 1,
        "numchild": 1,
        "name": "Cat1",
        "description": "",
        "image": "http://localhost:8001/media/categories/images/categories/cat1.jpg",
        "slug": "cat1",
        "path": "0001",
        "depth": 1,
        "subcategories": [
            2
        ]
    },
    {
        "id": 2,
        "numchild": 0,
        "name": "SubCat1",
        "description": "",
        "image": null,
        "slug": "subcat1",
        "path": "00010001",
        "depth": 2,
        "subcategories": [
        ]
    },
]

【讨论】:

  • 这工作正常并返回每个类别和子类别下的产品,但是当单击类别链接时如何显示子类别。我希望能够显示它下面的子类别。例如Nike, Adidas, Puma are subcategory of Shoes。如果我点击 url "availability": "http://127.0.0.1:8000/api/caty/1/" 我应该能够看到相应的子类别
  • @King 我已经更新了答案以显示相应的子类别。
【解决方案2】:

django-oscar 使用django-treebeard 实现materialized path,与您要检索的嵌套层次结构几乎相反。

我没有与 treebeard 一起编写序列化程序的经验,但我很确定您需要将序列化程序重写为类似的东西

# Get all categories from /caty
class CategorySerializer(serializers.ModelSerializer):
    children = serializers.SerializerMethodField('get_children')

    def get_children(self, obj):
        if obj.numchild == 0:
            return None
        # Use treebeards built-in tree generation
        [CategorySerializer(child) for child in Category.get_tree(obj)]

    class Meta:
        model = Category

请注意,我没有测试任何这些,我只是想为您指出一个可能使您更接近解决方案的方向。

【讨论】:

  • 啊,我忘记了 get_tree 从父对象开始返回整个嵌套列表 - 你可能会使用 .depth 等等,但我只是看到 shad0w_wa1k3r 已经更新了他的答案,并且从第一个开始看我会说这就是你需要的
  • 我也想过使用内置插件,但不幸的是,您仍然需要根据要求对其进行调整,并且 JSON 序列化树结果将是另一个障碍。
  • 我明白了,我知道它会有一些问题,我只是想给出一个一般性的提示,比如去哪里......还考虑过对 Category 模型进行更改,以便它能够有一个父级,在模型的更新/保存时设置它,并仅在序列化程序中使用该递归,而在其他逻辑中没有,但这是一项艰巨的任务,并且在更新依赖项时容易出错..
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-30
相关资源
最近更新 更多