Отображение подкатегорий Django в категории и продуктах в каждой категории json as Json Child
Привет в моем проекте dzhango oscar, который реализует Django oscar. Я могу реализовать свой пользовательский API, который я использую для просмотра категорий и отображения их. Проблема с API теперь заключается в том, что подкатегории категории отображаются в моем представлении API как категории, и я бы хотел, чтобы они были в массиве, указывающем, что они являются подкатегориями. Код моей категории выглядит следующим образом
класс serialapi serializer
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 ancestors,
e.g. 'Books > Non-fiction > Essential programming'.
It rarely used in Oscar codebase, but used to be stored as a
CharField and is hence kept for backward compatibility. It 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 slug concatenated with the slugs
of it 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 slug and to not
include it 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 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 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 get_ancestors
if you don't want to include the category itself. It a separate function as it commonly used in templates.
"""
return list(self.get_ancestors()) + [self]
def get_descendants_and_self(self):
"""
Gets descendants and includes itself. Use treebeard get_descendants
if you don't want to include the category itself. It a separate function as it 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 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 так выглядит
{
"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
},]
это означает, что возвращаются только продукты под этой категорией. и если категория имеет число, если ребенок, то число ребенка должно быть возвращено как массив объектов.
Ответы
Ответ 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 (простой поиск из существующего списка категорий)
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": [
]
},
]
Ответ 2
django-oscar
использует django-treebeard
для реализации материализованного пути, в значительной степени противоположной вложенной иерархии, которую вы хотите получить.
У меня нет опыта написания сериализаторов вместе с treebeard, но я уверен, что вам нужно будет переписать свой Serializer на что-то вроде
# 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
Обратите внимание, что я НЕ тестировал это, я просто пытаюсь указать вам направление, которое может приблизиться к решению.