Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/twtrubiks/django-rest-framework-tutorial

Django-REST-framework 基本教學 - 從無到有 DRF-Beginners-Guide 📝
https://github.com/twtrubiks/django-rest-framework-tutorial

django-rest-framework pycharm python rest-api restful-api tutorial

Last synced: 4 days ago
JSON representation

Django-REST-framework 基本教學 - 從無到有 DRF-Beginners-Guide 📝

Awesome Lists containing this project

README

        

# django-rest-framework-tutorial

`django > 2.0` 可參考 [django2](https://github.com/twtrubiks/django-rest-framework-tutorial/tree/django2) 分支.

`django > 3.0` 可參考 [django3](https://github.com/twtrubiks/django-rest-framework-tutorial/tree/django3) 分支.

Django-REST-framework 基本教學 - 從無到有 DRF-Beginners-Guide 📝

* [Youtube Tutorial PART 1](https://youtu.be/lunVXqMVsrs)
* [Youtube Tutorial PART 2](https://youtu.be/Qnir5iFpMyQ)
* [Youtube Tutorial PART 3](https://youtu.be/3qoB3RVoOvA)
* [Youtube Tutorial PART 4](https://youtu.be/yvH1-jx_-z4)
* [Youtube Tutorial PART 5](https://youtu.be/YMtz7OSwIlE)
* [Youtube Tutorial PART 6](https://youtu.be/jONV4Bfjq6g)

透過 [Django REST framework](http://www.django-rest-framework.org/) ( DRF ) 建立 REST API 非常方便快速,

REST API ? 這是什麼,可以吃嗎 ? 如果你想先對 REST API 有一些認識,可參考之前寫的 [認識 RESTful API](https://github.com/twtrubiks/django-rest-framework-tutorial/tree/master/RESTful-API-Tutorial)

在這裡教大家建立自己的第一個 [Django-REST-framework](http://www.django-rest-framework.org/) :smile:

建議對 [Django](https://github.com/django/django) 還不熟的人,可以先閱讀我之前寫的 [Django 基本教學 - 從無到有 Django-Beginners-Guide](https://github.com/twtrubiks/django-tutorial),

先建立一些基本觀念,再來看 DRF 會比較清楚。

## 教學

請先確認電腦有安裝 [Python](https://www.python.org/)

請在你的命令提示字元 (cmd ) 底下輸入

安裝 [Django](https://github.com/django/django)

>pip install django

安裝 [Django-REST-framework](http://www.django-rest-framework.org/)
>pip install djangorestframework

基本上安裝應該沒什麼問題。

### django-rest-framework 設定

***請記得要將 [Django-REST-framework](http://www.django-rest-framework.org/) 加入設定檔***

請在 settings.py 裡面的 **INSTALLED_APPS** 加入下方程式碼 (下圖)

```python
INSTALLED_APPS = (
...
'rest_framework',
...
)
```

![alt tag](http://i.imgur.com/bm7cO0e.jpg)

### 建立 Django App

先建立一個觀念,在 [Django](https://github.com/django/django) 中,通常我們會依照 **功能** 去建立一個 App , 例如範例的 musics ,代表他是 管理音樂 的部份。

有了這個觀念之後,我們動手開始做吧~

請在你的命令提示字元 (cmd ) 底下輸入

>python manage.py startapp musics

***建立完請記得要將 App 加入設定檔***

請在 settings.py 裡面的 **INSTALLED_APPS** 加入 musics (也就是你自己建立的 App 名稱)

![alt tag](http://i.imgur.com/xP1MoFI.jpg)

### Models

定義出資料庫中的結構(schema),並且透過 Django 中的指令去建立資料庫。

[Django](https://github.com/django/django) 預設是使用 [SQLite](https://www.sqlite.org/) ,如果想要修改為其他的資料庫,可以在 settings.py 裡面進行修改。

首先,請先在 models.py 裡面增加下方程式碼 (下圖)

```python
from django.db import models

# Create your models here.
class Music(models.Model):
song = models.TextField()
singer = models.TextField()
last_modify_date = models.DateTimeField(auto_now=True)
created = models.DateTimeField(auto_now_add=True)

class Meta:
db_table = "music"

```

![alt tag](http://i.imgur.com/gydF0x4.jpg)

接著在命令提示字元 (cmd ) 底下輸入

>python manage.py makemigrations

![alt tag](http://i.imgur.com/xH4Sm3s.jpg)

> python manage.py migrate

![alt tag](http://i.imgur.com/CpcdT3X.jpg)

makemigrations : 會幚你建立一個檔案,去記錄你更新了哪些東西。

migrate : 根據 makemigrations 建立的檔案,去更新你的 DATABASE 。

執行完上面的指令之後,

你可以使用[SQLiteBrowser](http://sqlitebrowser.org/) 或 [PyCharm](https://www.jetbrains.com/pycharm/) 觀看 DATABASE,

你會發現多出一個 **music** 的 table ( 如下圖 )

![alt tag](http://i.imgur.com/xVbTtjq.jpg)

有沒有注意到我們明明在 models.py 裡面就沒有輸入 id ,可是 database 裡面卻有 id 欄位,

這是因為 Django 預設會幫你帶入,所以可以不用設定。

### Serializers 序列化

Serializers 序列化 是 DRF 很重要的一個地方 :star:

主要功能是將 Python 結構序列化為其他格式,例如我們常用的 JSON。

在 musics 裡面新增 serializers.py,並輸入下方程式碼

```python
from rest_framework import serializers
from musics.models import Music

class MusicSerializer(serializers.ModelSerializer):
class Meta:
model = Music
# fields = '__all__'
fields = ('id', 'song', 'singer', 'last_modify_date', 'created')

```

![alt tag](http://i.imgur.com/KY5UwHW.jpg)

如果你想要全部 fields ,可以使用第 8 行的寫法。

2017/9/8 新增

增加 `SerializerMethodField` 使用方法 ,可參考 [serializers.py](https://github.com/twtrubiks/django-rest-framework-tutorial/blob/master/musics/serializers.py), days_since_created 為例

```python
class MusicSerializer(serializers.ModelSerializer):
days_since_created = serializers.SerializerMethodField()

class Meta:
model = Music
# fields = '__all__'
fields = ('id', 'song', 'singer', 'last_modify_date', 'created', 'days_since_created')

def get_days_since_created(self, obj):
return (now() - obj.created).days
```

更多說明請參考 [http://www.django-rest-framework.org/api-guide/fields/#serializermethodfield](http://www.django-rest-framework.org/api-guide/fields/#serializermethodfield)

2018/2/11 新增

有時候會需要自定義序列化,舉個例子,假如我希望將回傳的 singer 都轉成大寫這樣我要該怎麼辦 ?

這邊不希望又多一個 property 回傳 ( singer1 之類的 ),所以這時候我們就必須自定義序列化,也就是

透過 `.to_representation(self, value)` 這個方法,更多說明請參考 [Custom relational fields](http://www.django-rest-framework.org/api-guide/relations/#custom-relational-fields)。

範例寫法可參考 [serializers.py](https://github.com/twtrubiks/django-rest-framework-tutorial/blob/master/musics/serializers.py)

```python
from django.utils.timezone import now
from rest_framework import serializers
from musics.models import Music

class ToUpperCaseCharField(serializers.CharField):
def to_representation(self, value):
return value.upper()

class MusicSerializer(serializers.ModelSerializer):
days_since_created = serializers.SerializerMethodField()
singer = ToUpperCaseCharField()

class Meta:
model = Music
# fields = '__all__'
fields = ('id', 'song', 'singer', 'last_modify_date', 'created', 'days_since_created')

def get_days_since_created(self, obj):
return (now() - obj.created).days
```

這樣你就會發現回傳的 singer 都被轉成大寫了

![alt tag](https://i.imgur.com/WsVG86d.png)

### Views

在 [Django 基本教學 - 從無到有 Django-Beginners-Guide](https://github.com/twtrubiks/django-tutorial) 中我們使用 views,

而在 DRF 中提供我們可以使用另一種稱為 viewsets 。

請在 views.py 裡輸入下方程式碼 (下圖)

```python
# Create your views here.
from musics.models import Music
from musics.serializers import MusicSerializer

from rest_framework import viewsets

# Create your views here.
class MusicViewSet(viewsets.ModelViewSet):
queryset = Music.objects.all()
serializer_class = MusicSerializer

```

![alt tag](http://i.imgur.com/GMSz7u7.jpg)

只需要寫這樣,你就擁有 CRUD 的全部功能,是不是非常強大 :open_mouth:

為什麼呢? 因為 DRF 的 **viewsets.ModelViewSet** 裡面幫你定義了這些功能,

![alt tag](http://i.imgur.com/GHbUOT5.jpg)

當然,如果你需要,也可以覆寫他。

### Routers 路由

DRF 提供 DefaultRouter 讓我們快速建立 Routers 路由。

請先將 urls.py 裡面增加一些程式碼,如下圖

```python
from django.conf.urls import url, include
from django.contrib import admin
from rest_framework.routers import DefaultRouter
from musics import views

router = DefaultRouter()
router.register(r'music', views.MusicViewSet)

urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^api/', include(router.urls))
]

```

![alt tag](http://i.imgur.com/imdF1f8.jpg)

最後執行 Django , 然後瀏覽 [http://127.0.0.1:8000/api/](http://127.0.0.1:8000/api/)

你應該會看到如下圖

![alt tag](http://i.imgur.com/ZpmiVnG.jpg)

恭喜你,成功了 :smile:

接下來,讓我來測試 API 吧~

### 測試 API

在測試 API 之前,大家必須先了解一下什麼是 REST API

REST API 全名為 RESTful API,它並不是一個新東西、新技術,它只是一個規範。

簡單說明 :

GET : 讀取資源

PUT : 替換資源

DELETE : 刪除資源

POST : 新增資源

PATCH : 更新資源部份內容

剩下更詳細的資料就麻煩大家 GOOGLE了,我在現在來 測試 API :smiley:

測試 API 的工具很多,在這裡我們使用 [Postman](https://www.getpostman.com/) ,大家可以用自己習慣的工具。

#### POST

我們先來新增幾筆資料,如下圖

![alt tag](http://i.imgur.com/zalPhwM.jpg)

在 步驟1 的地方輸入你的 API 的網址,範例為 [http://127.0.0.1:8000/api/music/](http://127.0.0.1:8000/api/music/)

在 步驟2 body 的地方,填入 song 和 singer 的值,然後按下 Send,

接著看 response ( 步驟3 ),也就是你新增進去 dabase 的資料。

#### GET

如果你想一次看裡面全部的資料,可以使用 [http://127.0.0.1:8000/api/music/](http://127.0.0.1:8000/api/music/)

![alt tag](http://i.imgur.com/clilnZL.jpg)

或是你只想看特定的某一筆,可以使用 [http://127.0.0.1:8000/api/music/2/](http://127.0.0.1:8000/api/music/2/)

![alt tag](http://i.imgur.com/RHwAjpU.jpg)

#### PUT

如果你想修改特定資料,可以使用 [http://127.0.0.1:8000/api/music/2/](http://127.0.0.1:8000/api/music/2/)

![alt tag](http://i.imgur.com/7v5U03P.jpg)

當按下 send 之後,會看到 response ( 步驟3 )的地方回傳修改後的值。

#### DELETE

如果你想刪除特定資料,可以使用 [http://127.0.0.1:8000/api/music/3/](http://127.0.0.1:8000/api/music/3/)

![alt tag](http://i.imgur.com/HjCCICb.jpg)

執行後,你會發現 id=3 的資料被刪除了。

![alt tag](http://i.imgur.com/tOQS5cq.jpg)

### Performing raw SQL queries

* [Youtube Tutorial PART 5](https://youtu.be/YMtz7OSwIlE)

2018/2/11 新增

雖然 Django ORM 使用起來很棒,又容易使用 ( 如不了解 Django ORM,請參考我之前的介紹文章 [Django ORM](https://github.com/twtrubiks/django-tutorial#django-orm) ),

但有時候我們還是會希望使用 raw SQL ,像是邏輯比較複雜的,不適合使用 Django ORM 寫,畢竟 Django ORM 的底層

還是 raw SQL,Django 提供兩種方法來完成他,分別是 **Performing raw queries** 以及 **Executing custom SQL directly**。

這邊提醒一下,如果使用這種方法,請注意 [SQL injection protection](https://docs.djangoproject.com/en/1.11/topics/security/#sql-injection-protection)。

更多詳細可參考 [https://docs.djangoproject.com/en/1.11/topics/db/sql/#performing-raw-queries](https://docs.djangoproject.com/en/1.11/topics/db/sql/#performing-raw-queries)

#### Performing raw queries

透過 `Manager.raw()`這個方法,可參考 [models.py](https://github.com/twtrubiks/django-rest-framework-tutorial/blob/master/musics/models.py)

簡單說明一下這段 code,前端可以帶入 song 的名稱近來查詢,也可以不帶,不帶的話就是回傳全部

```python
def fun_raw_sql_query(**kwargs):
song = kwargs.get('song')
if song:
result = Music.objects.raw('SELECT * FROM music WHERE song = %s', [song])
else:
result = Music.objects.raw('SELECT * FROM music')
return result
```

[views.py](https://github.com/twtrubiks/django-rest-framework-tutorial/blob/master/musics/views.py) 中的片段 code

```python
# /api/music/raw_sql_query/
@list_route(methods=['get'])
def raw_sql_query(self, request):
song = request.query_params.get('song', None)
music = fun_raw_sql_query(song=song)
serializer = MusicSerializer(music, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
```

這個方法有 map 到你的 models,所以一樣可以序列化

request

![alt tag](https://i.imgur.com/jz9aqi4.png)

response

![alt tag](https://i.imgur.com/0p3KN3e.png)

更多詳細可參考 [https://docs.djangoproject.com/en/1.11/topics/db/sql/#performing-raw-queries](https://docs.djangoproject.com/en/1.11/topics/db/sql/#performing-raw-queries)

#### Executing custom SQL directly

有時候 `Manager.raw()` 是不夠的,像是你可能需要 queries 沒有完全 map 到 models 的資料,

或是執行 UPDATE, INSERT, or DELETE。

當我們使用這個方法時,是完全的繞過 model ,直接 access database。

可參考 [models.py](https://github.com/twtrubiks/django-rest-framework-tutorial/blob/master/musics/models.py)

簡單說明一下這段 code,前端可以帶入 id 和 song 來更新資料

```python
def namedtuplefetchall(cursor):
# Return all rows from a cursor as a namedtuple
desc = cursor.description
nt_result = namedtuple('Result', [col[0] for col in desc])
return [nt_result(*row) for row in cursor.fetchall()]

def fun_sql_cursor_update(**kwargs):
song = kwargs.get('song')
pk = kwargs.get('pk')

'''
Note that if you want to include literal percent signs in the query,
you have to double them in the case you are passing parameters:
'''
with connection.cursor() as cursor:
cursor.execute("UPDATE music SET song = %s WHERE id = %s", [song, pk])
cursor.execute("SELECT * FROM music WHERE id = %s", [pk])
# result = cursor.fetchone()
result = namedtuplefetchall(cursor)
result = [
{
'id': r.id,
'song': r.song,
'singer': r.singer,
'last_modify_date': r.last_modify_date,
'created': r.created,
}
for r in result
]

return result
```

補充一下上面英文註解的說明,假設今天我們使用 like 搜尋,也就是會包含 `%` 的符號,

這時候我們必須重複 `%` 這個符號,也就是 `%%`,請看以下的例子,

假如我想執行這個 sql

```sql
SELECT * FROM music WHERE song like 'song%'
```

在 `cursor.execute` 中,必須多加上一個 `%`

```python
cursor.execute("SELECT * FROM music WHERE song like 'song%%'", [])
```

[views.py](https://github.com/twtrubiks/django-rest-framework-tutorial/blob/master/musics/views.py) 中的片段 code

由於這個方法是沒有 map 到 model,所以我們沒辦法進行序列化,

這邊將直接回傳一個 dict 字典,

```python
# /api/music/{pk}/sql_cursor_update/
@detail_route(methods=['put'])
def sql_cursor_update(self, request, pk=None):
song = request.data.get('song', None)
if song:
music = fun_sql_cursor_update(song=song, pk=pk)
return Response(music, status=status.HTTP_200_OK)
```

request

![alt tag](https://i.imgur.com/0Qfyrra.png)

response

![alt tag](https://i.imgur.com/gVFgSPx.png)

更多詳細可參考 [https://docs.djangoproject.com/en/1.11/topics/db/sql/#executing-custom-sql-directly](https://docs.djangoproject.com/en/1.11/topics/db/sql/#executing-custom-sql-directly)

### 授權 (Authentications )

在 REST API 中,授權很重要,如果沒有授權,別人一直任意不受限制的操作你的 API ,很危險,

所以 DRF 有提供 Authentications,讓我們來試試看吧~

首先,請在 views.py 裡面新增 permission_classes

```python
# Create your views here.
from musics.models import Music
from musics.serializers import MusicSerializer

from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated

# Create your views here.
class MusicViewSet(viewsets.ModelViewSet):
queryset = Music.objects.all()
serializer_class = MusicSerializer
permission_classes = (IsAuthenticated,)
```

![alt tag](http://i.imgur.com/RbQrZLt.jpg)

接著在 urls.py 裡面增加 api-auth

```python
from django.conf.urls import url, include
from django.contrib import admin
from rest_framework.routers import DefaultRouter
from musics import views

router = DefaultRouter()
router.register(r'music', views.MusicViewSet)

urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^api/', include(router.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
```

![alt tag](http://i.imgur.com/YISdOvo.jpg)

最後執行 Django , 然後瀏覽 [http://127.0.0.1:8000/api/](http://127.0.0.1:8000/api/) ,你會發現右上角多了 Log in 的按鈕

![alt tag](http://i.imgur.com/DxgSK9q.jpg)

我們先使用我們在 [Django 基本教學 - 從無到有 Django-Beginners-Guide](https://github.com/twtrubiks/django-tutorial) 裡面學到的 建立超級使用者

>python manage.py createsuperuser

![alt tag](http://i.imgur.com/wqacaCR.jpg)

讓我們再次使用 POSTMAN,我們用 GET 當作範例

#### GET 授權

![alt tag](http://i.imgur.com/MoMLRB3.jpg)

有注意到嗎? response 說我沒有 授權,

所以這時候我們就必須再加上授權才能操作 API (如下圖),我們可以操作 API 了

我的 帳號/密碼 設定為 twtrubiks/password123

![alt tag](http://i.imgur.com/8leY8ZH.jpg)

2017/12/3 新增

* [Youtube Tutorial PART 3](https://youtu.be/3qoB3RVoOvA)

上面的方法是針對整個 `class` 設定權限,那我們可不可以依照 method 呢?

幾個例子,我希望 GET 時不用權限,但是 POST 時就需要權限,這樣該怎麼做呢?

可以參考 shares/[views.py](https://github.com/twtrubiks/django-rest-framework-tutorial/blob/master/shares/views.py)

```python
class ShareViewSet(viewsets.ModelViewSet):
queryset = Share.objects.all()
serializer_class = ShareSerializer
parser_classes = (JSONParser,)

def get_permissions(self):
if self.action in ('create',):
self.permission_classes = [IsAuthenticated]
return [permission() for permission in self.permission_classes]

# [GET] api/shares/
def list(self, request, **kwargs):
users = Share.objects.all()
serializer = ShareSerializer(users, many=True)

return Response(serializer.data, status=status.HTTP_200_OK)

# [POST] api/shares/
def create(self, request, **kwargs):
name = request.data.get('name')
users = Share.objects.create(name=name)
serializer = ShareSerializer(users)

return Response(serializer.data, status=status.HTTP_201_CREATED)
```

透過`get_permissions`來決定是否需要權限(在這裡設定 `create`, 也就是 POST)。

這個例子就是 **GET** 時**不用權限**,但是 **POST** 時就**需要權限**。

更多詳細介紹可參考官網 [authentication](http://www.django-rest-framework.org/api-guide/authentication/)

### Parsers

在 REST framework 中有一個 [Parser classes](http://www.django-rest-framework.org/api-guide/parsers/#parsers) ,這個 Parser
classes 主要是能控制接收的 Content-Type ,

例如說我規定 Content-Type 只接受 application/json ,這樣你就不能傳其他的 Content-Type ( 舉例 : text/plain ) 。

通常如果沒有特別去設定 ,一般預設是使用 application / x-www-form-urlencode ,不過預設的可能不是你想要的或是

說你想要設計只允許規範一種 Content-Type 。

設定 Parsers 也很簡單,如果你希望全域的設定,可以加在 [settings.py](https://github.com/twtrubiks/django-rest-framework-tutorial/blob/master/django_rest_framework_tutorial/settings.py),

這樣就代表我只允許 Content-Type 是 application/json 。

```python
REST_FRAMEWORK = {
'DEFAULT_PARSER_CLASSES': (
'rest_framework.parsers.JSONParser',
)
}
```

也可以針對特定 view 或 viewsets 加以設定 ,直接在 [views.py](https://github.com/twtrubiks/django-rest-framework-tutorial/blob/master/musics/views.py) 加上 parser_classes 即可

```python
class MusicViewSet(viewsets.ModelViewSet):
queryset = Music.objects.all()
serializer_class = MusicSerializer
permission_classes = (IsAuthenticated,)
parser_classes = (JSONParser,)
```

當然,parser_classes 不只有 [JSONParser](http://www.django-rest-framework.org/api-guide/parsers/#jsonparser),還有 [FormParser](http://www.django-rest-framework.org/api-guide/parsers/#formparser) , [MultiPartParser](http://www.django-rest-framework.org/api-guide/parsers/#multipartparser) 等等

更多資訊可參考
[http://www.django-rest-framework.org/api-guide/parsers/#parsersr](http://www.django-rest-framework.org/api-guide/parsers/#parsersr)

### Extra link and actions

* [Youtube Tutorial PART 4](https://youtu.be/yvH1-jx_-z4)

我們使用 REST framework 時,難免會有想要制定額外的 route ,這時候我們可以利用
`@detail_route` 或 `@list_route`。

範例程式碼可參考 [views.py](https://github.com/twtrubiks/django-rest-framework-tutorial/blob/master/musics/views.py)

***detail_route***

使用方法很簡單,直接加上裝飾器 `@detail_route` 即可

```python
@detail_route(methods=['get'])
def detail(self, request, pk=None):
music = get_object_or_404(Music, pk=pk)
result = {
'singer': music.singer,
'song': music.song
}

return Response(result, status=status.HTTP_200_OK)
```

以上面這個例子來說, URL pattern: `/api/music/{pk}/detail/`,

如果你沒有額外指定,通常你的 url_path 就是你 function 命名的名稱,

當然,我們也可以自己額外定義 url_path,只需要加上 url_path 參數,

範例如下

```python
@detail_route(methods=['get'], url_path='detail_self')
def detail(self, request, pk=None):
music = get_object_or_404(Music, pk=pk)
result = {
'singer': music.singer,
'song': music.song
}

return Response(result, status=status.HTTP_200_OK)
```

以上面這個例子來說, URL pattern: `/api/music/{pk}/detail_self/`,

這樣就不會使用你的 function 做為 url_path 了。

***list_route***

使用方法很簡單,直接加上裝飾器 `@list_route` 即可

```python
@list_route(methods=['get'])
def all_singer(self, request):
music = Music.objects.values_list('singer', flat=True).distinct()
return Response(music, status=status.HTTP_200_OK)
```

以上面這個例子來說,URL pattern: `/api/music/all_singer/`

他也有 url_path 的特性,如果要自定義,只需要加上 url_path 參數。

看完了以上的例子,相信大家可以分辨 `@detail_route` 以及 `@list_route`的不同。

更多資訊可參考 [http://www.django-rest-framework.org/api-guide/routers/#extra-link-and-actions](http://www.django-rest-framework.org/api-guide/routers/#extra-link-and-actions)

### Testing

先簡單介紹一下大家常聽到的 ***TDD*** 以及 ***BDD***

TDD : Test-Driven Development。

BDD : Behavior-driven development 。

詳細地請大家再自行 GOOGLE,這邊要講 DRF 的 Testing,

你也可以參考官網的教學 [http://www.django-rest-framework.org/api-guide/testing/](http://www.django-rest-framework.org/api-guide/testing/)

或是你也可以參考我寫的範例
[tests.py](https://github.com/twtrubiks/django-rest-framework-tutorial/blob/master/musics/tests.py)

#### Test Case Scenarios

* Create a music with API.
* Retrieve a music with API.
* Partial Update a music with API.
* Update a music with API.
* Delete a music with API.
* Retrieve a music detail with API.
* Get All singer with API.

#### API Endpoints

Music

* ***/api/music/ (Music create and list endpoint)***
* ***/api/music/{music-id}/ (Music retrieve, update and partial update and destroy endpoint)***

* ***/api/music/{music-id}/detail/ (Music retrieve detail endpoint)***

* ***/api/music/all_singer/ (Music list singer endpoint)***

Usage

```python
python manage.py test
```

![img](http://i.imgur.com/OTZ1IRD.png)

因為本範例剛好只有建立一個 APP ,如果你有很多個 APP ,你也可以指定

你要測試的 APP,範例如下

```python
python manage.py test [app 名稱]
```

```python
python manage.py test musics
```

### Versioning

* [Youtube Tutorial PART 6](https://youtu.be/jONV4Bfjq6g)

有時候我們可能需要版本來控制 API ,當然沒版本的 API 也是可以被接受的,

可參考 [Non-versioned systems can also be appropriate](https://www.infoq.com/articles/roy-fielding-on-versioning)。

要設定 versioning,請先到 [settings.py](https://github.com/twtrubiks/django-rest-framework-tutorial/blob/master/django_rest_framework_tutorial/settings.py) 加入下方設定,

```python
REST_FRAMEWORK = {
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.AcceptHeaderVersioning'
}
```

有很多方法可以實現,分別為

`AcceptHeaderVersioning` `URLPathVersioning` `NamespaceVersioning` `HostNameVersioning` `QueryParameterVersioning`,

由於 `AcceptHeaderVersioning` 這個方法通常被認為是最佳的設計,所以這邊就用它來介紹。

使用序列化的不同來介紹 Versioning,[views.py](https://github.com/twtrubiks/django-rest-framework-tutorial/blob/master/musics/views.py) 如下,

```python
# /api/music/version_api/
@list_route(methods=['get'])
def version_api(self, request):
music = Music.objects.all()
if self.request.version == '1.0':
serializer = MusicSerializerV1(music, many=True)
else:
serializer = MusicSerializer(music, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
```

其實也很簡單,就是判斷 `self.request.version` 是否有值,

如果 header 沒有帶入版本號,就會使用 `MusicSerializer` 進行序列化,

![alt tag](https://i.imgur.com/kOuzqgG.png)

如果 header 有帶入版本號,就會使用 `MusicSerializerV1` 進行序列化。

![alt tag](https://i.imgur.com/kGRJmt2.png)

其他的使用方法,請參考官網 [Versioning](http://www.django-rest-framework.org/api-guide/versioning/)。

### Model Meta options

`app_label`

還記得文章前面提到的 `INSTALLED_APPS` 嗎 ? 如果你沒有將 model 寫在 `INSTALLED_APPS` 中,

這時候你就必須在 Model Meta 中宣告 ( 否則會報錯 ),像下面這樣

```python
class Music(models.Model):
song = models.TextField()
singer = models.TextField()
last_modify_date = models.DateTimeField(auto_now=True)
created = models.DateTimeField(auto_now_add=True)

class Meta:
db_table = "music"
app_label = "music"
```

可參考 [https://docs.djangoproject.com/en/1.11/ref/models/options/#app-label](https://docs.djangoproject.com/en/1.11/ref/models/options/#app-label)

這邊的東西很多,我有用到就會慢慢補 :kissing_closed_eyes:

更多詳細可參考 [https://docs.djangoproject.com/en/1.11/ref/models/options/#model-meta-options](https://docs.djangoproject.com/en/1.11/ref/models/options/#model-meta-options)

### Multiple databases

這邊的部分也蠻多的,有空我會補 :kissing_closed_eyes:

更多詳細可參考 [https://docs.djangoproject.com/en/2.0/topics/db/multi-db/](https://docs.djangoproject.com/en/2.0/topics/db/multi-db/)

#### Automatic database routing

這部分我先簡單寫個範例,以後有情境我在將細節補上來:smiley:

更多詳細可參考 [https://docs.djangoproject.com/en/2.0/topics/db/multi-db/#automatic-database-routing)](https://docs.djangoproject.com/en/2.0/topics/db/multi-db/#automatic-database-routing)

api/[routers.py](https://github.com/twtrubiks/django-rest-framework-tutorial/blob/master/api/routers.py)

```python
class AuthRouter:
"""
A router to control all database operations on models in the
auth application.
"""
def db_for_read(self, model, **hints):
"""
Attempts to read auth models go to auth_db.
"""
if model._meta.app_label == 'musics':
return 'default'
return None

def db_for_write(self, model, **hints):
"""
Attempts to write auth models go to auth_db.
"""
if model._meta.app_label == 'auth':
return 'auth_db'
return None
```

在 [settings.py](https://github.com/twtrubiks/django-rest-framework-tutorial/blob/master/django_rest_framework_tutorial/settings.py) 中加上這段

```python
DATABASE_ROUTERS = ['api.routers.AuthRouter']
```

## 後記

恭喜你,基本上到這裡,已經是一個非常簡單的 [Django-REST-framework](http://www.django-rest-framework.org/) ,趕快動手下去玩玩吧 :stuck_out_tongue:

如果意猶未盡,延伸閱讀 :satisfied:

* [Django-REST-framework 基本教學 - 從無到有 DRF-Beginners-Guide](https://github.com/twtrubiks/django-rest-framework-tutorial)

* [DRF-dataTable-Example-server-side](https://github.com/twtrubiks/DRF-dataTable-Example-server-side) - DataTables Example (server-side) - Python Django REST framework

* [Deploying_Django_To_Heroku_Tutorial](https://github.com/twtrubiks/Deploying_Django_To_Heroku_Tutorial) - Deploying a Django App To Heroku Tutorial

* [結合 Django + jQuery 實現無限捲軸 Infinite Scroll 📝](https://github.com/twtrubiks/ptt_beauty_infinite_scroll)

## 執行環境

* Python 3.4.3

## Reference

* [Django](https://www.djangoproject.com/)
* [Django-REST-framework](http://www.django-rest-framework.org/)

## Donation

文章都是我自己研究內化後原創,如果有幫助到您,也想鼓勵我的話,歡迎請我喝一杯咖啡:laughing:

![alt tag](https://i.imgur.com/LRct9xa.png)

[贊助者付款](https://payment.opay.tw/Broadcaster/Donate/9E47FDEF85ABE383A0F5FC6A218606F8)

## License

MIT license