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

Alternative Views

Alternative Views

Exploring the power of class-based views, their current status and how the concepts behind them can be applied to other areas.

Ben Firshman

August 19, 2011
Tweet

More Decks by Ben Firshman

Other Decks in Programming

Transcript

  1. Alternative Views Ben Firshman I’d like to bring your attention

    to a tale of neglect in the Django community
  2. Models Models get a huge amount of attention - queryset

    refactor, multi-db Even templates - caching, if tag, all sorts of filters and tags Something missing...
  3. Models Templates Models get a huge amount of attention -

    queryset refactor, multi-db Even templates - caching, if tag, all sorts of filters and tags Something missing...
  4. Models Templates ? Models get a huge amount of attention

    - queryset refactor, multi-db Even templates - caching, if tag, all sorts of filters and tags Something missing...
  5. Views .... that’s views. How views work - generic views

    in particular - have barely been touched since Django was released. I’m going to explain one problem with views that needs fixing
  6. View At their heart, they are simple They are functions

    that take a request and return a response
  7. View request response At their heart, they are simple They

    are functions that take a request and return a response
  8. def post_detail(request, pk): post = get_object_or_404(Post, pk=pk) return render_to_response(‘post_detail.html’, {

    ‘post’: post }) This lets you write simple, clear, explicit views (explain step by step)
  9. (r’post/(\d+)/’, ‘django.views.generic.list_detail.object_detail’, { ‘queryset’: Post.objects.all() }) Generic views let you

    write this less verbosely BUT - they are very inflexible. How many people have used a generic view, then almost immediately found they have to rewrite it? Now say we want to fetch our posts by thread id and slug...
  10. def post_detail(request, thread, slug): post = get_object_or_404(Post, thread__pk=thread, slug=slug )

    return render_to_response(‘post_detail.html’, { ‘post’: post }) We’ve had to rewrite the functionality of our generic view. This could be even more complicated if we were using other generic view features Say this view is a view in a reusable application - we’ll have to rewrite it again to change a small bit of functionality
  11. newforms-admin To go on a bit of a tangent -

    a couple of years ago, we got newforms-admin. * made admin work with newforms * also added loads of hooks for customisability
  12. class BookAdmin(admin.ModelAdmin): def save_model(self, request, obj, form, change): if not

    change: obj.author = request.user obj.save() def has_change_permission(self, request, obj=None): if not obj: return True if obj.author == request.user: return True else: return False admin.site.register(Book, BookAdmin) Customisability by subclassing the admin class and overriding a bunch of methods This is a simplified example - coincidently the same one Alex used in his talk earlier. It’s overriding a hook in the admin that only lets users edit objects they’ve created. Almost every decision made in the admin is done in a method that you can override. This means you don’t have to rewrite the whole admin just to add an extra bit of functionality
  13. View request response With our simple model of views -

    I said views are a function that takes a request and returns a response. I lied - they are a *callable* that takes a request and returns a response.
  14. __call__() You can define this magic method on objects that

    gets called if you call the object like a function
  15. class PostView(DetailView): queryset = Post.objects.all() Joseph’s idea let you write

    views like this. This is equivalent of the generic view we had earlier. If we take an instance of PostView.....
  16. (r’post/(?P<pk>\d+)/’, PostView()) ... we can stick it in a urlconf,

    and it acts like a normal view. DetailView has __call__() method which takes a request and primary key from the URL dispatcher
  17. class PostView(DetailView): queryset = Post.objects.all() def get_object(thread, slug): return get_object_or_404(Post,

    thread__pk=thread, slug=slug ) Now, say we want to extend it like we did before. We don’t have to rewrite the generic view, just override a method This is not a great example because its the same length as the simple function we had before. But - views in functions can get very hairy very quickly - you can see how this method will scale. You can even inherit from this class again to create a view with slightly different functionality There are also generic views like this for listing objects; creating, editing and deleting objects with forms; and listing objects within certain time periods. There are even views for just displaying something in a template, which all these views inherit from
  18. from jingo import render_to_string, env class JinjaMixin(object): def render(self, names=None,

    context=None): template = env.select_template(names) return render_to_string( self.request, template, context) We can also do clever stuff. Mixins are really handy to alter the functionality of class-based views. This, for example, is a Mixin that makes any view render with Jinja instead of Django’s templates. TemplateView defines a method for rendering to a Django template. We can override this to render to a Jinja template instead
  19. class PostView(JinjaMixin, DetailView): queryset = Post.objects.all() If we add that

    mixin to our PostView we made earlier, it will now render with Jinja templates
  20. class JsonMixin(object): def render_to_response(self, names=None, context=None): if self.kwargs.get('format') == 'json':

    return self.get_response( self.get_resource(context), mimetype='application/json' ) return super(JsonMixin, self).render_to_response( names, context) Outputs JSON as well as templates given a format keyword argument from the URL Overriding a different method to the jinja example because we need to set the mimetype of the response, but the idea’s the same
  21. from django.core import serializers class PostView(JsonMixin, DetailView): queryset = Post.objects.all()

    def get_resource(self, context): return serializers.serialize('json', context['object_list']) Now we have a view that can output to JSON as well as templates. All the logic we’ve written to output data to templates can be reused. This could be used to write a really neat REST interface Many API tools for Django mean you have to use different URL namespaces, which isn’t RESTful
  22. from django.core import serializers class PostView(JinjaMixin, JsonMixin, DetailView): queryset =

    Post.objects.all() def get_resource(self, context): return serializers.serialize('json', context['object_list']) and of course - if we hook in our Jinja plugin to this view, we have a view that outputs to Jinja templates and JSON
  23. django.views Not just for generic views - this can become

    the standard way of writing views. If everybody builds upon a single base view, cool stuff like Jinja mixins will work everywhere, including reusable apps That’s not saying we’re getting rid of the old way of writing views though - views are still just a callable that takes a request and a response
  24. class PostAdmin(admin.ModelAdmin): def get_urls(self): return patterns('', (r'^custom/$', self.custom_view), ) +

    super(PostAdmin, self).get_urls() These class-based ideas can also be used for other things. One of my favourite things in the admin is that ModelAdmin is basically a class-based urlconf. You include the admin into a urlconf like you would include a urls.py. It lets you write urlconfs which can do clever routing, and subclass and extend them Example - newspaper sections
  25. class LoginConsumer(Consumer): def do_index(self, request, extra_message=None): return self.do_login(request, extra_message) do_index.urlregex

    = '^$' Simon Willison did a similar thing with django openid, but with a neater API. You include a consumer into the urlconf like you include the admin interface, but it iterates over all the methods on itself and finds ones with a url regex defined. If somebody were to develop a generic way of writing class-based urlconfs, and set some conventions, it could be a really useful pattern. Maybe the urlconfs in Django core could be smarter too......
  26. This class-based view stuff is all well and good, but

    ticket 6735 has been around for almost 3 years. I’d like to cover some of the history of this and what we need to do to get this into Django core. As Eric mentioned yesterday, class-based views need conventions. Once it’s a part of Django, this will be the convention that views can be built upon. It started with Joseph’s patches, which had a number of people working on it, but was never finished. After 1.0, Jacob KM worked on it for 1.1, but again, never finished it. Since then, it has drifted, and the wheel has been reinvented many times.
  27. The problem is, class-based views are prime bike-shedding material. The

    details of implementing them have been argued over many flame wars on django-dev. It’s incredibly hard to decide on how it should work because it is one huge naming problem. It’s designing an API - choosing method names.
  28. Thread safety was another sensitive topic. (explain) This is in

    fact a tiny part of how class-based views work. If you don’t like how it works - just write a mixin that changes it.
  29. http://github.com/bfirsh/ django-class-based-views Here is my stab at an API. It

    is based on Joseph and Jacob’s initial implementation, with work from David Larlet and inspiration from Andrew Godwin.