How to Build a Wagtail Website

 Why Build Websites This Way


A full-stack website, which integrates both front-end (user interface) and back-end (server and database) components, offers a comprehensive solution that streamlines development, ensures consistency in design and functionality, and enhances communication between different parts of the web application. 


This holistic approach not only accelerates the development process by allowing simultaneous progress on user-facing features and server-side logic but also simplifies maintenance and scalability. 


As developers have control over the entire system architecture, they can implement features more efficiently and ensure that all elements work seamlessly together, providing a smoother user experience and improving overall performance and security.


 Note: This walkthrough uses Wagtail 6.2 on Windows.

What is Django?​​


Django is a powerful tool used by web developers to build websites quickly and efficiently, developed with Python. It allows developers to focus on creating a unique and functional website without reinventing the wheel for each new project.



What is Wagtail?


Wagtail, is an add-on for Django that makes it easier to manage the content of a website. It provides a user-friendly interface where editors can easily modify and organize their website.


 Continue reading this guide to learn more!

 Getting Started


Local Development Environment

  1. Install PyCharm Community Edition (Download)
  2. Install Python (Download)
  3. Install Docker (Download) & Sign-up (Account)
  4. Install Git (Download)


Project Creation

  1. Open PyCharm
  2. Create a new project
  3. Set the project location, if not already default
  4. Give it a Project Name
  5. Choose "Create a Git repository"
  6. Set the project virtual environment (venv) to Python 3.12 (current as of 6/15/24)
  7. Click the "Create" button
  8. Open the PyCharm Terminal panel and set Git info: 
    git config user.email "your@email.com"

 Watch & Learn



 New Wagtail Website

  1. Install Wagtail: pip install wagtail
    1. Note, if you need to install 6.2 specifically, use pip install wagtail==6.2
    2. Note, if you may need to update pip or clear the cache
  2. Start Wagtail site: wagtail start [site-name]
  3. Change directories to new wagtail site: cd [site-name]
  4. Make database migrations: python manage.py makemigrations
  5. Run database migration: python manage.py migrate
  6. Create super user: python manage.py createsuperuser
    1. Provide a username, email, and password
  7. Run website for the first time: python manage.py runserver
    1. Test your website at 127.0.0.1:8000
  8. Stop the server process by typing CTRL+C in the PyCharm Terminal.


 Save Your Work! 
Add recent updates to your Git Repo: 
git add . then git commit -m "New Wagtail Website"


 Extend Data Models

 Why use custom data models? By extending data models, you can easily add custom fields to your user authentication, images or documents (e.g., source, information, or type). Extending from the beginning gives you flexibility without altering the core features of Django. This is particularly useful if your application evolves and you need to store more information about your data objects.

Note, delete the initial db.sqlite3 file as we'll migrate again with custom models.


Custom User Model
(Django for Professionals, Page 56 and Wagtail Docs)

  1. Create accounts app: python manage.py startapp accounts
  2. Add code to /accounts/models.py
  3. Update [site-name]/settings/base.py
    1. Add "accounts" to INSTALLED_APPS above "wagtail.users"
    2. Add AUTH_USER_MODEL = "accounts.CustomUser" below INSTALLED_APPS
    3. Add DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' below AUTH_USER_MODEL
  4. Make database migrations: python manage.py makemigrations
    1. Note, this will recreate the db.sqlite3 file that we deleted earlier.
  5. Now migrate with: python manage.py migrate
  6. Create the custom user admin form here: /accounts/forms.py - then add the custom classes.
  7. Update configuration here: /accounts/apps.py
  8. Update custom user admin here: /accounts/admin.py
  9. Add more custom fields, for example Bio text: 
    1. Update /accounts/models.py with new bio_text TextField
    2. Update /accounts/forms.py with new bio_text field
    3. Update /accounts/admin.py with new bio_text fieldset
    4. Add /accounts/viewsets.py with ViewSet code.
    5. Extend the Wagtail Create and Edit templates.
      1. Create /accounts/templates/accounts/edit.html and /accounts/templates/accounts/create.html 
      2. Add extra_fields block for the new custom fields, like Country.
    6. Make database migrations: python manage.py makemigrations
    7. Run database migration: python manage.py migrate
    8. Run and test the local website again.


 Save Your Work! 
Add recent updates to your Git Repo: 
git add . then git commit -m "Custom User Model"


Custom Image Model
(Wagtail Docs)

  1. Create images app: python manage.py startapp images
  2. Update /images/models.py with custom classes.
  3. Add the images app to INSTALLED_APPS within /settings/base.py: "images"
  4. Then set the WAGTAILIMAGES_IMAGE_MODEL setting to point to it by placing the following code in your /settings/base.py configuration:
    WAGTAILIMAGES_IMAGE_MODEL = 'images.CustomImage'
  5. Make database migrations: python manage.py makemigrations
  6. Run database migration: python manage.py migrate
  7. Run and test the local website again, then  Save Your Work if successful.


Custom Document Model
(Wagtail Docs)

  1. Create documents app: python manage.py startapp documents
  2. Update /documents/models.py with custom classes.
  3. Add the images app to INSTALLED_APPS within /settings/base.py: "documents"
  4. Then set the WAGTAILDOCS_DOCUMENT_MODEL setting to point to it by placing the following code in your /settings/base.py configuration:
    WAGTAILDOCS_DOCUMENT_MODEL = 'documents.CustomDocument'
  5. Make database migrations: python manage.py makemigrations
  6. Make database migrations: python manage.py migrate
  7. Run and test the local website again, then  Save Your Work if successful.

 Code Snippets

/accounts/models.py

from django.db import models
from django.contrib.auth.models import AbstractUser
from wagtail.admin.panels import FieldPanel

class CustomUser(AbstractUser):
    country = models.CharField(verbose_name='country', max_length=50)

/settings/base.py

INSTALLED_APPS = [
    ...
    "accounts",
    "wagtail.users",
    ...
]

# Place below INSTALLED_APPS
AUTH_USER_MODEL = 'accounts.CustomUser'
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

/accounts/forms.py

from django import forms
from django.contrib.auth import get_user_model
from wagtail.users.forms import UserEditForm, UserCreationForm
from django.utils.translation import gettext_lazy as _

User = get_user_model()


class CustomUserEditForm(UserEditForm):
    country = forms.CharField(required=False, label=_("Country"))

    class Meta(UserEditForm.Meta):
        model = User
        fields = UserEditForm.Meta.fields | {'country'}


class CustomUserCreationForm(UserCreationForm):
    country = forms.CharField(required=False, label=_("Country"))

    class Meta(UserCreationForm.Meta):
        model = User
        fields = UserCreationForm.Meta.fields | {'country'}

/accounts/admin.py

from django.contrib import admin
from django.contrib.auth import get_user_model
from django.contrib.auth.admin import UserAdmin

from .forms import CustomUserCreationForm, CustomUserEditForm

CustomUser = get_user_model()


@admin.register(CustomUser)
class CustomUserAdmin(UserAdmin):
    add_form = CustomUserCreationForm
    form = CustomUserEditForm
    model = CustomUser
    list_display = ['username', 'email', 'is_active', 'is_staff', 'is_superuser', 'last_login']

    # To display custom fields in the admin form
    fieldsets = UserAdmin.fieldsets + (
        ('Custom User Info', {'fields': ('country',)}),
    )
    add_fieldsets = UserAdmin.add_fieldsets + (
        ('Custom User Info', {'fields': ('country',)}),
    )

/accounts/apps.py

from wagtail.users.apps import WagtailUsersAppConfig


class CustomUsersAppConfig(WagtailUsersAppConfig):
    user_viewset = "accounts.viewsets.UserViewSet"

/accounts/viewsets.py

from wagtail.users.views.users import UserViewSet as WagtailUserViewSet

from .forms import CustomUserCreationForm, CustomUserEditForm


class UserViewSet(WagtailUserViewSet):
    def get_form_class(self, for_update=False):
        if for_update:
            return CustomUserEditForm
        return CustomUserCreationForm

/accounts/templates/accounts/create.html

{% extends "wagtailusers/users/create.html" %}

{% block extra_fields %}
    
  • {% include "wagtailadmin/shared/field.html" with field=form.country %}
  • {% endblock extra_fields %}

    /accounts/templates/accounts/edit.html

    {% extends "wagtailusers/users/edit.html" %}
    
    {% block extra_fields %}
        
  • {% include "wagtailadmin/shared/field.html" with field=form.country %}
  • {% endblock extra_fields %}

    /images/models.py

    from django.db import models
    
    from wagtail.images.models import Image, AbstractImage, AbstractRendition
    
    
    class CustomImage(AbstractImage):
        caption = models.CharField(verbose_name="Caption", max_length=255, blank=True)
    
        admin_form_fields = Image.admin_form_fields + (
            'caption'
        )
    
    
    class CustomRendition(AbstractRendition):
        image = models.ForeignKey(CustomImage, on_delete=models.CASCADE, related_name='renditions')
    
        class Meta:
            unique_together = (
                ('image', 'filter_spec', 'focal_point_key'),
            )
    
    

    /documents/models.py

    from django.db import models
    
    from wagtail.documents.models import Document, AbstractDocument
    
    class CustomDocument(AbstractDocument):
        source = models.CharField(
            max_length=255,
            blank=True,
            null=True
        )
    
        admin_form_fields = Document.admin_form_fields + (
            'source',
        )
    
    

    Other Settings

    1. PIP
      1. pip install environs
      2. pip freeze > requirements.txt
    2. Settings
      1. Base.py
        1. Django Private Key
      2. dev.py
        1. Database
          1. Heroku postgres plan 
          2. DATABASE_URL Config Var is set under Settings
      3. production.py
        1. Django Settings
        2. “DATABASES”
          1. Dj_db_url package
        3. “ALLOWED_HOSTS”
        4. Debug True/False

    Next: Docker Configuration

     Docker Configuration

     What is a Dockerfile? It defines the instructions for creating a Docker image, specifying the environment, dependencies, and configuration needed to run an application.

    1. Open the Docker desktop app
    2. Open the PyCharm Terminal panel and run the docker ps command to ensure that Docker is running. Typically, you should see no processes running.
    3. Update the Dockerfile with:
      1. Match the local Python 3.12 virtual environment (venv) by updating line 2 with: 
        FROM python:3.12-slim
      2. Update line 22 to an updated package name with: 
        libmariadb-dev \
    4. Create the docker-compose.yml configuration file. (Django for Professionals, Page 39)
      1. This file tells Docker how to run the app, database, and other parts. Instead of starting each one by one, you can start everything at once and keep everything organized and working together correctly.
      2. Create a new file and name it docker-compose.yml then copy/paste in the provided snippet.
    5. Before first time run needs to build the docker image with: 
      docker compose build
    6. If the build runs without errors, run your website for the first time with: 
      docker compose up -d
    7. Test your website at http://127.0.0.1:8000
      1. Note: Why doesn't http://0.0.0.0:8000/ work? The server suggests `http://0.0.0.0:8000/` because `0.0.0.0` tells the server to listen on all interfaces inside the container. However, `0.0.0.0` is not a valid address for a browser to connect to; it’s just a placeholder. From your host machine, you should use `http://127.0.0.1:8000` or `http://localhost:8000` because that maps to the server through Docker's port forwarding.
    8. Migrate the database within the Docker container: 
      docker compose exec web python manage.py migrate
    9. Create a new Super User within the Docker container:
      docker compose exec web python manage.py createsuperuser
    10. Reload the website in your browser to ensure that it's still serving.
    11. Navigate to the Admin section of your website at http://127.0.0.1:8000/admin/ and log in using the super user credentials you just created.


     Save Your Work! 
    Add recent updates to your Git Repo: 
    git add . then git commit -m "Docker configuration"

    What is Docker?

    Docker is a platform that enables developers to automate the deployment of applications inside lightweight, portable containers that package code along with all its dependencies.

    Docker Website

     Code Snippets

    /docker-compose.yml

    services:
      web:
        build: .
        command: python manage.py runserver 0.0.0.0:8000
        volumes:
          - .:/app
        ports:
          - "8000:8000"
    
    

     Customize Wagtail Home Page

    Note: this can be done without adding a CSS framework.

    1. Locate and open [site-name]/home/templates/home/home_page.html
    2. Within the same directory (/home/), create a new html file named custom_page.html
    3. In home_page.html, locate the line that contains: {% include 'home/welcome_page.html' %} and update it to reflect the new file name with: {% include 'home/custom_page.html' %}
    4. If your website isn't already running, start it and then refresh your browser. You should see a blank page, awaiting your custom template and content!


     Upload an image to Wagtail

    1. Download the Tetra Prime logo (or use an image of your own)
    2. Navigate to the Admin section of your website at http://127.0.0.1:8000/admin/
    3. In the left hand navigation, click on the Images menu.
    4. At the top of the Images page, click on "Add an image"
    5. Select an image from your local computer to upload it.
    6. Once the upload is complete, you'll see a thumbnail of the image, plus fields for Title, Tags, and Caption; feel free to add content to these for testing.
    7. Next, add this image to your blank custom_page.html by pasting the code below into it. 
    8. Copy your uploaded image's URL by clicking on the Images menu in Wagtail Admin, then click the thumbnail of the desired image, and finally copy the URL from the link within the File section.
      1. Note: if you used the Tetra Prime logo image, then the code below should work as-is.


    <style>

      main img { display: block; margin: 2rem auto; max-width: 400px; }

    </style>

    <main>

      <img src="/media/images/TPC_Logo__--v1_--1K.original.png" loading="lazy">

    </main>

    </code>

     Right-click & download this image


    Example Wagtail home page:

     Adding a CSS Framework

     What is a CSS Framework? It's a library of CSS code that helps streamline web design by providing ready-to-use styles, layouts, and components, making it easier and faster to create responsive and consistent user interfaces.

    1. Download Bootstrap 5 (Download)
    2. Unzip the download into your project's static files directory, located in [site-name]/static/lib/ 
      1. Note: the lib/ directory has been manually created to organize third party libraries.
    3. Add the Bootstrap CSS and JS references to your [site-name]/templates/base.html file:
      Place above "{# Global stylesheets #}":
      <link href="{% static 'lib/bootstrap-5.3.3-dist/css/bootstrap.min.css' %}" rel="stylesheet">
      and,
      Place above "{# Global javascript #}":
      <script src="{% static 'lib/bootstrap-5.3.3-dist/js/bootstrap.bundle.min.js' %}"></script>
    4. Run and test the local website again, then  Save Your Work if successful.
    5. Continue to update your homepage template by continuing reading below.


     Customize the Home Page

    1. Start by selecting a template from the Bootstrap Examples page. In our example below, we'll use the Cover Template.
    2. While on the Bootstrap Example Template page, view the page's HTML source code by either typing Ctrl+U or right-click and choose View Page Source.
    3. From the source code, copy all of the HTML between the <body> tags and paste it below the existing HTML in your custom_page.html
    4. Next, copy/paste your previous <main> tag and its contents, over the <main> tag that exists in the Bootstrap HTML. Doing this will show the logo you uploaded to Wagtail rather than the example text from the template.
    5. Correct the missing CSS styles by copying the <style> tag block from the Bootstrap Example template's HTML source, then download the template's custom CSS file, in our case cover.css, to your [site-name]/static/css/ folder. (Hint: search for <!-- Custom styles for this template -->).
    6. Reference the template's custom CSS file from within your custom_page.html code:
      <link href="{% static 'css/cover.css' %}" rel="stylesheet">
      1. Note: You'll need to add the {% load static %} directive to the very top of custom_page.html
    7. Add the additional CSS classes from the <body> tag of the Bootstrap Template by opening your home_page.html and updating the {% body_class %} block like so:
      {% block body_class %}template-homepage d-flex h-100 text-center text-bg-dark{% endblock %}
    8. Additionally, update your <html> tag in the base.html file like so:
      <html lang="en" class="h-100" data-bs-theme="dark">
    9. Run and test the local website again, then  Save Your Work if successful.

    What is Bootstrap?

    A popular CSS framework that provides pre-designed components, grid layouts, and utilities to quickly create responsive and modern websites.

     Deploy your website to Heroku

    Coming Soon!

    Next, we'll deploy our Wagtail website to a cloud host like Heroku.


    Want to be notified of updates to this article?

    Sign-up Below

    What is Heroku?

    Heroku is a cloud platform that allows developers to build, run, and scale applications quickly by providing a fully managed environment for deploying apps without infrastructure management.

    Heroku Website


    Heroku Configuration

    1. Create heroku.yml
    2. Deploy to Heroku Container Service
    3. … “heroku login”
    4. … “heroku container:login”
    5. … “heroku container:push web -a [app name]
    6. … “heroku container:release web -a [app name]
    7. Check app logs on Heroku
    8. Django commands on Heroku CLI
    9. … “heroku run web python manage.py migrate -a [app name]”
    10. … “heroku run web python manage.py createsuperuser -a [app name]”

    Next: Your New Wagtail Website

    Join Our Community

    Get a brief notification when we improve this article or publish new ones.