---
# System prepended metadata

title: '[影片欣賞] Django 的validator不好用? 不如自幹一個'

---

# [影片欣賞] Django 的validator不好用? 不如自幹一個
![](https://i.imgur.com/VcAOMeQ.png)
- 影片網址: https://www.youtube.com/watch?v=urOLJqX0lZ0

### Outline([3:33](https://www.youtube.com/watch?v=urOLJqX0lZ0&t=213s))
1. 介紹Django validator
2. 當我們使用Django的validator時會遇到什麼問題
3. 介紹 Data-Spec-Validator (DSV)
4. Q&A

### Django validator
1. 簡單介紹Django內建的BaseValidator source code([4:44](https://www.youtube.com/watch?v=urOLJqX0lZ0&t=284s))
    - `__init__`: define error message & limit value
    - 如果要override的話可以去改這兩個method
        - clean(): 資料清洗
        - compare(): 比較方式
2. 介紹目前Django內建的Validator([6:52](https://www.youtube.com/watch?v=urOLJqX0lZ0&t=412s))
    - 目前內建只有上面這10種
![](https://i.imgur.com/1yOCzh3.png)
3. 使用情境([8:15](https://www.youtube.com/watch?v=urOLJqX0lZ0&t=495s))
    - 先定義一個只接受偶數的method，否則raise ValidationError，再Django內的Model、Form裡面的validators指定該method，進行參數驗證
        - Model：對應到DB內某張表的欄位
        - Form：對應到輸入參數或是Jinja顯示的表單
    :::info
    遇到的第一個問題：一個validator的method必須綁定在一個Model、Form上面，
    :::
    ![](https://i.imgur.com/0cLDbeS.png)


### 遇到什麼問題
1. Django內建提供的validator太少了，像是我們要驗證輸入參數的下列操作都無法達成([10:10](https://www.youtube.com/watch?v=urOLJqX0lZ0&t=610s))
    1. 型態：bool、int、float、str、dict、list、None....
    2. 物件：UUID、Json、Datetime、Custom class
    3. 欄位之間的關係：
        - 如果b、c不存在，那a必須在
        - b、c必須同時存在
        - 如果a、b、c有一個有值，d才可以存在
2. 依賴於框架、實做起來的不便([11:55](https://www.youtube.com/watch?v=urOLJqX0lZ0&t=715s))
    1. 實作Form上有小瑕疵，可以直接指定參數(`f2.even_field = 5`)，然後 **`f2.is_valid() = False，但f2.errors卻是空的??`**。如果不是一個Django仔，真的不知道要怎麼設定才好
    ![](https://i.imgur.com/NdZVMa3.png)
    2. 在Model中設定validators了(even_filed必須是偶數)，但在實際存取時還是可以==直接存進DB==。需要自己額外呼叫`full_clean()`才會顯示錯誤
    ![](https://i.imgur.com/lN3JE2X.png)
    3. 使用上的不方便：因為跟Django綁死了，如果不使用Django開發要使用Django validator要額外再pip install
3. Form有各種職責([15:05](https://www.youtube.com/watch?v=urOLJqX0lZ0&t=905s))
    1. 把準備好的資料拿去render產生一個form
        => 從程式碼看起來應該是在講 `request.method == POST` 的時候，form會去驗證欄位是否吻合，沒問題再redirect去另外一個API
    2. `[GET]`把這些資料建立出一個html的form，再透過jinja template顯示出form
    3. 從前端接到 form object 你要轉成 python object
    
    :::warning
    - Form跟前端有欄位、習慣關聯，後端在form設定的值，前端就一定會有那個欄位值
    - 如果我只是想要做參數驗證勒? 不想要創建一個Form
    - Form這個詞在現在，已經不合時宜了，因為很多前端的框架(React、Angular)打進後端都不是用Form來封裝
    :::
    ![](https://i.imgur.com/C4h7BjO.png)

4. 你可能看過([17:15](https://www.youtube.com/watch?v=urOLJqX0lZ0&t=1035s))
    1. 傳進來的參數可能會經過一些特別的處理，像是`process_param2`會產生一個物件 or 產生side effects，==導致function不夠純粹(`這邊有一個英文沒聽懂是什麼意思XD，但我猜是在講不純粹Pure function`)==。再把最終data透過serializer組裝並`自行`驗證(`sz.is_valid()`)是否有誤，還需指定`raise_exception=True`才會噴exception，不然只會噴True、False
    ![](https://i.imgur.com/w1BRVO6.png)
    2. 經過`複雜的參數驗證`後，才能進行真正需要的`商業邏輯`開發
    ![](https://i.imgur.com/JwmbrHT.png)

5. 最終我們想要達到怎樣?([20:08](https://www.youtube.com/watch?v=urOLJqX0lZ0&t=1208s))
    - 不想要有上面說的那些問題
    - 希望有一個package有更多彈性的validator type、容易變成API params doc
    - 減少function內驗證的參數的code
    - 不只能驗證request的參數，還能直接跨function(pure python code)驗證
    - 不要依賴於任何的Framework
    ![](https://i.imgur.com/MGlUW2H.png)

### Data-Spec-Validator(DSV)
- 功能介紹([21:05](https://www.youtube.com/watch?v=urOLJqX0lZ0&t=1265s))
    - `check`(int, digit_str, date, one_of...)，一次只驗證一個條件，但可以設定`op=CheckerOP.ALL`來達到同時判斷多個型態或條件(type=int、>10)
    ```py
    class UserSpec:
        age = Checker([INT, INT_GT_10], op=CheckerOP.ALL)
    ```
    - 參數的資料結構可以是巢狀的(`{"key": [1,2,3]}`)的 => SPEC, LIST_OF(下面有舉例)
    - 可以使用`ANY_KEY_EXISTES`, `CO_EXISTS`來驗證參數之間的關聯。e.g：b、c同時存在
    - 可以自定義`check & validator`
    - 支援 Django/ DRF view decorators (@dsv)
    ![](https://i.imgur.com/BDXaAQE.png)

- use case([23:32](https://www.youtube.com/watch?v=urOLJqX0lZ0&t=1412s))
    - 只講了這兩個範例：f_digistr、f_childb。我大概打了一點他簡報上的sample code出來給大家細細品嘗
    ```python
    child_data=dict(
        f_jb='false'
    )

    parent_data = dict(
        f_digistr="4"
        f_childb=[child_data, child_data, child_data]
    )
    
    from data_spec_validator.spec import *
    
    class ChildSpec:
        f_jb = Checker([JSON_BOOL])
    
    # f_digistr: 這個key可以不存在，但存在的話必須是數字字串'1'、'2'
    # f_childb: 是一個list，而且list中的每個物件都必須符合 ChildSpec規範
    class ParentSepc:
        f_digistr = Checker([DIGIT_STR], optional=True)
        f_childb = Checker([LIST_OF], extra={LIST_OF: SPEC, SPEC: ChildSpec})
    ```
    - ps: 這邊蠻建議大家把照片放大仔細看她的中文註解，可以好好理解一下他們是怎麼設定的，或是去下面的github連結看一下example
    ![](https://i.imgur.com/o1Xovv5.png)
    
    - 可以自行驗證參數，驗證的時候會回傳True、False、raise Exception(ValueError、TypeError、RuntimeError、PermissionError)。在驗證的時候不想要有exception可以使用`nothrow=True`這樣就只回傳True、False
    ![](https://i.imgur.com/85FPc68.png)

- 在Django View中使用Decorator([26:55](https://www.youtube.com/watch?v=urOLJqX0lZ0&t=1615s))
    - 你想要很快速的驗證輸入參數是否合規，可以在function上加@dsv(你定義的驗證規格)，會從request的params or body來進行驗證。可以直接用在Django的`FBV(function base views)`和`CBV(class base views)`上
    ![](https://i.imgur.com/YnYHICJ.png)
- 支援客製check & validator
    - 客製了一個validator，`輸入的參數必須>10`才會return True，否則return False
    ![](https://i.imgur.com/UhJLxau.png)

### Q&A([28:30](https://www.youtube.com/watch?v=urOLJqX0lZ0&t=1710s))
:::success
Q: 是否可以結合DRF Viewset使用?
A: 如果Viewset也是View的格式的話，是可以的
    => 自己google、實作後是可以的
:::

### 結論
- 使用心得
    - 優點：用起來該package確實很輕量、方便使用，也都有解決上面他所說到的問題點
    - 缺點：剛開始會不習慣該package的使用方式(驗證定義的方法)，需要自己找文件或github上面的範例，自己摸索一下
    - 分享一下自己使用的定義
    ![](https://i.imgur.com/VmdJNBW.png)

- 整理一下目前python主要的web framework都是怎麼驗證API輸入參數的?
    - Fastapi：使用Query、pydantic
    - Flask：google查到是用這個套件`from flask_restful import reqparse`
    - Django：使用Form validator
    - 想一下如果這個套件是可以套用這三個主要框架的話，學習的CP值是不是超高的啊XD

### 參考
- [Github](https://github.com/hardcoretech/data-spec-validator)
- [作者之前的文章](https://medium.com/gofreight_hq/django-%E4%BE%86%E8%87%AA%E5%B9%B9%E4%B8%80%E5%A5%97%E5%8F%83%E6%95%B8%E9%A9%97%E8%AD%89%E5%B7%A5%E5%85%B7-dc0748304fdd)
- [Flask輸入參數驗證](https://ithelp.ithome.com.tw/articles/10202886)
