For some reasons I had to create a form class in Django by using the type()
function.
The return statement of my function generator (makeFurnitureForm()
) looked like this :
return (type("FurnitureForm",
(forms.BaseForm, form_utils.forms.BetterBaseForm,),
{"_fieldsets" : dic, "base_fields" : fields,
"_fieldset_collection" : None, '_row_attrs' : {}}))
The class object I create inherits from two other classes. Here is the constructor of one of them : form_utils.forms.BetterBaseForm
def __init__(self, *args, **kwargs):
self._fieldsets = deepcopy(self.base_fieldsets)
self._row_attrs = deepcopy(self.base_row_attrs)
self._fieldset_collection = None
super(BetterBaseForm, self).__init__(*args, **kwargs)
Everything works fine with my code, but on a previous version, I used a code like this :
return (type("FurnitureForm",
(forms.BaseForm, form_utils.forms.BetterBaseForm,),
{"base_fieldsets" : dic, "base_fields" : fields}))
Since _fieldsets
and _fieldset_collection
are explicitly defined and set in the BetterBaseForm
constructor by deepcopying base_fieldsets
attribute and assigning None
value, I didn't understand why I got AttributeError
s when trying to access _fieldsets
and _fieldset_collection
attribute of an instance (when rendering my django page for instance).
Doesn't type()
calls base classes` constructors, and don't these constructors define and set attributes properly?
Why did I have to explicitly set these attributes by myself?
Why didn't I got an AttributeError
when the constructor tries to
access an attribute (row_attrs
for instance) during the type()
call? (I got one when trying to access it later though).
I use my makeFurnitureForm()
like this in my django view :
return (render(request,'main/devis-bis.html', {"form" : (makeFurnitureForm())()}))
The class object is directly passed to a view, and then to a template to be rendered.
Example with this return :
return (type("FurnitureForm",
(forms.BaseForm, form_utils.forms.BetterBaseForm,),
{"base_fieldsets" : dic, "base_fields" : fields,
"_fieldset_collection" : None, '_row_attrs' : {}}))
I also found out I directly pass a class object to the render() function, I don't understand why it even works because it expects an instance. I corrected it in the render()
call.
File "/nfs/2013/v/vmonteco/Envs/demenhouse/lib/python3.5/site-packages/django/forms/forms.py" in __getitem__
141. field = self.fields[name]
During handling of the above exception ('fieldsets'), another exception occurred:
File "/nfs/2013/v/vmonteco/Envs/demenhouse/lib/python3.5/site-packages/django/template/base.py" in _resolve_lookup
883. current = current[bit]
File "/nfs/2013/v/vmonteco/Envs/demenhouse/lib/python3.5/site-packages/django/forms/forms.py" in __getitem__
144. "Key %r not found in '%s'" % (name, self.__class__.__name__))
During handling of the above exception ("Key 'fieldsets' not found in 'FurnitureForm'"), another exception occurred:
File "/nfs/2013/v/vmonteco/Envs/demenhouse/lib/python3.5/site-packages/django/core/handlers/base.py" in get_response
149. response = self.process_exception_by_middleware(e, request)
File "/nfs/2013/v/vmonteco/Envs/demenhouse/lib/python3.5/site-packages/django/core/handlers/base.py" in get_response
147. response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/nfs/2013/v/vmonteco/Code/web/demenhouse/htdocs/main/views.py" in secondform
69. return (render(request,'main/devis-bis.html', {"form" : makeFurnitureForm()}))
File "/nfs/2013/v/vmonteco/Envs/demenhouse/lib/python3.5/site-packages/django/shortcuts.py" in render
67. template_name, context, request=request, using=using)
File "/nfs/2013/v/vmonteco/Envs/demenhouse/lib/python3.5/site-packages/django/template/loader.py" in render_to_string
97. return template.render(context, request)
File "/nfs/2013/v/vmonteco/Envs/demenhouse/lib/python3.5/site-packages/django/template/backends/django.py" in render
95. return self.template.render(context)
File "/nfs/2013/v/vmonteco/Envs/demenhouse/lib/python3.5/site-packages/django/template/base.py" in render
206. return self._render(context)
File "/nfs/2013/v/vmonteco/Envs/demenhouse/lib/python3.5/site-packages/django/template/base.py" in _render
197. return self.nodelist.render(context)
File "/nfs/2013/v/vmonteco/Envs/demenhouse/lib/python3.5/site-packages/django/template/base.py" in render
992. bit = node.render_annotated(context)
File "/nfs/2013/v/vmonteco/Envs/demenhouse/lib/python3.5/site-packages/django/template/base.py" in render_annotated
959. return self.render(context)
File "/nfs/2013/v/vmonteco/Envs/demenhouse/lib/python3.5/site-packages/django/template/loader_tags.py" in render
173. return compiled_parent._render(context)
File "/nfs/2013/v/vmonteco/Envs/demenhouse/lib/python3.5/site-packages/django/template/base.py" in _render
197. return self.nodelist.render(context)
File "/nfs/2013/v/vmonteco/Envs/demenhouse/lib/python3.5/site-packages/django/template/base.py" in render
992. bit = node.render_annotated(context)
File "/nfs/2013/v/vmonteco/Envs/demenhouse/lib/python3.5/site-packages/django/template/base.py" in render_annotated
959. return self.render(context)
File "/nfs/2013/v/vmonteco/Envs/demenhouse/lib/python3.5/site-packages/django/template/loader_tags.py" in render
69. result = block.nodelist.render(context)
File "/nfs/2013/v/vmonteco/Envs/demenhouse/lib/python3.5/site-packages/django/template/base.py" in render
992. bit = node.render_annotated(context)
File "/nfs/2013/v/vmonteco/Envs/demenhouse/lib/python3.5/site-packages/django/template/base.py" in render_annotated
959. return self.render(context)
File "/nfs/2013/v/vmonteco/Envs/demenhouse/lib/python3.5/site-packages/django/template/defaulttags.py" in render
161. values = self.sequence.resolve(context, True)
File "/nfs/2013/v/vmonteco/Envs/demenhouse/lib/python3.5/site-packages/django/template/base.py" in resolve
709. obj = self.var.resolve(context)
File "/nfs/2013/v/vmonteco/Envs/demenhouse/lib/python3.5/site-packages/django/template/base.py" in resolve
850. value = self._resolve_lookup(context)
File "/nfs/2013/v/vmonteco/Envs/demenhouse/lib/python3.5/site-packages/django/template/base.py" in _resolve_lookup
891. current = getattr(current, bit)
File "/nfs/2013/v/vmonteco/Envs/demenhouse/lib/python3.5/site-packages/form_utils/forms.py" in fieldsets
253. self, self._fieldsets)
Exception Type: AttributeError at /devisbis/1679091c5a880faf6fb5e6087eb1b2dc/
Exception Value: 'FurnitureForm' object has no attribute '_fieldsets'
Doesn't
type()
calls base classes` constructors, and don't these constructors define and set attributes properly?
You are looking at initialisers, not constructors, hence the name __init__
. These are automatically called when an instance is created. type()
doesn't call these, because you are not producing an instance, you are producing a class.
Once you call the resulting class to create an instance, then the __init__
method is called.
Why did I have to explicitly set these attributes by myself?
I don't know, you don't show how you are creating an instance, or any other code showing your error. If you are expecting those attributes to exist on the class, then your assumption there is wrong.
Why didn't I got an
AttributeError
when the constructor tries to access an attribute (row_attrs for instance) during the type() call? (I got one when trying to access it later though).
Again, type()
produces a new class, not an instance, and __init__
is not called at this time.
You should not need to add those attributes to your class namespace if the __init__
method will add them to your instance namespace.
I must note that the forms.BaseForm.__init__
method is not designed to cooperate with other classes coming later in the MRO; it never calls super().__init__()
. If you do create an instance of your new class, the form_utils.forms.BetterBaseForm.__init__()
method will not be called, unless you were to swap the order of your two base classes:
return type(
"FurnitureForm", (form_utils.forms.BetterBaseForm, forms.BaseForm,),
{"base_fieldsets" : dic, "base_fields" : fields})
That's because with BetterBaseForm
listed first, it'll have it's __init__
be called, and the super(BetterBaseForm, self).__init__(*args, **kwargs)
line in that method will then call BaseForm.__init__()
.
Reading your traceback I do note that you never create an instance. You should always create an instance of a form class, and optionally pass in request.POST
to pre-populate the fields and validate the form input.
So instead of:
return (render(request,'main/devis-bis.html', {"form" : makeFurnitureForm()}))
use
return render(request,'main/devis-bis.html', {"form" : makeFurnitureForm()()})
Note the extra ()
. You probably want to check if the form is valid at some point, however, so you'd use this:
form_class = makeFurnitureForm()
if request.method == 'POST':
form = form_class(request.POST)
if form.is_valid():
# ...
# process data, redirect somewhere
return HttpResponseRedirect('/thanks/')
else:
form = form_class()
return render(request,'main/devis-bis.html', {"form" : form})