Slug translation issue in wagtail - 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

Related

Wagtail streamfield inside a snippet / setting not working as expected

Edit: Seems like I can't add images :(, added links to imgur instead.
I'm trying to implement settings in order to enter social media accounts.
#register_setting
class SocialMediaSettings(BaseGenericSetting):
discord_url = models.URLField(
help_text="Link used."
)
discord_name = models.CharField(
max_length=100,
help_text="Name to be displayed."
)
gitlab_url = models.URLField(
help_text="Link used."
)
gitlab_name = models.CharField(
max_length=100,
help_text="Name to be displayed."
)
panels = [
FieldPanel("discord_url"),
FieldPanel("discord_name"),
FieldPanel("gitlab_url"),
FieldPanel("gitlab_name")
]
Which works totally fine, but as it feels like a super bad idea to do it this way, I wanted to use a streamfield like this:
#register_setting
class SocialMediaSettings(BaseGenericSetting):
body = StreamField([
("social_account", blocks.SocialBlock()),
], null=True, blank=True, use_json_field=True)
panels = [
FieldPanel("body")
]
class SocialBlock(blocks.StructBlock):
name = blocks.CharBlock(
max_length=100,
help_text="Name used for tooltips."
)
url = blocks.URLBlock(
help_text="URL"
)
Image of wagtail admin (image)
This way it shows up partly. In the admin panel I can only enter the name, not the url. And saving doesn't work eighter (saving will reset what I entered in the admin panel).
Adding the SocialBlock to a normal page works as expected:
SocialBlock working in another Page (image)
Which is why I ran out of ideas. Are streamfields not supported in snippets / settings? (the above exapmles are done with #register_setting but I tried #register_snippet with the same result.)
Any idea how I could get this up and running?
Wagtail 4.0
Django 4.1
Well, wagtail itself just showed me the solution :x
It was a bug in Wagtail 4.0, which is solved in 4.0.1
Release Notes Wagtail 4.0.1

many-to-many relation between wagtail page models

I'm looking for a solution regarding following issue: I'm working on a wagtail app which has the following page models
CharacteristicsPage: characteristics of a technology
ModulePage: combination of technologies
For those page models I need a many-to-many relationship because one technology can be included into many modules and one module consists of many technologies.
On the Characteristics page I want to add the related modules with a PageChooserPanel and on the Module page I want to add the related characteristics.
I already found a possible solution on this issue in the following post, but it throws me an error and I can't figure out what's the problem.
Wagtail many-to-many links between different Page models
So maybe can anyone help me with fixing this issue? Am I on the wrong track? Is there a better way of implementing what I want to do? I apprechiate your help.
This is my code:
class ModulePageCharacteristicsPageRelation(models.Model):
module = ParentalKey('library.Modules', on_delete=models.CASCADE, related_name='characteristics'),
characteristic = ParentalKey('library.Characteristics', on_delete=models.CASCADE, related_name='modules')
class Meta:
unique_together = ('module', 'characteristic')
class Modules(Page):
content_panels = Page.content_panels + [
InlinePanel('characteristics', label='Related Characteristic', panels=[PageChooserPanel('characteristic')]),
]
class Characteristics(Page):
content_panels = Page.content_panels + [
InlinePanel('modules', label='Related Modules', panels=[PageChooserPanel('module')])
]
The error message, when I run makemigrations is:
AttributeError: 'ReverseOneToOneDescriptor' object has no attribute 'rel'

Wagtail many-to-many links between different Page models

Does anyone have or know of a recipe (sample code and/or instructions) on setting up many-to-many relationships between different Page models? If I have PersonPage and SitePage models, how do I connect the pages (a person can work at multiple sites and a site can have multiple people working there)?
Here's what I've found related to, but not directly on, this topic—
Wagtail docs: from a search for "many-to-many" the only hit is in the section on the taggit module (Recipes page).
Wagtail docs: the only reference to the ParentalManyToManyField is a demo of how it can be used to create M2Ms between pages and categories (Tutorial)
This 2015 post on M2M relationships in Wagtail (it's referenced in an SO 'answer' to basically the same question I'm asking here). Although it doesn't discuss page-page relationships the approach presented might be adapted to work. My modified imitation failed with various errors depending on how I tried to set up the InlinePanel call — but the sample code from the post fails in just the same ways, so either it wasn't tested or it's been made obsolete in 2.x.
class PersonPage(Page):
pass
PersonPage.content_panels = [
InlinePanel('ps_links', label='PS Links'),
]
class PersonSitePageLink():
spage = models.ForeignKey('SitePage', on_delete=models.SET_NULL, related_name='sites')
ppage = ParentalKey('PersonPage', related_name='ps_links', on_delete=models.SET_NULL,)
panels = [
FieldPanel('spage')
]
class SitePage(Page):
pass
This technique works fine for relating a Page model to itself, but expanding it to encompass two distinct models creates two parallel but unconnected sets of relationships (you can pick arbitrary Bug pages to link to any Plant page, or vice versa, but the Plants you picked don't show when you edit Bugs). I see why in the code, I think, but I don't see how to make a single M2M connection between the two pages.
class PlantPage(Page):
related_bugs = ParentalManyToManyField('BugPage', blank=True)
content_panels = Page.content_panels + [
FieldPanel('related_bugs'),
]
class BugPage(Page):
related_plants = ParentalManyToManyField('PlantPage', blank=True)
content_panels = Page.content_panels + [
FieldPanel('related_plants'),
]
This one also only talks about intra-page model (rather than inter-page model) M2Ms. (It is pre-ParentalManyToManyField and in fact only available from the Wayback Machine.)
I hope this helps, I took inspiration from this article about moving from ParentalManyToManyField to a central model that 'links' each page from this AccordBox article.
It turns out that InlinePanel does not fully support ParentalManyToManyField, hence the issues you were running into.
I was able to implement a refined approach to your option one above and it should solve your problem.
A reminder that all Page models already extend ClusterableModel so there is no need to add that to any models you create.
Overview
Create a new 'relation' that extends models.Model which will be the relation between these two page models.
Each field within this new model will be the two page types via the model-cluster ParentalKey each with a logical related_name set that is the OTHER side of the relationship.
No need to set panels on this model as we will declare the panels individually via the panels kwarg to InlinePanel - see the InlinePanel docs.
Finally, each individual Page's content_panels has an InlinePanel added that refers to the central relation model indirectly via that model's related_name, adding the other side reference to PageChooserPanel.
Example Code
from modelcluster.fields import ParentalKey
from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, PageChooserPanel
class PersonPageSitePageRelation(models.Model):
person = ParentalKey('app.PersonPage', on_delete=models.CASCADE, related_name='sites')
site = ParentalKey('app.SitePage', on_delete=models.CASCADE, related_name='people')
# Optional: some additional fields (e.g. 'note') for this relation
# Important: NOT setting any `panels` here, will be set individually for each 'direction'
class Meta:
unique_together = ('person', 'site')
class PersonPage(Page):
# ... fields (note: `sites` does NOT need to be declared as a field)
# Now we add an `InlinePanel` that will connect to the parental connection to PersonPageSitePageRelation via the related name `sites`, but the panels available will be the PersonPageSitePageRelation's field `site`
content_panels = Page.content_panels + [
# ... other FieldPanel etc
InlinePanel('sites', label='Related Sites', [PageChooserPanel('site')]),
]
class SitePage(Page):
# ... fields (note: `people` does NOT need to be declared as a field)
# Now we add an `InlinePanel` that will connect to the parental connection to PersonPageSitePageRelation via the related name `people`, but the panels available will be the PersonPageSitePageRelation's field `person`
content_panels = Page.content_panels + [
# ... other FieldPanel etc
InlinePanel('people', label='Related People', panels=[PageChooserPanel('person')]),
]
Further Reading
Read about Django Modelcluster - which is the library that ParentalKey comes from.

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.

Why is the SnippetChooserPanel not opening in 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

Resources