Multiple forms in the same template

597 views
Skip to first unread message

scha...@gmail.com

unread,
Jan 23, 2017, 9:41:53 AM1/23/17
to Django users
HI All,
I'm new in Django and in Web-Development. Therefore I'm a little confused now.
I have taken over a Django project and first I want to upgrade it to the actual version.
Now I was doing the migration to Django 1.9.12 and struggling with the following problem:


Using ModelFormMixin (base class of MFormsView) without the 'fields' attribute is prohibited.

My views.py is having a class which is defined like:
class MFormsView(LoginRequiredMixin, MFView):
    template_name
= 'temps/cnfg_form.html'
    form_classes
= {
       
'abc': abcForm,
       
'md': mdForm,
   
}
...
...


There is a separate module mfview.py which contains a class MFView:
class MFView(ProcessFormView):
   
"""
    A mixin that processes multiple forms on POST. Every form must be
    valid.
    """

   
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))
...
...

The forms passed to the get_context_data contains the 'abc': abcForm and the 'md': mdForm.
Form both ModelForms abcForm and mdForm the fields are defined.

The template uses the forms and fields like:
<div class="col-md-5">{{ forms.abc.days }}</div>
or (in the same template)
<div class="col-md-13 checkbox col-sm-pad">{{ forms.md.started }}</div>

This worked well like that until Django 1.8, but now with Django 1.9 the following error occurs:
Using ModelFormMixin (base class of MFormsView) without the 'fields' attribute is prohibited.

Now my question:
1.) Why do I get this message, as the fields are defined in both ModelForms?
2.) What is the best way to include 2 different forms into the same template? Do you have an example?

Thanks
schaf

Melvyn Sopacua

unread,
Jan 23, 2017, 10:46:15 AM1/23/17
to django...@googlegroups.com

On Monday 23 January 2017 06:41:53 scha...@gmail.com wrote:

 

> This worked well like that until Django 1.8, but now with Django 1.9

 

This stopped working in Django 1.8 :

"Class-based views that use ModelFormMixin will raise an ImproperlyConfigured exception when both the fields and form_class attributes are specified. Previously, fields was silently ignored."

 

And burried in the docs, a different case for the same thing:

In older versions, omitting both fields and exclude resulted in a form with all the model’s fields. Doing this now raises an ImproperlyConfigured exception.

 

For someone who didn't follow the discussion on the topic, it sure is hard to find in release notes.

 

> the

> following error occurs:

> > Using ModelFormMixin (base class of MFormsView) without the 'fields'

> > attribute is prohibited.

> Now my question:

> 1.) Why do I get this message, as the fields are defined in both

> ModelForms?

 

The model form is missing a 'fields' attribute. Example:

class ComponentStockAllocationAssign(StockMixin, generic.CreateView):
model = models.ComponentLocationStock
fields = ['component', 'location', 'stock']
success_url = reverse_lazy("component_allocation_list")

 

> 2.) What is the best way to include 2 different forms

> into the same template? Do you have an example?

 

That's a much bigger question than it seems. What's the relation between the forms?

If they're unrelated, implement get_context_data() to assign them, and form_valid() to process them.

Also, bookmark this: https://ccbv.co.uk/

--

Melvyn Sopacua

scha...@gmail.com

unread,
Jan 23, 2017, 11:14:10 AM1/23/17
to Django users
Hi!
And thanks for the answer.


 

This stopped working in Django 1.8 :

"Class-based views that use ModelFormMixin will raise an ImproperlyConfigured exception when both the fields and form_class attributes are specified. Previously, fields was silently ignored."


But there is no form_class defined. There is a form_classes dictionary, but this is not a form_class attribute. Form this point of view it should work.
But it seems that it is not able to get the fields of those ModelForms (fields are defined for both ModelForms), if this dictionary with both forms is passed to the get_context_data.
Is it allowed to pass such a dictionary, or which prerequisites have to be fulfilled?



In older versions, omitting both fields and exclude resulted in a form with all the model’s fields. Doing this now raises an ImproperlyConfigured exception.
This was really the case here, but I already removed all include and exculde and exchanged them with a corresponding fields attribute.

I guess I first need to fully understand the Django ModelForm and how a template can handle two independent forms. Maybe I also have to change the concept?
data from different models have to be displayed on one template and for that 2 ModelForms were generated and added to a main view, but this does notwork in 1.9 till now.

Thanks for any hints.
schaf

Melvyn Sopacua

unread,
Jan 24, 2017, 6:12:50 AM1/24/17
to django...@googlegroups.com

Hello,

 

Assuming the fields issue is resolved. If not, please show the abcForm.

 

On Monday 23 January 2017 08:14:09 scha...@gmail.com wrote:

 

> I guess I first need to fully understand the Django ModelForm and how

> a template can handle two independent forms. Maybe I also have to

> change the concept?

> data from different models have to be displayed on one template and

> for that 2 ModelForms were generated and added to a main view, but

> this does notwork in 1.9 till now.

 

The generic class-based views support common patterns, but in a way that more complex patterns can be built. Yours is a more complex pattern.

The defaults always assume a one on one relation between a ListView/DetailView and a Model and also one Form with one FormView.

 

Under the hood, Model forms are handled like this:

* add the form instance to the template context (using get_context_data) so it can be rendered

* deligate to form_valid or form_invalid based on the form's is_valid() method, if the request method is put or post (using ProcessFormView post and put)

* call save() on the ModelForm to save the object(s) (using form_valid)

 

What I'm not seeing in your code is how ModelFormMixin ties in. I'm going to guess that ProcessFormView is not the ProcessFormView from django.views.generic.edit.

Could you run the django shell (python manage.py shell) import the view and show it's MRO? For example:

>>> from kernel.views import ModelJSONEncoder

>>> ModelJSONEncoder.__mro__

(<class 'kernel.views.ModelJSONEncoder'>, <class 'django.core.serializers.json.DjangoJSONEncoder'>, <class 'json.encoder.JSONEncoder'>, <class 'object'>)

 

--

Melvyn Sopacua

Message has been deleted

scha...@gmail.com

unread,
Jan 24, 2017, 8:07:26 AM1/24/17
to Django users
HI
sorry I wanted to shorten the code before, to not post too much.
Here the code:

views.py
class MFormsView(LoginRequiredMixin, StaffuserRequiredMixin, UpdateView, MFView):

    template_name
= 'temps/cnfg_form.html'
    form_classes
= {
       
'abc': abcForm,
       
'md': mdForm,
   
}

    breadcrumbs
= ['cnfg_ovw']

   
def get_initial(self):
       
return {'pk': self.kwargs['pk']}

   
def get_success_url(self):
       
return reverse('cnfg_ovw')

   
def post(self, request, *args, **kwargs):
       
return super(MFormsView, self).post(request, *args, **kwargs)


   
def get(self, request, *args, **kwargs):

       
return super(MFormsView, self).get(request, *args, **kwargs)

   
def forms_valid(self, form):
       
if 'save' in self.request.POST.keys():
            change_settings
(form=form, **self.kwargs)
           
return HttpResponseRedirect(self.get_success_url())

       
elif 'reset_to_default' in self.request.POST.keys():
            reset_to_default
(form=form, **self.kwargs)
           
return HttpResponseRedirect(reverse('updt_cnfg', kwargs={'pk': self.kwargs['pk']}))

   
def get_object(self, queryset=None):
       
self.object = ConfigSetting.objects.all().filter(config__settings__pk=self.kwargs['pk']).get()
       
return self.object

   
def get_queryset(self):
       
self.queryset = ConfigSetting.objects.all().filter(config__settings__pk=self.kwargs['pk'])
       
return self.queryset

   
def get_context_data(self, **kwargs):
        context
= super(MFormsView, self).get_context_data(**kwargs)
       
if self.object.label not in ['default']:
            context
['label'] = "custom config"
       
else:
            context
['label'] = self.object.label

       
return context


mfview
.py
from django.views.generic.base import View, TemplateResponseMixin
from django.views.generic.edit import FormMixin, ProcessFormView


class MFMixin(FormMixin):
    form_classes
= {}

   
def get_form_classes(self):
       
return self.form_classes

   
def get_forms(self, form_classes):
       
return dict([(key, klass(**self.get_form_kwargs())) \
           
for key, klass in form_classes.items()])

   
def forms_valid(self, forms):
       
return super(MFMixin, self).form_valid(forms)

   
def forms_invalid(self, forms):
       
return self.render_to_response(self.get_context_data(forms=forms))


class ProcesMFView(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()
        forms
= self.get_forms(form_classes)

       
if all([form.is_valid() for form in forms.values()]):
           
return self.forms_valid(forms)
       
else:
           
return self.forms_invalid(forms)

class BaseMFView(MFMixin, ProcesMFView):

class MFView(TemplateResponseMixin, BaseMFView):



forms
.py
class abcForm(ModelForm):
   
def __init__(self, *arg, **kwargs):
       
super(abcForm, self).__init__(*arg, **kwargs)
        c
= model_to_dict(self.instance.abc_setting)
       
for k, v in self.fields.items():
            setattr
(v, 'initial', c[k])
           
           
if k in ['c']:
                setattr
(v, 'label', "Active settings")
           
       
self.prefix = "abc"

   
class Meta:
        model
= abcSetting
        fields
= ['a', 'b', 'c', 'f',]



class mdForm(ModelForm):
   
def __init__(self, *arg, **kwargs):
       
super(mdForm, self).__init__(*arg, **kwargs)
        c
= model_to_dict(self.instance.md_setting)
       
for k, v in self.fields.items():
            setattr
(v, 'initial', c[k])
           
       
self.prefix = "md"

   
class Meta:
        model
= mdSetting
        fields
= ['k', 'r',]


In the template it is then used like:
<div class="col-md-3">{{ forms.abc.f }}</div>

As you can see in the ProcesMFView.get, the get_context_data gets the dictionary form_classes:

form_classes = {
        'abc': abcForm,
        'md': mdForm,
    }

It seems that the Django code is then not able to get the fields and runs into this error.


Thanks for any hints.
schaf


Melvyn Sopacua

unread,
Jan 25, 2017, 6:19:55 AM1/25/17
to django...@googlegroups.com

On Tuesday 24 January 2017 05:07:25 scha...@gmail.com wrote:

> sorry I wanted to shorten the code before, to not post too much.

> Here the code:

>

> views.py

> class MFormsView(LoginRequiredMixin, StaffuserRequiredMixin,

> UpdateView, MFView):

 

And there you have it:

MFormsView uses UpdateView which pulls in ModelFormMixin. So this view needs a fields attribute:

 

class M MFormsView(LoginRequiredMixin, StaffuserRequiredMixin, UpdateView, MFView):

fields = ['foo', 'bar', 'baz']

 

 

--

Melvyn Sopacua

scha...@gmail.com

unread,
Jan 26, 2017, 8:54:35 AM1/26/17
to Django users
Ohh thanks that helped a lot.
Now in one view I still have a problem.
It uses the a formset and tries to give that back while overwriting the get_form function:

views.py
class LayoutFormView(LoginRequiredMixin, StaffuserRequiredMixin, UpdateView):
    template_name
= 'abc/layout.html'
    form_class
= LayoutFormSet
    breadcrumbs
= ['config_list']
   
   
def form_valid(self, form):
       
if 'save' in self.request.POST.keys():
            layout_change_settings
(form=form, **kwargs)

           
return HttpResponseRedirect(self.get_success_url())
           
       
elif 'reset_to_default' in self.request.POST.keys():

            layout_reset_to_default
(form=form, **kwargs)
           
return HttpResponseRedirect(reverse('update_layout', kwargs={'pk': self.kwargs['pk']}))

   
def get_object(self, queryset=None):
       
return LayoutSet.objects.get(settings__pk=self.kwargs['pk'])
       
   
def get_context_data(self, **kwargs):
        context
= super(LayoutFormView, self).get_context_data(**kwargs)

       
       
if self.object.label not in ['default']:
            context
['label'] = "custom"

       
else:
            context
['label'] = self.object.label

       
return
context
   
   
def get_success_url(self):
       
return reverse('update_layout', kwargs={'pk': self.kwargs['pk']})
   
   
def get_form(self, form_class=None):
       
if self.request.POST:
           
return form_class(self.request.POST)
       
else:
            initial
= [{'param': 'abc',
                       
'choosen': 'None'}]
           
return form_class(initial=initial)

forms.py
class LayoutForm(forms.Form):
   
def __init__(self, *args, **kwargs):
       
super(LayoutForm, self).__init__(*args, **kwargs)
   
    param
= forms.CharField()
    choosen
= forms.BooleanField()
   
   
class Meta:
        fields
= ['choosen']
   
   
def is_valid(self):
       
return True

LayoutFormSet = formset_factory(LayoutForm, extra=0)


According to my understanding this does not work, because a formset is assigned to the form_class attribute.
It is not possible to determine the form in the base get_form, because a formset is assigned.

I would add the formset in the context and then use it in the template. I still seem to have a problem after saving, but I guess this is releated with the improper use of the formset.

Thanks for hints.
schaf
Reply all
Reply to author
Forward
0 new messages