Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Class-based Views: patterns and anti-patterns

Class-based Views: patterns and anti-patterns

Bruno Renié

June 04, 2012

More Decks by Bruno Renié

Other Decks in Programming


  1. Bruno Renié djangocon.eu 2012 Class-based Views patterns & anti-patterns

  2. New in Django 1.3 Better in Django 1.4

  3. Controversy http://lukeplant.me.uk/blog/posts/djangos-cbvs-were-a-mistake/ http://www.boredomandlaziness.org/2012/05/djangos-cbvs-are-not-mistake-but.html

  4. The view contract in Django

  5. # Your code def view(request): return HttpResponse('yay') urlpatterns = patterns('',

    r'^$', view) # Django callback, args, kwargs = resolve(request.path_info) response = callback(request, *args, **kwargs)
  6. A view is a callable that takes a request and

    returns a response
  7. Deprecation function-based generic views are being deprecated function-based views aren't

    going anywhere
  8. Other class-based stuff

  9. class Middleware(object): def process_request(self, request): ... Middleware

  10. class PageAdmin(admin.ModelAdmin): def get_changelist(self, request, **kwargs): ... Admin

  11. single instance shared across requests

  12. from django.views import generic class Users(generic.ListView): ... users = Users.as_view()

    CBV api
  13. as_view()?

  14. class View(object): @classonlymethod def as_view(cls, **init): def view(request, *args, **kwargs):

    self = cls(**init) return self.dispatch(request, *args, **kwargs) return view
  15. class View(object): @classonlymethod def as_view(cls, **init): def view(request, *args, **kwargs):

    self = cls(**init) return self.dispatch(request, *args, **kwargs) return view Thread-safety self.request self.args self.kwargs
  16. Declarative vs. imperative

  17. ccbv.co.uk

  18. (biased) usage tips

  19. Keep urls.py for URL definition Decorate in views.py

  20. from django.utils.decorators import method_decorator def cbv_decorator(decorator): def _decorator(cls): cls.dispatch =

    method_decorator(decorator)(cls.dispatch) return cls return _decorator @cbv_decorator(login_required) class MyView(generic.ListView): pass Decorating
  21. Decorating class MyView(generic.ListView): pass my_view = MyView.as_view() my_view = login_required(my_view)

  22. Decorating from django.views.decorators.cache import cache_page class CacheMixin(object): cache_timeout = 60

    def dispatch(self, *args, **kwargs): return cache_page(self.cache_timeout)( super(CacheMixin, self).dispatch )(*args, **kwargs) class CachedView(CacheMixin, ListView): cache_timeout = 100 @cyberdelia — https://gist.github.com/1231560
  23. MRO Extend, don't override unless you're 100% sure of what

    you're doing
  24. class MyView(generic.FormView): def get_initial(self): initial = super(MyViews, self).get_initial() initial.update({ 'foo':

    'bar', 'other': 'thing', }) return initial
  25. Case studies

  26. Form processing class MyView(generic.FormView): def get_form_kwargs(self): kw = super(MyView, self).get_form_kwargs()

    kw['user'] = self.request.user return kw def form_valid(self, form): form.save() return super(MyView, self).form_valid(form)
  27. Form processing class MyForm(forms.Form): def __init__(self, *args, **kwargs): self.user =

    kwargs.pop('user') super(MyForm, self).__init__(*args, **kwargs) def save(self): # self‐contained, user is known
  28. Nested navigation class Level1(generic.TemplateView): template_name = 'level_1.html' def get_context_data(self, **kwargs):

    ctx = super(Level1, self).get_context_data(**kwargs) ctx['stuff'] = do_some_work() return ctx class Level2(Level1): template_name = 'level2.html' def get_context_data(self, **kwargs): ctx = super(Level2, self).get_context_data(**kwargs) ctx['other_stuff'] = level_2_work() return ctx
  29. Drop-in features class CleverPaginator(object): paginate_by = 100 def get_count(self): raise

    NotImplementedError def get_paginate_by(self, queryset): count = self.get_count() if count > self.paginate_by * 1.5: return self.paginate_by return count class CountryView(CleverPaginator, ListView): def get_count(): return self.country.num_people
  30. Registration

  31. django-registration is great, but… I want more template variables I'm

    not using contrib.auth I want to send an SMS instead of an email …
  32. Writing a custom backend is not as simple as subclassing

    the default views from le_social.registration import views class Register(views.Register): form_class = SMSRegistrationForm def send_notification(self): ... Sane, easily overridable defaults pip install django-le-social
  33. Settings

  34. Does it have to be that global? Is it a

    switch people will want to flip at any time? Could it be… a class attribute/method instead? I know, I'll add a setting
  35. ACCOUNT_EXPIRES_IN from le_social.registration import views class Activate(views.Activate): expires_in = 3600

    * 24 * 7 # 7 days
  36. SOCIAL_AUTH_LOGIN_REDIRECT_URL SOCIAL_AUTH_BACKEND_ERROR_URL from le_social.twitter import views class Callback(views.Callback): def success(self,

    auth): ... def error(self, message): ...
  37. CBVs are great but don't use them for everything

  38. class Handler500(generic.TemplateView): template_name = '500.html'

  39. None
  40. class View(object): def dispatch(self, request, *args, **kwargs): handler = getattr(self,

    request.method.lower(), self.http_method_not_allowed) return handler(request, *args, **kwargs)
  41. Fantastic API for shipping reusable views Good API for extending

    and plugging features in existing code
  42. As a FBGV replacement: more power, less simplicity Don't limit

    yourself to Django's implementation. Use the base View and your creativity
  43. @brutasse bruno@renie.fr