Access other object fields in StreamField / RichText field/block in wagtail - wagtail

I would like to do the following:
Create some django model (or wagtail page) that contains data.
Create another Page type that includes a StreamField or RichtextField.
3.
When the author or editor enters text in this field, he/she can have the information of this appear somewhere in the rendered output of the text. Preferably using common template techniques.
So, lets say, we have a model like this:
class Person(models.Model):
name = models.CharField(max_length=30)
Then I have a normal page that uses a StreamField:
class NormalPage(Page):
body = StreamField(block_types=[
('paragraph', blocks.RichTextBlock()),
])
content_panels = Page.content_panels + [
StreamFieldPanel('body')
]
I would like for the editor to choose which Person he/she would like from the database and the be able to do something like this in the RichTextBlock:
{{ person.name }}
Is this possible?

In order to be able to choose a plain Django model, register it as a snippet as shown here, and then use SnippetChooserPanel to choose the person:
from wagtail.snippets.models import register_snippet
#register_snippet
class Person(models.Model):
name = models.CharField(max_length=30)
And then if you want to associate a particular person with a particular page:
from wagtail.snippets.edit_handlers import SnippetChooserPanel
class NormalPage(Page):
body = StreamField(block_types=[
('paragraph', blocks.RichTextBlock()),
])
person = models.ForeignKeyField(Person)
content_panels = Page.content_panels + [
StreamFieldPanel('body'),
SnippetChooserPanel('person'),
]

Related

How to create a tags field for multiple Page classes in Wagtail?

I want to add a field for keywords that almost every model in my site will have. It would be ideal if I didn't have to define a "TaggedPage" class for every Page model. So I created a BasePage abstract model but ParentalKey doesn't appear to work with an abstract model. How can I solve this?
I get this error:
home.TaggedPage.content_object: (fields.E300) Field defines a relation with model 'home.BasePage', which is either not installed, or is abstract.
home.TaggedPage.content_object: (fields.E307) The field home.TaggedPage.content_object was declared with a lazy reference to 'home.basepage', but app 'home' doesn't provide model 'basepage'.
home/models.py contains these models:
class PageTag(TagBase):
""" Tag used for the keywords meta html tag"""
class Meta:
verbose_name = "search/meta keyword"
verbose_name_plural = "search/meta keywords"
class TaggedPage(ItemBase):
tag = models.ForeignKey(
PageTag,
related_name="tagged_pages",
on_delete=models.CASCADE,
)
content_object = ParentalKey(
to='home.BasePage',
on_delete=models.CASCADE,
related_name='tagged_items'
)
class BasePage(MetadataPageMixin, Page):
tags = ClusterTaggableManager(
through='home.TaggedPage',
blank=True,
help_text="Used for the keywords meta html tag",
)
promote_panels = Page.promote_panels + [
FieldPanel('tags'),
]
class Meta:
abstract = True
class HomePage(BasePage):
parent_page_types = []
Point the ParentalKey at the Page model instead:
content_object = ParentalKey(
to='wagtailcore.Page',
on_delete=models.CASCADE,
related_name='tagged_items'
)
At the database level this will have the behaviour you're looking for: a foreign key field referencing the ID of the page object. The one minor side effect is that the tagged_items relation will be defined on all Page objects, not just ones inheriting from BasePage, but that shouldn't cause any problems.

Is there any way to add a custom unique identifier to a wagtail block

I am looking to create a streamfield of items with each item having a custom id. I have created an 'ItemBlock' and added a 'unique_identifier' attribute which is populated using uuid4. The issue is that each item has the same uuid. Is there a way to generate a different uuid for each ItemBlock?
class ItemBlock(blocks.StructBlock):
unique_identifier = blocks.CharBlock(default=uuid.uuid4())
item_name = blocks.CharBlock()
body = blocks.RichTextBlock()
class CategoryBlock(blocks.StructBlock):
title = blocks.CharBlock()
class ListPage(Page):
subtitle = models.CharField(max_length=50)
checklist = StreamField([('category', CategoryBlock()), ('checklist_item', ItemBlock())])
content_panels = Page.content_panels + [
FieldPanel('subtitle'),
StreamFieldPanel('checklist'),
]
Wagtail blocks already have UUIDs:
https://github.com/wagtail/wagtail/blob/master/wagtail/core/blocks/stream_block.py#L452
You can access them in templates with your_block.id.
Does this help you?

Inline creation of snippet in a streamfield block (Wagtail 2.3+)

so lets say I have the following models set up for Wagtail:
#register_snippet
class MySnippet(models.Model):
name = models.CharField(max_length=200, null=True)
panels = [FieldPanel('name'),]
def __str__(self):
return self.name
class Meta:
ordering = ['name',]
class MyPage(Page):
body = StreamField([
('mysnippet', SnippetChooserBlock(required=False, label='MySnippet', target_model='MySnippet')),
], blank=True, help_text='')
content_panels = Page.content_panels + [
StreamFieldPanel('body', heading='Stuff to add'),
]
My client will be creating a lot of MySnippet items as they go. It's going to be super awkward for them to move to another view in their CMS, create a MySnippet, then come back to their main MyPage editor to choose it.
Q1 Is there a simple way to add a SnippetChooseOrInlineCreate() block so clients can add new MySnippets as they create MyPages?
Q2 If there's no existing simple way, how would you recommend approaching this?

Wagtail Inlinepanel demo erorr

I am relatively new to Django Wagtail and I was following the demo from the docs.wagtail.io website which can be found here on how to add a list of Links to a Page using an InlinePanel with Related links
I seem to have reached an error that I donot fully understand it's meaning.
The error says
AttributeError: type object 'BookPageRelatedLinks' has no attribute 'rel'
The code for the demo is as follows
from wagtail.wagtailcore.models import Orderable, Page
from modelcluster.fields import ParentalKey
from wagtail.wagtailadmin.edit_handlers import FieldPanel,InlinePanel
from django.db import models
class BookPage(Page):
# The abstract model for related links, complete with panels
class RelatedLink(models.Model):
title = models.CharField(max_length=255)
link_external = models.URLField("External link", blank=True)
panels = [
FieldPanel('title'),
FieldPanel('link_external'),
]
class Meta:
abstract = True
# The real model which combines the abstract model, an
# Orderable helper class, and what amounts to a ForeignKey link
# to the model we want to add related links to (BookPage)
class BookPageRelatedLinks(Orderable, RelatedLink):
page = ParentalKey('demo.BookPage', related_name='related_links')
content_panels = Page.content_panels + [
InlinePanel('BookPageRelatedLinks', label="Related Links"),
]
My primary objective was to learn this so I can add image links to a sidebar on a BlogPage app I am developing.
Your InlinePanel declaration isn't quite correct - it needs to be:
InlinePanel('related_links', label="Related Links")
Here's what's going on:
By defining a ParentalKey with related_name='related_links', you set up a one-to-many relation called related_links on BookPage. This allows you to retrieve all of the BookPageRelatedLinks objects associated with a given BookPage instance (for example, if your BookPage instance was called page, you could write page.related_links.all()).
The InlinePanel declaration then tells Wagtail to make the related_links property editable within the admin.
The reason you're getting a misleading error message is that you've defined the RelatedLink and BookPageRelatedLinks classes inside the BookPage - which is a little bit unusual, but still valid. This results in BookPageRelatedLinks being defined as a property of BookPage (i.e. BookPage.BookPageRelatedLinks). Then, when Wagtail tries to set up the InlinePanel, it retrieves that property and fails because it's not the expected type of object (it's a class definition, not a relation).
If you write your models file in the more conventional way, with the related models defined below (or above) BookPage:
class BookPage(Page):
content_panels = Page.content_panels + [
InlinePanel('BookPageRelatedLinks', label="Related Links"),
]
# The abstract model for related links, complete with panels
class RelatedLink(models.Model):
title = models.CharField(max_length=255)
link_external = models.URLField("External link", blank=True)
panels = [
FieldPanel('title'),
FieldPanel('link_external'),
]
class Meta:
abstract = True
# The real model which combines the abstract model, an
# Orderable helper class, and what amounts to a ForeignKey link
# to the model we want to add related links to (BookPage)
class BookPageRelatedLinks(Orderable, RelatedLink):
page = ParentalKey('home.BookPage', related_name='related_links')
...then you would get a more informative error: AttributeError: type object 'BookPage' has no attribute 'BookPageRelatedLinks'.

Relationships in Django Admin

I get really confused with many-to-many database relationships, so can some one please clarify how I would achieve this?
I need a table of "Tags" (as in tag words) and a table for "Entries", such at many "Entries" could correspond to many Tag words.
Right now I have my models like this:
# models.py
class Tags(models.Model):
tag = models.CharField(max_length=255)
entry = models.ManyToManyField(Entry)
class Entry(models.Model):
entry = models.CharField(max_length=255)
description = models.TextField()
Now I'm confused, how would I setup my admin.py so I could then add tags when I create a new entry?
What you need is using the through feature of models:
class Tag(models.Model):
tag = models.CharField(max_length=255)
entry = models.ManyToManyField(Entry, through='TaggedEntries')
class Entry(models.Model):
entry = models.CharField(max_length=255)
description = models.TextField()
class TaggedEntries(models.Model):
entry = models.ForeignKey(Entry)
tag = models.ForeignKey(Tag)
and now use that model in your admin:
class TagsInline(admin.TabularInline):
model = TaggedEntries
extra = 1
class EntryAdmin(admin.ModelAdmin):
inlines = (TagsInline, )
admin.site.register(Entry, EntryAdmin)
admin.site.register(Tag)
You will need something along the lines of:
# admin.py
from django.contrib import admin
from models import *
class TagsInline(admin.TabularInline):
model = Tag
extra = 1
class EntryAdmin(admin.ModelAdmin):
inlines = (TagsInline, )
admin.site.register(Entry, EntryAdmin)
admin.site.register(Tag)
(Note, this code was written in a browser!)

Resources