Ibr*_*ole 2 python django content-management-system wagtail
在来这里寻求建议之前,我总是确保我已经尝试了所有可能的途径。
也就是说,这就是我目前正在努力解决的问题;创建多级/嵌套类别。顺便说一句,如果 wagtail 核心开发人员可以实现一种简单的方法来创建多级类别,而无需我们为它编写一些 vanilla-django hack,那就太好了。
我已经在这个应用程序上工作了几个星期,一切都运行得很顺利,除了现在,有一个要实现嵌套类别的业务决策。
我最初的 MO 是创建一个 ServiceCategoryIndex 页面,一个 ServiceCategoryPage 然后使 ServiceIndex 页面成为 ServiceCategoryIndex 页面的后代或可排序为 ServiceCategoryPage,这似乎不正确。
经过几次迭代,我回到了我的默认模型,然后使用视图和 url 尝试了类别的 url,如 vanilla-django,问题是,我无法查询具有直通关系的外键模板,所以我仍然无法将服务页面的内容作为列表查询集呈现出来。
下面是我的模型代码,对此的任何建议或解决方法都绝对有帮助。PS:我几乎要在 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'),
Run Code Online (Sandbox Code Playgroud)
]
我一直在研究类似的问题 - 除了我们打电话给他们Topic而不是Category但希望这能帮助你。
get_children或is_root。base_form_classoverride来完成。我有一个专用的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']
Run Code Online (Sandbox Code Playgroud)
wagtail_hooks.py这确保了在 Wagtail Admin 中使用了前面代码中的 TopicAdmin。你会知道它是有效的,因为它会出现在左侧的管理侧边栏modeladmin register docs 上。
from wagtail.contrib.modeladmin.options import modeladmin_register
from .models import TopicAdmin
modeladmin_register(TopicAdmin)
Run Code Online (Sandbox Code Playgroud)
现在是进行迁移和运行迁移的好时机,请记住,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',
)
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
2001 次 |
| 最近记录: |