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

- 影片網址: 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種

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上面,
:::

### 遇到什麼問題
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仔,真的不知道要怎麼設定才好

2. 在Model中設定validators了(even_filed必須是偶數),但在實際存取時還是可以==直接存進DB==。需要自己額外呼叫`full_clean()`才會顯示錯誤

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來封裝
:::

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

2. 經過`複雜的參數驗證`後,才能進行真正需要的`商業邏輯`開發

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

### 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)

- 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

- 可以自行驗證參數,驗證的時候會回傳True、False、raise Exception(ValueError、TypeError、RuntimeError、PermissionError)。在驗證的時候不想要有exception可以使用`nothrow=True`這樣就只回傳True、False

- 在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)`上

- 支援客製check & validator
- 客製了一個validator,`輸入的參數必須>10`才會return True,否則return False

### Q&A([28:30](https://www.youtube.com/watch?v=urOLJqX0lZ0&t=1710s))
:::success
Q: 是否可以結合DRF Viewset使用?
A: 如果Viewset也是View的格式的話,是可以的
=> 自己google、實作後是可以的
:::
### 結論
- 使用心得
- 優點:用起來該package確實很輕量、方便使用,也都有解決上面他所說到的問題點
- 缺點:剛開始會不習慣該package的使用方式(驗證定義的方法),需要自己找文件或github上面的範例,自己摸索一下
- 分享一下自己使用的定義

- 整理一下目前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)