1. 程式人生 > >Django(九)上:ModelForm操作

Django(九)上:ModelForm操作

一、內容回顧

    Model
        - 資料庫操作
        - 驗證
        class A(MOdel):
            user = 
            email = 
            pwd = 

    Form
        - class LoginForm(Form): 
            email = fields.EmailField()
            user = 
            pwd = 

        - is_valid -> 每一個欄位進行正則(欄位內建正則)+clean_欄位 -> clean(__all__)  -> _post_clean
        - cleand_data
        - error
    --------> 推薦 <---------

二、ModelForm操作及驗證

Model + Form ==> ModelForm。model和form的結合體,所以有以下功能:

  • 驗證
  • 資料庫操作

model有操作資料庫的欄位,form驗證也有那幾個欄位,雖然耦合度降低,但是程式碼是有重複的。如果利用model裡的欄位,那是不是form裡的欄位就不用寫了。

1、Model + Form (之前的操作)

models.py

class UserType(models.Model):
    caption = models.CharField(max_length=32)

class UserInfo
(models.Model):
username = models.CharField(max_length=32) email = models.EmailField() user_type = models.ForeignKey(to='UserType',to_field='id')

forms.py

from django import forms
from django.forms import fields

class UserInfoForm(forms.Form):
    # username = models.CharField(max_length=32)    <-- models
username = fields.CharField(max_length=32) # email = models.EmailField() <-- models email = fields.EmailField() # user_type = models.ForeignKey(to='UserType',to_field='id') <-- models user_type = fields.ChoiceField( choices=models.UserType.objects.values_list('id','caption') ) # 下面的操作是讓資料在網頁上實時更新。 def __init__(self, *args, **kwargs): super(UserInfoForm,self).__init__(*args, **kwargs) self.fields['user_type'].choices = models.UserType.objects.values_list('id','caption')

index.html

<body>
    <form action="/index/" method="POST" novalidate="novalidate">
        {% csrf_token %}
        {{ obj.as_p }}
        <input type="submit" value="提交">
    </form>
</body>

novalidate 注: HTML5輸入型別和瀏覽器驗證

如果表單中包含URLField、EmailField和其他整數字段類似,Django將使用url、email和number這樣的HTML5輸入型別。預設情況下,瀏覽器可能會對這些欄位進行他們自身的驗證,這些驗證可能比Django的驗證更嚴格。如果你想禁用這個行為,請設定form標籤的novalidate屬性,或者制定一個不同的欄位,如TextInput。

2、ModelForm 基本操作

下面通過modelform實現

forms.py

class UserInfoModelForm(forms.ModelForm):

    class Meta:
        model = models.UserInfo    # 與models建立了依賴關係
        fields = "__all__"

views.py

def index(request):
    if request.method == "GET":
        obj = UserInfoModelForm()
        return render(request,"index.html",{'obj':obj})
    elif request.method == "POST":
        obj = UserInfoModelForm(request.POST)
        print(obj.is_valid())  # 這是方法,別忘記了加括號
        print(obj.cleaned_data)
        print(obj.errors)
        return render(request,"index.html",{'obj':obj})

自定製欄位名

如何定義http上定義的欄位呢,自定義寫成中文的?之前的用法是在Form裡寫上label。Model Form定義要用verbose_name

models.py

class UserInfo(models.Model):
    username = models.CharField(max_length=32, verbose_name='使用者')
    email = models.EmailField(verbose_name='郵箱')
    user_type = models.ForeignKey(to='UserType',to_field='id', verbose_name='型別')

如果不在model裡定義,在modelForm裡實現,利用labels

class UserInfoModelForm(forms.ModelForm):

    class Meta:
        model = models.UserInfo
        fields = "__all__"
        labels = {
            'username':'使用者名稱',
            'email':'郵箱',
        }

展示指定的列

fields = "__all__"上面展示所有的,可不可以展示指定的列

        fields = ['username','email']   # 顯示指定列
        exclude = ['username']          # 排除指定列

為什麼modelForm裡也能做驗證?

form裡面有is_validcleaned_dataerrors

# Form驗證:
    UserInfoForm -> Form -> BaseForm( 包含is_valid等方法)

# ModelForm驗證:
    UserInfoModelForm -> ModelForm -> BaseModelForm -> BaseForm

3、ModelForm元件

ModelForm
    a.  class Meta:
            model,                           # 對應Model的
            fields=None,                     # 欄位
            exclude=None,                    # 排除欄位
            labels=None,                     # 提示資訊
            help_texts=None,                 # 幫助提示資訊
            widgets=None,                    # 自定義外掛
            error_messages=None,             # 自定義錯誤資訊(整體錯誤資訊from django.core.exceptions import NON_FIELD_ERRORS)
            field_classes=None               # 自定義欄位類 (也可以自定義欄位)
            localized_fields=('birth_date',) # 本地化,如:根據不同時區顯示資料
            如:
                資料庫中
                    2016-12-27 04:10:57
                setting中的配置
                    TIME_ZONE = 'Asia/Shanghai'
                    USE_TZ = True
                則顯示:
                    2016-12-27 12:10:57
    b. 驗證執行過程
        is_valid -> full_clean -> 鉤子 -> 整體錯誤

    c. 字典欄位驗證
        def clean_欄位名(self):
            # 可以丟擲異常
            # from django.core.exceptions import ValidationError
            return "新值"
    d. 用於驗證
        model_form_obj = XXOOModelForm()
        model_form_obj.is_valid()
        model_form_obj.errors.as_json()
        model_form_obj.clean()
        model_form_obj.cleaned_data
    e. 用於建立
        model_form_obj = XXOOModelForm(request.POST)
        #### 頁面顯示,並提交 #####
        # 預設儲存多對多
            obj = form.save(commit=True)
        # 不做任何操作,內部定義 save_m2m(用於儲存多對多)
            obj = form.save(commit=False)
            obj.save()      # 儲存單表資訊
            obj.save_m2m()  # 儲存關聯多對多資訊

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

        PS: 單純初始化
            model_form_obj = XXOOModelForm(initial={...})

注意:匯入模組名(fields、widgets)和欄位名重複,所以匯入時要起個別名。

from django import forms
from django.forms import fields as Ffields
from django.forms import widgets as Fwidgets
class UserInfoModelForm(forms.ModelForm):

    is_rmb = Ffields.CharField(widget=Fwidgets.CheckboxInput())

    class Meta:
        model = models.UserInfo
        fields = '__all__'
        # fields =  ['username','email']
        # exclude = ['username']
        labels = {
            'username': '使用者名稱',
            'email': '郵箱',
        }
        help_texts = {
            'username': '...'
        }
        widgets = {
            'username': Fwidgets.Textarea(attrs={'class': 'c1'})
        }
        error_messages = {
            '__all__':{    # 整體錯誤資訊

            },
            'email': {
                'required': '郵箱不能為空',
                'invalid': '郵箱格式錯誤..',
            }
        }
        field_classes = {  # 定義欄位的類是什麼
            # 'email': Ffields.URLField  # 這裡只能填類,加上括號就是物件了。
        }

        # localized_fields=('ctime',)  # 哪些欄位做本地化

4、ModelForm 資料庫操作

1.1、建立資料save

如果資料驗證是ok的,那麼save,就直接在資料庫中建立完資料了

        if obj.is_valid():
            obj.save()      # 建立資料

在如下一對多、多對多關係中:

class UserType(models.Model):
    caption = models.CharField(max_length=32)

class UserGroup(models.Model):
    name = models.CharField(max_length=32)

class UserInfo(models.Model):
    username = models.CharField(max_length=32)
    email = models.EmailField()
    user_type = models.ForeignKey(to='UserType',to_field='id')
    u2g = models.ManyToManyField(UserGroup)

這樣的話,執行上面的obj.save()會在UserInfo表和多對多關係表裡都增加資料。

views.py

def index(request):
    if request.method == "GET":
        obj = UserInfoModelForm()
        return render(request,'index.html',{'obj': obj})
    elif request.method == "POST":
        obj = UserInfoModelForm(request.POST)
        if obj.is_valid():
            obj.save()  # 等價以下三句
            # instance = obj.save(False)
            # instance.save()
            # obj.save_m2m()
        return render(request,'index.html',{'obj': obj})

1.2、save 做了哪些操作?

save原始碼裡:

def save(self, commit=True):
    """"""
    if commit:
        self.instance.save()    # 指的當前model物件
        self._save_m2m()        # 指:儲存m2m物件
    else:
        self.save_m2m = self._save_m2m
    return self.instance    # model 類的物件
    """"""

所以instance = obj.save(False)時,什麼都不會操作。

if obj.is_valid():
    instance = obj.save(False)
    instance.save()     # 當前物件表資料建立
    obj.save_m2m()      # 多對多表資料建立
    # 上面這三句完成的是和上面 obj.save 一樣的操作。拆開就可以自定製操作了

2、修改資料

修改表資料是,記得把instance資訊也傳進去,不然是新建資料,而不是對某行資料進行修改。

編輯使用者資訊,新url方式保留預設資料

urls.py

    url(r'^user_list/', views.user_list),
    url(r'^edit-(\d+)/', views.user_edit),

views.py

def user_list(request):
    li = models.UserInfo.objects.all().select_related('user_type')  # 這裡只能是外來鍵,多對多欄位也不可以
    return render(request,'user_list.html',{'li': li})

def user_edit(request, nid):
    # 獲取當前id物件的使用者資訊
    # 顯示使用者已經存在資料
    if request.method == "GET":
        user_obj = models.UserInfo.objects.filter(id=nid).first()
        mf = UserInfoModelForm(instance=user_obj)   # 把預設資料傳遞進去
        return render(request,'user_edit.html',{'mf': mf, 'nid': nid})
    elif request.method == 'POST':
        # 資料修改的資訊,給資料庫的哪一行做修改?
        user_obj = models.UserInfo.objects.filter(id=nid).first()
        mf = UserInfoModelForm(request.POST,instance=user_obj)  # 指定給誰做修改
        if mf.is_valid():
            mf.save()
        else:
            print(mf.errors.as_json())
        return render(request,'user_edit.html',{'mf': mf, 'nid': nid})

user_list.html

<body>
    <ul>
        {% for row in li %}
            <li>{{ row.username }} - {{ row.user_type.caption }} - <a href="/edit-{{ row.id }}/">編輯</a></li>
        {% endfor %}
    </ul>
</body>

user_edit.html

<body>
    <form method="POST" action="/edit-{{ nid }}/">
        {% csrf_token %}
    {{ mf.as_p }}
        <input type="submit" value="提交" />
    </form>
</body>

5、ModelForm鉤子、額外欄位

資料驗證鉤子

從上面的Form和ModelForm中,他們都是繼承了BaseForm,而is_valid是在BaseForm中定義的,所以ModelForm也能和Form一樣使用各種鉤子

額外欄位

像網頁上的checkbox,一個月內免登陸,用提交到資料庫麼?這個只需要設定session和cookie就可以了。

views.py

class UserInfoModelForm(forms.ModelForm):

    is_rmb = fields.CharField(widget=widgets.CheckboxInput())  # 額外欄位

    class Meta:
        model = models.UserInfo
        fields = '__all__'

6、總結

    1. 生成HTML標籤:class Meta: ...
    2. mf = xxxModelForm(instance=ModelObj) 生成預設值
    3. 額外的標籤, is_rmb = Ffields.CharField(widget=Fwidgets.CheckboxInput())
    4. 各種驗證 is_valid() -> 各種鉤子...
    5.  mf.save()
        # 或
        instance = mf.save(False)
        instance.save()
        mf.save_m2m()

ModelForm因為model和form耦合太密切,所以一般寫小程式用它。