一、DjangoForm組件介紹
我們之前在html頁面中利用form表單向后端提交數據時,都會寫一些獲取用戶輸入的標簽并且用form標簽把它們包起來。
與此同時我們在好多場景下都需要對用戶的輸入做校驗,比如驗證用戶是否輸入,輸入的長度和格式等是否正確,如果用戶輸入的內容有錯誤就需要在頁面上相應的位置顯示對應的錯誤信息。
Django Form組件就實現了上面所述的功能。
1、生成頁面可用的html標簽(生成input框)
2、對用戶提交的數據進行校驗
3、錯誤信息的展示
4、保留上次的輸入
缺點:只能生成form表單里面的內容,form標簽需要自己建立,提交的按鈕需要自己寫,csrf_token也需要自己添加
二、form組件的字段和插件
創建form類時,主要涉及到【字段】和【插件】,字段用于對用戶請求數據的驗證,插件用于自動生成html
字段創建,默認為input框中的類型為text,可以通過插件來修改成按鈕,多選框等等。
想通過插件修改,需要導入插件模塊:from django.forms import widgets
1、字段
CharField(參數)? 所有的字段都繼承Field類,初始化都是TextFied,改變type都可以通過widgets改變
注:所有的字段類型都可以用CharField,然后通過widgets來修改
ChoiceField()? ?選擇類的字段
通過widgets可改變為:widget = forms.widgets.radioSelect()
radioSelect(單選按鈕)
Select(單選下拉)
SelectMultiple(多選下拉)
checkbox(單選方形按鈕)
checkboxSelectMultiple(多選方形按鈕)
示例:如何使用choiseField字段,及使用數據庫中的數據
class LoginForm(forms.Form):...hobby = forms.fields.ChoiceField(# choices=((1, "籃球"), (2, "足球"), (3, "雙色球"),),choices= models.Hobby.object.all().values_list("id", "name")label="愛好",initial=[1, 3],widget=forms.widgets.CheckboxSelectMultiple()) # 上面choices就用到了從數據庫中獲取數據,但這種方式,每次更改數據庫,都需要重啟項目,前端才能更新信息
關于choice的注意事項:
在使用選擇標簽字段時,需要注意choices的選項可以從數據庫中獲取,但時由于時靜態字段,獲取的值無法實時更新,即
中途向數據庫中添加數據,前端不會實時更新,需要重啟項目的問題,但重啟項目明顯不好,故可通過構造函數,每次查詢時,都先去數據庫中查一下使用的數據庫中的字段。將如下的方法也放到類中即可。
方案一
def __init__(self, *args, **kwargs):super().__init__(*args, **kwargs)self.fields['hobby3'].choices = models.Hobby.objects.all().values_list('id', 'name')
注:其中fields代表的是自定義form類中所有的字段
注:當定義了__init__方法,那么類中對應字段的choices就可以刪掉了
方案二
from django import forms from django.forms import fields from django.forms import models as form_modelclass FInfo(forms.Form):authors = form_model.ModelMultipleChoiceField(queryset=models.NNewType.objects.all()) # 多選# authors = form_model.ModelChoiceField(queryset=models.NNewType.objects.all()) # 單選
DjangoForm所有的內置字段


Fieldrequired=True, 是否允許為空widget=None, HTML插件label=None, 用于生成Label標簽或顯示內容initial=None, 初始值help_text='', 幫助信息(在標簽旁邊顯示)error_messages=None, 錯誤信息 {'required': '不能為空', 'invalid': '格式錯誤'}validators=[], 自定義驗證規則localize=False, 是否支持本地化disabled=False, 是否可以編輯label_suffix=None Label內容后綴CharField(Field)max_length=None, 最大長度min_length=None, 最小長度strip=True 是否移除用戶輸入空白IntegerField(Field)max_value=None, 最大值min_value=None, 最小值FloatField(IntegerField)...DecimalField(IntegerField)max_value=None, 最大值min_value=None, 最小值max_digits=None, 總長度decimal_places=None, 小數位長度BaseTemporalField(Field)input_formats=None 時間格式化 DateField(BaseTemporalField) 格式:2015-09-01 TimeField(BaseTemporalField) 格式:11:12 DateTimeField(BaseTemporalField)格式:2015-09-01 11:12DurationField(Field) 時間間隔:%d %H:%M:%S.%f...RegexField(CharField)regex, 自定制正則表達式max_length=None, 最大長度min_length=None, 最小長度error_message=None, 忽略,錯誤信息使用 error_messages={'invalid': '...'}EmailField(CharField) ...FileField(Field)allow_empty_file=False 是否允許空文件ImageField(FileField) ...注:需要PIL模塊,pip3 install Pillow以上兩個字典使用時,需要注意兩點:- form表單中 enctype="multipart/form-data"- view函數中 obj = MyForm(request.POST, request.FILES)URLField(Field)...BooleanField(Field) ...NullBooleanField(BooleanField)...ChoiceField(Field)...choices=(), 選項,如:choices = ((0,'上海'),(1,'北京'),)required=True, 是否必填widget=None, 插件,默認select插件label=None, Label內容initial=None, 初始值help_text='', 幫助提示ModelChoiceField(ChoiceField)... django.forms.models.ModelChoiceFieldqueryset, # 查詢數據庫中的數據empty_label="---------", # 默認空顯示內容to_field_name=None, # HTML中value的值對應的字段limit_choices_to=None # ModelForm中對queryset二次篩選 ModelMultipleChoiceField(ModelChoiceField)... django.forms.models.ModelMultipleChoiceFieldTypedChoiceField(ChoiceField)coerce = lambda val: val 對選中的值進行一次轉換empty_value= '' 空值的默認值MultipleChoiceField(ChoiceField)...TypedMultipleChoiceField(MultipleChoiceField)coerce = lambda val: val 對選中的每一個值進行一次轉換empty_value= '' 空值的默認值ComboField(Field)fields=() 使用多個驗證,如下:即驗證最大長度20,又驗證郵箱格式fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),])MultiValueField(Field)PS: 抽象類,子類中可以實現聚合多個字典去匹配一個值,要配合MultiWidget使用SplitDateTimeField(MultiValueField)input_date_formats=None, 格式列表:['%Y--%m--%d', '%m%d/%Y', '%m/%d/%y']input_time_formats=None 格式列表:['%H:%M:%S', '%H:%M:%S.%f', '%H:%M']FilePathField(ChoiceField) 文件選項,目錄下文件顯示在頁面中path, 文件夾路徑match=None, 正則匹配recursive=False, 遞歸下面的文件夾allow_files=True, 允許文件allow_folders=False, 允許文件夾required=True,widget=None,label=None,initial=None,help_text=''GenericIPAddressFieldprotocol='both', both,ipv4,ipv6支持的IP格式unpack_ipv4=False 解析ipv4地址,如果是::ffff:192.0.2.1時候,可解析為192.0.2.1, PS:protocol必須為both才能啟用SlugField(CharField) 數字,字母,下劃線,減號(連字符)...UUIDField(CharField) uuid類型
2、參數
label? ? ? ?顯示的文本,如input框前的“用戶名:”
initail? ? ? ?初始化的值,即默認值
required? ? 是否必填,required=True
help_text? ?在input框后的提示信息
error_messages?={"min_length": '太短了',"required": '此數字必須填寫'} ?定義字段,驗證不通過時提示的錯誤信息,格式為字典? ?
widget = forms.widgets.PasswrodInput(attrs={'class': "form_contronl"})? ? 加插件,可以改變input框的類型,也可以為input標簽加各種屬性,如class,style等等。
validators = [驗證器1,]? ?里面可以時django默認的驗證器,也可以自己定義驗證方法,然后放入其中
?3、驗證
1、djangoForm組件字段驗證順序
form_obj.is_vaild()==> 通過則將字段加入clean_data字典集合中,如果不通過,則報錯,不會將對應字段名加入到字典中,故后面自定義的方法是拿不到字段的
|
django自己的clean(self) 方法??
|
自定義的clean_字段名 的方法 ==>? 系統會通過反射檢索是否自定義以clean_開頭的方法,如果有就執行
|
自定義的clean(self) 方法? ==> 這個方法是全局的,對所有的字段校驗,本質是復寫django的clean方法
2、form校驗的幾種方式
validators = [驗證器1,]?
2.1、內置的驗證
max_length/min_length
required
2.2、自定義驗證器
方式一、
-
from django.core.Validators import RegexCalidator 有很多這樣的類,這個是正則驗證的模塊validators=[RegexValidator(r'^[a-zA-Z][a-zA-Z0-9_@]{4,18}', '4~18個字符,首字符為字母')]格式為:前部分為正則,后半部分為提示信息
方式二、
-
# 定義函數
from django.core.exceptions import ValidationErrordef validator_check(value):# 定義方法,設計規則,用于驗證if 'sex' in value:raise ValidationError('含有非法字符')
然后在類中的字段 validators中調用方法即可,validators = [validator_check] # 不用加括號
2.3、利用鉤子函數自定義驗證
?
鉤子,框架級別的東西,流程過程中,預留一些空白,有,就進行驗證,沒有就過
?
鉤子函數,利用反射hasattr,拿到方法的字符串,有就執行,沒有就過
?
局部:單獨為某個字段設置? ? ? ? ? ? ?
-
clean_字段名(self):通過校驗:返回當前校驗的值 【for循環校驗】不通過:raise ValidationError('不通過') 報錯為什么報錯? 因為源碼中通過try捕獲到錯誤,放入到err_diact中,繼續執行其它的程序
全局:所有的字段都校驗完了,才回執行,里面只有通過的字段
-
clean(self):通過校驗:返回 self.cleaned_data不通過:self.add_error('re_wd', '兩次密碼不一致')raise ValidationError('不通過') 報錯
?
PS:form表單中,如果在form標簽中加入屬性novalidate ,則前端不會做校驗。
三、定義一個form組件
1、創建
可在視圖中創建,但最好在app下單獨建立一個py文件,如myform.py
1.1、導入模塊 from? django? import? forms
1.2、定義form類,繼承forms.Form(只能定義若干個數據庫表中的字段)? 或 forms.Models(可批量定義)
1.3、定義字段(可以是數據庫中的,那么值就需要通過orm操作獲取數據庫中的值)
示例: ?
from django import forms from django.forms import widgets # 插件的意思,就是改變【字段】input的類型,如checkbox等等 from django.core.validators import RegexValidator # 用于校驗,正則class MyForms(forms.Form):username = forms.CharField(label='用戶名',help_text='人生庫端4444', # input框右邊的提示語min_length=4,initial='不能小于5個字符',error_messages={'min_length': '長度不符合規則'},widget=widgets.TextInput(attrs={'class': 'form-control'}),validators=[RegexValidator(r'^[a-zA-Z][a-zA-Z0-9_@]{4,18}', '4~18個字符,首字符為字母')])password = forms.CharField(label='密碼',# 密碼默認明文顯示,可通過插件修改widget=widgets.PasswordInput(attrs={'class': 'form-control'}),hobby2 = forms.ChoiceField(label='love:下拉多選', initial=3, choices=((1, 'lan'), (2, 'qiu'), (3, 'dd')),widget=widgets.SelectMultiple(attrs={'class': 'form-control'}) # 下拉多選 ) )
?2、使用
2.1、后端使用:views.py
-
from app_name.myform import MyFormdef view_func(request):form_obj = MyForm() 實例化if request.method == 'POSt':form_obj = MyForm(request.POST) 將接收到的數據傳進去form_obj.is_vaild() 做校驗,后臺自動完成form_obj.cleaned_data 字典,已經通過校驗的所有字段,沒有通過,不會放入到里面return render(request, 'index.html' , {'form_obj': form_obj})注:兩次實例化變量名都為form_obj這樣優化代碼,不然post請求驗證不過,返回錯誤信息,還要寫一個return。
2.2、前端使用
-
{{form_obj}} 直接生成所有的字段,不過沒有格式
{{form_obj.name.lable_tag}} 直接生成一個lable的標簽
{{form_obj.as_p}} 所有的字段生成input框,每個input外面時p標簽生成的html代碼如下以as_p為例:<P><lable for='{{form_obj.name.id_for_label}}'>{{form_obj.name.label}}</lable><input></p>{{form_obj.as_table/as_ui}}{{form_obj.name}} name字段生成的input框,框的類型type和定義時charField定義有關{{form_obj.name.label}} name的標簽顯示的文字,如用戶名{{form_obj.name.id_for_label}} input框的id,用于與label標簽的for關聯{{form_obj.name.errors}} 該字段后臺驗證返回來的所有的錯誤{{form_obj.name.errors.0}} 該字段后臺驗證返回來的所有的錯誤的第一個錯誤,所有的錯誤放在一個字典中{{form_obj.errors}} form表單的所有的錯誤
四、應用bootstrap樣式,如何使用form組件
方式一:一個一個字段寫
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><meta http-equiv="x-ua-compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1"><link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css"><title>login</title> </head> <body> <div class="container"><div class="row"><form action="/login2/" method="post" novalidate class="form-horizontal">{% csrf_token %}<div class="form-group"><label for="{{ form_obj.username.id_for_label }}"class="col-md-2 control-label">{{ form_obj.username.label }}</label><div class="col-md-10">{{ form_obj.username }}<span class="help-block">{{ form_obj.username.errors.0 }}</span></div></div><div class="form-group"><label for="{{ form_obj.pwd.id_for_label }}" class="col-md-2 control-label">{{ form_obj.pwd.label }}</label><div class="col-md-10">{{ form_obj.pwd }}<span class="help-block">{{ form_obj.pwd.errors.0 }}</span></div></div><div class="form-group"><label class="col-md-2 control-label">{{ form_obj.gender.label }}</label><div class="col-md-10"><div class="radio">{% for radio in form_obj.gender %}<label for="{{ radio.id_for_label }}">{{ radio.tag }}{{ radio.choice_label }}</label>{% endfor %}</div></div></div><div class="form-group"><div class="col-md-offset-2 col-md-10"><button type="submit" class="btn btn-default">注冊</button></div></div></form></div> </div><script src="/static/jquery-3.2.1.min.js"></script> <script src="/static/bootstrap/js/bootstrap.min.js"></script> </body> </html>
?方式二:通過for循環,直接通過field即可
<div class="panel panel-primary"><div class="panel-heading ">編輯頁面</div><div class="panel-body"><form class="form-horizontal " method="post">{% csrf_token %}{% for field in customer_form_obj %}<div class="form-group "><label for="{{ field.id_for_label }}" class="col-sm-2 control-label">{{ field.label }}</label><div class="col-sm-10 ">{{ field }}</div><span class="help-block">{{ field.errors.0 }}</span></div>{% endfor %}<button type="submit" class="btn btn-lg btn-success btn-block center-block">提交</button></form></div> </div>
五、form組件批量添加樣式
可通過重寫form類的init方法來實現。?


class LoginForm(forms.Form):username = forms.CharField(min_length=8,label="用戶名",initial="張三",error_messages={"required": "不能為空","invalid": "格式錯誤","min_length": "用戶名最短8位"}...def __init__(self, *args, **kwargs):super(LoginForm, self).__init__(*args, **kwargs)for field in iter(self.fields):self.fields[field].widget.attrs.update({'class': 'form-control'})
如何限制Form中下來框中值的選取范圍?
1.? 可以將model的實例以參數instance=obj傳入,在form組件中引用self.instance 來操作orm,進行限制
2. 在post請求時,data= request.POST ,instance=obj?
- views.py # 客戶的增刪改查 def customer_change(request, edit_id=None):"""要求添加完成后還回到之前的頁面"""if re.match('^/del_customer/(\d+)/$', request.path_info):check_obj = models.Customer.objects.filter(id=edit_id)if not check_obj:return HttpResponse("目標不存在,非法操作")check_obj.delete()next_url = request.GET.get('next_url', '')return redirect(next_url)msg = ""edit_obj = models.Customer.objects.filter(id=edit_id).first()customer_form_obj = forms.CustomerForm(instance=edit_obj)if request.method == "POST":next_url = request.GET.get('next_url', '')customer_form_obj = forms.CustomerForm(data=request.POST, instance=edit_obj)if customer_form_obj.is_valid():customer_form_obj.save()return redirect(to=next_url)else:msg = "請按要求填寫信息"- forms.py class ConsultRecordForm(BaseStyle):def __init__(self, *args, **kwargs):super().__init__(*args, **kwargs)# 限制添加客戶跟進記錄,只能選擇自己的客戶self.fields['customer'].widget.choices = [(i.id, i) for i in self.instance.consultant.customers.all()]# 限制添加客戶記錄,銷售下拉只能是自己self.fields['consultant'].widget.choices = [(self.instance.consultant.id, self.instance.consultant), ]class Meta:model = models.ConsultRecordfields = '__all__'exclude = ['delete_status']widgets = {'consultant': forms.widgets.Select()}
六、ModelForm
form與model的終極結合,可直接使用models中的所有字段
class BookForm(forms.ModelForm):class Meta:model = models.Bookfields = "__all__"labels = {"title": "書名","price": "價格"}widgets = {"password": forms.widgets.PasswordInput(attrs={"class": "c1"}),}
class Meta:下常用參數:
model = models.Student # 對應的Model中的類 fields = "__all__" # 字段,如果是__all__,就是表示列出所有的字段 exclude = None # 排除的字段 labels = None # 提示信息 help_texts = None # 幫助提示信息 widgets = None # 自定義插件 error_messages = None # 自定義錯誤信息