Slide 1

Slide 1 text

Alternative Views Ben Firshman I’d like to bring your attention to a tale of neglect in the Django community

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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)

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

Back in 2008, Joseph Kocherhans applied these ideas to views

Slide 14

Slide 14 text

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.

Slide 15

Slide 15 text

__call__() You can define this magic method on objects that gets called if you call the object like a function

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

(r’post/(?P\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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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.

Slide 28

Slide 28 text

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.

Slide 29

Slide 29 text

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.

Slide 30

Slide 30 text

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.

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

http://github.com/bfirsh/ django-class-based-views Please use, fork and improve. Docs and tests need writing

Slide 33

Slide 33 text

Questions? @bfirsh

Slide 34

Slide 34 text

Credits http://www.flickr.com/photos/gerlos/3119891607/ http://www.flickr.com/photos/buttersweet/33684613/