-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.html
626 lines (431 loc) · 72.6 KB
/
index.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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 5.4.0">
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png">
<link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png">
<link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png">
<link rel="mask-icon" href="/images/logo.svg" color="#222">
<meta name="google-site-verification" content="JrWeppxFW2sdcNxrm3rl4ZwGnsrAtUWpNZwW_vQ2LUA">
<meta name="msvalidate.01" content="3AB57DED1A94941A58C26BD883D15457">
<link rel="stylesheet" href="/css/main.css">
<link rel="stylesheet" href="/lib/font-awesome/css/all.min.css">
<script id="hexo-configurations">
var NexT = window.NexT || {};
var CONFIG = {"hostname":"phbernardes.github.io","root":"/","scheme":"Gemini","version":"7.8.0","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12,"onmobile":false},"copycode":{"enable":true,"show_result":true,"style":"default"},"back2top":{"enable":true,"sidebar":true,"scrollpercent":false},"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"buttons","active":null,"storage":true,"lazyload":false,"nav":null},"algolia":{"hits":{"per_page":10},"labels":{"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}},"localsearch":{"enable":true,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},"path":"search.xml"};
</script>
<meta property="og:type" content="website">
<meta property="og:title" content="Pedro Bernardes">
<meta property="og:url" content="https://phbernardes.github.io/">
<meta property="og:site_name" content="Pedro Bernardes">
<meta property="og:locale" content="en_US">
<meta property="article:author" content="Pedro Bernardes">
<meta name="twitter:card" content="summary">
<link rel="canonical" href="https://phbernardes.github.io/">
<script id="page-configurations">
// https://hexo.io/docs/variables.html
CONFIG.page = {
sidebar: "",
isHome : true,
isPost : false,
lang : 'en'
};
</script>
<title>Pedro Bernardes</title>
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-209180662-1"></script>
<script>
if (CONFIG.hostname === location.hostname) {
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-209180662-1');
}
</script>
<noscript>
<style>
.use-motion .brand,
.use-motion .menu-item,
.sidebar-inner,
.use-motion .post-block,
.use-motion .pagination,
.use-motion .comments,
.use-motion .post-header,
.use-motion .post-body,
.use-motion .collection-header { opacity: initial; }
.use-motion .site-title,
.use-motion .site-subtitle {
opacity: initial;
top: initial;
}
.use-motion .logo-line-before i { left: initial; }
.use-motion .logo-line-after i { right: initial; }
</style>
</noscript>
<link rel="alternate" href="/atom.xml" title="Pedro Bernardes" type="application/atom+xml">
</head>
<body itemscope itemtype="http://schema.org/WebPage">
<div class="container use-motion">
<div class="headband"></div>
<header class="header" itemscope itemtype="http://schema.org/WPHeader">
<div class="header-inner"><div class="site-brand-container">
<div class="site-nav-toggle">
<div class="toggle" aria-label="Toggle navigation bar">
<span class="toggle-line toggle-line-first"></span>
<span class="toggle-line toggle-line-middle"></span>
<span class="toggle-line toggle-line-last"></span>
</div>
</div>
<div class="site-meta">
<a href="/" class="brand" rel="start">
<span class="logo-line-before"><i></i></span>
<h1 class="site-title">Pedro Bernardes</h1>
<span class="logo-line-after"><i></i></span>
</a>
</div>
<div class="site-nav-right">
<div class="toggle popup-trigger">
<i class="fa fa-search fa-fw fa-lg"></i>
</div>
</div>
</div>
<nav class="site-nav">
<ul id="menu" class="main-menu menu">
<li class="menu-item menu-item-home">
<a href="/" rel="section"><i class="fa fa-home fa-fw"></i>Home</a>
</li>
<li class="menu-item menu-item-tags">
<a href="/tags/" rel="section"><i class="fa fa-tags fa-fw"></i>Tags</a>
</li>
<li class="menu-item menu-item-categories">
<a href="/categories/" rel="section"><i class="fa fa-th fa-fw"></i>Categories</a>
</li>
<li class="menu-item menu-item-archives">
<a href="/archives/" rel="section"><i class="fa fa-archive fa-fw"></i>Archives</a>
</li>
<li class="menu-item menu-item-search">
<a role="button" class="popup-trigger"><i class="fa fa-search fa-fw"></i>Search
</a>
</li>
</ul>
</nav>
<div class="search-pop-overlay">
<div class="popup search-popup">
<div class="search-header">
<span class="search-icon">
<i class="fa fa-search"></i>
</span>
<div class="search-input-container">
<input autocomplete="off" autocapitalize="off"
placeholder="Searching..." spellcheck="false"
type="search" class="search-input">
</div>
<span class="popup-btn-close">
<i class="fa fa-times-circle"></i>
</span>
</div>
<div id="search-result">
<div id="no-result">
<i class="fa fa-spinner fa-pulse fa-5x fa-fw"></i>
</div>
</div>
</div>
</div>
</div>
</header>
<div class="reading-progress-bar"></div>
<main class="main">
<div class="main-inner">
<div class="content-wrap">
<div class="content index posts-expand">
<article itemscope itemtype="http://schema.org/Article" class="post-block" lang="en">
<link itemprop="mainEntityOfPage" href="https://phbernardes.github.io/categories/django-pydantic/django-pydantic-2022-05-15-Creating-a-web-API-with-FastAPI-and-Django/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="image" content="/images/avatar.gif">
<meta itemprop="name" content="Pedro Bernardes">
<meta itemprop="description" content="">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="Pedro Bernardes">
</span>
<header class="post-header">
<h2 class="post-title" itemprop="name headline">
<a href="/categories/django-pydantic/django-pydantic-2022-05-15-Creating-a-web-API-with-FastAPI-and-Django/" class="post-title-link" itemprop="url">Creating a web API with FastAPI and Django</a>
</h2>
<div class="post-meta">
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar"></i>
</span>
<span class="post-meta-item-text">Posted on</span>
<time title="Created: 2022-05-15 19:49:48 / Modified: 21:49:19" itemprop="dateCreated datePublished" datetime="2022-05-15T19:49:48+02:00">2022-05-15</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-folder"></i>
</span>
<span class="post-meta-item-text">In</span>
<span itemprop="about" itemscope itemtype="http://schema.org/Thing">
<a href="/categories/django-pydantic/" itemprop="url" rel="index"><span itemprop="name">django-pydantic</span></a>
</span>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-comment"></i>
</span>
<span class="post-meta-item-text">Disqus: </span>
<a title="disqus" href="/categories/django-pydantic/django-pydantic-2022-05-15-Creating-a-web-API-with-FastAPI-and-Django/#disqus_thread" itemprop="discussionUrl">
<span class="post-comments-count disqus-comment-count" data-disqus-identifier="categories/django-pydantic/django-pydantic-2022-05-15-Creating-a-web-API-with-FastAPI-and-Django/" itemprop="commentCount"></span>
</a>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<h1 id="FastAPI"><a href="#FastAPI" class="headerlink" title="FastAPI"></a>FastAPI</h1><p>FastAPI is based in Starlette and Pydantic.</p>
<p>Starlette is a lightweight ASGI framework, it has a <a target="_blank" rel="noopener" href="https://www.techempower.com/benchmarks/#section=data-r20&hw=ph&test=fortune&l=zijzen-sf">impressive performance</a>, supports async and its simplicity allow us to easily write scalable web systems.</p>
<p>Pydantic provides data validation and serialization using python type annotations, it enforces type hints at runtime, provides user friendly errors when data is invalid, is fast (it <a target="_blank" rel="noopener" href="https://pydantic-docs.helpmanual.io/benchmarks/">claims to be 12x faster than DRF</a>).</p>
<p>The union of Starlette and Pydantic added to automatic OpenAPI schemas generation and swagger gives us a great toolset to quickly develop a (Fast) API. Type annotations enforcement results in an amazing developer experience.</p>
<p>FastAPI is database agnostic and easily integrable with any Python ORM (here enters Django ORM in this example).</p>
<h1 id="Django"><a href="#Django" class="headerlink" title="Django"></a>Django</h1><h2 id="Advantages"><a href="#Advantages" class="headerlink" title="Advantages"></a>Advantages</h2><p>Django provides a great toolbox to quickly develop a web application, as from their logo: <code>"The web framework for perfectionists with deadlines"</code>.</p>
<p>Django’s ORM is simple, allow us to easily write clean code, is powerful to translate different types of queries without use of raw SQL and has a great migration control system.</p>
<p>Django’s admin is great tool to manage data with minimum development effort.</p>
<h2 id="Disadvantages"><a href="#Disadvantages" class="headerlink" title="Disadvantages"></a>Disadvantages</h2><p>Django Rest Framework (DRF) serializers are way slower than Pydantic validators/serializer. </p>
<p>Django is built based in metaclasses, this results in missing type hints in several objects you manipulate.</p>
<p>Django ORM is not able to operate safely in an async environment. But discussions about supporting async are active and hopefully Psycopg3 implementation will enable this support. Use of <a target="_blank" rel="noopener" href="https://docs.djangoproject.com/en/4.0/topics/async/#async-views">asgiref library</a> solves this problem until this is implemented.</p>
<h1 id="Uniting-forces"><a href="#Uniting-forces" class="headerlink" title="Uniting forces"></a>Uniting forces</h1><p>Starlette will provide a light weight ASGI framework to server our API endpoints.</p>
<p>Pydantic will provide a toolset to validate and serialize our payloads, while enforcing type hints in our code base.</p>
<p>FastAPI’s OpenAPI support will provide a toolset to generate a nice API documentation.</p>
<p>Django will provide a powerful ORM, migration control system and the admin page to manage our data.</p>
<p>Djantic will be the bridge between Pydantic schemas and Django models.</p>
<h1 id="Requirements"><a href="#Requirements" class="headerlink" title="Requirements"></a>Requirements</h1><p>This tutorial expects you to have some familiarity of:</p>
<ul>
<li>Python</li>
<li>Django (part 1 and 2 of the <a target="_blank" rel="noopener" href="https://docs.djangoproject.com/en/4.0/intro/tutorial01/">official tutorial</a> are enough)</li>
<li>FastAPI (<a target="_blank" rel="noopener" href="https://fastapi.tiangolo.com/tutorial/first-steps/">official first steps</a> guide are enough)</li>
</ul>
<h1 id="Packages-installation"><a href="#Packages-installation" class="headerlink" title="Packages installation"></a>Packages installation</h1><p>Install the following dependencies. Fell free to use your favorite package/environment manager instead of pip.</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip install fastapi uvicorn django djantic django-extensions djangorestframework-simplejwt python-multipart</span><br></pre></td></tr></table></figure>
<h1 id="Django-project"><a href="#Django-project" class="headerlink" title="Django project"></a>Django project</h1><p>Start a new Django project. We could start as well from a cookiecutter template for FastAPI and add Django models and admin endpoint.</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">django-admin startproject fastdjango .</span><br></pre></td></tr></table></figure>
<p>Here is our directory tree:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">├── fastdjango</span><br><span class="line">│ ├── __init__.py</span><br><span class="line">│ ├── asgi.py</span><br><span class="line">│ ├── settings.py</span><br><span class="line">│ ├── urls.py</span><br><span class="line">│ └── wsgi.py</span><br><span class="line">└── manage.py</span><br></pre></td></tr></table></figure>
<p>We are able to run the Django project:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">uvicorn fastdjango.asgi:application --reload</span><br></pre></td></tr></table></figure>
<h1 id="Defining-the-ASGI-module-Django-FastAPI"><a href="#Defining-the-ASGI-module-Django-FastAPI" class="headerlink" title="Defining the ASGI module (Django + FastAPI)"></a>Defining the ASGI module (Django + FastAPI)</h1><p>Refactoring the ASGI module to integrate FastAPI application.</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mkdir fastdjango/asgi; mv fastdjango/asgi.py fastdjango/asgi/django.py; touch fastdjango/asgi/__init__.py fastdjango/asgi/fastapi.py</span><br></pre></td></tr></table></figure>
<p>Directory tree:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">├── fastdjango</span><br><span class="line">│ ├── __init__.py</span><br><span class="line">│ ├── asgi</span><br><span class="line">│ │ ├── __init__.py</span><br><span class="line">│ │ ├── django.py</span><br><span class="line">│ │ └── fastapi.py</span><br><span class="line">│ ├── settings.py</span><br><span class="line">│ ├── urls.py</span><br><span class="line">│ └── wsgi.py</span><br><span class="line">└── manage.py</span><br></pre></td></tr></table></figure>
<p>Editing the new ASGI files:</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># fastdjango/asgi/fastapi.py </span></span><br><span class="line"><span class="keyword">from</span> fastapi <span class="keyword">import</span> FastAPI</span><br><span class="line"></span><br><span class="line">app = FastAPI()</span><br></pre></td></tr></table></figure>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># fastdjango/asgi/__init__.py </span></span><br><span class="line"><span class="keyword">from</span> fastapi.staticfiles <span class="keyword">import</span> StaticFiles</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> .django <span class="keyword">import</span> application</span><br><span class="line"><span class="keyword">from</span> .fastapi <span class="keyword">import</span> app</span><br><span class="line"></span><br><span class="line">__all__ = [<span class="string">"app"</span>]</span><br><span class="line"></span><br><span class="line">app.mount(<span class="string">"/django/static"</span>, StaticFiles(directory=<span class="string">"static"</span>), name=<span class="string">"static"</span>)</span><br><span class="line">app.mount(<span class="string">"/django"</span>, application)</span><br></pre></td></tr></table></figure>
<p>Add this to settings files:</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># fastdjango/settings.py</span></span><br><span class="line">...</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> os</span><br><span class="line">STATIC_URL = <span class="string">'static/'</span></span><br><span class="line">STATIC_ROOT = os.path.join(BASE_DIR, <span class="string">"static"</span>)</span><br></pre></td></tr></table></figure>
<p>And then run collectstatic to generate Django’s static files and migrate to create our database:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./manage.py collectstatic; ./manage.py migrate</span><br></pre></td></tr></table></figure>
<p>Running our application now will serve both Django and FastAPI:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">uvicorn fastdjango.asgi:app --reload</span><br></pre></td></tr></table></figure>
<p>FastAPI docs (no endpoint for now):</p>
<ul>
<li><a target="_blank" rel="noopener" href="http://127.0.0.1:8000/docs">http://127.0.0.1:8000/docs</a></li>
</ul>
<p>Django Admin and Django (no endpoint neither):</p>
<ul>
<li><a target="_blank" rel="noopener" href="http://127.0.0.1:8000/django/admin/">http://127.0.0.1:8000/django/admin/</a></li>
<li><a target="_blank" rel="noopener" href="http://127.0.0.1:8000/django">http://127.0.0.1:8000/django</a></li>
</ul>
<h1 id="User-stories"><a href="#User-stories" class="headerlink" title="User stories"></a>User stories</h1><p>For this example we will create an API for a forum where people can create and read posts.</p>
<ul>
<li>As a anonymous user I want to be able to create a new User account.</li>
<li>As a anonymous user I want to be able to login.</li>
<li>As an authentified User I want to list all the posts.</li>
<li>As an authentified User I want to post a new post.</li>
<li>As an authentified User I want to list my posts.</li>
</ul>
<h1 id="Models"><a href="#Models" class="headerlink" title="Models"></a>Models</h1><h2 id="Users"><a href="#Users" class="headerlink" title="Users"></a>Users</h2><p>Start the users app:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./manage.py startapp users</span><br></pre></td></tr></table></figure>
<p>File tree:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">├── fastdjango</span><br><span class="line">├── manage.py</span><br><span class="line">└── users</span><br><span class="line"> ├── __init__.py</span><br><span class="line"> ├── admin.py</span><br><span class="line"> ├── apps.py</span><br><span class="line"> ├── migrations</span><br><span class="line"> │ └── __init__.py</span><br><span class="line"> ├── models.py</span><br><span class="line"> ├── tests.py</span><br><span class="line"> └── views.py</span><br></pre></td></tr></table></figure>
<p>Settings:</p>
<p>Add users.apps.UsersConfig to INSTALLED_APPS:</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># fastdjango/settings.py</span></span><br><span class="line">...</span><br><span class="line">INSTALLED_APPS = [</span><br><span class="line"> <span class="string">'django.contrib.admin'</span>,</span><br><span class="line"> <span class="string">'django.contrib.auth'</span>,</span><br><span class="line"> <span class="string">'django.contrib.contenttypes'</span>,</span><br><span class="line"> <span class="string">'django.contrib.sessions'</span>,</span><br><span class="line"> <span class="string">'django.contrib.messages'</span>,</span><br><span class="line"> <span class="string">'django.contrib.staticfiles'</span>,</span><br><span class="line"> <span class="string">'users.apps.UsersConfig'</span>,</span><br><span class="line">]</span><br><span class="line">...</span><br></pre></td></tr></table></figure>
<p>User model:</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># users/models.py</span></span><br><span class="line"><span class="keyword">import</span> uuid</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> django.contrib.auth.models <span class="keyword">import</span> AbstractUser</span><br><span class="line"><span class="keyword">from</span> django.db <span class="keyword">import</span> models</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">User</span>(<span class="params">AbstractUser</span>):</span></span><br><span class="line"> pubkey = models.UUIDField(</span><br><span class="line"> default=uuid.uuid4, db_index=<span class="literal">True</span>, editable=<span class="literal">False</span>, unique=<span class="literal">True</span></span><br><span class="line"> )</span><br><span class="line"></span><br><span class="line"> EMAIL_FIELD = <span class="string">"username"</span></span><br><span class="line"> USERNAME_FIELD = <span class="string">"username"</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">__str__</span>(<span class="params">self</span>):</span></span><br><span class="line"> <span class="keyword">return</span> <span class="string">f"<span class="subst">{self.username}</span> - <span class="subst">{self.first_name}</span> <span class="subst">{self.last_name}</span>"</span></span><br></pre></td></tr></table></figure>
<p>Settings:</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># fastdjango/settings.py</span></span><br><span class="line">USER_MODEL = <span class="string">"users.User"</span></span><br><span class="line">AUTH_USER_MODEL = <span class="string">"users.User"</span></span><br></pre></td></tr></table></figure>
<h2 id="Forum"><a href="#Forum" class="headerlink" title="Forum"></a>Forum</h2><p>Start the forum app:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./manage.py startapp forum</span><br></pre></td></tr></table></figure>
<p>Settings:</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># fastdjango/settings.py</span></span><br><span class="line">INSTALLED_APPS = [</span><br><span class="line"> <span class="string">"django.contrib.admin"</span>,</span><br><span class="line"> <span class="string">"django.contrib.auth"</span>,</span><br><span class="line"> <span class="string">"django.contrib.contenttypes"</span>,</span><br><span class="line"> <span class="string">"django.contrib.sessions"</span>,</span><br><span class="line"> <span class="string">"django.contrib.messages"</span>,</span><br><span class="line"> <span class="string">"django.contrib.staticfiles"</span>,</span><br><span class="line"> <span class="string">"users.apps.UsersConfig"</span>,</span><br><span class="line"> <span class="string">"forum.apps.ForumConfig"</span>,</span><br><span class="line">]</span><br></pre></td></tr></table></figure>
<p>File tree:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">.</span><br><span class="line">├── fastdjango</span><br><span class="line">├── forum</span><br><span class="line">│ ├── __init__.py</span><br><span class="line">│ ├── admin.py</span><br><span class="line">│ ├── apps.py</span><br><span class="line">│ ├── migrations</span><br><span class="line">│ │ └── __init__.py</span><br><span class="line">│ ├── models.py</span><br><span class="line">│ ├── tests.py</span><br><span class="line">│ └── views.py</span><br><span class="line">├── manage.py</span><br><span class="line">└── users</span><br></pre></td></tr></table></figure>
<p>Forum models:</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># forum/models.py</span></span><br><span class="line"><span class="keyword">import</span> uuid</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> django.conf <span class="keyword">import</span> settings</span><br><span class="line"><span class="keyword">from</span> django.db <span class="keyword">import</span> models</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Post</span>(<span class="params">models.Model</span>):</span></span><br><span class="line"> pubkey = models.UUIDField(</span><br><span class="line"> default=uuid.uuid4, db_index=<span class="literal">True</span>, editable=<span class="literal">False</span>, unique=<span class="literal">True</span></span><br><span class="line"> )</span><br><span class="line"> title = models.CharField(max_length=<span class="number">100</span>)</span><br><span class="line"> content = models.TextField()</span><br><span class="line"> created_at = models.DateTimeField(auto_now_add=<span class="literal">True</span>)</span><br><span class="line"> updated_at = models.DateTimeField(auto_now=<span class="literal">True</span>)</span><br><span class="line"> author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">__str__</span>(<span class="params">self</span>):</span></span><br><span class="line"> <span class="keyword">return</span> self.title</span><br><span class="line"></span><br><span class="line"> <span class="class"><span class="keyword">class</span> <span class="title">Meta</span>:</span></span><br><span class="line"> ordering = [<span class="string">"-created_at"</span>]</span><br><span class="line"> get_latest_by = <span class="string">"-created_at"</span></span><br></pre></td></tr></table></figure>
<p>Migrate:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./manage.py makemigrations; ./manage.py migrate</span><br></pre></td></tr></table></figure>
<h1 id="FastAPI-endpoints"><a href="#FastAPI-endpoints" class="headerlink" title="FastAPI endpoints"></a>FastAPI endpoints</h1><p>We will now create the FastAPI endpoints.</p>
<h2 id="User-registration"><a href="#User-registration" class="headerlink" title="User registration"></a>User registration</h2><p>There will be endpoints for registration and authentication:</p>
<ul>
<li>/register</li>
<li>/login</li>
</ul>
<h3 id="Schemas"><a href="#Schemas" class="headerlink" title="Schemas:"></a>Schemas:</h3><p>We will use Djantic to automatically generate the Pydantic schemas basing in our User model.<br>We will also define the JWTPairSchema to serialize the JWT in the /login endpoint. Let’s keep it simple and not define the JWT refresh endpoint for now.</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># users/schemas.py</span></span><br><span class="line"><span class="keyword">from</span> djantic <span class="keyword">import</span> ModelSchema</span><br><span class="line"><span class="keyword">from</span> pydantic <span class="keyword">import</span> BaseModel</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> users.models <span class="keyword">import</span> User</span><br><span class="line"></span><br><span class="line">__all__ = [<span class="string">"JWTPairSchema"</span>]</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">JWTPairSchema</span>(<span class="params">BaseModel</span>):</span></span><br><span class="line"> refresh_token: <span class="built_in">str</span></span><br><span class="line"> access_token: <span class="built_in">str</span></span><br><span class="line"> token_type: <span class="built_in">str</span> = <span class="string">"bearer"</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">CreateUserSchema</span>(<span class="params">ModelSchema</span>):</span></span><br><span class="line"> <span class="class"><span class="keyword">class</span> <span class="title">Config</span>:</span></span><br><span class="line"> model = User</span><br><span class="line"> include = (</span><br><span class="line"> <span class="string">"username"</span>,</span><br><span class="line"> <span class="string">"password"</span>,</span><br><span class="line"> <span class="string">"first_name"</span>,</span><br><span class="line"> <span class="string">"last_name"</span>,</span><br><span class="line"> )</span><br></pre></td></tr></table></figure>
<h3 id="Views"><a href="#Views" class="headerlink" title="Views:"></a>Views:</h3><p>We will use Django’s authentication system and DRF Simple JWT for the authentication.</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">```python</span><br><span class="line"># users/views.py</span><br><span class="line">from django.contrib.auth import authenticate</span><br><span class="line">from django.db import IntegrityError</span><br><span class="line">from fastapi import APIRouter, Depends, HTTPException</span><br><span class="line">from fastapi.security import OAuth2PasswordRequestForm</span><br><span class="line">from rest_framework_simplejwt.tokens import RefreshToken</span><br><span class="line"></span><br><span class="line">from .models import User</span><br><span class="line">from .schemas import CreateUserSchema, JWTPairSchema</span><br><span class="line"></span><br><span class="line">router = APIRouter()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">def get_jwt(user: User) -> JWTPairSchema:</span><br><span class="line"> refresh = RefreshToken.for_user(user)</span><br><span class="line"> return JWTPairSchema(</span><br><span class="line"> refresh_token=str(refresh),</span><br><span class="line"> access_token=str(refresh.access_token),</span><br><span class="line"> )</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">@router.post("/register", response_model=JWTPairSchema)</span><br><span class="line">def register_user(new_user: CreateUserSchema):</span><br><span class="line"> try:</span><br><span class="line"> user = User.objects.create_user(**new_user.dict())</span><br><span class="line"> return get_jwt(user)</span><br><span class="line"> except IntegrityError:</span><br><span class="line"> raise HTTPException(detail="Cannot create user.", status_code=400)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">@router.post("/login", response_model=JWTPairSchema)</span><br><span class="line">def login(form_data: OAuth2PasswordRequestForm = Depends()):</span><br><span class="line"> user = authenticate(username=form_data.username, password=form_data.password)</span><br><span class="line"> if user is not None:</span><br><span class="line"> return get_jwt(user)</span><br><span class="line"> raise HTTPException(status_code=400, detail="Incorrect username or password.")</span><br></pre></td></tr></table></figure>
<h3 id="ASGI"><a href="#ASGI" class="headerlink" title="ASGI:"></a>ASGI:</h3><p>Include the user views in the ASGI application:</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># fastdjango/asgi/fastapi.py</span></span><br><span class="line"><span class="keyword">from</span> fastapi <span class="keyword">import</span> FastAPI</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> users.views <span class="keyword">import</span> router <span class="keyword">as</span> users_router</span><br><span class="line"></span><br><span class="line">app = FastAPI()</span><br><span class="line"></span><br><span class="line">app.include_router(users_router, tags=[<span class="string">"auth"</span>], prefix=<span class="string">""</span>)</span><br></pre></td></tr></table></figure>
<p>Now you can run the server:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">uvicorn fastdjango.asgi:app --reload</span><br></pre></td></tr></table></figure>
<p>And access the swagger at <a target="_blank" rel="noopener" href="http://127.0.0.1:8000/docs">http://127.0.0.1:8000/docs</a>. You can register an account and then login with the same credentials.</p>
<h2 id="User-authentification"><a href="#User-authentification" class="headerlink" title="User authentification"></a>User authentification</h2><p>We will now create an util to authenticate the user:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mkdir -p utils/auth; touch utils/__init__.py utils/auth/__init__.py utils/auth/user.py</span><br></pre></td></tr></table></figure>
<p>We will use FastAPI OAuth2 package and DRF Simple JWT for the authentication:</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># utils/auth/user.py</span></span><br><span class="line"><span class="keyword">from</span> fastapi <span class="keyword">import</span> Depends, HTTPException, status</span><br><span class="line"><span class="keyword">from</span> fastapi.security <span class="keyword">import</span> OAuth2PasswordBearer</span><br><span class="line"><span class="keyword">from</span> rest_framework_simplejwt.authentication <span class="keyword">import</span> JWTAuthentication</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> users.models <span class="keyword">import</span> User</span><br><span class="line"></span><br><span class="line">__all__ = [<span class="string">"UserAuthentified"</span>]</span><br><span class="line"></span><br><span class="line">oauth2_scheme = OAuth2PasswordBearer(tokenUrl=<span class="string">"/login"</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">get_authentified_user</span>(<span class="params">token: <span class="built_in">str</span> = Depends(<span class="params">oauth2_scheme</span>)</span>) -> User:</span></span><br><span class="line"> jwt_authenticator = JWTAuthentication()</span><br><span class="line"> validated_token = jwt_authenticator.get_validated_token(token)</span><br><span class="line"></span><br><span class="line"> user = jwt_authenticator.get_user(validated_token)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">not</span> user:</span><br><span class="line"> <span class="keyword">raise</span> HTTPException(</span><br><span class="line"> status_code=status.HTTP_401_UNAUTHORIZED,</span><br><span class="line"> detail=<span class="string">"Invalid authentication credentials"</span>,</span><br><span class="line"> headers={<span class="string">"WWW-Authenticate"</span>: <span class="string">"Bearer"</span>},</span><br><span class="line"> )</span><br><span class="line"> <span class="keyword">return</span> user</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">UserAuthentified: User = Depends(get_authentified_user)</span><br></pre></td></tr></table></figure>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># utils/auth/__init__.py</span></span><br><span class="line"><span class="keyword">from</span> .user <span class="keyword">import</span> UserAuthentified</span><br><span class="line"></span><br><span class="line">__all__ = [<span class="string">"UserAuthentified"</span>]</span><br></pre></td></tr></table></figure>
<p>Now we can use UserAuthentified to ensure the user is authenticated and get the user instance in our views.</p>
<h2 id="Pagination"><a href="#Pagination" class="headerlink" title="Pagination"></a>Pagination</h2><p>We will also define the pagination util using Django paginator so we can limit the number of items in the response.</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mkdir -p utils/page; touch utils/page/__init__.py utils/page/paginator.py utils/page/schemas.py</span><br></pre></td></tr></table></figure>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># utils/page/schemas.py</span></span><br><span class="line"><span class="keyword">from</span> typing <span class="keyword">import</span> <span class="type">Generic</span>, <span class="type">Optional</span>, TypeVar</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> pydantic <span class="keyword">import</span> BaseModel, conint</span><br><span class="line"><span class="keyword">from</span> pydantic.generics <span class="keyword">import</span> GenericModel</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> django.conf <span class="keyword">import</span> settings</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Link</span>(<span class="params">BaseModel</span>):</span></span><br><span class="line"> first: conint(ge=<span class="number">1</span>)</span><br><span class="line"> last: conint(ge=<span class="number">1</span>)</span><br><span class="line"> current: conint(ge=<span class="number">1</span>)</span><br><span class="line"> <span class="built_in">next</span>: conint(ge=<span class="number">1</span>) | <span class="literal">None</span></span><br><span class="line"> prev: conint(ge=<span class="number">1</span>) | <span class="literal">None</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">DataT = TypeVar(<span class="string">"DataT"</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">PageResponse</span>(<span class="params">GenericModel, <span class="type">Generic</span>[DataT]</span>):</span></span><br><span class="line"> data: <span class="type">Optional</span>[<span class="built_in">list</span>[DataT]]</span><br><span class="line"> pages: Link</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">PageRequest</span>(<span class="params">BaseModel</span>):</span></span><br><span class="line"> page: conint(ge=<span class="number">1</span>) = <span class="number">1</span></span><br><span class="line"> size: conint(ge=<span class="number">1</span>, le=settings.PAGINATION[<span class="string">"max_size"</span>]) = settings.PAGINATION[</span><br><span class="line"> <span class="string">"default_size"</span></span><br><span class="line"> ]</span><br></pre></td></tr></table></figure>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># utils/page/paginator.py</span></span><br><span class="line"><span class="keyword">from</span> typing <span class="keyword">import</span> <span class="type">Any</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> django.core.paginator <span class="keyword">import</span> EmptyPage, Paginator</span><br><span class="line"><span class="keyword">from</span> django.db.models <span class="keyword">import</span> QuerySet</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> .schemas <span class="keyword">import</span> Link, PageRequest, PageResponse</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">next_page_number</span>(<span class="params">page</span>):</span></span><br><span class="line"> <span class="keyword">try</span>:</span><br><span class="line"> <span class="keyword">return</span> page.next_page_number()</span><br><span class="line"> <span class="keyword">except</span> EmptyPage:</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">None</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">previous_page_number</span>(<span class="params">page</span>):</span></span><br><span class="line"> <span class="keyword">try</span>:</span><br><span class="line"> <span class="keyword">return</span> page.previous_page_number()</span><br><span class="line"> <span class="keyword">except</span> EmptyPage:</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">None</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">paginate</span>(<span class="params">page_request: PageRequest, data: QuerySet | <span class="built_in">list</span></span>) -> <span class="built_in">dict</span>[<span class="built_in">str</span>, <span class="type">Any</span>]:</span></span><br><span class="line"> paginator = Paginator(data, page_request.size)</span><br><span class="line"> page = paginator.get_page(page_request.page)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> PageResponse(</span><br><span class="line"> data=<span class="built_in">list</span>(page.object_list),</span><br><span class="line"> pages=Link(</span><br><span class="line"> current=page.number,</span><br><span class="line"> first=<span class="number">1</span>,</span><br><span class="line"> last=paginator.num_pages,</span><br><span class="line"> <span class="built_in">next</span>=next_page_number(page),</span><br><span class="line"> prev=previous_page_number(page),</span><br><span class="line"> ),</span><br><span class="line"> )</span><br></pre></td></tr></table></figure>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># utils/page/__init__.py</span></span><br><span class="line"><span class="keyword">from</span> .paginator <span class="keyword">import</span> paginate</span><br><span class="line"><span class="keyword">from</span> .schemas <span class="keyword">import</span> PageRequest, PageResponse</span><br><span class="line"></span><br><span class="line">__all__ = [<span class="string">"paginate"</span>, <span class="string">"PageRequest"</span>, <span class="string">"PageResponse"</span>]</span><br></pre></td></tr></table></figure>
<h2 id="Forum-1"><a href="#Forum-1" class="headerlink" title="Forum"></a>Forum</h2><h3 id="Schemas-1"><a href="#Schemas-1" class="headerlink" title="Schemas:"></a>Schemas:</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># forum/schemas.py</span></span><br><span class="line"><span class="keyword">from</span> djantic <span class="keyword">import</span> ModelSchema</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> .models <span class="keyword">import</span> Post</span><br><span class="line"></span><br><span class="line">__all__ = [<span class="string">"CreatePostSchema"</span>, <span class="string">"PostSchema"</span>]</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">PostSchema</span>(<span class="params">ModelSchema</span>):</span></span><br><span class="line"> <span class="class"><span class="keyword">class</span> <span class="title">Config</span>:</span></span><br><span class="line"> model = Post</span><br><span class="line"> exclude = (<span class="string">"id"</span>,)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">CreatePostSchema</span>(<span class="params">ModelSchema</span>):</span></span><br><span class="line"> <span class="class"><span class="keyword">class</span> <span class="title">Config</span>:</span></span><br><span class="line"> model = Post</span><br><span class="line"> include = (</span><br><span class="line"> <span class="string">"title"</span>,</span><br><span class="line"> <span class="string">"content"</span>,</span><br><span class="line"> )</span><br></pre></td></tr></table></figure>
<h3 id="Views-1"><a href="#Views-1" class="headerlink" title="Views:"></a>Views:</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># forum/views.py</span></span><br><span class="line"><span class="keyword">from</span> fastapi <span class="keyword">import</span> APIRouter</span><br><span class="line"><span class="keyword">from</span> fastapi <span class="keyword">import</span> APIRouter, Depends</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> users.models <span class="keyword">import</span> User</span><br><span class="line"><span class="keyword">from</span> utils.auth <span class="keyword">import</span> UserAuthentified</span><br><span class="line"><span class="keyword">from</span> utils.page <span class="keyword">import</span> PageRequest, PageResponse, paginate</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> .models <span class="keyword">import</span> Post</span><br><span class="line"><span class="keyword">from</span> .schemas <span class="keyword">import</span> CreatePostSchema, PostSchema</span><br><span class="line"></span><br><span class="line">router = APIRouter()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@router.post(<span class="params"><span class="string">"/posts"</span>, response_model=PostSchema</span>)</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">list_user_posts</span>(<span class="params">post: CreatePostSchema, user: User = UserAuthentified</span>):</span></span><br><span class="line"> posts = Post.objects.create(author=user, **post.<span class="built_in">dict</span>())</span><br><span class="line"> <span class="keyword">return</span> PostSchema.from_django(posts)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@router.get(<span class="params"><span class="string">"/posts/mine"</span>, response_model=PageResponse[PostSchema]</span>)</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">list_user_posts</span>(<span class="params">page: PageRequest = Depends(<span class="params"></span>), user: User = UserAuthentified</span>):</span></span><br><span class="line"> posts = Post.objects.<span class="built_in">filter</span>(author=user)</span><br><span class="line"> <span class="keyword">return</span> paginate(page, posts)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@router.get(<span class="params"><span class="string">"/posts"</span>, response_model=PageResponse[PostSchema]</span>)</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">list_posts</span>(<span class="params">page: PageRequest = Depends(<span class="params"></span>), user: User = UserAuthentified</span>):</span></span><br><span class="line"> posts = Post.objects.<span class="built_in">all</span>()</span><br><span class="line"> <span class="keyword">return</span> paginate(page, posts)</span><br></pre></td></tr></table></figure>
<h3 id="ASGI-1"><a href="#ASGI-1" class="headerlink" title="ASGI:"></a>ASGI:</h3><p>Add forum router to FastAPI ASGI:</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># fastdjango/asgi/fastapi.py</span></span><br><span class="line"><span class="keyword">from</span> fastapi <span class="keyword">import</span> FastAPI</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> forum.views <span class="keyword">import</span> router <span class="keyword">as</span> forum_router</span><br><span class="line"><span class="keyword">from</span> users.views <span class="keyword">import</span> router <span class="keyword">as</span> users_router</span><br><span class="line"></span><br><span class="line">app = FastAPI()</span><br><span class="line"></span><br><span class="line">app.include_router(users_router, tags=[<span class="string">"auth"</span>], prefix=<span class="string">""</span>)</span><br><span class="line">app.include_router(forum_router, tags=[<span class="string">"forum"</span>], prefix=<span class="string">"/forum"</span>)</span><br></pre></td></tr></table></figure>
<h2 id="Django-Admin"><a href="#Django-Admin" class="headerlink" title="Django Admin"></a>Django Admin</h2><p>We can register our models to Django’s admin:</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># users/admin.py</span></span><br><span class="line"><span class="keyword">from</span> django.contrib <span class="keyword">import</span> admin</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> .models <span class="keyword">import</span> User</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@admin.register(<span class="params">User</span>)</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">UserAdmin</span>(<span class="params">admin.ModelAdmin</span>):</span></span><br><span class="line"> <span class="keyword">pass</span></span><br></pre></td></tr></table></figure>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># forum/admin.py</span></span><br><span class="line"><span class="keyword">from</span> django.contrib <span class="keyword">import</span> admin</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> .models <span class="keyword">import</span> Post</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@admin.register(<span class="params">Post</span>)</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">PostAdmin</span>(<span class="params">admin.ModelAdmin</span>):</span></span><br><span class="line"> <span class="keyword">pass</span></span><br></pre></td></tr></table></figure>
<p>Now you can go to <a target="_blank" rel="noopener" href="http://127.0.0.1:8000/django/admin">http://127.0.0.1:8000/django/admin</a> to manage users and forum posts.</p>
<h1 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h1><p>We developed an API and you can access the swagger (<a target="_blank" rel="noopener" href="http://127.0.0.1:8000/docs">http://127.0.0.1:8000/docs</a>) and Django admin (<a target="_blank" rel="noopener" href="http://127.0.0.1:8000/django/admin">http://127.0.0.1:8000/django/admin</a>).</p>
<p>FastAPI provides better performance than Django REST Framework, enforces type hints in the code and has minimal boilerplate code. Django ORM is a great choice for abstracting database operations and managing database migrations.</p>
<h2 id="Async"><a href="#Async" class="headerlink" title="Async"></a>Async</h2><p>We didn’t cover the use of async in this tutorial, Django ORM doesn’t safely support async yet. But we can <a target="_blank" rel="noopener" href="https://docs.djangoproject.com/en/4.0/topics/async/#async-views">use the asgiref library</a> to await Django DB transactions until support for async is released. FastAPI is fully compatible with async views.</p>
<h2 id="Out-of-the-shell-solutions"><a href="#Out-of-the-shell-solutions" class="headerlink" title="Out of the shell solutions"></a>Out of the shell solutions</h2><p>Instead of combining FastAPI and Django in a project like in this example, there are other options like <a target="_blank" rel="noopener" href="https://github.com/vitalik/django-ninja">Django Ninja</a> that has almost 3k stars in Github (as of May 2022). But I recomend directly using FastAPI because most of the <a target="_blank" rel="noopener" href="https://github.com/vitalik/django-ninja/blob/master/ninja/main.py">code for the ASGI framework of Django Ninja</a> is an adaptation of <a target="_blank" rel="noopener" href="https://github.com/tiangolo/fastapi/blob/master/fastapi/applications.py">FastAPI’s code</a>. FastAPI is wider adapted and will probably provide more comunity support and maintenability.</p>
<h2 id="Source-code"><a href="#Source-code" class="headerlink" title="Source code"></a>Source code</h2><p>You can find the source code of this project at:<br><a target="_blank" rel="noopener" href="https://github.com/phbernardes/fastapi-django">https://github.com/phbernardes/fastapi-django</a></p>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</article>
</div>
<script>
window.addEventListener('tabs:register', () => {
let { activeClass } = CONFIG.comments;
if (CONFIG.comments.storage) {
activeClass = localStorage.getItem('comments_active') || activeClass;
}
if (activeClass) {
let activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
if (activeTab) {
activeTab.click();
}
}
});
if (CONFIG.comments.storage) {
window.addEventListener('tabs:click', event => {
if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
let commentClass = event.target.classList[1];
localStorage.setItem('comments_active', commentClass);
});
}
</script>
</div>
<div class="toggle sidebar-toggle">
<span class="toggle-line toggle-line-first"></span>
<span class="toggle-line toggle-line-middle"></span>
<span class="toggle-line toggle-line-last"></span>
</div>
<aside class="sidebar">
<div class="sidebar-inner">
<ul class="sidebar-nav motion-element">
<li class="sidebar-nav-toc">
Table of Contents
</li>
<li class="sidebar-nav-overview">
Overview
</li>
</ul>
<!--noindex-->
<div class="post-toc-wrap sidebar-panel">
</div>
<!--/noindex-->
<div class="site-overview-wrap sidebar-panel">
<div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
<p class="site-author-name" itemprop="name">Pedro Bernardes</p>
<div class="site-description" itemprop="description"></div>
</div>
<div class="site-state-wrap motion-element">
<nav class="site-state">
<div class="site-state-item site-state-posts">
<a href="/archives/">
<span class="site-state-item-count">1</span>
<span class="site-state-item-name">posts</span>
</a>
</div>
<div class="site-state-item site-state-categories">
<a href="/categories/">
<span class="site-state-item-count">1</span>
<span class="site-state-item-name">categories</span></a>
</div>
<div class="site-state-item site-state-tags">
<a href="/tags/">
<span class="site-state-item-count">5</span>
<span class="site-state-item-name">tags</span></a>
</div>
</nav>
</div>
<div class="links-of-author motion-element">
<span class="links-of-author-item">
<a href="https://github.com/phbernardes" title="GitHub → https://github.com/phbernardes" rel="noopener" target="_blank"><i class="fab fa-github fa-fw"></i>GitHub</a>
</span>
<span class="links-of-author-item">
<a href="https://www.linkedin.com/in/pedro-bernardes/" title="LinkedIn → https://www.linkedin.com/in/pedro-bernardes/" rel="noopener" target="_blank"><i class="fab fa-linkedin fa-fw"></i>LinkedIn</a>
</span>
</div>
</div>
<div class="back-to-top motion-element">
<i class="fa fa-arrow-up"></i>
<span>0%</span>
</div>
</div>
</aside>
<div id="sidebar-dimmer"></div>
</div>
</main>
<footer class="footer">
<div class="footer-inner">
<div class="copyright">
© 2021 –
<span itemprop="copyrightYear">2022</span>
<span class="with-love">
<i class=""></i>
</span>
<span class="author" itemprop="copyrightHolder">Pedro Bernardes</span>
</div>
<div class="addthis_inline_share_toolbox">
<script src="//s7.addthis.com/js/300/addthis_widget.js#pubid=ra-6187ec0521c6ec20" async="async"></script>
</div>
</div>
</footer>
</div>
<script src="/lib/anime.min.js"></script>
<script src="/lib/velocity/velocity.min.js"></script>
<script src="/lib/velocity/velocity.ui.min.js"></script>
<script src="/js/utils.js"></script>
<script src="/js/motion.js"></script>
<script src="/js/schemes/pisces.js"></script>
<script src="/js/next-boot.js"></script>
<script src="/js/local-search.js"></script>
<script>
function loadCount() {
var d = document, s = d.createElement('script');
s.src = 'https://phbernardes.disqus.com/count.js';
s.id = 'dsq-count-scr';
(d.head || d.body).appendChild(s);
}
// defer loading until the whole page loading is completed
window.addEventListener('load', loadCount, false);
</script>
</body>
</html>