Your SlideShare is downloading. ×
Five class-based views everyone has written by now
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×
Saving this for later? Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime – even offline.
Text the download link to your phone
Standard text messaging rates apply

Five class-based views everyone has written by now

8,277

Published on

Published in: Business, Technology
1 Comment
8 Likes
Statistics
Notes
  • There's a (possibly) more Django idiomatic approach to the editing stuff (model formsets and the like) at http://pypi.python.org/pypi/django-extra-views by Andrew Ingram.

    The DetailListView concept I've pared down a little and submitted as an example to the Django documentation, so it may appear in some form there at some point.

    Permissions still waiting someone to do it nicely. It doesn't help that the 'official' way of doing decorators decorates the dispatch method, or decorates the entire class and both get magically applied to the generated view function…neither of which have access to the kinds of things we need to be able to implement a SingleObjectPermissionMixin. And we still don't have arbitrary raisable exceptions in Django yet.
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
No Downloads
Views
Total Views
8,277
On Slideshare
0
From Embeds
0
Number of Embeds
2
Actions
Shares
0
Downloads
45
Comments
1
Likes
8
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. Five class-based views everyone has written by now James Aylett artfinder.com Wednesday, 2 November 11 Django 1.3, released in March, introduced CLASS BASED VIEWS, which are intended to be make writing views easy and delightful. They replaced the function-based generic views, and therefore must be better, or something.
  • 2. from  django.views.generic  import  * class  AWub(DetailView):    model  =  Wub class  Wubs(ListView):    model  =  Wub Wednesday, 2 November 11 We’ll pretend you can’t pass parameters in urlconfs, because that’s icky and also misses the point.
  • 3. from  django.views.generic  import  * class  AWub(DetailView):    model  =  Wub class  Wubs(ListView):    model  =  Wub    def  get_queryset(self):        return  Wub.objects.exclude(            hidden=True        ) Wednesday, 2 November 11 The point is that you can override small bits of the ways the views work, to refine them for your application. Add further context for your templates, change the queryset and so on. There are conventional places to put templates, and for naming template context members. Upshot: little code, big effect.
  • 4. from  django.views.generic  import  * class  NewWub(CreateView):    model  =  Wub class  DeleteWub(DeleteView):    model  =  Wub class  UpdateWub(UpdateView):    model  =  Wub Wednesday, 2 November 11 Generic views had ways of editing, creating and deleting, so you get that too. Write a suitable template, and EVERYTHING else is automatic, using a default form. If you want a different form, you can override that easily enough. Sounds good, right?
  • 5. Wednesday, 2 November 11 Unfortunately you are about to enter a world of pain. Let’s consider an example that isn’t completely trivial.
  • 6. from  django.db  import  models class  WubFlock(models.Model):    name  =  models.CharField(…) class  Wub(models.Model):    name  =  models.CharField(…)    flock  =  models.ForeignKey(        WubFlock,  related_name=‘wubs’) Wednesday, 2 November 11 Okay, so we have a flock of Wubs. We’re building a wub management interface, so we’ll need to create new wubs within a flock. Except…oh no. You can’t do that.
  • 7. from  django.views.generic  import  * class  NewFlockWub(CreateView):    model  =  Wub    #  what  goes  here? Wednesday, 2 November 11 Okay, so you want to customise the form so it’ll exclude the “flock” field, but set it pre-save to the flock this wub is going to be in. (Yes you could have a dropdown of all flocks, but then your designer will murder you in your sleep.)
  • 8. from  django.views.generic  import  * class  NewFlockWub(CreateView):    model  =  Wub    form_class  =          SomethingSomethingForm Wednesday, 2 November 11 Okay, so we’ll define the form somewhere. Only…
  • 9. from  django.views.generic  import  * class  NewFlockWub(CreateView):    model  =  Wub    form_class  =          SomethingSomethingForm    #  erm,  but  we  need  to  get    #  the  flock  object  for  the  form Wednesday, 2 November 11 You want to figure out the flock from the URL, say “/flock/my-awesome-wubs”. DetailView does this, and like all class-based views is built up of composable little pieces, so you could bring in SingleObjectMixin, which provides get_object to do this. Then we could override get_form_class to set up the form dynamically. But this behaviour is generic, so…
  • 10. from  moreviews  import  * class  NewFlockWub(BoundCreateView):    model  =  Wub    bound_field  =  ‘flock’    queryset  =  WubFlock.objects.all() Wednesday, 2 November 11 If your BoundCreateView isn’t this easy, you’re doing it wrong. “Bound” because the Wub is bound to the WubFlock.
  • 11. class  DeleteWub(DeleteView):    model  =  Wub class  F(forms.ModelForm):    class  Meta:        model  =  Wub        exclude  =  (‘flock’,) class  UpdateWub(UpdateView):    model  =  Wub    form_class  =  F Wednesday, 2 November 11 We don’t have to worry about binding for deleting, and for updating we just ensure we don’t change the “flock” field.
  • 12. Wednesday, 2 November 11 But what about the WubFlock? We should be able to create it AND ITS WUBS in one go. In traditional function views you’d do this with forms and formsets within the same request. So we want to do the same thing in class-based views.
  • 13. class  ProcessMultipleFormsMixin:    """Modify  GET  and  POST  behaviour   to  construct  and  process  multiple   forms  in  one  go.  There's  always  a   primary  form,  which  is  a  ModelForm. By  the  time  secondary  forms  are   saved,  self.new_object  on  the  view   will  contain  the  primary  object,  ie   the  object  that  the  primary  form   operates  on.""" Wednesday, 2 November 11 This isn’t one of the five classes, this is just a mixin. It’s not named perfectly, because although it DOES process multiple forms at once, it assumes one is the main form. This allows us to save the PRINCIPAL object, and then leaves a reference for all the other forms to use.
  • 14. from  django.views.generic  import  * class  NewFlock(MultiCreateView):    model  =  WubFlock    forms_models  =  [        {            ‘model’:  Wub,            ‘extra’:  1,        }    ] Wednesday, 2 November 11 This syntax is a little opaque, but it didn’t seem worth creating yet more classes just as helpers when we have lists and dictionaries. The exclude of the project field in the ModelForm for making little Wubs is taken care of for you.
  • 15. from  django.views.generic  import  * class  UpdateFlock(MultiUpdateView):    model  =  WubFlock    forms_models  =  [        {            ‘model’:  Wub,            ‘extra’:  1,            ‘can_delete’:  True,        }    ] Wednesday, 2 November 11 extra and can_delete here are both passed through to modelformset_factory. You can also set form inside the dictionary so you don’t just get a default ModelForm but can customise to your heart’s content.
  • 16. from  django.views.generic  import  * class  UpdateFlock(MultiUpdateView):    …    def  get_forms(self):        class  WubInlineForm(ModelForm):            def  save(self):                #  perhaps  we  default  the                  #  name  based  on  the  flock?        self.forms_models[0][‘form’]              =  WubInlineForm        return  super(...)() Wednesday, 2 November 11 You can even dynamically adjust things if you so choose. In fact, you can avoid forms_models by implementing get_forms directly if you prefer.
  • 17. from  django.views.generic  import  * class  DeleteFlock(DeleteView):    model  =  WubFlock Wednesday, 2 November 11 And of course deleting a flock will delete its wubs via the evil of the ORM’s implementing CASCADE DELETE itself.
  • 18. Wednesday, 2 November 11 There’s also a variant which will allow you to create an object bound to another but which itself has children bound to it. It’s imaginatively called MultiBoundCreateView. That’s not one of the five, that’s a bonus one. However now we need to think about non-editing views again, because we’ve still got a problem.
  • 19. from  django.views.generic  import  * class  Wubs(ListView):    model  =  Wub class  Flock(DetailView):    model  =  WubFlock Wednesday, 2 November 11 This is fine until you have a flock with three thousand wubs in. And wubs are very gregarious.
  • 20. from  django.views.generic  import  * class  Wubs(ListView):    model  =  Wub    paginate_by  =  10 class  Flock(DetailView):    model  =  WubFlock Wednesday, 2 November 11 ListView has pagination built in. Wouldn’t it be nice if we could do that for the CHILDREN bound to the object in a DetailView?
  • 21. from  django.views.generic  import  * from  moreviews  import  * class  Wubs(ListView):    model  =  Wub    paginate_by  =  10 class  Flock(DetailListView):    model  =  WubFlock    paginate_by  =  10 Wednesday, 2 November 11 This is the fourth view. Warning: this exists, deep within the Artfinder codebase, but isn’t re- usable yet.
  • 22. Wednesday, 2 November 11 We’re not finished yet.
  • 23. From:  Your  Boss  <phb@myco.co.com> To:  You  <peon@myco.co.com> Subject:  Permissions Yo  dawg.  I  was  over  at  Big  Client   Co  this  morning  and  noticed  that   they’re  able  to  edit  the  WubFlock   for  the  Sinister  Government   Agency.  This  contains  proprietary   Wub  technology,  and  geese,  so  THIS   SHOULD  NOT  BE  ALLOWED. Wednesday, 2 November 11 Maybe your site allows everyone to see and do everything. Most don’t. What you used to do with function views was to check the permission at the top of the view and return a 403. In class-based views you need to do that around get_object, which gives you direct access to the object.
  • 24. def  editable():    def  decorator(fn):        @wraps(fn,  assigned=available_attrs(fn))        def  _wrapped_fn(self,  *args,  **kwargs):            obj  =  fn(self,  *args,  **kwargs)            if  obj.editable(self.request.user):                return  obj            else:                raise  HttpForbidden()        return  _wrapped_fn    return  decorator class  _UpdateFlock(UpdateFlock):    get_object  =          editable(UpdateFlock.get_object) Wednesday, 2 November 11 Oh god oh god I want to die. Ignore that decorators make most people’s heads hurt, just LOOK AT THAT CODE AT THE BOTTOM.
  • 25. class  _UpdateFlock(UpdateFlock):    get_object  =          editable(            UpdateFlock.get_object        ) Wednesday, 2 November 11 There is no way this is going to look nice, no matter how many lines we wrap it onto. So this is the fifth class…that I’m really hoping someone has written. I want to write something like the following.
  • 26. class  _UpdateFlock(UpdateFlock):    def  allowed(self):        return  self.object.editable(            self.request.user        ) Wednesday, 2 November 11 Which is basically the same but not ugly. I guess what we actually want here is a permissions- activating mixin.
  • 27. class  _UpdateFlock(    UpdateFlock,    SingleObjectPermissionsMixin):    def  allowed(self):        return  self.object.editable(            self.request.user        ) Wednesday, 2 November 11
  • 28. class  SingleObjectPermissionsMixin(    object):    def  get_object(self):        obj  =  super(            SingleObjectPermissionsMixin,            self).get_object()        if  not  self.allowed(obj):            raise  HttpForbidden()        return  obj    def  allowed(self):        raise  NotImplementedError() Wednesday, 2 November 11 So this would be the fifth class. I haven’t written this, but I assume someone has. I suspect as I have it here, there are lots of problems, not least that HttpForbidden doesn’t exist in Django itself and so you’re dependent on something else to not only provide it but catch it in middleware and do something sensible with it.
  • 29. Summary • BoundCreateView for making Wubs • MultiCreateView for making WubFlocks • MultiUpdateView to update WubFlocks • DetailListView for paginating child objects • SingleObjectPermissionsMixin is mythical Wednesday, 2 November 11 The first three are written, but Ben caught me by surprise by asking me to talk, so they aren’t packaged and they’re not directly tested. DetailListView needs extracting from well-used internal code. The permissions stuff is entirely speculative; maybe we don’t need it.
  • 30. Questions? • James Aylett • @jaylett • artfinder.com • http://bit.ly/djugl-art Wednesday, 2 November 11

×