gpt4 book ai didi

python - 鹡鸰中的多级分类

转载 作者:太空宇宙 更新时间:2023-11-04 02:39:24 24 4
gpt4 key购买 nike

在来这里寻求建议之前,我总是确保我已经尝试了所有可能的途径。

也就是说,这就是我目前正在努力解决的问题;创建多级/嵌套类别。顺便说一句,如果 wagtail 核心开发人员能够实现一种创建多级类别的简单方法,而无需我们为其编写一些 vanilla-django hack,那就太好了。

我已经在这个应用程序上工作了几个星期,一切运行顺利,除了现在,有一个要实现嵌套类别的业务决策。

我最初的 M.O 是创建一个 ServiceCategoryIndex 页面,一个 ServiceCategoryPage,然后使 ServiceIndex 页面成为 ServiceCategoryIndex 页面的后代或可订购的 ServiceCategoryPage,这似乎不对。

经过几次迭代,我回到我的默认模型,然后尝试使用 View 和 url 的类别 url,如 vanilla-django,问题是,我无法通过直通查询外键模板上的关系,所以我仍然无法将服务页面的内容作为列表查询集呈现出来。

下面是我的模型代码,对此的任何建议或解决方法都绝对有帮助。 P.S:我几乎要用 vanilla-django 重写整个项目,因为我在接下来的几天内找不到解决方案。

def get_service_context(context):
context['all_categories'] = ServiceCategory.objects.all()
context['root_categories'] = ServiceCategory.objects.filter(
parent=None,
).prefetch_related(
'children',
).annotate(
service_count=Count('servicepage'),
)
return context

class ServiceIndexPage(Page):
header_image = models.ForeignKey(
'wagtailimages.Image',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='+'
)
heading = models.CharField(max_length=500, null=True, blank=True)
sub_heading = models.CharField(max_length=500, null=True, blank=True)
body = RichTextField(null=True, blank=True)

def get_context(self, request, category=None, *args, **kwargs):
context = super(ServiceIndexPage, self).get_context(request, *args, **kwargs)

services = ServicePage.objects.child_of(self).live().order_by('-first_published_at').prefetch_related('categories', 'categories__category')
if category is None:
if request.GET.get('category'):
category = get_object_or_404(ServiceCategory, slug=request.GET.get('category'))
if category:
if not request.GET.get('category'):
category = get_object_or_404(ServiceCategory, slug=category)
services = services.filter(categories__category__name=category)

# Pagination
page = request.GET.get('page')
page_size = 10
if hasattr(settings, 'SERVICE_PAGINATION_PER_PAGE'):
page_size = settings.SERVICE_PAGINATION_PER_PAGE

if page_size is not None:
paginator = Paginator(services, page_size) # Show 10 services per page
try:
services = paginator.page(page)
except PageNotAnInteger:
services = paginator.page(1)
except EmptyPage:
services = paginator.page(paginator.num_pages)


context['services'] = services
context['category'] = category
context = get_service_context(context)

return context


@register_snippet
class ServiceCategory(models.Model):
name = models.CharField(max_length=250, unique=True, verbose_name=_('Category Name'))
slug = models.SlugField(unique=True, max_length=250)
parent = models.ForeignKey('self', blank=True, null=True, related_name="children")
date = models.DateField(auto_now_add=True, auto_now=False, null=True, blank=True)
description = RichTextField(blank=True)

class Meta:
ordering = ['-date']
verbose_name = _("Service Category")
verbose_name_plural = _("Service Categories")

panels = [
FieldPanel('name'),
FieldPanel('parent'),
FieldPanel('description'),
]

def __str__(self):
return self.name

def clean(self):
if self.parent:
parent = self.parent
if self.parent == self:
raise ValidationError('Parent category cannot be self.')
if parent.parent and parent.parent == self:
raise ValidationError('Cannot have circular Parents.')

def save(self, *args, **kwargs):
if not self.slug:
slug = slugify(self.name)
count = ServiceCategory.objects.filter(slug=slug).count()
if count > 0:
slug = '{}-{}'.format(slug, count)
self.slug = slug
return super(ServiceCategory, self).save(*args, **kwargs)

class ServiceCategoryServicePage(models.Model):
category = models.ForeignKey(ServiceCategory, related_name="+", verbose_name=_('Category'))
page = ParentalKey('ServicePage', related_name='categories')
panels = [
FieldPanel('category'),
]



class ServicePage(Page):
header_image = models.ForeignKey(
'wagtailimages.Image',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='+',
verbose_name=_('Header image')
)
service_title = models.CharField(max_length=300, null=True, blank=True)
body = StreamField([
('h1', CharBlock(icon="title", classanme="title")),
('h2', CharBlock(icon="title", classanme="title")),
('h3', CharBlock(icon="title", classanme="title")),
('h4', CharBlock(icon="title", classanme="title")),
('h5', CharBlock(icon="title", classanme="title")),
('h6', CharBlock(icon="title", classanme="title")),
('paragraph', RichTextBlock(icon="pilcrow")),
('aligned_image', ImageBlock(label="Aligned image", icon="image")),
('pullquote', PullQuoteBlock()),
('raw_html', RawHTMLBlock(label='Raw HTML', icon="code")),
('embed', EmbedBlock(icon="code")),
])
date = models.DateField("Post date")
service_categories = models.ManyToManyField(ServiceCategory, through=ServiceCategoryServicePage, blank=True)

feed_image = models.ForeignKey(
'wagtailimages.Image',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='+',
verbose_name=_('Feed image')
)

search_fields = Page.search_fields + [
index.SearchField('body'),
index.SearchField('service_title'),
index.SearchField('title'),]


def get_absolute_url(self):
return self.url


def get_service_index(self):
# Find closest ancestor which is a service index
return self.get_ancestors().type(ServiceIndexPage).last()


def get_context(self, request, *args, **kwargs):
context = super(ServicePage, self).get_context(request, *args, **kwargs)
context['services'] = self.get_service_index().serviceindexpage
context = get_service_context(context)
return context

class Meta:
verbose_name = _('Service page')
verbose_name_plural = _('Services pages')

parent_page_types = ['services.ServiceIndexPage']


ServicePage.content_panels = [
FieldPanel('title', classname="full title"),
FieldPanel('service_title'),
ImageChooserPanel('header_image'),
FieldPanel('date'),
InlinePanel('categories', label=_("Categories")),
StreamFieldPanel('body'),
ImageChooserPanel('feed_image'),

]

最佳答案

我一直在研究类似的问题 - 除了我们将它们称为 Topic 而不是 Category 但希望这对您有所帮助。

解决方案总结

  • 使用Django-Treebeard library为了管理您的树,它们最多可以嵌套 63 层,并让您可以完全访问 API 以获取诸如 get_childrenis_root 之类的东西。
  • 您需要覆盖一些创建和“移动”节点的行为,这最好由 base_form_class override 完成。 .
  • 我用过ModelAdmin为此,如果是代码片段,它应该也能正常工作,但是如果您想添加更复杂的编辑,ModelAdmin 可以让您对 future 有更多的控制权。
  • 最后,您可以使用 ForeignKey 或其他一些相关链接将这些主题/类别链接到您的页面。
  • 注意事项:在此示例中,除了字母顺序之外,没有对子节点进行重新排序,这可以添加,但它有点复杂,因为您需要一个 UI - 因此使用 ModelAdmin。另外,你永远不应该让用户删除根,它会删除所有节点。
  • Django Treebeard Caveats - 值得一读

1 - 构建模型

我有一个专用的 Topics 应用程序,但您可以将它放在任何 models.py 中。请参阅解释代码的注释。

from __future__ import unicode_literals

from django import forms
from django.core.exceptions import PermissionDenied
from django.db import models

from treebeard.mp_tree import MP_Node

from wagtail.contrib.modeladmin.options import ModelAdmin
from wagtail.wagtailadmin.edit_handlers import FieldPanel
from wagtail.wagtailadmin.forms import WagtailAdminModelForm


# This is your main 'node' model, it inherits mp_node
# mp_node is short for materialized path, it means the tree has a clear path
class Topic(MP_Node):
"""
Topics can be nested and ordered.
Root (id 1) cannot be deleted, can be edited.
User should not edit path, depth, numchild directly.
"""

name = models.CharField(max_length=30)
is_selectable = models.BooleanField(default=True) # means selectable by pages
# any other fields for the Topic/Category can go here
# eg. slug, date, description

# may need to rework node_order_by to be orderable
# careful - cannot change after initial data is set up
node_order_by = ['name']

# just like any model in wagtail, you will need to set up panels for editing fields
panels = [
FieldPanel('parent'), # parent is not a field on the model, it is built in the TopicForm form class
FieldPanel('name', classname='full'),
FieldPanel('is_selectable'),
]

# this is just a convenience function to make the names appear with lines
# eg root | - first child
def name_with_depth(self):
depth = '— ' * (self.get_depth() - 1)
return depth + self.name
name_with_depth.short_description = 'Name'

# another convenience function/property - just for use in modeladmin index
@property
def parent_name(self):
if not self.is_root():
return self.get_parent().name
return None

# a bit of a hacky way to stop users from deleting root
def delete(self):
if self.is_root():
raise PermissionDenied('Cannot delete root topic.')
else:
super(Topic, self).delete()

# pick your python string representation
def __unicode__(self):
return self.name_with_depth()

def __str__(self):
return self.name_with_depth()

class Meta:
verbose_name = 'Topic'
verbose_name_plural = 'Topics'


# this class is the form class override for Topic
# it handles the logic to ensure that pages can be moved
# root pages need to be treated specially
# including the first created item always being the root
class TopicForm(WagtailAdminModelForm):

# build a parent field that will show the available topics
parent = forms.ModelChoiceField(
required=True,
empty_label=None,
queryset=Topic.objects.none(),
)

def __init__(self, *args, **kwargs):
super(TopicForm, self).__init__(*args, **kwargs)
instance = kwargs['instance']
all = Topic.objects.all()
is_root = False

if len(all) == 0 or instance.is_root():
# no nodes, first created must be root or is editing root
is_root = True

if is_root:
# disable the parent field, rename name label
self.fields['parent'].empty_label = 'N/A - Root Node'
self.fields['parent'].disabled = True
self.fields['parent'].required = False
self.fields['parent'].help_text = 'Root Node has no Parent'
self.fields['name'].label += ' (Root)'
else:
# sets the queryset on the parent field
# ensure that they cannot select the existing topic as parent
self.fields['parent'].queryset = Topic.objects.exclude(
pk=instance.pk)
self.fields['parent'].initial = instance.get_parent()

def save(self, commit=True):
parent = self.cleaned_data['parent']
instance = super(TopicForm, self).save(commit=False)
all = Topic.objects.all()

is_new = instance.id is None
is_root = False
if is_new and len(all) == 0:
is_root = True
elif not is_new and instance.is_root():
is_root = True

# saving / creating
if is_root and is_new and commit:
# adding the root
instance = Topic.add_root(instance=instance)
elif is_new and commit:
# adding a new child under the seleced parent
instance = parent.add_child(instance=instance)
elif not is_new and instance.get_parent() != parent and commit:
# moving the instance to under a new parent, editing existing node
# must use 'sorted-child' - will base sorting on node_order_by
instance.move(parent, pos='sorted-child')
elif commit:
# no moving required, just save
instance.save()

return instance


# tell Wagtail to use our form class override
Topic.base_form_class = TopicForm


class TopicAdmin(ModelAdmin):
model = Topic
menu_icon = 'radio-empty'
menu_order = 200
add_to_settings_menu = False
list_display = ['name_with_depth', 'parent_name']
search_fields = ['name']

2 - 在wagtail_hooks.py

中注册modeladmin函数

这确保了前面代码中的 TopicAdmin 在 Wagtail Admin 中使用。你会知道它是有效的,因为它会出现在左侧管理侧边栏上 modeladmin register docs .

from wagtail.contrib.modeladmin.options import modeladmin_register
from .models import TopicAdmin


modeladmin_register(TopicAdmin)

3 - 迁移并创建第一个主题

现在是进行迁移和运行迁移的好时机,请记住 node_order_by 在构建模型后不容易更改。如果您想添加 child 的自定义排序,例如。重新排序容量或按其他字段排序,请在迁移之前执行此操作。

然后进入管理并创建第一个根节点。

4 - 链接到您的页面

这是一个快速而讨厌的示例,让您可以将一个主题链接到一个页面,而不需要任何花哨的东西。请注意,我们在这里限制了选择,这可以扩展为根据您在主题中设置的字段进行更复杂的限制。

topic = models.ForeignKey(
'topics.Topic',
on_delete=models.SET_NULL,
blank=True,
null=True,
limit_choices_to={'is_selectable': True},
related_name='blog_page_topic',
)

5 - 改进空间

  • 主题字符串表示总是包含破折号以显示其“深度”,这在其他地方看起来有点难看。最好使用扩展字段类型并仅在需要时构建此表示。
  • 如前所述,无法手动重新排序子节点,您可以在模型管理中创建自定义按钮,这样就可以添加向上/向下移动的按钮并以这种方式工作。
  • 示例代码,可能有些粗糙,但应该足以让您入门。我已经在演示应用程序的 Wagtail 1.13 上对此进行了测试,它可以正常工作。

关于python - 鹡鸰中的多级分类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46995540/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com