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?

 

  1. No idea what you're talking about
  2. I'm a bit shaky on how it works
  3. Confident with standard stuff such as mixins
    and method overriding
  4. 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

  1. Seven views you will come to love

  2. Method overriding

  3. 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

  1. LoginRequiredMixin
  2. SuccessMessageMixin
  3. 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

  1.  Use class based views!
     
  2. Leverage the power of generic class based views.
     
  3. 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,076