40
Best practices for Class-Based Views Two Scoops of Django - Chapter 9 @starwilly

Ch9 .Best Practices for Class-Based Views

Embed Size (px)

DESCRIPTION

Two Scoops of Django - Chapter 9: Best practices for Class-Based Views

Citation preview

Page 1: Ch9 .Best Practices  for  Class-Based Views

Best practices for

Class-Based ViewsTwo����������� ������������������  Scoops����������� ������������������  of����������� ������������������  Django����������� ������������������  -����������� ������������������  Chapter����������� ������������������  9

@starwilly

Page 2: Ch9 .Best Practices  for  Class-Based Views

Why Class-Based View

Page 3: Ch9 .Best Practices  for  Class-Based Views

https://www.flickr.com/photos/kent-chen/8986036246

DRYDon’t����������� ������������������  Repeat����������� ������������������  Yourself

Page 4: Ch9 .Best Practices  for  Class-Based Views

Learning Curve

http://ccbv.co.uk/����������� ������������������  Django����������� ������������������  Class-Based-View����������� ������������������  Inspector

Page 5: Ch9 .Best Practices  for  Class-Based Views

Outline

• Django View

• Class-Based View (CBV)

• Generic Class-based View (GCBV)

• Detail View

• General Tips for Django CBVs

Page 6: Ch9 .Best Practices  for  Class-Based Views

Django View

request responseView

is simply a Python function that takes a Web request and returns a Web response.

Page 7: Ch9 .Best Practices  for  Class-Based Views

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')

Page 8: Ch9 .Best Practices  for  Class-Based Views

Let’s Using Class-based View

Page 9: Ch9 .Best Practices  for  Class-Based Views

Class-Based View

def my_view(request):

from django.views.generic import View

class MyView(View):

FBV CBV

django.views.generic.View

Page 10: Ch9 .Best Practices  for  Class-Based Views

def my_view(request):

… return HttpResponse(‘result’)

from django.views.generic import View

class MyView(View):

request responseView

request����������� ������������������  ?response����������� ������������������  ?

function����������� ������������������  ?

FBV CBV

Page 11: Ch9 .Best Practices  for  Class-Based Views

django.views.generic.View

Page 12: Ch9 .Best Practices  for  Class-Based Views

as_view()Returns a callable view

that takes a request and returns a response

Page 13: Ch9 .Best Practices  for  Class-Based Views

URLconf

urlpatterns = patterns(‘',

url(r'^$', ‘blog.views.homepage’),

)

from blog.views import HomepageView

urlpatterns = patterns(‘',

url(r'^$', HomepageView.as_view()),

)

FBV

CBV

Page 14: Ch9 .Best Practices  for  Class-Based Views

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):

?

?

Page 15: Ch9 .Best Practices  for  Class-Based Views

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)

Page 16: Ch9 .Best Practices  for  Class-Based Views

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

Page 17: Ch9 .Best Practices  for  Class-Based Views

Generic Class-based views (GCBVs)

CreateView

UpdateView

DetailView

DeleteView

ListView

TemplateView

RedirectView

Page 18: Ch9 .Best Practices  for  Class-Based Views

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

Page 19: Ch9 .Best Practices  for  Class-Based Views

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()

Page 20: Ch9 .Best Practices  for  Class-Based Views

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()

Page 21: Ch9 .Best Practices  for  Class-Based Views

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()

Page 22: Ch9 .Best Practices  for  Class-Based Views

How do you customize CBVs behavior?

Page 23: Ch9 .Best Practices  for  Class-Based Views

1. Attributes

Page 24: Ch9 .Best Practices  for  Class-Based Views

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

Page 25: Ch9 .Best Practices  for  Class-Based Views

2. Override methods

Page 26: Ch9 .Best Practices  for  Class-Based Views

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

Page 27: Ch9 .Best Practices  for  Class-Based Views

3. Mixins

Page 28: Ch9 .Best Practices  for  Class-Based Views

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

Page 29: Ch9 .Best Practices  for  Class-Based Views

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

Page 30: Ch9 .Best Practices  for  Class-Based Views

Tips for CBVs

Page 31: Ch9 .Best Practices  for  Class-Based Views

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

Page 32: Ch9 .Best Practices  for  Class-Based Views

django-braceshttps://github.com/brack3t/django-braces

LoginRequiredMixin

PermissionRequiredMixin

MultiplePermissionsRequiredMixin

CsrfExemptMixin

SuccessURLRedirectListMixin

FormValidMessageMixin

FormInvalidMessageMixin

SelectRelatedMixin

JSONResponseMixin

AjaxResponseMixin

Page 33: Ch9 .Best Practices  for  Class-Based Views

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

Page 34: Ch9 .Best Practices  for  Class-Based Views

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

Page 35: Ch9 .Best Practices  for  Class-Based Views

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>

Page 36: Ch9 .Best Practices  for  Class-Based Views

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)

Page 37: Ch9 .Best Practices  for  Class-Based Views

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'] )

Page 38: Ch9 .Best Practices  for  Class-Based Views

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

Page 39: Ch9 .Best Practices  for  Class-Based Views

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

Page 40: Ch9 .Best Practices  for  Class-Based Views

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