Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Django MultipleFormMixin for displaying dynamic number of forms on the same page. Compatible with the standard FormMixin.
class ContactForm(forms.Form):
name = forms.CharField(max_length=60)
message = forms.CharField(max_length=200, widget=forms.TextInput)
class SubscriptionForm(forms.Form):
email = forms.EmailField()
want_spam = forms.BooleanField(required=False)
class SuggestionForm(forms.Form):
text = forms.CharField(max_length=200, widget=forms.TextInput)
type = forms.ChoiceField(choices=[('bug', 'Bug'), ('feature', 'Feature')])
class GlobalMessageForm(forms.Form):
staff_only = True
global_message = forms.CharField(max_length=200, widget=forms.TextInput)
import ./example_forms as forms
from multiple_forms import MultipleFormsView
class MultipleFormsDemoView(MultipleFormsView):
template_name = 'forms.html'
success_url = '/'
# here we specify all forms that should be displayed
forms_classes = [
forms.GlobalMessageForm,
forms.ContactForm,
forms.SubscriptionForm,
forms.SuggestionForm
]
def get_forms_classes(self):
# we hide staff_only forms from not-staff users
# example how to dynamically change forms
forms_classes = super(MultipleFormsDemoView, self).get_forms_classes()
user = self.request.user
if not user.is_authenticated() or not user.is_staff:
return list(filter(lambda form: not getattr(form, 'staff_only', False), forms_classes))
return forms_classes
def form_valid(self, form):
print("yay it's valid!")
return super(MultipleFormsDemoView).form_valid(form)
<html>
<head></head>
<body>
{% for form in forms %}
<form method="post">
{% csrf_token %}
{{ form }}
<input type="hidden" name="selected_form" value="{{ forloop.counter0 }}">
<button type="submit">Submit</button>
</form>
{% endfor %}
</body>
</html>
from django.core.exceptions import ImproperlyConfigured
from django.http import HttpResponseRedirect
from django.utils.encoding import force_text
from django.views.generic.base import ContextMixin, TemplateResponseMixin
from django.views.generic.edit import ProcessFormView
class MultipleFormsMixin(ContextMixin):
"""
A mixin that provides a way to show and handle multiple forms in a request.
It's almost fully-compatible with regular FormsMixin
"""
initial = {}
forms_classes = []
success_url = None
prefix = None
active_form_keyword = "selected_form"
def get_initial(self):
"""
Returns the initial data to use for forms on this view.
"""
return self.initial.copy()
def get_prefix(self):
"""
Returns the prefix to use for forms on this view
"""
return self.prefix
def get_forms_classes(self):
"""
Returns the forms classes to use in this view. Can be dynamic.
"""
return self.forms_classes
def get_active_form_number(self):
"""
Returns submitted form index in available forms list
"""
if self.request.method in ('POST', 'PUT'):
try:
return int(self.request.POST[self.active_form_keyword])
except (KeyError, ValueError):
raise ImproperlyConfigured(
"You must include hidden field with field index in every form! Example:")
def get_forms(self, active_form=None):
"""
Returns instances of the forms to be used in this view.
Includes provided `active_form` in forms list.
"""
all_forms_classes = self.get_forms_classes()
all_forms = [
form_class(**self.get_form_kwargs())
for form_class in all_forms_classes]
if active_form:
active_form_number = self.get_active_form_number()
all_forms[active_form_number] = active_form
return all_forms
def get_form(self):
"""
Returns active form. Works only on `POST` and `PUT`, otherwise returns None.
"""
active_form_number = self.get_active_form_number()
if active_form_number is not None:
all_forms_classes = self.get_forms_classes()
active_form_class = all_forms_classes[active_form_number]
return active_form_class(**self.get_form_kwargs(is_active=True))
def get_form_kwargs(self, is_active=False):
"""
Returns the keyword arguments for instantiating the form.
"""
kwargs = {
'initial': self.get_initial(),
'prefix': self.get_prefix(),
}
if is_active:
kwargs.update({
'data': self.request.POST,
'files': self.request.FILES,
})
return kwargs
def get_success_url(self):
"""
Returns the supplied success URL.
"""
if self.success_url:
# Forcing possible reverse_lazy evaluation
url = force_text(self.success_url)
else:
raise ImproperlyConfigured(
"No URL to redirect to. Provide a success_url.")
return url
def form_valid(self, form):
"""
If the form is valid, redirect to the supplied URL.
"""
return HttpResponseRedirect(self.get_success_url())
def form_invalid(self, form):
"""
If the form is invalid, re-render the context data with the
data-filled forms and errors.
"""
return self.render_to_response(self.get_context_data(active_form=form))
def get_context_data(self, **kwargs):
"""
Insert the forms into the context dict.
"""
if 'forms' not in kwargs:
kwargs['forms'] = self.get_forms(kwargs.get('active_form'))
return super(MultipleFormsMixin, self).get_context_data(**kwargs)
class MultipleFormsView(TemplateResponseMixin, MultipleFormsMixin, ProcessFormView):
pass
@Monte42
Copy link

Monte42 commented Jan 27, 2020

Bunk

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment