Slide 1

Slide 1 text

Overcoming Troubles with Class Based (Generic) Views A short guide to managing the beast

Slide 2

Slide 2 text

afrg.co/otwcbv

Slide 3

Slide 3 text

shameless

Slide 4

Slide 4 text

© DREAMWORKS 2010

Slide 5

Slide 5 text

Basic Project

Slide 6

Slide 6 text

Project Description Two Models: Account & Transaction Account: slug, get_absolute_url() Transaction: two FK’s to Account ModelForms for both

Slide 7

Slide 7 text

Pages Account List Account Detail Account Create Transaction Create

Slide 8

Slide 8 text

Refactoring to Class-Based Views

Slide 9

Slide 9 text

# bank/views.py from django.views.generic import View

Slide 10

Slide 10 text

# bank/views.py class AccountList(View): def get(self, request): acct_list = Account.objects.all() return render(request, 'bank/account_list.html', {'account_list': acct_list}) class AccountDetail(View): def get(self, request, slug): acct = get_object_or_404(Account, slug=slug) return render(request, 'bank/account_detail.html', {'account': acct})

Slide 11

Slide 11 text

# bank/views.py class AccountCreate(View): def get(self, request): form = AccountForm() return render(request, 'bank/account_form.html', {'form': form}) def post(self, request): form = AccountForm(request.POST) if form.is_valid(): new_acct = form.save() return redirect(new_acct) else: return render(request, 'bank/account_form.html', {'form': form})

Slide 12

Slide 12 text

# bank/views.py class TransactionCreate(View): def get(self, request): form = TransactionForm() return render(request, 'bank/account_form.html', {'form': form}) def post(self, request): form = TransactionForm(request.POST) if form.is_valid(): new_acct = form.save() return redirect('bank_account_list') else: return render(request, 'bank/account_form.html', {'form': form})

Slide 13

Slide 13 text

# bank/views.py class TransactionCreate(View): def get(self, request): form = TransactionForm() return render(request, 'bank/account_form.html', {'form': form}) def post(self, request): form = TransactionForm(request.POST) if form.is_valid(): new_acct = form.save() return redirect('bank_account_list') else: return render(request, 'bank/account_form.html', {'form': form})

Slide 14

Slide 14 text

# bank/urls.py from django.conf.urls import patterns, url from .models import Account from .views import AccountList, AccountDetail, \ AccountCreate, TransactionCreate urlpatterns = patterns('', url(r'^account/$', AccountList.as_view(), name='bank_account_list'), url(r'^account/create/$', AccountCreate.as_view(), name='bank_account_create'), url(r'^account/(?P[\w\-]+)/$', AccountDetail.as_view(), name='bank_account_detail'), url(r'^transaction/$', TransactionCreate.as_view(), name='bank_trans_create'), )

Slide 15

Slide 15 text

Taking advantage of CBVs

Slide 16

Slide 16 text

# bank/views.py class PostFormMixin(object): form = None template = '' redirect = ''

Slide 17

Slide 17 text

# bank/views.py class PostFormMixin(object): ... def get(self, request): form = self.form() return render(request, self.template, {'form': form}) def post(self, request): form = self.form(request.POST) if form.is_valid(): new_obj = form.save() if self.redirect: return redirect(self.redirect) else: return redirect(new_obj) else: return render(request, self.template, {'form': form})

Slide 18

Slide 18 text

# bank/views.py class AccountCreate(PostFormMixin, View): form = AccountForm template = 'bank/account_form.html' class TransactionCreate(PostFormMixin, View): form = TransactionForm template = 'bank/account_form.html' redirect = 'bank_account_list'

Slide 19

Slide 19 text

# bank/views.py class PostFormMixin(object): ... def get_template(self): if self.template == '': raise ImproperlyConfigured( '"template" variable not defined in %s' % self.__class__.__name__) return self.template

Slide 20

Slide 20 text

# bank/views.py class PostFormMixin(object): ... def get_redirect_url(self,obj): if self.redirect: url = self.redirect else: try: url = obj.get_absolute_url() except AttributeError: raise ImproperlyConfigured( '"redirect" variable must be defined ' 'in %s when redirecting %s objects.' % (self.__class__.__name__, obj.__class__.__name__)) return url

Slide 21

Slide 21 text

# bank/views.py class PostFormMixin(object): ... def get(self, request): form = self.form() return render(request, self.get_template(), {'form': form}) def post(self, request): form = self.form(request.POST) if form.is_valid(): new_obj = form.save() return redirect(self.get_redirect_url(new_obj)) else: return render(request, self.get_template(), {'form': form})

Slide 22

Slide 22 text

Class-Based Generic Views

Slide 23

Slide 23 text

# bank/views.py from django.core.urlresolvers import reverse_lazy from django.views.generic import (ListView, DetailView, CreateView) from .models import Account, Transaction class AccountList(ListView): model = Account class AccountDetail(DetailView): model = Account class AccountCreate(CreateView): model = Account class TransactionCreate(CreateView): model = Transaction success_url = reverse_lazy('bank_account_list')

Slide 24

Slide 24 text

from django.conf.urls import patterns, url from django.core.urlresolvers import reverse_lazy from django.views.generic import (ListView, DetailView, CreateView) from .models import Account, Transaction urlpatterns = patterns('', url(r'^account/$', ListView.as_view(model=Account), name='bank_account_list'), url(r'^account/create/$', CreateView.as_view(model=Account), name='bank_account_create'), url(r'^account/(?P[\w\-]+)/$', DetailView.as_view(model=Account), name='bank_account_detail'), url(r'^transaction/$', CreateView.as_view(model=Transaction, success_url=reverse_lazy( 'bank_account_list')), name='bank_trans_create'), )

Slide 25

Slide 25 text

Quick Recap

Slide 26

Slide 26 text

Customizing CBGVs

Slide 27

Slide 27 text

Feature Changes List Accounts and Transactions in AccountList Split TransactionCreate Send To link Send From link Generate Slug Automatically

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

No content

Slide 30

Slide 30 text

Multiple Inheritance New Style-classes (object) in Python 2.2 C3 MRO in Python 2.3

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

No content

Slide 33

Slide 33 text

No content

Slide 34

Slide 34 text

@classonlymethod def as_view(cls, **initkwargs): for key in initkwargs: if key in cls.http_method_names: raise TypeError('...') if not hasattr(cls, key): raise TypeError('...') def view(request, *args, **kwargs): ... update_wrapper(view, cls, updated=()) update_wrapper(view, cls.dispatch, assigned=()) return view

Slide 35

Slide 35 text

def as_view(cls, **initkwargs): ... def view(request, *args, **kwargs): self = cls(**initkwargs) if hasattr(self, 'get') and not hasattr(self, 'head'): self.head = self.get self.request = request self.args = args self.kwargs = kwargs return self.dispatch(request, *args, **kwargs)

Slide 36

Slide 36 text

def __init__(self, **kwargs): for key, value in six.iteritems(kwargs): setattr(self, key, value)

Slide 37

Slide 37 text

def dispatch(self, request, *args, **kwargs): if request.method.lower() in self.http_method_names: handler = getattr(self, request.method.lower(), self.http_method_not_allowed) else: handler = self.http_method_not_allowed return handler(request, *args, **kwargs)

Slide 38

Slide 38 text

http_method_names = ['get', 'post', 'put', 'delete', 'head', 'options', 'trace'] def dispatch(self, request, *args, **kwargs): if request.method.lower() in self.http_method_names: handler = getattr(self, request.method.lower(), self.http_method_not_allowed) else: handler = self.http_method_not_allowed return handler(request, *args, **kwargs)

Slide 39

Slide 39 text

© TECHNABOB.COM 2012

Slide 40

Slide 40 text

CBGV TemplateView RedirectView DetailView ListView FormView CreateView UpdateView DeleteView ArchiveIndexView YearArchiveView MonthArchiveView WeekArchiveView DayArchiveView TodayArchiveView DateDetailView

Slide 41

Slide 41 text

No content

Slide 42

Slide 42 text

© DISNEY 2012

Slide 43

Slide 43 text

© MARVEL 2013

Slide 44

Slide 44 text

No content

Slide 45

Slide 45 text

© WARNER BROS 1942

Slide 46

Slide 46 text

© MGM 1995

Slide 47

Slide 47 text

Transaction List ListView.get() ListView.get_context_data() MultipleObjectMixin ContextMixin

Slide 48

Slide 48 text

# bank/views.py class AccountList(ListView): model = Account def get_context_data(self, **kwargs): context = super(AccountList, self)\ .get_context_data(**kwargs) context['transaction_list'] = Transaction\ .objects\ .order_by('-date')\ [:10] return context

Slide 49

Slide 49 text

Automating Slug slugify() clean()

Slide 50

Slide 50 text

from django.forms import ModelForm from django.utils.text import slugify from .models import Account class AccountForm(ModelForm): class Meta: model = Account def clean(self): cleaned_data = super(AccountForm, self).clean() name = cleaned_data.get("name") slug = cleaned_data.get("slug") if not slug and name: cleaned_data['slug'] = slugify(name) return cleaned_data

Slide 51

Slide 51 text

from django.forms import ModelForm from django.utils.text import slugify from .models import Account class AccountForm(ModelForm): class Meta: model = Account exclude = ('slug',) def clean(self): cleaned_data = super(AccountForm, self).clean() name = cleaned_data.get("name") slug = cleaned_data.get("slug") if not slug and name: cleaned_data['slug'] = slugify(name) return cleaned_data

Slide 52

Slide 52 text

Saving slug django-src/forms/models.py ModelForm ModelFormMetaclass BaseModelForm

Slide 53

Slide 53 text

Saving slug BaseModelForm.save() save_instance save_m2m construct_instance

Slide 54

Slide 54 text

class AccountForm(ModelForm): class Meta: model = Account exclude = ('slug',) def save(self, commit=True): instance = super(AccountForm, self)\ .save(commit=False) instance.slug = slugify( self.cleaned_data\ .get('name', '')) if commit: instance.save() self.save_m2m() return instance

Slide 55

Slide 55 text

Transaction Forms To Account From Account

Slide 56

Slide 56 text

Overrides Introduce two new forms get_initial() get_context_data() view() -> dispatch() -> get()/post() get_success_url()

Slide 57

Slide 57 text

class TransactionCreate(CreateView): model = Transaction

Slide 58

Slide 58 text

class TransactionCreate(CreateView): ... def dispatch(self, request, *args, **kwargs): slug = kwargs.get('slug', None) account_obj = get_object_or_404(Account,\ slug__iexact=slug) self.acct_name = account_obj.name self.acct_pk = account_obj.pk self.acct_url = getattr(account_obj,\ 'get_absolute_url', '') return super(TransactionCreate, self)\ .dispatch(request, *args, **kwargs)

Slide 59

Slide 59 text

class TransactionCreate(CreateView): ... def get_initial(self): initial_data = super(TransactionCreate, self)\ .get_initial() if self.form_class == TransferFormFrom: initial_data['from_account'] = self.acct_pk elif self.form_class == TransferFormTo: initial_data['to_account'] = self.acct_pk else: raise ImproperlyConfigured( '"form_class" variable must be defined 'in %s for correct initial behavior.' % (self.__class__.__name__, obj.__class__.__name__)) return initial_data

Slide 60

Slide 60 text

class TransactionCreate(CreateView): ... def get_context_data(self, **kwargs): context = {} context['account_name'] = self.acct_name if self.form_class == TransferFormFrom: context['trans_dir'] = 'From:' elif self.form_class == TransferFormTo: context['trans_dir'] = 'To:' else: raise ImproperlyConfigured( '"form_class" variable must be defined 'in %s for correct initial behavior.' % (self.__class__.__name__, obj.__class__.__name__)) context.update(kwargs) return super(TransactionCreate, self)\ .get_context_data(**context)

Slide 61

Slide 61 text

class TransactionCreate(CreateView): ... def get_success_url(self): if (hasattr(self, 'acct_url') and self.acct_url != ''): return self.acct_url() else: return reverse('bank_account_list')

Slide 62

Slide 62 text

Congratulations!

Slide 63

Slide 63 text

© DREAMWORKS 2010

Slide 64

Slide 64 text

Take Aways CBV ≠ CBGV Full understanding: MRO, View ccbv.co.uk CBGV: Lasagna is a sometimes food

Slide 65

Slide 65 text

No content

Slide 66

Slide 66 text

Take Aways CBV ≠ CBGV Full understanding: MRO, View ccbv.co.uk CBGV: Lasagna is a sometimes food

Slide 67

Slide 67 text

Thank you! afrg.co/otwcbv @andrewsforge