20 Mar 2026
Django community aggregator: Community blog posts
How to Show a Waitlist Until Your Wagtail Site Is Ready

This year, I want to bring my centralized gamified donation platform www.make-impact.org to life (at least technically). Earlier I had the version I was developing separate from the waiting list, but I decided to merge them and have a switch between the waitlist and an early preview.
This allows me to have no data duplication, the possibility to create user accounts immediately, and saves hosting and maintenance costs.
This guide walks through a pattern that lets you ship a temporary waitlist page while your Wagtail site is still being built, with the ability to show your progress to chosen people. If you are building a Software as a Service (SaaS) or a web platform with Django, this article is for you.
The Concept
A custom start page view will check for a specific cookie value. If it is unset, the visitor will be redirected to a waitlist form at /waitlist/. If it is set, the visitor will be served the Wagtail home page.
All views under development will have a decorator that checks the cookie value and redirects to the start page if it is unset.
There will be a special view at /preview-access/ with a passphrase form that allows the visitor to gain preview access by setting the mentioned cookie. This view will also allow preview access to be deactivated.
These are the steps to implement this:
1. Generate and store two secrets
You will need two secret values, either set manually or generated with a cryptographically secure random generator (e.g. Python's secrets module):
PREVIEW_ACCESS_PASSPHRASE- the human-readable passphrase typed into the form. Share this with the people who need site access.PREVIEW_ACCESS_TOKEN- the opaque random value stored in the cookie. Never exposed to users; only the server compares against it.
>>> import secrets
>>> print(secrets.token_urlsafe(16)) # passphrase
dI5nGNftZOBx8m-r0m6glg
>>> print(secrets.token_hex(32)) # cookie token
c1b7a76e3ad5cbfb1657fa4e9885a3c8baa6a5a869f49a136abd0e873a9be9ee
Add both to environment variables or a secrets file untracked by Git, and load them in the Django project settings:
# myproject/settings/_base.py
PREVIEW_ACCESS_PASSPHRASE = get_secret("PREVIEW_ACCESS_PASSPHRASE")
PREVIEW_ACCESS_TOKEN = get_secret("PREVIEW_ACCESS_TOKEN")
The get_secret() here is my custom function to retrieve a secret from the secrets source.
2. Create the access-control decorator
Create myproject/apps/misc/decorators.py. Every protected view will import from here.
# myproject/apps/misc/decorators.py
from functools import wraps
from django.conf import settings
from django.shortcuts import redirect
def preview_access_required(view_func):
@wraps(view_func)
def wrapper(request, *args, **kwargs):
if request.COOKIES.get("preview_access") == settings.PREVIEW_ACCESS_TOKEN:
return view_func(request, *args, **kwargs)
return redirect("misc:home_page")
return wrapper
The decorator compares the cookie against the opaque unguessable token from settings, so unless the token value is known, a random attacker cannot gain access by setting the cookie manually in DevTools.
3. Create the passphrase form
Create myproject/apps/misc/forms.py. The form will have a single required password field. Validation will reject any value that does not match the setting.
# myproject/apps/misc/forms.py
from django import forms
from django.conf import settings
from django.utils.translation import gettext_lazy as _
class PreviewAccessForm(forms.Form):
passphrase = forms.CharField(
label=_("Passphrase"),
widget=forms.PasswordInput(
attrs={"autocomplete": "current-password"}
),
required=True,
)
def clean_passphrase(self):
value = self.cleaned_data["passphrase"]
if value != settings.PREVIEW_ACCESS_PASSPHRASE:
raise forms.ValidationError(
_("Incorrect passphrase.")
)
return value
4. Build the cookie toggle view
Point your browser to /preview-access/. When access is off it shows a passphrase form; when access is on it shows a disable button.
# myproject/apps/misc/views.py
from django.conf import settings
from django.shortcuts import redirect, render
from .forms import PreviewAccessForm
def preview_access(request):
has_access = request.COOKIES.get("preview_access") == settings.PREVIEW_ACCESS_TOKEN
if request.method == "POST":
if has_access:
response = redirect("misc:home_page")
response.delete_cookie("preview_access")
return response
form = PreviewAccessForm(request.POST)
if form.is_valid():
response = redirect("misc:home_page")
response.set_cookie(
"preview_access",
settings.PREVIEW_ACCESS_TOKEN,
httponly=True,
samesite="Strict",
)
return response
else:
form = PreviewAccessForm()
return render(
request,
"preview_access/preview_access.html",
{"has_access": has_access, "form": form}
)
Key points: - Disabling never requires the passphrase - the cookie is already proof of prior access. - The cookie is set with httponly=True (not readable by JavaScript) and samesite="Strict" (not sent on cross-site requests). - The cookie value is the opaque token, not "1", so it cannot be guessed.
The template renders the passphrase input only when not has_access, and shows field-level errors from the form if the passphrase is wrong.
5. Wrap the Wagtail catch-all with the decorator
Replace the default Wagtail catch-all route handler with a thin wrapper that enforces the same cookie check.
# myproject/apps/misc/views.py
from myproject.apps.misc.decorators import preview_access_required
from wagtail.views import serve as wagtail_serve
@preview_access_required
def serve_wagtail_page(request, path=""):
return wagtail_serve(request, path)
Without this, a visitor who knows any Wagtail page URL could bypass the gate by typing it directly into the browser.
6. Build the proxy home page view
This view is the only entry point to the site. It decides what every visitor sees first.
# myproject/apps/misc/views.py
from django.conf import settings
from wagtail.models import Site
from wagtail.views import serve as wagtail_serve
def home_page(request):
if request.COOKIES.get("preview_access") == settings.PREVIEW_ACCESS_TOKEN:
# serve Wagtail directly
site = Site.find_for_request(request)
return wagtail_serve(request, "")
# redirect to waiting list
return redirect("waiting_list")
Key point: the waiting_list view and a Wagtail Site and page must exist and be matched to the request domain before wagtail_serve is called.
7. Wire up the URLs
Django project URL rules:
# myproject/urls.py
from django.conf.urls.i18n import i18n_patterns
from django.urls import re_path
from wagtail.coreutils import WAGTAIL_APPEND_SLASH
from myproject.apps.misc import views as misc_views
if WAGTAIL_APPEND_SLASH:
wagtail_serve_pattern = r"^((?:[\w\-]+/)*)$"
else:
wagtail_serve_pattern = r"^([\w\-/]*)$"
urlpatterns += i18n_patterns(
# ... all your other app URLs above ...
# Catch-all - must be last
re_path(
wagtail_serve_pattern,
misc_views.serve_wagtail_page,
name="wagtail_serve"
),
)
The misc app URLs:
# myproject/apps/misc/urls.py
from django.urls import path
from . import views
app_name = "misc"
urlpatterns = [
path("", views.home_page, name="home_page"),
path("preview-access/", views.preview_access, name="preview_access"),
]
The waiting_list app URLs:
# myproject/apps/waiting_list/urls.py
from django.urls import path
from . import views
urlpatterns = [
path("waitlist/", views.show_waiting_list_form, name="waiting_list"),
]
8. Protect every other app view
Import and apply @preview_access_required to every view that belongs to the real site. Class-based views can be wrapped at assignment time:
from myproject.apps.misc.decorators import preview_access_required
# Function-based view
@preview_access_required
def event_list(request):
...
# Class-based view
event_list = preview_access_required(
EventListView.as_view()
)
Waiting-list views, API views, social authentication views, and static/legal pages (/imprint/, /privacy/, etc.) must not receive this decorator - they need to remain publicly accessible.
Final words
You get a lot of benefits from this setup. The waitlist measures demand for your website while you are still building. Invited test users can evaluate your progress at any time. While you are developing the website, you do not necessarily need multiple servers. Launching later is also easier - no hassle or delays with domain IP updates and SSL certificates.
20 Mar 2026 5:00pm GMT
Django News - Sunsetting Jazzband - Mar 20th 2026
News
Sunsetting Jazzband
After more than a decade maintaining 80+ Python projects, Jazzband is winding down as AI-generated spam and long-standing sustainability challenges make its open, shared-maintenance model no longer viable.
Astral to join OpenAI
Astral, creators of Ruff and uv, are joining OpenAI's Codex team to push the future of AI-powered Python development while continuing to support their open source tools.
Wagtail CMS News
Wagtail Security team no longer accepts GPG-encrypted emails
Wagtail's security team has dropped GPG-encrypted email support, citing zero real-world use and modern encryption making it unnecessary while simplifying their workflow.
Updates to Django
Today, "Updates to Django" is presented by Raffaella from Djangonaut Space! 🚀
Last week we had 18 pull requests merged into Django by 15 different contributors - including a first-time contributor! Congratulations to dcsid for having their first commits merged into Django - welcome on board!
The undocumented get_placeholder method of Field is deprecated in favor of the newly introduced get_placeholder_sql method, which has the same input signature but is expected to return a two-elements tuple composed of an SQL format string and a tuple of associated parameters. This method should now expect to be provided expressions meant to be compiled via the provided compiler argument.(#36727)
Django Newsletter
Sponsored Link 1
The deployment service for developers and teams.
Articles
DjangoCon US Talks I'd Like to See 2026 Edition
A curated wishlist of timely, thought-provoking DjangoCon US 2026 talk ideas, from Python's future and deployment wins to Rust, LLMs, and real-world team productivity.
Defense in Depth: A Practical Guide to Python Supply Chain Security
A practical, defense-in-depth guide to securing Python's supply chain, covering everything from linting and dependency pinning to SBOMs, vulnerability scanning, and trusted publishing.
Python Unplugged on PyTV Recap
A behind-the-scenes post on this first-ever digital Python conference that featured a host of Django speakers.
django-security-label: A third-party package to anonymize data in your models
Define data masking rules directly on your Django models and let PostgreSQL enforce anonymization automatically, keeping sensitive data out of your app layer by design.
Djangonaut Diaries: Week 1, part 2 - Creating and debugging a Django project - DEV Community
A hands-on guide to spinning up a local Django project, generating realistic test data, and using VS Code's debugger to step into Django internals and understand how admin delete views work.
Typing Your Django Project in 2026
Typing Django in 2026 is still a tradeoff between slower, accurate mypy + django-stubs and faster tools that struggle with Django's dynamic magic, though native typing support may finally be on the horizon.
Python 3.15's JIT is now back on track
Python 3.15's once-struggling JIT is finally delivering real speedups, thanks to a scrappy, community-driven effort and a few surprisingly lucky design bets.
Thoughts on OpenAI acquiring Astral and uv/ruff/ty
Simon Willison provides some timely insights on the recent acquisition making waves in our community.
OpenAI Acquiring Astral: A 4th Option for Funding Open Source
Thoughts on the three traditional ways to fund open source and the new fourth option (VC funding) currently makes waves.
Events
How DjangoCon US Selects Talk Proposals
A behind-the-scenes look at how DjangoCon US turns anonymous proposals and community reviews into a balanced, inclusive conference lineup.
PyCon US 2026 Conference Schedule is live!
PyCon US 2026's Conference Schedule is live!
Podcasts
Django Chat #198: PyCon US 2026 - Elaine Wong & Jon Banafato
Elaine and Jon are the chair/co-chair respectively of PyCon US, the largest Python conference in North America, happening this May in Long Beach, CA. We discuss what to expect at the conference, new additions from last year, tips on where to stay, and generally how to maximize your PyCon experience.
Django Job Board
Two standout Python roles this week include a client-facing Solutions Architect position at JetBrains and an Infrastructure Engineer opening at the Python Software Foundation.
Solutions Architect - Python (Client-facing) at JetBrains
Infrastructure Engineer at Python Software Foundation
Django Newsletter
Django Forum
Improve free-threading performance - Django Internals
A CPython core developer is proposing small but impactful changes to help Django scale better under free-threaded Python, sparking early collaboration on tackling shared state, caching, and lock contention issues.
Projects
codingjoe/django-mail-auth
Django authentication via login URLs, no passwords required.
duriantaco/skylos
Yet another static analysis tool for Python codebases written in Python that detects dead code.
This RSS feed is published on https://django-news.com/. You can also subscribe via email.
20 Mar 2026 3:00pm GMT
19 Mar 2026
Django community aggregator: Community blog posts
OpenAI Acquiring Astral: A 4th Option for Funding Open Source
Thoughts on the recent acquisition and what it portends for open source software.
19 Mar 2026 10:56am GMT

