Design by minimalism

Tom Christie, DabAPPs

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



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

Form data populated directly

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



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):
        return super(AccountActivationView, self).form_valid(form)

Write this...

    def form_valid(self, form):
        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,)



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):
	        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 = return HttpResponseRedirect(account.account_activated_url())


Made with