Skip to content

Instantly share code, notes, and snippets.

@bendog
Last active February 17, 2022 23:46
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bendog/746fc318292edf560eaa8b377a9e21e0 to your computer and use it in GitHub Desktop.
Save bendog/746fc318292edf560eaa8b377a9e21e0 to your computer and use it in GitHub Desktop.
Django functional view to class based view refactoring

Django functional view to class based view refactoring

To understand what's happening with django generic class based views, lets create a function view where we handle all of the functionality explicity, then migrate the functionality to a class.

Setup the model and form

First let's setup the supporting files.

Create the models

models.py

from django.db import models


class Event(models.Model):
    title = models.CharField(max_length=200)
    location = models.CharField(max_length=200)
    venue = models.CharField(max_length=200)
    start_time = models.DateTimeField('start time and date')
    end_time = models.DateTimeField('end time and date')
    categories = models.ManyToManyField('Category', related_name='events')
    
    def __str__(self):
        return self.title


class Category(models.Model):
    name = models.CharField(max_length=50)
    
    def __str__(self):
        return self.name

Create a form

forms.py

from django.forms import ModelForm
from .models import Event, Category


class EventForm(ModelForm):
    class Meta:
        model = Event
        fields = [
            'title', 
            'location', 
            'venue', 
            'start_time',
            'end_time',
            'categories' 
            ]

create the form.html template

templates/eventapp/form.html

{% extends "./base.html" %}

{% block content %}
<form action="" method="post">
  {% csrf_token %}
  {{ form.as_p }}
  <input type="submit" value="Submit">
</form>
{% endblock %}

Setup the functional view

Let's create out functional view, which will handle the get request and the post request.

The GET request returns the blank form for the user to fill out.

The POST request handles the submitted form and if successful redirects to the index.

view.py

from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.urls import reverse_lazy
from django.views import generic

from .forms import EventForm


# the fucntional view for add event
def addevent(request):
    # if this is a POST request we need to process the form data
    if request.method == 'POST':
        # create a form instance and populate it with data from the request:
        eventform = EventForm(request.POST)
        # check whether it's valid:
        if eventform.is_valid():
            # save the data from the form
            eventform.save()
            # redirect to the event list
            return HttpResponseRedirect(reverse_lazy('eventapp:index'))
    # if a GET (or any other method) we'll create a blank form
    else:
        eventform = EventForm()
    # create the context for our template
    context = {'form': eventform}
    # build the response with our template
    template = 'eventapp/form.html'
    return render(request, template, context)

Setup the url for the functional view

urls.py

from django.urls import path
from . import views

app_name = 'eventapp'
urlpatterns = [
    path('addevent/', views.addevent, name='addevent'),
]

Convert the functional view to a class based view

views.py

from django.http import HttpResponse, HttpResponseRedirect
from django.urls import reverse, reverse_lazy
from django.views import generic
from django.shortcuts import render
from .models import Event
from .forms import EventForm


# the Class based view for add event
class AddEventView(generic.View):

    # in the class basded view we handle the GET request with a get() function
    def get(self, request):
        # create our form instance
        eventform = EventForm()
        # assign it to the context
        context = {'form': eventform}
        # return our template with our context
        template = 'eventapp/form.html'
        return render(request, template, context)

    # in the class based view we handle the POST request with a post() function
    def post(self, request):
        # we create our form instance with the data from the request
        eventform = EventForm(request.POST)
        # check if the form is valid
        if eventform.is_valid():
            # save the data of the form
            eventform.save()
            # redirect to the list of events 
            return HttpResponseRedirect(reverse_lazy('eventapp:index'))
        # if the form isn't valid return the form (with automatic errors)
        # create the context for our template
        context = {'form': eventform}
        # build the response with our template
        template = 'eventapp/form.html'
        return render(request, template, context)

update urls.py for usage with the new class based view

urls.py

from django.urls import path
from . import views

app_name = 'eventapp'
urlpatterns = [
    path('addevent/', views.AddEventView.as_view(), name='addevent'),
]

Remove duplication in the class based view

there's a lot of duplicated lines in our AddEventView so lets try and fix that this class is exactly the same as the one above! it just is arranged a little differently

views.py

from django.http import HttpResponse, HttpResponseRedirect
from django.urls import reverse, reverse_lazy
from django.views import generic
from django.shortcuts import render
from .models import Event
from .forms import EventForm


class AddEventView(generic.View):

    template = 'eventapp/form.html'
    form_class = EventForm
    success_url = reverse_lazy('eventapp:index')
    # we have to use reverse_lazy so that urls.py can load our class
    # and not get stuck in a recursive loop 

    def form_context(self, eventform):
        # assign the form to the context
        return {'form': eventform}

    # in the class basded view we handle the GET request with a get() function
    def get(self, request):
        # create our form instance
        eventform = self.form_class()
        # return our template with our contex
        return render(request, self.template, self.form_context(eventform))

    # in the class based view we handle the POST request with a post() function
    def post(self, request):
        # we create our form instance with the data from the request
        eventform = self.form_class(request.POST)
        # check if the form is valid
        if eventform.is_valid():
            # save the data of the form
            eventform.save()
            # redirect to the list of events 
            return HttpResponseRedirect(self.success_url)
        # if the form isn't valid return the form (with automatic errors)
        # build the response with our template
        return render(request, self.template, self.form_context(eventform))

Utilise Django's built in generic view

since we are doing something very common django has a built in tool to do this for us

views.py

from django.http import HttpResponse, HttpResponseRedirect
from django.urls import reverse, reverse_lazy
from django.views import generic
from django.shortcuts import render
from .models import Event
from .forms import EventForm


class AddEventView(generic.CreateView):
    # using the create view we can just give it the variables 
    # as the functionaity is already built in!
    form_class = EventForm
    template_name = 'eventapp/form.html'
    success_url = reverse_lazy('eventapp:index')
    # we have to use reverse_lazy so that urls.py can load our class
    # and not get stuck in a recursive loop 
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment