I want to make File Upload a possible Wagtail Form field type on form pages. How do I configure the models to make this possible? Please note I am more interested in allowing users to upload document files like PDFs rather than images.
How can I add an Upload File field to a Wagtail Form?
Here is a great article from LB Johnson on that matter. The code below is directly taken from the article (but copying here in case the article goes away), however you might want to follow the article as it explains everything step by step.
It's quite involved at the moment, you'll need to:
- extend
AbstractFormField
to define a new field type. - extend
FormBuilder
to deal with the new field type. - set
form_builder
to your customFormBuilder
class on your FormPage . - override the
serve
method on the FormPage to pass the file data to the form (only if you use Wagtail 1.12 and below as it does it automatically from Wagtail 1.13) - override the
process_form_submission
to process the file
Here is the full code:
from wagtail.wagtailforms.models import AbstractFormField, FORM_FIELD_CHOICES
from wagtail.wagtailforms.forms import FormBuilder
from wagtail.wagtailimages.fields import WagtailImageField
def filename_to_title(filename):
from os.path import splitext
if filename:
result = splitext(filename)[0]
result = result.replace('-', ' ').replace('_', ' ')
return result.title()
class FormField(AbstractFormField):
FORM_FIELD_CHOICES = list(FORM_FIELD_CHOICES) + [('image', 'Upload Image')]
field_type = models.CharField(
verbose_name=_('field type'),
max_length=16,
choices=FORM_FIELD_CHOICES)
page = ParentalKey('FormPage', related_name='form_fields')
class ExtendedFormBuilder(FormBuilder):
def create_image_upload_field(self, field, options):
return WagtailImageField(**options)
FIELD_TYPES = FormBuilder.FIELD_TYPES
FIELD_TYPES.update({
'image': create_image_upload_field,
})
class FormPage(AbstractEmailForm):
form_builder = ExtendedFormBuilder
def serve(self, request, *args, **kwargs):
if request.method == 'POST':
# form = self.get_form(request.POST, page=self, user=request.user) # Original line
form = self.get_form(request.POST, request.FILES, page=self, user=request.user)
if form.is_valid():
self.process_form_submission(form)
return render(
request,
self.get_landing_page_template(request),
self.get_context(request)
)
else:
form = self.get_form(page=self, user=request.user)
context = self.get_context(request)
context['form'] = form
return render(
request,
self.get_template(request),
context
)
def process_form_submission(self, form):
cleaned_data = form.cleaned_data
for name, field in form.fields.iteritems():
if isinstance(field, WagtailImageField):
image_file_data = cleaned_data[name]
if image_file_data:
ImageModel = get_image_model()
image = ImageModel(
file=cleaned_data[name],
title=filename_to_title(cleaned_data[name].name),
collection=self.upload_image_to_collection,
# assumes there is always a user - will fail otherwise
uploaded_by_user=form.user,
)
image.save()
cleaned_data.update({name: image.id})
else:
# remove the value from the data
del cleaned_data[name]
form_data = json.dumps(cleaned_data, cls=DjangoJSONEncoder)
submission_object = dict(
page=self,
form_data=form_data,
user=form.user,
)
intro = RichTextField(blank=True)
thank_you_text = RichTextField(blank=True)
FormPage.content_panels = [
FieldPanel('title', classname="full title"),
FieldPanel('intro', classname="full"),
InlinePanel('form_fields', label="Form fields"),
FieldPanel('thank_you_text', classname="full"),
MultiFieldPanel([
FieldRowPanel([
FieldPanel('from_address', classname="col6"),
FieldPanel('to_address', classname="col6"),
]),
FieldPanel('subject'),
], "Email"),
]
Here is the (untested) code to handle a document instead of an image:
from django.forms import FileField
from wagtail.wagtaildocs.models import get_document_model
# Other imports
class FormField(AbstractFormField):
FORM_FIELD_CHOICES = list(FORM_FIELD_CHOICES) + [('document', 'Upload Document')]
# `field_type` and `page` remain unchanged
class ExtendedFormBuilder(FormBuilder):
def create_document_upload_field(self, field, options):
return FileField(**options)
FIELD_TYPES = FormBuilder.FIELD_TYPES
FIELD_TYPES.update({
'document': create_document_upload_field,
})
class FormPage(AbstractEmailForm):
# `form_builder` attribute and `serve` remain unchanged.
def process_form_submission(self, form):
cleaned_data = form.cleaned_data
for name, field in form.fields.iteritems():
if isinstance(field, FileField):
document_file_data = cleaned_data[name]
if document_file_data:
DocumentModel = get_document_model()
document = DocumentModel(
file=cleaned_data[name],
title=filename_to_title(cleaned_data[name].name),
# assumes there is always a user - will fail otherwise
uploaded_by_user=form.user,
)
document.save()
cleaned_data.update({name: document.id})
else:
# remove the value from the data
del cleaned_data[name]
# The rest of the function is unchanged
thank you! But what if I wanted to allow users to upload a PDF, so a document instead of an image? –
Catlee
Sorry I missed the part of the question where you ask for document instead of image. That being said, the steps are identical and I really encourage you to try to tweak the code above yourself and understand how it all works together. I've added an example if you get stuck, although I did not have time to test it. –
Cathcart
Cannot have FIELD_TYPES object in FormBuilder how can i solve this. Can you give me code for this. @LoïcTeixeira –
Constrictive
Hey, I have recently revised the original post about adding image uploads and it works on Wagtail 2.10 - dev.to/lb/image-uploads-in-wagtail-forms-39pl I hope this helps anyone getting to this question in > 2020. –
Airborne
Now there are some changes made by wagtail developer in FormBuilder So in Below given code is not working now
class ExtendedFormBuilder(FormBuilder):
def create_document_upload_field(self, field, options):
return FileField(**options)
FIELD_TYPES = FormBuilder.FIELD_TYPES
FIELD_TYPES.update({
'document': create_document_upload_field,
})
Instead of this they made
class ExtendedFormBuilder(FormBuilder):
def create_document_upload_field(self, field, options):
return FileField(**options)
FIELD_TYPES = FormBuilder.formfields
Wagtail Core developer changed FormBuilder.FIELD_TYPES property to FormBuilder.formfields
So i hope this is helpful for developer to let you know which changes happened recently.
Hey, I have recently updated my original post (referenced in the question above) and it works on Wagtail 2.10 - dev.to/lb/image-uploads-in-wagtail-forms-39pl –
Airborne
© 2022 - 2024 — McMap. All rights reserved.