Popular New Releases in Wagtail
coderedcms
0.22.3
draftail
v1.4.1
draft-convert
v2.1.12
formeo
v1.6.2
wagtail-bootstrap-blog
v1.0.0
Popular Libraries in Wagtail
by springload python
1450
A curated list of awesome packages, articles, and other cool resources from the Wagtail community.
by ietf-wg-acme python
882
A protocol for automating certificate issuance
by praekelt python
761 BSD-3-Clause
Django reCAPTCHA form field/widget integration app.
by wagtail python
563 BSD-3-Clause
Next generation Wagtail demo, born in Reykjavik
by coderedcorp python
498 BSD-3-Clause
A content management system for marketing websites based on Django and Wagtail.
by springload javascript
485 MIT
📝🍸 A configurable rich text editor built with Draft.js
by APSL python
481 MIT
A Django blog app implemented in Wagtail
by bkniffler javascript
454 MIT
Draft-JS experiments with drag&drop, resizing, tooltips, WIP
by HubSpot javascript
444 Apache-2.0
Extensibly serialize & deserialize Draft.js ContentState with HTML.
Trending New libraries in Wagtail
by dinoperovic python
209 BSD-3-Clause
Headless e-commerce framework for Django.
by stevliu python
108 MIT
Editing a Conditional Radiance Field
by neon-jungle python
57
Create, send, preview, edit and test email campaigns from within Wagtail
by BuilderIO javascript
56 MIT
Gatsby starter with drag and drop page building
by danestves javascript
43 MIT
A plugin for Strapi Headless CMS that provides content preview to integrate with any frontend
by wagtail shell
37
by nhsx python
32 MIT
NHSX Website - built with Wagtail
by Aleksi44 python
27 GPL-3.0
Wagtail + SVG
by wagtail python
24 BSD-3-Clause
Airtable import and export support for Wagtail pages and Django models.
Top Authors in Wagtail
1
24 Libraries
1970
2
22 Libraries
779
3
17 Libraries
411
4
15 Libraries
32
5
12 Libraries
2215
6
11 Libraries
132
7
8 Libraries
170
8
6 Libraries
374
9
6 Libraries
202
10
5 Libraries
89
1
24 Libraries
1970
2
22 Libraries
779
3
17 Libraries
411
4
15 Libraries
32
5
12 Libraries
2215
6
11 Libraries
132
7
8 Libraries
170
8
6 Libraries
374
9
6 Libraries
202
10
5 Libraries
89
Trending Kits in Wagtail
No Trending Kits are available at this moment for Wagtail
Trending Discussions on Wagtail
Snippet title problem after upgrade from wagtail 1.13 to 2.0
dependencies reference nonexistent child node
Wagtail Admin: How to Add ReadOnlyBlocks to show snippets fields values in a StructBlock
How to switch wagtail homepage depending on user logged in status
Adding external javascript snippet to a Wagtail page field
Changing Template Location in Wagtail
Wagtail 'View live' button provides wrong url after page creation while using id as slug
Testing a Wagtail StructBlock with a StreamBlockField
How to customize the admin form for a custom image model in Wagtail CMS?
Why won't a Wagtail TableBlock Display Properly in the Admin?
QUESTION
Snippet title problem after upgrade from wagtail 1.13 to 2.0
Asked 2022-Apr-11 at 11:13I am working on wagtail upgrade on a site from version 1.13.4 . Was mostly following this post https://wagtail.org/blog/upgrading-wagtail/ . However, since the first update to wagtail 2.0 snippet titles are wrong. I could sort it out on the templated front end pages, but they still appear wrong on the admin panel.
How titles are displayed on the admin panel And this same issue is with fields that link to those snippets
I got as far as updating wagtail to 2.4, since the blog post said that it should support python 3.7, but issue still persists. As far as I can tell, there are no error messages on the terminal output, migrations run without any issues. Any clue where I should start looking?
This should be the relevant artist snippet declaration from the project
1@register_snippet
2class Artist(index.Indexed, models.Model):
3 class Meta:
4 ordering = ['name']
5
6 def __unicode__(self):
7 return unicode(self.name)
8
9 objects = ArtistQuerySet.as_manager()
10
11 name = models.CharField(max_length=512)
12 name_encoded = models.CharField(max_length=512)
13 image_url = models.URLField(blank=True, max_length=512)
14 image = models.ForeignKey(
15 'wagtailimages.Image',
16 blank=True,
17 null=True,
18 on_delete=models.SET_NULL,
19 related_name='+',
20 )
21
22 @property
23 def status(self):
24 # If there are no events currently attached to this artist
25 if not self.artistatevent_set.all():
26 return NO_CURRENT_DATES
27
28 # This artist does have some events, next see if there's any tickets
29 if self.total_ticket_count is not 0:
30
31 # Are they all the same type?
32 if self.available_ticket_count == self.total_ticket_count:
33 return AVAILABLE
34 if self.sold_out_ticket_count == self.total_ticket_count:
35 return SOLD_OUT
36 if self.cancelled_ticket_count == self.total_ticket_count:
37 return CANCELLED
38 if self.unavailable_ticket_count == self.total_ticket_count:
39 return UNAVAILABLE
40
41 # See if we have ANY of the following
42 if self.available_ticket_count:
43 return AVAILABLE
44 if self.coming_soon_ticket_count:
45 return COMING_SOON
46 if self.sold_out_ticket_count:
47 return SOLD_OUT
48
49 # Nothing matched, so default
50 return UNAVAILABLE
51
52 panels = [
53 FieldPanel('name'),
54 ImageChooserPanel('image'),
55 ]
56
57 search_fields = [
58 index.SearchField('name', partial_match=True),
59 ]
60
61
62
EDIT:
This is a snippet from admin.py declaration
1@register_snippet
2class Artist(index.Indexed, models.Model):
3 class Meta:
4 ordering = ['name']
5
6 def __unicode__(self):
7 return unicode(self.name)
8
9 objects = ArtistQuerySet.as_manager()
10
11 name = models.CharField(max_length=512)
12 name_encoded = models.CharField(max_length=512)
13 image_url = models.URLField(blank=True, max_length=512)
14 image = models.ForeignKey(
15 'wagtailimages.Image',
16 blank=True,
17 null=True,
18 on_delete=models.SET_NULL,
19 related_name='+',
20 )
21
22 @property
23 def status(self):
24 # If there are no events currently attached to this artist
25 if not self.artistatevent_set.all():
26 return NO_CURRENT_DATES
27
28 # This artist does have some events, next see if there's any tickets
29 if self.total_ticket_count is not 0:
30
31 # Are they all the same type?
32 if self.available_ticket_count == self.total_ticket_count:
33 return AVAILABLE
34 if self.sold_out_ticket_count == self.total_ticket_count:
35 return SOLD_OUT
36 if self.cancelled_ticket_count == self.total_ticket_count:
37 return CANCELLED
38 if self.unavailable_ticket_count == self.total_ticket_count:
39 return UNAVAILABLE
40
41 # See if we have ANY of the following
42 if self.available_ticket_count:
43 return AVAILABLE
44 if self.coming_soon_ticket_count:
45 return COMING_SOON
46 if self.sold_out_ticket_count:
47 return SOLD_OUT
48
49 # Nothing matched, so default
50 return UNAVAILABLE
51
52 panels = [
53 FieldPanel('name'),
54 ImageChooserPanel('image'),
55 ]
56
57 search_fields = [
58 index.SearchField('name', partial_match=True),
59 ]
60
61
62...
63class NameIDAdmin(admin.ModelAdmin):
64 list_display = ('name', 'id')
65...
66admin.site.register(Artist, NameIDAdmin)
67
68
ANSWER
Answered 2022-Apr-11 at 11:13When upgrading from Python 2 to 3, you should replace the __unicode__
method with __str__
: https://docs.djangoproject.com/en/1.10/topics/python3/#str-and-unicode-methods
QUESTION
dependencies reference nonexistent child node
Asked 2022-Apr-10 at 15:46I tried to dockerize my Wagtail Web Application and this error Occurred.
I tried docker-compose build
there was no errors.
after that i tried docker-compose up
then this error occurred
I really need help on this error.
Error-
149a28e66a331_wagtail_landing_web_1 | django.db.migrations.exceptions.NodeNotFoundError: Migration home.0002_create_homepage dependencies reference nonexistent child node ('wagtailcore', '0053_locale_model')
249a28e66a331_wagtail_landing_web_1 exited with code 1
3
This is the Dockerfile this is the dockerfile of my wagtail site.I'm very new to wagtail and docker .Please guide me if there is an Error
149a28e66a331_wagtail_landing_web_1 | django.db.migrations.exceptions.NodeNotFoundError: Migration home.0002_create_homepage dependencies reference nonexistent child node ('wagtailcore', '0053_locale_model')
249a28e66a331_wagtail_landing_web_1 exited with code 1
3FROM python:3.8.1-slim-buster
4RUN useradd wagtail
5EXPOSE 80
6ENV PYTHONUNBUFFERED=1 \
7 PORT=80
8ENV PYTHONDONTWRITEBYTECODE 1
9RUN apt-get update --yes --quiet && apt-get install --yes --quiet --no-install-recommends \
10 build-essential \
11 libpq-dev \
12 libmariadbclient-dev \
13 libjpeg62-turbo-dev \
14 zlib1g-dev \
15 libwebp-dev \
16&& rm -rf /var/lib/apt/lists/*
17RUN pip install "gunicorn==20.0.4"
18
19COPY ./requirements.txt /requirements.txt
20RUN pip install -r /requirements.txt
21
22COPY ./compose/local/web/entrypoint /entrypoint
23RUN sed -i 's/\r$//g' /entrypoint
24RUN chmod +x /entrypoint
25
26COPY ./compose/local/web/start /start
27RUN sed -i 's/\r$//g' /start
28RUN chmod +x /start
29WORKDIR /app
30
31ENTRYPOINT ["/entrypoint"]
32
This is the YAML file This is the yaml file of the System
149a28e66a331_wagtail_landing_web_1 | django.db.migrations.exceptions.NodeNotFoundError: Migration home.0002_create_homepage dependencies reference nonexistent child node ('wagtailcore', '0053_locale_model')
249a28e66a331_wagtail_landing_web_1 exited with code 1
3FROM python:3.8.1-slim-buster
4RUN useradd wagtail
5EXPOSE 80
6ENV PYTHONUNBUFFERED=1 \
7 PORT=80
8ENV PYTHONDONTWRITEBYTECODE 1
9RUN apt-get update --yes --quiet && apt-get install --yes --quiet --no-install-recommends \
10 build-essential \
11 libpq-dev \
12 libmariadbclient-dev \
13 libjpeg62-turbo-dev \
14 zlib1g-dev \
15 libwebp-dev \
16&& rm -rf /var/lib/apt/lists/*
17RUN pip install "gunicorn==20.0.4"
18
19COPY ./requirements.txt /requirements.txt
20RUN pip install -r /requirements.txt
21
22COPY ./compose/local/web/entrypoint /entrypoint
23RUN sed -i 's/\r$//g' /entrypoint
24RUN chmod +x /entrypoint
25
26COPY ./compose/local/web/start /start
27RUN sed -i 's/\r$//g' /start
28RUN chmod +x /start
29WORKDIR /app
30
31ENTRYPOINT ["/entrypoint"]
32version: "3.7"
33
34services:
35 web:
36 build:
37 context: .
38 dockerfile: ./compose/local/web/Dockerfile
39 image: wagtail_bootstrap_blog_web
40 command: /start
41 volumes:
42 - .:/app
43 ports:
44 - 8000:8000
45 env_file:
46 - ./.env/.dev-sample
47 depends_on:
48 - db
49
50 db:
51 image: postgres:12.0-alpine
52 volumes:
53 - postgres_data:/var/lib/postgresql/data/
54 environment:
55 - POSTGRES_DB=wagtail_bootstrap_blog
56 - POSTGRES_USER=wagtail_bootstrap_blog
57 - POSTGRES_PASSWORD=wagtail_bootstrap_blog
58
59volumes:
60 postgres_data:
61
62
ANSWER
Answered 2022-Apr-10 at 15:46You didn't mention wagtail versions or if this is an upgrade. But that error sounds like this: https://docs.wagtail.org/en/stable/releases/2.11.3.html#run-before-declaration-needed-in-initial-homepage-migration
QUESTION
Wagtail Admin: How to Add ReadOnlyBlocks to show snippets fields values in a StructBlock
Asked 2022-Mar-04 at 12:41I have a Snippet model(SampleSnippet) with fields field_a, field_b, and field_c. And I am using it in a StructBlock like:
1class SampleBlock(blocks.StructBlock):
2 service_snippet = SnippetChooserBlock(SampleSnippet)
3 ...
4
5
Now I want to show some of the SampleSnippet
fields(field_a and field_b) in SampleBlock
after saving the SampleBlock for the first time(after saving the snippet) on the wagtail admin site. How can I achieve this? I looked around a lot but couldn't find a similar issue.
If it were a model/snippet, I could have used property with ReadOnlyPanel but in the case of a StructBlock, I am stuck and not getting any ideas. If anybody knows any way to achieve this, please help me out. Thanks in advance.
ANSWER
Answered 2022-Mar-04 at 10:42This is going to be quite difficult, since in current Wagtail versions (>=2.13) the StreamField editing UI is populated client-side in Javascript - as such, there are some extra steps to make the necessary data available to the client-side code, and the standard Django form / template mechanisms won't be available.
A simple workaround, if the fields aren't too long, would be to define the snippet's __str__
method to include the desired data - this will then display any time the snippet is shown in the admin.
For a full solution, I'd suggest this approach:
Set up a custom
form_template
for your StructBlock as detailed in Custom editing interfaces for StructBlock, with placeholder<div>
elements to contain your additional data.Override the StructBlock's
get_form_state
so that the returned dict (which will be used to populate the form template on the client side) includes your additional fields - something like:
1class SampleBlock(blocks.StructBlock):
2 service_snippet = SnippetChooserBlock(SampleSnippet)
3 ...
4
5def get_form_state(self, value):
6 data = super().get_form_state(value)
7 data['field_a'] = value['service_snippet'].field_a
8 data['field_b'] = value['service_snippet'].field_b
9 return data
10
Set up a custom JS adapter following the steps in Additional JavaScript on StructBlock forms. In the final render
method, the data you returned from get_form_state
will be available as initialState
, and you can use this to populate the placeholder <div>
s in the template.
QUESTION
How to switch wagtail homepage depending on user logged in status
Asked 2022-Mar-03 at 09:36I have previously been using path("", include(wagtail_urls))
for my home_page url which displays the template at home/home_page.html correctly
I wish to however display different pages depending on whether a user is logged in or not so have changed this to:
1def logged_in_switch_view(logged_in_view, logged_out_view):
2 '''switches views dependedn on logon status'''
3 def inner_view(request, *args, **kwargs):
4 if request.user.is_authenticated:
5 return logged_in_view(request, *args, **kwargs)
6 return logged_out_view(request, *args, **kwargs)
7
8 return inner_view
9
10urlpatterns = [
11
12 path("",
13 logged_in_switch_view(
14 TemplateView.as_view(template_name="home/home_page.html")),
15 TemplateView.as_view(template_name="home/not_authenticated.html")),
16 name="home"),
17]
18
With this approach (directly specifying the template rather than using wagtail_urls) the home page does not display correctly when logged in, in that all the wagtail tags in the html e.g. the blog posts are not displaying
home_page.html
1def logged_in_switch_view(logged_in_view, logged_out_view):
2 '''switches views dependedn on logon status'''
3 def inner_view(request, *args, **kwargs):
4 if request.user.is_authenticated:
5 return logged_in_view(request, *args, **kwargs)
6 return logged_out_view(request, *args, **kwargs)
7
8 return inner_view
9
10urlpatterns = [
11
12 path("",
13 logged_in_switch_view(
14 TemplateView.as_view(template_name="home/home_page.html")),
15 TemplateView.as_view(template_name="home/not_authenticated.html")),
16 name="home"),
17]
18{% extends "base.html" %}
19{% load wagtailcore_tags wagtailimages_tags %}
20{% block content %}
21<main class="container">
22 {% for post in page.blogs %}
23 {% with post=post.specific %}
24
25 <div class="col-md-8 mx-auto px-auto">
26 <div class="row border rounded overflow-auto flex-md-row mb-4 shadow-sm position-relative ">
27 <div class="col p-4 d-flex flex-column position-static">
28 <strong class="d-inline-block mb-2 text-primary">{{ post.category }}</strong>
29 <div class="mb-1 text-muted">{{ post.date }}</div>
30 <h3 class="mb-0">{{ post.title }}</h3>
31 <p>{{post.intro}}</p>
32 </div>
33
34 <div class="col-auto my-auto py-2 px-2 d-none d-lg-block">
35 <a href="{% pageurl post %}" class="stretched-link">
36 {% with post.main_image as main_image %}{% if main_image %}
37 {% image main_image min-250x250 max-350x350%}
38 {% endif %}{% endwith %}
39 </a>
40 </div>
41 </div>
42 </div>
43 {% endwith %}
44 {% endfor %}
45
46 </main>
47
48{% endblock %}
49
How should I specify the home_page in the logged_in_switch_view function?
ANSWER
Answered 2022-Mar-03 at 09:36The include(wagtail_urls)
pulls in Wagtail's page handling logic for selecting which page should be returned for a given URL. If you swap that line out with your own code, you're effectively swapping out all of Wagtail...
First, consider whether you're reinventing the page privacy feature that Wagtail already provides. If you want the site as a whole to require a login, and non-logged-in users to be given a blanket generic login form, you can enable this using the Privacy control under the Settings tab (or in the top right corner on older Wagtail releases) when editing the homepage, and set WAGTAIL_FRONTEND_LOGIN_TEMPLATE = "home/not_authenticated.html"
in your project settings file.
If you just want to swap the template for the homepage specifically, you can do that by defining a get_template
method on your HomePage model:
1def logged_in_switch_view(logged_in_view, logged_out_view):
2 '''switches views dependedn on logon status'''
3 def inner_view(request, *args, **kwargs):
4 if request.user.is_authenticated:
5 return logged_in_view(request, *args, **kwargs)
6 return logged_out_view(request, *args, **kwargs)
7
8 return inner_view
9
10urlpatterns = [
11
12 path("",
13 logged_in_switch_view(
14 TemplateView.as_view(template_name="home/home_page.html")),
15 TemplateView.as_view(template_name="home/not_authenticated.html")),
16 name="home"),
17]
18{% extends "base.html" %}
19{% load wagtailcore_tags wagtailimages_tags %}
20{% block content %}
21<main class="container">
22 {% for post in page.blogs %}
23 {% with post=post.specific %}
24
25 <div class="col-md-8 mx-auto px-auto">
26 <div class="row border rounded overflow-auto flex-md-row mb-4 shadow-sm position-relative ">
27 <div class="col p-4 d-flex flex-column position-static">
28 <strong class="d-inline-block mb-2 text-primary">{{ post.category }}</strong>
29 <div class="mb-1 text-muted">{{ post.date }}</div>
30 <h3 class="mb-0">{{ post.title }}</h3>
31 <p>{{post.intro}}</p>
32 </div>
33
34 <div class="col-auto my-auto py-2 px-2 d-none d-lg-block">
35 <a href="{% pageurl post %}" class="stretched-link">
36 {% with post.main_image as main_image %}{% if main_image %}
37 {% image main_image min-250x250 max-350x350%}
38 {% endif %}{% endwith %}
39 </a>
40 </div>
41 </div>
42 </div>
43 {% endwith %}
44 {% endfor %}
45
46 </main>
47
48{% endblock %}
49class HomePage(Page):
50 # ...
51 def get_template(self, request, *args, **kwargs):
52 if request.user.is_authenticated:
53 return "home/home_page.html"
54 else:
55 return "home/not_authenticated.html"
56
This way, your homepage will select one template or the other, but still keep the page
object available on the template.
QUESTION
Adding external javascript snippet to a Wagtail page field
Asked 2022-Feb-28 at 22:44My current Wagtail project has a very simple structure with some generic pages that have just a body RichTextField
. These are just basic CMS type pages where an editor will edit some content in the rich text editor and publish.
One of the pages (and probably more in time) is a "Help Wanted" page and uses an external javascript snippet from a third-party service that lists current job openings with links to the applications that are hosted by them.
Since adding a <script>
tag to the rich text editor simply escapes it and displays only the text value, I added an additional extra_scripts = models.TextField()
to my page model along with a corresponding FieldPanel('extra_scripts')
to the content panels. This lets a user paste in some arbitrary javascript snippets.
If I include this field value in my template with {{page.extra_scripts}}
it just displays the content and does not execute the javascript. Looking through the available wagtailcore_tags
I'm not seeing a filter that I should use to execute the content, and when it's used with a {{}}
tag, I presume the template engine handles it so that it doesn't execute.
How can I include this field in a template so that the javascript is executed?
I know I can change the field to just be the actual javascript content without the <script>
tag, but the snippet also includes some basic HTML elements, and the end users aren't going to intuitively know that they need to manually edit the provided snippet if I only give them a field to paste certain parts of the snippet. I want the end user to be able to just copy/paste the snippet they get and have it work without them needing to understand anything else.
ANSWER
Answered 2022-Feb-28 at 22:44This is a result of the Django template language's auto-escaping behaviour, and can be overridden by adding a |safe
filter:
1{{ page.extra_scripts|safe }}
2
Of course, you should only do this if your editorial users are fully trusted, since it will give them the ability to run arbitrary code (including, for example, hijacking the session cookie of a superuser who happens to visit that page).
QUESTION
Changing Template Location in Wagtail
Asked 2022-Jan-29 at 18:23I'm trying to direct wagtail
to use a template in a centralised location at the top of the project tree. So for example:
1Project
2|_ HomePage
3|_ Search
4|_ Mypage
5|_ templates
6
My project re-uses templates and I'd like to give UX/UI Developers access to a single folder rather than multiple sub-folders in multiple pages.
I've tried in Mypage/models.py
1Project
2|_ HomePage
3|_ Search
4|_ Mypage
5|_ templates
6class AdvisorSummaryPages(Page):
7
8 intro = models.CharField(max_length=250, blank=True, null=True)
9
10 content_panels = Page.content_panels + [
11 FieldPanel('intro'),
12 InlinePanel('carousell', heading="Carousell Images")
13 ]
14
15 template = "advisors.html"
16
With the following template setting in Project/Project/settings/base.py
1Project
2|_ HomePage
3|_ Search
4|_ Mypage
5|_ templates
6class AdvisorSummaryPages(Page):
7
8 intro = models.CharField(max_length=250, blank=True, null=True)
9
10 content_panels = Page.content_panels + [
11 FieldPanel('intro'),
12 InlinePanel('carousell', heading="Carousell Images")
13 ]
14
15 template = "advisors.html"
16TEMPLATES = [
17 {
18 'BACKEND': 'django.template.backends.django.DjangoTemplates',
19 'DIRS': [
20 os.path.join(BASE_DIR, 'templates'),
21 ],
22
With no luck. I can't seem to find any solution on SO or through the documentation or Google that might work. There is a solution presented here using separate model admins
but that doesn't work for me. How might I specify the location of the template differently to a subdirectory of templates in the MyPage App?
Thanks
ANSWER
Answered 2022-Jan-29 at 18:23Below is how I organize templates and static assets. I have a themes
folder that is located in the main project folder with named theme subfolders within the themes
folder. I then have the following in settings/base.py
:
1Project
2|_ HomePage
3|_ Search
4|_ Mypage
5|_ templates
6class AdvisorSummaryPages(Page):
7
8 intro = models.CharField(max_length=250, blank=True, null=True)
9
10 content_panels = Page.content_panels + [
11 FieldPanel('intro'),
12 InlinePanel('carousell', heading="Carousell Images")
13 ]
14
15 template = "advisors.html"
16TEMPLATES = [
17 {
18 'BACKEND': 'django.template.backends.django.DjangoTemplates',
19 'DIRS': [
20 os.path.join(BASE_DIR, 'templates'),
21 ],
22PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
23BASE_DIR = os.path.dirname(PROJECT_DIR)
24
25THEMES_URL = '/themes/'
26THEME_PATH = os.path.join(BASE_DIR, THEMES_URL.replace('/', ''), 'name_of_theme')
27
28TEMPLATES = [
29 {
30 'BACKEND': 'django.template.backends.django.DjangoTemplates',
31 'DIRS': [
32 THEME_PATH,
33 ...other directories...,
34 ],
35... other TEMPLATES-RELATED CODE
36 }
37]
38
39STATICFILES_DIRS = [
40 os.path.join(THEME_PATH, 'static'),
41]
42
43
44STATIC_ROOT = os.path.join(BASE_DIR, 'static')
45STATIC_URL = '/static/'
46
The name_of_theme
folder in the THEME_PATH
definition contains all of the templates and static files for the theme. In order for the static files to be collected correctly, the folder structure for the css, js, etc. files within each theme folder needs to be:
/themes/name_of_theme/static/name_of_theme/js (or css, etc.)/filename.js (or filename.css, etc.)
The /name_of_theme/static/name_of_theme/
namespacing is necessary for collectstatic
to be able to collect the files correctly (see Staticfile namespacing here for more info). When including a reference to a static file in a template, you then do:
{% static 'name_of_theme/js/filename.js' %}
The STATICFILES_DIRS
definition is only set up for one theme. You would need to change or add to that if you're using more than one theme.
Some time ago I also came across this Wagtail package: Wagtail Themes. It looks very interesting, but I'm not sure if it provides for handling static files organized within named theme folders as I describe above.
QUESTION
Wagtail 'View live' button provides wrong url after page creation while using id as slug
Asked 2022-Jan-26 at 14:39I have a case that uses Page ID as a slug, after creating a new Page, Wagtail provides us a "View live" button but when we click on that button, it provides a wrong URL
The right URL should be ".../property-list/<property-id>"
I have searched on stack overflow, found this thread but the answer is still a mystery: Wrong 'View live' url in Wagtail admin message after page creation when using id as slug
I have followed the Wagtail official document, using Wagtail Hooks to manipulate data. However, no success yet. This is my code:
1@hooks.register('after_create_page')
2def set_number_and_slug_after_property_page_created(request, page):
3 page.number = page.slug = str(page.id)
4 page.save()
5 new_revision = page.save_revision()
6 if page.live:
7 new_revision.publish()
8
Please help me, thank you.
ANSWER
Answered 2022-Jan-26 at 14:39The after_create_page
hook doesn't work for this, as it's one of the last things to run during the Wagtail admin's page creation view, and the confirmation message (including the old URL) has already been constructed by that point. This can be remedied by using the post_save
signal provided by Django instead - being a more low-level mechanism, it's more closely tied to the act of saving the database record, without Wagtail's admin logic getting in the way.
(It's also a bit more future-proof: Wagtail's after_create_page
hook is designed to only be called when a user goes through the page creation area of the Wagtail admin, so that it can be used to customise that user's path through the admin if appropriate. If there's ever any other route by which a page might be created - like, say, a data import, or using translation tools for a multi-language site - then after_create_page
will be bypassed, but the post_save
signal will still be triggered.)
Assuming your project has a properties
app where you're defining a PropertyPage model, your code can be rewritten to use post_save
as follows - in properties/signals.py
:
1@hooks.register('after_create_page')
2def set_number_and_slug_after_property_page_created(request, page):
3 page.number = page.slug = str(page.id)
4 page.save()
5 new_revision = page.save_revision()
6 if page.live:
7 new_revision.publish()
8from django.db.models.signals import post_save
9from django.dispatch import receiver
10
11from .models import PropertyPage
12
13
14@receiver(post_save)
15def set_number_and_slug_after_property_page_created(sender, instance, created, **kwargs):
16 if issubclass(sender, PropertyPage) and created:
17 page = instance
18 page.number = page.slug = str(page.id)
19 page.save()
20 new_revision = page.save_revision()
21 if page.live:
22 new_revision.publish()
23
Then, to connect this signal up on server startup, create a properties/apps.py
containing the following (or just add / edit the ready
method if you have an AppConfig class there already):
1@hooks.register('after_create_page')
2def set_number_and_slug_after_property_page_created(request, page):
3 page.number = page.slug = str(page.id)
4 page.save()
5 new_revision = page.save_revision()
6 if page.live:
7 new_revision.publish()
8from django.db.models.signals import post_save
9from django.dispatch import receiver
10
11from .models import PropertyPage
12
13
14@receiver(post_save)
15def set_number_and_slug_after_property_page_created(sender, instance, created, **kwargs):
16 if issubclass(sender, PropertyPage) and created:
17 page = instance
18 page.number = page.slug = str(page.id)
19 page.save()
20 new_revision = page.save_revision()
21 if page.live:
22 new_revision.publish()
23from django.apps import AppConfig
24
25
26class PropertiesConfig(AppConfig):
27 name = 'properties'
28
29 def ready(self):
30 from . import signals
31
QUESTION
Testing a Wagtail StructBlock with a StreamBlockField
Asked 2021-Dec-29 at 15:02I'm attempting to run some unit tests on a StructBlock, which is composed of normal block fields and a StreamBlock.
The problem I'm running into is that I can construct a test that renders the block, but I cannot test the StreamBlock validation (i.e., I can't test the block's clean()
)
Stack
- Python 3.9.6
- Django 3.2
- Wagtail 2.13
- pytest 6.2
- pytest-django 4.4
Block Definitions
MyStructBlock
1 class MyStructBlock(blocks.StructBlock):
2 image = ImageChooserBlock()
3
4 heading = blocks.CharBlock(required=True)
5 description = blocks.RichTextBlock(features=["bold", "italic"])
6 links = blocks.StreamBlock([("link", LinkBlock())], icon="link", min_num=1, max_num=6)
7
8 class Meta:
9 icon = "list-ul"
10 template = "blocks/two_column_with_link_list_block.html"
11
LinkBlock
1 class MyStructBlock(blocks.StructBlock):
2 image = ImageChooserBlock()
3
4 heading = blocks.CharBlock(required=True)
5 description = blocks.RichTextBlock(features=["bold", "italic"])
6 links = blocks.StreamBlock([("link", LinkBlock())], icon="link", min_num=1, max_num=6)
7
8 class Meta:
9 icon = "list-ul"
10 template = "blocks/two_column_with_link_list_block.html"
11 class LinkBlock(blocks.StructBlock):
12 link_text = blocks.CharBlock()
13 internal_link = blocks.PageChooserBlock()
14 internal_document = DocumentChooserBlock()
15 external_link = blocks.URLBlock()
16
With this setup I create this kind of block_definition
:
1 class MyStructBlock(blocks.StructBlock):
2 image = ImageChooserBlock()
3
4 heading = blocks.CharBlock(required=True)
5 description = blocks.RichTextBlock(features=["bold", "italic"])
6 links = blocks.StreamBlock([("link", LinkBlock())], icon="link", min_num=1, max_num=6)
7
8 class Meta:
9 icon = "list-ul"
10 template = "blocks/two_column_with_link_list_block.html"
11 class LinkBlock(blocks.StructBlock):
12 link_text = blocks.CharBlock()
13 internal_link = blocks.PageChooserBlock()
14 internal_document = DocumentChooserBlock()
15 external_link = blocks.URLBlock()
16{
17 'image': <Image: Thumbnail of subject>,
18 'heading': 'SomeText',
19 'description': 'Hello, Son',
20 'links':
21 [
22 {'value':
23 {
24 'link_text': 'Walk trip thought region.',
25 'internal_link': None,
26 'document_link': None,
27 'external_link': 'www.example.com'
28 }
29 },
30 {'value': {...}},
31 {'value': {...}},
32 ]
33}
34
Then a test like this will work just fine:
1 class MyStructBlock(blocks.StructBlock):
2 image = ImageChooserBlock()
3
4 heading = blocks.CharBlock(required=True)
5 description = blocks.RichTextBlock(features=["bold", "italic"])
6 links = blocks.StreamBlock([("link", LinkBlock())], icon="link", min_num=1, max_num=6)
7
8 class Meta:
9 icon = "list-ul"
10 template = "blocks/two_column_with_link_list_block.html"
11 class LinkBlock(blocks.StructBlock):
12 link_text = blocks.CharBlock()
13 internal_link = blocks.PageChooserBlock()
14 internal_document = DocumentChooserBlock()
15 external_link = blocks.URLBlock()
16{
17 'image': <Image: Thumbnail of subject>,
18 'heading': 'SomeText',
19 'description': 'Hello, Son',
20 'links':
21 [
22 {'value':
23 {
24 'link_text': 'Walk trip thought region.',
25 'internal_link': None,
26 'document_link': None,
27 'external_link': 'www.example.com'
28 }
29 },
30 {'value': {...}},
31 {'value': {...}},
32 ]
33}
34 def test_structure():
35 my_struct_block = MyStructBlock()
36 block_def = block_definition
37 rendered_block_html = BeautifulSoup(my_struct_block.render(value=block_def)))
38 assert <<THINGS ABOUT THE STRUCTURE>>
39
The block renders and all is well, but when I try to clean
the block everything starts to go sideways.
1 class MyStructBlock(blocks.StructBlock):
2 image = ImageChooserBlock()
3
4 heading = blocks.CharBlock(required=True)
5 description = blocks.RichTextBlock(features=["bold", "italic"])
6 links = blocks.StreamBlock([("link", LinkBlock())], icon="link", min_num=1, max_num=6)
7
8 class Meta:
9 icon = "list-ul"
10 template = "blocks/two_column_with_link_list_block.html"
11 class LinkBlock(blocks.StructBlock):
12 link_text = blocks.CharBlock()
13 internal_link = blocks.PageChooserBlock()
14 internal_document = DocumentChooserBlock()
15 external_link = blocks.URLBlock()
16{
17 'image': <Image: Thumbnail of subject>,
18 'heading': 'SomeText',
19 'description': 'Hello, Son',
20 'links':
21 [
22 {'value':
23 {
24 'link_text': 'Walk trip thought region.',
25 'internal_link': None,
26 'document_link': None,
27 'external_link': 'www.example.com'
28 }
29 },
30 {'value': {...}},
31 {'value': {...}},
32 ]
33}
34 def test_structure():
35 my_struct_block = MyStructBlock()
36 block_def = block_definition
37 rendered_block_html = BeautifulSoup(my_struct_block.render(value=block_def)))
38 assert <<THINGS ABOUT THE STRUCTURE>>
39 def test_validation():
40 my_struct_block = MyStructBlock()
41 block_def = block_definition
42 rendered_block_html = BeautifulSoup(my_struct_block.render(my_struct_block.clean(block_def)))
43
44
Will result in something like this:
1 class MyStructBlock(blocks.StructBlock):
2 image = ImageChooserBlock()
3
4 heading = blocks.CharBlock(required=True)
5 description = blocks.RichTextBlock(features=["bold", "italic"])
6 links = blocks.StreamBlock([("link", LinkBlock())], icon="link", min_num=1, max_num=6)
7
8 class Meta:
9 icon = "list-ul"
10 template = "blocks/two_column_with_link_list_block.html"
11 class LinkBlock(blocks.StructBlock):
12 link_text = blocks.CharBlock()
13 internal_link = blocks.PageChooserBlock()
14 internal_document = DocumentChooserBlock()
15 external_link = blocks.URLBlock()
16{
17 'image': <Image: Thumbnail of subject>,
18 'heading': 'SomeText',
19 'description': 'Hello, Son',
20 'links':
21 [
22 {'value':
23 {
24 'link_text': 'Walk trip thought region.',
25 'internal_link': None,
26 'document_link': None,
27 'external_link': 'www.example.com'
28 }
29 },
30 {'value': {...}},
31 {'value': {...}},
32 ]
33}
34 def test_structure():
35 my_struct_block = MyStructBlock()
36 block_def = block_definition
37 rendered_block_html = BeautifulSoup(my_struct_block.render(value=block_def)))
38 assert <<THINGS ABOUT THE STRUCTURE>>
39 def test_validation():
40 my_struct_block = MyStructBlock()
41 block_def = block_definition
42 rendered_block_html = BeautifulSoup(my_struct_block.render(my_struct_block.clean(block_def)))
43
44self = <wagtail.core.blocks.field_block.RichTextBlock object at 0x7f874f430580>, value = 'Hello, Son'
45
46 def value_for_form(self, value):
47 # Rich text editors take the source-HTML string as input (and takes care
48 # of expanding it for the purposes of the editor)
49> return value.source
50E AttributeError: 'str' object has no attribute 'source'
51
52.direnv/python-3.9.6/lib/python3.9/site-packages/wagtail/core/blocks/field_block.py:602: AttributeError
53
Which is descriptive enough -- I get that it requires a Richtext Block definition -- but why the difference?
NOTE: I have tried defining the block_definition
using RichText
as the value for fields that expect RichText, this fails. If I remove the RichText
field, the clean proceeds to fail on the dicts
I used to define the LinkBlock
.
Question
Is there a canonical way to set up a test like this that can handle complex blocks, so that a common source of block definition can test render
as well as clean
and render
? Or, does this type of block, in some way, require an integration approach where I construct a Page with the complex blocks added as stream_data
and then request the Page and use the response's rendering?
ANSWER
Answered 2021-Dec-29 at 15:02Every block type has a corresponding 'native' data type for the data it expects to work with - for the simpler blocks, this data type is what you'd expect (e.g. a string for CharBlock, an Image instance for ImageChooserBlock) but for a few of the more complex ones, there's a custom type defined:
- for RichTextBlock, the native type is
wagtail.core.rich_text.RichText
(which behaves similarly to a string, but also has asource
property where e.g. page IDs in page links are kept intact) - for StreamBlock, the native type is
wagtail.core.blocks.StreamValue
(a sequence type, where each item is a StreamValue withblock_type
andvalue
properties).
The render
method will generally be quite forgiving if you use the wrong types (such as a string for RichTextBlock or a list of dicts for StreamBlock), since it's really just invoking your own template code. The clean
method will be more picky, since it's running Python logic specific to each block.
Unfortunately the correct types to use for each block aren't really formally documented, and some of them are quite fiddly to construct (e.g. a StructValue needs to be passed a reference to the corresponding StructBlock) - outside of test code, there isn't much need to create these objects from scratch, because the data will usually be coming from some outside source instead (e.g. a form submission or the database), and each block will be responsible for converting that to its native type.
With that in mind, I'd recommend that you construct your data by piggybacking on the to_python
method, which converts the JSON representation as stored in the database (consisting of just simple Python data types - integers, strings, lists, dicts) into the native data types:
1 class MyStructBlock(blocks.StructBlock):
2 image = ImageChooserBlock()
3
4 heading = blocks.CharBlock(required=True)
5 description = blocks.RichTextBlock(features=["bold", "italic"])
6 links = blocks.StreamBlock([("link", LinkBlock())], icon="link", min_num=1, max_num=6)
7
8 class Meta:
9 icon = "list-ul"
10 template = "blocks/two_column_with_link_list_block.html"
11 class LinkBlock(blocks.StructBlock):
12 link_text = blocks.CharBlock()
13 internal_link = blocks.PageChooserBlock()
14 internal_document = DocumentChooserBlock()
15 external_link = blocks.URLBlock()
16{
17 'image': <Image: Thumbnail of subject>,
18 'heading': 'SomeText',
19 'description': 'Hello, Son',
20 'links':
21 [
22 {'value':
23 {
24 'link_text': 'Walk trip thought region.',
25 'internal_link': None,
26 'document_link': None,
27 'external_link': 'www.example.com'
28 }
29 },
30 {'value': {...}},
31 {'value': {...}},
32 ]
33}
34 def test_structure():
35 my_struct_block = MyStructBlock()
36 block_def = block_definition
37 rendered_block_html = BeautifulSoup(my_struct_block.render(value=block_def)))
38 assert <<THINGS ABOUT THE STRUCTURE>>
39 def test_validation():
40 my_struct_block = MyStructBlock()
41 block_def = block_definition
42 rendered_block_html = BeautifulSoup(my_struct_block.render(my_struct_block.clean(block_def)))
43
44self = <wagtail.core.blocks.field_block.RichTextBlock object at 0x7f874f430580>, value = 'Hello, Son'
45
46 def value_for_form(self, value):
47 # Rich text editors take the source-HTML string as input (and takes care
48 # of expanding it for the purposes of the editor)
49> return value.source
50E AttributeError: 'str' object has no attribute 'source'
51
52.direnv/python-3.9.6/lib/python3.9/site-packages/wagtail/core/blocks/field_block.py:602: AttributeError
53block_def = my_struct_block.to_python({
54 'image': 123, # the image ID
55 'heading': 'SomeText',
56 'description': 'Hello, Son',
57 'links':
58 [
59 {'type': 'link', 'value':
60 {
61 'link_text': 'Walk trip thought region.',
62 'internal_link': None,
63 'document_link': None,
64 'external_link': 'www.example.com'
65 }
66 },
67 {'type': 'link', 'value': {...}},
68 {'type': 'link', 'value': {...}},
69 ]
70})
71
my_struct_block.clean(block_def)
will hopefully then succeed.
QUESTION
How to customize the admin form for a custom image model in Wagtail CMS?
Asked 2021-Dec-02 at 16:45I need to add a string based unique ID to Wagtail’s image model. These IDs are a relatively short combination of letters, numbers and punctuation, e.g. "AS.M-1.001". So I am using a custom image model with Django’s standard CharField
for that with the argument unique=True
. But the editing form unfortunately does not check if the ID is unique. So I can use the same ID for multiple images. This check for uniqueness does work in any other standard form in Wagtail, e.g. the Page
model. But not for the image model.
1from django.db import models
2from wagtail.images.models import Image, AbstractImage
3
4class CustomImage(AbstractImage):
5 custom_id = models.CharField(max_length=32, unique=True, null=True, blank=True)
6 admin_form_fields = ( 'custom_id', ) + Image.admin_form_fields
7
My approach would be to override the editing form with a custom one to display more warnings and errors, like you can do with base_form_class
for Wagtail’s Page
model etc., as documented here. I tried both wagtail.admin.forms.WagtailAdminModelForm as well as wagtail.images.forms.BaseImageForm.
1from django.db import models
2from wagtail.images.models import Image, AbstractImage
3
4class CustomImage(AbstractImage):
5 custom_id = models.CharField(max_length=32, unique=True, null=True, blank=True)
6 admin_form_fields = ( 'custom_id', ) + Image.admin_form_fields
7from wagtail.images.forms import BaseImageForm
8from wagtail.admin.forms import WagtailAdminModelForm
9
10class CustomImageForm(WagtailAdminModelForm):
11 # add more logic here
12 pass
13
14class CustomImage(ArtButlerIDMixin, AbstractImage):
15 ...
16 base_form_class = CustomImageForm
17
Both lead to the same exception:
1from django.db import models
2from wagtail.images.models import Image, AbstractImage
3
4class CustomImage(AbstractImage):
5 custom_id = models.CharField(max_length=32, unique=True, null=True, blank=True)
6 admin_form_fields = ( 'custom_id', ) + Image.admin_form_fields
7from wagtail.images.forms import BaseImageForm
8from wagtail.admin.forms import WagtailAdminModelForm
9
10class CustomImageForm(WagtailAdminModelForm):
11 # add more logic here
12 pass
13
14class CustomImage(ArtButlerIDMixin, AbstractImage):
15 ...
16 base_form_class = CustomImageForm
17raise AppRegistryNotReady("Models aren't loaded yet.")
18
So a tried to resort the apps in my settings with no effect. Does the standard approach how to override an admin form in Wagtail work for the image model at all? What could be other ways to get a unique string identifier working here? ... or to customize this form.
Solution (Update)Following @gasman’s advice, I added the following line to my settings/base.py
:
1from django.db import models
2from wagtail.images.models import Image, AbstractImage
3
4class CustomImage(AbstractImage):
5 custom_id = models.CharField(max_length=32, unique=True, null=True, blank=True)
6 admin_form_fields = ( 'custom_id', ) + Image.admin_form_fields
7from wagtail.images.forms import BaseImageForm
8from wagtail.admin.forms import WagtailAdminModelForm
9
10class CustomImageForm(WagtailAdminModelForm):
11 # add more logic here
12 pass
13
14class CustomImage(ArtButlerIDMixin, AbstractImage):
15 ...
16 base_form_class = CustomImageForm
17raise AppRegistryNotReady("Models aren't loaded yet.")
18WAGTAILIMAGES_IMAGE_MODEL = 'images.CustomImage'
19WAGTAILIMAGES_IMAGE_FORM_BASE = 'images.forms.CustomImageForm' # NEW
20
And added a the following form to a forms.py
in my images app:
1from django.db import models
2from wagtail.images.models import Image, AbstractImage
3
4class CustomImage(AbstractImage):
5 custom_id = models.CharField(max_length=32, unique=True, null=True, blank=True)
6 admin_form_fields = ( 'custom_id', ) + Image.admin_form_fields
7from wagtail.images.forms import BaseImageForm
8from wagtail.admin.forms import WagtailAdminModelForm
9
10class CustomImageForm(WagtailAdminModelForm):
11 # add more logic here
12 pass
13
14class CustomImage(ArtButlerIDMixin, AbstractImage):
15 ...
16 base_form_class = CustomImageForm
17raise AppRegistryNotReady("Models aren't loaded yet.")
18WAGTAILIMAGES_IMAGE_MODEL = 'images.CustomImage'
19WAGTAILIMAGES_IMAGE_FORM_BASE = 'images.forms.CustomImageForm' # NEW
20from django.core.exceptions import ValidationError
21from wagtail.images.forms import BaseImageForm
22from .models import CustomImage
23
24class CustomImageForm(BaseImageForm):
25
26 def clean(self):
27 cleaned_data = super().clean()
28 custom_id = cleaned_data.get("custom_id")
29
30 if CustomImage.objects.filter(custom_id=custom_id).exists():
31 raise ValidationError(
32 "Custom ID already exists"
33 )
34
35 return cleaned_data
36
ANSWER
Answered 2021-Dec-02 at 14:36Images in Wagtail don't use WagtailAdminModelForm
or the base_form_class
attribute - these are used by pages, snippets and ModelAdmin to support Wagtail-specific features like inline children and panels, but images work through plain Django models and forms.
You can customise the form by subclassing BaseImageForm and setting WAGTAILIMAGES_IMAGE_FORM_BASE in your project settings. As long as you define your form class somewhere outside of models.py (e.g. in a separate forms.py module), you'll avoid the circular dependency that leads to the "Models aren't loaded yet" error.
QUESTION
Why won't a Wagtail TableBlock Display Properly in the Admin?
Asked 2021-Nov-05 at 01:53I'd like to have a TableBlock
display in my admin panel, but it isn't displaying properly.
Here are the errors I'm getting:
And here's the code block:
1from wagtail.contrib.table_block.blocks import TableBlock
2from wagtail.core.blocks import StreamBlock
3from wagtail.core.fields import StreamField
4
5class BaseStreamBlock(StreamBlock):
6 table = TableBlock()
7
8
9class ArticlePage(Page):
10 parent_page_types = ['home.HomePage']
11 subpage_types = []
12
13 content = StreamField(BaseStreamBlock(), verbose_name=_('Content'), blank=True)
14
15 content_panels = [
16 MultiFieldPanel([
17 FieldPanel('title'),
18 ]),
19 MultiFieldPanel(
20 [
21 StreamFieldPanel('content'),
22 ]
23 ),
24 ]
25
ANSWER
Answered 2021-Nov-03 at 17:36The errors in the browser console show that the Javascript files included in the wagtail.contrib.table_block
app are not loading. Most likely, these are missing from your S3 file hosting (S3 returns 403 Forbidden for missing files).
After adding wagtail.contrib.table_block
to INSTALLED_APPS and deploying to your server, you'll need to re-run ./manage.py collectstatic
to ensure these JS files are uploaded to S3.
Community Discussions contain sources that include Stack Exchange Network
Tutorials and Learning Resources in Wagtail
Tutorials and Learning Resources are not available at this moment for Wagtail