Best practices for
Class-Based ViewsTwo����������� ������������������ Scoops����������� ������������������ of����������� ������������������ Django����������� ������������������ -����������� ������������������ Chapter����������� ������������������ 9
@starwilly
Why Class-Based View
https://www.flickr.com/photos/kent-chen/8986036246
DRYDon’t����������� ������������������ Repeat����������� ������������������ Yourself
Learning Curve
http://ccbv.co.uk/����������� ������������������ Django����������� ������������������ Class-Based-View����������� ������������������ Inspector
Outline
• Django View
• Class-Based View (CBV)
• Generic Class-based View (GCBV)
• Detail View
• General Tips for Django CBVs
Django View
request responseView
is simply a Python function that takes a Web request and returns a Web response.
A Simple Function-Based View
from django.http import HttpResponse def my_view(request):
if request.method == 'GET': # <view logic> return HttpResponse('result')
if request.method == 'POST': # <view logic> return HttpResponse('result')
Let’s Using Class-based View
Class-Based View
def my_view(request):
from django.views.generic import View
class MyView(View):
FBV CBV
django.views.generic.View
def my_view(request):
… return HttpResponse(‘result’)
from django.views.generic import View
class MyView(View):
request responseView
request����������� ������������������ ?response����������� ������������������ ?
function����������� ������������������ ?
FBV CBV
django.views.generic.View
as_view()Returns a callable view
that takes a request and returns a response
URLconf
urlpatterns = patterns(‘',
url(r'^$', ‘blog.views.homepage’),
)
from blog.views import HomepageView
urlpatterns = patterns(‘',
url(r'^$', HomepageView.as_view()),
)
FBV
CBV
Dispatch HTTP Verbs
from django.http import HttpResponse def my_view(request):
if request.method == 'GET': # <view logic> return HttpResponse('result')
if request.method == 'POST': # <view logic> return HttpResponse('result')
from django.views.generic import Viewfrom django.http import HttpResponse class MyView(View):
?
?
dispatch()def dispatch(self, request, *args, **kwargs):
# Try to dispatch to the right method; # if a method doesn't exist, defer to the error handler.
# Also defer to the error handler if the # request method isn't on the approved list.
if request.method.lower() in self.http_method_names: handler = getattr(self, request.method.lower(), self.http_method_not_allowed) else: handler = self.http_method_not_allowed return handler(request, *args, **kwargs)
From FBV to CBV
from django.http import HttpResponse def my_view(request):
if request.method == 'GET': # <view logic> return HttpResponse('result')
if request.method == 'POST': # <view logic> return HttpResponse('result')
from django.views.generic import Viewfrom django.http import HttpResponse class MyView(View):
def get(self, request): # <view logic> return HttpResponse('result')
def post(self, request): # <view logic> return HttpResponse('result')
FBV CBV
Generic Class-based views (GCBVs)
CreateView
UpdateView
DetailView
DeleteView
ListView
TemplateView
RedirectView
Display A Blog Post (FBV v.s. CBV)
CBVFBV
http://blog.mysite.com/post/12997/
def post_detail(request, pk): post = get_object_or_404(Post, pk=pk) return render(request, 'post_detail.html', {'post’: post})
class PostDetailView(DetailView): model = Post
DetailView
Attributes content_type = Nonecontext_object_name = Nonemodel = Nonepk_url_kwarg = ‘pk'queryset = Noneslug_field = ‘slug'slug_url_kwarg = ‘slug'template_name = Nonetemplate_name_field = Nonetemplate_name_suffix = ‘_detail'
Method Flowchart 1 dispatch() 2 http_method_not_allowed() 3 get_template_names() 4 get_slug_field() 5 get_queryset() 6 get_object() 7 get_context_object_name() 8 get_context_data() 9 get() 10 render_to_response()
def get(self, request, *args, **kwargs):
self.object = self.get_object()
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
as_view()
dispatch()
get()
get_object()
render_to_response() get_context_data()
DetailView - get()
def post_detail(request, pk): post = get_object_or_404(Post, pk=pk) return render(request, ‘post_detail.html', {'post': post})
DetailView
Method Flowchart 1 dispatch() 2 http_method_not_allowed() 3 get_template_names() 4 get_slug_field() 5 get_queryset() 6 get_object() 7 get_context_object_name() 8 get_context_data() 9 get() 10 render_to_response()
render_to_response()
get_object()
get_context_data()
How do you customize CBVs behavior?
1. Attributes
class PostDetailView(DetailView):
model = Post context_object_name = 'post_obj' template_name = 'post.html'
<h1>{{ object.title }}</h1> <div> {{ object.content }} </div>
<h1>{{ post.title }}</h1> <div> {{ post.content }} </div>
<h1>{{ post_obj.title }}</h1> <div> {{ post_obj.content }} </div>
post.html
Customize - Attributes
post_detail.html
2. Override methods
Customize - Overrides
class PostDetailView(DetailView): model = Post
def get_queryset(self): qs = super(PostDetail, self).get_queryset() return qs.published()
def get_context_data(self, **kwargs): context = super(PostDetail, self).get_context_data(**kwargs) context[‘recommended_posts’] = (self.object. get_recommended_post(user=self.request.user)[:5]) return context
3. Mixins
class SecretMessageMixin(object):
def get_context_data(self,**kwargs):self).get_context_data(**kwargs) context[“secret_message"] = ‘Hello’ return context
class PostDetailView(SecretMessageMixin, DetailView): model = Post
Customize - Mixins
{% extends ‘base.html’ %}
<div> Secret Message is {{ secret_message }} </div>
views.py
post_detail.html
Mixins1. Mixins should inherit from Python’s built-in object type
2. Base view by Django always go to the right
3. Mixins go to the left of the base view
class SecretMessageMixin(object): …
class PostDetailView(SecretMessageMixin, DetailView): model = Post
1
23
Tips for CBVs
Tip1. Access Control
from django.contrib.auth.decorators import login_required
class LoginRequiredMixin(object):
@classmethod def as_view(cls, **initkwargs): view = super(LoginRequiredMixin, cls).as_view(**initkwargs) return login_required(view)
class PostDetail(LoginRequiredMixin, DetailView): model = Post
django-braceshttps://github.com/brack3t/django-braces
LoginRequiredMixin
PermissionRequiredMixin
MultiplePermissionsRequiredMixin
CsrfExemptMixin
SuccessURLRedirectListMixin
FormValidMessageMixin
FormInvalidMessageMixin
SelectRelatedMixin
JSONResponseMixin
AjaxResponseMixin
Tip2. Where should I put my code ?
form_valid()
form_invalid()
get_queryset()
dispatch()
get_context_data()
• Custom actions on every Http request
• Add additional object to context
• Custom Actions on Views with Valid Forms
• Custom Actions on Views with Invalid Forms
• Filter posts by query string
from django.views.generic import CreateView
from braces.views import LoginRequiredMixin
from .models import Post
class PostCreateView(LoginRequiredMixin, CreateView): model = Post fields = ('title', ‘content') def form_invalid(self, form): # Do custom logic return super(PostCreateView, self).form_valid(form)
def form_valid(self, form): # Do custom logic return super(PostCreateView, self).form_valid(form)
Custom Actions on Views with Valid Forms form_valid()form_invalid()Custom Actions on Views with Invalid Forms
from django.views.generic import ListView from .models import Post class PostListView(ListView): model = Post def get_queryset(self): queryset = super(PostListView, self).get_queryset() q = self.request.GET.get('q') if q: queryset = qs.filter(title__icontains=q) return queryset
get_queryset()Filter posts by query string
{# templates/blog/_post_search.html #}
<form action="{% url “post-list" %} method="GET""> <input type="text" name="q"></> <button type="submit">Search</> </form>
Tip3. Access url parametershttp://blog.mysite.com/author/john/
url(r’^author/(?P<username>\w+)/$’, AuthorPostListView.as_view())
from django.views.generic import ListView from .models import Post class AuthorPostListView.as_view(ListView): model = Post paginate_by = 10 def get_queryset(self): user = get_object_or_404(User, username=self.kwargs['author']) queryset = super(AuthorPostListView.as_view, self).get_queryset()
return queryset.filter(author=user)
Tip4. Using the View Objectclass PostMixin(object): @cached_property def likes_and_favorites(self):
likes = self.objects.likes() favorites = self.objects.favorites() return { 'likes': likes, 'favorites': favorites, 'favorites_count': favorites.count(), }
from django.utils.functional import cached_propertyfrom django.views.generic import UpdateView
from .tasks import notify_users_who_favorited
class PostUpdateView(PostMixin, UpdateView): model = Post fields = ('title', 'content')
def form_valid(self, form): notify_users_who_favorited( instance=self.object, favorites = self.like_and_favorites['favorites'] )
Tip4. Using the View Object
ContextMixindef get_context_data(self, **kwargs): if 'view' not in kwargs: kwargs['view'] = self return kwargs
{% extends 'base.html' %}
{% block likes_and_favorites %} <ul> <li>Likes: {{ view.likes_and_favorites.likes }}</li> <li>Favorites: {{ view.likes_and_favorites.favorites_count}} </li> </ul>
{% endblock likes_and_favorites %}
class PostMixin(object): @cached_property def likes_and_favorites(self):
likes = self.objects.likes() favorites = self.objects.favorites() return { 'likes': likes, 'favorites': favorites, 'favorites_count': favorites.count(), }
How����������� ������������������ it����������� ������������������ works����������� ������������������ ?
call����������� ������������������ from����������� ������������������ template
Guidelines
• Less view code is better
• Never repeat code in views
• Views should handle presentation logic
• Keep your view simple
• Use FBV for 403, 404, 500 error handlers
• Keep your mixins simple
Summary
as_view() dispatch()
Generic Class-based View
Attribute Method Override Mixins
LoginRequiredMixin Override Which Methods Access url parameters Using View Object
FBV v.s. CBV Generic Class-based View
Customize CBVs BehaviorTips for CBVs