django
用繼承 TextField 的方式,達成底下目標:
https://docs.djangoproject.com/en/2.2/howto/custom-model-fields/
deconstruct()
& clone()
所有 custom field 都需要設定 makemigrations 的時候怎麼自動在 migration file 裡產生自己的 field。使用的 method 為 deconstruct
。
deconstruct
回傳一個 4-tuple:
name
: the field's attribute name,直接繼承就可以path
: 表示要放在 migration file 裡的 model pathargs
, kwargs
: 放在 migration file 裡怎麼 initialize 這個 field因為我們的需求只是在 django 使用端,db 存的完全跟 TextField 一樣,所以 deconstruct 只要能建出 TextField 就好(path 回傳 django.models.db.TextField
)。可以把跟 db 無關的 kwargs 都拿掉,才不會不需要 migrate 的時候一直產生新的 migration files。假設 custom field 是直接寫在 app 裡,修改 path 也保證到時如果整個 custom field 換位置、改名字不會讓原本的 migration files 找不到 import path 而需要手動修改。
但是因為預設 field.clone()
會使用到 deconstruct 的 args
, kwargs
,用來建出自己。所以如果 deconstruct
回傳的 args
, kwargs
不能建出自己,就需要再修改 clone
method。
deconstruct
path 不指到自己還不太確定會不會有什麼問題,因為目前個人使用是只有發現會被用在 makemigrations,還沒遇到什麼問題。
from_db_value()
& get_prep_value()
文件中寫到 3 個轉換用的 methods,其實是在底下三者之間轉換:
Form => Attribute <=> DB
to_python
: Form => Attributefrom_db_value
: DB => Attributeget_prep_value
: Attribute => DB所以如果只是要達成目標 1 不需要寫 to_python
,只要完成和 db 之間的轉換: from_db_value
和 get_prep_value
即可。
就可以像這樣使用:
formfield
or to_python
我們繼承的 models.TextField
的預設 form field 是 forms.CharField
(預設 widget 為 textarea)。因為 forms.CharField
會直接把 attribute 設定成前端輸入的 str
,所以沒辦法存成 dict
,永遠只能存 str
。
在看要怎麼解決這個問題之前,需要先了解 form data 轉換成的過程 model attribute 的過程(ModelForm.full_clean
),大致如下:
所以要達成把 textarea 裡的 text 當成 json string 存到 db 裡,有兩個做法:修改 formfield
or 使用 to_python
。
修改 formfield 可以在第一步 formfield.clean(value)
之後就將 value
轉換成 dict
設定到 obj.my_field
中。
首先繼承 forms.CharField
,在 field.clean
的時候把 value 轉換成 dict
:
然後使用 formfield
修改 custom model field 預設的 form fiel。但是因為 TextField.to_python
會將任何 value 轉換成 str
,所以也需要修改:
form.full_clean()
的過程就類似這樣:
=(str)=> Form =(dict)=> Attribute =(str)=> DB
如果使用 to_python
就表示在 form set attribute 時,只允許 str
的值,但是在 Model.full_clean
時利用 to_python
將 attribute 轉換成我們想要的格式:
這樣在使用 model.full_clean
的時候,這個 field 的值一定要是 str
or None
。也就是 form.full_clean()
的過程類似這樣:
=(str)=> Form =(str)=> Attribute =(dict)=> Attribute =(str)=> DB
如果希望有這樣的效果:
可以模仿 FileField 的方式,在 contribute_to_class 加上 descriptor:
但有一個問題是,因為 json 理論上也可以是 str
,所以如果要設定成 str
就只能這樣設定:
另外,寫了 attribute 之後,要達成目標 2 要做的事情也不能省略。因為雖然在這邊 get attribute 會把 str 轉換成 dict,但是這個步驟不會做 validation,應該只在 formfield.clean()
和 to_python()
丟出 validation error。
所以總結來說,如果沒有特殊原因,這樣做應該是不太適合。
from_db_value(str)
=> value_to_string(obj)
to_python(value)
=> get_prep_value(value)