Django Form和ModelForm


Form

局部鈎子、全局鈎子

ModelForm

兩種的區別

          Form介紹

form組件的主要功能如下:

  • 生成頁面可用的HTML標簽
  • 對用戶提交的數據進行校驗
  • 保留上次輸入內容

        使用form組件實現書籍的增加功能

首先定義一個類

在views.py中定義一個BookForm的類

from django import forms

#自定義一個form類
class BookForm(forms.Form):
    title = forms.CharField(
        max_length = 12,
        max_length = 6,
        label = "書名",        #頁面 label標簽顯示的中文名
        widget = forms.widgets.TextInput(attrs={"class":"form-control"})        #widget 插件
    )

    publish_date = forms.DateField(
        label = "出版日期"
        widget = forms.widgets.DateInput(attrs={"type":"date","class":"form-control"})
    )

    phone = forms.CharField(
        label = "手機號",
        max_length = 11,
        required = True,    #是否允許為空
        widget = forms.widgets.TextInput(attrs={"class":"form-control"})
    )

    publisher = forms.ModelChoiceField(
        queryset = models.Publisher.objects.all(),    #queryset:查詢數據庫中的數據
        widget = forms.widgets.Select(attrs = {"class":"form-control})
    )

    authors = forms.ModelMultipleChoiceField(
        queryset = models.Author.objects.all(),
        widget = forms.widgets.SelectMultiple(attrs={"class":"form-control"})
    )
views.py

對應的html操作

<form action="" method="post" novalidate autocomplete="off">

    {%csrf_token%}

    {% for field in form_obj %}
        <div class="form-group">
            <label for="{{ field.id_for_label }}">{{ field.label }}</label>
            {{ field }}
            <p> {{ field.errors.0 }}</p>
        </div>
    {% endfor %}

    <input type="submit" class="btn btn-info">
</form>
addbook.html

 添加書籍函數

def addbook(request):
    form_obj = BookForm()
    if request.POST:
        #做校驗
        form_obj = BookForm(request.POST)
        if form_obj.is_valid(): #做數據有效性的校驗
            # 因為有多對多的字段,需要額外處理
            authors = form_obj.cleaned_data.pop("authors")
            # 創建新書籍對象
            book_obj = models.Book.objects.create(**form_obj.cleaned_data)
            # 將書籍對象和作者建立關聯
            book_obj.authors.add(*authors)

            return redirect('/book_list/')

    return render(request,'addbook.html',locals())
views.py

          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="密碼")

        radioSelect

單radio值為字符串,choices不建議固定,具體方法下方有說明

class LoginForm(forms.Form):
    ...
    ...
    gender = forms.fields.ChoiceField(
        choices = ((1,''), (2,''), (3,'保密')),
        label = "性別",
        initial = 3,
        widget = forms.widgets.RadioSelect()
    )

        單選select

class LoginForm(forms.Form):
    ...
    hobby = forms.fields.ChoiceField(
        choice = ((1, "籃球"), (2, "足球"), (3, "雙色球"), ),
        label = "愛好",
        initial = 3,
        widget = forms.widgets.Select()
    )

        多選select

class LoginForm(forms.Form):
    ...
    hobby = forms.fields.MultipleChoiceField(
        choices=((1, "籃球"), (2, "足球"), (3, "雙色球"), ),
        label="愛好",
        initial=[1, 3],
        widget = forms.widgets.SelectMultiple()
    )

        單選CheckBox

class Login(forms.Form):
    ...
    keep = forms.fields.ChoiceField(
        label="是否記住密碼",
        initial="checked",
        widget = forms.widgets.CheckboxInput()
    )

        多選checkbox

class LoginForm(forms.Form):
    ...
    hobby = forms.fields.MultipleChoiceField(
        choices=((1, "籃球"), (2, "足球"), (3, "雙色球"),),
        label="愛好",
        initial=[1, 3],
        widget = forms.widgets.CheckboxSelectMultiple()
    )

關於choice的注意事項

在使用選擇標簽時,需要注意choices的選項可以從數據庫中獲取,但是由於是靜態字段,**獲取的值無法實時更新**,那么需要自定義構造方法從而達到此目的

具體方法:

class FInfo(forms.Form):
    ...
    publisher = forms.ModelChoiceField(
        queryset=models.Publisher.objects.all(),
        widget=forms.widgets.Select(attrs={"class": "form-control"})
    )

          Django 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,              最小值


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


RegexField(CharField)
    regex,                      自定制正則表達式
    max_length=None,            最大長度
    min_length=None,            最小長度
    error_message=None,         忽略,錯誤信息使用 error_messages={'invalid': '...'}


FileField(Field)
    allow_empty_file=False     是否允許空文件


ImageField(FileField)      
    ...
    注:需要PIL模塊,pip3 install Pillow
    以上兩個字典使用時,需要注意兩點:
        - form表單中 enctype="multipart/form-data"
        - view函數中 obj = MyForm(request.POST, request.FILES)


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= ''            空值的默認值


TypedMultipleChoiceField(MultipleChoiceField)
    coerce = lambda val: val   對選中的每一個值進行一次轉換
    empty_value= ''            空值的默認值


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類型
Django Form內置字段

 自定義校驗規則

1、使用內置的正則校驗器: validators=[]
from django.core.validators import RegexValidator

class BookForm(forms.Form):
    phone = forms.CharField(
    max_length=11,
    validators=[RegexValidator(r'^1[35789]\d{9}$',"手機號格式不正確")]
    )
2、手寫一個校驗函數
from django.core.exceptions import ValidationError

# 自定義一個字段的校驗規則函數

def phone_validate(value):
    # 拿用戶填寫的手機號去數據庫中校驗
    is_exits = models.Book.objects.filter(phone=value)
    if is_exits:
        #如果手機號被注冊,不能再注冊
        raise ValidationError("該手機號已經被注冊")
    else:
        return value


# 第二步在自定義的BookForm類中phone字段添加phone_validate
from django.core.validators import RegexValidator

class BookForm(forms.Form):
    ...

    phone = forms.CharField(
        max_length=11,
        validators = [RegexValidator(r'^1[356789]\d{9}$','手機號格式不正確'),phone_validate]
    )
3、使用鈎子函數做校驗
局部鈎子(hook)
from django.core.exceptions import ValidationError

black_list = ["alex",]
class BookForm(forms.Form):
    ...

    # 定義一個局部鈎子,對title字段做有效性驗證
    def clean_title(self): 
        value = self.cleand_data.get("title") #判斷有沒有敏感詞
      #第一種方法:
        #if "alex" in value:
        #    raise ValidationError("alex已被和諧")
        #else:
        #    return value

        #第二種方法:
        for v in black_list:
            if v in value:
                raise ValidationError("{}為敏感詞".format(v))
        return value
全局鈎子 (兩個字段值做比較)
#全局鈎子,在form類內部定義一個 clean(self) 方法

black_list = ["alex",]

class BookForm(forms.Form):
    def clean(self):
        #取到所有字段的數據
        # self.add_error("字段","密碼和確認密碼不一致")

        title = self.cleand_data.get("title")
        for v in black_list:
            if v in title:
                self.add_error("title","包含敏感詞{}".format(v))
        return title

        form組件給html標簽設置默認值

# 以修改書籍功能為例:

def editbook(request,edit_id):
    book_obj = models.Book.objects.filter(id=edit_id).first()
    # 把ORM中一個對象快速轉換成字典格式
    from django.forms import model_to_dict
    book_dict = model_to_dict(book_obj)
    book_dict["publish_date"] = book_obj.publish_date.strftime("%Y-%m-%d")
    form_obj = BookForm(book_dict)

    if request.POST:
        # 從用戶提交過來的數據中取數據
        form_obj = BookForm(request.POST)
        if form_obj.is_valid():
            #去數據庫更新數據
            book_obj.title = form_obj.cleaned_data.get("title")
            book_obj.publish_date = form_obj.cleaned_data.get("publish_date")
            book_obj.publisher_id = form_obj.cleaned_data.get("publisher")
            book_obj.save()
            book_obj.authors.set(form_obj.cleaned_data.get("authors"))
            return redirect('/book_list/')

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

ORM中把一個對象轉換成字典格式

from django.forms import model_to_dict

book_obj = models.Book.objects.filter(id=edit_id).first()
book_dict = model_to_dict(book_obj)

補充進階

批量添加樣式

可通過重寫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'
            })

         ModelForm

form與model的終極結合,具有以下功能:驗證、數據庫操作。

# 寫一個和Model類一一對應的form
from django import forms
class BookModelForm(forms.ModelForm): class Meta: model = models.Book #對應的model中的類 fields = "__all__" # model類里所有的字段都展示 # fields = ["title",] # 指定展示某些字段 exclude = None #除了指定字段,其他字段都顯示 labels = { #設置label標簽名 "title":"書名", "phone":"手機號", "publish_date":"出版日期", "publisher":"出版社", "authors":"作者", } widgets = { #設置每個字段的插件信息 "title":forms.widgets.TextInput(attrs={"class":"form-control"}), "phone": forms.widgets.TextInput(attrs={"class": "form-control"}), "publish_date":forms.widgets.DateInput(attrs={"type":"date","class":"form-control"}), "publisher": forms.widgets.Select(attrs={"class": "form-control"}), "authors": forms.widgets.SelectMultiple(attrs={"class": "form-control"}), } error_messages = { #設置每個字段的報錯提示信息 "publisher":{ "required":"必須選一個出版社!" } }
class Meta: 常用參數
model = models.Student  # 對應的Model中的類
fields = "__all__"  # 字段,如果是__all__,就是表示列出所有的字段
exclude = None  # 排除的字段
labels = None  # 提示信息
help_texts = None  # 幫助提示信息
widgets = None  # 自定義插件
error_messages = None  # 自定義錯誤信息
instance = book_obj #實例名
def editbook(request,edit_id):
    book_obj = models.Book.objects.filter(id=edit_id).first()
    form_obj = BookModelForm(instance=book_obj)

    if request.POST:
        # 從用戶提交過來的數據中取數據
        form_obj = BookModelForm(request.POST,instance=book_obj)
        if form_obj.is_valid():
            #去數據庫更新數據
            form_obj.save()
            return redirect('/book_list/')

    return render(request, 'editbook.html', locals())
書籍編輯操作
def addbook(request):
    form_obj = BookModelForm()
    if request.POST:
        #做校驗
        form_obj = BookModelForm(request.POST)
        if form_obj.is_valid(): #做數據有效性的校驗
            form_obj.save()
            return redirect('/book_list/')

    # 實例化BookForm
    return render(request,'addbook.html',locals())
書籍添加操作

 

class Meta:
model,                            #對應model的類
fields                                #字段
exclude=None                    #排查的字段
labels=None                        #提示信息
widgets=None                    #自定義插件
error_messages=None        #自定義錯誤信息
field_classes=None                #自定義字段類

驗證執行過程:
is_valid -> full_clean --> 鈎子 --> 整體錯誤

字典字段驗證:
def clean_字段名(self):
    return "新值"

驗證:
model_form_obj.is_valid()
model_form_obj.clean()
model_form_obj.cleand_data

用於創建:
model_form_obj = XXOOModelForm(request.POST)

用於更新和初始化
obj = model.tb.objects.get(id=1)
model_form_obj = XXOOModelForm(request.POST,instance=obj)
Model Form組件

 

          Form和ModelForm區別

model可以操作數據庫字段,form驗證也有那幾個字段,雖然耦合度很低,但是代碼還是有重復的。如果使用ModelForm,可以利用model

里的字段,form里的字段就可以不用寫了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM