一 Form介紹 我們之前在HTML頁面中利用form表單向後端提交數據時,都會寫一些獲取用戶輸入的標簽並且用form標簽把它們包起來。 與此同時我們在好多場景下都需要對用戶的輸入做校驗,比如校驗用戶是否輸入,輸入的長度和格式等正不正確。如果用戶輸入的內容有錯誤就需要在頁面上相應的位置顯示對應的錯誤 ...
一 Form介紹
我們之前在HTML頁面中利用form表單向後端提交數據時,都會寫一些獲取用戶輸入的標簽並且用form標簽把它們包起來。
與此同時我們在好多場景下都需要對用戶的輸入做校驗,比如校驗用戶是否輸入,輸入的長度和格式等正不正確。如果用戶輸入的內容有錯誤就需要在頁面上相應的位置顯示對應的錯誤信息.。
Django form組件就實現了上面所述的功能。
總結一下,其實form組件的主要功能如下:
生成頁面可用的HTML標簽
對用戶提交的數據進行校驗
保留上次輸入內容
普通方式手寫註冊功能
views.py
# 註冊
def register(request):
error_msg = ""
if request.method == "POST":
username = request.POST.get("name")
pwd = request.POST.get("pwd")
# 對註冊信息做校驗
if len(username) < 6:
# 用戶長度小於6位
error_msg = "用戶名長度不能小於6位"
else:
# 將用戶名和密碼存到資料庫
return HttpResponse("註冊成功")
return render(request, "register.html", {"error_msg": error_msg})
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>註冊頁面</title>
</head>
<body>
<form action="/reg/" method="post">
{% csrf_token %}
<p>
用戶名:
<input type="text" name="name">
</p>
<p>
密碼:
<input type="password" name="pwd">
</p>
<p>
<input type="submit" value="註冊">
<p style="color: red">{{ error_msg }}</p>
</p>
</form>
</body>
</html>
使用form組件實現註冊功能
views.py
先定義好一個RegForm類:
from django import forms
# 按照Django form組件的要求自己寫一個類
class RegForm(forms.Form):
name = forms.CharField(label="用戶名") #form欄位的名稱寫的是什麼,那麼前端生成input標簽的時候,input標簽的name屬性的值就是什麼
pwd = forms.CharField(label="密碼")
再寫一個視圖函數:
# 使用form組件實現註冊方式
def register2(request):
form_obj = RegForm()
if request.method == "POST":
# 實例化form對象的時候,把post提交過來的數據直接傳進去
form_obj = RegForm(data=request.POST) #既然傳過來的input標簽的name屬性值和form類對應的欄位名是一樣的,所以接過來後,form就取出對應的form欄位名相同的數據進行form校驗
# 調用form_obj校驗數據的方法
if form_obj.is_valid():
return HttpResponse("註冊成功")
return render(request, "register2.html", {"form_obj": form_obj})
login2.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>註冊2</title>
</head>
<body>
<form action="/reg2/" method="post" novalidate autocomplete="off"> #novalidate 告訴前端form表單,不要對輸入的內容做校驗
{% csrf_token %} #{{ form_obj.as_p }} 直接寫個這個,下麵的用戶名和密碼的標簽不自己寫,你看看效果
<div>
<label for="{{ form_obj.name.id_for_label }}">{{ form_obj.name.label }}</label>
{{ form_obj.name }} {{ form_obj.name.errors.0 }} #errors是這個欄位所有的錯誤,我就用其中一個錯誤提示就可以了,再錯了再提示,並且不是給你生成ul標簽了,單純的是錯誤文本 {{ form_obj.errors }} #這是全局的所有錯誤,找對應欄位的錯誤,就要form_obj.欄位名
</div>
<div>
<label for="{{ form_obj.pwd.id_for_label }}">{{ form_obj.pwd.label }}</label>
{{ form_obj.pwd }} {{ form_obj.pwd.errors.0 }}
</div>
<div>
<input type="submit" class="btn btn-success" value="註冊">
</div>
</form>
</body>
</html>
看網頁效果發現 也驗證了form的功能:
前端頁面是form類的對象生成的 -->生成HTML標簽功能
當用戶名和密碼輸入為空或輸錯之後 頁面都會提示 -->用戶提交校驗功能
當用戶輸錯之後 再次輸入 上次的內容還保留在input框 -->保留上次輸入內容
二 Form常用欄位與插件
創建Form類時,主要涉及到 【欄位】 和 【插件】,欄位用於對用戶請求數據的驗證,插件用於自動生成HTML;
initial
初始值,input框裡面的初始值。
class LoginForm(forms.Form):
username = forms.CharField(
min_length=8,
label="用戶名",
initial="張三" # 設置預設值
)
pwd = forms.CharField(min_length=6, label="密碼")
error_messages
重寫錯誤信息。
class LoginForm(forms.Form):
username = forms.CharField(
min_length=8,
label="用戶名",
initial="張三",
error_messages={
"required": "不能為空",
"invalid": "格式錯誤",
"min_length": "用戶名最短8位"
}
)
pwd = forms.CharField(min_length=6, label="密碼")
password
class LoginForm(forms.Form):
...
pwd = forms.CharField(
min_length=6,
label="密碼",
widget=forms.widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True) #這個密碼欄位和其他欄位不一樣,預設在前端輸入數據錯誤的時候,點擊提交之後,預設是不保存的原來數據的,但是可以通過這個render_value=True讓這個欄位在前端保留用戶輸入的數據
)
radioSelect
單radio值為字元串
class LoginForm(forms.Form):
username = forms.CharField( #其他選擇框或者輸入框,基本都是在這個CharField的基礎上通過插件來搞的
min_length=8,
label="用戶名",
initial="張三",
error_messages={
"required": "不能為空",
"invalid": "格式錯誤",
"min_length": "用戶名最短8位"
}
)
pwd = forms.CharField(min_length=6, label="密碼")
gender = forms.fields.ChoiceField(
choices=((1, "男"), (2, "女"), (3, "保密")),
label="性別",
initial=3,
widget=forms.widgets.RadioSelect()
)
單選Select
class LoginForm(forms.Form):
...
hobby = forms.fields.ChoiceField( #註意,單選框用的是ChoiceField,並且裡面的插件是Select,不然驗證的時候會報錯, Select a valid choice的錯誤。
choices=((1, "籃球"), (2, "足球"), (3, "雙色球"), ),
label="愛好",
initial=3,
widget=forms.widgets.Select()
)
多選Select
class LoginForm(forms.Form):
...
hobby = forms.fields.MultipleChoiceField( #多選框的時候用MultipleChoiceField,並且裡面的插件用的是SelectMultiple,不然驗證的時候會報錯。
choices=((1, "籃球"), (2, "足球"), (3, "雙色球"), ),
label="愛好",
initial=[1, 3],
widget=forms.widgets.SelectMultiple()
)
單選checkbox
class LoginForm(forms.Form):
...
keep = forms.fields.ChoiceField(
label="是否記住密碼",
initial="checked",
widget=forms.widgets.CheckboxInput()
)
單選checkbox示例:
#單選的checkbox
class TestForm2(forms.Form):
keep = forms.ChoiceField(
choices=(
('True',1),
('False',0),
),
label="是否7天內自動登錄",
initial="1",
widget=forms.widgets.CheckboxInput(),
)
選中:'True' #form只是幫我們做校驗,校驗選擇內容的時候,就是看在沒在我們的choices裡面,裡面有這個值,表示合法,沒有就不合法
沒選中:'False'
---保存到資料庫裡面 keep:'True'
if keep == 'True':
session 設置有效期7天
else:
pass
多選checkbox
class LoginForm(forms.Form):
...
hobby = forms.fields.MultipleChoiceField(
choices=((1, "籃球"), (2, "足球"), (3, "雙色球"),),
label="愛好",
initial=[1, 3],
widget=forms.widgets.CheckboxSelectMultiple()
)
date類型
from django import forms
from django.forms import widgets
class BookForm(forms.Form):
date = forms.DateField(widget=widgets.TextInput(attrs={'type':'date'})) #必須指定type,不然不能渲染成選擇時間的input框
choice欄位註意事項
在使用選擇標簽時,需要註意choices的選項可以配置從資料庫中獲取,但是由於是靜態欄位 獲取的值無法實時更新,需要重寫構造方法從而實現choice實時更新。
方式一:
from django.forms import Form
from django.forms import widgets
from django.forms import fields
class MyForm(Form):
user = fields.ChoiceField(
# choices=((1, '上海'), (2, '北京'),),
initial=2,
widget=widgets.Select
)
def __init__(self, *args, **kwargs):
super(MyForm,self).__init__(*args, **kwargs) #註意重寫init方法的時候,*args和**kwargs一定要給人家寫上,不然會出問題,並且驗證總是不能通過,還不顯示報錯信息
# self.fields['user'].choices = ((1, '上海'), (2, '北京'),)
# 或
self.fields['user'].choices = models.Classes.objects.all().values_list('id','caption')
方式二:
from django import forms
from django.forms import fields
from django.forms import models as form_model
class FInfo(forms.Form):
authors = forms.ModelMultipleChoiceField(queryset=models.NNewType.objects.all()) # 多選 #或者下麵這種方式,通過forms裡面的models中提供的方法也是一樣的。
authors = form_model.ModelMultipleChoiceField(queryset=models.NNewType.objects.all()) # 多選
#authors = form_model.ModelChoiceField(queryset=models.NNewType.objects.all()) # 單選 #或者,forms.ModelChoiceField(queryset=models.Publisth.objects.all(),widget=forms.widgets.Select()) 單選 #
authors = forms.ModelMultipleChoiceField( queryset=models.Author.objects.all(), widget = forms.widgets.Select(attrs={'class': 'form-control'} ))
#如果用這種方式,別忘了model表中,NNEWType的__str__方法要寫上,不然選擇框裡面是一個個的object對象
三 Form所有內置欄位
內置欄位:
Field
required=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:12
DurationField(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.ModelChoiceField
queryset, # 查詢資料庫中的數據
empty_label="---------", # 預設空顯示內容
to_field_name=None, # HTML中value的值對應的欄位
limit_choices_to=None # ModelForm中對queryset二次篩選
ModelMultipleChoiceField(ModelChoiceField)
... django.forms.models.ModelMultipleChoiceField
TypedChoiceField(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=''
GenericIPAddressField
protocol='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類型
複製代碼
四 欄位校驗
RegexValidator驗證器
from django.forms import Form
from django.forms import widgets
from django.forms import fields
from django.core.validators import RegexValidator
class MyForm(Form):
user = fields.CharField(
validators=[RegexValidator(r'^[0-9]+$', '請輸入數字'), RegexValidator(r'^159[0-9]+$', '數字必須以159開頭')],
)
自定義驗證函數
import re
from django.forms import Form
from django.forms import widgets
from django.forms import fields
from django.core.exceptions import ValidationError
# 自定義驗證規則
def mobile_validate(value):
mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$')
if not mobile_re.match(value):
raise ValidationError('手機號碼格式錯誤') #自定義驗證規則的時候,如果不符合你的規則,需要自己發起錯誤
class PublishForm(Form):
title = fields.CharField(max_length=20,
min_length=5,
error_messages={'required': '標題不能為空',
'min_length': '標題最少為5個字元',
'max_length': '標題最多為20個字元'},
widget=widgets.TextInput(attrs={'class': "form-control",
'placeholder': '標題5-20個字元'}))
# 使用自定義驗證規則
phone = fields.CharField(validators=[mobile_validate, ],
error_messages={'required': '手機不能為空'},
widget=widgets.TextInput(attrs={'class': "form-control",
'placeholder': u'手機號碼'}))
email = fields.EmailField(required=False,
error_messages={'required': u'郵箱不能為空','invalid': u'郵箱格式錯誤'},
widget=widgets.TextInput(attrs={'class': "form-control", 'placeholder': u'郵箱'}))
五 Hook鉤子方法
除了上面兩種方式,我們還可以在Form類中定義鉤子函數,來實現自定義的驗證功能。
局部鉤子
我們在Fom類中定義 clean_欄位名() 方法,就能夠實現對特定欄位進行校驗。
舉個例子:
class LoginForm(forms.Form):
username = forms.CharField(
min_length=8,
label="用戶名",
initial="張三",
error_messages={
"required": "不能為空",
"invalid": "格式錯誤",
"min_length": "用戶名最短8位"
},
widget=forms.widgets.TextInput(attrs={"class": "form-control"})
)
...
# 定義局部鉤子,用來校驗username欄位,之前的校驗股則還在,給你提供了一個添加一些校驗功能的鉤子
def clean_username(self):
value = self.cleaned_data.get("username")
if "666" in value:
raise ValidationError("光喊666是不行的")
else:
return value
全局鉤子
我們在Fom類中定義 clean() 方法,就能夠實現對欄位進行全局校驗,欄位全部驗證完,局部鉤子也全部執行完之後,執行這個全局鉤子校驗。
class LoginForm(forms.Form):
...
password = forms.CharField(
min_length=6,
label="密碼",
widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}, render_value=True)
)
re_password = forms.CharField(
min_length=6,
label="確認密碼",
widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}, render_value=True)
)
...
# 定義全局的鉤子,用來校驗密碼和確認密碼欄位是否相同,執行全局鉤子的時候,cleaned_data裡面肯定是有了通過前面驗證的所有數據
def clean(self):
password_value = self.cleaned_data.get('password')
re_password_value = self.cleaned_data.get('re_password')
if password_value == re_password_value:
return self.cleaned_data #全局鉤子要返回所有的數據
else:
self.add_error('re_password', '兩次密碼不一致') #在re_password這個欄位的錯誤列表中加上一個錯誤,並且clean_data裡面會自動清除這個re_password的值,所以列印clean_data的時候會看不到它
raise ValidationError('兩次密碼不一致')
六 進階補充
應用Bootstrap樣式
Django form應用Bootstrap樣式簡單示例:
<!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>
批量添加樣式
可通過重寫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'
})
簡單寫一個小項目:book表的添加和數據展示
views.py內容如下:
from django.shortcuts import render,HttpResponse,redirect
from app01 import models
from django import forms
# Create your views here.
class BookForm(forms.Form):
title = forms.CharField(
max_length=16,
label='書名:',
initial='zhang',
# widget = forms.widgets.PasswordInput(attrs={'class': 'form-control'},render_value=True),
widget = forms.widgets.TextInput(attrs={'class': 'form-control'},),
)
# title2 = forms.CharField(
# max_length=16,
# label='書名:',
# initial='zhang',
# # widget = forms.widgets.PasswordInput(attrs={'class': 'form-control'},render_value=True),
# widget=forms.widgets.TextInput(attrs={'class': 'form-control'}, ),
#
# )
sex = forms.ChoiceField(
choices=(
('1','男'),
('2','女'),
)
,
label='性別:',
# widget=forms.widgets.RadioSelect(),
# widget=forms.widgets.RadioSelect(),
# widget=forms.widgets.SelectMultiple(attrs={'class':'form-control'}),
widget=forms.widgets.Select(attrs={'class':'form-control'}),
# widget=forms.widgets.CheckboxInput(),
# widget=forms.widgets.CheckboxSelectMultiple(),
)
publishDate = forms.DateField(
label='出版日期:',
widget=forms.widgets.TextInput(attrs={'type':'date','class':'form-control'}),
)
price=forms.DecimalField(
max_digits=5,
decimal_places=2,
label='書籍的價格:',
widget=forms.widgets.NumberInput(attrs={'class': 'form-control'}
))
# publish = forms.ModelChoiceField(
# label='出版社:',
# queryset=models.Publish.objects.all(),
# widget=forms.widgets.Select(attrs={'class': 'form-control'}
# ))
publish_id = forms.ChoiceField(
label='出版社:',
widget=forms.widgets.Select(attrs={'class': 'form-control'}
))
authors = forms.ModelMultipleChoiceField(
queryset=models.Author.objects.all(),
widget = forms.widgets.SelectMultiple(attrs={'class': 'form-control'}
))
def __init__(self,*args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['publish_id'].choices = models.Publish.objects.values_list('pk','name')
def index(request):
if request.method == 'GET':
form_obj = BookForm()
return render(request,'index.html',{'form_obj':form_obj})
else:
print(request.POST)
form_obj = BookForm(request.POST)
print(form_obj.is_valid())
print(form_obj.errors)
if form_obj.is_valid():
print(form_obj.cleaned_data)
authors_obj = form_obj.cleaned_data.pop('authors')
new_book_obj = models.Book.objects.create(**form_obj.cleaned_data)
new_book_obj.authors.add(*authors_obj)
return redirect('show')
else:
print(form_obj.errors)
return render(request,'index.html',{'form_obj':form_obj})
def show(request):
book_objs = models.Book.objects.all()
return render(request,'show.html',{'book_objs':book_objs})
def edit_book(request,n):
return HttpResponse('歡迎來到編輯頁面')
def delete_book(request,n):
return HttpResponse('歡迎來到刪除頁面')
urls.py內容如下
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^index/', views.index,name='index'),
url(r'^show/', views.show,name='show'),
url(r'^edit_book/(\d+)/', views.edit_book,name='edit_book'),
url(r'^delete_book/(\d+)/', views.delete_book,name='delete_book'),
]
index.html內容如下:
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="{% static 'bootstrap-3.3.0-dist/dist/css/bootstrap.min.css' %}">
</head>
<body>
<h1>展示頁面</h1>
{#<div>#}
{# {{ form_obj.as_p }}#}
{##}
{#</div>#}
{#{% for field in form_obj %}#}
{# <div>#}
{# {{ field }}#}
{# </div>#}
{##}
{#{% endfor %}#}
<div class="container-fluid">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<form action="" novalidate method="post">
{% for field in form_obj %}
<div class="form-group {% if field.errors.0 %}has-error{% endif %}">
<label for="{{ field.id_for_label }}">{{ field.label }}</label>
{{ field }}
<span class="text-danger">{{ field.errors.0 }}</span>
</div>
{% endfor %}
{# <div class="form-group">#}
{# <label for="{{ form_obj.sex.id_for_label }}">{{ form_obj.sex.label }}</label>#}
{# {{ form_obj.sex }}#}
{# {{ form_obj.sex.errors.0 }}#}
{# </div>#}
{# <div class="form-group">#}
{# <label for="{{ form_obj.publishDate.id_for_label }}">{{ form_obj.publishDate.label }}</label>#}
{# {{ form_obj.publishDate }}#}
{# {{ form_obj.publishDate.errors.0 }}#}
{# </div>#}
{# <div class="form-group">#}
{# <label for="{{ form_obj.price.id_for_label }}">{{ form_obj.price.label }}</label>#}
{# {{ form_obj.price }}#}
{# <span class="text-danger">{{ form_obj.price.errors.0 }}</span>#}
{# </div>#}
{# <div class="form-group">#}
{# <label for="{{ form_obj.publish.id_for_label }}">{{ form_obj.publish.label }}</label>#}
{# {{ form_obj.publish }}#}
{# <span class="text-danger">{{ form_obj.publish.errors.0 }}</span>#}
{# </div>#}
{# <div class="form-group">#}
{# <label for="{{ form_obj.authors.id_for_label }}">{{ form_obj.authors.label }}</label>#}
{# {{ form_obj.authors }}#}
{# <span class="text-danger">{{ form_obj.authors.errors.0 }}</span>#}
{# </div>#}
<input type="submit">
</form>
</div>
</div>
</div>
<div>
</div>
</body>
<script src="{% static 'bootstrap-3.3.0-dist/dist/jQuery/jquery-3.1.1.js' %}"></script>
<script src="{% static 'bootstrap-3.3.0-dist/dist/js/bootstrap.min.js' %}"></script>
</html>
show.html內容如下
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="{% static 'bootstrap-3.3.0-dist/dist/css/bootstrap.min.css' %}">
</head>
<body>
<h1>數據展示</h1>
<div class="container-fluid">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<table class="table">
<thead>
<tr>
<th>id</th>
<th>title</th>
<th>性別</th>
<th>出版日期</th>
<th>出版社</th>
<th>作者</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for book_obj in book_objs %}
<tr>
<td>{{ book_obj.pk }}</td>
<td>{{ book_obj.title }}</td>
<td>{{ book_obj.get_sex_display }}</td>
<td>{{ book_obj.publishDate|date:'Y-d-m' }}</td>
<td>{{ book_obj.publish.name }}</td>
<td>
{% for author in book_obj.authors.all %}
{{ author.name }}
{% endfor %}
</td>
<td>
<a class="btn btn-warning" href="{% url 'edit_book' book_obj.pk %}">編輯</a>
<a class="btn btn-danger" href="{% url 'delete_book' book_obj.pk %}">刪除</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</body>
<script src="{% static 'bootstrap-3.3.0-dist/dist/jQuery/jquery-3.1.1.js' %}"></script>
<script src="{% static 'bootstrap-3.3.0-dist/dist/js/bootstrap.min.js' %}"></script>
</html>
別忘了在settings.py中配置靜態文件
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR,'statics'),
]
別忘了去下載一個bootstrap和jquery然後在上面的html文件中引入一下。
models.py文件內容如下:
from django.db import models
# Create your models here.
from django.db import models
# Create your models here.
class Author(models.Model):
nid = models.AutoField(primary_key=True)
name=models.CharField( max_length=32)
age=models.IntegerField()
authorDetail=models.OneToOneField(to="AuthorDetail",to_field="nid")
def __str__(self):
return self.name
class AuthorDetail(models.Model):
nid = models.AutoField(primary_key=True)
birthday=models.DateField()
telephone=models.BigIntegerField()
addr=models.CharField( max_length=64)
class Publish(models.Model):
nid = models.AutoField(primary_key=True)
name=models.CharField( max_length=32)
city=models.CharField( max_length=32)
email=models.EmailField()
def __str__(self):
return self.name
class Book(models.Model):
nid = models.AutoField(primary_key=True)
title = models.CharField( max_length=32)
sex_choice = (
(1,'男'),
(2,'女'),
)
sex = models.IntegerField(choices=sex_choice,default=1)
publishDate=models.DateField()
price=models.DecimalField(max_digits=5,decimal_places=2)
publish=models.ForeignKey(to="Publish",to_field="nid")
authors=models.ManyToManyField(to='Author',)
def __str__(self):
return self.title
別忘了給每個表添加一些數據。
七 ModelForm
通常在Django項目中,我們編寫的大部分都是與Django 的模型緊密映射的表單。 舉個例子,你也許會有個Book 模型,並且你還想創建一個form表單用來添加和編輯書籍信息到這個模型中。 在這種情況下,在form表單中定義欄位將是冗餘的,因為我們已經在模型中定義了那些欄位。
基於這個原因,Django 提供一個輔助類來讓我們可以從Django 的模型創建Form,這就是ModelForm。
modelForm定義
form與model的終極結合,會根據你model中的欄位轉換成對應的form欄位,並且並你生成標簽等操作。
比如你的models中的表是下麵的內容:
class Book(models.Model):
nid = models.AutoField(primary_key=True)
title = models.CharField( max_length=32)
publishDate=models.DateField()
price=models.DecimalField(max_digits=5,decimal_places=2)
publish=models.ForeignKey(to="Publish",to_field="nid")
authors=models.ManyToManyField(to='Author',)
def __str__(self):
return self.title
modelform類的寫法。
class BookForm(forms.ModelForm):
class Meta:
model = models.Book
fields = "__all__"
labels = {
"title": "書名",
"price": "價格"
}
widgets = {
"password": forms.widgets.PasswordInput(attrs={"class": "c1"}),
"publishDate": forms.widgets.DateInput(attrs={"type": "date"}),
}
class Meta下常用參數:
model = models.Book # 對應的Model中的類
fields = "__all__" # 欄位,如果是__all__,就是表示列出所有的欄位
exclude = None # 排除的欄位
labels = None # 提示信息
help_texts = None # 幫助提示信息
widgets = None # 自定義插件
error_messages = None # 自定義錯誤信息
error_messages = { 'title':{'required':'不能為空',...} #每個欄位的所有的錯誤都可以寫,...是省略的意思,複製黏貼我代碼的時候別忘了刪了...}
批量添加樣式:和form的一樣
class BookForm(forms.ModelForm): #password = forms.CharField(min_length=10) #可以重寫欄位,會覆蓋modelform中的這個欄位,那麼modelform下麵關於這個欄位的設置就會被覆蓋,比如果設置插件啊,error_messages啊等等,
r_password = forms.CharField() #想多驗證一些欄位可以單獨拿出來寫,按照form的寫法,寫在Meta的上面或者下麵都可以
class Meta:
model = models.Book
# fields = ['title','price']
fields = "__all__" #['title,'price'] 指定欄位生成form
# exclude=['title',] #排除欄位
labels = {
"title": "書名",
"price": "價格"
}
error_messages = {
'title':{'required':'不能為空',} #每個欄位的錯誤都可以寫
}
#如果models中的欄位和咱們需要驗證的欄位對不齊的是,比如註冊時,咱們需要驗證密碼和確認密碼兩個欄位數據,但是後端資料庫就保存一個數據就行,那麼驗證是兩個,數據保存是一個,就可以再接著寫form欄位
r_password = forms.CharField()。 #同樣的,如果想做一些特殊的驗證定製,那麼和form一昂,也是那兩個鉤子(全局和局部),寫法也是form那個的寫法,直接在咱們的類裡面寫: #局部鉤子: def clean_title(self): pass #全局鉤子 def clean(self): pass
def __init__(self,*args,**kwargs): #批量操作
super().__init__(*args,**kwargs)
for field in self.fields: #field.error_messages = {'required':'不能為空'} #批量添加錯誤信息,這是都一樣的錯誤,不一樣的還是要單獨寫。
self.fields[field].widget.attrs.update({'class':'form-control'})
ModelForm的驗證
與普通的Form表單驗證類型類似,ModelForm表單的驗證在調用is_valid() 或訪問errors 屬性時隱式調用。
我們可以像使用Form類一樣自定義局部鉤子方法和全局鉤子方法來實現自定義的校驗規則。
如果我們不重寫具體欄位並設置validators屬性的話,ModelForm是按照模型中欄位的validators來校驗的。
save()方法
每個ModelForm還具有一個save()方法。 這個方法根據表單綁定的數據創建並保存資料庫對象。 ModelForm的子類可以接受現有的模型實例作為關鍵字參數instance;如果提供此功能,則save()將更新該實例。 如果沒有提供,save() 將創建模型的一個新實例:
>>> from myapp.models import Book
>>> from myapp.forms import BookForm
# 根據POST數據創建一個新的form對象
>>> form_obj = BookForm(request.POST)
# 創建書籍對象
>>> new_ book = form_obj.save()
# 基於一個書籍對象創建form對象
>>> edit_obj = Book.objects.get(id=1)
# 使用POST提交的數據更新書籍對象
>>> form_obj = BookForm(request.POST, instance=edit_obj)
>>> form_obj.save()
之前我們通過form組件來保存書籍表數據的時候的寫法:
def index(request):
if request.method == 'GET':
form_obj = BookForm()
return render(request,'index.html',{'form_obj':form_obj})
else:
form_obj = BookForm(request.POST)
if form_obj.is_valid():
# authors_obj = form_obj.cleaned_data.pop('authors')
# new_book_obj = models.Book.objects.create(**form_obj.cleaned_data)
# new_book_obj.authors.add(*authors_obj)
form_obj.save() #因為我們再Meta中指定了是哪張表,所以它會自動識別,不管是外鍵還是多對多等,都會自行處理保存,它完成的就是上面三句話做的事情,並且還有就是如果你驗證的數據比你後端數據表中的欄位多,那麼他自會自動剔除多餘的不需要保存的欄位,比如那個重覆確認密碼就不要保存
return redirect('show')
else:
print(form_obj.errors)
return render(request,'index.html',{'form_obj':form_obj})
比如說我們圖書管理系統頁面之前是這樣寫的
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="{% static 'bootstrap-3.3.0-dist/dist/css/bootstrap.min.css' %}">
</head>
<body>
<h1>編輯頁面</h1>
<div class="container-fluid">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<form action="">
<div class="form-group">
<label for="title">書名</label>
<input type="text" class="form-control" id="title" placeholder="title" value="{{ book_obj.title }}">
</div>
<div class="form-group">
<label for="publishDate">出版日期</label>
<input type="text" class="form-control" id="publishDate" placeholder="publishDate" value="{{ book_obj.publishDate|date:'Y-m-d' }}">
</div>
<div class="form-group">
<label for="price">價格</label>
<input type="number" class="form-control" id="price" placeholder="price" value="{{ book_obj.price }}">
</div>
<div class="form-group">
<label for="publish">書名</label>
<select name="publish" id="publish" class="form-control">
{% for publish in all_publish %}
{% if publish == book_obj.publish %}
<option value="{{ publish.id }}" selected>{{ publish.name }}</option>
{% else %}
<option value="{{ publish.id }}">{{ publish.name }}</option>
{% endif %}
{% endfor %}
</select>
</div>
<div class="form-group">
<label for="authors">書名</label>
<select name="authors" id="authors" multiple class="form-control">
{% for author in all_authors %}
{% if author in book_obj.authors.all %}
<option value="{{ author.id }}" selected>{{ author.name }}</option>
{% else %}
<option value="{{ author.id }}" >{{ author.name }}</option>
{% endif %}
{% endfor %}
</select>
</div>
</form>
</div>
</div>
</div>
</body>
<script src="{% static 'bootstrap-3.3.0-dist/dist/jQuery/jquery-3.1.1.js' %}"></script>
<script src="{% static 'bootstrap-3.3.0-dist/dist/js/bootstrap.min.js' %}"></script>
</html>
views.py是這樣寫的:
def edit_book(request,n):
book_obj = models.Book.objects.filter(pk=n).first()
if request.method == 'GET':
all_authors = models.Author.objects.all() #
all_publish = models.Publish.objects.all()
return render(request,'edit_book.html',{'book_obj':book_obj,'all_authors':all_authors,'all_publish':all_publish})
改成使用modelform之後,我們這樣寫:
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="{% static 'bootstrap-3.3.0-dist/dist/css/bootstrap.min.css' %}">
</head>
<body>
<h1>編輯頁面</h1>
<div class="container-fluid">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<form action="{% url 'edit_book' n %}" novalidate method="post">
{% csrf_token %}
{% for field in form %}
<div class="form-group">
<label for="{{ field.id_for_label }}">{{ field.label }}</label>
{{ field }}
<span class="text-danger">{{ field.errors.0 }}</span>
</div>
{% endfor %}
<div class="form-group">
<input type="submit" class="btn btn-primary pull-right">
</div>
</form>
</div>
</div>
</div>
</body>
<script src="{% static 'bootstrap-3.3.0-dist/dist/jQuery/jquery-3.1.1.js' %}"></script>
<script src="{% static 'bootstrap-3.3.0-dist/dist/js/bootstrap.min.js' %}"></script>
</html>
views.py這樣寫:
def edit_book(request,n):
book_obj = models.Book.objects.filter(pk=n).first()
if request.method == 'GET':
# all_authors = models.Author.objects.all() #
# all_publish = models.Publish.objects.all()
form = BookForm(instance=book_obj)
return render(request,'edit_book.html',{'form':form,'n':n}) #傳遞的這個n參數是給form表單提交數據的是的action的url用的,因為它需要一個參數來識別是更新的哪條記錄
else:
form = BookForm(request.POST,instance=book_obj) #必須指定instance,不然我們調用save方法的是又變成了添加操作
if form.is_valid():
form.save()
return redirect('show')
else:
return render(request,'edit_book.html',{'form':form,'n':n})
創建modelform
這是一個神奇的組件,通過名字我們可以看出來,這個組件的功能就是把model和form組合起來,先來一個簡單的例子來看一下這個東西怎麼用:比如我們的資料庫中有這樣一張學生表,欄位有姓名,年齡,愛好,郵箱,電話,住址,註冊時間等等一大堆信息,現在讓你寫一個創建學生的頁面,你的後臺應該怎麼寫呢?首先我們會在前端一個一個羅列出這些欄位,讓用戶去填寫,然後我們從後天一個一個接收用戶的輸入,創建一個新的學生對象,保存其實,重點不是這些,而是合法性驗證,我們需要在前端判斷用戶輸入是否合法,比如姓名必須在多少字元以內,電話號碼必須是多少位的數字,郵箱必須是郵箱的格式這些當然可以一點一點手動寫限制,各種判斷,這毫無問題,除了麻煩我們現在有個更優雅(以後在Python相關的內容里,要多用“優雅”這個詞,並且養成習慣)的方法:ModelForm先來簡單的,生硬的把它用上,再來加驗證條件。
#首先導入ModelForm
from django.forms import ModelForm
#在視圖函數中,定義一個類,比如就叫StudentList,這個類要繼承ModelForm,在這個類中再寫一個原類Meta(規定寫法,並註意首字母是大寫的)
#在這個原類中,有以下屬性(部分):
class StudentList(ModelForm):
class Meta:
model =Student #對應的Model中的類
fields = "__all__" #欄位,如果是__all__,就是表示列出所有的欄位
exclude = None #排除的欄位
#error_messages用法:
error_messages = {
'name':{'required':"用戶名不能為空",},
'age':{'required':"年齡不能為空",},
}
#widgets用法,比如把輸入用戶名的input框給為Textarea
#首先得導入模塊
from django.forms import widgets as wid #因為重名,所以起個別名
widgets = {
"name":wid.Textarea(attrs={"class":"c1"}) #還可以自定義屬性
}
#labels,自定義在前端顯示的名字
labels= {
"name":"用戶名"
}
然後在url對應的視圖函數中實例化這個類,把這個對象傳給前端
def student(request):
if request.method == 'GET':
student_list = StudentList()
return render(request,'student.html',{'student_list':student_list})
然後前端只需要 {{ student_list.as_p }} 一下,所有的欄位就都出來了,可以用as_p顯示全部,也可以通過for迴圈這
student_list,拿到的是一個個input框,現在我們就不用as_p,手動把這些input框搞出來,as_p拿到的頁面太醜。
首先 for迴圈這個student_list,拿到student對象,直接在前端列印這個student,是個input框student.label ,拿到資料庫中每個欄位的verbose_name ,如果沒有設置這個屬性,拿到的預設就是欄位名,還可以通過student.errors.0 拿到錯誤信息有了這些,我們就可以通過bootstrap,自己拼出來想要的樣式了,比如:
<body>
<div class="container">
<h1>student</h1>
<form method="POST" novalidate>
{% csrf_token %}
{# {{ student_list.as_p }}#}
{% for student in student_list %}
<div class="form-group col-md-6">
{# 拿到數據欄位的verbose_name,沒有就預設顯示欄位名 #}
<label class="col-md-3 control-label">{{ student.label }}</label>
<div class="col-md-9" style="position: relative;">{{ student }}</div>
</div>
{% endfor %}
<div class="col-md-2 col-md-offset-10">
<input type="submit" value="提交" class="btn-primary">
</div>
</form>
</div>
</body>
現在還缺一個input框的form-contral樣式,可以考慮在後臺的widget裡面添加
比如這樣:
from django.forms import widgets as wid #因為重名,所以起個別名
widgets = {
"name":wid.TextInput(attrs={'class':'form-control'}),
"age":wid.NumberInput(attrs={'class':'form-control'}),
"email":wid.EmailInput(attrs={'class':'form-control'})
}
當然也可以在js中,找到所有的input框,加上這個樣式,也行。
添加紀錄
保存數據的時候,不用挨個取數據了,只需要save一下
def student(request):
if request.method == 'GET':
student_list = StudentList()
return render(request,'student.html',{'student_list':student_list})
else:
student_list = StudentList(request.POST)
if student_list.is_valid():
student_list.save()
return redirect(request,'student_list.html',{'student_list':student_list})
編輯數據
如果不用ModelForm,編輯的時候得顯示之前的數據吧,還得挨個取一遍值,如果ModelForm,只需要加一個instance=obj(obj是要修改的資料庫的一條數據的對象)就可以得到同樣的效果
保存的時候要註意,一定要註意有這個對象(instance=obj),否則不知道更新哪一個數據
代碼示例:
from django.shortcuts import render,HttpResponse,redirect
from django.forms import ModelForm
# Create your views here.
from app01 import models
def test(request):
# model_form = models.Student
model_form = models.Student.objects.all()
return render(request,'test.html',{'model_form':model_form})
class StudentList(ModelForm):
class Meta:
model = models.Student #對應的Model中的類
fields = "__all__" #欄位,如果是__all__,就是表示列出所有的欄位
exclude = None #排除的欄位
labels = None #提示信息
help_texts = None #幫助提示信息
widgets = None #自定義插件
error_messages = None #自定義錯誤信息
#error_messages用法:
error_messages = {
'name':{'required':"用戶名不能為空",},
'age':{'required':"年齡不能為空",},
}
#widgets用法,比如把輸入用戶名的input框給為Textarea
#首先得導入模塊
from django.forms import widgets as wid #因為重名,所以起個別名
widgets = {
"name":wid.Textarea
}
#labels,自定義在前端顯示的名字
labels= {
"name":"用戶名"
}
def student(request):
if request.method == 'GET':
student_list = StudentList()
return render(request,'student.html',{'student_list':student_list})
else:
student_list = StudentList(request.POST)
if student_list.is_valid():
student_list.save()
return render(request,'student.html',{'student_list':student_list})
def student_edit(request,pk):
obj = models.Student.objects.filter(pk=pk).first()
if not obj:
return redirect('test')
if request.method == "GET":
student_list = StudentList(instance=obj)
return render(request,'student_edit.html',{'student_list':student_list})
else:
student_list = StudentList(request.POST,instance=obj)
if student_list.is_valid():
student_list.save()
return render(request,'student_edit.html',{'student_list':student_list})
總結: 從上邊可以看到ModelForm用起來是非常方便的,比如增加修改之類的操作。但是也帶來額外不好的地方,model和form之間耦合了。如果不耦合的話,mf.save()方法也無法直接提交保存。 但是耦合的話使用場景通常局限用於小程式,寫大程式就最好不用了。