Browse Source

Fixed #16807 - Added a class-based views intro.

Thanks Preston Holmes for the text.
Tim Graham 12 years ago
parent
commit
2108941677

+ 1 - 65
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.

+ 289 - 0
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':
+            # <view logic>
+            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):
+            # <view logic>
+            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 </topics/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():
+                # <process form cleaned data>
+                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():
+                # <process form cleaned data>
+                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.

+ 0 - 1
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`