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
Tweet

More Decks by Bruno Renié

Other Decks in Programming

Transcript

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

    View Slide

  2. New in Django 1.3
    Better in Django 1.4

    View Slide

  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

    View Slide

  4. The view contract
    in Django

    View Slide

  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)

    View Slide

  6. A view is a callable
    that takes a request
    and returns a response

    View Slide

  7. Deprecation
    function-based generic views are being deprecated
    function-based views aren't going anywhere

    View Slide

  8. Other class-based stuff

    View Slide

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

    View Slide

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

    View Slide

  11. single instance
    shared across requests

    View Slide

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

    View Slide

  13. as_view()?

    View Slide

  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

    View Slide

  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

    View Slide

  16. Declarative
    vs.
    imperative

    View Slide

  17. ccbv.co.uk

    View Slide

  18. (biased) usage tips

    View Slide

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

    View Slide

  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

    View Slide

  21. Decorating
    class MyView(generic.ListView):
    pass
    my_view = MyView.as_view()
    my_view = login_required(my_view)

    View Slide

  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

    View Slide

  23. MRO
    Extend, don't override
    unless you're 100% sure of what you're doing

    View Slide

  24. class MyView(generic.FormView):
    def get_initial(self):
    initial = super(MyViews, self).get_initial()
    initial.update({
    'foo': 'bar',
    'other': 'thing',
    })
    return initial

    View Slide

  25. Case studies

    View Slide

  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)

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  30. Registration

    View Slide

  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

    View Slide

  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

    View Slide

  33. Settings

    View Slide

  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

    View Slide

  35. ACCOUNT_EXPIRES_IN
    from le_social.registration import views
    class Activate(views.Activate):
    expires_in = 3600 * 24 * 7 # 7 days

    View Slide

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

    View Slide

  37. CBVs are great
    but don't use them
    for everything

    View Slide

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

    View Slide

  39. View Slide

  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)

    View Slide

  41. Fantastic API for shipping reusable views
    Good API for extending and plugging
    features in existing code

    View Slide

  42. As a FBGV replacement: more power, less simplicity
    Don't limit yourself to Django's implementation.
    Use the base View and your creativity

    View Slide

  43. View Slide