Last active
February 8, 2021 14:32
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Bunk