-
-
Save jamesbrobb/748c47f46b9bd224b07f to your computer and use it in GitHub Desktop.
class MultiFormMixin(ContextMixin): | |
form_classes = {} | |
prefixes = {} | |
success_urls = {} | |
grouped_forms = {} | |
initial = {} | |
prefix = None | |
success_url = None | |
def get_form_classes(self): | |
return self.form_classes | |
def get_forms(self, form_classes, form_names=None, bind_all=False): | |
return dict([(key, self._create_form(key, klass, (form_names and key in form_names) or bind_all)) \ | |
for key, klass in form_classes.items()]) | |
def get_form_kwargs(self, form_name, bind_form=False): | |
kwargs = {} | |
kwargs.update({'initial':self.get_initial(form_name)}) | |
kwargs.update({'prefix':self.get_prefix(form_name)}) | |
if bind_form: | |
kwargs.update(self._bind_form_data()) | |
return kwargs | |
def forms_valid(self, forms, form_name): | |
form_valid_method = '%s_form_valid' % form_name | |
if hasattr(self, form_valid_method): | |
return getattr(self, form_valid_method)(forms[form_name]) | |
else: | |
return HttpResponseRedirect(self.get_success_url(form_name)) | |
def forms_invalid(self, forms): | |
return self.render_to_response(self.get_context_data(forms=forms)) | |
def get_initial(self, form_name): | |
initial_method = 'get_%s_initial' % form_name | |
if hasattr(self, initial_method): | |
return getattr(self, initial_method)() | |
else: | |
return self.initial.copy() | |
def get_prefix(self, form_name): | |
return self.prefixes.get(form_name, self.prefix) | |
def get_success_url(self, form_name=None): | |
return self.success_urls.get(form_name, self.success_url) | |
def _create_form(self, form_name, klass, bind_form): | |
form_kwargs = self.get_form_kwargs(form_name, bind_form) | |
form_create_method = 'create_%s_form' % form_name | |
if hasattr(self, form_create_method): | |
form = getattr(self, form_create_method)(**form_kwargs) | |
else: | |
form = klass(**form_kwargs) | |
return form | |
def _bind_form_data(self): | |
if self.request.method in ('POST', 'PUT'): | |
return{'data': self.request.POST, | |
'files': self.request.FILES,} | |
return {} | |
class ProcessMultipleFormsView(ProcessFormView): | |
def get(self, request, *args, **kwargs): | |
form_classes = self.get_form_classes() | |
forms = self.get_forms(form_classes) | |
return self.render_to_response(self.get_context_data(forms=forms)) | |
def post(self, request, *args, **kwargs): | |
form_classes = self.get_form_classes() | |
form_name = request.POST.get('action') | |
if self._individual_exists(form_name): | |
return self._process_individual_form(form_name, form_classes) | |
elif self._group_exists(form_name): | |
return self._process_grouped_forms(form_name, form_classes) | |
else: | |
return self._process_all_forms(form_classes) | |
def _individual_exists(self, form_name): | |
return form_name in self.form_classes | |
def _group_exists(self, group_name): | |
return group_name in self.grouped_forms | |
def _process_individual_form(self, form_name, form_classes): | |
forms = self.get_forms(form_classes, (form_name,)) | |
form = forms.get(form_name) | |
if not form: | |
return HttpResponseForbidden() | |
elif form.is_valid(): | |
return self.forms_valid(forms, form_name) | |
else: | |
return self.forms_invalid(forms) | |
def _process_grouped_forms(self, group_name, form_classes): | |
form_names = self.grouped_forms[group_name] | |
forms = self.get_forms(form_classes, form_names) | |
if all([forms.get(form_name).is_valid() for form_name in form_names.values()]): | |
return self.forms_valid(forms) | |
else: | |
return self.forms_invalid(forms) | |
def _process_all_forms(self, form_classes): | |
forms = self.get_forms(form_classes, None, True) | |
if all([form.is_valid() for form in forms.values()]): | |
return self.forms_valid(forms) | |
else: | |
return self.forms_invalid(forms) | |
class BaseMultipleFormsView(MultiFormMixin, ProcessMultipleFormsView): | |
""" | |
A base view for displaying several forms. | |
""" | |
class MultiFormsView(TemplateResponseMixin, BaseMultipleFormsView): | |
""" | |
A view for displaying several forms, and rendering a template response. | |
""" |
Epic! Will give it a try soon. Facing a similar challenge and at the end, with your solution, I'm sure I'm gonna dance. Thanks
Hello,
Please how do l add "request" to the method def get_form_kwargs(self, form_name, bind_form=False): ?
Thanks and hope to hear from you
missing imports
from django.views.generic.base import ContextMixin, TemplateResponseMixin
from django.views.generic.edit import ProcessFormView
and:
from django.http.response import HttpResponseRedirect
and this:
from django.http import HttpResponseForbidden
all togehter for easy copy-pasting:
from django.views.generic.base import ContextMixin, TemplateResponseMixin
from django.views.generic.edit import ProcessFormView
from django.http.response import HttpResponseRedirect, HttpResponseForbidden
Haha, thanks @Mohl
What is the purpose of get_initial? I see that in the example you have get_login_initial and get_signup_initial return emails. What is the emails used for?
bro this is great and working out of the box
THANKS
so, I am working with 10 forms :) I have a wizard and user can skip/rewind/recreate_submit_new form, why ?? because this is what i need.
again so far working out of the box with just a little modification
such as
from django.template.defaulttags import register
@register.filter
def get_item(dictionary, key):
return dictionary.get(key)
def get_context_data(self, **kwargs):
context = super(SignupLoginView, self).get_context_data(**kwargs)
context['steps_numbering'] = {'foo1': 1, 'foo2': 2, 'foo3': 3, 'foo4': 4, 'foo5': 5,
'foo6': 6, 'foo7': 7, 'foo8': 8, 'foo9': 9, 'foo10': 10}
context['all_steps'] = ['category', 'vehicle', 'model', 'sub', 'year',
'revision', 'image', 'part', 'filter', 'stock']
return context
plus some extras
{% for i in all_steps %}
{{ forms|get_item:steps|crispy }}
{% endfor %}
threre is Html to make the wizard how ever its still work in progress
what I would love is
def login_form_valid(self, form):
to some how make a generic abstract so I dont need 10 times repeated lines
if you have any suggestion .....
Hi jamesrobb. Could you provide an example usage of multiple forms being saved together with one submit button?
I have tried to figure it out by looking at the code, but couldn't make it work. It would be awesome to have that example.
If you put both of the forms under the same submit button this will work. I had to modify his code a bit, I'll post my forked gist tonight or tomorrow.
How do you deal with individual form validation in this case @jamesbrobb
I have copied this gist into my project but it throws an error when running the line 'form_kwargs = self.get_form_kwargs(form_name, bind_form)'
The error message s
form_kwargs = self.get_form_kwargs(form_name, bind_form)
TypeError: get_form_kwargs() takes 1 positional argument but 3 were given
This isn't something I have modified and the values of form_name and bind_form make sense.
Is this an issue with the version of Django / pythin given the last comment on here is about 22 months old?
Any such luck?
Please @jamesbrobb could you show to override the save() method and an example for the post method?
How to override the post() method in the view.py to actually save an object to the database
I tried this but it's not working
class OrderFormView(LoginRequiredMixin, SuccessMessageMixin, MultiFormsView):
template_name = '_order.html'
form_classes = {'cart': CartForm,
'order': OrderForm
}
queryset = Cart.objects.all()
success_url = reverse_lazy('make_payment')
def get_cart_initial(self):
user = self.request.user.get_full_name
return {'user':user}
# return {'email':'[email protected]', 'user':user,}
def get_order_initial(self):
return {'email':'[email protected]'}
def get_context_data(self, **kwargs):
context = super(OrderFormView, self).get_context_data(**kwargs)
context.update({"menu":Menu.objects.all(), "cart": self.queryset})
return context
def post(self, request):
form = OrderForm(request.POST)
if form.is_valid():
form.save()
return redirect('make_payment')
return render(request, self.template_name, context={'form':form,})
def cart_form_valid(self, form):
form.save(self.request)
# cart.menu = self.request.POST['menu']
# cart.qty = self.request.POST['qty']
# cart.price = Menu.objects.filter(id(self.request.POST['pk'])).price
# return redirect('order_view')
# return form(self.request, cart, redirect_url=self.get_success_url())
def order_form_valid(self, form):
order = form.save(self.request)
return redirect('make_payment')
# return form.signup(self.request, order, self.get_success_url())
Thank you so much for this!! But can someone help me with the "grouping" part, cause i'm not able to send like 2 forms, only one or all
@caiopaje could you add a screenshot of your view?
From the error, you didn't exposed the field "atividade" field from your forms or you don't have such field in your model "NovaAtividade"
Hi guys, could you help me with a trial to use your approach for my needs?
https://stackoverflow.com/questions/76357340/cannot-proceed-with-multiform-view-in-class-based-views
example usage here
http://stackoverflow.com/questions/15497693/django-can-class-based-views-accept-two-forms-at-a-time/24011448#24011448