forked from andreagrandi/andreagrandi.github.io
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex4.html
More file actions
983 lines (823 loc) · 73.9 KB
/
index4.html
File metadata and controls
983 lines (823 loc) · 73.9 KB
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Andrea Grandi</title>
<link rel="stylesheet" href="https://www.andreagrandi.it/theme/css/main.css" />
<link rel="stylesheet" href="https://www.andreagrandi.it/theme/tipuesearch/css/tipuesearch.css">
<link href="https://www.andreagrandi.it/feeds/all.rss.xml" type="application/rss+xml" rel="alternate" title="Andrea Grandi RSS Feed" />
<!--[if IE]>
<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
<link rel="icon" href="/favicon.ico" type="image/x-icon">
</head>
<body id="index" class="home">
<header id="banner" class="body">
<h1><a href="https://www.andreagrandi.it/">Andrea Grandi </a></h1>
<nav><ul>
<li><a href="https://www.andreagrandi.it/about/">About</a></li>
<li><a href="https://www.andreagrandi.it/curriculum/">Curriculum</a></li>
<li><a href="https://www.andreagrandi.it/pgp-key/">PGP Key</a></li>
</ul>
<form id="search" action="https://www.andreagrandi.it/search.html" onsubmit="return validateForm(this.elements['q'].value);">
<input type="text" class="search-query" placeholder="" name="q" id="tipue_search_input">
</form>
</nav>
</header><!-- /#banner -->
<section id="content" class="body">
<ol id="posts-list" class="hfeed" start="3">
<li><article class="hentry">
<header>
<h1><a href="https://www.andreagrandi.it/2016/10/01/creating-a-production-ready-api-with-python-and-django-rest-framework-part-2/" rel="bookmark"
title="Permalink to Creating a production ready API with Python and Django Rest Framework – part 2">Creating a production ready API with Python and Django Rest Framework – part 2</a></h1>
</header>
<div class="entry-content">
<footer class="post-info">
<span>Sat 01 October 2016</span>
<span>| in <a href="https://www.andreagrandi.it/category/development.html">Development</a></span>
<span>| tags: <a href="https://www.andreagrandi.it/tag/api.html">API</a><a href="https://www.andreagrandi.it/tag/django.html">Django</a><a href="https://www.andreagrandi.it/tag/framework.html">framework</a><a href="https://www.andreagrandi.it/tag/python.html">Python</a><a href="https://www.andreagrandi.it/tag/rest.html">rest</a><a href="https://www.andreagrandi.it/tag/tutorial.html">tutorial</a></span>
</footer><!-- /.post-info --> <p>In the <a href="https://www.andreagrandi.it/2016/creating-production-ready-api-python-django-rest-framework-part-1.md">first
part</a>
of this tutorial we have seen how to create a basic API using <strong>Django
Rest Framework</strong>. This second part will explain how to implement
<strong>POST</strong> methods and add different levels of <strong>permissions</strong> and
<strong>authentication</strong>. If you are starting from part 2, you may want to
checkout the source code at this exact point:</p>
<div class="highlight"><pre><span></span>git checkout tutorial-1.4
</pre></div>
<h3>A step back</h3>
<p>Before showing how easy it is to implement a <strong>POST</strong> method for our
existing API, I want to do a step back and show you the "manual way",
using just the
<a href="http://www.django-rest-framework.org/api-guide/views/"><strong>APIView</strong></a>
class. Edit the file <strong>catalog/views.py</strong> and change the code in this
way:</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">django.http</span> <span class="kn">import</span> <span class="n">HttpResponse</span>
<span class="kn">from</span> <span class="nn">rest_framework.response</span> <span class="kn">import</span> <span class="n">Response</span>
<span class="kn">from</span> <span class="nn">rest_framework.views</span> <span class="kn">import</span> <span class="n">APIView</span>
<span class="kn">from</span> <span class="nn">.models</span> <span class="kn">import</span> <span class="n">Product</span>
<span class="kn">from</span> <span class="nn">.serializers</span> <span class="kn">import</span> <span class="n">ProductSerializer</span>
<span class="k">class</span> <span class="nc">ProductList</span><span class="p">(</span><span class="n">APIView</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="n">format</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
<span class="n">products</span> <span class="o">=</span> <span class="n">Product</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">all</span><span class="p">()</span>
<span class="n">serializer</span> <span class="o">=</span> <span class="n">ProductSerializer</span><span class="p">(</span><span class="n">products</span><span class="p">,</span> <span class="n">many</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
<span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="n">serializer</span><span class="o">.</span><span class="n">data</span><span class="p">)</span>
</pre></div>
<p>If we try to use the API again (from the browser of from the http
client), it will still work in the same way. The difference here is that
we are using the very basic <strong>APIView</strong> class and we have explicitly
defined the <strong>GET</strong> method for it.</p>
<h3>Implementing a POST method with APIView</h3>
<p>An API is not being used at its full potential if it's read only. We are
going to implement a POST method for the existing view and testing it
with <a href="https://httpie.org/"><strong>httpie</strong></a> client again. First of all we
need to add an import to <strong>catalog/views.py</strong></p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">rest_framework</span> <span class="kn">import</span> <span class="n">status</span>
</pre></div>
<p>then we add this method to our <strong>ProductList</strong> class:</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">post</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="n">format</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
<span class="n">serializer</span> <span class="o">=</span> <span class="n">ProductSerializer</span><span class="p">(</span><span class="n">data</span><span class="o">=</span><span class="n">request</span><span class="o">.</span><span class="n">data</span><span class="p">)</span>
<span class="k">if</span> <span class="n">serializer</span><span class="o">.</span><span class="n">is_valid</span><span class="p">():</span>
<span class="n">serializer</span><span class="o">.</span><span class="n">save</span><span class="p">()</span>
<span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="n">serializer</span><span class="o">.</span><span class="n">data</span><span class="p">,</span> <span class="n">status</span><span class="o">=</span><span class="n">status</span><span class="o">.</span><span class="n">HTTP_201_CREATED</span><span class="p">)</span>
<span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="n">serializer</span><span class="o">.</span><span class="n">errors</span><span class="p">,</span> <span class="n">status</span><span class="o">=</span><span class="n">status</span><span class="o">.</span><span class="n">HTTP_400_BAD_REQUEST</span><span class="p">)</span>
</pre></div>
<p>Now let's test our <strong>POST</strong> method we just implemented:</p>
<div class="highlight"><pre><span></span>$ http --json POST http://127.0.0.1:8000/products/ <span class="nv">name</span><span class="o">=</span><span class="s2">"Salamino"</span> <span class="nv">description</span><span class="o">=</span><span class="s2">"Salamino Piccante"</span> <span class="nv">price</span><span class="o">=</span><span class="s2">"10.50"</span>
HTTP/1.0 <span class="m">201</span> Created
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Date: Thu, <span class="m">29</span> Sep <span class="m">2016</span> <span class="m">11</span>:48:48 GMT
Server: WSGIServer/0.1 Python/2.7.10
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN
<span class="o">{</span>
<span class="s2">"description"</span>: <span class="s2">"Salamino Piccante"</span>,
<span class="s2">"name"</span>: <span class="s2">"Salamino"</span>,
<span class="s2">"price"</span>: <span class="s2">"10.50"</span>
<span class="o">}</span>
</pre></div>
<p>It works! In case something doesn't work, try to fetch the source code
at this point:</p>
<div class="highlight"><pre><span></span>git checkout tutorial-1.7
</pre></div>
<h3>Implementing a POST method with ListCreateAPIView</h3>
<p>Do you remember when I mentioned at the beginning that there is an easy
way to do the same thing? I wasn't cheating. Let's change again our old
code in <strong>catalog/views.py</strong> but this time we will use a different base
class:</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">django.http</span> <span class="kn">import</span> <span class="n">HttpResponse</span>
<span class="kn">from</span> <span class="nn">rest_framework</span> <span class="kn">import</span> <span class="n">generics</span>
<span class="kn">from</span> <span class="nn">rest_framework.response</span> <span class="kn">import</span> <span class="n">Response</span>
<span class="kn">from</span> <span class="nn">.models</span> <span class="kn">import</span> <span class="n">Product</span>
<span class="kn">from</span> <span class="nn">.serializers</span> <span class="kn">import</span> <span class="n">ProductSerializer</span>
<span class="k">class</span> <span class="nc">ProductList</span><span class="p">(</span><span class="n">generics</span><span class="o">.</span><span class="n">ListCreateAPIView</span><span class="p">):</span>
<span class="n">queryset</span> <span class="o">=</span> <span class="n">Product</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">all</span><span class="p">()</span>
<span class="n">serializer_class</span> <span class="o">=</span> <span class="n">ProductSerializer</span>
</pre></div>
<p>let's test this again with <strong>httpie</strong>:</p>
<div class="highlight"><pre><span></span>$ http --json POST http://127.0.0.1:8000/products/ <span class="nv">name</span><span class="o">=</span><span class="s2">"Pecorino"</span> <span class="nv">description</span><span class="o">=</span><span class="s2">"Pecorino Sardo"</span> <span class="nv">price</span><span class="o">=</span><span class="s2">"7.00"</span>
HTTP/1.0 <span class="m">201</span> Created
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Date: Thu, <span class="m">29</span> Sep <span class="m">2016</span> <span class="m">15</span>:21:20 GMT
Server: WSGIServer/0.1 Python/2.7.10
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN
<span class="o">{</span>
<span class="s2">"description"</span>: <span class="s2">"Pecorino Sardo"</span>,
<span class="s2">"name"</span>: <span class="s2">"Pecorino"</span>,
<span class="s2">"price"</span>: <span class="s2">"7.00"</span>
<span class="o">}</span>
</pre></div>
<p>We just POSTed some data on the API! How can it work? Well, we have
changed the base class from <strong>ListAPIView</strong> to
<a href="http://www.django-rest-framework.org/api-guide/generic-views/#listcreateapiview"><strong>ListCreateAPIView</strong></a>.
This particular class implements <strong>a generic POST method</strong> that will
accept and validate all the fields through the specified serializer.</p>
<h3>Authentication</h3>
<p>Now our API let us add products to the catalog, amazing! But... is it
exactly what we want? In a real scenario we don't want any random user
to be able to add products in our database, so we are going to protect
the POST method allowing only Admin users.</p>
<p>Before digging into Django Rest Framework permissions, we need to setup
an authentication system. For simplicity we will implement
<a href="http://www.django-rest-framework.org/api-guide/authentication/#tokenauthentication"><strong>TokenAuthentication</strong></a>.
As first step we need to edit <strong>settings.py</strong> and
insert <strong>rest_framework.authtoken</strong> in the <strong>INSTALLED_APPS</strong>:</p>
<div class="highlight"><pre><span></span> <span class="o">...</span>
<span class="s1">'rest_framework'</span><span class="p">,</span>
<span class="s1">'rest_framework.authtoken'</span><span class="p">,</span>
<span class="s1">'catalog'</span><span class="p">,</span>
<span class="p">]</span>
</pre></div>
<p>after this, we need to add <strong>TokenAuthentication</strong> as default
authentication class (append this in <strong>settings.py</strong> at the end):</p>
<div class="highlight"><pre><span></span><span class="n">REST_FRAMEWORK</span> <span class="o">=</span> <span class="p">{</span>
<span class="s1">'DEFAULT_AUTHENTICATION_CLASSES'</span><span class="p">:</span> <span class="p">(</span>
<span class="s1">'rest_framework.authentication.TokenAuthentication'</span><span class="p">,</span>
<span class="p">)</span>
<span class="p">}</span>
</pre></div>
<p>Finally we need to add a particular URL to the project so that clients
will be able to call an endpoint passing <strong>username</strong> and <strong>password</strong>
to get a <strong>token</strong> back. Edit <strong>drftutorial/urls.py</strong> and make it's like
this:</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">django.conf.urls</span> <span class="kn">import</span> <span class="n">url</span><span class="p">,</span> <span class="n">include</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">rest_framework.authtoken.views</span> <span class="kn">import</span> <span class="n">obtain_auth_token</span>
<span class="n">urlpatterns</span> <span class="o">=</span> <span class="p">[</span>
<span class="n">url</span><span class="p">(</span><span class="sa">r</span><span class="s1">'^admin/'</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="sa">r</span><span class="s1">'^'</span><span class="p">,</span> <span class="n">include</span><span class="p">(</span><span class="s1">'catalog.urls'</span><span class="p">)),</span>
<span class="n">url</span><span class="p">(</span><span class="sa">r</span><span class="s1">'^api-token-auth/'</span><span class="p">,</span> <span class="n">obtain_auth_token</span><span class="p">),</span>
<span class="p">]</span>
</pre></div>
<p>Don't forget to re-run the <strong>migrations</strong>, because TokenAuthorization
needs to change a couple of tables:</p>
<div class="highlight"><pre><span></span>$ ./manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, authtoken, catalog, contenttypes, sessions
Running migrations:
Applying authtoken.0001_initial... OK
Applying authtoken.0002_auto_20160226_1747... OK
</pre></div>
<p>In case you had any problem changing the code up to this point, you can
always fetch the related git tag:</p>
<div class="highlight"><pre><span></span>git checkout tutorial-1.9
</pre></div>
<h4>Testing the Authentication</h4>
<p>Before testing the authentication, make sure you created at least the
Django <strong>superuser</strong> with:</p>
<div class="highlight"><pre><span></span>$ ./manage.py createsuperuser
</pre></div>
<p>now let's try to <strong>obtain the token</strong> we will need later for our API
calls:</p>
<div class="highlight"><pre><span></span>$ http --json POST http://127.0.0.1:8000/api-token-auth/ <span class="nv">username</span><span class="o">=</span><span class="s2">"yourusername"</span> <span class="nv">password</span><span class="o">=</span><span class="s2">"yourpassword"</span>
HTTP/1.0 <span class="m">200</span> OK
Allow: POST, OPTIONS
Content-Type: application/json
Date: Fri, <span class="m">30</span> Sep <span class="m">2016</span> <span class="m">08</span>:55:07 GMT
Server: WSGIServer/0.1 Python/2.7.11
X-Frame-Options: SAMEORIGIN
<span class="o">{</span>
<span class="s2">"token"</span>: <span class="s2">"bc9514f0892368cfd0ea792a977aff55d53e3634"</span>
<span class="o">}</span>
</pre></div>
<p>We will need to pass this token in every API call we want to be
authenticated. The token is being passed through the "Authentication"
header parameter.</p>
<h3>API Permissions</h3>
<p>Authentication is something that identify the user with a particular
system. Permissions instead are the level of things that are allowed or
not allowed for a particular user. In our case we said we want to let
Admin users to be able to POST new products and we want to let even
anonymous users to GET the product list.</p>
<p>Django Rest Framework has some built-in classes that we can apply to our
views to define the level of permissions. We could have used the
<a href="http://www.django-rest-framework.org/api-guide/permissions/#isadminuser"><strong>IsAdminUser</strong></a>
class, but it would not allow anonymous users to perform the GET
request. Or we could have used
<a href="http://www.django-rest-framework.org/api-guide/permissions/#isauthenticatedorreadonly"><strong>IsAuthenticatedOrReadOnly</strong></a>
class, but this would allow any registered user to add products (and we
want to let only admins).</p>
<p>Or...we can define our own permission class and have exactly what we
want. Create a new file <strong>catalog/permissions.py</strong></p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">rest_framework.permissions</span> <span class="kn">import</span> <span class="n">BasePermission</span><span class="p">,</span> <span class="n">SAFE_METHODS</span>
<span class="k">class</span> <span class="nc">IsAdminOrReadOnly</span><span class="p">(</span><span class="n">BasePermission</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">has_permission</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="n">view</span><span class="p">):</span>
<span class="k">if</span> <span class="n">request</span><span class="o">.</span><span class="n">method</span> <span class="ow">in</span> <span class="n">SAFE_METHODS</span><span class="p">:</span>
<span class="k">return</span> <span class="bp">True</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">return</span> <span class="n">request</span><span class="o">.</span><span class="n">user</span><span class="o">.</span><span class="n">is_staff</span>
</pre></div>
<p>Just as a side note, <strong>SAFE_METHODS</strong> are <strong>GET</strong>, <strong>HEAD</strong> and
<strong>OPTIONS</strong>. These method are considered "safe" because they don't
change any existing data. Open <strong>catalog/views.py</strong> again, import this
at the beginning:</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">.permissions</span> <span class="kn">import</span> <span class="n">IsAdminOrReadOnly</span>
</pre></div>
<p>and set this as <strong>permission_classes</strong> to <strong>ProductList</strong>:</p>
<div class="highlight"><pre><span></span><span class="o">...</span>
<span class="n">serializer_class</span> <span class="o">=</span> <span class="n">ProductSerializer</span>
<span class="n">permission_classes</span> <span class="o">=</span> <span class="p">(</span><span class="n">IsAdminOrReadOnly</span><span class="p">,</span> <span class="p">)</span>
</pre></div>
<p>Let's now try to add a new product using the <strong>token</strong> we got before
(you will have to use your own token of course, mine only works on my
local db):</p>
<div class="highlight"><pre><span></span>$ http --json POST http://127.0.0.1:8000/products/ <span class="nv">name</span><span class="o">=</span><span class="s2">"Lardo"</span> <span class="nv">description</span><span class="o">=</span><span class="s2">"Lardo di Colonnata"</span> <span class="nv">price</span><span class="o">=</span><span class="s2">"8.50"</span> <span class="s1">'Authorization: Token bc9514f0892368cfd0ea792a977aff55d53e3634'</span>
HTTP/1.0 <span class="m">201</span> Created
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Date: Fri, <span class="m">30</span> Sep <span class="m">2016</span> <span class="m">13</span>:04:13 GMT
Server: WSGIServer/0.1 Python/2.7.11
Vary: Accept
X-Frame-Options: SAMEORIGIN
<span class="o">{</span>
<span class="s2">"description"</span>: <span class="s2">"Lardo di Colonnata"</span>,
<span class="s2">"name"</span>: <span class="s2">"Lardo"</span>,
<span class="s2">"price"</span>: <span class="s2">"8.50"</span>
<span class="o">}</span>
</pre></div>
<p>It worked! We have now protected our API so that not admin people can't
create any product. If you have any problem with the code, you can check
it out with this tag:</p>
<div class="highlight"><pre><span></span>git checkout tutorial-1.10
</pre></div>
<h3>Wrapping Up</h3>
<p>We have now implemented the POST method to add new products to our
catalog. In the <a href="https://www.andreagrandi.it/2017/1-creating-a-production-ready-api-with-python-and-django-rest-framework-part-3.md">next
episode</a>
we will see how to implement endpoints to get a single product, to
update or delete products and finally we will allow registered users to
send a review for a specific product.</p>
<h3>Feedback Please</h3>
<p>I know, this blog doesn't have any "comment" feature (I was tired of
dealing with spam), but if you want to provide some feedback you can
still do it by email. Just visit my
<a href="https://www.andreagrandi.it/pages/about.md"><strong>About</strong></a> page, you will find my
email there.</p>
<a class="readmore" href="https://www.andreagrandi.it/2016/10/01/creating-a-production-ready-api-with-python-and-django-rest-framework-part-2/">read more</a>
<p><a href="https://www.andreagrandi.it/2016/10/01/creating-a-production-ready-api-with-python-and-django-rest-framework-part-2/#disqus_thread">comments</a></p> </div><!-- /.entry-content -->
</article></li>
<li><article class="hentry">
<header>
<h1><a href="https://www.andreagrandi.it/2016/09/28/creating-production-ready-api-python-django-rest-framework-part-1/" rel="bookmark"
title="Permalink to Creating a production ready API with Python and Django Rest Framework - part 1">Creating a production ready API with Python and Django Rest Framework - part 1</a></h1>
</header>
<div class="entry-content">
<footer class="post-info">
<span>Wed 28 September 2016</span>
<span>| in <a href="https://www.andreagrandi.it/category/development.html">Development</a></span>
<span>| tags: <a href="https://www.andreagrandi.it/tag/api.html">API</a><a href="https://www.andreagrandi.it/tag/django.html">Django</a><a href="https://www.andreagrandi.it/tag/framework.html">framework</a><a href="https://www.andreagrandi.it/tag/python.html">Python</a><a href="https://www.andreagrandi.it/tag/rest.html">rest</a><a href="https://www.andreagrandi.it/tag/tutorial.html">tutorial</a></span>
</footer><!-- /.post-info --> <p>The aim if this tutorial is to show how to create a production ready
solution for a <strong>REST API</strong>, using <strong>Python</strong> and <a href="http://www.django-rest-framework.org">Django Rest
Framework</a>. I will show you how to
first create a very basic API, how to handle the authentication and
permissions and I will cover deployment and hosting of images. The full
source code of the tutorial is available
at: <a href="https://github.com/andreagrandi/drf-tutorial">https://github.com/andreagrandi/drf-tutorial</a></p>
<h3>Summary of the complete tutorial</h3>
<ol>
<li>Create the basic structure for the API</li>
<li><a href="https://www.andreagrandi.it/2016/creating-a-production-ready-api-with-python-and-django-rest-framework-part-2.md">Add Authentication and POST
methods</a></li>
<li><a href="https://www.andreagrandi.it/2017/1-creating-a-production-ready-api-with-python-and-django-rest-framework-part-3.md">Handling details and changes to existing
data</a></li>
<li><a href="https://www.andreagrandi.it/2017/2-creating-a-production-ready-api-with-python-and-django-rest-framework-part-4.md">Testing the API</a></li>
<li>Switching from Sqlite to PostgreSQL</li>
<li>Hosting the API on Heroku</li>
<li>Add an Image field and save images to S3</li>
</ol>
<h3>Create the basic structure for the API</h3>
<p>For this tutorial I will assume you have correctly installed at least
<strong>Python</strong> (I will use Python 2.7.x),
<a href="https://pypi.python.org/pypi/virtualenv"><strong>virtualenv</strong></a> and
<a href="https://virtualenvwrapper.readthedocs.io/en/latest/"><strong>virtualenvwrapper</strong></a>
on your system and I will explain how to create everything else step by
step.</p>
<p><strong>Note:</strong> at the time of writing, the tutorial has been based on
<strong>Django 1.10.1</strong> and <strong>Django Rest Framework 3.4.7</strong></p>
<h3>Creating the main project structure</h3>
<div class="highlight"><pre><span></span>mkdir drf-tutorial
mkvirtualenv drf-tutorial
<span class="nb">cd</span> drf-tutorial
pip install django djangorestframework
django-admin.py startproject drftutorial .
<span class="nb">cd</span> drftutorial
django-admin.py startapp catalog
</pre></div>
<h3>Data Model</h3>
<p>We will create the API for a generic products catalog, using a very
simple structure (to keep things simple). Edit the file
<strong>catalog/models.py</strong> adding these lines:</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">__future__</span> <span class="kn">import</span> <span class="n">unicode_literals</span>
<span class="kn">from</span> <span class="nn">django.db</span> <span class="kn">import</span> <span class="n">models</span>
<span class="k">class</span> <span class="nc">Product</span><span class="p">(</span><span class="n">models</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>
<span class="n">name</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">255</span><span class="p">)</span>
<span class="n">description</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">TextField</span><span class="p">()</span>
<span class="n">price</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">DecimalField</span><span class="p">(</span><span class="n">decimal_places</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span> <span class="n">max_digits</span><span class="o">=</span><span class="mi">20</span><span class="p">)</span>
</pre></div>
<p>after creating the model, we need to add 'catalog' application to
<strong>INSTALLED_APPS</strong>. Edit <strong>settings.py</strong> and add the app at the end of
the list:</p>
<div class="highlight"><pre><span></span><span class="n">INSTALLED_APPS</span> <span class="o">=</span> <span class="p">[</span>
<span class="s1">'django.contrib.admin'</span><span class="p">,</span>
<span class="s1">'django.contrib.auth'</span><span class="p">,</span>
<span class="s1">'django.contrib.contenttypes'</span><span class="p">,</span>
<span class="s1">'django.contrib.sessions'</span><span class="p">,</span>
<span class="s1">'django.contrib.messages'</span><span class="p">,</span>
<span class="s1">'django.contrib.staticfiles'</span><span class="p">,</span>
<span class="s1">'catalog'</span><span class="p">,</span>
<span class="p">]</span>
</pre></div>
<p>at this point the Django application will be recognised by the project
and we can create and apply the schema migration:</p>
<div class="highlight"><pre><span></span><span class="o">(</span>drf-tutorial<span class="o">)</span> ➜ drftutorial git:<span class="o">(</span>235dfcc<span class="o">)</span> ✗ ./manage.py makemigrations
Migrations <span class="k">for</span> <span class="s1">'catalog'</span>:
catalog/migrations/0001_initial.py:
- Create model Product
<span class="o">(</span>drf-tutorial<span class="o">)</span> ➜ drftutorial git:<span class="o">(</span>235dfcc<span class="o">)</span> ✗ ./manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, catalog, contenttypes, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying catalog.0001_initial... OK
Applying sessions.0001_initial... OK
</pre></div>
<h3>API Serializer</h3>
<p><a href="http://www.django-rest-framework.org/api-guide/serializers/"><strong>Serializers</strong></a>
are those components used to convert the received data from JSON format
to the relative Django model and viceversa. Create the new file
<strong>catalog/serializers.py</strong> and place this code inside:</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">.models</span> <span class="kn">import</span> <span class="n">Product</span>
<span class="kn">from</span> <span class="nn">rest_framework</span> <span class="kn">import</span> <span class="n">serializers</span>
<span class="k">class</span> <span class="nc">ProductSerializer</span><span class="p">(</span><span class="n">serializers</span><span class="o">.</span><span class="n">ModelSerializer</span><span class="p">):</span>
<span class="k">class</span> <span class="nc">Meta</span><span class="p">:</span>
<span class="n">model</span> <span class="o">=</span> <span class="n">Product</span>
<span class="n">fields</span> <span class="o">=</span> <span class="p">(</span><span class="s1">'name'</span><span class="p">,</span> <span class="s1">'description'</span><span class="p">,</span> <span class="s1">'price'</span><span class="p">)</span>
</pre></div>
<p>In this case we are using a
<a href="http://www.django-rest-framework.org/api-guide/serializers/#modelserializer"><strong>ModelSerializer</strong></a>.
We need to create a new class from it, and specify the model attribute,
that's it. In this case we also specify the fields we want to return.</p>
<h3>API View</h3>
<p>The serializer alone is not able to respond to an API request, that's
why we need to implement a view. In this first version of the view (that
we will improve soon) we will "manually" transform the data available in
the serializer dictionary to a JSON response. In <strong>catalog/views.py</strong>
add this code:</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">django.http</span> <span class="kn">import</span> <span class="n">HttpResponse</span>
<span class="kn">from</span> <span class="nn">rest_framework.renderers</span> <span class="kn">import</span> <span class="n">JSONRenderer</span>
<span class="kn">from</span> <span class="nn">rest_framework.parsers</span> <span class="kn">import</span> <span class="n">JSONParser</span>
<span class="kn">from</span> <span class="nn">.models</span> <span class="kn">import</span> <span class="n">Product</span>
<span class="kn">from</span> <span class="nn">.serializers</span> <span class="kn">import</span> <span class="n">ProductSerializer</span>
<span class="k">class</span> <span class="nc">JSONResponse</span><span class="p">(</span><span class="n">HttpResponse</span><span class="p">):</span>
<span class="sd">"""</span>
<span class="sd"> An HttpResponse that renders its content into JSON.</span>
<span class="sd"> """</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="n">content</span> <span class="o">=</span> <span class="n">JSONRenderer</span><span class="p">()</span><span class="o">.</span><span class="n">render</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
<span class="n">kwargs</span><span class="p">[</span><span class="s1">'content_type'</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'application/json'</span>
<span class="nb">super</span><span class="p">(</span><span class="n">JSONResponse</span><span class="p">,</span> <span class="bp">self</span><span class="p">)</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">content</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">product_list</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
<span class="k">if</span> <span class="n">request</span><span class="o">.</span><span class="n">method</span> <span class="o">==</span> <span class="s1">'GET'</span><span class="p">:</span>
<span class="n">products</span> <span class="o">=</span> <span class="n">Product</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">all</span><span class="p">()</span>
<span class="n">serializer</span> <span class="o">=</span> <span class="n">ProductSerializer</span><span class="p">(</span><span class="n">products</span><span class="p">,</span> <span class="n">many</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
<span class="k">return</span> <span class="n">JSONResponse</span><span class="p">(</span><span class="n">serializer</span><span class="o">.</span><span class="n">data</span><span class="p">)</span>
</pre></div>
<p>At this point we need to tell our Django app to use this API view when a
certain URL is requested. We first need to add this code in
<strong>catalog/urls.py</strong></p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">django.conf.urls</span> <span class="kn">import</span> <span class="n">url</span>
<span class="kn">from</span> <span class="nn">.</span> <span class="kn">import</span> <span class="n">views</span>
<span class="n">urlpatterns</span> <span class="o">=</span> <span class="p">[</span>
<span class="n">url</span><span class="p">(</span><span class="sa">r</span><span class="s1">'^products/$'</span><span class="p">,</span> <span class="n">views</span><span class="o">.</span><span class="n">product_list</span><span class="p">),</span>
<span class="p">]</span>
</pre></div>
<p>and finally we need to add this to <strong>drftutorial/urls.py</strong></p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">django.conf.urls</span> <span class="kn">import</span> <span class="n">url</span><span class="p">,</span> <span class="n">include</span>
<span class="kn">from</span> <span class="nn">django.contrib</span> <span class="kn">import</span> <span class="n">admin</span>
<span class="n">urlpatterns</span> <span class="o">=</span> <span class="p">[</span>
<span class="n">url</span><span class="p">(</span><span class="sa">r</span><span class="s1">'^admin/'</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="sa">r</span><span class="s1">'^'</span><span class="p">,</span> <span class="n">include</span><span class="p">(</span><span class="s1">'catalog.urls'</span><span class="p">)),</span>
<span class="p">]</span>
</pre></div>
<h4>Testing our work</h4>
<p>At this point we should be able to start our Django app:</p>
<div class="highlight"><pre><span></span>./manage.py runserver
</pre></div>
<p>Let's install a tool that will help us to test the API:</p>
<div class="highlight"><pre><span></span>pip install httpie
</pre></div>
<p>now we can use it to call our URL:</p>
<div class="highlight"><pre><span></span>$ http http://127.0.0.1:8000/products/
HTTP/1.0 <span class="m">200</span> OK
Content-Type: application/json
Date: Wed, <span class="m">28</span> Sep <span class="m">2016</span> <span class="m">09</span>:54:50 GMT
Server: WSGIServer/0.1 Python/2.7.11
X-Frame-Options: SAMEORIGIN
<span class="o">[]</span>
</pre></div>
<p>It works! It's an empty response of course, because we still don't have
any data to show, but we will see later how to load some example data in
our database. If you have been able to follow the tutorial up to this
point, that's good. If not, don't worry. You can checkout the code at
exactly this point of the tutorial doing:</p>
<div class="highlight"><pre><span></span>git checkout tutorial-1.0
</pre></div>
<h3>Improving the API View</h3>
<p>There is a quicker and more efficient way of implementing the same API
view we have seen before. We can use a class based view, in particular
the APIView class and also have the JSON response implemented
automatically. Change the code inside <strong>catalog/views.py</strong> with this
one:</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">django.http</span> <span class="kn">import</span> <span class="n">HttpResponse</span>
<span class="kn">from</span> <span class="nn">rest_framework.views</span> <span class="kn">import</span> <span class="n">APIView</span>
<span class="kn">from</span> <span class="nn">rest_framework.response</span> <span class="kn">import</span> <span class="n">Response</span>
<span class="kn">from</span> <span class="nn">.models</span> <span class="kn">import</span> <span class="n">Product</span>
<span class="kn">from</span> <span class="nn">.serializers</span> <span class="kn">import</span> <span class="n">ProductSerializer</span>
<span class="k">class</span> <span class="nc">ProductList</span><span class="p">(</span><span class="n">APIView</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="n">format</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
<span class="n">products</span> <span class="o">=</span> <span class="n">Product</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">all</span><span class="p">()</span>
<span class="n">serializer</span> <span class="o">=</span> <span class="n">ProductSerializer</span><span class="p">(</span><span class="n">products</span><span class="p">,</span> <span class="n">many</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
<span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="n">serializer</span><span class="o">.</span><span class="n">data</span><span class="p">)</span>
</pre></div>
<p>You will also have to change <strong>catalog/urls.py</strong> in this way:</p>
<div class="highlight"><pre><span></span><span class="n">urlpatterns</span> <span class="o">=</span> <span class="p">[</span>
<span class="n">url</span><span class="p">(</span><span class="sa">r</span><span class="s1">'^products/$'</span><span class="p">,</span> <span class="n">views</span><span class="o">.</span><span class="n">ProductList</span><span class="o">.</span><span class="n">as_view</span><span class="p">()),</span>
<span class="p">]</span>
</pre></div>
<p>You can check the source code for this step of the tutorial with:</p>
<div class="highlight"><pre><span></span><span class="n">git</span> <span class="n">checkout</span> <span class="n">tutorial</span><span class="o">-</span><span class="mf">1.1</span>
</pre></div>
<p>There is also another way of writing the same view. Let's try it with
<a href="http://www.django-rest-framework.org/api-guide/generic-views/#listapiview"><strong>ListAPIView</strong></a>.
Edit <strong>catalog/views.py</strong> again and substitute the code with this one:</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">django.http</span> <span class="kn">import</span> <span class="n">HttpResponse</span>
<span class="kn">from</span> <span class="nn">rest_framework</span> <span class="kn">import</span> <span class="n">generics</span>
<span class="kn">from</span> <span class="nn">rest_framework.response</span> <span class="kn">import</span> <span class="n">Response</span>
<span class="kn">from</span> <span class="nn">.models</span> <span class="kn">import</span> <span class="n">Product</span>
<span class="kn">from</span> <span class="nn">.serializers</span> <span class="kn">import</span> <span class="n">ProductSerializer</span>
<span class="k">class</span> <span class="nc">ProductList</span><span class="p">(</span><span class="n">generics</span><span class="o">.</span><span class="n">ListAPIView</span><span class="p">):</span>
<span class="n">queryset</span> <span class="o">=</span> <span class="n">Product</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">all</span><span class="p">()</span>
<span class="n">serializer_class</span> <span class="o">=</span> <span class="n">ProductSerializer</span>
</pre></div>
<p>With a <strong>ListAPIView</strong> we are basically creating a read-only API that is
supposed to return a list of items. We need to specify a <strong>queryset</strong>
and the <strong>serializer_class</strong> parameters. Once again, you can get up to
this point, checking out the related git tag:</p>
<div class="highlight"><pre><span></span>git checkout tutorial-1.2
</pre></div>
<h3>Creating Initial Data</h3>
<p>An API that doesn't return any data is not very useful, right? Also, at
the moment we haven't implemented yet any feature that let us insert any
data. That's why I've created a data migration for you that will insert
some data for you. You may notice that the example data contains some
Italian products... out of the scope of this tutorial, I strongly advise
you to google this products and if you ever happen to visit Italy, try
them. You won't regret!</p>
<p>Going back to our data migration, you first have to create an empty one
with:</p>
<div class="highlight"><pre><span></span>./manage.py makemigrations --empty catalog
</pre></div>
<p>and then open the file that has been created under
<strong>catalog/migrations/</strong> named <strong>0002_.....</strong> (your name will be
different from mine, so just edit the one starting with 0002 and you
will be fine) and fill it with this code:</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">__future__</span> <span class="kn">import</span> <span class="n">unicode_literals</span>
<span class="kn">from</span> <span class="nn">django.db</span> <span class="kn">import</span> <span class="n">migrations</span>
<span class="k">def</span> <span class="nf">create_initial_products</span><span class="p">(</span><span class="n">apps</span><span class="p">,</span> <span class="n">schema_editor</span><span class="p">):</span>
<span class="n">Product</span> <span class="o">=</span> <span class="n">apps</span><span class="o">.</span><span class="n">get_model</span><span class="p">(</span><span class="s1">'catalog'</span><span class="p">,</span> <span class="s1">'Product'</span><span class="p">)</span>
<span class="n">Product</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s1">'Salame'</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s1">'Salame Toscano'</span><span class="p">,</span> <span class="n">price</span><span class="o">=</span><span class="mi">12</span><span class="p">)</span><span class="o">.</span><span class="n">save</span><span class="p">()</span>
<span class="n">Product</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s1">'Olio Balsamico'</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s1">'Olio balsamico di Modena'</span><span class="p">,</span> <span class="n">price</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span><span class="o">.</span><span class="n">save</span><span class="p">()</span>
<span class="n">Product</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s1">'Parmigiano'</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s1">'Parmigiano Reggiano'</span><span class="p">,</span> <span class="n">price</span><span class="o">=</span><span class="mf">8.50</span><span class="p">)</span><span class="o">.</span><span class="n">save</span><span class="p">()</span>
<span class="n">Product</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s1">'Olio'</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s1">'Olio Oliva Toscano'</span><span class="p">,</span> <span class="n">price</span><span class="o">=</span><span class="mi">13</span><span class="p">)</span><span class="o">.</span><span class="n">save</span><span class="p">()</span>
<span class="n">Product</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s1">'Porchetta'</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s1">'Porchetta toscana cotta a legna'</span><span class="p">,</span> <span class="n">price</span><span class="o">=</span><span class="mf">7.50</span><span class="p">)</span><span class="o">.</span><span class="n">save</span><span class="p">()</span>
<span class="n">Product</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s1">'Cantucci'</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s1">'Cantucci di Prato'</span><span class="p">,</span> <span class="n">price</span><span class="o">=</span><span class="mi">4</span><span class="p">)</span><span class="o">.</span><span class="n">save</span><span class="p">()</span>
<span class="n">Product</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s1">'Vino Rosso'</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s1">'Vino Rosso del Chianti'</span><span class="p">,</span> <span class="n">price</span><span class="o">=</span><span class="mf">9.50</span><span class="p">)</span><span class="o">.</span><span class="n">save</span><span class="p">()</span>
<span class="n">Product</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s1">'Brigidini'</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s1">'Brigidini di Lamporecchio'</span><span class="p">,</span> <span class="n">price</span><span class="o">=</span><span class="mf">3.50</span><span class="p">)</span><span class="o">.</span><span class="n">save</span><span class="p">()</span>
<span class="k">class</span> <span class="nc">Migration</span><span class="p">(</span><span class="n">migrations</span><span class="o">.</span><span class="n">Migration</span><span class="p">):</span>
<span class="n">dependencies</span> <span class="o">=</span> <span class="p">[</span>
<span class="p">(</span><span class="s1">'catalog'</span><span class="p">,</span> <span class="s1">'0001_initial'</span><span class="p">),</span>
<span class="p">]</span>
<span class="n">operations</span> <span class="o">=</span> <span class="p">[</span>
<span class="n">migrations</span><span class="o">.</span><span class="n">RunPython</span><span class="p">(</span><span class="n">create_initial_products</span><span class="p">),</span>
<span class="p">]</span>
</pre></div>
<p>to apply the migration we just created, just do:</p>
<div class="highlight"><pre><span></span>./manage.py migrate
</pre></div>
<p>If you try to test the API again from the command line, you will get
these products back:</p>
<div class="highlight"><pre><span></span>$ http http://127.0.0.1:8000/products/
HTTP/1.0 <span class="m">200</span> OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
Date: Wed, <span class="m">28</span> Sep <span class="m">2016</span> <span class="m">12</span>:29:36 GMT
Server: WSGIServer/0.1 Python/2.7.11
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN
<span class="o">[</span>
<span class="o">{</span>
<span class="s2">"description"</span>: <span class="s2">"Salame Toscano"</span>,
<span class="s2">"name"</span>: <span class="s2">"Salame"</span>,
<span class="s2">"price"</span>: <span class="s2">"12.00"</span>
<span class="o">}</span>,
<span class="o">{</span>
<span class="s2">"description"</span>: <span class="s2">"Olio balsamico di Modena"</span>,
<span class="s2">"name"</span>: <span class="s2">"Olio Balsamico"</span>,
<span class="s2">"price"</span>: <span class="s2">"10.00"</span>
<span class="o">}</span>,
<span class="o">{</span>
<span class="s2">"description"</span>: <span class="s2">"Parmigiano Reggiano"</span>,
<span class="s2">"name"</span>: <span class="s2">"Parmigiano"</span>,
<span class="s2">"price"</span>: <span class="s2">"8.50"</span>
<span class="o">}</span>,
<span class="o">{</span>
<span class="s2">"description"</span>: <span class="s2">"Olio Oliva Toscano"</span>,
<span class="s2">"name"</span>: <span class="s2">"Olio"</span>,
<span class="s2">"price"</span>: <span class="s2">"13.00"</span>
<span class="o">}</span>,
<span class="o">{</span>
<span class="s2">"description"</span>: <span class="s2">"Porchetta toscana cotta a legna"</span>,
<span class="s2">"name"</span>: <span class="s2">"Porchetta"</span>,
<span class="s2">"price"</span>: <span class="s2">"7.50"</span>
<span class="o">}</span>,
<span class="o">{</span>
<span class="s2">"description"</span>: <span class="s2">"Cantucci di Prato"</span>,
<span class="s2">"name"</span>: <span class="s2">"Cantucci"</span>,
<span class="s2">"price"</span>: <span class="s2">"4.00"</span>
<span class="o">}</span>,
<span class="o">{</span>
<span class="s2">"description"</span>: <span class="s2">"Vino Rosso del Chianti"</span>,
<span class="s2">"name"</span>: <span class="s2">"Vino Rosso"</span>,
<span class="s2">"price"</span>: <span class="s2">"9.50"</span>
<span class="o">}</span>,
<span class="o">{</span>
<span class="s2">"description"</span>: <span class="s2">"Brigidini di Lamporecchio"</span>,
<span class="s2">"name"</span>: <span class="s2">"Brigidini"</span>,
<span class="s2">"price"</span>: <span class="s2">"3.50"</span>
<span class="o">}</span>
<span class="o">]</span>
</pre></div>
<p>Again, you can get up to this point with:</p>
<div class="highlight"><pre><span></span>git checkout tutorial-1.3
</pre></div>
<h3>One more thing...</h3>
<p>No, we are not going to present a new amazing device, I'm sorry, but I
want to show you a nice Django Rest Framework feature you can have
without much additional work. Edit <strong>settings.py</strong> and add
<strong>rest_framework</strong> to the list of <strong>INSTALLED_APPS</strong>:</p>
<div class="highlight"><pre><span></span><span class="n">INSTALLED_APPS</span> <span class="o">=</span> <span class="p">[</span>
<span class="s1">'django.contrib.admin'</span><span class="p">,</span>
<span class="s1">'django.contrib.auth'</span><span class="p">,</span>
<span class="s1">'django.contrib.contenttypes'</span><span class="p">,</span>
<span class="s1">'django.contrib.sessions'</span><span class="p">,</span>
<span class="s1">'django.contrib.messages'</span><span class="p">,</span>
<span class="s1">'django.contrib.staticfiles'</span><span class="p">,</span>
<span class="s1">'rest_framework'</span><span class="p">,</span>
<span class="s1">'catalog'</span><span class="p">,</span>
<span class="p">]</span>
</pre></div>
<p>Now, if you are still running the Django app, try to visit this url from
your browser: <a href="http://127.0.0.1:8000/products/">http://127.0.0.1:8000/products/</a><br>
That's very nice, isn't it? You can have browsable APIs at no cost.</p>
<h3>Wrapping Up</h3>
<p>I've mentioned at the beginning that this is just the first part of my
API tutorial. In the <a href="https://www.andreagrandi.it/2016/creating-a-production-ready-api-with-python-and-django-rest-framework-part-2.md">next
part</a>
I will show you how to let the consumer of your API add some products
and leave reviews (we will introduce a new model for this). Also, we
will see how to set proper permissions to these new API methods so that
only admin users will be able to add products while normal users will be
able to add reviews. So, if you feel ready, you can begin to follow the
<a href="https://www.andreagrandi.it/2016/creating-a-production-ready-api-with-python-and-django-rest-framework-part-2.md">second part of this tutorial</a></p>
<h3>References</h3>
<p>Some parts of this tutorial and a few examples have been taken directly
from the original <a href="http://www.django-rest-framework.org/tutorial/quickstart/">Django Rest Framework
tutorial</a>.</p>
<a class="readmore" href="https://www.andreagrandi.it/2016/09/28/creating-production-ready-api-python-django-rest-framework-part-1/">read more</a>
<p><a href="https://www.andreagrandi.it/2016/09/28/creating-production-ready-api-python-django-rest-framework-part-1/#disqus_thread">comments</a></p> </div><!-- /.entry-content -->
</article></li>
<li><article class="hentry">
<header>
<h1><a href="https://www.andreagrandi.it/2016/05/10/using-python-ipdb-from-jupyter/" rel="bookmark"
title="Permalink to Using Python ipdb from Jupyter">Using Python ipdb from Jupyter</a></h1>
</header>
<div class="entry-content">
<footer class="post-info">
<span>Tue 10 May 2016</span>
<span>| in <a href="https://www.andreagrandi.it/category/development.html">Development</a></span>
<span>| tags: <a href="https://www.andreagrandi.it/tag/debugging.html">debugging</a><a href="https://www.andreagrandi.it/tag/ipdb.html">ipdb</a><a href="https://www.andreagrandi.it/tag/python.html">Python</a></span>
</footer><!-- /.post-info --> <p>If we try to use the usual ipdb commands from a Jupyter (IPython
notebook)</p>
<div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">ipdb</span><span class="p">;</span> <span class="n">ipdb</span><span class="o">.</span><span class="n">set_trace</span><span class="p">()</span>
</pre></div>
<p>we will get a similar error:</p>
<div class="highlight"><pre><span></span><span class="o">--------------------------------------------------------------------------</span>
<span class="n">MultipleInstanceError</span> <span class="n">Traceback</span> <span class="p">(</span><span class="n">most</span> <span class="n">recent</span> <span class="n">call</span> <span class="n">last</span><span class="p">)</span>
<span class="o"><</span><span class="n">ipython</span><span class="o">-</span><span class="nb">input</span><span class="o">-</span><span class="mi">1</span><span class="o">-</span><span class="n">f2b356251c56</span><span class="o">></span> <span class="ow">in</span> <span class="o"><</span><span class="n">module</span><span class="o">></span><span class="p">()</span>
<span class="mi">1</span> <span class="n">a</span><span class="o">=</span><span class="mi">4</span>
<span class="o">----></span> <span class="mi">2</span> <span class="kn">import</span> <span class="nn">ipdb</span><span class="p">;</span> <span class="n">ipdb</span><span class="o">.</span><span class="n">set_trace</span><span class="p">()</span>
<span class="mi">3</span> <span class="n">b</span><span class="o">=</span><span class="mi">5</span>
<span class="mi">4</span> <span class="k">print</span> <span class="n">a</span>
<span class="mi">5</span> <span class="k">print</span> <span class="n">b</span>
<span class="o">/</span><span class="n">home</span><span class="o">/</span><span class="n">nnn</span><span class="o">/</span><span class="n">anaconda</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">python2</span><span class="o">.</span><span class="mi">7</span><span class="o">/</span><span class="n">site</span><span class="o">-</span><span class="n">packages</span><span class="o">/</span><span class="n">ipdb</span><span class="o">/</span><span class="fm">__init__</span><span class="o">.</span><span class="n">py</span> <span class="ow">in</span> <span class="o"><</span><span class="n">module</span><span class="o">></span><span class="p">()</span>
<span class="mi">14</span> <span class="c1"># You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/.</span>
<span class="mi">15</span>
<span class="o">---></span> <span class="mi">16</span> <span class="kn">from</span> <span class="nn">ipdb.__main__</span> <span class="kn">import</span> <span class="n">set_trace</span><span class="p">,</span> <span class="n">post_mortem</span><span class="p">,</span> <span class="n">pm</span><span class="p">,</span> <span class="n">run</span><span class="p">,</span> <span class="n">runcall</span><span class="p">,</span> <span class="n">runeval</span><span class="p">,</span> <span class="n">launch_ipdb_on_exception</span>
<span class="mi">17</span>
<span class="mi">18</span> <span class="n">pm</span> <span class="c1"># please pyflakes</span>
<span class="o">/</span><span class="n">home</span><span class="o">/</span><span class="n">nnn</span><span class="o">/</span><span class="n">anaconda</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">python2</span><span class="o">.</span><span class="mi">7</span><span class="o">/</span><span class="n">site</span><span class="o">-</span><span class="n">packages</span><span class="o">/</span><span class="n">ipdb</span><span class="o">/</span><span class="n">__main__</span><span class="o">.</span><span class="n">py</span> <span class="ow">in</span> <span class="o"><</span><span class="n">module</span><span class="o">></span><span class="p">()</span>
<span class="mi">71</span> <span class="c1"># the instance method will create a new one without loading the config.</span>
<span class="mi">72</span> <span class="c1"># i.e: if we are in an embed instance we do not want to load the config.</span>
<span class="o">---></span> <span class="mi">73</span> <span class="n">ipapp</span> <span class="o">=</span> <span class="n">TerminalIPythonApp</span><span class="o">.</span><span class="n">instance</span><span class="p">()</span>
<span class="mi">74</span> <span class="n">shell</span> <span class="o">=</span> <span class="n">get_ipython</span><span class="p">()</span>
<span class="mi">75</span> <span class="n">def_colors</span> <span class="o">=</span> <span class="n">shell</span><span class="o">.</span><span class="n">colors</span>
<span class="o">/</span><span class="n">home</span><span class="o">/</span><span class="n">nnn</span><span class="o">/</span><span class="n">anaconda</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">python2</span><span class="o">.</span><span class="mi">7</span><span class="o">/</span><span class="n">site</span><span class="o">-</span><span class="n">packages</span><span class="o">/</span><span class="n">traitlets</span><span class="o">/</span><span class="n">config</span><span class="o">/</span><span class="n">configurable</span><span class="o">.</span><span class="n">pyc</span> <span class="ow">in</span> <span class="n">instance</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
<span class="mi">413</span> <span class="k">raise</span> <span class="n">MultipleInstanceError</span><span class="p">(</span>
<span class="mi">414</span> <span class="s1">'Multiple incompatible subclass instances of '</span>
<span class="o">--></span> <span class="mi">415</span> <span class="s1">'</span><span class="si">%s</span><span class="s1"> are being created.'</span> <span class="o">%</span> <span class="bp">cls</span><span class="o">.</span><span class="vm">__name__</span>
<span class="mi">416</span> <span class="p">)</span>
<span class="mi">417</span>
<span class="n">MultipleInstanceError</span><span class="p">:</span> <span class="n">Multiple</span> <span class="n">incompatible</span> <span class="n">subclass</span> <span class="n">instances</span> <span class="n">of</span> <span class="n">TerminalIPythonApp</span> <span class="n">are</span> <span class="n">being</span> <span class="n">created</span><span class="o">.</span>
</pre></div>
<p>The solution is to use Tracer instead:</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">IPython.core.debugger</span> <span class="kn">import</span> <span class="n">Tracer</span>
<span class="n">Tracer</span><span class="p">()()</span>
</pre></div>
<p><strong>Source:</strong> <a href="http://stackoverflow.com/questions/35613249/using-ipdb-to-debug-python-code-in-one-cell-jupyter-or-ipython">http://stackoverflow.com/questions/35613249/using-ipdb-to-debug-python-code-in-one-cell-jupyter-or-ipython</a></p>
<a class="readmore" href="https://www.andreagrandi.it/2016/05/10/using-python-ipdb-from-jupyter/">read more</a>
<p><a href="https://www.andreagrandi.it/2016/05/10/using-python-ipdb-from-jupyter/#disqus_thread">comments</a></p> </div><!-- /.entry-content -->
</article></li>
<li><article class="hentry">
<header>
<h1><a href="https://www.andreagrandi.it/2016/04/10/how-to-publish-a-python-package-to-pypi/" rel="bookmark"
title="Permalink to How to publish a Python package to PyPI">How to publish a Python package to PyPI</a></h1>
</header>
<div class="entry-content">
<footer class="post-info">
<span>Sun 10 April 2016</span>
<span>| in <a href="https://www.andreagrandi.it/category/development.html">Development</a></span>
<span>| tags: <a href="https://www.andreagrandi.it/tag/pip.html">pip</a><a href="https://www.andreagrandi.it/tag/pypi.html">pypi</a><a href="https://www.andreagrandi.it/tag/python.html">Python</a></span>
</footer><!-- /.post-info --> <p><strong>PyPI</strong> is the <strong>Python Package Index</strong>, that archive that let you
install a package using pip, for example: <strong>pip install Flask</strong></p>
<p>In the past days I started writing a <strong>Python API client</strong> for
<strong><a href="https://www.toshl.com">Toshl</a></strong> expense manager and I decided to
publish the library on PyPI. You can have a look at my library
here <a href="https://github.com/andreagrandi/toshl-python">https://github.com/andreagrandi/toshl-python</a> (please note: it's
still in development and <a href="https://developer.toshl.com/">Toshl API</a> is
not even public yet) in case you are not sure how to structure it.</p>
<p>I found a <a href="http://peterdowns.com/posts/first-time-with-pypi.html">nice
guide</a> but it
wasn't complete (for example it didn't say how to sign packages) so I
decided to rewrite it adding more information.</p>
<h3>Create PyPI accounts</h3>
<p>To publish packages on PyPI you need to create two accounts: one for the
<a href="http://pypi.python.org/pypi?%3Aaction=register_form">production server</a>
and another one for the <a href="http://testpypi.python.org/pypi?%3Aaction=register_form">test
server</a>. When
you register, please specify (if you have one, but I really hope you do)
the <strong>PGP</strong> id of your public key. Once the accounts are created, you
need to create a file named <strong>.pypirc</strong> in your \$HOME folder containing
the following configuration:</p>
<div class="highlight"><pre><span></span><span class="o">[</span>distutils<span class="o">]</span>
index-servers <span class="o">=</span>
pypi
pypitest
<span class="o">[</span>pypi<span class="o">]</span>
<span class="nv">repository</span><span class="o">=</span>https://pypi.python.org/pypi
<span class="nv">username</span><span class="o">=</span>your_username
<span class="nv">password</span><span class="o">=</span>your_password
<span class="o">[</span>pypitest<span class="o">]</span>
<span class="nv">repository</span><span class="o">=</span>https://testpypi.python.org/pypi
<span class="nv">username</span><span class="o">=</span>your_username
<span class="nv">password</span><span class="o">=</span>your_password
</pre></div>
<p>Please substitute <strong>your_username</strong> and <strong>your_password</strong> with the
details you sent during the registration.</p>
<h3>Preparing the package</h3>
<p>I assume you have structured your library in the proper way and have
included a <strong>setup.py</strong> with all the configuration (it's not something
specific to PyPI so you should have done it already). If you haven't I
remember you can give a look at my library
here <a href="https://github.com/andreagrandi/toshl-python">https://github.com/andreagrandi/toshl-python</a> in particular to the
<strong>setup.py</strong>:</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">setuptools</span> <span class="kn">import</span> <span class="n">setup</span><span class="p">,</span> <span class="n">find_packages</span>
<span class="n">setup</span><span class="p">(</span>
<span class="n">name</span><span class="o">=</span><span class="s1">'toshl'</span><span class="p">,</span>
<span class="n">version</span><span class="o">=</span><span class="s1">'0.0.3'</span><span class="p">,</span>
<span class="n">url</span><span class="o">=</span><span class="s1">'https://github.com/andreagrandi/toshl-python'</span><span class="p">,</span>
<span class="n">download_url</span><span class="o">=</span><span class="s1">'https://github.com/andreagrandi/toshl-python/tarball/0.0.3'</span><span class="p">,</span>
<span class="n">author</span><span class="o">=</span><span class="s1">'Andrea Grandi'</span><span class="p">,</span>
<span class="n">author_email</span><span class="o">=</span><span class="s1">'a.grandi@gmail.com'</span><span class="p">,</span>
<span class="n">description</span><span class="o">=</span><span class="s1">'Python client library for Toshl API.'</span><span class="p">,</span>
<span class="n">packages</span><span class="o">=</span><span class="n">find_packages</span><span class="p">(</span><span class="n">exclude</span><span class="o">=</span><span class="p">[</span><span class="s1">'tests'</span><span class="p">]),</span>
<span class="n">zip_safe</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span>
<span class="n">include_package_data</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span>
<span class="n">platforms</span><span class="o">=</span><span class="s1">'any'</span><span class="p">,</span>
<span class="n">license</span><span class="o">=</span><span class="s1">'MIT'</span><span class="p">,</span>
<span class="n">install_requires</span><span class="o">=</span><span class="p">[</span>
<span class="s1">'requests==2.9.1'</span><span class="p">,</span>
<span class="p">],</span>
<span class="p">)</span>
</pre></div>
<h3>Upload the package to PyPI Test server</h3>
<p>The first time you upload the package you will need to register it:</p>
<div class="highlight"><pre><span></span>python setup.py register -r pypitest
</pre></div>
<p>and then you will need to build the package and upload it (please note
I'm using the <strong>--sign</strong> to sign the package with PGP):</p>
<div class="highlight"><pre><span></span>python setup.py sdist upload --sign -r pypitest
</pre></div>
<h3>Upload the package to PyPI production server</h3>
<p>Once you have verified that you are able to build and upload the package
to the test server (without getting any errors), you should upload it to
the production server:</p>
<div class="highlight"><pre><span></span>python setup.py register -r pypi
python setup.py sdist upload --sign -r pypi
</pre></div>
<p>This is everything you need to do if you want to publish a Python
package on PyPI. Happy coding!</p>
<a class="readmore" href="https://www.andreagrandi.it/2016/04/10/how-to-publish-a-python-package-to-pypi/">read more</a>
<p><a href="https://www.andreagrandi.it/2016/04/10/how-to-publish-a-python-package-to-pypi/#disqus_thread">comments</a></p> </div><!-- /.entry-content -->
</article></li>
</ol><!-- /#posts-list -->
<p class="paginator">
<a href="https://www.andreagrandi.it/index3.html">«</a>
Page 4 / 50
<a href="https://www.andreagrandi.it/index5.html">»</a>
</p>
</section><!-- /#content -->
<section id="extras" class="body">
<div class="social">
<h2>social</h2>
<ul>
<li><a href="https://www.andreagrandi.it/feeds/all.rss.xml" type="application/rss+xml" rel="alternate">rss feed</a></li>
<li><a href="https://twitter.com/andreagrandi">twitter</a></li>
<li><a href="https://github.com/andreagrandi">github</a></li>
</ul>
</div><!-- /.social -->
</section><!-- /#extras -->
<footer id="contentinfo" class="body">
<p>
Powered by <a href="http://getpelican.com/">Pelican</a> and Python -
Source code available on <a href="https://github.com/andreagrandi/andreagrandi.it">GitHub</a>
<a rel="license" href="http://creativecommons.org/licenses/by-nc/4.0/">
<img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by-nc/4.0/80x15.png" />
</a>
</p>
</footer><!-- /#contentinfo -->
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-2140684-3']);
_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_shortname = 'andrea-grandi-it';
(function () {
var s = document.createElement('script'); s.async = true;
s.type = 'text/javascript';
s.src = '//' + disqus_shortname + '.disqus.com/count.js';
(document.getElementsByTagName('HEAD')[0] || document.getElementsByTagName('BODY')[0]).appendChild(s);
}());
</script>
</body>
</html>