Skip to content

Instantly share code, notes, and snippets.

@Valian
Last active February 8, 2021 14:32
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Valian/1fbca0783df7149a328877fef1013954 to your computer and use it in GitHub Desktop.
Save Valian/1fbca0783df7149a328877fef1013954 to your computer and use it in GitHub Desktop.
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