Moving towards Generic Class based views

Yesterday we finished we the following class-based view:

class GetNameView(View):
    def get(self, request, *args, **kwargs):
        form = NameForm()
        return render(request, "name.html", {"form": form})

    def post(self, request, *args, **kwargs):
        form = NameForm(request.POST)
        # check whether it's valid:
        if form.is_valid():
            # process the data in form.cleaned_data as required
            form.save()
            # redirect to a new URL:
            return HttpResponseRedirect("/thanks/")
        return render(request, "name.html", {"form": form})

However, there is some minor repeatition and it's not easy to reuse the functionality in multiple views. Let's fix this!

First up, is the render call is happening in both methods so lets refactor that into it's own function:

    template_name = "name.html"
    def render_to_response(self, context):
        return render(request, self.template_name, context)

While we were here we passed in the context as a parameter and made so the template could easily be switched around.

Next on the list would be to handle the instiation of the form since this is mostly common across both methods:

    form_class = NameForm
    def get_form_kwargs(self):
        kwargs = {}
        if self.request.method in ("POST", "PUT"):
            kwargs.update(
                {
                    "data": self.request.POST,
                }
            )
        return kwargs

    def get_form(self):
        return self.form_class(**self.get_form_kwargs())

Finally the redirect could be better handled as a class attribute:

    success_url = "/thanks/"

All this comes together as follows:

class GetNameView(View):
    template_name = "name.html"
    success_url = "/thanks/"
    form_class = NameForm

    def get_form_kwargs(self):
        kwargs = {}
        if self.request.method in ("POST", "PUT"):
            kwargs.update(
                {
                    "data": self.request.POST,
                }
            )
        return kwargs

    def get_form(self):
        return self.form_class(**self.get_form_kwargs())

    def render_to_response(self, context):
        return render(request, self.template_name, context)

    def get(self, request, *args, **kwargs):
        form = self.get_form()
        return self.render_to_response({"form": form})

    def post(self, request, *args, **kwargs):
        form = self.get_form()
        # check whether it's valid:
        if form.is_valid():
            # process the data in form.cleaned_data as required
            form.save()
            # redirect to a new URL:
            return HttpResponseRedirect(self.success_url)
        return self.render_to_response({"form": form})

While this is much more code it can now be split into two classes as follows:

class MyFormView(View):
    def get_form_kwargs(self):
        kwargs = {}
        if self.request.method in ("POST", "PUT"):
            kwargs.update(
                {
                    "data": self.request.POST,
                }
            )
        return kwargs

    def get_form(self):
        return self.form_class(**self.get_form_kwargs())

    def render_to_response(self, context):
        return render(request, self.template_name, context)

    def get(self, request, *args, **kwargs):
        form = self.get_form()
        return self.render_to_response({"form": form})

    def post(self, request, *args, **kwargs):
        form = self.get_form()
        # check whether it's valid:
        if form.is_valid():
            # process the data in form.cleaned_data as required
            form.save()
            # redirect to a new URL:
            return HttpResponseRedirect(self.success_url)
        return self.render_to_response({"form": form})

class GetNameView(MyFormView):
    template_name = "name.html"
    success_url = "/thanks/"
    form_class = NameForm

The original view is not much more condensed and the base Form class (MyFormView) can be reused again with minor modifications. However at this point I would recommend having a look at the FormView in Django at this point as the above generic class view is a simplified version of it!