1. 程式人生 > 實用技巧 >23.Django(form元件 自定製校驗規則、鉤子、更改圖書管理系統)

23.Django(form元件 自定製校驗規則、鉤子、更改圖書管理系統)

自定製校驗規則

之前form表單給我們提供了一些校驗功能:

這些基本你的校驗功能,不足以滿足我們日常的需要,使用者名稱不允許出現敏感字元,電話號碼的驗證,等等。

  • 正則校驗器(validators)

    name = form.CharField(
    label='使用者名稱:',
    required=True, #必須填入內容的設定,預設為True
    initial='dong', #設定初始值
    min_length=6,
    max_length=12,
    help_text='使用者名稱不能有特殊字元',
    # 一個引數正則的匹配規則,第二引數不滿足這個匹配規則報的錯誤資訊
    validators=[RegexValidator(r'^金ping梅', '必須以金ping梅開頭'),
    RegexValidator(r'xx$', '必須以xx結尾'),],
    errpr_messages={'min_length': '太短了', 'max_length': '太長了',
    'required': '必須填寫內容'},
    widget=forms.widgets.TextInput,
    )

    這種正則校驗器比較侷限,用起來相對麻煩。

  • 自定製函式校驗

    (注意:模組的引入要向下面的規則寫)

    import re

    from django.shortcuts import render, HttpResponse
    from django import forms
    from django.core.validators import ValidationError


    def moblie_validate(value):
    # 我們這裡就給name欄位設定這個函式的的匹配規則
    # 那麼這個value就是name欄位實際得到的資料
    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 RegisterForm(forms.Form):
    """
    form元件生成前端的標籤寫法,與models構建的表結構非常相似
    """
    name = forms.CharField(
    label='使用者名稱:',
    required=True, # 必須填入內容的設定,預設為True
    initial='dong', # 設定初始值
    min_length=8,
    max_length=14,
    help_text='使用者名稱不能有特殊字元',
    # validators=[RegexValidator(r'^金ping梅', '必須以金ping梅開頭'), RegexValidator(r'xxx$', '必須以xxx結尾')],
    error_messages={'min_length': '太短了', 'max_length': '太長了', 'required': '必須填寫內容'},
    validators=[moblie_validate],
    widget=forms.widgets.TextInput(),
    )

    password = forms.CharField(
    label='密碼:',
    min_length=6,
    help_text='密碼不得少於8位',
    # widget這個功能可以當成一個工具,這個工具就是確認標籤型別以及i增加標籤屬性的
    error_messages={'min_length': '太短了'},
    widget=forms.widgets.PasswordInput,
    )

鉤子

form元件給你提供了另一個自定製校驗規則的介面:區域性鉤子,全域性鉤子

  • 區域性鉤子

    比如,你的使用者名稱不能出現一些敏感詞彙,敏感詞彙有:

    東京熱,蒼⽼師,⾦ping梅,..... 這些⽤正則匹配有點⼉⼒不從⼼了。所以我們就得⽤鉤 ⼦去解決。

    區域性鉤子是針對某個欄位校驗的。

    import re

    from django.shortcuts import render, HttpResponse
    from django import forms
    from django.core.validators import ValidationError


    class RegisterForm(forms.Form):
    """
    form元件生成前端的標籤寫法,與models構建的表結構非常相似
    """

    name = forms.CharField(
    label='使用者名稱:',
    required=True, # 必須填入內容的設定,預設為True
    initial='dong', # 設定初始值
    min_length=8,
    max_length=14,
    help_text='使用者名稱不能有特殊字元',
    # validators=[RegexValidator(r'^金ping梅', '必須以金ping梅開頭'), RegexValidator(r'xxx$', '必須以xxx結尾')],
    error_messages={'min_length': '太短了', 'max_length': '太長了', 'required': '必須填寫內容'},
    # validators=[moblie_validate],
    widget=forms.widgets.TextInput(),
    )

    password = forms.CharField(
    label='密碼:',
    min_length=6,
    help_text='密碼不得少於8位',
    # widget這個功能可以當成一個工具,這個工具就是確認標籤型別以及i增加標籤屬性的
    error_messages={'min_length': '太短了'},
    widget=forms.widgets.PasswordInput,
    )

    def clean_name(self): # 針對於name欄位做一個用於校驗的區域性鉤子
    print('進入clean_name區域性鉤子')
    sensitive_vocabulary = ['京東熱', '蒼老師', '金ping梅']
    name_value = self.cleaned_data['name']
    for i in sensitive_vocabulary:
    if i in name_value:
    raise ValidationError(f'含有敏感詞彙{i}')
    else:
    return self.cleaned_data['name'] # 必須將所有的驗證過後的資料返回

    注意:
    區域性鉤⼦命名:clean_欄位名
    如果有錯誤:主動丟擲異常 ValidationError
    校驗成功之後,必須返回self.cleaned_data['欄位名']
  • 全域性鉤子

    全域性鉤子就是對form類中的所有的欄位做一個對比驗證, 或者橫向驗證。我們一般註冊時,都需要輸入使用者名稱、密碼。再次輸入密碼。這樣的需求在全域性鉤子中做一些驗證比較合適。

    import re

    from django.shortcuts import render, HttpResponse
    from django import forms
    from django.core.validators import ValidationError



    class RegisterForm(forms.Form):
    """
    form元件生成前端的標籤寫法,與models構建的表結構非常相似
    """

    name = forms.CharField(
    label='使用者名稱:',
    required=True, # 必須填入內容的設定,預設為True
    initial='dong', # 設定初始值
    min_length=8,
    max_length=14,
    help_text='使用者名稱不能有特殊字元',
    # validators=[RegexValidator(r'^金ping梅', '必須以金ping梅開頭'), RegexValidator(r'xxx$', '必須以xxx結尾')],
    error_messages={'min_length': '太短了', 'max_length': '太長了', 'required': '必須填寫內容'},
    # validators=[moblie_validate],
    widget=forms.widgets.TextInput(),
    )

    password = forms.CharField(
    label='密碼:',
    min_length=6,
    help_text='密碼不得少於8位',
    # widget這個功能可以當成一個工具,這個工具就是確認標籤型別以及i增加標籤屬性的
    error_messages={'min_length': '太短了'},
    widget=forms.widgets.PasswordInput,
    )
    confirm_password = forms.CharField(
    label='再次密碼:',
    min_length=6,
    help_text='密碼不得少於8位',
    # widget這個功能可以當成一個工具,這個工具就是確認標籤型別以及i增加標籤屬性的
    error_messages={'min_length': '太短了'},
    widget=forms.widgets.PasswordInput,
    )

    def clean_name(self): # 針對於name欄位做一個用於校驗的區域性鉤子
    print('進入clean_name區域性鉤子')
    sensitive_vocabulary = ['京東熱', '蒼老師', '金ping梅']
    name_value = self.cleaned_data['name']
    for i in sensitive_vocabulary:
    if i in name_value:
    raise ValidationError(f'含有敏感詞彙{i}')
    else:
    return self.cleaned_data # 必須將所有的驗證過後的資料返回

    def clean(self): # 全域性鉤子
    v1 = self.cleaned_data['password'],
    v2 = self.cleaned_data['confirm_password'],
    if v1 == v2:
    return self.cleaned_data # 驗證成功必須返回
    else:
    # confirm_password新增錯誤資訊
    self.add_error('confirm_password', '兩次密碼不一樣')
    # 主動丟擲一個異常
    raise ValidationError('兩次密碼不一樣')

圖書管理系統:

將增加的那個⻚⾯,的前端的標籤,利⽤form元件⾃動⽣成,並且可以進⾏簡單的驗證。

1、在views建立form元件

class BookFrom(forms.Form):
"""
form元件生成前端的標籤
"""
book_name = forms.CharField(
label='書籍名稱:',
required=True,
initial='書名',
min_length=1,
max_length=20,
help_text='書名不能有特殊字元',
error_messages={'min_length': '太短了', 'max_length': '太長了', 'required': '必須填寫內容'},
widget=forms.widgets.TextInput(),
)
book_date = forms.CharField(
label='出版日期:',
widget=forms.widgets.TextInput(attrs={'type': 'date'}), # 標籤型別
)
book_price = forms.DecimalField(
label='書籍價格:',
max_digits=5,
decimal_places=2,
help_text='人民幣(¥)',
widget=forms.widgets.TextInput(attrs={'type': 'number'})
)
book_publish = forms.ChoiceField(
label='出版社:',
initial=1,
choices=((1, 'xx出版社'), (2, 'qq出版社'), (3, 'aa出版社')),
widget=forms.widgets.Select(), # 標籤型別
)
book_author = forms.MultipleChoiceField(
label='作者:',
initial=(1, 2), # 設定初始值
choices=((1, '張三'), (2, '李四'), (3, '王五')),
widget=forms.widgets.SelectMultiple(), # 標籤型別
)

2、更改前端頁面

{% load static %}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link href="{% static 'bootstrap.min.css' %}" rel="stylesheet">
<link href="{% static 'dashboard.css' %}" rel="stylesheet">
<style>
#frame {
width: 350px;
height: 400px;
border: 1px solid black;
margin: 180px auto;
}

div {
margin-top: 20px;
margin-left: 20px;
}

.pull {
margin-top: 50px;
margin-left: 150px;
}

select {
margin-left: 17px;
}
</style>
</head>
<body>
<h1>新增書籍頁面</h1>
<div class="row">
<div class="col-md-5 col-md-offset-3">
<form action="" method="post" class="form-horizontal">
{% csrf_token %}
<div>
<label for="">{{ form_obj.book_name.label }}</label>
{{ form_obj.book_name }}
{{ form_obj.book_name.help_text }}
</div>
<div>
<label for="">{{ form_obj.book_date.label }}</label>
{{ form_obj.book_date }}
</div>
<div>
<label for="">{{ form_obj.book_price.label }}</label>
{{ form_obj.book_price }}
{{ form_obj.book_price.help_text }}
</div>
<div>
<label for="">{{ form_obj.book_publish.label }}</label>
{{ form_obj.book_publish }}
</div>
<div>
<label for="">{{ form_obj.book_author.label }}</label>
{{ form_obj.book_author }}
</div>
</form>
</div>
</div>


</body>
</html>

增加bootstrap樣式:

在BookFrom類裡面寫上這個函式:

   def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
print(self.fields)
for obj in self.fields.values():
obj.widget.attrs.update({'class': 'form-control'})

點選標籤名稱:inptu自動獲取游標

讓label的for = {{ form_obj.欄位名.id_for_label }}

{% load static %}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link href="{% static 'bootstrap.min.css' %}" rel="stylesheet">
<link href="{% static 'dashboard.css' %}" rel="stylesheet">
<style>
#frame {
width: 350px;
height: 400px;
border: 1px solid black;
margin: 180px auto;
}

div {
margin-top: 20px;
margin-left: 20px;
}

.pull {
margin-top: 50px;
margin-left: 150px;
}

select {
margin-left: 17px;
}
</style>
</head>
<body>
<h1>新增書籍頁面</h1>
<div class="row">
<div class="col-md-5 col-md-offset-3">
<form action="" method="post" class="form-horizontal">
{% csrf_token %}
<div>
<label for="{{ form_obj.book_name.id_for_label }}">{{ form_obj.book_name.label }}</label>
{{ form_obj.book_name }}
{{ form_obj.book_name.help_text }}
</div>
<div>
<label for="{{ form_obj.book_date.id_for_label }}">{{ form_obj.book_date.label }}</label>
{{ form_obj.book_date }}
</div>
<div>
<label for="{{ form_obj.book_price.id_for_label }}">{{ form_obj.book_price.label }}</label>
{{ form_obj.book_price }}
{{ form_obj.book_price.help_text }}
</div>
<div>
<label for="{{ form_obj.book_publish.id_for_label }}">{{ form_obj.book_publish.label }}</label>
{{ form_obj.book_publish }}
</div>
<div>
<label for="{{ form_obj.book_author.id_for_label }}">{{ form_obj.book_author.label }}</label>
{{ form_obj.book_author }}
</div>
</form>
</div>
</div>


</body>
</html>

3、獲取資料的真是出版社以及作者資料

class BookFrom(forms.Form):
    """
    form元件生成前端的標籤
    """
    book_name = forms.CharField(
        label='書籍名稱:',
        required=True,
        initial='書名',
        min_length=1,
        max_length=20,
        help_text='書名不能有特殊字元',
        error_messages={'min_length': '太短了', 'max_length': '太長了', 'required': '必須填寫內容'},
        widget=forms.widgets.TextInput(),
    )
    book_date = forms.CharField(
        label='出版日期:',
        widget=forms.widgets.TextInput(attrs={'type': 'date'}),  # 標籤型別
    )
    book_price = forms.DecimalField(
        label='書籍價格:',
        max_digits=5,
        decimal_places=2,
        help_text='人民幣(¥)',
        widget=forms.widgets.TextInput(attrs={'type': 'number'})
    )
    book_publish = forms.ModelChoiceField(
        label='出版社:',
        initial=1,
        # choices=((1, 'xx出版社'), (2, 'qq出版社'), (3, 'aa出版社')),
        queryset=models.Publish.objects.all(),
        widget=forms.widgets.Select(),  # 標籤型別
    )
    book_author = forms.ModelMultipleChoiceField(
        label='作者:',
        initial=(1, 2),  # 設定初始值
        # choices=((1, '張三'), (2, '李四'), (3, '王五')),
        queryset=models.Author.objects.all(),
        widget=forms.widgets.SelectMultiple(),  # 標籤型別
    )

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        print(self.fields)
        for obj in self.fields.values():
            obj.widget.attrs.update({'class': 'form-control'})

4、將post提交的資料寫入資料庫

def add(request):
    form_obj = BookFrom()
    if request.method == 'GET':
        return render(request, 'add.html', {'form_obj': form_obj})
    else:
        form_obj = BookFrom(request.POST)
        if form_obj.is_valid():
            authors = form_obj.cleaned_data.pop('author')
            book_obj = models.Book.objects.create(**form_obj.cleaned_data)
            book_obj.author.add(*authors)
            return redirect('home')
        else:
            return render(request, 'add.html', {'form_obj': form_obj})