Design by minimalism

Tom Christie, DabAPPs


https://github.com/tomchristie
https://twitter.com/_tomchristie

Django Vanilla Views


  • Aim to provide the exact same set of functionality as existing GCBVs.
  • But with a simplified API and implementation.
 

Simple Class Hierarchy


    
    View --+------------------------- RedirectView
           |
           +-- GenericView -------+-- TemplateView
           |                      |
           |                      +-- FormView
           |
           +-- GenericModelView --+-- ListView
                                  |
                                  +-- DetailView
                                  |
                                  +-- CreateView
                                  |
                                  +-- UpdateView
                                  |
                                  +-- DeleteView
    

Less magical behavioUr

Cascading set of options.

  1. `.template_name` attribute on the view.
  2. `.template_name_field` set on the `.object`.
  3. `{app}/{model}{suffix}.html` based on the `.object`.
  4. `{app}/{model}{suffix}.html` based on the `.model`.
  5. Configuration error.

Explicit Template name or fall back to default.

  1. `.template_name_field` set on the `.object`.
  2. `{app}/{model}{suffix}.html` based on the `.model`.
  3. Configuration error.

 

More DIRECT Style

 

Form DATA POPULATED INDIRECTLY

    
    def get_form_kwargs(self):
        # ...
        if self.request.method in ('POST', 'PUT'):
            kwargs.update({
                'data': self.request.POST,
                'files': self.request.FILES,
            })
        return kwargs
    
 

Form data populated directly

    
    form = self.get_form(request.DATA, request.FILES)
    

LESS API

 

Let's remove all these

  • initial
  • prefix
  • get_initial()
  • get_prefix()
  • get_form_kwargs()

Just OVERRIDE `get_form()`


    def get_form(self, data, files, instance=None):
        user = self.request.user
        return AccountForm(data, files, user=user, instance=instance)

 

Let's remove all these

  • paginator_cls
  • paginate_orphans
  • get_paginate_orphans()

Just OVERRIDE `get_paginator()`


    def get_paginator(self, queryset, page_size):
        return CustomPaginator(queryset, page_size, orphans=3)

The network effect of constraints

  • Aim for a small set of often repeated patterns.
  • So we can all follow the well-trodden path.

The complexity feedback loop

 

  • Introducing extra API makes the implementation a little more complex.
  • In turn it becomes a little less obvious how to simply override the the behaviour.
  • Future users will tend to request extra API to make their lives easier.

In particular...

"Write your own class based views"

The Miuse of super

 

Calling super can obfuscate
what's actually happening.

 

Instead of this...

    
    def get_context_data(self, **kwargs):
        context = super(MyView, self).get_context_data(**kwargs)
        context['user'] = self.request.user
        return context

Write this...

        
    def get_context_data(self, **kwargs):
        kwargs['object'] = self.object
        kwargs['user'] = self.request.user
        return kwargs

 

Instead of this...

    
    def form_valid(self, form):
        send_activation_email(self.request.user)
        return super(AccountActivationView, self).form_valid(form)

Write this...

        
    def form_valid(self, form):
        send_activation_email(self.request.user)
        form.save()        
        return HttpResponseRedirect(self.success_url)

Why mixins Rarely make A good API

 

Instead of this...

    
    class MyView(LoginRequired, View):
        # ...

Aim for this...

        
    class MyView(View):
        permissions = (LoginRequired,)

Design by COMMITTEE

CAN tend to COMPLEXITY

Be an advocate for design restraint.

API as education

 
  • The CBVs are our bread and butter.
  • Recognise the knock on effects of our design choices.

The end result

 

Instead of this style...

 	class AccountCreateView(CreateView):
	    model = Account
	    form_class = AccountForm

	    def get_success_url(self):
	        return self.object.account_activated_url()

	    def get_form_kwargs(self):
	        kwargs = super(AccountView, self).get_form_kwargs()
	        kwargs['owner'] = self.request.user
	        return kwargs
 
	    def form_valid(self, form):
	        send_activation_email(self.request.user)
	        return super(AccountCreateView, self).form_valid(form)
 

Simpler, More Direct

   class AccountCreateView(CreateView):
      model = Account

def get_form(self, data=None, files=None, instance=None): owner = self.request.user return AccountForm(data, files, instance=instance, owner=owner) def form_valid(self, form): send_activation_email(self.request.user) account = form.save() return HttpResponseRedirect(account.account_activated_url())
 

Thanks

https://github.com/tomchristie/django-vanilla-views

@_tomchristie

Design by minimalism

By tomchristie

Design by minimalism

  • 5,300