在来到这里寻求建议之前,我总是确保我已经尝试了所有可能的途径。
那就是说,这就是我目前正在努力的方面;创建多级/嵌套类别。顺便说一下,如果令人讨厌的核心开发人员可以实现多级别类别创建的简单方法,那将是很好的,而不必为此编写一些vanilla-django hack。
我已经在这个应用程序上工作了几个星期,一切运行顺利,除了现在,有一个商业决定要实现嵌套类别。
我最初的M.O是创建一个ServiceCategoryIndex页面,一个ServiceCategoryPage然后使ServiceIndex页面成为ServiceCategoryIndex页面的后代或可订购的ServiceCategoryPage,这似乎不对。
经过几次迭代后,我回到了我的默认模型,然后使用视图和类似vanilla-django的url尝试了类别的url,问题是,我无法用通过查询外键来查询模板上的关系,所以我仍然无法将服务页面的内容作为呈现的列表查询集。
以下是我的型号代码,任何建议或解决方法都绝对有用。 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'),
答案 0 :(得分:2)
我一直在处理类似的问题 - 除了我们称之为Topic
而不是Category
,但希望这可以帮助你解决问题。
get_children
或is_root
。base_form_class
override完成。我有一个专用的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']
wagtail_hooks.py
这可确保在Wagtail Admin中使用上一代码中的TopicAdmin。您将知道它的工作方式将显示在左侧管理侧栏modeladmin register docs。
from wagtail.contrib.modeladmin.options import modeladmin_register
from .models import TopicAdmin
modeladmin_register(TopicAdmin)
现在是进行迁移并运行迁移的好时机,请记住在构建模型后node_order_by
不容易更改。如果你想添加儿童的自定义顺序,例如。重新排序容量或其他字段的排序,请在迁移之前执行此操作。
然后进入admin并创建第一个根节点。
这是一个快速而令人讨厌的例子,让您将一个主题链接到一个没有任何花哨的页面。请注意,我们在这里限制了选择,这可以扩展为根据您在主题中设置的字段进行更复杂的限制。
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',
)
答案 1 :(得分:0)
值得注意的是,Collection hierarchy
正在进行工作,但这对图像/文档来说会更多。
它已被标记为2.0版本。