Why is the SnippetChooserPanel not opening in Wagtail? - wagtail

Some time ago I stopped using #register_snippet to decorate Snippets. This takes the Snippet out of the snippets section of admin.
Instead I used the wagtail_hooks.py to show the snippet directly in the left admin panel for user convenience. See below. This works nicely as the user can go directly to the snippet and you can also alter the displayed fields and ordering of fields - nice.
So in the below example I removed the line that says #register_snippet. What's the catch? The SnippetChooserPanel does not work! Later I was building a complex model and the SnippetChooserPanel did not work. I wasted quite a bit of time thinking the problem was in the complexity of my model. I want to save others' time!
wagtail_hooks.py:
from wagtail.contrib.modeladmin.options import ModelAdmin, modeladmin_register
from wagtail.wagtailsnippets.models import register_snippet
from demo.models import Advert
class AdvertAdmin(ModelAdmin):
model = Advert
modeladmin_register(AdvertAdmin)
Here is the snippet example from Wagtail: snippets
#register_snippet #<------- Source of issue (I removed this line!)
#python_2_unicode_compatible # provide equivalent __unicode__ and __str__ methods on Python 2
class Advert(models.Model):
url = models.URLField(null=True, blank=True)
text = models.CharField(max_length=255)
panels = [
FieldPanel('url'),
FieldPanel('text'),
]
def __str__(self):
return self.text
class BookPage(Page):
advert = models.ForeignKey(
'demo.Advert',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='+'
)
content_panels = Page.content_panels + [
SnippetChooserPanel('advert'),
# ...
]

If you make your Snippets editable via modelAdmin you still need to apply the decorator #register_snippet. Otherwise the chooser panel route/view won't be available. This view is requested by the ajax request fired on SnippetChooser modal open. Missing #register snippet will trow a 404.
You can register menu items via construct_main_menu hook. You can use the same hook to remove exiting menu-items. If you do not want the 'Snippets' menu item remove it. In wagtail_hooks.py:
#hooks.register('construct_main_menu')
def hide_snippet(request, menu_items):
menu_items[:] = [item for item in menu_items if item.name != 'snippets']

The solution is always use #register_snippet decorator otherwise the SnippetChooserPanel doesn't work!
#register_snippet
#python_2_unicode_compatible
class Advert(models.Model):
url = models.URLField(null=True, blank=True)
text = models.CharField(max_length=255)
panels = [
FieldPanel('url'),
FieldPanel('text'),
]
def __str__(self):
return self.text

Related

Slug translation issue in wagtail

I've been around the Internet the whole day reading about the following issue, in wagtail if I registered the following model for translation like this:
class RecipientsPage(Page):
intro = RichTextField(null=True, blank=True)
banner_image = models.ForeignKey(
"wagtailimages.Image",
on_delete=models.SET_NULL,
related_name="+",
null=True,
blank=False,
help_text=_("the Image shouldn't exceed ") + "1350 * 210",
)
content_panels = Page.content_panels + [
FieldPanel("intro"),
ImageChooserPanel("image"),
]
this is how I registered the model:
#register(RecipientsCountriesPage)
class RecipientsCountriesPage(TranslationOptions):
fields = ("intro",)
It causes a problem, because like this I'll have two slugs following the two titles (The original English one and the Arabic translated one), if I change the Arabic slug manually to equal the English one it'll work, but it's not efficient to do so for each page manually
I've read about the issue a lot like in here:
https://github.com/infoportugal/wagtail-modeltranslation/issues/195
I've found also the following question with no answer
How do you translate the slug value of a page?
I've also read that I can override some of the wagtail Page methods but without further explanation and I'm a bit lost, what's the best way to overcome this issue?
I did it using Django signals
#receiver(pre_save)
def set_arabic_slug_on_new_instance(sender, instance, **kwargs):
if isinstance(instance, Page):
instance.slug_ar = instance.slug_en

How would I extend the wagtailmenus LinkPage to add a generic picture

I have been using the AbstractLinkPage model from wagtailmenus in my site for a while now. I find it quite useful.
Source: wagtailmenus AbstractLinkPage model
My site is building cards from children pages and adds banner images from children pages automatically to these cards. Some of the children pages are LinkPages.
How can I extend the subclassed LinkPage (from AbstractLinkPage) to add a generic image which can be used as a card image?
I have added the field:
class LinkPage(AbstractLinkPage):
card_image = models.ForeignKey(
get_image_model_string(),
null=True,
blank=True,
related_name='+',
on_delete=models.SET_NULL,
)
content_panels = AbstractLinkPage.content_panels + [
ImageChooserPanel("card_image"),
]
However the image card_image field does not show in the admin interface.
Any help to move forward is appreciated.
Wagtailmenus uses it's own tabbed interface (in wagtailmenus.panels):
linkpage_tab = ObjectList(
linkpage_panels, heading=_("Settings"), classname="settings"
)
linkpage_edit_handler = TabbedInterface([linkpage_tab])
So what you have to do is to create a new ObjectList:
from wagtailmenus.panels import linkpage_panels
from wagtail.admin.edit_handlers import ObjectList, TabbedInterface
my_linkpage_tab = ObjectList(
linkpage_panels + [ImageChooserPanel("card_image")],
heading=_("Settings"), classname="settings"
)
.. and add it to your class:
class LinkPage(AbstractLinkPage):
[..]
edit_handler = TabbedInterface([my_linkpage_tab])

Wagtail: filter available pages in PageChooserPanel

In Wagtail, is it possible to filter pages that show up within the PageChooserPanel page?
For example if I'm setting a link for a french page, I would only want to see pages marked as French. Something like the fake example below:
class MyPage(Page):
french_link = models.ForeignKey(
Page,
null=True,
blank=True,
related_name='+',
on_delete=models.SET_NULL
)
panels = [
# something like this that can
# limit the pages to only ones where lang equals fr
PageChooserPanel('french_link', filter=limit_by_lang),
]
def limit_by_lang(query):
return query.get(lang='fr')
Thanks.

How can I set the number of initial empty (orderable) items in an InlinePanel?

I have an Orderable model called SetListItem with a ParentalKey on a ClusterableModel called FloorWithSets. The parent FloorWithSets model defines using an InlinePanel to control adding/ordering/removing of the SetListItems. The issue I have is that the admin form automatically renders three empty SetListItems for each FloorWithSets, and I cannot find any way to control this setting.
The InlinePanel class takes parameters to, e.g. set the minimum and maximum number of items, but nothing to set the number of initial empty items rendered.
I cannot find any information about this in the Wagtail docs. I've also dug into the source for InlinePanel and EditHandler but cannot find anything I could override.
I do see from the InlinePanel template file that there is a hidden input with id ending -INITIAL_FORMS which is being rendered via self.formset.management_form. The value of this field is consistently lower than a neighbouring hidden input with id ending -TOTAL_FORMS, which makes sense. I just don't understand where the value is coming from or how to control it.
The only information I can find about this INITIAL_FORMS all seems to relate to testing, (e.g. this documentation) and I cannot see how to relate what that says to what I need.
class FloorWithSets(ClusterableModel):
page = ParentalKey(EventPage, on_delete=models.CASCADE, related_name='floor_with_sets')
FLOOR_CHOICES = [
('1', 'X'),
('2', 'Y'),
('3', 'Z'),
]
floor = models.CharField(
max_length=1,
choices=FLOOR_CHOICES,
default='1',
)
panels = [
FieldPanel('floor'),
InlinePanel('set_list', label=_("set")),
]
class SetListItem(Orderable):
floor = ParentalKey(FloorWithSets, on_delete=models.CASCADE, related_name='set_list')
artist = models.CharField(max_length=255, blank=True, verbose_name=_('artist'))
label = models.CharField(max_length=255, blank=True, verbose_name=_('label'))
start_time = models.TimeField(blank=True, null=True, verbose_name=_('start time'))
end_time = models.TimeField(blank=True, null=True, verbose_name=_('end time'))
set_list_item = FieldRowPanel([
FieldPanel('artist', classname="col6"),
FieldPanel('label', classname="col6")
])
set_list_item_details = FieldRowPanel([
FieldPanel('start_time', classname="col6"),
FieldPanel('end_time', classname="col6")
])
panels = [set_list_item, set_list_item_details]
I think I've found a solution. Try creating a custom Form class for your EventPage model with a custom metaclass like so:
class EventPageFormMetaclass(WagtailAdminModelFormMetaclass):
#classmethod
def child_form(cls):
return EventPageForm
class EventPageForm(WagtailAdminPageForm, metaclass=EventPageFormMetaclass):
pass
class EventPage(Page):
# Whatever you have in your model
base_form_class = EventPageForm
I believe the problem stems from the fact that the ClusterFormMetaclass is hard-coded to create instances of ClusterForm for child models. So your EventPage gets a WagtailAdminPageForm, but the FloorWithSets models gets a ClusterForm. If you stop there, it's fine, but when FloorWithSets generates it's inline panels, it does so as a ClusterForm, whose metaclass has extra_form_count set to 3, as opposed to the WagtailAdminPageForm whose metaclass has it set to 0.
So the solution above creates a new Form class, whose metaclass overrides the child_form class method to return a Form class with extra_form_count set to 0.
Whew.

Wagtail Custom Pages?

I'm totally new to Wagtail/ Django.
Here's what I am trying to achieve:
I'd like to have an ability in the backend of my Wagtail CMS install to create 'pages' or 'posts' that follow a strict template.
The template would have custom fields like 'header' and aim content' etc.
I'm sure that this is possible, I'd just be interested to know how I'd go about achieving this?
For example, does anyone know if Wagtail has a plugin or other to enable this?
Thanks for all help/ direction.
You want to create pages and posts that follow a strict template: that's exactly what Django and Wagtail let you do. But there's one catch: Wagtail takes this a step further and lets you move entire sections of a page — these are called Streamfields. It's an amazing feature, to be honest.
Here's an example to get you started (note: this is untested and not linted)
# -*- coding: utf-8 -*-
"""Basic Page model."""
from django.db import models
from wagtail.admin.edit_handlers import FieldPanel, MultiFieldPanel, StreamFieldPanel
from wagtail.core.fields import StreamField
from wagtail.core.models import Page
from your_custom_app.streams import streamfields
class BasicPage(Page):
"""A basic page class."""
template = "templates/pages/basic_page.html"
parent_page_type = ["pages.HomePage", "pages.BasicPage"]
subpage_types = ["pages.BasicPage"]
header = models.CharField(max_length=100)
content = StreamField(
('streamfield_name', streamfields.CustomStreamfield()),
# ... More streams
null=True,
blank=True,
)
# Other additional fields you want on your page.
# Panels are how you lay out your pages in the /admin/
content_panels = [
FieldPanel("title", classname="full title"),
FieldPanel("header"),
# FieldPanel("other_fields"),
StreamFieldPanel("content"),
]
settings_panels = Page.settings_panels + [] # Custom settings panel
promote_panels = Page.promote_panels + [] # Custom promote panel
class Meta:
"""Meta information."""
verbose_name = "Basic Page"
verbose_name_plural = "Basic Pages"
You can also download and setup the Wagtail Bakery Demo, it has a lot of great examples in it.

Resources