四【用django2.0來開發(fā)】后臺會員管理(二) ModelForm表單的使用方法以及數(shù)據(jù)驗證

上一節(jié)我們講完了ModelAdmin的使用, 但是在操作中也發(fā)現(xiàn), 新增編輯會員時, 我們無法驗證數(shù)據(jù)是否正確, 比如

  1. 用戶名、手機號号显、郵箱都應該是唯一的
  2. 用戶名和密碼長度的驗證
  3. 編輯用戶信息時不填寫密碼則不更新密碼, 填寫了則更新密碼
  4. 自定義驗證不通過的文案
    ...

這些要求, 我們就必須得使用自定義的表單來完成了

項目地址:https://gitee.com/ccnv07/django_example

Form介紹

通過表單, 我們可以實現(xiàn)以下的功能

  1. 自定義字段的樣式
  2. 類似的表單可以通過類繼承減少代碼量
  3. 完成自定義的表單數(shù)據(jù)驗證

定義表單字段

關于表單的代碼我們一般放在每個模塊的forms.py中


image.png

表單字段類型說明

CharField
單行文本輸入字段, 對應模型的CharField字段
表單中的樣式就是input type=text
max_length: 最大長度
min_length: 最小長度
strip: 是否過濾左右的空格
empty_value: 為空時的值, 默認是空字符串

EmailField
郵箱輸入文本字段, 對應模型的EmailField字段
標案中是input type=text
但是會自動增加一個郵箱格式的校驗

ChoiceField
下拉單選字段, 這個在模型中是沒有的
對應表單的select標簽
choices參數(shù)也是二維元組的格式

BooleanField
選擇字段, 對應表單中的checkbox

DateField
日期選擇字段
input_formats: 定義時間格式, 默認是:

['%Y-%m-%d',      # '2006-10-25'
 '%m/%d/%Y',      # '10/25/2006'
 '%m/%d/%y']      # '10/25/06'

DateTimeField
日期時間字段, 同DateField

TimeField
時間字段, 同DateField

DecimalField
十進制數(shù)字字段
max_value: 最大值
min_value: 最小值
max_digits: 前置0被去除后的最大位數(shù)
decimal_places: 允許的小數(shù)位的長度

FileField
文件上傳字段

IntegerField
整數(shù)字段
max_value: 最大值
min_value: 最小值

剩下還有很多表單字段類型, 在之后的教程中再繼續(xù)介紹臭猜, 目前這些, 就算是比較常見的字段了

通過介紹以后, 大家發(fā)現(xiàn), 我沒說最常見的PasswordField, 其實django的表單中就沒有這個字段類型, 那么, 如何實現(xiàn)密碼字段呢?

password = forms.CharField(widget=forms.PasswordInput(),max_length=12,min_length=6, strip=True,
        help_text='編輯時為空則不更改密碼')

通過制定widget參數(shù)為forms.PasswordInput(), 就可以實現(xiàn)密碼字段了

所以其實并不是Form類來規(guī)定表單字段的類型, 而是widget來實現(xiàn)的表單字段的類型, 對于每種表單字段的類型, django都有對應的模板, 通過字段的參數(shù), 生成對應的html代碼

表單字段的通用參數(shù)

label: 表單字段的label標簽的名稱
widget: 指定此字段采用的字段樣式類型
help_text: 字段的幫助文本

表單的Meta 元類

只有ModelForm才有元類, Form是不需要元類的
以下是一個元類的例子

from django.forms import ModelForm
class Meta:
        model = Account
        # 使用自定義的Form, 就必須指定fields or exclude屬性, 否則報錯
        fields = ('account', 'password', 'email', 'phone', 'status')
        error_messages = {
            NON_FIELD_ERRORS: {
                'unique_together': "%(model_name)s's %(field_labels)s are not unique.",
            }
        }

model: 指定ModelForm綁定的模型
fields: 指定后臺新增編輯時要顯示的字段
error_messages: 指定通用的錯誤信息文案

其他還有一些復雜的操作, 會在之后的教程中詳細講解。現(xiàn)在我們的一個表單就完成了

將表單綁定到ModelAdmin中

但是如果想讓表單在后臺中生效, 就需要把表單綁定到ModelAdmin上

from django.contrib import admin
from .forms import AccountForm

@admin.register(Account)
class AccountAdmin(admin.ModelAdmin):
    form = AccountForm

在ModelAdmin中指定form參數(shù), 就可以把表單綁定上去了押蚤。 點擊新增/編輯頁面, 也就可以看到表單生效了

image.png

表單數(shù)據(jù)的驗證

接下來, 我們就需要完成表單提交數(shù)據(jù)以后的驗證的方法了
其實在定義表單字段時, 我們就已經(jīng)完成了一部分的驗證了
比如

 account = forms.CharField(
        required=True, error_messages={
            'required': '請輸入用戶名',
        }, label='用戶名')

就定義了account字段必須填寫, 如果出錯則返回“請輸入用戶名”的提示

但是這個并不能完成我們所有的驗證, 所以我們也可以根據(jù)字段進行自定義的驗證
比如, 我要實現(xiàn)account用戶名字段是唯一的

from django import forms
from django.core.exceptions import ValidationError
from .models import Account

class AccountForm(forms.ModelForm):
    ...省略代碼
    def clean_account(self):
        _info = Account.objects.filter(account=self.cleaned_data['account'],
                is_deleted=0).values('id')

        if _info:
            raise ValidationError('用戶已存在')
        return self.cleaned_data['account']

當我們執(zhí)行form.is_valid()方法進行驗證時, django的form類會依次執(zhí)行clean_字段名的自定義驗證方法, 如果有拋出異常(raise ValidationError('用戶已存在')), 則中斷并返回錯誤, 否則讀取到clean_字段名的方法的返回值, 并且寫入到cleaned_data這個字典中

根據(jù)同樣的方法, 我們也可以寫出來對email和phone字段的驗證

在ModelAdmin操作Form

以上已經(jīng)基本能實現(xiàn)我們的功能了, 但是在后臺的操作中, 新增和編輯用的是同一個表單, 如果我們需要針對新增和編輯的不同場景, 進行一些不同的操作, 就比較麻煩了蔑歌。
所以我們需要通過在ModelAdmin中, 對Form進行一定的操作, 似的Form可以完成更多的判斷

# account/admin.py
class AccountAdmin(admin.ModelAdmin):
    def get_form(self, request, obj=None, **kwargs):
        form = super(AccountAdmin, self).get_form(request, obj=obj, **kwargs)

        # obj 保存的是models.Account的信息
        # 根據(jù)是否有pk, 來賦予form不同的場景, 根據(jù)不同的場景可以進行不同的驗證
        if (obj is not None):
            form.id = obj.pk
            form.scene = 'update'
        else:
            form.scene = 'insert'
        return form

get_form方法的參數(shù)request保存的是HttpRequest操作對象
而obj是當前操作的數(shù)據(jù)模型對象(Model), 而且在新增的操作時, obj是None, 只有在編輯時, 才存在obj

form = super(AccountAdmin, self).get_form(request, obj=obj, **kwargs) 會返回當前操作的form對象
如果obj不是None, 則當前操作是編輯, 我們就可以給form增加一個自定義的屬性scene(場景) = 'update', 否則就是新增

并且我們在Form表單中, 可以針對不同的場景, 進行不同的驗證判斷

# account/forms.py
class AccountForm(forms.ModelForm):
    def clean_account(self):
        # 自動驗證account字段
        if self.scene == 'insert':
            _info = Account.objects.filter(
                account=self.cleaned_data['account'],
                is_deleted=0).values('id')

        elif self.scene == 'update':
            _info = Account.objects.filter(~Q(id=self.id) & Q(
                account=self.cleaned_data['account']) & Q(
                    is_deleted=0)).values('id')

        if _info:
            raise ValidationError('用戶已存在')
        return self.cleaned_data['account']

這個驗證的意思是, 如果當前場景(self.scene)是insert, 就只按照account查詢, 如果是update, 則增加id不為當前操作id的過濾條件


image.png

Django的密碼加密方法

django.contrib.auth.hashers 有兩個關于密碼加密的操作方法
make_password和check_password

make_password 是將指定的明文密碼加密
check_password 是校驗給出的明文密碼是否正確

image.png

新增與編輯操作下密碼框的不同操作

當新增用戶時, 密碼框必填, 當編輯時, 密碼框非必填。如果填了, 則修改密碼, 如果沒填, 則不更改密碼

第一步, 我們在校驗輸入的密碼時, 同時也需要實現(xiàn)對密碼的加密(畢竟數(shù)據(jù)庫被人破解了, 后臺還是很嚴重的)

from django import forms
class AccountForm(forms.ModelForm):
    def clean_password(self):
        # 自動驗證密碼字段
        if self.scene == 'insert':
            if not self.cleaned_data['password']:
                raise ValidationError('請輸入密碼')
        elif self.scene == 'update':
            if not self.cleaned_data['password']:
                return None
            else:
                return self.cleaned_data['password']
        return make_password(self.cleaned_data['password'])

這個也很好理解, 如果新增用戶時, 未輸入密碼, 則返回錯誤
更新時沒有輸入密碼, 則返回None
如果輸入, 就返回make_password加密后的密碼字符串

根據(jù)之前教程的scene場景參數(shù)的指定, 就可以跟容易的實現(xiàn)這個功能, 但是在編輯時, 如果密碼為空不填的話, 密碼居然也會被設置為空揽碘。

這個是因為, 如果不填寫密碼, 的model對象會把password=None一直帶著, 轉換為sql執(zhí)行時, 就變成password=''了

所以, 如果password沒有輸入值, 我們就要在執(zhí)行保存之前, 干掉model攜帶的password, 這樣才正確丐膝。

重寫ModelAdmin的保存方法

class AccountAdmin(admin.ModelAdmin):
    def save_model(self, request, obj, form, change):
        if form.cleaned_data['password'] is None:
            del obj.password
        super().save_model(request, obj, form, change)

form.cleaned_data中就是表單提交后驗證過的數(shù)據(jù), 如果password是None, 就del掉, 然后調(diào)用父類的save_model方法, 繼續(xù)執(zhí)行保存操作。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末钾菊,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子偎肃,更是在濱河造成了極大的恐慌煞烫,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件累颂,死亡現(xiàn)場離奇詭異滞详,居然都是意外死亡,警方通過查閱死者的電腦和手機紊馏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進店門料饥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人朱监,你說我怎么就攤上這事岸啡。” “怎么了赫编?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵巡蘸,是天一觀的道長奋隶。 經(jīng)常有香客問我,道長悦荒,這世上最難降的妖魔是什么唯欣? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮搬味,結果婚禮上境氢,老公的妹妹穿的比我還像新娘。我一直安慰自己碰纬,他們只是感情好萍聊,可當我...
    茶點故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著嘀趟,像睡著了一般脐区。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上她按,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天牛隅,我揣著相機與錄音,去河邊找鬼酌泰。 笑死媒佣,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的陵刹。 我是一名探鬼主播默伍,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼衰琐!你這毒婦竟也來了也糊?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤羡宙,失蹤者是張志新(化名)和其女友劉穎狸剃,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體狗热,經(jīng)...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡钞馁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了匿刮。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片僧凰。...
    茶點故事閱讀 40,013評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖熟丸,靈堂內(nèi)的尸體忽然破棺而出训措,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布隙弛,位于F島的核電站架馋,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏全闷。R本人自食惡果不足惜叉寂,卻給世界環(huán)境...
    茶點故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望总珠。 院中可真熱鬧屏鳍,春花似錦、人聲如沸局服。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽淫奔。三九已至山涡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間唆迁,已是汗流浹背鸭丛。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留唐责,地道東北人鳞溉。 一個月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像鼠哥,于是被迫代替她去往敵國和親熟菲。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,960評論 2 355

推薦閱讀更多精彩內(nèi)容