A Touch of Class
Getting the most out of Django's
Class Based Views
David Seddon
david@seddonym.me
http://seddonym.me
Which type of view
do you prefer?
a. Function based
b. Class based
c. What's a view?
How's your OOP?
- No idea what you're talking about
- I'm a bit shaky on how it works
- Confident with standard stuff such as mixins
and method overriding - I actually understand what a metaclass is
Function based
Class based
def my_page(request):
return render(request, 'my_page.html')
urlpatterns = [
url(r'^my-page/', my_page),
]
class MyPage(TemplateView):
template_name = 'my_page.html'
urlpatterns = [
url(r'^my-page/', MyPage.as_view()),
]
This talk
-
Seven views you will come to love
-
Method overriding
-
Mixins and custom views
Part 1
Seven views you will come to love
Seven views you will come to love
Non-model views
Model views
1.TemplateView 2.FormView
3.ListView 4.DetailView 5.CreateView 6.UpdateView 7.DeleteView
Seven views you will come to love
1.TemplateView
class HomePageView(TemplateView):
template_name = "home.html"
Renders a template.
Seven views you will come to love
2.FormView
class ContactView(FormView):
template_name = 'contact.html'
form_class = ContactForm
success_url = '/thanks/'
Displays and handles a form.
Seven views you will come to love
3.ListView
# views.py
class CatListView(ListView):
model = Cat
# templates/cat/cat_list.html
<h1>Cats</h1>
<ul>
{% for object in object_list %}
<li>{{ object }}</li>
{% endfor %}
</ul>
Displays a list of objects.
Seven views you will come to love
4.DetailView
# urls.py
urlpatterns = [
url(r'cats/^(?P<pk>[\d]+)/$', CatDetailView.as_view(),
name='cat_detail'),
]
# views.py
class CatDetailView(DetailView):
model = Cat
# templates/cat/cat_detail.html
<h1>{{ object.name }}</h1>
{% if object.image %}
<img src='{{ object.image.url }}'/>
{% endif %}
Displays a single object.
Seven views you will come to love
5.CreateView
class CatCreateView(CreateView):
model = Cat
Creates an object.
Seven views you will come to love
6.UpdateView
class CatUpdateView(UpdateView):
model = Cat
Edits an object.
Seven views you will come to love
7.DeleteView
class CatDeleteView(DeleteView):
model = Cat
Deletes an object.
Seven views you will come to love
Non-model views
Model views
1.TemplateView 2.FormView
3.ListView 4.DetailView 5.CreateView 6.UpdateView 7.DeleteView
Part 2
Method overriding
Method overriding
The class based view hierarchy is too complex for any ordinary human to remember
Method overriding
To help us with the complexity,
we have:
- Extensive Django documentation: https://docs.djangoproject.com/en/1.9/ref/class-based-views/
- The source code:
https://github.com/django/django
- Class Based Views Explorer
https://ccbv.co.uk
Method overriding
A few notable methods:
-
get_context_data
-
get_queryset
-
get_object
-
form_valid
-
get_success_url
- get_form_kwargs
-
get, post
-
dispatch
Method overriding
get_context_data
Returns the context dictionary passed to the template.
class MyView(TemplateView):
def get_context_data(self, *args, **kwargs):
context = super(MyView, self).get_context_data(*args, **kwargs)
context['foo'] = 'bar'
return context
Method overriding
get_queryset
Returns the queryset that will be used to retrieve the object that this view will display.
class MyView(DetailView):
model = MyModel
def get_queryset(self):
queryset = super(MyView, self).get_queryset()
# Make sure that the current user is the author
return queryset.filter(author=self.request.user)
Method overriding
get_object
Returns the single object that this view will display.
# urls.py
...
urlpatterns = [
url(r'^users/(?P<username>[\w]+)/cats/(?P<cat_name>[\w]+)/$',
views.CatDetailView.as_view(),
]
# views.py
...
class CatDetailView(DetailView):
def get_object(self):
# Returns the cat based on the user and cat name from the url
return Cat.objects.get(
user__username=self.kwargs['username'],
name=self.kwargs['cat_name']
)
Method overriding
form_valid
Runs if the form passed validation.
Should return an HTTPResponse.
from django.contrib import messages
class CatCreateView(CreateView):
model = Cat
def form_valid(self, form):
messages.success('Your cat was created.')
return super(CatCreateView, self).form_valid(form)
Method overriding
get_success_url
Returns the url to redirect to
if the form passed validation.
class CatCreateView(CreateView):
model = Cat
def get_success_url(self):
# Redirect to the same page, so they can create another cat
return self.request.path
Method overriding
get_form_kwargs
Returns the keyword arguments used
to instantiate the form.
class CatForm(forms.ModelForm):
"""Form for a Cat, that will always
save the user passed to it as the
cat's user.
"""
def __init__(self, *args, **kwargs):
self.user = self.kwargs.pop('user')
super(CatForm, self).__init__(
*args, **kwargs)
def save(self, *args, **kwargs):
self.instance.user = self.user
return super(CatForm,
self).save(*args, **kwargs)
class Meta:
model = Cat
# Exclude the 'user' field
# from the form
exclude = ('user',)
class CreateCatView(CreateView):
model = Cat
form_class = CatForm
def get_form_kwargs(self):
form_kwargs = super(CreateCatView,
self).get_form_kwargs()
# Pass the current user to the form
form_kwargs['user'] = \
self.request.user
return form_kwargs
Method overriding
get, post
The methods used for handling GET or POST requests
class SearchView(ListView):
model = Cat
def get(self, request, *args, **kwargs):
# We use a form, but with the GET method as it's a search form.
if self.request.GET.get('search', None):
# A search has been made
self.form = SearchForm(data=self.request.GET)
else:
self.form = SearchForm()
return super(SearchView, self).get(request, *args, **kwargs)
def get_queryset(self):
# Return the object_list, but only if the search form validates
if self.form.is_valid():
return self.form.get_results()
return []
Method overriding
dispatch
Accepts a request argument plus arguments, and returns a HTTP response.
from django.contrib import messages
class RestrictedView(TemplateView):
def dispatch(self, request, *args, **kwargs):
# If the user hasn't created 10 cats, redirect them
# to the cat create page instead
if Cat.objects.filter(
user=request.user).count() < 10:
messages.error('You must have created at least '
'10 cats to access this page.')
return redirect('cat_create')
# Otherwise, proceed as normal
return super(RestrictedView, self).dispatch(request, *args, **kwargs)
Method overriding
Is it necessary to
override a method?
In many cases, you can configure the view
just by overriding an attribute.
E.g. get_success_url will check the
success_url attribute by default:
class CatCreateView(CreateView):
model = Cat
success_url = reverse_lazy('cat_list')
Method overriding
Those methods again:
-
get_context_data
-
get_queryset
-
get_object
-
form_valid
-
get_success_url
- get_form_kwargs
-
get, post
-
dispatch
Part 3
Mixins and
custom views
Mixins and custom views
MyForm
MyView
Big view
Small form
Mixins and custom views
Views and forms composed of smaller pluggable components
Form class tends to do the weight lifting
MyView
MyForm
BaseView
Mixin
Mixin
Mixin
BaseForm
Mixin
Mixin
Mixin
Mixins and custom views
-
LoginRequiredMixin
- SuccessMessageMixin
- A custom mixin: MemberOnlyMixin
Three examples
Mixins and custom views
LoginRequiredMixin
# Django >= 1.9
from django.contrib.auth.mixins import LoginRequiredMixin
class CatCreateView(LoginRequiredMixin, CreateView):
model = Cat
If the user is not logged in,
redirect to a login page
For Django < 1.9, you can find this mixin in django-braces.
Mixins and custom views
SuccessMessageMixin
from django.contrib.messages.views import SuccessMessageMixin
class CatCreateView(SuccessMessageMixin, CreateView):
model = Cat
success_message = 'Your cat was created successfully.'
Present a message to the user on
successful form submission.
Mixins and custom views
MemberOnlyMixin
class MemberOnlyMixin(object):
"""Views mixin - only allow members to access.
Adds member profile object as an attribute on the view.
"""
def dispatch(self, request, *args, **kwargs):
# If the user is not logged in, give them the chance to
if self.request.user.is_anonymous():
return redirect_to_login(self.request.path)
try:
self.member_profile = self.request.user.member_profile
except MemberProfile.DoesNotExist:
raise PermissionDenied
return super(MemberOnlyMixin, self).dispatch(request,
*args, **kwargs)
class CatCreateView(MemberOnlyMixin, SuccessMessageMixin,
CreateView):
model = Cat
success_message = 'Your cat was successfully created.'
Allow only users with member profiles to access. Adds the member profile as an attribute on the view (custom view)
In conclusion
-
Use class based views!
-
Leverage the power of generic class based views.
- Keep views as small as possible, abstracting out generic behaviour into custom views and mixins.
https://slides.com/davidseddon/a-touch-of-class
David Seddon
david@seddonym.me
http://seddonym.me
A Touch of Class
By David Seddon
A Touch of Class
Getting the most out of Django's Class Based Views
- 2,296