Generic what-based who?
- Class-based views were introduced in Django 1.3, about 2 years ago.
- They're still not as well known and popular as they should be.
-
The basic idea:
Define view classes instead of view functions.
A function-based view
from django.template.response import TemplateResponse
def homepage_view(request):
return TemplateResponse("homepage.html")
A class-based view
from django.template.response import TemplateResponse
from django.views.generic import View
class HomepageView(View):
def get(self, request):
return TemplateResponse("homepage.html")
There are View
sublcasses that cover common cases, like
rendering a template. If we use those instead we can simplify things.
A better class-based view
from django.views.generic import TemplateView
class HomepageView(TemplateView):
template_name = "homepage.html"
Examples of other View
subclasses
class BlogPostListView(ListView):
model = BlogPost
class BlogPostDetailView(DetailView):
model = BlogPost
class BlogPostCreateView(CreateView):
model = BlogPost
class BlogPostUpdateView(UpdateView):
model = BlogPost
class BlogPostDeleteView(DeleteView):
model = BlogPost
success_url = reverse_lazy("blog_post_list")
# ... and there are more!
Why is this better?
-
They give us four big benefits:
- Sensible default behaviour from the
View
class.
- Classes are better than functions.
- Convention is better than configuration.
- Repeating yourself is boring.
The View
class is smarter than it looks
-
Take a look at the
View
class source code.
- Get
OPTIONS
requests for free.
- Get HTTP Method Not Allowed (405) errors for free.
- With function-based views, we have to remember to check HTTP verbs ourselves.
from django.template.response import TemplateResponse
from django.views.decorators.http import require_safe
@require_safe
def homepage_view(request):
return TemplateResponse("homepage.html")
GET
vs. POST
- The HTTP verbs we permit matter.
GET
and HEAD
requests shouldn't have side
effects. POST
, PUT
, and DELETE
may have side effects.
- If you accept
GET
requests to views that have side
effects you could end up in trouble.
Classes over functions
- Split up complex view functions into multiple methods with shared
state.
- Take advantage of inheritance (we'll see a lot of this with the
generic views).
Convention over configuration
- Lots of small decisions don't matter, as long as we're consistent (e.g. what should I call the template that this view renders?).
- Inconsistency in an application slows developers down.
- Maintaining app-level conventions takes time.
-
Framework-level convention is best:
- It's easy for developers to move between apps.
- Consistency takes less time; the decision is hidden from us.
Conventions are encoded in subclasses of View
- Django provides subclasses of
View
for common types of view.
- These subclasses provide conventions; small, usually unimportant decisions are lifted out of our code into the parent class in Django.
Decisions, decisions
class BlogPostDetailView(View):
def get(self, request, *args, **kwargs):
return TemplateResponse(
"blog/blogpost_detail.html",
context={
"blogpost": self.get_object(),
},
)
def get_object(self):
return get_object_or_404(
BlogPost,
pk=self.kwargs.get("pk"),
)
- There are lots of small decisions highlighted in this class.
Using Django's DetailView
class BlogPostDetailView(DetailView):
model = BlogPost
- Django's
DetailView
takes the small decisions off our hands.
- We can set attributes or override methods when we need to go outside of the conventions.
But my app is unconventional!
Override conventions with attributes
class BlogPostDetailView(DetailView):
model = BlogPost
queryset = BlogPost.objects.filter(published=True)
Override conventions with methods
class BlogPostDetailView(DetailView):
model = BlogPost
def get_queryset(self):
return BlogPost.objects.filter(published=True)
- The attributes we can set and the methods we can override mostly
follow this pattern: the attribute is
queryset
, the method
is get_queryset
.
The Template Method pattern
- Each
View
subclass includes various mixins.
- Each mixin can be thought of as implementing the
Template Method pattern
Example: The SingleObject
mixin
get_object()
is the template method
get_queryset()
⇒ self.queryset
self.pk_url_kwarg
and self.slug_url_kwarg
get_slug_field()
⇒ self.slug_field
How do I know what to override?
- If required attributes or URL keyword arguments are missing, you will
get a very helpful
ImproperlyConfigured
or
AttributeError
exception telling you exactly what to
do.
- There is a learning curve, but you will end up learning a
set of conventions, so it makes sense to learn this one.
- Read the documentation, the
flattened index
is especially useful for reference.
- Read the code, start from
django/views/generic/__init__.py
Use class-based views!
- The
View
class will give you free functionality.
- Replacing complex functions with classes will give you a cleaner codebase.
- Conventions will help you work faster.
- Generic base classes stop you repeating yourself.