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.
First let's setup the supporting files.
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
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'
]
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 %}
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'),
]
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'),
]
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))
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