91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

解決django中ModelForm多表單組合的問題

發布時間:2020-09-16 23:31:16 來源:腳本之家 閱讀:436 作者:陶輝 欄目:開發技術

django是python語言快速實現web服務的大殺器,其開發效率可以非常的高!但因為秉承了語言的靈活性,django框架又太靈活,以至于想實現任何功能都有種“條條大路通羅馬”的感覺。這么多種選擇放在一起,如何分出高下?我想此時的場景下就兩個標準:

1、相同的功能用最少的代碼實現(代碼少BUG也會少);

2、相對最易于理解,從而易于維護和擴展。書歸正傳,web服務允許用戶輸入,基本上要靠表單。而django對表單的支持力度非常大,我們用不著在瀏覽器端的html文件里寫大量<form>代碼,再到web端去匹配form里的id/name/value、驗證規則,再與持久層數據庫比較并做操作。我們需要完成的工作非常少,可以沒有相似的重復代碼。有些復雜的場景,會要求一個表單的內容存放到多張表里,本文將通過4個部分,闡述它的實現方法。

1、django基礎表單的功能

定義一個表單非常簡單,繼承類django.forms.Form即可,例如:

class ProjectForm(forms.Form):
  name = forms.CharField(label='項目名稱', max_length=20)

這個表單類可以生成HTML形式的form,可以從request.POST中解析form到ProjectForm類實例。怎么做到的呢?

看下django.forms.Form定義:

class Form(six.with_metaclass(DeclarativeFieldsMetaclass, BaseForm)):
  "A collection of Fields, plus their associated data."
  # This is a separate class from BaseForm in order to abstract the way
  # self.fields is specified. This class (Form) is the one that does the
  # fancy metaclass stuff purely for the semantic sugar -- it allows one
  # to define a form using declarative syntax.
  # BaseForm itself has no way of designating self.fields.

注釋說得很清楚,Form這個類就是為了實現declarative syntax的,也就是說,繼承了Form后,我們直觀的表達ProjectForm里要有一個Field名叫name,不關心其語法實現,而通過Form多繼承中的DeclarativeFieldsMetaclass語法糖,將會把name弄到類實例的self.fields里。

我們重點關注表單的BaseForm類,它實現了基本的邏輯。截選了一小段對接下來的陳述有意義的代碼,做一個簡單的注釋。

class BaseForm(object):
  def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
         initial=None, error_class=ErrorList, label_suffix=None,
         empty_permitted=False, field_order=None, use_required_attribute=None):
    #data參數用于接收request.POST字典,如果是GET方法就不傳
	self.data = data or {}
	#files用于接收request.FILES,也就是處理上傳文件
	self.files = files or {}
	#本篇文章的重點在于多個表單集成到一個form中,此時為防止有同名的field,需要加prefix前綴
    if prefix is not None:
      self.prefix = prefix
	#GET顯示表單時,如果要顯示初始值,請用initial參數
    self.initial = initial or {}
 
  #模板中顯示{{form}}時,默認是以<table></table>顯示的
  def __str__(self):
    return self.as_table()
 
  #如果模板中不想寫重復代碼,只以固定的格式來顯示每一個field,那么就用{% for field, val in form %}來遍歷處理吧
  def __iter__(self):
    for name in self.fields:
      yield self[name]
 
  #如果傳入了prefix參數,html中每個field的name和id里都會加上prefix前綴
  def add_prefix(self, field_name):
    return '%s-%s' % (self.prefix, field_name) if self.prefix else field_name
 
  #模板中以html格式顯示form就靠這個方法
  def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_on_separate_row):
    "Helper function for outputting HTML. Used by as_table(), as_ul(), as_p()."
    top_errors = self.non_field_errors() # Errors that should be displayed above all fields.
    output, hidden_fields = [], []
 
  #除了默認的table方式顯示外,還可以<ul><li>或者<p>方式顯示
  def as_table(self):
    "Returns this form rendered as HTML <tr>s -- excluding the <table></table>."
  def as_ul(self):
    "Returns this form rendered as HTML <li>s -- excluding the <ul></ul>."
  def as_p(self):
    "Returns this form rendered as HTML <p>s."

所以,基本表單的功能看BaseForm已經足夠了。

2、從模型創建表單

django對于MVC中的C與M間的映射是非常體貼的,集中體現中Model模型中(比如模型的權限與用戶認證)。那么,一個模型代表著RDS中的一張表,模型的實例代表著關系數據庫中的一行,而form如何與一行相對應呢?

定義一個模型引申出的表單非常簡單,例如:

class ProjectForm(ModelForm):
  class Meta:
    model = Project
    fields = ['approvals','manager','name','fund_rource','content','range',]

在model中告訴django模型是誰,在fields中告訴django需要在表單中創建哪些字段。django會有一個django.db.models.Field到django.forms.Field的轉換規則,此時會生成Form。我們看看ModelForm是什么樣的:

class ModelForm(six.with_metaclass(ModelFormMetaclass, BaseModelForm)):
  pass

類似Form類,ModelFormMetaclass就是語法糖,我們重點看BaseModelForm類:

class BaseModelForm(BaseForm):
  def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
         initial=None, error_class=ErrorList, label_suffix=None,
         empty_permitted=False, instance=None, use_required_attribute=None):
    opts = self._meta
	#相比較BaseForm,多了instance參數,它等價于Model模型的一個實例
    if instance is None:
      #不傳instance參數,則會新構造model對象
      self.instance = opts.model()
      object_data = {}
    else:
      self.instance = instance
      object_data = model_to_dict(instance, opts.fields, opts.exclude)
	#此時傳遞了initial也一樣可以生效,同時還會設置到Model中
    if initial is not None:
      object_data.update(initial)
 
  def save(self, commit=True):
	#默認commit是True,此時就會保存Model實例到數據庫
    if commit:
      self.instance.save()
	#同時保存many-to-many字段對應的關系表
      self._save_m2m()
    else:
	#注意,本篇文章主要用到commit=False這個參數,它會返回Model實例,允許我們在修改instance后,在instance上再調用save方法
      self.save_m2m = self._save_m2m
    return self.instance

所以,對于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的繼承關系:

解決django中ModelForm多表單組合的問題

簡單介紹下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方法的行為控制,解決方案已經一目了然了。

拿上面提到的例子來說,我們創建合同時,指明了項目,包括項目地址和合同簽訂地址,這涉及到三張表和四條記錄(地址表有兩條)。

我們三張表的模型如下:

class PrimeContract(models.Model):
  address = models.ForeignKey(Address, related_name="prime_contract_address", verbose_name="address")
  project = models.ForeignKey(Project, related_name="prime_contract", verbose_name="project")
class Project(models.Model):
  address = models.ForeignKey(Address, related_name="project_address", verbose_name="project address")
class Address(models.Model):
  pass

接著,定義ModelForm表單,這非常簡單:

class AddressForm(ModelForm):
  class Meta:
    model = Address
 fields = ...
class ProjectForm(ModelForm):
  class Meta:
    model = Project
 fields = ...
class PrimeContractForm(ModelForm):
  class Meta:
    model = PrimeContract
 fields = ...

再寫視圖,這里要重寫2個方法:

class PrimeContractAdd(CreateView):
  success_url = ...
  template_name = ...
  form_class = PrimeContractForm
  def get_context_data(self, **kwargs):
    context = super(PrimeContractAdd, self).get_context_data(**kwargs)
    #SingleObjectMixin父類只會處理PrimeContractForm表單,另外三條數據庫記錄對應的表單我們要自己處理了,此時prefix派上用場了,因為Field重名是百分百的事
    if self.request.method == 'POST':
      contractAddressForm = AddressForm(self.request.POST, prefix='contractAddressForm')
      projectAddressForm = AddressForm(self.request.POST, prefix='projectAddressForm')
      projectForm = ProjectForm(self.request.POST, prefix='projectForm')
    else:
      contractAddressForm = AddressForm(prefix='contractAddressForm')
      projectAddressForm = AddressForm(prefix='projectAddressForm')
      projectForm = ProjectForm(prefix='projectForm')
    #注意要把自己處理的表單放到context上下文中,供模板文件使用
    context['contractAddressForm'] = contractAddressForm
    context['projectAddressForm'] = projectAddressForm
    context['projectForm'] = projectForm
    return context
  
	#重寫form_valid,父類ProcessFormView會在PrimeContractForm表單is_valid方法返回True時調用該方法
  def form_valid(self, form):
	  #首先我們要獲取到PrimeContractForm表單對應的模型,此時是不能save的,因為外鍵project和address對應的數據庫記錄還沒有創建,所以commit傳為False
    contract = form.save(commit=False)
		#獲取上面get_context_data方法中在POST里得到的表單
    context = self.get_context_data()
		#按照四條數據庫記錄的順序依次的創建(調用save方法)、主鍵賦到下一條記錄的外鍵中、下一次記錄創建(save)
    projectAddress = context['projectAddressForm'].save()
		#從項目表單中獲取到模型,先把地址的id賦到外鍵上再保存
    project = context['projectForm'].save(commit=False)
    project.address = projectAddress
    project.save()
    contractAddress = context['contractAddressForm'].save()
		#將合同模型中的address和project都設置好后再保存
    contract.address = contractAddress
    contract.project = project
    contract.save()
    
    return super(PrimeContractAdd, self).form_valid(form)

最后寫模板:

#這三個表單我們手動處理過的
{{ contractAddressForm }}
{{ projectAddressForm }}
{{ projectForm }}
#這是FormMixin父類幫我們生成的
{{ form }}

至此,我們可以只用幾十行代碼就完成復雜的功能,代碼邏輯也清晰可控。

從這篇文章里也可以看得出,django實在是快速開發網站的必備神器!當然,快速不代表不能夠支撐大并發的應用,instagram這個很火的服務就是用django寫的。由于python和django過于靈活,都將要求django的開發者們唯有更資深才能寫出生產環境下的服務。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

商水县| 精河县| 禄丰县| 阜新市| 巴东县| 清河县| 房产| 陆川县| 辽阳县| 红原县| 新乡县| 许昌市| 宿州市| 宁安市| 通化县| 都安| 保康县| 同心县| 平邑县| 家居| 尼木县| 顺昌县| 岐山县| 青河县| 五常市| 嘉祥县| 孝义市| 松潘县| 陆良县| 平乡县| 于田县| 长武县| 登封市| 安图县| 岗巴县| 兰溪市| 景谷| 德州市| 和林格尔县| 新晃| 聊城市|