123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327 |
- =================================
- 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
- 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 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 returns the ``form_class`` attribute of the class.
- This gives you several options for specifying what form to use, from an
- 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 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 returns a
- function that can be called when a request arrives for a URL matching the
- associated pattern. The function creates an instance of the class, calls
- :meth:`~django.views.generic.base.View.setup` to initialize its attributes, and
- then 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.urls import path
- from myapp.views import MyView
- urlpatterns = [
- path("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 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(GreetingView):
- 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 = [
- path("about/", GreetingView.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 django.views import View
- 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 minimal 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 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
- ---------------------
- You can adjust class-based views by decorating 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 = [
- path("about/", login_required(TemplateView.as_view(template_name="secret.html"))),
- path("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().dispatch(*args, **kwargs)
- Or, more succinctly, you can decorate the class instead and pass the name
- of the method to be decorated as the keyword argument ``name``::
- @method_decorator(login_required, name="dispatch")
- class ProtectedView(TemplateView):
- template_name = "secret.html"
- If you have a set of common decorators used in several places, you can define
- a list or tuple of decorators and use this instead of invoking
- ``method_decorator()`` multiple times. These two classes are equivalent::
- decorators = [never_cache, login_required]
- @method_decorator(decorators, name="dispatch")
- class ProtectedView(TemplateView):
- template_name = "secret.html"
- @method_decorator(never_cache, name="dispatch")
- @method_decorator(login_required, name="dispatch")
- class ProtectedView(TemplateView):
- template_name = "secret.html"
- The decorators will process a request in the order they are passed to the
- decorator. In the example, ``never_cache()`` will process the request before
- ``login_required()``.
- In this example, every instance of ``ProtectedView`` will have login
- protection. These examples use ``login_required``, however, the same behavior
- can be obtained by using
- :class:`~django.contrib.auth.mixins.LoginRequiredMixin`.
- .. 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.
|