django是python語言快速實現web服務的大殺器,其開發效率可以非常的高!但因為秉承了語言的靈活性,django框架又太靈活,以至於想實現任何功能都有種“條條大路通羅馬”的感覺。這么多種選擇放在一起,如何分出高下?我想此時的場景下就兩個標准:1、相同的功能用最少的代碼實現(代碼少BUG也會少);2、相對最易於理解,從而易於維護和擴展。書歸正傳,web服務允許用戶輸入,基本上要靠表單。而django對表單的支持力度非常大,我們用不着在瀏覽器端的html文件里寫大量<form>代碼,再到web端去匹配form里的id/name/value、驗證規則,再與持久層數據庫比較並做操作。我們需要完成的工作非常少,可以沒有相似的重復代碼。有些復雜的場景,會要求一個表單的內容存放到多張表里,本文將通過4個部分,闡述它的實現方法。
1、django基礎表單的功能
定義一個表單非常簡單,繼承類django.forms.Form即可,例如:
這個表單類可以生成HTML形式的form,可以從request.POST中解析form到ProjectForm類實例。怎么做到的呢?
看下django.forms.Form定義:
注釋說得很清楚,Form這個類就是為了實現declarative syntax的,也就是說,繼承了Form后,我們直觀的表達ProjectForm里要有一個Field名叫name,不關心其語法實現,而通過Form多繼承中的DeclarativeFieldsMetaclass語法糖,將會把name弄到類實例的self.fields里。
我們重點關注表單的BaseForm類,它實現了基本的邏輯。截選了一小段對接下來的陳述有意義的代碼,做一個簡單的注釋。
所以,基本表單的功能看BaseForm已經足夠了。
2、從模型創建表單
django對於MVC中的C與M間的映射是非常體貼的,集中體現中Model模型中(比如模型的權限與用戶認證)。那么,一個模型代表着RDS中的一張表,模型的實例代表着關系數據庫中的一行,而form如何與一行相對應呢?
定義一個模型引申出的表單非常簡單,例如:
在model中告訴django模型是誰,在fields中告訴django需要在表單中創建哪些字段。django會有一個django.db.models.Field到django.forms.Field的轉換規則,此時會生成Form。我們看看ModelForm是什么樣的:
類似Form類,ModelFormMetaclass就是語法糖,我們重點看BaseModelForm類:
所以,對於ModelForm我們可以傳入instance參數初始化表單,可以調用save()方法直接將從html里得到的表單數據持久化到數據庫中。而我們只需要幾十行代碼就可以完成這么多工作。
3、通用視圖
django.views.generic.ListView和django.views.generic.edit下的CreateView, UpdateView, DeleteView都是通用視圖。即,我們又可以通過它們,把很多重復的工作交給django完成,又可以少寫很多代碼完成同樣的功能了。這里僅以CreateView為例說明,因為它相對最復雜,接下來的多ModelForm的提交也是在CreateView上進行的。
通用視圖使用時,只需要承繼后,再設置model或者form_class即可。比如CreateView就會由django自動的把頁面上POST出的form數據解析到model生成的表單(或者form_calss指定的ModelForm類型表單),同時調用表單的save方法將數據添加到模型對應的數據庫表中。當然GET請求時會生成空form到頁面上。可以看到,除去定義model或者form類外,幾行代碼就可以搞定這么多事。我們看看CreateView的繼承關系:
簡單介紹下CreateView通用視圖中每個父類的作用。
- View是所有視圖類的父類,根據方法名分發請求到具體的get或者post等方法,提供as_view方法。
- TemplateResponseMixin提供render_to_response方法將響應通過context上下文在模板上渲染。
- ContextMixin在context上下文中加入'view'元素,值為self實例。
- ProcessFormView在GET請求上渲染表單,在POST請求上解析form到表單實例。注意,它會在post請求中判斷表單是否可用,is_valid為真時,會調用form_valid方法,因此,重寫form_valid方法是第4部分處理多model到一個form的關鍵。
- FormMixin允許處理表單,可指定form_class為某個表單。
- SingleObjectMixin生成context上下文,同時根據model模型名稱生成object並添加到上下文中的'object'元素。
- ModelFormMixin提供在請求中處理modelform的方式。
- SingleObjectTemplateResponseMixin幫助TemplateResponseMixin提供模板。
所以,在用CreateView、一個模型、一個模板實現添加一行記錄的功能時是多么簡單,因為這些父類會自動生成object,渲染到模板,解析form表單,save到數據庫中。所以,從模型創建出的表單ModelForm,配合上通用視圖后,威力巨大!!
4、多個ModelForm在一個form里提交
終於可以回到本文的主題了。CreateView默認是處理一個Model模型、一個ModelForm表單的,然而,很多時候為了解耦,會把一張表拆成多張表,通過id關聯在一起。在django的模型中就體現為ForeignKey、ManyToManyField或者OneToOneField。而在業務邏輯上,需要體現為一張表單,對應着數據庫里的多張表。
例如,我們希望錄入合同,其中合同Model中還有地址Model和項目Model,而項目Model中又有地址Model,等等。
當然,我們有很多種實現的方案,但是,前面三部分說了那么多,不是浪費口水的。我們已經有了通用視圖+ModelForm這樣的利器,難道還需要手動去寫Form表單?我們已經習慣了在Model里定義好類型和有點注釋作用還能當label的verbose_name,還需要在forms.Form里再來一遍?還需要在視圖中寫這么通用的邏輯代碼嗎?當然不用。
inlineformset_factory是一種方案,但它限制太多,而且有些晦澀,我個人感覺是不太好用的。
那么,從第1部分我介紹的Form里的prefix,以及第3部分里類圖中的ProcessFormView允許重定義form_valid,以及第2部分中ModelForm的save方法的行為控制,解決方案已經一目了然了。
拿上面提到的例子來說,我們創建合同時,指明了項目,包括項目地址和合同簽訂地址,這涉及到三張表和四條記錄(地址表有兩條)。
我們三張表的模型如下:
接着,定義ModelForm表單,這非常簡單:
再寫視圖,這里要重寫2個方法:
最后寫模板:
至此,我們可以只用幾十行代碼就完成復雜的功能,代碼邏輯也清晰可控。
從這篇文章里也可以看得出,django實在是快速開發網站的必備神器!當然,快速不代表不能夠支撐大並發的應用,instagram這個很火的服務就是用django寫的。由於python和django過於靈活,都將要求django的開發者們唯有更資深才能寫出生產環境下的服務。