Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #18804 - Reorganized class based views docs a bit; thanks antho…

…nyb for the initial patch.
  • Loading branch information...
commit df7c1a13a0a400747966098049093cebdac385bf 1 parent 01b9c3d
Tim Graham authored August 25, 2012
149  docs/topics/class-based-views/index.txt
@@ -11,8 +11,7 @@ to structure your views and reuse code by harnessing inheritance and
11 11
 mixins. There are also some generic views for simple tasks which we'll
12 12
 get to later, but you may want to design your own structure of
13 13
 reusable views which suits your use case. For full details, see the
14  
-:doc:`class-based views reference
15  
-documentation</ref/class-based-views/index>`.
  14
+:doc:`class-based views reference documentation</ref/class-based-views/index>`.
16 15
 
17 16
 .. toctree::
18 17
    :maxdepth: 1
@@ -32,41 +31,12 @@ redirect, and :class:`~django.views.generic.base.TemplateView` extends the base
32 31
 to make it also render a template.
33 32
 
34 33
 
35  
-Simple usage
36  
-============
37  
-
38  
-Class-based generic views (and any class-based views that inherit from
39  
-the base classes Django provides) can be configured in two
40  
-ways: subclassing, or passing in arguments directly in the URLconf.
41  
-
42  
-When you subclass a class-based view, you can override attributes
43  
-(such as the ``template_name``) or methods (such as ``get_context_data``)
44  
-in your subclass to provide new values or methods. Consider, for example,
45  
-a view that just displays one template, ``about.html``. Django has a
46  
-generic view to do this - :class:`~django.views.generic.base.TemplateView` -
47  
-so we can just subclass it, and override the template name::
48  
-
49  
-    # some_app/views.py
50  
-    from django.views.generic import TemplateView
51  
-
52  
-    class AboutView(TemplateView):
53  
-        template_name = "about.html"
54  
-
55  
-Then, we just need to add this new view into our URLconf. As the class-based
56  
-views themselves are classes, we point the URL to the ``as_view`` class method
57  
-instead, which is the entry point for class-based views::
58  
-
59  
-    # urls.py
60  
-    from django.conf.urls import patterns, url, include
61  
-    from some_app.views import AboutView
62  
-
63  
-    urlpatterns = patterns('',
64  
-        (r'^about/', AboutView.as_view()),
65  
-    )
  34
+Simple usage in your URLconf
  35
+============================
66 36
 
67  
-Alternatively, if you're only changing a few simple attributes on a
68  
-class-based view, you can simply pass the new attributes into the ``as_view``
69  
-method call itself::
  37
+The simplest way to use generic views is to create them directly in your
  38
+URLconf. If you're only changing a few simple attributes on a class-based view,
  39
+you can simply pass them into the ``as_view`` method call itself::
70 40
 
71 41
     from django.conf.urls import patterns, url, include
72 42
     from django.views.generic import TemplateView
@@ -75,93 +45,41 @@ method call itself::
75 45
         (r'^about/', TemplateView.as_view(template_name="about.html")),
76 46
     )
77 47
 
  48
+Any arguments given will override the ``template_name`` on the
78 49
 A similar overriding pattern can be used for the ``url`` attribute on
79 50
 :class:`~django.views.generic.base.RedirectView`.
80 51
 
81  
-.. _jsonresponsemixin-example:
82  
-
83  
-More than just HTML
84  
--------------------
85  
-
86  
-Where class based views shine is when you want to do the same thing many times.
87  
-Suppose you're writing an API, and every view should return JSON instead of
88  
-rendered HTML.
89 52
 
90  
-We can create a mixin class to use in all of our views, handling the
91  
-conversion to JSON once.
  53
+Subclassing generic views
  54
+=========================
92 55
 
93  
-For example, a simple JSON mixin might look something like this::
  56
+The second, more powerful way to use generic views is to inherit from an
  57
+existing view and override attributes (such as the ``template_name``) or
  58
+methods (such as ``get_context_data``) in your subclass to provide new values
  59
+or methods. Consider, for example, a view that just displays one template,
  60
+``about.html``. Django has a generic view to do this -
  61
+:class:`~django.views.generic.base.TemplateView` - so we can just subclass it,
  62
+and override the template name::
94 63
 
95  
-    import json
96  
-    from django.http import HttpResponse
97  
-
98  
-    class JSONResponseMixin(object):
99  
-        """
100  
-        A mixin that can be used to render a JSON response.
101  
-        """
102  
-        response_class = HttpResponse
  64
+    # some_app/views.py
  65
+    from django.views.generic import TemplateView
103 66
 
104  
-        def render_to_response(self, context, **response_kwargs):
105  
-            """
106  
-            Returns a JSON response, transforming 'context' to make the payload.
107  
-            """
108  
-            response_kwargs['content_type'] = 'application/json'
109  
-            return self.response_class(
110  
-                self.convert_context_to_json(context),
111  
-                **response_kwargs
112  
-            )
  67
+    class AboutView(TemplateView):
  68
+        template_name = "about.html"
113 69
 
114  
-        def convert_context_to_json(self, context):
115  
-            "Convert the context dictionary into a JSON object"
116  
-            # Note: This is *EXTREMELY* naive; in reality, you'll need
117  
-            # to do much more complex handling to ensure that arbitrary
118  
-            # objects -- such as Django model instances or querysets
119  
-            # -- can be serialized as JSON.
120  
-            return json.dumps(context)
  70
+Then we just need to add this new view into our URLconf.
  71
+`~django.views.generic.base.TemplateView` is a class, not a function, so we
  72
+point the URL to the ``as_view`` class method instead, which provides a
  73
+function-like entry to class-based views::
121 74
 
122  
-Now we mix this into the base TemplateView::
  75
+    # urls.py
  76
+    from django.conf.urls import patterns, url, include
  77
+    from some_app.views import AboutView
123 78
 
124  
-    from django.views.generic import TemplateView
  79
+    urlpatterns = patterns('',
  80
+        (r'^about/', AboutView.as_view()),
  81
+    )
125 82
 
126  
-    class JSONView(JSONResponseMixin, TemplateView):
127  
-        pass
128  
-
129  
-Equally we could use our mixin with one of the generic views. We can make our
130  
-own version of :class:`~django.views.generic.detail.DetailView` by mixing
131  
-:class:`JSONResponseMixin` with the
132  
-:class:`~django.views.generic.detail.BaseDetailView` -- (the
133  
-:class:`~django.views.generic.detail.DetailView` before template
134  
-rendering behavior has been mixed in)::
135  
-
136  
-    class JSONDetailView(JSONResponseMixin, BaseDetailView):
137  
-        pass
138  
-
139  
-This view can then be deployed in the same way as any other
140  
-:class:`~django.views.generic.detail.DetailView`, with exactly the
141  
-same behavior -- except for the format of the response.
142  
-
143  
-If you want to be really adventurous, you could even mix a
144  
-:class:`~django.views.generic.detail.DetailView` subclass that is able
145  
-to return *both* HTML and JSON content, depending on some property of
146  
-the HTTP request, such as a query argument or a HTTP header. Just mix
147  
-in both the :class:`JSONResponseMixin` and a
148  
-:class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`,
149  
-and override the implementation of :func:`render_to_response()` to defer
150  
-to the appropriate subclass depending on the type of response that the user
151  
-requested::
152  
-
153  
-    class HybridDetailView(JSONResponseMixin, SingleObjectTemplateResponseMixin, BaseDetailView):
154  
-        def render_to_response(self, context):
155  
-            # Look for a 'format=json' GET argument
156  
-            if self.request.GET.get('format','html') == 'json':
157  
-                return JSONResponseMixin.render_to_response(self, context)
158  
-            else:
159  
-                return SingleObjectTemplateResponseMixin.render_to_response(self, context)
160  
-
161  
-Because of the way that Python resolves method overloading, the local
162  
-``render_to_response()`` implementation will override the versions provided by
163  
-:class:`JSONResponseMixin` and
164  
-:class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`.
165 83
 
166 84
 For more information on how to use the built in generic views, consult the next
167 85
 topic on :doc:`generic class based views</topics/class-based-views/generic-display>`.
@@ -171,16 +89,15 @@ Decorating class-based views
171 89
 
172 90
 .. highlightlang:: python
173 91
 
174  
-The extension of class-based views isn't limited to using mixins. You
175  
-can use also use decorators.
  92
+Since class-based views aren't functions, decorating them works differently
  93
+depending on if you're using ``as_view`` or creating a subclass.
176 94
 
177 95
 Decorating in URLconf
178 96
 ---------------------
179 97
 
180 98
 The simplest way of decorating class-based views is to decorate the
181 99
 result of the :meth:`~django.views.generic.base.View.as_view` method.
182  
-The easiest place to do this is in the URLconf where you deploy your
183  
-view::
  100
+The easiest place to do this is in the URLconf where you deploy your view::
184 101
 
185 102
     from django.contrib.auth.decorators import login_required, permission_required
186 103
     from django.views.generic import TemplateView
150  docs/topics/class-based-views/mixins.txt
@@ -69,7 +69,7 @@ interface to working with templates in class-based views.
69 69
     add more members to the dictionary.
70 70
 
71 71
 Building up Django's generic class-based views
72  
-===============================================
  72
+==============================================
73 73
 
74 74
 Let's look at how two of Django's generic class-based views are built
75 75
 out of mixins providing discrete functionality. We'll consider
@@ -222,8 +222,7 @@ we'll want the functionality provided by
222 222
 :class:`~django.views.generic.detail.SingleObjectMixin`.
223 223
 
224 224
 We'll demonstrate this with the publisher modelling we used in the
225  
-:doc:`generic class-based views
226  
-introduction<generic-display>`.
  225
+:doc:`generic class-based views introduction<generic-display>`.
227 226
 
228 227
 .. code-block:: python
229 228
 
@@ -233,11 +232,11 @@ introduction<generic-display>`.
233 232
     from django.views.generic import View
234 233
     from django.views.generic.detail import SingleObjectMixin
235 234
     from books.models import Author
236  
-    
  235
+
237 236
     class RecordInterest(View, SingleObjectMixin):
238 237
         """Records the current user's interest in an author."""
239 238
         model = Author
240  
-    
  239
+
241 240
         def post(self, request, *args, **kwargs):
242 241
             if not request.user.is_authenticated():
243 242
                 return HttpResponseForbidden()
@@ -256,9 +255,7 @@ we're interested in, which it just does with a simple call to
256 255
 ``self.get_object()``. Everything else is taken care of for us by the
257 256
 mixin.
258 257
 
259  
-We can hook this into our URLs easily enough:
260  
-
261  
-.. code-block:: python
  258
+We can hook this into our URLs easily enough::
262 259
 
263 260
     # urls.py
264 261
     from books.views import RecordInterest
@@ -294,8 +291,6 @@ object. In order to do this, we need to have two different querysets:
294 291
     We'll figure that out ourselves in :meth:`get_queryset()` so we
295 292
     can take into account the Publisher we're looking at.
296 293
 
297  
-.. highlightlang:: python
298  
-
299 294
 .. note::
300 295
 
301 296
     We have to think carefully about :meth:`get_context_data()`.
@@ -311,15 +306,15 @@ Now we can write a new :class:`PublisherDetail`::
311 306
     from django.views.generic import ListView
312 307
     from django.views.generic.detail import SingleObjectMixin
313 308
     from books.models import Publisher
314  
-    
  309
+
315 310
     class PublisherDetail(SingleObjectMixin, ListView):
316 311
         paginate_by = 2
317 312
         template_name = "books/publisher_detail.html"
318  
-    
  313
+
319 314
         def get_context_data(self, **kwargs):
320 315
             kwargs['publisher'] = self.object
321 316
             return super(PublisherDetail, self).get_context_data(**kwargs)
322  
-    
  317
+
323 318
         def get_queryset(self):
324 319
             self.object = self.get_object(Publisher.objects.all())
325 320
             return self.object.book_set.all()
@@ -339,26 +334,26 @@ have to create lots of books to see the pagination working! Here's the
339 334
 template you'd want to use::
340 335
 
341 336
     {% extends "base.html" %}
342  
-    
  337
+
343 338
     {% block content %}
344 339
         <h2>Publisher {{ publisher.name }}</h2>
345  
-        
  340
+
346 341
         <ol>
347 342
           {% for book in page_obj %}
348 343
             <li>{{ book.title }}</li>
349 344
           {% endfor %}
350 345
         </ol>
351  
-        
  346
+
352 347
         <div class="pagination">
353 348
             <span class="step-links">
354 349
                 {% if page_obj.has_previous %}
355 350
                     <a href="?page={{ page_obj.previous_page_number }}">previous</a>
356 351
                 {% endif %}
357  
-        
  352
+
358 353
                 <span class="current">
359 354
                     Page {{ page_obj.number }} of {{ paginator.num_pages }}.
360 355
                 </span>
361  
-        
  356
+
362 357
                 {% if page_obj.has_next %}
363 358
                     <a href="?page={{ page_obj.next_page_number }}">next</a>
364 359
                 {% endif %}
@@ -428,9 +423,9 @@ code so that on ``POST`` the form gets called appropriately.
428 423
     the views implement :meth:`get()`, and things would get much more
429 424
     confusing.
430 425
 
431  
-Our new :class:`AuthorDetail` looks like this:
  426
+.. highlightlang:: python
432 427
 
433  
-.. code-block:: python
  428
+Our new :class:`AuthorDetail` looks like this::
434 429
 
435 430
     # CAUTION: you almost certainly do not want to do this.
436 431
     # It is provided as part of a discussion of problems you can
@@ -455,7 +450,7 @@ Our new :class:`AuthorDetail` looks like this:
455 450
                 'author-detail',
456 451
                 kwargs = {'pk': self.object.pk},
457 452
             )
458  
-    
  453
+
459 454
         def get_context_data(self, **kwargs):
460 455
             form_class = self.get_form_class()
461 456
             form = self.get_form(form_class)
@@ -464,7 +459,7 @@ Our new :class:`AuthorDetail` looks like this:
464 459
             }
465 460
             context.update(kwargs)
466 461
             return super(AuthorDetail, self).get_context_data(**context)
467  
-    
  462
+
468 463
         def post(self, request, *args, **kwargs):
469 464
             form_class = self.get_form_class()
470 465
             form = self.get_form(form_class)
@@ -472,14 +467,14 @@ Our new :class:`AuthorDetail` looks like this:
472 467
                 return self.form_valid(form)
473 468
             else:
474 469
                 return self.form_invalid(form)
475  
-    
  470
+
476 471
         def form_valid(self, form):
477 472
             if not self.request.user.is_authenticated():
478 473
                 return HttpResponseForbidden()
479 474
             self.object = self.get_object()
480 475
             # record the interest using the message in form.cleaned_data
481 476
             return super(AuthorDetail, self).form_valid(form)
482  
-    
  477
+
483 478
 :meth:`get_success_url()` is just providing somewhere to redirect to,
484 479
 which gets used in the default implementation of
485 480
 :meth:`form_valid()`. We have to provide our own :meth:`post()` as
@@ -525,21 +520,21 @@ write our own :meth:`get_context_data()` to make the
525 520
     from django.views.generic import DetailView
526 521
     from django import forms
527 522
     from books.models import Author
528  
-    
  523
+
529 524
     class AuthorInterestForm(forms.Form):
530 525
         message = forms.CharField()
531  
-    
  526
+
532 527
     class AuthorDisplay(DetailView):
533  
-    
  528
+
534 529
         queryset = Author.objects.all()
535  
-    
  530
+
536 531
         def get_context_data(self, **kwargs):
537 532
             context = {
538 533
                 'form': AuthorInterestForm(),
539 534
             }
540 535
             context.update(kwargs)
541 536
             return super(AuthorDisplay, self).get_context_data(**context)
542  
-    
  537
+
543 538
 Then the :class:`AuthorInterest` is a simple :class:`FormView`, but we
544 539
 have to bring in :class:`SingleObjectMixin` so we can find the author
545 540
 we're talking about, and we have to remember to set
@@ -550,7 +545,7 @@ template as :class:`AuthorDisplay` is using on ``GET``.
550 545
 
551 546
     from django.views.generic import FormView
552 547
     from django.views.generic.detail import SingleObjectMixin
553  
-    
  548
+
554 549
     class AuthorInterest(FormView, SingleObjectMixin):
555 550
         template_name = 'books/author_detail.html'
556 551
         form_class = AuthorInterestForm
@@ -561,13 +556,13 @@ template as :class:`AuthorDisplay` is using on ``GET``.
561 556
                 'object': self.get_object(),
562 557
             }
563 558
             return super(AuthorInterest, self).get_context_data(**context)
564  
-    
  559
+
565 560
         def get_success_url(self):
566 561
             return reverse(
567 562
                 'author-detail',
568 563
                 kwargs = {'pk': self.object.pk},
569 564
             )
570  
-    
  565
+
571 566
         def form_valid(self, form):
572 567
             if not self.request.user.is_authenticated():
573 568
                 return HttpResponseForbidden()
@@ -588,13 +583,13 @@ using a different template.
588 583
 .. code-block:: python
589 584
 
590 585
     from django.views.generic import View
591  
-    
  586
+
592 587
     class AuthorDetail(View):
593  
-    
  588
+
594 589
         def get(self, request, *args, **kwargs):
595 590
             view = AuthorDisplay.as_view()
596 591
             return view(request, *args, **kwargs)
597  
-    
  592
+
598 593
         def post(self, request, *args, **kwargs):
599 594
             view = AuthorInterest.as_view()
600 595
             return view(request, *args, **kwargs)
@@ -603,3 +598,88 @@ This approach can also be used with any other generic class-based
603 598
 views or your own class-based views inheriting directly from
604 599
 :class:`View` or :class:`TemplateView`, as it keeps the different
605 600
 views as separate as possible.
  601
+
  602
+.. _jsonresponsemixin-example:
  603
+
  604
+More than just HTML
  605
+===================
  606
+
  607
+Where class based views shine is when you want to do the same thing many times.
  608
+Suppose you're writing an API, and every view should return JSON instead of
  609
+rendered HTML.
  610
+
  611
+We can create a mixin class to use in all of our views, handling the
  612
+conversion to JSON once.
  613
+
  614
+For example, a simple JSON mixin might look something like this::
  615
+
  616
+    import json
  617
+    from django.http import HttpResponse
  618
+
  619
+    class JSONResponseMixin(object):
  620
+        """
  621
+        A mixin that can be used to render a JSON response.
  622
+        """
  623
+        response_class = HttpResponse
  624
+
  625
+        def render_to_response(self, context, **response_kwargs):
  626
+            """
  627
+            Returns a JSON response, transforming 'context' to make the payload.
  628
+            """
  629
+            response_kwargs['content_type'] = 'application/json'
  630
+            return self.response_class(
  631
+                self.convert_context_to_json(context),
  632
+                **response_kwargs
  633
+            )
  634
+
  635
+        def convert_context_to_json(self, context):
  636
+            "Convert the context dictionary into a JSON object"
  637
+            # Note: This is *EXTREMELY* naive; in reality, you'll need
  638
+            # to do much more complex handling to ensure that arbitrary
  639
+            # objects -- such as Django model instances or querysets
  640
+            # -- can be serialized as JSON.
  641
+            return json.dumps(context)
  642
+
  643
+Now we mix this into the base TemplateView::
  644
+
  645
+    from django.views.generic import TemplateView
  646
+
  647
+    class JSONView(JSONResponseMixin, TemplateView):
  648
+        pass
  649
+
  650
+Equally we could use our mixin with one of the generic views. We can make our
  651
+own version of :class:`~django.views.generic.detail.DetailView` by mixing
  652
+:class:`JSONResponseMixin` with the
  653
+:class:`~django.views.generic.detail.BaseDetailView` -- (the
  654
+:class:`~django.views.generic.detail.DetailView` before template
  655
+rendering behavior has been mixed in)::
  656
+
  657
+    class JSONDetailView(JSONResponseMixin, BaseDetailView):
  658
+        pass
  659
+
  660
+This view can then be deployed in the same way as any other
  661
+:class:`~django.views.generic.detail.DetailView`, with exactly the
  662
+same behavior -- except for the format of the response.
  663
+
  664
+If you want to be really adventurous, you could even mix a
  665
+:class:`~django.views.generic.detail.DetailView` subclass that is able
  666
+to return *both* HTML and JSON content, depending on some property of
  667
+the HTTP request, such as a query argument or a HTTP header. Just mix
  668
+in both the :class:`JSONResponseMixin` and a
  669
+:class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`,
  670
+and override the implementation of :func:`render_to_response()` to defer
  671
+to the appropriate subclass depending on the type of response that the user
  672
+requested::
  673
+
  674
+    class HybridDetailView(JSONResponseMixin, SingleObjectTemplateResponseMixin, BaseDetailView):
  675
+        def render_to_response(self, context):
  676
+            # Look for a 'format=json' GET argument
  677
+            if self.request.GET.get('format','html') == 'json':
  678
+                return JSONResponseMixin.render_to_response(self, context)
  679
+            else:
  680
+                return SingleObjectTemplateResponseMixin.render_to_response(self, context)
  681
+
  682
+Because of the way that Python resolves method overloading, the local
  683
+``render_to_response()`` implementation will override the versions provided by
  684
+:class:`JSONResponseMixin` and
  685
+:class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`.

0 notes on commit df7c1a1

Please sign in to comment.
Something went wrong with that request. Please try again.