天天看點

django.forms生成HTML,第21天,Django之Form元件添加使用者

ModelForm

一、Form元件初識

Django的Form主要具有一下幾大功能:

生成HTML标簽

驗證使用者資料(顯示錯誤資訊)

HTML Form送出保留上次送出資料

初始化頁面顯示内容

models.py檔案内容如下:

from django.db import models

# Create your models here.

class UserType(models.Model):

title = models.CharField(max_length=32)

class UserInfo(models.Model):

username = models.CharField(max_length=32)

password = models.CharField(max_length=64)

email = models.EmailField()

usertype = models.ForeignKey(UserType,null=True,blank=True)

1、在app下建立一個forms.py檔案

# 先導入以下三個子產品

from django.forms import Form

from django.forms import fields

from django.forms import widgets

from .models import *

class UserForm(Form):

username = fields.CharField(

required=True, #規定該字段不能為空

error_messages={'required':'使用者名不能為空'} # 自定義錯誤資訊,'required'是定義該字段為空時的錯誤資訊

)

password = fields.CharField(

required=True,

error_messages={'required':'密碼不能為空'}

)

email = fields.EmailField(

required=True,

error_messages={'required':'郵箱不能為空','invalid':'郵箱格式錯誤'}

) # 'invalid'是定義該字段值無效時的錯誤資訊

# ip = fields.GenericIPAddressField(

# required=True,

# error_messages={'required':'IP不能為空','invalid':'IP格式錯誤'}

# )

usertype_id = fields.ChoiceField(choices=UserType.objects.values_list('id','title'))

# choices=清單,也就是多選的,對應html的select标簽

注意:

建立的表單類必須繼承Form類;

每個字段預設就是required=True;

字段沒有自定義error_messages的值時,預設值是英文的錯誤提示;自定義的error_messages值中可以有多個錯誤資訊提示,"required"是在該字段為空是提示,"invalid"是在該字段有值,但是值無效時提示,如郵箱或IP位址不對;

如果想通過UserInfo.objects.create(**form.cleaned_data)這種方式快速插入資料到資料庫,則在定義Form表單字段時必須與models中表字段一緻,特别注意:由于Django會models中的models.ForeignKey()字段名自動加_id,所在Form表單字段在定義時,需要手動給字段名稱添加_id,友善插入資料,如上:usertype_id

fields.GenericIPAddressField()這個字段類型是專門給IP輸入用的,其中有一個參數protocol預設等于both,表示支援IPV4和IPV6兩種協定,如果隻想讓其支援IPV4協定,隻需要protocol='ipv4'即可。

2、在views.py中導入forms.py

from app01.forms import *

def adduser(request):

form = UserForm() # 執行個體化得到一個沒有值的form表單對象

if request.method == 'POST':

form = UserForm(data=request.POST)

# 将前端擷取的資料傳入到forms.UserForm類,執行個體化得到一個包含字段值的表單對象form

if form.is_valid(): # is_valid()得到一個布爾值,判斷傳入的字段值是否全部有效

print(form.cleaned_data) #form.cleaned_data是字典類型,得到全部字段的有效值,即幹淨的資料

UserInfo.objects.create(**form.cleaned_data) # 直接插入資料庫,form字段必須與models中定義的一緻

return redirect('/index/')

else:

print(form.errors) # 得到錯誤資訊,并生成html标簽,哪個字段值有誤,就會對應産生哪個字段的錯誤資訊

return render(request,'adduser.html',locals())

注意:

form.is_valid()方法得到一個布爾值,判斷從前端得到表單資料是全部有效;

form.cleaned_data得到全部驗證有效的資料,是一個字典類型,如:{'username': 'admin', 'ip': '1.1.1.1', 'usertype_id': '3', 'email': '[email protected]', 'password': 'admin'},字典中的key對應UserForm類中的每個字段名;

form.errors驗證有誤的錯誤提示資訊,與錯誤字段一一對應,如:form.errors.user可以得到user字段的全部錯誤資訊,是一個清單類型,form.errors.user[0]得到user字段的第一個錯誤資訊。form.errors.email[0]得到email字段的第一個錯誤資訊;

3、在templates模版檔案中渲染

添加使用者

{% csrf_token %}

使用者名:{{ form.username }} {{ form.errors.username.0 }}

密碼:{{ form.password }} {{ form.errors.password.0 }}

郵箱:{{ form.email }} {{ form.errors.email.0 }}

使用者類型:{{ form.usertype_id }} {{ form.errors.usertype_id.0 }}

4、浏覽器中顯示

django.forms生成HTML,第21天,Django之Form元件添加使用者

GET方式請求頁面

沒有填寫資料時送出,form.errors得到的值是

  • email
    • 郵箱不能為空
  • pwd
    • 密碼不能為空
  • ip
    • IP不能為空
  • user
    • 使用者名不能為空

,頁面如下圖:

django.forms生成HTML,第21天,Django之Form元件添加使用者

沒有填寫資料時送出

資料格式填寫出錯時,form.errors得到的值是

  • email
    • 郵箱格式錯誤
  • ip
    • IP格式錯誤

,頁面如下圖:

django.forms生成HTML,第21天,Django之Form元件添加使用者

郵箱或IP格式錯誤時送出

資料格式填寫正确時,form.cleaned_data得到的值是字典{'email': '[email protected]', 'pwd': 'admin', 'ip': '1.1.1.1', 'usertype_id': '1', 'user': 'admin'}

django.forms生成HTML,第21天,Django之Form元件添加使用者

資料格式全部填寫正确時

5. 在輸入框中顯示預設值

需要在執行個體化form表單對象時加入initial參數,如下:

form = UserForm(initial={'username':obj.username,

'password':obj.password,

'email':obj.email,

'usertype_id':obj.usertype.id})

如下示例,在編輯使用者資訊時,需要輸入框中有預設值:

def edituser(request,pk):

# 參數pk是urls傳入的id

obj = UserInfo.objects.get(id=pk)

form = UserForm(initial={'username':obj.username,

'password':obj.password,

'email':obj.email,

'usertype_id':obj.usertype.id})

if request.method == 'POST':

form = UserForm(data=request.POST)

print(request.POST)

if form.is_valid():

UserInfo.objects.filter(id=pk).update(**form.cleaned_data)

return redirect('/index/')

return render(request, 'edituser.html', locals())

django.forms生成HTML,第21天,Django之Form元件添加使用者

輸入框中顯示預設值

6. 解決Form元件中下拉框資料無法實時從資料庫擷取的問題

是因為類中靜态字段隻在代碼枞上到下加載的時候執行一次,上述的代碼中的usertype_id的值是從資料庫中取的,它隻是在加載UserForm類時執行了一次,每次執行個體化并不會執行。是以,如果想讓usertype_id這個字段實時從資料庫中取得資料(或者說,想讓usertype_id在每次執行個體化時都從資料庫取一次資料),就必須将擷取該字段的值寫在__init__()構造方法中,如下:

from django.forms import Form

from django.forms import fields

from django.forms import widgets

from .models import *

class UserForm(Form):

username = fields.CharField(

required=True,

error_messages={'required':'使用者名不能為空'}

)

password = fields.CharField(

required=True,

error_messages={'required':'密碼不能為空'}

)

email = fields.EmailField(

required=True,

error_messages={'required':'郵箱不能為空','invalid':'郵箱格式錯誤'}

)

# ip = fields.GenericIPAddressField(

# required=True,

# error_messages={'required':'IP不能為空','invalid':'IP格式錯誤'}

# )

usertype_id = fields.ChoiceField(choices=[])

def __init__(self,*args,**kwargs):

super(UserForm, self).__init__(*args,**kwargs)

self.fields['usertype_id'].choices=UserType.objects.values_list('id','title')

注意:

self.fields包括了所有的字段,django内部會将所有字段都深拷貝一份,并裝在self.fields中。是以通過self.fields['usertype_id'].choices=UserType.objects.values_list('id','title')就可以在每次執行個體化時,從資料庫取一次值賦給usertype_id

7. 多對多在Form元件中的操作

models.py中新添加一個技能表Technique,與UserInfo是多對多關系

from django.db import models

# Create your models here.

class UserType(models.Model):

title = models.CharField(max_length=32)

class Technique(models.Model):

title = models.CharField(max_length=32)

class UserInfo(models.Model):

username = models.CharField(max_length=32)

password = models.CharField(max_length=64)

email = models.EmailField()

usertype = models.ForeignKey(UserType,null=True,blank=True)

tn = models.ManyToManyField(Technique)

forms.py中的UserForm增加一個字段tn_id,對應models.UserInfo中的tn多對多字段

from django.forms import Form

from django.forms import fields

from django.forms import widgets

from .models import *

class UserForm(Form):

username = fields.CharField(

required=True,

error_messages={'required':'使用者名不能為空'}

)

password = fields.CharField(

required=True,

error_messages={'required':'密碼不能為空'}

)

email = fields.EmailField(

required=True,

error_messages={'required':'郵箱不能為空','invalid':'郵箱格式錯誤'}

)

usertype_id = fields.ChoiceField(choices=[])

tn_id = fields.MultipleChoiceField(choices=[]) #增加字段,是技能ID

def __init__(self,*args,**kwargs):

super(UserForm, self).__init__(*args,**kwargs)

self.fields['usertype_id'].choices=UserType.objects.values_list('id','title')

self.fields['tn_id'].choices=Technique.objects.values_list('id','title')

#實時從資料庫擷取資料

模版檔案中添加:

技能:{{ form.tn_id }} {{ form.errors.tn_id.0 }}

views.py,添加和編輯

def adduser(request):

form = UserForm() # 執行個體化得到一個form表單對象

if request.method == 'POST':

form = UserForm(data=request.POST)

# 将前端擷取的資料傳入到forms.UserForm類,執行個體化得到一個包含字段值的表單對象form

print(request.POST)

if form.is_valid(): # is_valid()得到一個布爾值,即判斷傳入的字段值是否全部有效

print(form.cleaned_data) #form.cleaned_data是字典類型,得到全部字段的有效值,即幹淨的資料

tn_id_list = form.cleaned_data.pop('tn_id') # 因為多對多字段不能直接添加,是以需要從幹淨資料中彈出,再通過obj.tn.add()方式添加

obj = UserInfo.objects.create(**form.cleaned_data) # 直接插入資料庫,form字段必須與models中定義的一緻

obj.tn.add(*tn_id_list) #添加多對多字段,add()添加一個清單時,必須加*星号

return redirect('/index/')

else:

print(form.errors) # 得到錯誤資訊,并生成html标簽,哪個字段值有誤,就會對應産生哪個字段的錯誤資訊

return render(request,'adduser.html',locals())

def edituser(request,pk):

# 參數pk是urls傳入的id

obj = UserInfo.objects.get(id=pk)

tn_id_tuple_list = obj.tn.values_list('id') # 得到

tn_id_list = list(zip(*tn_id_tuple_list))[0] if tn_id_tuple_list else [] #三元運算

form = UserForm(initial={'username':obj.username,

'password':obj.password,

'email':obj.email,

'usertype_id':obj.usertype.id,

'tn_id':tn_id_list})

if request.method == 'POST':

form = UserForm(data=request.POST)

if form.is_valid():

tn_id_list = form.cleaned_data.pop('tn_id') #彈出多對多字段

query_set = UserInfo.objects.filter(id=pk)

query_set.update(**form.cleaned_data)

obj = query_set.first()

obj.tn.set(tn_id_list) # 直接覆寫原有的技能ID值,set()方法傳入的清單前面不能加*星号

return redirect('/index/')

return render(request, 'edituser.html', locals())

注意:

多對多字段tn_id無法直接通過UserInfo.objects.create(**form.cleaned_data)這樣的方式直接添加至資料庫,隻能先将其從cleaned_data中用.pop('tn_id')方法彈出并指派給tn_id_list,再通過obj.tn.add(*tn_id_list)方式添加至資料庫中。

list(zip(*tn_id_tuple_list))得到的結果是類似這樣[(5, 6)],zip()函數可以将[(11,22,33),(44,55,66)]這樣的資料變換為[(11,44,),(22,55,),(33,66,)]這樣。

8. widgets插件,給Form生成的标簽添加class等屬性

通過widgets插件實作,必須先導入from django.forms import widgets

forms.py内容如下:

from django.forms import Form

from django.forms import fields

from django.forms import widgets

from .models import *

class UserForm(Form):

username = fields.CharField(

required=True,

error_messages={'required':'使用者名不能為空'},

widget = widgets.TextInput(attrs={'class': 'form-control'})

)

password = fields.CharField(

required=True,

error_messages={'required':'密碼不能為空'},

widget=widgets.PasswordInput(attrs={'class': 'form-control'})

)

email = fields.EmailField(

required=True,

error_messages={'required':'郵箱不能為空','invalid':'郵箱格式錯誤'},

widget=widgets.EmailInput(attrs={'class': 'form-control'})

)

usertype_id = fields.ChoiceField(

choices=[],

widget=widgets.Select(attrs={'class': 'form-control'})

)

tn_id = fields.MultipleChoiceField(

choices=[],

widget=widgets.SelectMultiple(attrs={'class': 'form-control'})

)

def __init__(self,*args,**kwargs):

super(UserForm, self).__init__(*args,**kwargs)

self.fields['usertype_id'].choices=UserType.objects.values_list('id','title')

self.fields['tn_id'].choices=Technique.objects.values_list('id','title')

注意:Form元件中,密碼字段想要在輸入時密文顯示,就必須利用插件widget=widgets.PasswordInput()

9. ajax配合Form元件,實作無重新整理驗證

以下代碼基于第8節中的forms.py。

views.py相關代碼:

import json

def reg(request):

form = UserForm()

if request.method == 'POST':

flag = {'status': True, 'msg': None}

form = UserForm(data=request.POST)

if form.is_valid():

tn_id_list = form.cleaned_data.pop('tn_id')

obj = UserInfo.objects.create(**form.cleaned_data)

obj.tn.add(*tn_id_list)

else:

flag['status'] = False

flag['msg'] = form.errors #将form驗證的錯誤資訊傳遞到給前端JS

return HttpResponse(json.dumps(flag))

return render(request,'reg.html',locals())

html和JS相關代碼:

{% csrf_token %}

使用者名: {{ form.username }}

密碼: {{ form.password }}

郵箱: {{ form.email }}

使用者類型: {{ form.usertype_id }}

技能: {{ form.tn_id }}

送出

$('.submit').click(function () {

$('#f1 .error').remove(); //避免重複出現錯誤提示

$.ajax('/reg/',{

type:'post',

data:$('#f1').serialize(),

dataType:'json',

success:function (retn_data) {

if (retn_data.status){

location.href='/index/'

} else {

$.each(retn_data.msg,function (key,value) {

//循環服務端傳過來的驗證後的錯誤資訊(字典形式)

//key是字典的鍵,也就是每個字段名稱;value是每個字段的錯誤資訊

var $span=$('');

$span.addClass('error');

$span.html(value[0]);

$('#f1 input[name="'+key+'"]').after($span)

//通過字元串拼接,得到$('#f1 input[name="username"]')

})

}

}

})

})

注意:通過ajax發送資料,當送出的字段資料為空或者格式出錯時,錯誤資訊隻能通過JS将服務端回傳的錯誤資訊加載至指定位置,而不能直接在模闆檔案中使用{{ form.errors.username.0 }}這種方式顯示錯誤資訊。

10. 自定義驗證規則

方式一:

在字段定義時,加入validators參數,其值為一個清單,清單中可包含多個正則驗證器,該字段值會依次與這些正則驗證器進行比對。

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開頭')],

)

方式二:

自定義一個驗證函數,并在定義字段時,将這個驗證函數放在validators等于的清單中,假如驗證函數名為mobile_validate,就這樣寫:validators=[mobile_validate,]

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'郵箱'}))

方式三:

在Form類中自定義一個方法,方法名稱有固定格式:clean_字段名稱。

Django内部會自動執行該方法對字段進行驗證,這個方法必須将驗證正确的值傳回。驗證錯誤需要抛出ValidationError錯誤。

from django import forms

from django.forms import fields

from django.forms import widgets

from django.core.exceptions import ValidationError

from django.core.validators import RegexValidator

class FInfo(forms.Form):

username = fields.CharField(max_length=5,

validators=[RegexValidator(r'^[0-9]+$', 'Enter a valid extension.', 'invalid')], )

email = fields.EmailField()

def clean_username(self):

"""

Form中字段中定義的格式比對完之後,執行此方法進行驗證

:return:

"""

value = self.cleaned_data['username']

if "666" in value:

raise ValidationError('666已經被玩爛了...', 'invalid')

return value

對所有字段做整體驗證

需求:在新增賬號時,填寫密碼後,還要再次填寫确認密碼,然後比較兩次密碼是否一緻,一緻後才能正常注冊。

對所有字段做整體驗證,django預留了clean鈎子方法,可以自定制處理邏輯代碼,但是clean方法必須傳回self.cleaned_data,如下列示例代碼

from django.forms import Form

from django.forms import fields

from django.forms import widgets

from django.core.exceptions import ValidationError

class RegForm(Form):

username = fields.CharField()

pwd = fields.CharField()

pwd_confirm = fields.CharField()

phone = fields.CharField()

def clean_phone(self):

value = self.cleaned_data['phone']

mobile_regex = re.compile(r'^1[3578][0-9]{9}$')

if not mobile_regex.match(value):

raise ValidationError('手機号碼格式錯誤')

return value

def clean(self):

if self.is_valid(): # 先確定每個輸入框都不為空

pwd = self.cleaned_data['pwd']

pwd_confirm = self.cleaned_data['pwd_confirm']

if not pwd == pwd_confirm:

self.add_error('pwd',ValidationError('确認密碼輸入不一緻'))

self.add_error('pwd_confirm',ValidationError('确認密碼輸入不一緻'))

return self.cleaned_data

return self.cleaned_data

上述代碼中,如果兩次密碼不一緻,則分别給對應字段添加一個錯誤資訊。

效果如下圖:

django.forms生成HTML,第21天,Django之Form元件添加使用者

image.png

11. 常用插件

class RegisterForm(Form):

# 文本輸入框

name = fields.CharField(

widget=widgets.TextInput(attrs={'class': 'c1'})

)

# 郵箱位址輸入框

email = fields.EmailField(

widget=widgets.EmailInput(attrs={'class':'c1'})

)

# 文本域

phone = fields.CharField(

widget=widgets.Textarea(attrs={'class':'c1'})

)

# 密文輸入框

pwd = fields.CharField(

widget=widgets.PasswordInput(attrs={'class':'c1'})

)

pwd_confirm = fields.CharField(

widget=widgets.PasswordInput(attrs={'class': 'c1'})

)

# 單選:select

# city = fields.ChoiceField(

# choices=[(0,"上海"),(1,'北京')],

# widget=widgets.Select(attrs={'class': 'c1'})

# )

# 多選:select

# city = fields.MultipleChoiceField(

# choices=[(1,"上海"),(2,'北京')],

# widget=widgets.SelectMultiple(attrs={'class': 'c1'})

# )

# 單選:checkbox

# city = fields.CharField(

# widget=widgets.CheckboxInput()

# )

# 多選:checkbox

# city = fields.MultipleChoiceField(

# choices=((1, '上海'), (2, '北京'),),

# widget=widgets.CheckboxSelectMultiple

# )

# 單選:radio

# city = fields.CharField(

# initial=2,

# widget=widgets.RadioSelect(choices=((1,'上海'),(2,'北京'),))

# )

注意:單選插件的值為字元串,多選插件的值為清單;寫預設值時,多選值對應清單,如:form = RegisterForm(initial={'city':[1,2],'name':'alex'})