# [影片欣賞] 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)