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 full-size slide

  2. New in Django 1.3
    Better in Django 1.4

    View full-size 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 full-size slide

  4. The view contract
    in Django

    View full-size 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 full-size slide

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

    View full-size slide

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

    View full-size slide

  8. Other class-based stuff

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  11. single instance
    shared across requests

    View full-size slide

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

    View full-size slide

  13. 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 full-size 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
    Thread-safety
    self.request
    self.args
    self.kwargs

    View full-size slide

  15. Declarative
    vs.
    imperative

    View full-size slide

  16. (biased) usage tips

    View full-size slide

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

    View full-size slide

  18. 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 full-size slide

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

    View full-size slide

  20. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

  23. Case studies

    View full-size slide

  24. 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 full-size slide

  25. 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 full-size slide

  26. 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 full-size slide

  27. 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 full-size slide

  28. Registration

    View full-size slide

  29. 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 full-size slide

  30. 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 full-size slide

  31. 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 full-size slide

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

    View full-size slide

  33. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

  36. 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 full-size slide

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

    View full-size slide

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

    View full-size slide