1Other Mixins
2============
3
4These mixins handle other random bits of Django's views, like controlling output, controlling content types, or setting values in the context.
5
6.. contents::
7
8.. _SetHeadlineMixin:
9
10SetHeadlineMixin
11----------------
12
13The ``SetHeadlineMixin`` allows you to *statically* or *programmatically* set the headline of any of your views. Ideally, you'll write as few templates as possible, so a mixin like this helps you reuse generic templates. Its usage is amazingly straightforward and works much like Django's built-in ``get_queryset`` method. This mixin has two ways of being used:
14
15Static Example
16^^^^^^^^^^^^^^
17
18::
19
20    from django.utils.translation import ugettext_lazy as _
21    from django.views import TemplateView
22
23    from braces.views import SetHeadlineMixin
24
25
26    class HeadlineView(SetHeadlineMixin, TemplateView):
27        headline = _(u"This is our headline")
28        template_name = u"path/to/template.html"
29
30
31Dynamic Example
32^^^^^^^^^^^^^^^
33
34::
35
36    from datetime import date
37
38    from django.views import TemplateView
39
40    from braces.views import SetHeadlineMixin
41
42
43    class HeadlineView(SetHeadlineMixin, TemplateView):
44        template_name = u"path/to/template.html"
45
46        def get_headline(self):
47            return u"This is our headline for {0}".format(date.today().isoformat())
48
49For both usages, the context now contains a ``headline`` key with your headline.
50
51
52.. _StaticContextMixin:
53
54StaticContextMixin
55------------------
56
57.. versionadded:: 1.4
58
59The ``StaticContextMixin`` allows you to easily set static context data by using the ``static_context`` property.
60
61.. note::
62    While it's possible to override the ``StaticContextMixin.get_static_context method``, it's not very practical. If you have a need to override a method for dynamic context data it's best to override the standard ``get_context_data`` method of Django's generic class-based views.
63
64
65View Example
66^^^^^^^^^^^^
67
68::
69
70    # views.py
71
72    from django.views import TemplateView
73
74    from braces.views import StaticContextMixin
75
76
77    class ContextTemplateView(StaticContextMixin, TemplateView):
78        static_context = {u"nav_home": True}
79
80
81URL Example
82^^^^^^^^^^^
83
84::
85
86    # urls.py
87
88    urlpatterns = patterns(
89        '',
90        url(ur"^$",
91            ContextTemplateView.as_view(
92                template_name=u"index.html",
93                static_context={u"nav_home": True}
94            ),
95            name=u"index")
96    )
97
98
99.. _SelectRelatedMixin:
100
101SelectRelatedMixin
102------------------
103
104A simple mixin which allows you to specify a list or tuple of foreign key fields to perform a `select_related`_ on.  See Django's docs for more information on `select_related`_.
105
106::
107
108    # views.py
109    from django.views.generic import DetailView
110
111    from braces.views import SelectRelatedMixin
112
113    from profiles.models import Profile
114
115
116    class UserProfileView(SelectRelatedMixin, DetailView):
117        model = Profile
118        select_related = [u"user"]
119        template_name = u"profiles/detail.html"
120
121.. _select_related: https://docs.djangoproject.com/en/dev/ref/models/querysets/#select-related
122
123
124.. _PrefetchRelatedMixin:
125
126PrefetchRelatedMixin
127--------------------
128
129A simple mixin which allows you to specify a list or tuple of reverse foreign key or ManyToMany fields to perform a `prefetch_related`_ on. See Django's docs for more information on `prefetch_related`_.
130
131::
132
133    # views.py
134    from django.contrib.auth.models import User
135    from django.views.generic import DetailView
136
137    from braces.views import PrefetchRelatedMixin
138
139
140    class UserView(PrefetchRelatedMixin, DetailView):
141        model = User
142        prefetch_related = [u"post_set"]  # where the Post model has an FK to the User model as an author.
143        template_name = u"users/detail.html"
144
145.. _prefetch_related: https://docs.djangoproject.com/en/dev/ref/models/querysets/#prefetch-related
146
147
148.. _JSONResponseMixin:
149
150JSONResponseMixin
151-----------------
152
153.. versionchanged:: 1.1
154    ``render_json_response`` now accepts a ``status`` keyword argument.
155    ``json_dumps_kwargs`` class-attribute and ``get_json_dumps_kwargs`` method to provide arguments to the ``json.dumps()`` method.
156
157A simple mixin to handle very simple serialization as a response to the browser.
158
159::
160
161    # views.py
162    from django.views.generic import DetailView
163
164    from braces.views import JSONResponseMixin
165
166    class UserProfileAJAXView(JSONResponseMixin, DetailView):
167        model = Profile
168        json_dumps_kwargs = {u"indent": 2}
169
170        def get(self, request, *args, **kwargs):
171            self.object = self.get_object()
172
173            context_dict = {
174                u"name": self.object.user.name,
175                u"location": self.object.location
176            }
177
178            return self.render_json_response(context_dict)
179
180You can additionally use the `AjaxResponseMixin`
181
182::
183
184    # views.py
185    from django.views import DetailView
186
187    from braces import views
188
189
190    class UserProfileView(views.JSONResponseMixin,
191                          views.AjaxResponseMixin,
192                          DetailView):
193        model = Profile
194
195        def get_ajax(self, request, *args, **kwargs):
196            return self.render_json_object_response(self.get_object())
197
198The `JSONResponseMixin` provides a class-level variable to control the response
199type as well. By default it is `application/json`, but you can override that by
200providing the `content_type` variable a different value or, programmatically, by
201overriding the `get_content_type()` method.
202
203::
204
205    from django.views import DetailView
206
207    from braces.views import JSONResponseMixin
208
209
210    class UserProfileAJAXView(JSONResponseMixin, DetailView):
211        content_type = u"application/javascript"
212        model = Profile
213
214        def get(self, request, *args, **kwargs):
215            self.object = self.get_object()
216
217            context_dict = {
218                u"name": self.object.user.name,
219                u"location": self.object.location
220            }
221
222            return self.render_json_response(context_dict)
223
224        def get_content_type(self):
225            # Shown just for illustrative purposes
226            return u"application/javascript"
227
228The `JSONResponseMixin` provides another class-level variable
229`json_encoder_class` to use a custom json encoder with `json.dumps`.
230By default it is `django.core.serializers.json.DjangoJsonEncoder`
231
232::
233
234    from django.core.serializers.json import DjangoJSONEncoder
235
236    from braces.views import JSONResponseMixin
237
238
239    class SetJSONEncoder(DjangoJSONEncoder):
240        """
241        A custom JSONEncoder extending `DjangoJSONEncoder` to handle serialization
242        of `set`.
243        """
244        def default(self, obj):
245            if isinstance(obj, set):
246                return list(obj)
247            return super(DjangoJSONEncoder, self).default(obj)
248
249
250    class GetSetDataView(JSONResponseMixin, View):
251        json_encoder_class = SetJSONEncoder
252
253        def get(self, request, *args, **kwargs):
254            numbers_set = set(range(10))
255            data = {'numbers': numbers_set}
256            return self.render_json_response(data)
257
258.. _JsonRequestResponseMixin:
259
260JsonRequestResponseMixin
261------------------------
262
263.. versionadded:: 1.3
264
265A mixin that attempts to parse the request as JSON.  If the request is properly formatted, the JSON is saved to ``self.request_json`` as a Python object.  ``request_json`` will be ``None`` for imparsible requests.
266
267To catch requests that aren't JSON-formatted, set the class attribute ``require_json`` to ``True``.
268
269Override the class attribute ``error_response_dict`` to customize the default error message.
270
271It extends :ref:`JSONResponseMixin`, so those utilities are available as well.
272
273.. note::
274    To allow public access to your view, you'll need to use the ``csrf_exempt`` decorator or :ref:`CsrfExemptMixin`.
275
276::
277
278    from django.views.generic import View
279
280    from braces import views
281
282    class SomeView(views.CsrfExemptMixin, views.JsonRequestResponseMixin, View):
283        require_json = True
284
285        def post(self, request, *args, **kwargs):
286            try:
287                burrito = self.request_json[u"burrito"]
288                toppings = self.request_json[u"toppings"]
289            except KeyError:
290                error_dict = {u"message":
291                   u"your order must include a burrito AND toppings"}
292                return self.render_bad_request_response(error_dict)
293            place_order(burrito, toppings)
294            return self.render_json_response(
295                {u"message": u"Your order has been placed!"})
296
297
298.. _AjaxResponseMixin:
299
300AjaxResponseMixin
301-----------------
302
303This mixin provides hooks for alternate processing of AJAX requests based on HTTP verb.
304
305To control AJAX-specific behavior, override ``get_ajax``, ``post_ajax``, ``put_ajax``, or ``delete_ajax``. All four methods take ``request``, ``*args``, and ``**kwargs`` like the standard view methods.
306
307::
308
309    # views.py
310    from django.views.generic import View
311
312    from braces import views
313
314    class SomeView(views.JSONResponseMixin, views.AjaxResponseMixin, View):
315        def get_ajax(self, request, *args, **kwargs):
316            json_dict = {
317                'name': "Benny's Burritos",
318                'location': "New York, NY"
319            }
320            return self.render_json_response(json_dict)
321
322.. note::
323    This mixin is only useful if you need to have behavior in your view fork based on ``request.is_ajax()``.
324
325
326.. _OrderableListMixin:
327
328OrderableListMixin
329------------------
330
331.. versionadded:: 1.1
332
333A mixin to allow easy ordering of your queryset basing on the GET parameters. Works with `ListView`.
334
335To use it, define columns that the data can be ordered by, as well as the default column to order by in your view. This can be done either by simply setting the class attributes:
336
337::
338
339    # views.py
340    from django.views import ListView
341
342    from braces.views import OrderableListMixin
343
344
345    class OrderableListView(OrderableListMixin, ListView):
346        model = Article
347        orderable_columns = (u"id", u"title",)
348        orderable_columns_default = u"id"
349
350Or by using similarly-named methods to set the ordering constraints more dynamically:
351
352::
353
354    # views.py
355    from django.views import ListView
356
357    from braces.views import OrderableListMixin
358
359
360    class OrderableListView(OrderableListMixin, ListView):
361        model = Article
362
363        def get_orderable_columns(self):
364            # return an iterable
365            return (u"id", u"title",)
366
367        def get_orderable_columns_default(self):
368            # return a string
369            return u"id"
370
371The ``orderable_columns`` restriction is here in order to stop your users from launching inefficient queries, like ordering by binary columns.
372
373``OrderableListMixin`` will order your queryset basing on following GET params:
374
375    * ``order_by``: column name, e.g. ``"title"``
376    * ``ordering``: ``"asc"`` (default) or ``"desc"``
377
378Example url: `http://127.0.0.1:8000/articles/?order_by=title&ordering=asc`
379
380You can also override the default ordering from ``"asc"`` to ``"desc"``
381by setting the ``"ordering_default"`` in your view class.
382
383::
384
385    # views.py
386    from django.views import ListView
387
388    from braces.views import OrderableListMixin
389
390
391    class OrderableListView(OrderableListMixin, ListView):
392        model = Article
393        orderable_columns = (u"id", u"title",)
394        orderable_columns_default = u"id"
395        ordering_default = u"desc"
396
397This will reverse the order of list objects if no query param is given.
398
399
400**Front-end Example Usage**
401
402If you're using bootstrap you could create a template like the following:
403
404.. code:: html
405
406    <div class="table-responsive">
407        <table class="table table-striped table-bordered">
408            <tr>
409                <th><a class="order-by-column" data-column="id" href="#">ID</a></th>
410                <th><a class="order-by-column" data-column="title" href="#">Title</a></th>
411            </tr>
412            {% for object in object_list %}
413                <tr>
414                    <td>{{ object.id }}</td>
415                    <td>{{ object.title }}</td>
416                </tr>
417            {% endfor %}
418        </table>
419    </div>
420
421    <script>
422    function setupOrderedColumns(order_by, orderin) {
423
424        $('.order-by-column').each(function() {
425
426            var $el = $(this),
427                column_name = $el.data('column'),
428                href = location.href,
429                next_order = 'asc',
430                has_query_string = (href.indexOf('?') !== -1),
431                order_by_param,
432                ordering_param;
433
434            if (order_by === column_name) {
435                $el.addClass('current');
436                $el.addClass(ordering);
437                $el.append('<span class="caret"></span>');
438                if (ordering === 'asc') {
439                    $el.addClass('dropup');
440                    next_order = 'desc';
441                }
442            }
443
444            order_by_param = "order_by=" + column_name;
445            ordering_param = "ordering=" + next_order;
446
447            if (!has_query_string) {
448                href = '?' + order_by_param + '&' + ordering_param;
449            } else {
450                if (href.match(/ordering=(asc|desc)/)) {
451                    href = href.replace(/ordering=(asc|desc)/, ordering_param);
452                } else {
453                    href += '&' + ordering_param;
454                }
455
456                if (href.match(/order_by=[_\w]+/)) {
457                    href = href.replace(/order_by=([_\w]+)/, order_by_param);
458                } else {
459                    href += '&' + order_by_param;
460                }
461
462            }
463
464            $el.attr('href', href);
465
466        });
467    }
468    setupOrderedColumns('{{ order_by }}', '{{ ordering }}');
469    </script>
470
471.. _CanonicalSlugDetailMixin:
472
473CanonicalSlugDetailMixin
474------------------------
475
476.. versionadded:: 1.3
477
478A mixin that enforces a canonical slug in the URL. Works with ``DetailView``.
479
480If a ``urlpattern`` takes a object's ``pk`` and ``slug`` as arguments and the ``slug`` URL argument does not equal the object's canonical slug, this mixin will redirect to the URL containing the canonical slug.
481
482To use it, the ``urlpattern`` must accept both a ``pk`` and ``slug`` argument in its regex:
483
484::
485
486    # urls.py
487    urlpatterns = patterns('',
488        url(r"^article/(?P<pk>\d+)-(?P<slug>[-\w]+)$")
489        ArticleView.as_view(),
490        "view_article"
491    )
492
493Then create a standard ``DetailView`` that inherits this mixin:
494
495::
496
497    class ArticleView(CanonicalSlugDetailMixin, DetailView):
498        model = Article
499
500Now, given an ``Article`` object with ``{pk: 1, slug: 'hello-world'}``, the URL `http://127.0.0.1:8000/article/1-goodbye-moon` will redirect to `http://127.0.0.1:8000/article/1-hello-world` with the HTTP status code 301 Moved Permanently. Any other non-canonical slug, not just 'goodbye-moon', will trigger the redirect as well.
501
502Control the canonical slug by either implementing the method ``get_canonical_slug()`` on the model class:
503
504::
505
506    class Article(models.Model):
507        blog = models.ForeignKey('Blog')
508        slug = models.SlugField()
509
510        def get_canonical_slug(self):
511          return "{0}-{1}".format(self.blog.get_canonical_slug(), self.slug)
512
513Or by overriding the ``get_canonical_slug()`` method on the view:
514
515::
516
517    class ArticleView(CanonicalSlugDetailMixin, DetailView):
518        model = Article
519
520        def get_canonical_slug():
521            import codecs
522            return codecs.encode(self.get_object().slug, "rot_13")
523
524Given the same Article as before, this will generate urls of `http://127.0.0.1:8000/article/1-my-blog-hello-world` and `http://127.0.0.1:8000/article/1-uryyb-jbeyq`, respectively.
525
526
527.. _MessageMixin:
528
529MessageMixin
530------------
531
532.. versionadded:: 1.4
533
534A mixin that adds a ``messages`` attribute on the view which acts as a wrapper
535to ``django.contrib.messages`` and passes the ``request`` object automatically.
536
537    .. warning::
538        If you're using Django 1.4, then the ``message`` attribute is only
539        available after the base view's ``dispatch`` method has been called
540        (so our second example would not work for instance).
541
542::
543
544    from django.views.generic import TemplateView
545
546    from braces.views import MessageMixin
547
548
549    class MyView(MessageMixin, TemplateView):
550        """
551        This view will add a debug message which can then be displayed
552        in the template.
553        """
554        template_name = "my_template.html"
555
556        def get(self, request, *args, **kwargs):
557            self.messages.debug("This is a debug message.")
558            return super(MyView, self).get(request, *args, **kwargs)
559
560
561::
562
563    from django.contrib import messages
564    from django.views.generic import TemplateView
565
566    from braces.views import MessageMixin
567
568
569    class OnlyWarningView(MessageMixin, TemplateView):
570        """
571        This view will only show messages that have a level
572        above `warning`.
573        """
574        template_name = "my_template.html"
575
576        def dispatch(self, request, *args, **kwargs):
577            self.messages.set_level(messages.WARNING)
578            return super(OnlyWarningView, self).dispatch(request, *args, **kwargs)
579
580
581.. _AllVerbsMixin:
582
583AllVerbsMixin
584-------------
585
586.. versionadded:: 1.4
587
588This mixin allows you to specify a single method that will responsed to all HTTP verbs, making a class-based view behave much like a function-based view.
589
590::
591
592    from django.views import TemplateView
593
594    from braces.views import AllVerbsMixin
595
596
597    class JustShowItView(AllVerbsMixin, TemplateView):
598        template_name = "just/show_it.html"
599
600        def all(self, request, *args, **kwargs):
601            return super(JustShowItView, self).get(request, *args, **kwargs)
602
603If you need to change the name of the method called, provide a new value to the ``all_handler`` attribute (default is ``'all'``)
604
605
606
607.. _HeaderMixin:
608
609HeaderMixin
610-------------
611
612.. versionadded:: 1.11
613
614This mixin allows you to add arbitrary HTTP header to a response. Static headers can be defined in the ``headers`` attribute of the view.
615
616::
617
618    from django.views import TemplateView
619
620    from braces.views import HeaderMixin
621
622
623    class StaticHeadersView(HeaderMixin, TemplateView):
624        template_name = "some/headers.html"
625        headers = {
626            'X-Header-Sample': 'some value',
627            'X-Some-Number': 42
628        }
629
630
631If you need to set the headers dynamically, e.g depending on some request information, override the ``get_headers`` method instead.
632
633::
634
635    from django.views import TemplateView
636
637    from braces.views import HeaderMixin
638
639
640    class EchoHeadersView(HeaderMixin, TemplateView):
641        template_name = "some/headers.html"
642
643        def get_headers(self, request):
644            """
645            Echo back request headers with ``X-Request-`` prefix.
646            """
647            for key, value in request.META.items():
648                yield "X-Request-{}".format(key), value
649