forked from spapas/spapas.github.io
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex4.html
1434 lines (1291 loc) · 117 KB
/
index4.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
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<!--[if IEMobile 7 ]><html class="no-js iem7"><![endif]-->
<!--[if lt IE 9]><html class="no-js lte-ie8"><![endif]-->
<!--[if (gt IE 8)|(gt IEMobile 7)|!(IEMobile)|!(IE)]><!--><html class="no-js" lang="en"><!--<![endif]-->
<head>
<meta charset="utf-8">
<meta name="google-site-verification" content="5_8DTmIiEq4gFvNAfxAD6TsGOgrMAjp8lQFQvfA6zZc" />
<title>/var/</title>
<meta name="author" content="Serafeim Papastefanos">
<!-- http://t.co/dKP3o1e -->
<meta name="HandheldFriendly" content="True">
<meta name="MobileOptimized" content="320">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="http://spapas.github.io/favicon.png" rel="icon">
<link href="http://spapas.github.io/theme/css/main.css" media="screen, projection"
rel="stylesheet" type="text/css">
<link href="//fonts.googleapis.com/css?family=PT+Serif:regular,italic,bold,bolditalic"
rel="stylesheet" type="text/css">
<link href="//fonts.googleapis.com/css?family=PT+Sans:regular,italic,bold,bolditalic"
rel="stylesheet" type="text/css">
</head>
<body>
<header role="banner"><hgroup>
<h1><a href="http://spapas.github.io/">/var/</a></h1>
<h2>Various programming stuff</h2>
</hgroup></header>
<nav role="navigation"><ul class="subscription" data-subscription="rss">
</ul>
<ul class="main-navigation">
<li >
<a href="http://spapas.github.io/category/css.html">Css</a>
</li>
<li >
<a href="http://spapas.github.io/category/django.html">Django</a>
</li>
<li >
<a href="http://spapas.github.io/category/flask.html">Flask</a>
</li>
<li >
<a href="http://spapas.github.io/category/git.html">Git</a>
</li>
<li >
<a href="http://spapas.github.io/category/javascript.html">Javascript</a>
</li>
<li >
<a href="http://spapas.github.io/category/pelican.html">Pelican</a>
</li>
<li >
<a href="http://spapas.github.io/category/python.html">Python</a>
</li>
<li >
<a href="http://spapas.github.io/category/spring.html">Spring</a>
</li>
<li >
<a href="http://spapas.github.io/category/wagtail.html">Wagtail</a>
</li>
</ul></nav>
<div id="main">
<div id="content">
<div class="blog-index">
<article>
<header>
<h1 class="entry-title">
<a href="http://spapas.github.io/2014/09/15/django-non-html-responses/">Django non-HTML responses</a>
</h1>
<p class="meta">
<time datetime="2014-09-15T14:20:00+03:00" pubdate>Δευ 15 Σεπτέμβριος 2014</time> </p>
</header>
<div class="entry-content"><div class="contents topic" id="contents">
<p class="topic-title first">Contents</p>
<ul class="simple">
<li><a class="reference internal" href="#introduction" id="id1">Introduction</a></li>
<li><a class="reference internal" href="#how-are-cbvs-rendered" id="id2">How are CBVs rendered</a></li>
<li><a class="reference internal" href="#rendering-to-non-html" id="id3">Rendering to non-<span class="caps">HTML</span></a></li>
<li><a class="reference internal" href="#a-non-html-mixin" id="id4">A non-<span class="caps">HTML</span> mixin</a></li>
<li><a class="reference internal" href="#a-more-complex-example" id="id5">A more complex example</a></li>
<li><a class="reference internal" href="#conclusion" id="id6">Conclusion</a></li>
</ul>
</div>
<div class="section" id="introduction">
<h2><a class="toc-backref" href="#id1">Introduction</a></h2>
<p>I have already written about the many advantages (<span class="caps">DRY</span>!) of using <a class="reference external" href="http://spapas.github.io/2014/04/11/django-generic-formviews-for-objects/">CBVs in a previous article.</a>
In this article I will present the correct (pythonic) way to allow a normal <span class="caps">CBV</span> to return non-<span class="caps">HTML</span> responses, like <span class="caps">PDF</span>, <span class="caps">CSV</span>, <span class="caps">XSL</span> etc.</p>
</div>
<div class="section" id="how-are-cbvs-rendered">
<h2><a class="toc-backref" href="#id2">How are CBVs rendered</a></h2>
<p>Before proceeding, we need to understand how CBVs are rendered. By walking through the
class hierarchy in the <a class="reference external" href="http://ccbv.co.uk/"><span class="caps">CBV</span> inspector</a>, we can see that
all the normal Django CBVs (DetailView, CreateView, TemplateView etc) are using a mixin
named <a class="reference external" href="http://ccbv.co.uk/projects/Django/1.7/django.views.generic.base/TemplateResponseMixin/">TemplateResponseMixin</a> which defines a method named <tt class="docutils literal">render_to_response</tt>. This
is the method that is used for rendering the output of the Views. Let’s take a look at
<a class="reference external" href="http://ccbv.co.uk/projects/Django/1.7/django.views.generic.base/TemplateResponseMixin/">how it is defined</a> (I’ll remove the comments):</p>
<div class="highlight"><pre><span class="k">class</span> <span class="nc">TemplateResponseMixin</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
<span class="n">template_name</span> <span class="o">=</span> <span class="bp">None</span>
<span class="n">response_class</span> <span class="o">=</span> <span class="n">TemplateResponse</span>
<span class="n">content_type</span> <span class="o">=</span> <span class="bp">None</span>
<span class="k">def</span> <span class="nf">render_to_response</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">,</span> <span class="o">**</span><span class="n">response_kwargs</span><span class="p">):</span>
<span class="n">response_kwargs</span><span class="o">.</span><span class="n">setdefault</span><span class="p">(</span><span class="s">'content_type'</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">content_type</span><span class="p">)</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">response_class</span><span class="p">(</span>
<span class="n">request</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">request</span><span class="p">,</span>
<span class="n">template</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">get_template_names</span><span class="p">(),</span>
<span class="n">context</span><span class="o">=</span><span class="n">context</span><span class="p">,</span>
<span class="o">**</span><span class="n">response_kwargs</span>
<span class="p">)</span>
<span class="k">def</span> <span class="nf">get_template_names</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">template_name</span> <span class="ow">is</span> <span class="bp">None</span><span class="p">:</span>
<span class="k">raise</span> <span class="n">ImproperlyConfigured</span><span class="p">(</span>
<span class="s">"TemplateResponseMixin requires either a definition of "</span>
<span class="s">"'template_name' or an implementation of 'get_template_names()'"</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">return</span> <span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">template_name</span><span class="p">]</span>
</pre></div>
<p>This method will try to find out which html template should be used to render the View
(using the <tt class="docutils literal">get_template_names</tt> method and template_name attribute of the mixin) and
then render this view by instantiating an instance of the <tt class="docutils literal">TemplateResponse</tt> class
(as defined in the <tt class="docutils literal">response_class</tt> attribute)
and passing the request, template, context and other response_args to it.</p>
<p>The <a class="reference external" href="https://github.com/django/django/blob/master/django/template/response.py">TemplateResponse</a> class which is instantiated in the <tt class="docutils literal">render_to_response</tt> method
inherits from a normal <tt class="docutils literal">HttpResponse</tt> and is used to render
the template passed to it as a parameter.</p>
</div>
<div class="section" id="rendering-to-non-html">
<h2><a class="toc-backref" href="#id3">Rendering to non-<span class="caps">HTML</span></a></h2>
<p>From the previous discussion we can conclude that if your non-<span class="caps">HTML</span> response <em>needs</em>
a template then you just need to create a subclass of <tt class="docutils literal">TemplateResponse</tt> and
assign it to the <tt class="docutils literal">response_class</tt> attribute (and also change the <tt class="docutils literal">content_type</tt>
attribute). On the other hand, if your non-<span class="caps">HTML</span> respond does not need a template
to be rendered then you have to override <tt class="docutils literal">render_to_response</tt> completely
(since the template parameter does not need to be passed now) and either define
a subclass of HttpResponse or do the rendering in the render_to_response.</p>
<p>Since almost always you won’t need a template to create a non-<span class="caps">HTML</span> view and because
I believe that the solution is <span class="caps">DRY</span>-enough by implementing the rendering code to
the <tt class="docutils literal">render_to_response</tt> method (<em>without</em> subclassing <tt class="docutils literal">HttpResponse</tt>) I will
implement a mixin that does exactly that.</p>
<p>Subclassing <tt class="docutils literal">HttpResponse</tt> will not make our design more <span class="caps">DRY</span> because for every
subclass of <tt class="docutils literal">HttpResponse</tt> the <tt class="docutils literal">render_to_response</tt> method would also need to
be modified (by subclassing <tt class="docutils literal">TemplateResponseMixin) to instantiate the subclass of ``HttpResponse</tt> with the correct parameters.
For instance, the existing <tt class="docutils literal">TemplateResponseMixin</tt> cannot be used if the subclass
of <tt class="docutils literal">HttpResponse</tt> does not take a template as a parameter (solutions like
passing None to the template parameter are signals of bad design).</p>
<p>In any case, changing just the <tt class="docutils literal">render_to_response</tt> method using a Mixin is in my opinion the best solution
to the above problem.
A <a class="reference external" href="http://stackoverflow.com/questions/533631/what-is-a-mixin-and-why-are-they-useful">Mixin</a> is a simple class that can be used to extend other classes either by overriding functionality of the base class or
by adding extra features. Django CBVs use various <a class="reference external" href="https://docs.djangoproject.com/en/dev/topics/class-based-views/mixins/">mixins</a> to extend the base Views and add functionality.</p>
</div>
<div class="section" id="a-non-html-mixin">
<h2><a class="toc-backref" href="#id4">A non-<span class="caps">HTML</span> mixin</a></h2>
<p>So, a basic skeleton for our mixin would be something like this:</p>
<div class="highlight"><pre><span class="k">class</span> <span class="nc">NonHtmlResponseMixin</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">render_to_response</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">,</span> <span class="o">**</span><span class="n">response_kwargs</span><span class="p">):</span>
<span class="n">response</span> <span class="o">=</span> <span class="n">HttpResponse</span><span class="p">(</span><span class="n">content_type</span><span class="o">=</span><span class="s">'text/plain'</span><span class="p">)</span>
<span class="n">response</span><span class="o">.</span><span class="n">write</span><span class="p">(</span> <span class="s">"Hello, world"</span> <span class="p">)</span>
<span class="k">return</span> <span class="n">response</span>
</pre></div>
<p>The previous mixin overrides the render_to_response method to just return the text "Hello, world". For instance
we could define the following class:</p>
<div class="highlight"><pre><span class="k">class</span> <span class="nc">DummyTextResponseView</span><span class="p">(</span><span class="n">NonHtmlResponseMixin</span><span class="p">,</span> <span class="n">TemplateView</span><span class="p">,):</span>
<span class="k">pass</span>
</pre></div>
<p>which can be added as a route to <tt class="docutils literal">urls.py</tt> (using the <tt class="docutils literal">as_view</tt> method) and will always return the "Hello, world" text.</p>
<p>Here’s something more complicated: A Mixin that can be used along with a DetailView and will output the properties of the
object as text:</p>
<div class="highlight"><pre><span class="k">class</span> <span class="nc">TextPropertiesResponseMixin</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">render_to_response</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">,</span> <span class="o">**</span><span class="n">response_kwargs</span><span class="p">):</span>
<span class="n">response</span> <span class="o">=</span> <span class="n">HttpResponse</span><span class="p">(</span><span class="n">content_type</span><span class="o">=</span><span class="s">'text/plain; charset=utf-8'</span><span class="p">)</span>
<span class="n">o</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_object</span><span class="p">()</span>
<span class="n">o</span><span class="o">.</span><span class="n">_meta</span><span class="o">.</span><span class="n">fields</span>
<span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">o</span><span class="o">.</span><span class="n">_meta</span><span class="o">.</span><span class="n">fields</span><span class="p">:</span>
<span class="n">response</span><span class="o">.</span><span class="n">write</span> <span class="p">(</span><span class="s">u'{0}: {1}</span><span class="se">\n</span><span class="s">'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">f</span><span class="o">.</span><span class="n">name</span><span class="p">,</span> <span class="nb">unicode</span><span class="p">(</span><span class="n">o</span><span class="o">.</span><span class="n">__dict__</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">f</span><span class="o">.</span><span class="n">name</span><span class="p">))</span> <span class="p">)</span> <span class="p">)</span>
<span class="k">return</span> <span class="n">response</span>
</pre></div>
<p>and can be used like this</p>
<div class="highlight"><pre><span class="k">class</span> <span class="nc">TextPropertiesDetailView</span><span class="p">(</span><span class="n">TextPropertiesResponseMixin</span><span class="p">,</span> <span class="n">FooDetailView</span><span class="p">,):</span>
<span class="k">pass</span>
</pre></div>
<p>The above mixin will use the get_object() method of the DetailView to get the object and then output
it as text. We can create similar mixins that will integrate with other types of CBVs, for instance
to export a ListView as an <span class="caps">CSV</span> or generate an png from a DetailView of an Image file.</p>
</div>
<div class="section" id="a-more-complex-example">
<h2><a class="toc-backref" href="#id5">A more complex example</a></h2>
<p>The previous examples all built upon an existing view (either a TemplateView, a DetailView or a ListView).
However, an existing view that will fit our requirements won’t always be available. For instance,
sometimes I want to export data from my database using a raw <span class="caps">SQL</span> query. Also I’d like to be able to easily
export this data as csv or excel.</p>
<p>First of all, we need to define a view that will inherit from <tt class="docutils literal">View</tt> and export the data as a <span class="caps">CSV</span>:</p>
<div class="highlight"><pre><span class="kn">import</span> <span class="nn">unicodecsv</span>
<span class="kn">from</span> <span class="nn">django.db</span> <span class="kn">import</span> <span class="n">connection</span>
<span class="kn">from</span> <span class="nn">django.views.generic</span> <span class="kn">import</span> <span class="n">View</span>
<span class="k">class</span> <span class="nc">CsvRawSqlExportView</span><span class="p">(</span><span class="n">View</span><span class="p">,</span> <span class="p">):</span>
<span class="n">sql</span> <span class="o">=</span> <span class="s">'select 1+1'</span>
<span class="n">headers</span> <span class="o">=</span> <span class="p">[</span><span class="s">'res'</span><span class="p">]</span>
<span class="n">params</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">def</span> <span class="nf">get</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">request</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">generate_data</span><span class="p">(</span><span class="n">cursor</span><span class="p">):</span>
<span class="k">for</span> <span class="n">row</span> <span class="ow">in</span> <span class="n">cursor</span><span class="o">.</span><span class="n">fetchall</span><span class="p">():</span>
<span class="k">yield</span> <span class="n">row</span>
<span class="n">cursor</span> <span class="o">=</span> <span class="n">connection</span><span class="o">.</span><span class="n">cursor</span><span class="p">()</span>
<span class="n">cursor</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">sql</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">params</span><span class="p">)</span>
<span class="n">generator</span> <span class="o">=</span> <span class="n">generate_data</span><span class="p">(</span><span class="n">cursor</span><span class="p">)</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">render_to_response</span><span class="p">(</span><span class="n">generator</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">render_to_response</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">generator</span><span class="p">,</span> <span class="o">**</span><span class="n">response_kwargs</span><span class="p">):</span>
<span class="n">response</span> <span class="o">=</span> <span class="n">HttpResponse</span><span class="p">(</span><span class="n">content_type</span><span class="o">=</span><span class="s">'text/plain; charset=utf-8'</span><span class="p">)</span>
<span class="n">response</span><span class="p">[</span><span class="s">'Content-Disposition'</span><span class="p">]</span> <span class="o">=</span> <span class="s">'attachment; filename=export.csv'</span>
<span class="n">w</span> <span class="o">=</span> <span class="n">unicodecsv</span><span class="o">.</span><span class="n">writer</span><span class="p">(</span><span class="n">response</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s">'utf-8'</span><span class="p">)</span>
<span class="n">w</span><span class="o">.</span><span class="n">writerow</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">headers</span><span class="p">)</span>
<span class="k">for</span> <span class="n">row</span> <span class="ow">in</span> <span class="n">generator</span><span class="p">:</span>
<span class="n">w</span><span class="o">.</span><span class="n">writerow</span><span class="p">(</span><span class="n">row</span><span class="p">)</span>
<span class="k">return</span> <span class="n">response</span>
</pre></div>
<p>The above View has three attributes:
* sql, which is a string with the raw sql that will be executed
* headers, which is an array with the names of each header of the resulting data
* params, which is an array with parameters that may need to be passed to the query</p>
<p>The <tt class="docutils literal">get</tt> method executes the query and passes the result to <tt class="docutils literal">render_to_response</tt>
using a generator. The <tt class="docutils literal">render_to_response</tt> method instantiates an HttpResponse
object with the correct attributes and writes the <span class="caps">CSV</span> to the response object using unicodecsv.</p>
<p>We can now quickly create a route that will export data from the users table:</p>
<div class="highlight"><pre><span class="n">url</span><span class="p">(</span>
<span class="s">r'^raw_export_users/$'</span><span class="p">,</span>
<span class="n">views</span><span class="o">.</span><span class="n">CsvRawSqlExportView</span><span class="o">.</span><span class="n">as_view</span><span class="p">(</span>
<span class="n">sql</span><span class="o">=</span><span class="s">'select id, username from auth_user'</span><span class="p">,</span>
<span class="n">headers</span><span class="o">=</span><span class="p">[</span><span class="s">'id'</span><span class="p">,</span> <span class="s">'username'</span><span class="p">]</span>
<span class="p">)</span> <span class="p">,</span>
<span class="n">name</span><span class="o">=</span><span class="s">'raw_export_users'</span>
<span class="p">),</span>
</pre></div>
<p>If instead of <span class="caps">CSV</span> we wanted to export to <span class="caps">XLS</span> (using xlwt), we’d just need to create a Mixin:</p>
<div class="highlight"><pre><span class="k">class</span> <span class="nc">XlsRawSqlResponseMixin</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">render_to_response</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">generator</span><span class="p">,</span> <span class="o">**</span><span class="n">response_kwargs</span><span class="p">):</span>
<span class="n">response</span> <span class="o">=</span> <span class="n">HttpResponse</span><span class="p">(</span><span class="n">content_type</span><span class="o">=</span><span class="s">'application/ms-excel'</span><span class="p">)</span>
<span class="n">response</span><span class="p">[</span><span class="s">'Content-Disposition'</span><span class="p">]</span> <span class="o">=</span> <span class="s">'attachment; filename=export.xls'</span>
<span class="n">wb</span> <span class="o">=</span> <span class="n">xlwt</span><span class="o">.</span><span class="n">Workbook</span><span class="p">(</span><span class="n">encoding</span><span class="o">=</span><span class="s">'utf-8'</span><span class="p">)</span>
<span class="n">ws</span> <span class="o">=</span> <span class="n">wb</span><span class="o">.</span><span class="n">add_sheet</span><span class="p">(</span><span class="s">"data"</span><span class="p">)</span>
<span class="k">for</span> <span class="n">j</span><span class="p">,</span><span class="n">c</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">headers</span><span class="p">):</span>
<span class="n">ws</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="n">j</span><span class="p">,</span><span class="n">c</span><span class="p">)</span>
<span class="k">for</span> <span class="n">i</span><span class="p">,</span><span class="n">row</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">generator</span><span class="p">):</span>
<span class="k">for</span> <span class="n">j</span><span class="p">,</span><span class="n">c</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">row</span><span class="p">):</span>
<span class="n">ws</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">,</span><span class="n">j</span><span class="p">,</span><span class="n">c</span><span class="p">)</span>
<span class="n">wb</span><span class="o">.</span><span class="n">save</span><span class="p">(</span><span class="n">response</span><span class="p">)</span>
<span class="k">return</span> <span class="n">response</span>
</pre></div>
<p>and create a View that inherits from <tt class="docutils literal">CsvRawSqlExportView</tt> and uses the above mixin:</p>
<div class="highlight"><pre><span class="k">class</span> <span class="nc">XlsRawSqlExportView</span><span class="p">(</span> <span class="n">XlsRawSqlResponseMixin</span><span class="p">,</span> <span class="n">CsvRawSqlExportView</span> <span class="p">):</span>
<span class="k">pass</span>
</pre></div>
<p>and route to that view to get the <span class="caps">XLS</span>:</p>
<div class="highlight"><pre><span class="n">url</span><span class="p">(</span>
<span class="s">r'^raw_export_users/$'</span><span class="p">,</span>
<span class="n">views</span><span class="o">.</span><span class="n">XlsRawSqlExportView</span><span class="o">.</span><span class="n">as_view</span><span class="p">(</span>
<span class="n">sql</span><span class="o">=</span><span class="s">'select id, username from auth_user'</span><span class="p">,</span>
<span class="n">headers</span><span class="o">=</span><span class="p">[</span><span class="s">'id'</span><span class="p">,</span> <span class="s">'username'</span><span class="p">]),</span>
<span class="n">name</span><span class="o">=</span><span class="s">'raw_export_users'</span>
<span class="p">),</span>
</pre></div>
</div>
<div class="section" id="conclusion">
<h2><a class="toc-backref" href="#id6">Conclusion</a></h2>
<p>Using the above techniques we can define CBVs that will output their content in various content types
beyond <span class="caps">HTML</span>. This will help us write write clean and <span class="caps">DRY</span> code.</p>
</div>
</div>
</article>
<article>
<header>
<h1 class="entry-title">
<a href="http://spapas.github.io/2014/06/30/rest-flask-mongodb-heroku/">Implementing a simple, Heroku-hosted REST service using Flask and mongoDB</a>
</h1>
<p class="meta">
<time datetime="2014-06-30T15:23:00+03:00" pubdate>Δευ 30 Ιούνιος 2014</time> </p>
</header>
<div class="entry-content"><div class="contents topic" id="contents">
<p class="topic-title first">Contents</p>
<ul class="simple">
<li><a class="reference internal" href="#introduction" id="id1">Introduction</a></li>
<li><a class="reference internal" href="#requirements" id="id2">Requirements</a></li>
<li><a class="reference internal" href="#implementing-the-rest-service" id="id3">Implementing the <span class="caps">REST</span> service</a></li>
<li><a class="reference internal" href="#testing-it-locally" id="id4">Testing it locally</a></li>
<li><a class="reference internal" href="#deploying-to-heroku" id="id5">Deploying to Heroku</a></li>
</ul>
</div>
<div class="section" id="introduction">
<h2><a class="toc-backref" href="#id1">Introduction</a></h2>
<p>In the following, I will describe how I used <a class="reference external" href="http://flask.pocoo.org/">Flask</a>, a very nice web <em>microframework</em> for python along with <a class="reference external" href="http://www.mongodb.org/">mongoDB</a>, the most
popular No-<span class="caps">SQL</span> database to implement a simple <span class="caps">REST</span> service that was hosted on <a class="reference external" href="https://dashboard.heroku.com/apps">Heroku</a>. This <span class="caps">REST</span> service would get readings from
a number of sensors from an Android device.</p>
<p>I chose Flask instead of Django mainly because the <span class="caps">REST</span> service that I needed to implement would be very simple and most of
the Django bells and whistles (<span class="caps">ORM</span>, auth, admin, etc) wouldn’t be needed anyway. Also, Flask is much quicker to set-up than
Django since almost everything (views, urls, etc) can be put inside one python module.</p>
<p>Concerning the choice of a NoSQL persistance solution (mongoDB), I wanted to have a table (or collection as it is called in the
mongoDB world) of readings from the sensor. Each reading would just have a timestamp and various other
arbitrary data depending on the type of the reading, so saving it as a <span class="caps">JSON</span> document in a NoSQL database is a good solution.</p>
<p>Finally, all the above will be deployed to Heroku which offers some great services for deploying python code in the cloud.</p>
</div>
<div class="section" id="requirements">
<h2><a class="toc-backref" href="#id2">Requirements</a></h2>
<p>I propose creating a file named <tt class="docutils literal">requirements.txt</tt> that will host the required packages for your project, so you will be
able to setup your projects after creating a virtual environment with <a class="reference external" href="http://virtualenv.readthedocs.org/en/latest/">virtualenv</a> just by running <tt class="docutils literal">pip install <span class="pre">-r</span> requirements.txt</tt>.
Also, the requirements.txt is required for deploying python to Heroku.</p>
<p>So, for my case, the contents of requirements.txt are the following:</p>
<pre class="code literal-block">
Flask==0.10.1
Flask-PyMongo==0.3.0
Flask-RESTful==0.2.12
Jinja2==2.7.3
MarkupSafe==0.23
Werkzeug==0.9.6
aniso8601==0.82
gunicorn==19.0.0
itsdangerous==0.24
pymongo==2.7.1
pytz==2014.4
six==1.7.2
</pre>
<ul class="simple">
<li>Flask-PyMongo is a simple wrapper for Flask around pymongo which is the python mongoDB driver.</li>
<li>Flask-RESTful is a simple library for creating <span class="caps">REST</span> APIs - it needs aniso8601 and pytz.</li>
<li>Jinja2 is the template library for Flask (we won’t use it but it is required by Flask installation) - it needs MarkupSafe.</li>
<li>Werkzeug is a <span class="caps">WSGI</span> utility library - required by Flask</li>
<li>gunicorn is a <span class="caps">WSGI</span> <span class="caps">HTTP</span> server - needed for deployment to Heroku</li>
<li>itsdangerous is used to sign data for usage in untrusted environments</li>
<li>six is the python 2/3 compatibility layer</li>
</ul>
</div>
<div class="section" id="implementing-the-rest-service">
<h2><a class="toc-backref" href="#id3">Implementing the <span class="caps">REST</span> service</a></h2>
<p>Instead of using just one single file for our Flask web application, we will create a python module to contain it and a
file named <tt class="docutils literal">runserver.py</tt> that will start a local development server to test it:</p>
<p>So, in the same folder as the <tt class="docutils literal">requirements.txt</tt> create a folder named <tt class="docutils literal">flask_rest_service</tt> and in there put
two files: <tt class="docutils literal">__init__.py</tt> and <tt class="docutils literal">resources.py</tt>.</p>
<p>The <tt class="docutils literal">__init__.py</tt> initializes our Flask application, our mongoDB connection and our Flask-RESTful api:</p>
<pre class="code literal-block">
import os
from flask import Flask
from flask.ext import restful
from flask.ext.pymongo import PyMongo
from flask import make_response
from bson.json_util import dumps
MONGO_URL = os.environ.get('MONGO_URL')
if not MONGO_URL:
MONGO_URL = "mongodb://localhost:27017/rest";
app = Flask(__name__)
app.config['MONGO_URI'] = MONGO_URL
mongo = PyMongo(app)
def output_json(obj, code, headers=None):
resp = make_response(dumps(obj), code)
resp.headers.extend(headers or {})
return resp
DEFAULT_REPRESENTATIONS = {'application/json': output_json}
api = restful.Api(app)
api.representations = DEFAULT_REPRESENTATIONS
import flask_rest_service.resources
</pre>
<p>So what happens here? After the imports, we check if we have a MONGO_URL environment variable. This is
how we set options in Heroku. If such option does not exist in the environment then we are in our
development environment so we set it to the localhost (we must have a running mongoDB installation in
our dev environment).</p>
<p>In the next lines, we initialize our Flask application and our mongoDB connection (pymongo
uses a <tt class="docutils literal">MONGO_URI</tt> configuration option to know the database <span class="caps">URI</span>).</p>
<p>The <tt class="docutils literal">output_json</tt> is used to dump the <span class="caps">BSON</span> encoded mongoDB objects to <span class="caps">JSON</span> and was borrowed from
<a class="reference external" href="http://blog.alienretro.com/using-mongodb-with-flask-restful/">alienretro’s blog</a> — we initialize our restful <span class="caps">REST</span> <span class="caps">API</span> with this function.</p>
<p>Finally, we import the <tt class="docutils literal">resources.py</tt> module which actually defines our <span class="caps">REST</span> resources.</p>
<pre class="code literal-block">
import json
from flask import request, abort
from flask.ext import restful
from flask.ext.restful import reqparse
from flask_rest_service import app, api, mongo
from bson.objectid import ObjectId
class ReadingList(restful.Resource):
def __init__(self, *args, **kwargs):
self.parser = reqparse.RequestParser()
self.parser.add_argument('reading', type=str)
super(ReadingList, self).__init__()
def get(self):
return [x for x in mongo.db.readings.find()]
def post(self):
args = self.parser.parse_args()
if not args['reading']:
abort(400)
jo = json.loads(args['reading'])
reading_id = mongo.db.readings.insert(jo)
return mongo.db.readings.find_one({"_id": reading_id})
class Reading(restful.Resource):
def get(self, reading_id):
return mongo.db.readings.find_one_or_404({"_id": reading_id})
def delete(self, reading_id):
mongo.db.readings.find_one_or_404({"_id": reading_id})
mongo.db.readings.remove({"_id": reading_id})
return '', 204
class Root(restful.Resource):
def get(self):
return {
'status': 'OK',
'mongo': str(mongo.db),
}
api.add_resource(Root, '/')
api.add_resource(ReadingList, '/readings/')
api.add_resource(Reading, '/readings/<ObjectId:reading_id>')
</pre>
<p>Here we define three <tt class="docutils literal">Resource</tt> classes and add them to our previously defined <tt class="docutils literal">api</tt>: <tt class="docutils literal">Root</tt>, <tt class="docutils literal">Reading</tt> and <tt class="docutils literal">ReadingList</tt>.</p>
<p><tt class="docutils literal">Root</tt> just returns a dictionary with an <span class="caps">OK</span> status and some info on our mongodb connection.</p>
<p><tt class="docutils literal">Reading</tt> has gets an ObjectId
(which is the mongodb primary key) as a parameter and depending on the <span class="caps">HTTP</span> operation, it returns the reading with that
id when receiving an <tt class="docutils literal"><span class="caps">HTTP</span> <span class="caps">GET</span></tt> and deletes the reading with that id when receiving an <tt class="docutils literal"><span class="caps">HTTP</span> <span class="caps">DELETE</span></tt>.</p>
<p><tt class="docutils literal">ReadingList</tt> will return all readings when receiving an <tt class="docutils literal"><span class="caps">HTTP</span> <span class="caps">GET</span></tt> and will create a new reading when
receiving an <tt class="docutils literal"><span class="caps">HTTP</span> <span class="caps">POST</span></tt> The <tt class="docutils literal">post</tt> function uses the parser defined in <tt class="docutils literal">__init__</tt> which requires
a <tt class="docutils literal">reading</tt> parameter with the actual reading to be inserted.</p>
</div>
<div class="section" id="testing-it-locally">
<h2><a class="toc-backref" href="#id4">Testing it locally</a></h2>
<p>In order to run the development server, you will need to install and start mongodb locally which is beyond the scope of this post. After that
create a file named <tt class="docutils literal">runserver.py</tt> in the same folder as with the <tt class="docutils literal">requirements.txt</tt>
and the <tt class="docutils literal">flask_rest_service</tt> folder. The contents of this file should be:</p>
<pre class="code literal-block">
from flask_rest_service import app
app.run(debug=True)
</pre>
<p>When you run this file with <tt class="docutils literal">python runserver.py</tt> you should be able top visit your rest service at <a class="reference external" href="http://localhost:5000">http://localhost:5000</a> and get
an "<span class="caps">OK</span>" status.</p>
</div>
<div class="section" id="deploying-to-heroku">
<h2><a class="toc-backref" href="#id5">Deploying to Heroku</a></h2>
<p>To deploy to Heroku, you must create a <tt class="docutils literal">Procfile</tt> that contains the workers of your application. In our case, the
<tt class="docutils literal">Procfile</tt> should contain the following:</p>
<pre class="code literal-block">
web: gunicorn flask_rest_service:app
</pre>
<p>Also, you should add a .gitignore file with the following:</p>
<pre class="code literal-block">
*.pyc
</pre>
<p>Finally, to deploy your application to Heroku you can follow the instructions here: <a class="reference external" href="https://devcenter.heroku.com/articles/getting-started-with-python">https://devcenter.heroku.com/articles/getting-started-with-python</a>:</p>
<ul class="simple">
<li>Initialize a git repository and commit everything:</li>
</ul>
<pre class="code literal-block">
git init
git add .
git commit -m
</pre>
<ul class="simple">
<li>Create a new Heroku application (after logging in to heroku with <tt class="docutils literal">heroku login</tt>) and set the MONGO_URL environment variable (of course you have to obtain ths MONGO_URL variable for your heroku envirotnment by adding a mongoDB database):</li>
</ul>
<pre class="code literal-block">
heroku create
heroku config:set MONGO_URL=mongodb://user:pass@mongoprovider.com:27409/rest
</pre>
<ul class="simple">
<li>And finally push your master branch to the heroku remote repository:</li>
</ul>
<pre class="code literal-block">
git push heroku master
</pre>
<p>If everything went ok you should be able to start a worker for your application, check that the worker is running, and finally visit it:</p>
<pre class="code literal-block">
heroku ps:scale web=1
heroku ps
heroku open
</pre>
<p>If everything was ok you should see an status-<span class="caps">OK</span> <span class="caps">JSON</span> !</p>
</div>
</div>
</article>
<article>
<header>
<h1 class="entry-title">
<a href="http://spapas.github.io/2014/04/11/django-generic-formviews-for-objects/">Django generic FormViews for objects</a>
</h1>
<p class="meta">
<time datetime="2014-04-11T10:23:00+03:00" pubdate>Παρ 11 Απρίλιος 2014</time> </p>
</header>
<div class="entry-content"><div class="contents topic" id="contents">
<p class="topic-title first">Contents</p>
<ul class="simple">
<li><a class="reference internal" href="#introduction" id="id1">Introduction</a></li>
<li><a class="reference internal" href="#a-quick-introduction-to-the-formview" id="id2">A quick introduction to the <tt class="docutils literal">FormView</tt></a></li>
<li><a class="reference internal" href="#a-quick-introduction-to-the-singleobjectmixin" id="id3">A quick introduction to the <tt class="docutils literal">SingleObjectMixin</tt></a></li>
<li><a class="reference internal" href="#being-generic-and-dry" id="id4">Being generic and <span class="caps">DRY</span></a></li>
<li><a class="reference internal" href="#being-more-generic-and-dry" id="id5">Being more generic and <span class="caps">DRY</span></a></li>
<li><a class="reference internal" href="#other-options" id="id6">Other options</a></li>
</ul>
</div>
<div class="section" id="introduction">
<h2><a class="toc-backref" href="#id1">Introduction</a></h2>
<p>We recently needed to create a number of views for changing the status of an Application model instance for our organization.
An Application model instance can be filled and then cancelled, submitted, acceptted etc - for each of these status changes a form should be
presented to the user. When the user submits the form the status of the Application will be changed.</p>
<p>To implement the above requirement we created a generic FormView that acts on the specific model instance. This
used two basic <span class="caps">CBV</span> components: The <tt class="docutils literal">FormView</tt> for the form manipulation and the <tt class="docutils literal">SingleObjectMixing</tt> for the
object handling.</p>
<p>Django <a class="reference external" href="https://docs.djangoproject.com/en/1.6/topics/class-based-views/">Class Based Views</a> (CBVs) can be used to create reusable Views using normal class inheritance. Most
people use the well-known <tt class="docutils literal">CreateView</tt>, <tt class="docutils literal">UpdateView</tt>, <tt class="docutils literal">DetailView</tt> and <tt class="docutils literal">ListView</tt>, however, as we
will see below, the <tt class="docutils literal">FormView</tt> will help us write <span class="caps">DRY</span> code.</p>
<p>I have to notice here that an invaluable tool to help you understanding CBVs is the <a class="reference external" href="http://ccbv.co.uk/"><span class="caps">CBV</span> inspector</a> which
has a nice web interface for browsing the <span class="caps">CBV</span> hierarchies, attributes and methods.</p>
</div>
<div class="section" id="a-quick-introduction-to-the-formview">
<h2><a class="toc-backref" href="#id2">A quick introduction to the <tt class="docutils literal">FormView</tt></a></h2>
<p>A simple <tt class="docutils literal">FormView</tt> can be defined like this (<a class="reference external" href="http://ccbv.co.uk/projects/Django/1.6/django.views.generic.edit/FormView/"><span class="caps">CBV</span> FormView</a>):</p>
<pre class="code literal-block">
class MyFormView(FormView):
form_class = forms.MyFormView
template_name = 'my_template.html'
</pre>
<p>The above can be used in urls.py like this:</p>
<pre class="code literal-block">
urlpatterns = patterns('',
url(r'^my_formview/$', views.MyFormView.as_view() , name='my_formview' ),
</pre>
<p>This will present a form to the user when he visits the <tt class="docutils literal">my_formview</tt> url — however this form won’t do anything. To allow
the form to actually do something when it’s been submitted we need to override the <tt class="docutils literal">form_valid</tt> method.</p>
<pre class="code literal-block">
def form_valid(self, form):
value = form.cleaned_data['value']
messages.info(self.request, "MyForm submitted with value {0}!".format(value) )
return HttpResponseRedirect( reverse('my_formview') )
</pre>
<p>As you can see the submitted form is passed in the method and can be used to receive its <tt class="docutils literal">cleaned_data</tt>. The <tt class="docutils literal">FormView</tt>
has various other options for instance a <tt class="docutils literal">form_invalid</tt> method, an <tt class="docutils literal">initial</tt> attribute to set the initial values for the form etc.</p>
</div>
<div class="section" id="a-quick-introduction-to-the-singleobjectmixin">
<h2><a class="toc-backref" href="#id3">A quick introduction to the <tt class="docutils literal">SingleObjectMixin</tt></a></h2>
<p>A <tt class="docutils literal">SingleObjectMixin</tt> adds a number of attributes <span class="amp">&</span> methods to a view that can be used for object manipulation. The
most important ones is the <tt class="docutils literal">model</tt> and <tt class="docutils literal">queryset</tt> attributes and the <tt class="docutils literal">get_queryset</tt> and <tt class="docutils literal">get_object()</tt>. To use
the <tt class="docutils literal">SingleObjectMixin</tt> in your <span class="caps">CBV</span> just add it to the list of the classes to inherit from and define either the
<tt class="docutils literal">model</tt> or the <tt class="docutils literal">queryset</tt> attribute. After that you may pass a <tt class="docutils literal">pk</tt> parameter to your view and you will get an
<tt class="docutils literal">object</tt> context variable in the template with the selected object!</p>
</div>
<div class="section" id="being-generic-and-dry">
<h2><a class="toc-backref" href="#id4">Being generic and <span class="caps">DRY</span></a></h2>
<p>We can more or less now understand how we should use <tt class="docutils literal">FormView</tt> and <tt class="docutils literal">SingleObjectMixin</tt> to generate our
generic <tt class="docutils literal">FormView</tt> for acting on objects: Our <tt class="docutils literal">FormView</tt> should <em>get</em> the object using the <tt class="docutils literal">SingleObjectMixin</tt>
and change it when the form is submitted using the values from the form. A first implementation would be the following:</p>
<pre class="code literal-block">
class GenericObjectFormView1(FormView, SingleObjectMixin):
def form_valid(self, form):
obj = self.get_object()
obj.change_status(form)
return HttpResponseRedirect( obj.get_absolute_url() )
</pre>
<p>So our <tt class="docutils literal">GenericObjectFormView1</tt> class inherits from <tt class="docutils literal">FormView</tt> and <tt class="docutils literal">SingleObjectMixin</tt>. The only thing that we have
to assure is that the Model we want to act on needs to implement a <tt class="docutils literal">change_status</tt> method which gets the <tt class="docutils literal">form</tt> and
changes the status of that object based on its value. For instance, two implementations can be the following:</p>
<pre class="code literal-block">
class CancelObjectFormView(GenericObjectFormView1):
template_name = 'cancel.html'
form_class = forms.CancelForm
model = models.Application
class SubmitObjectFormView(GenericObjectFormView1):
template_name = 'submit.html'
form_class = forms.SubmitForm
model = models.Application
</pre>
</div>
<div class="section" id="being-more-generic-and-dry">
<h2><a class="toc-backref" href="#id5">Being more generic and <span class="caps">DRY</span></a></h2>
<p>The previous implementation has two problems:</p>
<ul class="simple">
<li>What happens if the status of the object should not be changed even if the form <em>is</em> valid?</li>
<li>We shouldn’t need to create a new template for every new <tt class="docutils literal">GenericObjectFormView</tt> since all these templates will just output the object information, ask a question for the status change and output the form.</li>
</ul>
<p>Let’s write a new version of our GenericObjectFormView that actually resolves these:</p>
<pre class="code literal-block">
class GenericObjectFormView2(FormView, SingleObjectMixin):
template_name = 'generic_formview.html'
ok_message = ''
not_ok_message = ''
title = ''
question =''
def form_valid(self, form):
obj = self.get_object()
r = obj.change_status(form)
if r:
messages.info(self.request, self.yes_message)
else:
messages.info(self.request, self.not_ok_message)
return HttpResponseRedirect( obj.get_absolute_url() )
def get_context_data(self, **kwargs):
context = super(GenericYesNoFormView, self).get_context_data(**kwargs)
context['title'] = self.title
context['question'] = self.question
return context
</pre>
<p>The above adds an ok and not ok message which will be outputed if the status can or cannot be changed. To accomplish this,
the <tt class="docutils literal">change_status</tt> method should now return a boolean value to mark if the action was ok or not. Also, a generic template
will now be used. This template has two placeholders: One for the title of the page (<tt class="docutils literal">title</tt> attribute) and one for the
question asked to the user (<tt class="docutils literal">question</tt> attribute). Now we can use it like this:</p>
<pre class="code literal-block">
class CancelObjectFormView(GenericObjectFormView2):
form_class = forms.CancelForm
model = models.Application
ok_message = 'Cancel success!'
not_ok_message = 'Not able to cancel!'
title = 'Cancel an object'
question = 'Do you want to cancel this object?'
class SubmitObjectFormView(GenericObjectFormView2):
form_class = forms.SubmitForm
model = models.Application
ok_message = 'Submit ok'
not_ok_message = 'Cannot submit!'
title = 'Submit an object'
question ='Do you want to submit this object?'
</pre>
</div>
<div class="section" id="other-options">
<h2><a class="toc-backref" href="#id6">Other options</a></h2>
<p>We’ve just got a glimpse of how we can use CBVs to increase the DRYness of our Django applications. There are various
extra things that we can add to our <tt class="docutils literal">GenericObjectFormView2</tt> as attributes which will be defined by inheriting
classes. Some ideas is to check if the current user actually has access to modify the object (hint: override the
<tt class="docutils literal">get_object</tt> method of <tt class="docutils literal">SingleObjectMixin</tt>) or render the form diffirently depending on the current user (hint:
override the <tt class="docutils literal">get_form_kwargs</tt> method of <tt class="docutils literal">FormView</tt>).</p>
</div>
</div>
</article>
<article>
<header>
<h1 class="entry-title">
<a href="http://spapas.github.io/2014/02/13/wagtail-tutorial/">A Wagtail tutorial</a>
</h1>
<p class="meta">
<time datetime="2014-02-13T21:20:00+02:00" pubdate>Πεμ 13 Φεβρουάριος 2014</time> </p>
</header>
<div class="entry-content"><p><a href="http://wagtail.io">Wagtail</a> is a new Open Source <a href="https://www.djangoproject.com">Django</a>-based <span class="caps">CMS</span>. In this 20 minute tutorial we will see how you can create a blog from scratch using Wagtail. If you want to see some more examples of usage please take a look at the <a href="https://github.com/torchbox/wagtaildemo">wagtaildemo</a> GitHub project.</p>
<p>To follow this tutorial you will need to have <a href="http://python.org/">Python</a> 2.7 installed with a working version of <a href="https://pypi.python.org/pypi/pip">pip</a> and <a href="https://pypi.python.org/pypi/virtualenv">virtualenv</a>.</p>
<p><strong>Update</strong>: The result of this tutorial has been deployed to <a href="https://www.heroku.com/">Heroku</a>: <a href="http://gentle-refuge-2590.herokuapp.com/">http://gentle-refuge-2590.herokuapp.com/</a> - you may visit the <a href="http://gentle-refuge-2590.herokuapp.com/admin/">admin site</a> and login with root / 123 to play with Wagtail! Please don’t do anything naughty !!! Also, notice that <a href="https://devcenter.heroku.com/articles/dynos#isolation-and-security">because of how Heroku works</a> you won’t be able to upload anything.</p>
<p><strong>Update 08/09/2015: This tutorial has been written during the first days of Wagtail, when documentation and tutorials about it were sparce; right now it should be considered by all accounts obsolete and <em>not</em> be followed! Instead, you should read the official (and very well written) tutorial @ <a href="http://docs.wagtail.io/en/v1.0/getting_started/tutorial.html">http://docs.wagtail.io/en/v1.0/getting_started/tutorial.html</a> in the official Wagtail documentation!</strong></p>
<h2>Installing the wagtail dependencies</h2>
<p>It is recomended to create a new virtual environment that will host the wagtail tutorial. After you have changed to the virtual environment you will need to installl the Wagtail requirements. Create a file named <code>requirements.txt</code> containing the following:</p>
<div class="highlight"><pre>Django==1.6.2
South==1.0.0
django-compressor==1.4
django-modelcluster==0.3
-e git://github.com/torchbox/wagtail.git#egg=wagtail
django-taggit==0.11.2
django-libsass==0.2
</pre></div>
<p>and run
<code>pip install -r requirements.txt</code>. If you use Microsoft Windows you <em>will</em> experience problems with Pillow and lxml. Please download the
installation executables from https://pypi.python.org/pypi/Pillow/2.3.0 and https://pypi.python.org/pypi/lxml/3.3.1, install
them using <code>easy_install Pillow-2.3.0.x-py2.7.exe</code> and <code>easy_install lxml-3.3.1.x-py2.7.exe</code> (from inside your virtual environment)
and then install the other requirements. Also please use the latest version of Wagtail (hosted on github) because it has some changes from
the pypi (so <em>don’t</em> do a <code>pip install wagtail</code>).</p>
<p><strong>Warning:</strong> Unfortuanately, no official binaries for libsass (which is a django-libsass requirement) are (yet) available for Windows. I
have compiled a version for win32 and python 2.7 (which I use) using the <a href="http://www.confusedbycode.com/articles/compiling-python-modules.html">instructions found here</a>. You can download this version
<a href="http://spapas.github.io/images/libsass-0.3.0-cp27-none-win32.whl">as a wheel package</a>. Notice that because libsass was compiled with <span class="caps">VC</span> Express 2013
you should also install the <a href="http://www.microsoft.com/en-us/download/details.aspx?id=40784">Visual C++ 2013 redistributable</a> package from Microsoft.</p>
<p><em>Please use it at your own risk !!!</em></p>
<p>To install <a href="http://pythonwheels.com/">wheels</a> you have to use a version of pip >= 1.4 (so do an <code>easy_install -U pip</code> from your
virtual environment if you have a previous version) and then you can just do a normal <code>pip install libsass-0.3.0-cp27-none-win32.whl</code>. </p>
<p><strong>Warning no2:</strong> More unfortuanately, there seem to be <a href="https://github.com/torchbox/wagtail/issues/133">a number of issues</a> with how libsass handles <code>@import</code> statements
in Windows. Until this is fixed, Windows users are recommened to use the command line (Ruby) sass compiler. To use it, please
install Ruby in your system and then install sass with <code>gem install sass -v ">=3.3.0alpha" --pre</code>. After that, in the <code>COMPRESS_PRECOMPILERS</code>
setting of your <code>settings.py</code> (discussed in the next section), change the line <code>('text/x-scss', 'django_libsass.SassCompiler'),</code> to
<code>('text/x-scss', 'sass --scss {infile} {outfile}'),</code>.</p>
<h2>Creating and configuring your project</h2>
<p>Wagtail has to “live” inside a normal Django project so you now may create a new Django project by issuing:</p>
<div class="highlight"><pre>python <PATH_OF_YOUR_VIRTUAL_ENV>/scripts/django-admin.py startproject wagtailtutorial
</pre></div>
<p>If you use Unix you can just run <code>django-admin.py</code> etc however if you try to do the same in windows you
will find out that windows tries to run the .py file with the python executable that is assigned through
explorer (which of course is your main python installation) and <em>not</em> through your path! That’s why
all our commands will be in the form <code>python script.py</code> to make sure that Windows picks the python
executable from your path (which, if you’re inside the virtual enviroment will be the correct one).</p>
<p>Inside the <code>wagtailtutorial</code> folder you will see a file named <code>manage.py</code> and another folder named <code>wagtailtutorial</code>. Inside this <code>wagtailtutorial</code> folder you will find <code>settings.py</code> and <code>urls.py</code> which need to be changed.</p>
<p>Starting with <code>urls.py</code>, remove everything and change it like this:</p>
<div class="highlight"><pre><span class="kn">from</span> <span class="nn">django.conf.urls</span> <span class="kn">import</span> <span class="n">patterns</span><span class="p">,</span> <span class="n">include</span><span class="p">,</span> <span class="n">url</span>
<span class="kn">from</span> <span class="nn">django.conf.urls.static</span> <span class="kn">import</span> <span class="n">static</span>
<span class="kn">from</span> <span class="nn">django.views.generic.base</span> <span class="kn">import</span> <span class="n">RedirectView</span>
<span class="kn">from</span> <span class="nn">django.contrib</span> <span class="kn">import</span> <span class="n">admin</span>
<span class="kn">from</span> <span class="nn">django.conf</span> <span class="kn">import</span> <span class="n">settings</span>
<span class="kn">import</span> <span class="nn">os.path</span>
<span class="kn">from</span> <span class="nn">wagtail.wagtailcore</span> <span class="kn">import</span> <span class="n">urls</span> <span class="k">as</span> <span class="n">wagtail_urls</span>
<span class="kn">from</span> <span class="nn">wagtail.wagtailadmin</span> <span class="kn">import</span> <span class="n">urls</span> <span class="k">as</span> <span class="n">wagtailadmin_urls</span>
<span class="kn">from</span> <span class="nn">wagtail.wagtailimages</span> <span class="kn">import</span> <span class="n">urls</span> <span class="k">as</span> <span class="n">wagtailimages_urls</span>
<span class="kn">from</span> <span class="nn">wagtail.wagtailembeds</span> <span class="kn">import</span> <span class="n">urls</span> <span class="k">as</span> <span class="n">wagtailembeds_urls</span>
<span class="kn">from</span> <span class="nn">wagtail.wagtaildocs</span> <span class="kn">import</span> <span class="n">admin_urls</span> <span class="k">as</span> <span class="n">wagtaildocs_admin_urls</span>
<span class="kn">from</span> <span class="nn">wagtail.wagtaildocs</span> <span class="kn">import</span> <span class="n">urls</span> <span class="k">as</span> <span class="n">wagtaildocs_urls</span>
<span class="kn">from</span> <span class="nn">wagtail.wagtailsnippets</span> <span class="kn">import</span> <span class="n">urls</span> <span class="k">as</span> <span class="n">wagtailsnippets_urls</span>
<span class="kn">from</span> <span class="nn">wagtail.wagtailsearch.urls</span> <span class="kn">import</span> <span class="n">frontend</span> <span class="k">as</span> <span class="n">wagtailsearch_frontend_urls</span><span class="p">,</span> <span class="n">admin</span> <span class="k">as</span> <span class="n">wagtailsearch_admin_urls</span>
<span class="kn">from</span> <span class="nn">wagtail.wagtailusers</span> <span class="kn">import</span> <span class="n">urls</span> <span class="k">as</span> <span class="n">wagtailusers_urls</span>
<span class="kn">from</span> <span class="nn">wagtail.wagtailredirects</span> <span class="kn">import</span> <span class="n">urls</span> <span class="k">as</span> <span class="n">wagtailredirects_urls</span>
<span class="n">admin</span><span class="o">.</span><span class="n">autodiscover</span><span class="p">()</span>
<span class="c"># Signal handlers</span>
<span class="kn">from</span> <span class="nn">wagtail.wagtailsearch</span> <span class="kn">import</span> <span class="n">register_signal_handlers</span> <span class="k">as</span> <span class="n">wagtailsearch_register_signal_handlers</span>
<span class="n">wagtailsearch_register_signal_handlers</span><span class="p">()</span>
<span class="n">urlpatterns</span> <span class="o">=</span> <span class="n">patterns</span><span class="p">(</span><span class="s">''</span><span class="p">,</span>
<span class="n">url</span><span class="p">(</span><span class="s">r'^django-admin/'</span><span class="p">,</span> <span class="n">include</span><span class="p">(</span><span class="n">admin</span><span class="o">.</span><span class="n">site</span><span class="o">.</span><span class="n">urls</span><span class="p">)),</span>
<span class="n">url</span><span class="p">(</span><span class="s">r'^admin/images/'</span><span class="p">,</span> <span class="n">include</span><span class="p">(</span><span class="n">wagtailimages_urls</span><span class="p">)),</span>
<span class="n">url</span><span class="p">(</span><span class="s">r'^admin/embeds/'</span><span class="p">,</span> <span class="n">include</span><span class="p">(</span><span class="n">wagtailembeds_urls</span><span class="p">)),</span>
<span class="n">url</span><span class="p">(</span><span class="s">r'^admin/documents/'</span><span class="p">,</span> <span class="n">include</span><span class="p">(</span><span class="n">wagtaildocs_admin_urls</span><span class="p">)),</span>
<span class="n">url</span><span class="p">(</span><span class="s">r'^admin/snippets/'</span><span class="p">,</span> <span class="n">include</span><span class="p">(</span><span class="n">wagtailsnippets_urls</span><span class="p">)),</span>
<span class="n">url</span><span class="p">(</span><span class="s">r'^admin/search/'</span><span class="p">,</span> <span class="n">include</span><span class="p">(</span><span class="n">wagtailsearch_admin_urls</span><span class="p">)),</span>
<span class="n">url</span><span class="p">(</span><span class="s">r'^admin/users/'</span><span class="p">,</span> <span class="n">include</span><span class="p">(</span><span class="n">wagtailusers_urls</span><span class="p">)),</span>
<span class="n">url</span><span class="p">(</span><span class="s">r'^admin/redirects/'</span><span class="p">,</span> <span class="n">include</span><span class="p">(</span><span class="n">wagtailredirects_urls</span><span class="p">)),</span>
<span class="n">url</span><span class="p">(</span><span class="s">r'^admin/'</span><span class="p">,</span> <span class="n">include</span><span class="p">(</span><span class="n">wagtailadmin_urls</span><span class="p">)),</span>
<span class="n">url</span><span class="p">(</span><span class="s">r'^search/'</span><span class="p">,</span> <span class="n">include</span><span class="p">(</span><span class="n">wagtailsearch_frontend_urls</span><span class="p">)),</span>
<span class="n">url</span><span class="p">(</span><span class="s">r'^documents/'</span><span class="p">,</span> <span class="n">include</span><span class="p">(</span><span class="n">wagtaildocs_urls</span><span class="p">)),</span>
<span class="c"># For anything not caught by a more specific rule above, hand over to</span>
<span class="c"># Wagtail's serving mechanism</span>
<span class="n">url</span><span class="p">(</span><span class="s">r''</span><span class="p">,</span> <span class="n">include</span><span class="p">(</span><span class="n">wagtail_urls</span><span class="p">)),</span>
<span class="p">)</span>
<span class="k">if</span> <span class="n">settings</span><span class="o">.</span><span class="n">DEBUG</span><span class="p">:</span>
<span class="kn">from</span> <span class="nn">django.contrib.staticfiles.urls</span> <span class="kn">import</span> <span class="n">staticfiles_urlpatterns</span>
<span class="n">urlpatterns</span> <span class="o">+=</span> <span class="n">staticfiles_urlpatterns</span><span class="p">()</span> <span class="c"># tell gunicorn where static files are in dev mode</span>
<span class="n">urlpatterns</span> <span class="o">+=</span> <span class="n">static</span><span class="p">(</span><span class="n">settings</span><span class="o">.</span><span class="n">MEDIA_URL</span> <span class="o">+</span> <span class="s">'images/'</span><span class="p">,</span> <span class="n">document_root</span><span class="o">=</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">settings</span><span class="o">.</span><span class="n">MEDIA_ROOT</span><span class="p">,</span> <span class="s">'images'</span><span class="p">))</span>
</pre></div>
<p>You can se that there is a signal handler when a searchable thing is added or changed to handle indexing for search, normal django admin is mapped under /django-admin since /admin is use for Wagtail (of course you may map Wagtail wherever you’d like), inclusion of various wagtail related urls and finally a Wagtail handling everything else. Finally there are some handlers for media and static files.</p>
<p>After that please change your <code>settings.py</code> like this:</p>
<div class="highlight"><pre><span class="c"># Django settings for wagtailtutorial project.</span>
<span class="kn">import</span> <span class="nn">os</span>
<span class="n">PROJECT_ROOT</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="n">__file__</span><span class="p">),</span> <span class="s">'..'</span><span class="p">,</span> <span class="s">'..'</span><span class="p">)</span>
<span class="n">DEBUG</span> <span class="o">=</span> <span class="bp">True</span>
<span class="n">TEMPLATE_DEBUG</span> <span class="o">=</span> <span class="n">DEBUG</span>
<span class="n">ADMINS</span> <span class="o">=</span> <span class="p">()</span>
<span class="n">MANAGERS</span> <span class="o">=</span> <span class="n">ADMINS</span>
<span class="n">DATABASES</span> <span class="o">=</span> <span class="p">{</span>
<span class="s">'default'</span><span class="p">:</span> <span class="p">{</span>
<span class="s">'ENGINE'</span><span class="p">:</span> <span class="s">'django.db.backends.sqlite3'</span><span class="p">,</span>
<span class="s">'NAME'</span><span class="p">:</span> <span class="n">PROJECT_ROOT</span><span class="o">+</span><span class="s">'/wagtailtutorial.db'</span><span class="p">,</span>
<span class="s">'USER'</span><span class="p">:</span> <span class="s">''</span><span class="p">,</span>
<span class="s">'PASSWORD'</span><span class="p">:</span> <span class="s">''</span><span class="p">,</span>
<span class="s">'HOST'</span><span class="p">:</span> <span class="s">''</span><span class="p">,</span> <span class="c"># Set to empty string for localhost.</span>
<span class="s">'PORT'</span><span class="p">:</span> <span class="s">''</span><span class="p">,</span> <span class="c"># Set to empty string for default.</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="n">CONN_MAX_AGE</span> <span class="o">=</span> <span class="mi">600</span> <span class="c"># number of seconds database connections should persist for</span>
<span class="n">ALLOWED_HOSTS</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">TIME_ZONE</span> <span class="o">=</span> <span class="s">'Europe/London'</span>
<span class="n">LANGUAGE_CODE</span> <span class="o">=</span> <span class="s">'en-gb'</span>
<span class="n">SITE_ID</span> <span class="o">=</span> <span class="mi">1</span>
<span class="n">USE_I18N</span> <span class="o">=</span> <span class="bp">True</span>
<span class="n">USE_L10N</span> <span class="o">=</span> <span class="bp">False</span>
<span class="n">USE_TZ</span> <span class="o">=</span> <span class="bp">True</span>
<span class="n">MEDIA_ROOT</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">PROJECT_ROOT</span><span class="p">,</span> <span class="s">'media'</span><span class="p">)</span>
<span class="n">MEDIA_URL</span> <span class="o">=</span> <span class="s">'/media/'</span>
<span class="n">STATIC_ROOT</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">PROJECT_ROOT</span><span class="p">,</span> <span class="s">'static'</span><span class="p">)</span>
<span class="n">STATIC_URL</span> <span class="o">=</span> <span class="s">'/static/'</span>
<span class="n">STATICFILES_DIRS</span> <span class="o">=</span> <span class="p">()</span>
<span class="n">STATICFILES_FINDERS</span> <span class="o">=</span> <span class="p">(</span>
<span class="s">'django.contrib.staticfiles.finders.FileSystemFinder'</span><span class="p">,</span>
<span class="s">'django.contrib.staticfiles.finders.AppDirectoriesFinder'</span><span class="p">,</span>
<span class="s">'compressor.finders.CompressorFinder'</span><span class="p">,</span>
<span class="p">)</span>
<span class="n">SECRET_KEY</span> <span class="o">=</span> <span class="s">'wq21wtjo3@d_qfjvd-#td!</span><span class="si">%7g</span><span class="s">fy2updj2z+nev^k$iy%=m4_tr'</span>
<span class="n">TEMPLATE_LOADERS</span> <span class="o">=</span> <span class="p">(</span>
<span class="s">'django.template.loaders.filesystem.Loader'</span><span class="p">,</span>
<span class="s">'django.template.loaders.app_directories.Loader'</span><span class="p">,</span>
<span class="p">)</span>
<span class="n">MIDDLEWARE_CLASSES</span> <span class="o">=</span> <span class="p">(</span>
<span class="s">'django.middleware.common.CommonMiddleware'</span><span class="p">,</span>
<span class="s">'django.contrib.sessions.middleware.SessionMiddleware'</span><span class="p">,</span>
<span class="s">'django.middleware.csrf.CsrfViewMiddleware'</span><span class="p">,</span>
<span class="s">'django.contrib.auth.middleware.AuthenticationMiddleware'</span><span class="p">,</span>
<span class="s">'django.contrib.messages.middleware.MessageMiddleware'</span><span class="p">,</span>
<span class="s">'django.middleware.clickjacking.XFrameOptionsMiddleware'</span><span class="p">,</span>
<span class="s">'wagtail.wagtailcore.middleware.SiteMiddleware'</span><span class="p">,</span>
<span class="s">'wagtail.wagtailredirects.middleware.RedirectMiddleware'</span><span class="p">,</span>
<span class="p">)</span>
<span class="kn">from</span> <span class="nn">django.conf</span> <span class="kn">import</span> <span class="n">global_settings</span>
<span class="n">TEMPLATE_CONTEXT_PROCESSORS</span> <span class="o">=</span> <span class="n">global_settings</span><span class="o">.</span><span class="n">TEMPLATE_CONTEXT_PROCESSORS</span> <span class="o">+</span> <span class="p">(</span>
<span class="s">'django.core.context_processors.request'</span><span class="p">,</span>
<span class="p">)</span>
<span class="n">ROOT_URLCONF</span> <span class="o">=</span> <span class="s">'wagtailtutorial.urls'</span>
<span class="n">WSGI_APPLICATION</span> <span class="o">=</span> <span class="s">'wagtailtutorial.wsgi.application'</span>
<span class="n">TEMPLATE_DIRS</span> <span class="o">=</span> <span class="p">()</span>
<span class="n">INSTALLED_APPS</span> <span class="o">=</span> <span class="p">(</span>
<span class="s">'django.contrib.auth'</span><span class="p">,</span>
<span class="s">'django.contrib.contenttypes'</span><span class="p">,</span>
<span class="s">'django.contrib.sessions'</span><span class="p">,</span>
<span class="c"># 'django.contrib.sites', # Wagtail uses its own site management logic</span>
<span class="s">'django.contrib.messages'</span><span class="p">,</span>
<span class="s">'django.contrib.staticfiles'</span><span class="p">,</span>
<span class="s">'south'</span><span class="p">,</span>
<span class="s">'compressor'</span><span class="p">,</span>
<span class="s">'taggit'</span><span class="p">,</span>
<span class="s">'modelcluster'</span><span class="p">,</span>
<span class="s">'django.contrib.admin'</span><span class="p">,</span>
<span class="s">'wagtail.wagtailcore'</span><span class="p">,</span>
<span class="s">'wagtail.wagtailadmin'</span><span class="p">,</span>
<span class="s">'wagtail.wagtaildocs'</span><span class="p">,</span>
<span class="s">'wagtail.wagtailsnippets'</span><span class="p">,</span>
<span class="s">'wagtail.wagtailusers'</span><span class="p">,</span>
<span class="s">'wagtail.wagtailimages'</span><span class="p">,</span>
<span class="s">'wagtail.wagtailembeds'</span><span class="p">,</span>
<span class="s">'wagtail.wagtailsearch'</span><span class="p">,</span>
<span class="s">'wagtail.wagtailredirects'</span><span class="p">,</span>
<span class="s">'tutorial'</span><span class="p">,</span>
<span class="p">)</span>
<span class="n">EMAIL_SUBJECT_PREFIX</span> <span class="o">=</span> <span class="s">'[wagtailtutorial] '</span>
<span class="n">INTERNAL_IPS</span> <span class="o">=</span> <span class="p">(</span><span class="s">'127.0.0.1'</span><span class="p">,</span> <span class="s">'10.0.2.2'</span><span class="p">)</span>
<span class="n">COMPRESS_PRECOMPILERS</span> <span class="o">=</span> <span class="p">(</span>
<span class="p">(</span><span class="s">'text/x-scss'</span><span class="p">,</span> <span class="s">'django_libsass.SassCompiler'</span><span class="p">),</span>
<span class="p">)</span>
<span class="c"># Auth settings</span>
<span class="n">LOGIN_URL</span> <span class="o">=</span> <span class="s">'django.contrib.auth.views.login'</span>
<span class="n">LOGIN_REDIRECT_URL</span> <span class="o">=</span> <span class="s">'wagtailadmin_home'</span>
<span class="n">LOGGING</span> <span class="o">=</span> <span class="p">{</span>
<span class="s">'version'</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="s">'disable_existing_loggers'</span><span class="p">:</span> <span class="bp">False</span><span class="p">,</span>
<span class="s">'filters'</span><span class="p">:</span> <span class="p">{</span>
<span class="s">'require_debug_false'</span><span class="p">:</span> <span class="p">{</span>
<span class="s">'()'</span><span class="p">:</span> <span class="s">'django.utils.log.RequireDebugFalse'</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="s">'handlers'</span><span class="p">:</span> <span class="p">{</span>
<span class="s">'mail_admins'</span><span class="p">:</span> <span class="p">{</span>
<span class="s">'level'</span><span class="p">:</span> <span class="s">'ERROR'</span><span class="p">,</span>
<span class="s">'filters'</span><span class="p">:</span> <span class="p">[</span><span class="s">'require_debug_false'</span><span class="p">],</span>
<span class="s">'class'</span><span class="p">:</span> <span class="s">'django.utils.log.AdminEmailHandler'</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="s">'loggers'</span><span class="p">:</span> <span class="p">{</span>
<span class="s">'django.request'</span><span class="p">:</span> <span class="p">{</span>
<span class="s">'handlers'</span><span class="p">:</span> <span class="p">[</span><span class="s">'mail_admins'</span><span class="p">],</span>
<span class="s">'level'</span><span class="p">:</span> <span class="s">'ERROR'</span><span class="p">,</span>
<span class="s">'propagate'</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="c"># WAGTAIL SETTINGS</span>
<span class="n">WAGTAIL_SITE_NAME</span> <span class="o">=</span> <span class="s">'wagtailtutorial'</span>
<span class="c"># Override the search results template for wagtailsearch</span>
<span class="n">WAGTAILSEARCH_RESULTS_TEMPLATE</span> <span class="o">=</span> <span class="s">'tutorial/search_results.html'</span>
<span class="n">WAGTAILSEARCH_RESULTS_TEMPLATE_AJAX</span> <span class="o">=</span> <span class="s">'tutorial/includes/search_listing.html'</span>
<span class="n">WAGTAILSEARCH_ES_INDEX</span> <span class="o">=</span> <span class="s">'wagtailtutorial'</span>
</pre></div>
<p>The most important thing to notice is that the <code>INSTALLED_APPS</code> contains the usual apps from django.*, <a href="http://south.aeracode.org/">south</a> for database migrations, <a href="https://github.com/django-compressor/django-compressor">django-compressor</a> to support compressing static files (and automatic translating from less to css), <a href="https://github.com/alex/django-taggit">django-taggit</a> to add support for tags, and <a href="https://github.com/torchbox/django-modelcluster/">django-modelcluster</a> which adds support from clusters (groups) of models. It also contains the wagtail.* applications and the tutorial application which is where we will create our blog. Also there are two Wagtail related middleware (one to add a site attribute to each request and one to hand redirects), configuring django-compressor to use <code>django_libsass</code> to compile <code>less</code> files, and some other, not so important Wagtail settings.</p>
<p>So let’s create the missing tutorial application by issuing:</p>
<div class="highlight"><pre>python manage.py startapp tutorial
</pre></div>
<p>Now we’ll have a <code>tutorial</code> folder waiting to define our blog structure inside the <code>wagtailtutorial</code> folder!</p>
<h2>Checking to see if everything works</h2>
<p>Before continuing with our blog creation let’s make sure that everything works, first of all by generating the database schema:</p>
<div class="highlight"><pre>python manage.py syncdb
</pre></div>
<p>In the superuser question answer yes and add a superuser. Then you can run the migrations - however because there are some problems with the way SQLite3 runs migrations it is recommended to run the migrations in two steps:</p>
<div class="highlight"><pre>python manage.py migrate <span class="m">0001</span> --all
python manage.py migrate
</pre></div>
<p>Finally, you now may try a</p>
<div class="highlight"><pre>python manage.py runserver
</pre></div>
<p>and visit <code>http://127.0.0.1:8000</code>. If everything worked fine you will get a</p>
<blockquote>
<p>Welcome to your new Wagtail site!</p>
</blockquote>
<p>page — congratulations !</p>
<p>The homepage is rather simple (for now!) but you may already navigate to http://127.0.0.1:8000/admin and from there, login to Wagtail admin with the superuser you created earlier.
Now you may start experiencing Wagtail ! </p>
<p><img alt="Wagtail admin index" src="http://spapas.github.io/images/wagtail-index.png" /></p>
<h2>Exploring Wagtail admin</h2>
<p>When you login to Wagtail admin you will see a menu at the left with the options:</p>
<ul>
<li>Explorer is used to actually manage the content of your site</li>
<li>Search is used for searching through your content</li>
<li>Images is used to manage your images</li>
<li>Documents is used to manage your Documents</li>
<li>Snippets is used for side bars etc</li>
<li>Users is used for User management</li>
<li>Redirects is used to redirect to a specific page</li>
<li>Editors picks is used to promote search results</li>
</ul>
<p>Of the previous, the one needing more explanation is Explorer: Clicking it you will see that a label named “Welcome to your Wagtail site!” will open. This is the root Page of your site. If you click at it you will go to the actions of this page. The actions you can do here is:
- Add Child Page
- Edit
- View Live
- Move
- Delete
- Unpublish</p>
<p>The Pages (and more generally the Content) of Wagtail are Django Models that are saved through a Tree hierarchy. However, if you click “Add Child Page” you won’t be able to add anything because you must create your own Page types (we will see how it is done in the next section). </p>
<p>When you click edit you will be able to edit the parts of the page (each part is a normal Field of the model). Also, you will see that the form is split into two tabs: Content and Promote. In the Content for instance you will see that the “Welcome to your Wagtail site!” has only a “Title” CharField. These are the fields that will be available to all Pages since “Welcome to your Wagtail site!” has a class of <code>Page</code> from which every other page should inherit. After you finish editing a page you may save it as a draft, publish, sent it for moderation (if you don’t have the rights to publish it) etc.</p>
<h2>Creating our blog</h2>
<p>Each of our Page types is a normal Django Model which inherits from <code>Page</code>. Let’s suppose that our posts should contain a title, a body and a created date. Add the following to <code>tutorials/models.py</code>:</p>
<div class="highlight"><pre><span class="kn">from</span> <span class="nn">django.db</span> <span class="kn">import</span> <span class="n">models</span>
<span class="kn">from</span> <span class="nn">wagtail.wagtailcore.models</span> <span class="kn">import</span> <span class="n">Page</span>
<span class="kn">from</span> <span class="nn">wagtail.wagtailcore.fields</span> <span class="kn">import</span> <span class="n">RichTextField</span>
<span class="kn">from</span> <span class="nn">wagtail.wagtailadmin.edit_handlers</span> <span class="kn">import</span> <span class="n">FieldPanel</span>
<span class="k">class</span> <span class="nc">BlogPage</span><span class="p">(</span><span class="n">Page</span><span class="p">):</span>
<span class="n">body</span> <span class="o">=</span> <span class="n">RichTextField</span><span class="p">()</span>
<span class="n">date</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">DateField</span><span class="p">(</span><span class="s">"Post date"</span><span class="p">)</span>
<span class="n">search_name</span> <span class="o">=</span> <span class="s">"Blog Page"</span>
<span class="n">indexed_fields</span> <span class="o">=</span> <span class="p">(</span><span class="s">'body'</span><span class="p">,</span> <span class="p">)</span>
<span class="n">BlogPage</span><span class="o">.</span><span class="n">content_panels</span> <span class="o">=</span> <span class="p">[</span>
<span class="n">FieldPanel</span><span class="p">(</span><span class="s">'title'</span><span class="p">,</span> <span class="n">classname</span><span class="o">=</span><span class="s">"full title"</span><span class="p">),</span>
<span class="n">FieldPanel</span><span class="p">(</span><span class="s">'date'</span><span class="p">),</span>
<span class="n">FieldPanel</span><span class="p">(</span><span class="s">'body'</span><span class="p">,</span> <span class="n">classname</span><span class="o">=</span><span class="s">"full"</span><span class="p">),</span>
<span class="p">]</span>
</pre></div>
<p>and run <code>python manage.py syncdb</code> to create the <code>tutorial_blogpage</code> table. </p>
<p>Now, if you visit again the /admin and click on the “Add Child Page” action of “Welcome to your new Wagtail Site!” you will see the “Blog Page” page and after you click it you will be able to see a form with the Fields you defined <em>and</em> title(title, body, date). The title is a field inherited from Page, along with the fields in the Promote tab. </p>
<p>In our declaration of BlogPage we added three <code>FieldPanel</code>s on its content_panes. A <code>FieldPanel</code> is a special edit handler for each Field. That is why when you try to edit the body you will see a rich text toolbar that enables you to not only format text but also embed images, documents and even oembed links. If you hadn’t included the <code>FieldPanel('body', classname="full")</code> then you wouldn’t see the rich text editor.</p>
<p><img alt="Editing pages in Wagtail" src="http://spapas.github.io/images/wagtail-edit-page.png" /></p>
<p>So now we can add as many posts as we like! </p>
<p>The time has come to take a look at our fine blog post: After we publish our page we click to the view live action and… </p>
<blockquote>