Wagtail Tutorial Series:
To learn more about Wagtail CMS, please check Build Blog With Wagtail CMS (4.0.0)
- Create Wagtail Project
- Modern Frontend Techs for Wagtail
- Dockerizing Wagtail App
- Add Blog Models to Wagtail
- How to write Wagtail page template
- Create Stylish Wagtail Pages with Tailwind CSS
- How to use StreamField in Wagtail
- Wagtail Routable Page
- Add Pagination Component to Wagtail
- Customize Wagtail Page URL
- Add Full Text Search to Wagtail
- Add Markdown Support to Wagtail
- Add LaTeX Support & Code Highlight In Wagtail
- How to Build Form Page in Wagtail
- How to Create and Manage Menus in Wagtail
- Wagtail SEO Guide
- Online Demo http://wagtail-blog.accordbox.com/
- Source code: https://github.com/AccordBox/wagtail-tailwind-blog
Wagtail Tips:
- Wagtail Tip #1: How to replace ParentalManyToManyField with InlinePanel
- Wagtail Tip #2: How to Export & Restore Wagtail Site
Write style in Wagtail:
- How to use SCSS/SASS in your Django project (Python Way)
- How to use SCSS/SASS in your Django project (NPM Way)
Other Wagtail Topics:
Objective
By the end of this chapter, you should be able to:
- Understand how to build basic menus with
show_in_menus
field. - Learn what is page path and how page orders work.
- Build menus with
wagtailmenus
package.
Show in Menu
$ docker-compose up -d
$ docker-compose logs -f
Please go to Wagtail admin, edit the contact page
we just created.
In the promote
tab, you will see Show in menus
field, click it and then publish the page.
Next, let's check data in the Django shell
$ docker-compose run --rm web python manage.py shell
>>> from wagtail_app.blog.models import BlogPage
>>> blog_page = BlogPage.objects.first()
>>> blog_page.get_children().live().in_menu()
<PageQuerySet [<Page: Contact>]>
>>> exit()
As you can see, if we set show_in_menus=True
in Wagtail admin, we can get the page using in_menu
.
So we can display the page in the navbar
like this.
<ul>
{% for menu_page in blog_page.get_children.live.in_menu %}
<li>
<a href="{{ menu_page.url }}" class="nav-link">{{ menu_page.title }}</a>
</li>
{% endfor %}
</ul>
Page path
Some people might ask, what if I want the nested
menu.
Let's first check this part of the Wagtail source code
from treebeard.mp_tree import MP_Node
class AbstractPage(
LockableMixin,
PreviewableMixin,
DraftStateMixin,
RevisionMixin,
TranslatableMixin,
MP_Node,
):
"""
Abstract superclass for Page. According to Django's inheritance rules, managers set on
abstract models are inherited by subclasses, but managers set on concrete models that are extended
via multi-table inheritance are not. We therefore need to attach PageManager to an abstract
superclass to ensure that it is retained by subclasses of Page.
"""
objects = PageManager()
class Meta:
abstract = True
Notes:
- Here we see the Wagtail page inherit from
MP_Node
oftreebeard.mp_tree
(django-treebeard
is a library that implementsefficient tree implementations
for the Django)
Let's run some code in Django shell to help us better understand the tree structures.
$ docker-compose run --rm web python manage.py shell
>>> from wagtail.core.models import Page
>>> root_page = Page.objects.get(pk=1)
>>> root_page.depth
1
>>> root_page.path
'0001'
>>> blog_page = root_page.get_children().first()
>>> blog_page.depth
2
>>> blog_page.path
'00010002'
>>> post_page = blog_page.get_children().first()
>>> post_page.depth
3
>>> post_page.path
'000100020001'
>>> exit()
Notes:
depth
store the depth of the node in a tree, and theroot node
has depth1
path
stores the full materialized path for each node, each node would take4 char
. That why you see0001
,0002
- The char in the
path
can be0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ
(length is36
), so one node can contains up to1679615
(36 ** 4 - 1
) child pages by default.
You can also check django-treebeard doc to learn more
Page Order
Please check here Wagtail core
class BasePageManager(models.Manager):
def get_queryset(self):
return self._queryset_class(self.model).order_by("path")
So the PostPage.objects.all()
would order the page using the path
field by default.
When we check pages in Wagtail admin:
- By default, the index page would order the pages using the
latest_revision_created_at
field. (Recently edited page would be the first) - If we click the top
Sort menu order
button, the page will be ordered with default queryset order (path
field), and we can drag the item up or down to change the position in the tree (please note this would change data in the db). (You will see the URL in the browser has querystringordering=ord
)
Next, let's change the page order and then check the data in the Django shell.
Before I change:
$ docker-compose run --rm web python manage.py shell
>>> from wagtail_app.blog.models import BlogPage
>>> blog_page = BlogPage.objects.first()
>>> [(page.title, page.path) for page in blog_page.get_children()]
[('PostPage1', '000100020001'), ('MarkDown Example', '000100020002'), ('PostPage3', '000100020003'), ('Contact', '000100020004')]
After I DRAG Contact
page to the first
$ docker-compose run --rm web python manage.py shell
>>> from blog.models import BlogPage
>>> blog_page = BlogPage.objects.first()
>>> [(page.title, page.path) for page in blog_page.get_children()]
[('Contact', '000100020001'), ('PostPage1', '000100020002'), ('MarkDown Example', '000100020003'), ('PostPage3', '000100020004')]
Notes:
- As you can see, the
path
field in the pages all updated. - The core logic of the
path
change is done bytreebeard node.move
method, and you can check more from the doc
Some times, clients care about the page order in the menu, and we can use path
field to help us without adding new fields.
WagtailMenu
Now we have a good understanding of how menu in Wagtail works, so I'd like to give you a better solution for you to build menu in your Wagtail project.
Add wagtailmenus
to the requirements.txt
wagtailmenus==3.1.3
Add wagtailmenus
and wagtail.contrib.modeladmin
to the INSTALLED_APPS
in wagtail_app/settings.py
INSTALLED_APPS = [
'wagtail.contrib.modeladmin', # new
"wagtailmenus", # new
# code omitted for brevity
]
Please make sure wagtail.contrib.modeladmin
is also included in INSTALLED_APPS
Add wagtailmenus.context_processors.wagtailmenus
to the TEMPLATES
in wagtail_app/settings.py
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': ['wagtail_app/templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'wagtailmenus.context_processors.wagtailmenus', # new
],
},
},
]
# rebuild image and run
$ docker-compose up -d --build
$ docker-compose logs -f
Now please go to /settigns/main menu/
and add the contact page.
Template
Update wagtail_app/templates/blog/components/navbar.html
{% load menu_tags %}
<nav class="bg-white border-b border-opacity-75 border-gray-300 dark:bg-gray-900 dark:text-white">
<div class="mx-auto max-w-7xl px-2 sm:px-6 lg:px-8">
<div class="relative flex items-center justify-between h-16">
<div class="flex-1 flex items-center justify-center sm:items-stretch sm:justify-start">
<div class="flex-shrink-0 flex items-center">
<a href="/"><span class="text-bold text-grey-800">Wagtail Blog Demo</span></a>
</div>
<div class="hidden sm:block sm:ml-6">
<div class="flex space-x-4">
{% main_menu template="menu/main_desktop_menu.html" %}
</div>
</div>
</div>
</div>
</div>
</nav>
Notes:
- In the top, we add
{% load menu_tags %}
{% main_menu template="menu/main_desktop_menu.html" %}
means we render the main menu with the templatemenu/main_desktop_menu.html
Create wagtail_app/templates/menu/main_desktop_menu.html
{% for item in menu_items %}
<a href="{{ item.href }}" class="text-gray-500 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium" >
{{ item.text }}
</a>
{% endfor %}
Now Contact
page display on the top navbar.
Notes
- We will update the navbar to make it work on the mobile device in later chapter.
wagtailmenu
is very powerful and flexible, If you want to makewagtailmenu
to generate nested menu, you can take a look at this
Migration
If you have CI job that check Django migration, it might fail
/usr/local/lib/python3.10/site-packages/wagtailmenus/migration/0024_alter_flatmenu_id_alter_flatmenuitem_id_and_more.py
- Alter field id on flatmenu
- Alter field id on flatmenuitem
- Alter field id on mainmenu
- Alter field id on mainmenuitem
Before https://github.com/jazzband/wagtailmenus/issues/435 is resolved, below are some solutions
- One solution is to set
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
in django settings - The other solution is to write a custom command which can ignore some 3-party Django app https://forum.djangoproject.com/t/how-to-fix-ignore-pending-migration-in-3rd-party-app/11408/4
Wagtail Tutorial Series:
To learn more about Wagtail CMS, please check Build Blog With Wagtail CMS (4.0.0)
- Create Wagtail Project
- Modern Frontend Techs for Wagtail
- Dockerizing Wagtail App
- Add Blog Models to Wagtail
- How to write Wagtail page template
- Create Stylish Wagtail Pages with Tailwind CSS
- How to use StreamField in Wagtail
- Wagtail Routable Page
- Add Pagination Component to Wagtail
- Customize Wagtail Page URL
- Add Full Text Search to Wagtail
- Add Markdown Support to Wagtail
- Add LaTeX Support & Code Highlight In Wagtail
- How to Build Form Page in Wagtail
- How to Create and Manage Menus in Wagtail
- Wagtail SEO Guide
- Online Demo http://wagtail-blog.accordbox.com/
- Source code: https://github.com/AccordBox/wagtail-tailwind-blog
Wagtail Tips:
- Wagtail Tip #1: How to replace ParentalManyToManyField with InlinePanel
- Wagtail Tip #2: How to Export & Restore Wagtail Site
Write style in Wagtail:
- How to use SCSS/SASS in your Django project (Python Way)
- How to use SCSS/SASS in your Django project (NPM Way)
Other Wagtail Topics: