forked from twitter/opensource-website
-
Notifications
You must be signed in to change notification settings - Fork 1
/
our-custom-mixins.html
413 lines (358 loc) · 24.3 KB
/
our-custom-mixins.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
<!DOCTYPE html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<title>Our Custom Mixins [ brack3t ]</title>
<meta name="description" content="">
<meta name="author" content="Brack3t, aka Kenneth Love and Chris Jones">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/ico" href="./brack3t-theme/assets/favicon.ico">
<link href="./feeds/all.atom.xml" type="application/atom+xml" rel="alternate" title="brack3t ATOM Feed">
<!-- Le HTML5 shim, for IE6-8 support of HTML elements -->
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<!-- Le styles -->
<link href="http://fonts.googleapis.com/css?family=Exo:200,300,500,700,900,200italic,300italic,500italic,700italic,900italic" rel="stylesheet">
<link href="./brack3t-theme/assets/bootstrap/css/bootstrap.css" rel="stylesheet">
<link href="./brack3t-theme/assets/github.css" rel="stylesheet">
<link href="./brack3t-theme/assets/bootstrap/css/brack3t.css" rel="stylesheet">
<script>
var _gaq = _gaq || [];
_gaq.push(["_setAccount", "UA-4642386-4"]);
_gaq.push(["_trackPageview"]);
(function() {
var ga = document.createElement("script"); ga.type = "text/javascript"; ga.async = true;
ga.src = ("https:" == document.location.protocol ? "https://ssl" : "http://www") + ".google-analytics.com/ga.js";
var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
<script type="text/javascript">
var disqus_identifier = "our-custom-mixins.html";
(function() {
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
dsq.src = 'http://brack3t.disqus.com/embed.js';
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
})();
</script>
</head>
<body>
<div class="container">
<div class="row-fluid">
<div class="span8">
<header id="logo" role="banner">
<h1><a href="/">Brack3t</a></h1>
<p>Two guys… and Python.</p>
</header>
</div>
<aside class="span2" id="sidebar" role="complementary">
<nav>
<ul class="unstyled">
<li><a href="./pages/projects.html">Projects</a></li>
<li><a href="./archives.html">Archives</a></li>
<li><a href="./tags.html">Tags</a></li>
</ul>
</nav>
</aside>
</div>
<div class="row-fluid">
<div class="span7 offset1" id="main" role="main">
<article>
<header>
<h1><a href="./our-custom-mixins.html" class="slabtext">Our Custom Mixins</a></h1>
<h6><span class="permalink">Published: <a href="./our-custom-mixins.html">06-26-2012</a></span>
<span class="author">by <strong>Chris</strong></span>
<span class="tags">tags: <a href="./tag/django.html">django</a> <a href="./tag/CBVs.html">CBVs</a> </span>
</h6>
</header>
<p><strong>UPDATE</strong>: We've released a <a class="reference external" href="https://github.com/brack3t/django-braces">Github repo</a> and a <a class="reference external" href="http://pypi.python.org/pypi/django-braces/0.1.0">PyPI package</a> with our mixins. Feel free to fork and submit new ones through a pull-request.</p>
<p>Let's just start out and say it, <strong>Class Based Views</strong>. Ooohhhh. Unfortunately the topic of class based views is
thought of as somewhat of a dark art in the Django community. It doesn't help that the documentation is still
lacking but I find a lot of people, especially on <a class="reference external" href="http://reddit.com/r/django">Reddit</a>, refuse to use them. For whatever reason, it's a hard
pill for some to swallow.</p>
<p>Before DjangoCon 2011, we started playing with class-based views. At first they seemed like a nightmare and without
decent docs, we got frustrated really quickly. Skip forward to today and I can't imagine writing old function-based
views again. Some argue that the <tt class="docutils literal">generic views</tt> are only for generic applications and that, somehow, their work is far too
custom and complex to be handled in a generic class-based view. Based on my experience, 99% of the time, they would be wrong.</p>
<p>We plan on covering generic class-based views extensively with <a class="reference external" href="http://gettingstartedwithdjango.com">GSWD</a>. Today, I'd like to share some mixins we
have cooked up, on a rather large client project, that have helped us out tremendously.</p>
<p>For those of you not familiar with decorating class-based views, check out the Django documentation on
<a class="reference external" href="https://docs.djangoproject.com/en/dev/topics/class-based-views/#decorating-the-class">decorating the class</a>. We don't like the idea of doing decoration in Django's <tt class="docutils literal">urls.py</tt> or creating
another instance variable just to hold a decorated class. To us, mixins feel more Pythonic.</p>
<div class="contents well topic" id="mixins">
<p class="topic-title first">Mixins</p>
<ul class="simple">
<li><a class="reference internal" href="#loginrequiredmixin" id="id1">LoginRequiredMixin</a></li>
<li><a class="reference internal" href="#permissionrequiredmixin" id="id2">PermissionRequiredMixin</a></li>
<li><a class="reference internal" href="#superuserrequiredmixin" id="id3">SuperuserRequiredMixin</a></li>
<li><a class="reference internal" href="#userformkwargsmixin" id="id4">UserFormKwargsMixin</a></li>
<li><a class="reference internal" href="#userkwargmodelformmixin" id="id5">UserKwargModelFormMixin</a></li>
<li><a class="reference internal" href="#successurlredirectlistmixin" id="id6">SuccessURLRedirectListMixin</a></li>
<li><a class="reference internal" href="#setheadlinemixin" id="id7">SetHeadlineMixin</a></li>
<li><a class="reference internal" href="#conclusion" id="id8">Conclusion</a></li>
</ul>
</div>
<div class="section" id="loginrequiredmixin">
<h2><a class="toc-backref" href="#id1">LoginRequiredMixin</a></h2>
<div class="highlight"><pre><span class="x">class LoginRequiredMixin(object):</span>
<span class="x"> """</span>
<span class="x"> View mixin which verifies that the user has authenticated.</span>
<span class="x"> NOTE:</span>
<span class="x"> This should be the left-most mixin of a view.</span>
<span class="x"> """</span>
<span class="x"> @method_decorator(login_required)</span>
<span class="x"> def dispatch(self, *args, **kwargs):</span>
<span class="x"> return super(LoginRequiredMixin, self).dispatch(*args, **kwargs)</span>
</pre></div>
<p>This mixin is rather simple and is generally the first inherited class in any of our views. If we don't have an authenticated user
there's no need to go any further. If you've used Django before you are probably familiar with the <tt class="docutils literal">login_required</tt> decorator.
All we are doing here is requiring a user to be authenticated to be able to get to this view.</p>
<p>While this doesn't look like much, it frees us up from having to manually overload the dispatch method on every single view that
requires a user to be authenticated. If that's all that is needed on this view, we just saved 3 lines of code. Example usage below.</p>
<div class="highlight"><pre><span class="x">from django.views.generic import TemplateView</span>
<span class="x">from myapp.mixins import LoginRequiredMixin</span>
<span class="x">class SomeSecretView(LoginRequiredMixin, TemplateView):</span>
<span class="x"> template_name = "path/to/template.html"</span>
<span class="x"> def get(self, request):</span>
<span class="x"> return self.render_to_response({})</span>
</pre></div>
</div>
<div class="section" id="permissionrequiredmixin">
<h2><a class="toc-backref" href="#id2">PermissionRequiredMixin</a></h2>
<div class="highlight"><pre><span class="x">class PermissionRequiredMixin(object):</span>
<span class="x"> """</span>
<span class="x"> View mixin which verifies that the logged in user has the specified</span>
<span class="x"> permission.</span>
<span class="x"> Class Settings</span>
<span class="x"> `permission_required` - the permission to check for.</span>
<span class="x"> `login_url` - the login url of site</span>
<span class="x"> `redirect_field_name` - defaults to "next"</span>
<span class="x"> `raise_exception` - defaults to False - raise 403 if set to True</span>
<span class="x"> Example Usage</span>
<span class="x"> class SomeView(PermissionRequiredMixin, ListView):</span>
<span class="x"> ...</span>
<span class="x"> # required</span>
<span class="x"> permission_required = "app.permission"</span>
<span class="x"> # optional</span>
<span class="x"> login_url = "/signup/"</span>
<span class="x"> redirect_field_name = "hollaback"</span>
<span class="x"> raise_exception = True</span>
<span class="x"> ...</span>
<span class="x"> """</span>
<span class="x"> login_url = settings.LOGIN_URL</span>
<span class="x"> permission_required = None</span>
<span class="x"> raise_exception = False</span>
<span class="x"> redirect_field_name = REDIRECT_FIELD_NAME</span>
<span class="x"> def dispatch(self, request, *args, **kwargs):</span>
<span class="x"> # Verify class settings</span>
<span class="x"> if self.permission_required == None or len(</span>
<span class="x"> self.permission_required.split(".")) != 2:</span>
<span class="x"> raise ImproperlyConfigured("'PermissionRequiredMixin' requires "</span>
<span class="x"> "'permission_required' attribute to be set.")</span>
<span class="x"> has_permission = request.user.has_perm(self.permission_required)</span>
<span class="x"> if not has_permission:</span>
<span class="x"> if self.raise_exception:</span>
<span class="x"> return HttpResponseForbidden()</span>
<span class="x"> else:</span>
<span class="x"> path = urlquote(request.get_full_path())</span>
<span class="x"> tup = self.login_url, self.redirect_field_name, path</span>
<span class="x"> return HttpResponseRedirect("%s?%s=%s" % tup)</span>
<span class="x"> return super(PermissionRequiredMixin, self).dispatch(</span>
<span class="x"> request, *args, **kwargs)</span>
</pre></div>
<p>This mixin was originally written, I believe, by <a class="reference external" href="https://github.com/danols">Daniel Sokolowski</a> (<a class="reference external" href="https://github.com/lukaszb/django-guardian/issues/48">code here</a>).</p>
<p>The permission required mixin has been very handy for our client's custom CMS. Again, rather than overloading the
dispatch method manually on every view that needs to check for the existence of a permission, we inherit this class
and set the <tt class="docutils literal">permission_required</tt> class attribute on our view. If you don't specify <tt class="docutils literal">permission_required</tt> on
your view, an <tt class="docutils literal">ImproperlyConfigured</tt> exception is raised reminding you that you haven't set it.</p>
<p>The one limitation of this mixin is that it can <strong>only</strong> accept a single permission. It would need to be modified to
handle more than one. We haven't needed that yet, so this has worked out well for us.</p>
<p>In our normal use case for this mixin, <tt class="docutils literal">LoginRequiredMixin</tt> comes first, then the <tt class="docutils literal">PermissionRequiredMixin</tt>. If we
don't have an authenticated user, there is no sense in checking for any permissions.</p>
<blockquote>
<span class="label label-info">note</span> If you are using Django's built in auth system, <tt class="docutils literal">superusers</tt> automatically have all permissions in your system.</blockquote>
</div>
<div class="section" id="superuserrequiredmixin">
<h2><a class="toc-backref" href="#id3">SuperuserRequiredMixin</a></h2>
<div class="highlight"><pre><span class="x">class SuperuserRequiredMixin(object):</span>
<span class="x"> login_url = settings.LOGIN_URL</span>
<span class="x"> raise_exception = False</span>
<span class="x"> redirect_field_name = REDIRECT_FIELD_NAME</span>
<span class="x"> def dispatch(self, request, *args, **kwargs):</span>
<span class="x"> if not request.user.is_superuser:</span>
<span class="x"> if self.raise_exception:</span>
<span class="x"> return HttpResponseForbidden()</span>
<span class="x"> else:</span>
<span class="x"> path = urlquote(request.get_full_path())</span>
<span class="x"> tup = self.login_url, self.redirect_field_name, path</span>
<span class="x"> return HttpResponseRedirect("%s?%s=%s" % tup)</span>
<span class="x"> return super(SuperuserRequiredMixin, self).dispatch(</span>
<span class="x"> request, *args, **kwargs)</span>
</pre></div>
<p>Another permission-based mixin. This is specifically for requiring a user to be a superuser. Comes in handy for tools that only privileged
users should have access to.</p>
</div>
<div class="section" id="userformkwargsmixin">
<h2><a class="toc-backref" href="#id4">UserFormKwargsMixin</a></h2>
<div class="highlight"><pre><span class="x">class UserFormKwargsMixin(object):</span>
<span class="x"> """</span>
<span class="x"> CBV mixin which puts the user from the request into the form kwargs.</span>
<span class="x"> Note: Using this mixin requires you to pop the `user` kwarg</span>
<span class="x"> out of the dict in the super of your form's `__init__`.</span>
<span class="x"> """</span>
<span class="x"> def get_form_kwargs(self, **kwargs):</span>
<span class="x"> kwargs = super(UserFormKwargsMixin, self).get_form_kwargs(**kwargs)</span>
<span class="x"> kwargs.update({"user": self.request.user})</span>
<span class="x"> return kwargs</span>
</pre></div>
<p>In our clients CMS, we have a lot of form-based views that require a user to be passed in for permission-based form tools. For example,
only superusers can delete or disable certain objects. To custom tailor the form for users, we have to pass that user instance into the form
and based on their permission level, change certain fields or add specific options within the forms <tt class="docutils literal">__init__</tt> method.</p>
<p>This mixin automates the process of overloading the <tt class="docutils literal">get_form_kwargs</tt> (this method is available in any generic view which handles a form) method
and stuffs the user instance into the form kwargs. We can then pop the user off in the form and do with it what we need. <strong>Always</strong> remember
to pop the user from the kwargs before calling <tt class="docutils literal">super</tt> on your form, otherwise the form gets an unexpected keyword argument and everything
blows up. Example usage:</p>
<div class="highlight"><pre><span class="x">from django.views.generic import CreateView</span>
<span class="x">from myapp.mixins import LoginRequiredMixin, UserFormKwargsMixin</span>
<span class="x">from next.example import UserForm</span>
<span class="x">class SomeSecretView(LoginRequiredMixin, UserFormKwargsMixin,</span>
<span class="x"> TemplateView):</span>
<span class="x"> form_class = UserForm</span>
<span class="x"> model = User</span>
<span class="x"> template_name = "path/to/template.html"</span>
</pre></div>
</div>
<div class="section" id="userkwargmodelformmixin">
<h2><a class="toc-backref" href="#id5">UserKwargModelFormMixin</a></h2>
<div class="highlight"><pre><span class="x">class UserKwargModelFormMixin(object):</span>
<span class="x"> """</span>
<span class="x"> Generic model form mixin for popping user out of the kwargs and</span>
<span class="x"> attaching it to the instance.</span>
<span class="x"> This mixin must precede forms.ModelForm/forms.Form. The form is not</span>
<span class="x"> expecting these kwargs to be passed in, so they must be poppped off before</span>
<span class="x"> anything else is done.</span>
<span class="x"> """</span>
<span class="x"> def __init__(self, *args, **kwargs):</span>
<span class="x"> self.user = kwargs.pop("user", None)</span>
<span class="x"> super(UserKwargModelFormMixin, self).__init__(*args, **kwargs)</span>
</pre></div>
<p>The <tt class="docutils literal">UserKwargModelFormMixin</tt> is a new form mixin we just implemented this week to go along with our <tt class="docutils literal">UserFormKwargsMixin</tt>.
This becomes the first inherited class of our forms that receive the user keyword argument. With this mixin, we have automated
the popping off of the keyword argument in our form and no longer have to do it manually on every form that works this way.
While this may be overkill for a weekend project, for us, it speeds up adding new features. Example usage:</p>
<div class="highlight"><pre><span class="x">class UserForm(UserKwargModelFormMixin, forms.ModelForm):</span>
<span class="x"> class Meta:</span>
<span class="x"> model = User</span>
<span class="x"> def __init__(self, *args, **kwargs):</span>
<span class="x"> super(UserForm, self).__init__(*args, **kwargs):</span>
<span class="x"> if not self.user.is_superuser:</span>
<span class="x"> del self.fields["group"]</span>
</pre></div>
</div>
<div class="section" id="successurlredirectlistmixin">
<h2><a class="toc-backref" href="#id6">SuccessURLRedirectListMixin</a></h2>
<div class="highlight"><pre><span class="x">class SuccessURLRedirectListMixin(object):</span>
<span class="x"> """</span>
<span class="x"> Simple CBV mixin which sets the success url to the list view of</span>
<span class="x"> a given app. Set success_list_url as a class attribute of your</span>
<span class="x"> CBV and don't worry about overloading the get_success_url.</span>
<span class="x"> This is only to be used for redirecting to a list page. If you need</span>
<span class="x"> to reverse the url with kwargs, this is not the mixin to use.</span>
<span class="x"> """</span>
<span class="x"> success_list_url = None</span>
<span class="x"> def get_success_url(self):</span>
<span class="x"> return reverse(self.success_list_url)</span>
</pre></div>
<p>The <tt class="docutils literal">SuccessURLRedirectListMixin</tt> is a bit more tailored to how we handle <a class="reference external" href="http://en.wikipedia.org/wiki/Create,_read,_update_and_delete">CRUD</a> within our CMS. Our CMS's workflow, by design,
redirects the user to the <tt class="docutils literal">ListView</tt> for whatever model they are working with, whether they are creating a new instance, editing
an existing one or deleting one. Rather than having to override <tt class="docutils literal">get_success_url</tt> on every view, we simply use this mixin and pass it
a reversible route name. Example:</p>
<div class="highlight"><pre><span class="x"># urls.py</span>
<span class="x">url(r"^users/$", UserListView.as_view(), name="cms_users_list"),</span>
<span class="x"># views.py</span>
<span class="x">class UserCreateView(LoginRequiredMixin, PermissionRequiredMixin,</span>
<span class="x"> SuccessURLRedirectListMixin, CreateView):</span>
<span class="x"> form_class = UserForm</span>
<span class="x"> model = User</span>
<span class="x"> permission_required = "auth.add_user"</span>
<span class="x"> success_list_url = "cms_users_list"</span>
<span class="x"> ...</span>
</pre></div>
</div>
<div class="section" id="setheadlinemixin">
<h2><a class="toc-backref" href="#id7">SetHeadlineMixin</a></h2>
<div class="highlight"><pre><span class="x">class SetHeadlineMixin(object):</span>
<span class="x"> """</span>
<span class="x"> Mixin allows you to set a static headline through a static property on the</span>
<span class="x"> class or programmatically by overloading the get_headline method.</span>
<span class="x"> """</span>
<span class="x"> headline = None</span>
<span class="x"> def get_context_data(self, **kwargs):</span>
<span class="x"> kwargs = super(SetHeadlineMixin, self).get_context_data(**kwargs)</span>
<span class="x"> kwargs.update({"headline": self.get_headline()})</span>
<span class="x"> return kwargs</span>
<span class="x"> def get_headline(self):</span>
<span class="x"> if self.headline is None:</span>
<span class="x"> raise ImproperlyConfigured(u"%(cls)s is missing a headline. Define "</span>
<span class="x"> u"%(cls)s.headline, or override "</span>
<span class="x"> u"%(cls)s.get_headline()." % {"cls": self.__class__.__name__</span>
<span class="x"> })</span>
<span class="x"> return self.headline</span>
</pre></div>
<p>The <tt class="docutils literal">SetHeadlineMixin</tt> is a newer edition to our client's CMS. It allows us to <em>statically</em> or <em>programmatically</em> set the headline of any
of our views. We like to write as few templates as possible, so a mixin like this helps us reuse generic templates. Its usage is amazingly
straightforward and works much like Django's built-in <tt class="docutils literal">get_queryset</tt> method. This mixin has two ways of being used.</p>
<div class="section" id="static-example">
<h3>Static Example</h3>
<div class="highlight"><pre><span class="x">class HeadlineView(SetHeadlineMixin, TemplateView):</span>
<span class="x"> headline = "This is our headline"</span>
<span class="x"> template_name = "path/to/template.html"</span>
</pre></div>
</div>
<div class="section" id="dynamic-example">
<h3>Dynamic Example</h3>
<div class="highlight"><pre><span class="x">from datetime import date</span>
<span class="x">class HeadlineView(SetHeadlineMixin, TemplateView):</span>
<span class="x"> template_name = "path/to/template.html"</span>
<span class="x"> def get_headline(self):</span>
<span class="x"> return u"This is our headline for %s" % date.today().isoformat()</span>
</pre></div>
<p>In both usages, in the template, just print out <tt class="docutils literal">{{ headline }}</tt> to show the generated headline.</p>
</div>
</div>
<div class="section" id="conclusion">
<h2><a class="toc-backref" href="#id8">Conclusion</a></h2>
<p>Hopefully we've inspired you to use class-based views and custom mixins in your own projects or, at the very least, give class-based views another look.
Writing custom mixins helps to alleviate pain points in your project and make it faster to create new features, at least is has for us. If you have
any questions, leave a comment or hit us up on Twitter.</p>
</div>
</article>
<section>
<header>
<h1>Comments</h1>
</header>
<div id="disqus_thread"></div>
</section>
</div>
</div>
<footer><p>© Brack3t. All rights reserved. <a href="./feeds/all.atom.xml">ATOM feed</a></p></footer>
</div> <!-- /container -->
<!-- Le javascript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="./brack3t-theme/assets/jquery-1.8.2.min.js"></script>
<script src="./brack3t-theme/assets/modernizr.js"></script>
<script src="./brack3t-theme/assets/jquery.slabtext.min.js"></script>
<script src="./brack3t-theme/assets/jquery.fittext.js"></script>
<script src="./brack3t-theme/assets/highlight.pack.js"></script>
<script>
$(function() {
$(".slabtext").slabText({
"maxFontSize": 200,
"viewportBreakpoint": 768
});
$(".highlight pre").each(function(i, e) {hljs.highlightBlock(e, " ")});
});
</script>
</body>
</html>