I'm trying to figure out how the ModelForm is instantiated when I'm using generic UpdateView
.
I've gone through the django source code and looked into UpdateView
and relevant Form
classes but I can't see any line of code where we are explicitly passing instance
to the object of ModelForm
class.
For example, say we have PostForm
as a ModelForm
then we would have written :
form = PostForm(instance=Post.object.get(pk=pk))
to render the form from the models object.
I can't see similar code in the django source code and can't figure out how the generic ModelForm
is getting populated in case of UpdateView
i.e. how the self.instance
attribute of my form is getting instantiated when I submit data after POSTing the form.
The instance
attribute of ModelForm
is instantiated in get_form_kwargs()
defined in ModelFormMixin
For detailed explanation see below :
The UpdateView
view inherits SingleObjectTemplateResponseMixin, BaseUpdateView
BaseUpdateView
further inherits ModelFormMixin
and ProcessFormView
It also defines the get
and post
methods that are called via dispatch
These get
and post
methods sets the object
attribute as the current model object. Below is the code snippet from django docs :
class BaseUpdateView(ModelFormMixin, ProcessFormView):
"""
Base view for updating an existing object.
Using this base class requires subclassing to provide a response mixin.
"""
def get(self, request, *args, **kwargs):
self.object = self.get_object()
return super(BaseUpdateView, self).get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
self.object = self.get_object()
return super(BaseUpdateView, self).post(request, *args, **kwargs)
The get and post methods also call the parent's get and post method i.e. get and post defined in ProcessFormView
During GET request
The get
method defined in ProcessFormView
calls the get_context_data()
overridden under FormMixin
which further invokes get_form()
to return an instance of the form to be used in the view.
def get_context_data(self, **kwargs):
"""
Insert the form into the context dict.
"""
if 'form' not in kwargs:
kwargs['form'] = self.get_form()
return super(FormMixin, self).get_context_data(**kwargs)
get_form()
calls get_form_kwargs()
which lies in ModelFormMixin
as well as FormMixin
but since the ModelFormMixin
inherits from FormMixin
, the method defined in ModelFormMixin
overrides the one defined in FormMixin
. This get_form_kwargs()
method first calls the super/parent's method and then sets the instance
attribute of the form to the current model object i.e self.object
(or simply object
).
Code snippet from the docs below :
def get_form_kwargs(self): #defined in ModelFormMixin class
"""
Returns the keyword arguments for instantiating the form.
"""
kwargs = super(ModelFormMixin, self).get_form_kwargs()
if hasattr(self, 'object'):
kwargs.update({'instance': self.object})
return kwargs
The form is then rendered using the model object's attributes
During POST request :
As mentioned earlier (see first code snippet), just like get()
, post()
method also sets the object
attribute to the current model object i.e. self.object=self.get_object()
. ( get_object() is inherited from SingleObjectMixin
class )
It then calls post
method of ProcessFormView
i.e. parent class which creates the instance of form
using get_form()
method. (Just like get_context_method was doing in case of get
request)
get_form()
calls the get_form_kwargs
which further sets the instance
attribute of the form to the self.object
instantiated in first post
method call.
Code snippet below :
class ProcessFormView(View):
"""
A mixin that renders a form on GET and processes it on POST.
"""
def get(self, request, *args, **kwargs):
"""
Handles GET requests and instantiates a blank version of the form.
"""
return self.render_to_response(self.get_context_data())
def post(self, request, *args, **kwargs):
"""
Handles POST requests, instantiating a form instance with the passed
POST variables and then checked for validity.
"""
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
# PUT is a valid HTTP verb for creating (with a known URL) or editing an
# object, note that browsers only support POST for now.
def put(self, *args, **kwargs):
return self.post(*args, **kwargs)
Next, the form is validated against basic constraints and this is done by calling form.is_valid()
method which is inherited from BaseForm
class.
This is a very important step because at this point, the instance
object's attributes are updated to the data POSTed in the form.
This all is achieved via following stack of calls :
form.is_valid()
calls -> errors
property -> which calls full_clean()
-> _clean_fields()
-> _clean_form()
-> _post_clean()
_post_clean()
constructs the instance
from POST
data by calling construct_instance_method
To understand these functions better read the BaseForm
class for is_valid()
here and BaseModelForm
class for _post_clean()
here