From 210894167799780283101636c99d8010b30bf09c Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 27 Feb 2013 07:12:37 -0500 Subject: [PATCH] Fixed #16807 - Added a class-based views intro. Thanks Preston Holmes for the text. --- docs/topics/class-based-views/index.txt | 66 +----- docs/topics/class-based-views/intro.txt | 289 +++++++++++++++++++++++ docs/topics/class-based-views/mixins.txt | 1 - 3 files changed, 290 insertions(+), 66 deletions(-) create mode 100644 docs/topics/class-based-views/intro.txt diff --git a/docs/topics/class-based-views/index.txt b/docs/topics/class-based-views/index.txt index 302f473eea..b2fa93e05f 100644 --- a/docs/topics/class-based-views/index.txt +++ b/docs/topics/class-based-views/index.txt @@ -14,6 +14,7 @@ reusable views which suits your use case. For full details, see the .. toctree:: :maxdepth: 1 + intro generic-display generic-editing mixins @@ -127,68 +128,3 @@ the client issues a ``HEAD`` request, the response has an empty body and the ``Last-Modified`` header indicates when the most recent book was published. Based on this information, the client may or may not download the full object list. - -Decorating class-based views -============================ - -.. highlightlang:: python - -Since class-based views aren't functions, decorating them works differently -depending on if you're using ``as_view`` or creating a subclass. - -Decorating in URLconf ---------------------- - -The simplest way of decorating class-based views is to decorate the -result of the :meth:`~django.views.generic.base.View.as_view` method. -The easiest place to do this is in the URLconf where you deploy your view:: - - from django.contrib.auth.decorators import login_required, permission_required - from django.views.generic import TemplateView - - from .views import VoteView - - urlpatterns = patterns('', - (r'^about/', login_required(TemplateView.as_view(template_name="secret.html"))), - (r'^vote/', permission_required('polls.can_vote')(VoteView.as_view())), - ) - -This approach applies the decorator on a per-instance basis. If you -want every instance of a view to be decorated, you need to take a -different approach. - -.. _decorating-class-based-views: - -Decorating the class --------------------- - -To decorate every instance of a class-based view, you need to decorate -the class definition itself. To do this you apply the decorator to the -:meth:`~django.views.generic.base.View.dispatch` method of the class. - -A method on a class isn't quite the same as a standalone function, so -you can't just apply a function decorator to the method -- you need to -transform it into a method decorator first. The ``method_decorator`` -decorator transforms a function decorator into a method decorator so -that it can be used on an instance method. For example:: - - from django.contrib.auth.decorators import login_required - from django.utils.decorators import method_decorator - from django.views.generic import TemplateView - - class ProtectedView(TemplateView): - template_name = 'secret.html' - - @method_decorator(login_required) - def dispatch(self, *args, **kwargs): - return super(ProtectedView, self).dispatch(*args, **kwargs) - -In this example, every instance of ``ProtectedView`` will have -login protection. - -.. note:: - - ``method_decorator`` passes ``*args`` and ``**kwargs`` - as parameters to the decorated method on the class. If your method - does not accept a compatible set of parameters it will raise a - ``TypeError`` exception. diff --git a/docs/topics/class-based-views/intro.txt b/docs/topics/class-based-views/intro.txt new file mode 100644 index 0000000000..5868b6be03 --- /dev/null +++ b/docs/topics/class-based-views/intro.txt @@ -0,0 +1,289 @@ +================================= +Introduction to Class-based views +================================= + +Class-based views provide an alternative way to implement views as Python +objects instead of functions. They do not replace function-based views, but +have certain differences and advantages when compared to function-based views: + +* Organization of code related to specific HTTP methods (``GET``, ``POST``, + etc) can be addressed by separate methods instead of conditional branching. + +* Object oriented techniques such as mixins (multiple inheritance) can be + used to factor code into reusable components. + +The relationship and history of generic views, class-based views, and class-based generic views +=============================================================================================== + +In the beginning there was only the view function contract, Django passed your +function an :class:`~django.http.HttpRequest` and expected back an +:class:`~django.http.HttpResponse`. This was the extent of what Django provided. + +Early on it was recognized that there were common idioms and patterns found in +view development. Function-based generic views were introduced to abstract +these patterns and ease view development for the common cases. + +The problem with function-based generic views is that while they covered the +simple cases well, there was no way to extend or customize them beyond some +simple configuration options, limiting their usefulness in many real-world +applications. + +Class-based generic views were created with the same objective as +function-based generic views, to make view development easier. However, the way +the solution is implemented, through the use of mixins, provides a toolkit that +results in class-based generic views being more extensible and flexible than +their function-based counterparts. + +If you have tried function based generic views in the past and found them +lacking, you should not think of class-based generic views as simply a +class-based equivalent, but rather as a fresh approach to solving the original +problems that generic views were meant to solve. + +The toolkit of base classes and mixins that Django uses to build class-based +generic views are built for maximum flexibility, and as such have many hooks in +the form of default method implementations and attributes that you are unlikely +to be concerned with in the simplest use cases. For example, instead of +limiting you to a class based attribute for ``form_class``, the implementation +uses a ``get_form`` method, which calls a ``get_form_class`` method, which in +its default implementation just returns the ``form_class`` attribute of the +class. This gives you several options for specifying what form to use, from a +simple attribute, to a fully dynamic, callable hook. These options seem to add +hollow complexity for simple situations, but without them, more advanced +designs would be limited. + +Using class-based views +======================= + +At its core, a class-based view allows you to respond to different HTTP request +methods with different class instance methods, instead of with conditionally +branching code inside a single view function. + +So where the code to handle HTTP ``GET`` in a view function would look +something like:: + + from django.http import HttpResponse + + def my_view(request): + if request.method == 'GET': + # + return HttpResponse('result') + +In a class-based view, this would become:: + + from django.http import HttpResponse + from django.views.base import View + + class MyView(View): + def get(self, request): + # + return HttpResponse('result') + +Because Django's URL resolver expects to send the request and associated +arguments to a callable function, not a class, class-based views have an +:meth:`~django.views.generic.base.View.as_view` class method which serves as +the callable entry point to your class. The ``as_view`` entry point creates an +instance of your class and calls its +:meth:`~django.views.generic.base.View.dispatch` method. ``dispatch`` looks at +the request to determine whether it is a ``GET``, ``POST``, etc, and relays the +request to a matching method if one is defined, or raises +:class:`~django.http.HttpResponseNotAllowed` if not:: + + # urls.py + from django.conf.urls import patterns + from myapp.views import MyView + + urlpatterns = patterns('', + (r'^about/', MyView.as_view()), + ) + + +It is worth noting that what your method returns is identical to what you +return from a function-based view, namely some form of +:class:`~django.http.HttpResponse`. This means that +:doc:`http shortcuts ` or +:class:`~django.template.response.TemplateResponse` objects are valid to use +inside a class-based view. + +While a minimal class-based view does not require any class attributes to +perform its job, class attributes are useful in many class-based designs, +and there are two ways to configure or set class attributes. + +The first is the standard Python way of subclassing and overriding attributes +and methods in the subclass. So that if your parent class had an attribute +``greeting`` like this:: + + from django.http import HttpResponse + from django.views.base import View + + class GreetingView(View): + greeting = "Good Day" + + def get(self, request): + return HttpResponse(self.greeting) + +You can override that in a subclass:: + + class MorningGreetingView(MyView): + greeting = "Morning to ya" + +Another option is to configure class attributes as keyword arguments to the +:meth:`~django.views.generic.base.View.as_view` call in the URLconf:: + + urlpatterns = patterns('', + (r'^about/', MyView.as_view(greeting="G'day")), + ) + +.. note:: + + While your class is instantiated for each request dispatched to it, class + attributes set through the + :meth:`~django.views.generic.base.View.as_view` entry point are + configured only once at the time your URLs are imported. + +Using mixins +============ + +Mixins are a form of multiple inheritance where behaviors and attributes of +multiple parent classes can be combined. + +For example, in the generic class-based views there is a mixin called +:class:`~django.views.generic.base.TemplateResponseMixin` whose primary purpose +is to define the method +:meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`. +When combined with the behavior of the :class:`~django.views.generic.base.View` +base class, the result is a :class:`~django.views.generic.base.TemplateView` +class that will dispatch requests to the appropriate matching methods (a +behavior defined in the ``View`` base class), and that has a +:meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response` +method that uses a +:attr:`~django.views.generic.base.TemplateResponseMixin.template_name` +attribute to return a :class:`~django.template.response.TemplateResponse` +object (a behavior defined in the ``TemplateResponseMixin``). + +Mixins are an excellent way of reusing code across multiple classes, but they +come with some cost. The more your code is scattered among mixins, the harder +it will be to read a child class and know what exactly it is doing, and the +harder it will be to know which methods from which mixins to override if you +are subclassing something that has a deep inheritance tree. + +Note also that you can only inherit from one generic view - that is, only one +parent class may inherit from :class:`~django.views.generic.base.View` and +the rest (if any) should be mixins. Trying to inherit from more than one class +that inherits from ``View`` - for example, trying to use a form at the top of a +list and combining :class:`~django.views.generic.edit.ProcessFormView` and +:class:`~django.views.generic.list.ListView` - won't work as expected. + +Handling forms with class-based views +===================================== + +A basic function-based view that handles forms may look something like this:: + + from django.http import HttpResponseRedirect + from django.shortcuts import render + + from .forms import MyForm + + def myview(request): + if request.method == "POST": + form = MyForm(request.POST) + if form.is_valid(): + # + return HttpResponseRedirect('/success/') + else: + form = MyForm(initial={'key': 'value'}) + + return render(request, 'form_template.html', {'form': form}) + +A similar class-based view might look like:: + + from django.http import HttpResponseRedirect + from django.shortcuts import render + + from .forms import MyForm + + class MyFormView(View): + form_class = MyForm + initial = {'key': 'value'} + template_name = 'form_template.html' + + def get(self, request, *args, **kwargs): + form = self.form_class(initial=self.initial) + return render(request, self.template_name, {'form': form}) + + def post(self, request, *args, **kwargs): + form = self.form_class(request.POST) + if form.is_valid(): + # + return HttpResponseRedirect('/success/') + + return render(request, self.template_name, {'form': form}) + +This is a very simple case, but you can see that you would then have the option +of customizing this view by overriding any of the class attributes, e.g. +``form_class``, via URLconf configuration, or subclassing and overriding one or +more of the methods (or both!). + +Decorating class-based views +============================ + +The extension of class-based views isn't limited to using mixins. You +can use also use decorators. Since class-based views aren't functions, +decorating them works differently depending on if you're using ``as_view`` or +creating a subclass. + +Decorating in URLconf +--------------------- + +The simplest way of decorating class-based views is to decorate the +result of the :meth:`~django.views.generic.base.View.as_view` method. +The easiest place to do this is in the URLconf where you deploy your view:: + + from django.contrib.auth.decorators import login_required, permission_required + from django.views.generic import TemplateView + + from .views import VoteView + + urlpatterns = patterns('', + (r'^about/', login_required(TemplateView.as_view(template_name="secret.html"))), + (r'^vote/', permission_required('polls.can_vote')(VoteView.as_view())), + ) + +This approach applies the decorator on a per-instance basis. If you +want every instance of a view to be decorated, you need to take a +different approach. + +.. _decorating-class-based-views: + +Decorating the class +-------------------- + +To decorate every instance of a class-based view, you need to decorate +the class definition itself. To do this you apply the decorator to the +:meth:`~django.views.generic.base.View.dispatch` method of the class. + +A method on a class isn't quite the same as a standalone function, so +you can't just apply a function decorator to the method -- you need to +transform it into a method decorator first. The ``method_decorator`` +decorator transforms a function decorator into a method decorator so +that it can be used on an instance method. For example:: + + from django.contrib.auth.decorators import login_required + from django.utils.decorators import method_decorator + from django.views.generic import TemplateView + + class ProtectedView(TemplateView): + template_name = 'secret.html' + + @method_decorator(login_required) + def dispatch(self, *args, **kwargs): + return super(ProtectedView, self).dispatch(*args, **kwargs) + +In this example, every instance of ``ProtectedView`` will have +login protection. + +.. note:: + + ``method_decorator`` passes ``*args`` and ``**kwargs`` + as parameters to the decorated method on the class. If your method + does not accept a compatible set of parameters it will raise a + ``TypeError`` exception. diff --git a/docs/topics/class-based-views/mixins.txt b/docs/topics/class-based-views/mixins.txt index 4941ea9755..2adbd406c7 100644 --- a/docs/topics/class-based-views/mixins.txt +++ b/docs/topics/class-based-views/mixins.txt @@ -32,7 +32,6 @@ Two central mixins are provided that help in providing a consistent interface to working with templates in class-based views. :class:`~django.views.generic.base.TemplateResponseMixin` - Every built in view which returns a :class:`~django.template.response.TemplateResponse` will call the :meth:`~django.views.generic.base.TemplateResponseMixin.render_to_response`