How would i go about prepopulating a field in the Wagtail Admin UI with HTTP GET parameters? I have seen some solutions using the deprecated contrib.ModelAdmin, but have not really found something using the new ModelViewSet.
My simplified use case would be a simple calender in the Admin UI (using some javascript calender like fullcalandar.js) where i would create a new Event by dragging a timeframe and having the browser visit an url like /admin/event/new?start=startdate&end=enddate showing the Event Form with the start and end fields being prepoulated by the timeframe.
I have the following model
class Event(models.Model):
title = models.CharField(max_length=255)
[...]
class EventOccurrence(Orderable):
event = ParentalKey(Event, on_delete=models.CASCADE, related_name='event_occurrence')
start = DateTimeField()
end = DateTimeField()
So far i have tried to use an Custom Form Class inherting from WagtailAdminModelForm, which works nicely for the prepopulating, but i have no way to access the request object to fetch the GET paramters.
Helpful AIs would like me to use the deprecated ModelAdmin or inject some javascript to prepopulate the fields on the frontend. My personal hail mary would be to create the event via an API and the just refer the user to the freshly created event, but i would like to avoid that :) I found some references to a CreatePageView, but this does not seem to exist anymore in modern Wagtail.
I have the slight suspicion that there is a more straightforward solution. Something like creating my own route and just shadowing and overwriting some basis class and methods. But my wagtail/django-fu is definitely not strong enough to figure out where to start).
Maybe somebody can at least point me in the right direction? Or maybe even has an idea, how i could use a ChooserWidget for the same task? Or at least a modal embedding the form with the prepopulated fields.
You get access to the request
instance in FieldPanel.BoundPanel
. You can use this to set the initial value if there is no value already on the field.
This works for most widget cases, but some of the Wagtail ones need something extra to set the initial value (including the AdminDateTimeInput
widget). You can do this in the render_html()
method, though this does feel a little hacky, but there is zero in the docs on how these widgets work and the code comments don't provide any info either. I have used this for other cases where I needed to overcome the same obstacle.
Maybe there is a better way for this, but this works at least:
from bs4 import BeautifulSoup
from dateutil.parser import parse
from django.forms.fields import CharField, DateTimeField
from django.utils import timezone
from django.utils.safestring import mark_safe
from wagtail.admin.panels import FieldPanel
from wagtail.admin.widgets import AdminDateTimeInput
class PrepopulatePanel(FieldPanel):
"""
Prepopulate form fields in the Wagtail admin interface based on URL parameters.
Attributes:
url_parameter (str): The URL parameter used to prepopulate the field. Defaults to the field name if not provided.
Inner Classes:
BoundPanel:
Handles the logic for prepopulating the field value and rendering the HTML with the initial value.
Methods:
__init__(**kwargs):
Initializes the BoundPanel and sets the initial value of the field based on the URL parameter.
render_html(parent_context):
Renders the HTML for the panel, ensuring that the initial value is correctly set in the widget.
"""
def __init__(
self, field_name, url_parameter=None, *args, **kwargs
):
# Set the url_parameter to the field_name if not provided
self.url_parameter = url_parameter or field_name
super().__init__(field_name, *args, **kwargs)
def clone_kwargs(self):
super_kwargs = super().clone_kwargs()
super_kwargs['url_parameter'] = self.url_parameter
return super_kwargs
class BoundPanel(FieldPanel.BoundPanel):
def __init__(self, **kwargs):
super().__init__(**kwargs)
if not self.bound_field.value():
value = self.request.GET.get(self.panel.url_parameter)
if value:
match self.bound_field.field:
case DateTimeField():
try:
init_value = parse(value)
# convert value to db timezone if tz component supplied
if timezone.is_aware(init_value):
init_value = timezone.localtime(init_value)
self.bound_field.field.initial = init_value
except ValueError:
value = None
case CharField():
# If the field is a CharField, we can set the initial value directly
self.bound_field.field.initial = value
# add other cases as needed
@mark_safe
def render_html(self, parent_context):
# Wagtail widgets are not always rendered with the initial value - fix it here
html = super().render_html(parent_context)
if not self.bound_field.value() and self.bound_field.field.initial:
match self.bound_field.field.widget:
case AdminDateTimeInput():
try:
parsed = self.bound_field.field.initial.strftime(self.bound_field.field.widget.format)
soup = BeautifulSoup(html, "html.parser")
input_element = soup.find("input")
if input_element:
input_element["value"] = parsed
return str(soup)
except (ValueError, TypeError):
pass
# add other cases as needed
return html
CharField
is in there to demo how it works for straightforward cases - no extra rendering needed.
Replace FieldPanel
in your content_panels
declaration with
PrepopulatePanel('start_date'),
or, if the query string parameter differs from the field name
PrepopulatePanel('start_date', 'some_parameter'),
Example url: http://localhost:8000/admin/pages/31/edit/?start_date=2025-12-30T093000%2B1200
This pulls in 30-DEC-2025 0930 UTC+12 (NZDT). The db (in the example) is set to UTC so the input is converted (29-DEC-2025 2030). The widget automatically converts that to the time zone setting according to the editor's user profile then back again on save.