Adding a form builder app¶
The following example app uses form_designer to provide a forms builder integrated with the pages app described above. Apart from installing form_designer itself the following steps are necessary.
Extending the page model¶
Make the page model inherit PageTypeMixin
and LanguageMixin
and add
a TYPES
attribute to the class:
from feincms3.applications import PageTypeMixin
from feincms3.applications import ApplicationType, TemplateType
from feincms3.mixins import LanguageMixin
from feincms3.pages import AbstractPage
class Page(AbstractPage, AppsMixin, LanguageMixin, ...):
# ...
TYPES = [
TemplateType(
key="standard",
title="...",
regions=[Region(key="main", title="...")],
# Available as page.type.template_name
template_name="pages/standard.html",
),
ApplicationType(
key="forms",
title=_("forms"),
# Required: A module containing urlpatterns
urlconf="app.forms",
# If you want to add content, not just the form. Not required.
regions=[Region(key="main", title="...")],
# The "form" field on the page is required when
# selecting the forms app
required_fields=("form",),
# Only necessary if you want to add more than one form to the
# page tree (per language). Also helpful for finding a form's u
# URL using reverse_app(f"forms-{form.pk}", "form")
app_namespace=lambda page: f"{page.page_type}-{page.form_id}",
),
# ...
]
form = models.ForeignKey(
"form_designer.Form",
on_delete=models.SET_NULL,
blank=True, null=True,
verbose_name=_("form"),
)
Note
The LanguageMixin
is required, but if you have a site where
there’s only one language, you don’t even have to show the
language_code
field in your administration panel. Simply make
sure that the LANGUAGES
setting contains only the one language
and nothing else.
The application¶
Add the app/forms.py
module itself. Note that since control is
directly handed to the application view and no page view code runs
you’ll have to load the page instance yourself and do the necessary
language setup and provide the page etc. to the rendering context. The
best way to load the page instance responsible for the current app is by
calling feincms3.applications.page_for_app_request()
:
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.urls import path
from feincms3.applications import page_for_app_request
from app.pages.renderer import renderer
def form(request):
page = page_for_app_request(request)
page.activate_language(request)
context = {}
if "ok" not in request.GET:
form_class = page.form.form()
if request.method == "POST":
form = form_class(request.POST)
if form.is_valid():
# Discard return values from form processing.
page.form.process(form, request)
return HttpResponseRedirect("?ok")
else:
form = form_class()
context["form"] = form
context.update({
"page": page,
"page_regions": renderer.regions_from_item(
page,
inherit_from=page.ancestors().reverse(),
timeout=60,
)
})
return render(request, "form.html", context)
app_name = "forms"
urlpatterns = [
path("", form, name="form"),
]
Add the required template:
{% extends "base.html" %}
{% load feincms3 %}
{% block content %}
{% render_region page_regions 'main' %}
{% if form %}
<form method="post" action=".#form" id="form">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Submit</button>
</form>
{% else %}
<h1>Thank you!</h1>
{% endif %}
{% endblock %}
Of course if you’d rather add another URL for the “thank you” page
you’re free to add a second entry to the urlpatterns
list and
redirect to this URL instead.
Outlook¶
The example above shows how to add a contact form at the end of the rest of the content. However, it would be quite easy to e.g. add a placeholder plugin which content managers can use to place the form somewhere in-between. An outline how this might be done follows:
The plugin model definition:
class Placeholder(PagePlugin):
identifier = models.CharField(choices=[("form", "form")], ...)
The app:
def form(request):
page = ...
context = {}
if "ok" not in request.GET:
context.setdefault("placeholders", {})["form"] = form
context.update({
"page": page,
"page_regions": renderer.regions(
page, inherit_from=page.ancestors().reverse()),
})
return render(request, "form.html", context)
The rendering of the placeholder:
renderer.register_template_renderer(
models.Placeholder,
lambda plugin: "placeholder/{}.html".format(plugin.identifier),
lambda plugin, context: {
"plugin": plugin,
"placeholder": context["placeholders"].get(plugin.identifier),
},
)
The placeholder/form.html
template:
<form method="post" action=".#form" id="form">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Submit</button>
</form>
The rest of the steps is left as an exercise to the reader. The success message is missing, and missing is also what happens if the placeholder plugin hasn’t been added to the page.