Search code examples
djangodjango-modelsdjango-formsdjango-templatesdjango-views

Unable to figure out how ModelForm is rendered using generic UpdateView


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.


Solution

  • 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 ProcessFormViewi.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