Overcoming Troubles with Class-Based (Generic) Views

767450215aa2f669a3dcaefa5beb4643?s=47 Andrew Pinkham
September 05, 2013

Overcoming Troubles with Class-Based (Generic) Views

An introduction to Class-Based Views and Class-Based Generic Views. Talk given at DjangoCon 2013.

767450215aa2f669a3dcaefa5beb4643?s=128

Andrew Pinkham

September 05, 2013
Tweet

Transcript

  1. Overcoming Troubles with Class Based (Generic) Views A short guide

    to managing the beast
  2. afrg.co/otwcbv

  3. shameless

  4. © DREAMWORKS 2010

  5. Basic Project

  6. Project Description Two Models: Account & Transaction Account: slug, get_absolute_url()

    Transaction: two FK’s to Account ModelForms for both
  7. Pages Account List Account Detail Account Create Transaction Create

  8. Refactoring to Class-Based Views

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

  10. # 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})
  11. # 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})
  12. # 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})
  13. # 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})
  14. # 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<slug>[\w\-]+)/$', AccountDetail.as_view(), name='bank_account_detail'), url(r'^transaction/$', TransactionCreate.as_view(), name='bank_trans_create'), )
  15. Taking advantage of CBVs

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

    redirect = ''
  17. # 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})
  18. # 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'
  19. # 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
  20. # 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
  21. # 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})
  22. Class-Based Generic Views

  23. # 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')
  24. 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<slug>[\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'), )
  25. Quick Recap

  26. Customizing CBGVs

  27. Feature Changes List Accounts and Transactions in AccountList Split TransactionCreate

    Send To link Send From link Generate Slug Automatically
  28. None
  29. None
  30. Multiple Inheritance New Style-classes (object) in Python 2.2 C3 MRO

    in Python 2.3
  31. None
  32. None
  33. None
  34. @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
  35. 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)
  36. def __init__(self, **kwargs): for key, value in six.iteritems(kwargs): setattr(self, key,

    value)
  37. 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)
  38. 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)
  39. © TECHNABOB.COM 2012

  40. CBGV TemplateView RedirectView DetailView ListView FormView CreateView UpdateView DeleteView ArchiveIndexView

    YearArchiveView MonthArchiveView WeekArchiveView DayArchiveView TodayArchiveView DateDetailView
  41. None
  42. © DISNEY 2012

  43. © MARVEL 2013

  44. None
  45. © WARNER BROS 1942

  46. © MGM 1995

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

  48. # 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
  49. Automating Slug slugify() clean()

  50. 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
  51. 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
  52. Saving slug django-src/forms/models.py ModelForm ModelFormMetaclass BaseModelForm

  53. Saving slug BaseModelForm.save() save_instance save_m2m construct_instance

  54. 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
  55. Transaction Forms To Account From Account

  56. Overrides Introduce two new forms get_initial() get_context_data() view() -> dispatch()

    -> get()/post() get_success_url()
  57. class TransactionCreate(CreateView): model = Transaction

  58. 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)
  59. 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
  60. 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)
  61. 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')
  62. Congratulations!

  63. © DREAMWORKS 2010

  64. Take Aways CBV ≠ CBGV Full understanding: MRO, View ccbv.co.uk

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

    CBGV: Lasagna is a sometimes food
  67. Thank you! afrg.co/otwcbv @andrewsforge