django之表多對多創建方式、form組件、鉤子函數 08

多對多三種建立方式

1.全自動(用ManyToManyField建立第三張表)

class Book(models.Model):
    title = models.CharField(max_length=32)
    # # 經過ORM自帶的ManyToManyField自動建立第三張表
    authors=models.ManyToManyField(to='Author')

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

優勢:操做簡便,第三張表都是orm自動建立html

​ 內置的操做第三張表的方法:add、remove、set、clear前端

缺點:自動建立的第三張表沒法擴展、修改字段,表的擴展性較差python

2.純手寫

class Book(models.Model):
    title = models.CharField(max_length=32)
   
class Author(models.Model):
    name = models.CharField(max_length=32)

# 本身建立第三張表,分別經過外鍵關聯書和做者
class BookAuthor(models.Model):
    book = models.ForeignKey(to='Book')
    author = models.ForeignKey(to = 'Author')
    create_time = models.DataField(auto_now_add = True)

優勢:第三張表的字段個數和類型均可以本身定義,靈活性更高;jquery

缺點:再也不支持orm跨表查詢,就沒有了正反向的概念;git

3.半自動

當ManyToManyField只有一個參數to的狀況下, orm會自動幫你建立第三張表;
若是還有額外的through和through_fields參數,第三張表就要本身建立, orm還會維護與第三張表的關係維,可以繼續使用orm的跨表查詢;
through 指定第三張關係表;
through_fields 指定第三張關係表中的兩個外鍵字段。ajax

class Book(models.Model):
    title = models.CharField(max_length=32)
    author =   models.ManyToManyField(to='Author',,through='BookAuthor',through_fields=('book','author'))
    
class Author(models.Model):
    name = models.CharField(max_length=32)
    book =   models.ManyToManyField(to='Book',,through='BookAuthor',through_fields=('author','book'))
  # through_fields接受一個元組('field1','field2'):其中field1是定義ManyToManyField的模型外鍵的名(author),field2是關聯目標模型(book)的外鍵名。   

class BookAuthor(models.Model):
    book = models.ForeignKey(to='Book')
    author = models.ForeignKey(to = 'Author')
    create_time = models.DataField(auto_now_add = True)

優勢:能夠任意的添加和修改第三張表中的字段,而且支持orm跨表查詢正則表達式

缺點:再也不支持add、remove、clear、set方法的使用數據庫

form組件

form表單的主要功能:django

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

引入

需求:在瀏覽器頁面,經過form表單中的input框獲取用戶輸入,傳到後端進行校驗用戶的輸入是否合法;而後在對應的input框後展現一些提示信息;

這裏利用標籤的渲染(其實用ajax和BOM操做也能實現)

def register(request):
    back_dic = {'username':'','password':''}
    if request.method == 'POST':
        username = request.POST.get('usename')
        password = request.POST.get('password')
        if '小朋友' in username:
            back_dic['usernmae'] = '敏感信息!不符合!'
        if len(password) < 4:
            back_dic['password'] = '你過短!不行呀!'
    return render(request,'register.html',local())
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <link rel="stylesheet" href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css">
    <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
<body>
    
</body>
</html>

以上須要本身手寫HTML代碼獲取用戶輸入,傳到後端,若是數據有誤,還需展現錯誤信息,比較麻煩,下面利用django自帶的form組件來實現。

form組件的使用

使用form組件首先要定義一個類,繼承django的Form類

# views.py

from django import forms

class MyForm(forms.Form):
    # username字段,最少3位,最多8位
    username = forms.CharField(max_length=8,min_length=3)
    # password字段,最少3位,最多8位
    password = forms.CharField(max_length=8,min_length=3)
    # email字段,必須是郵箱格式
    email = forms.EmailField()
# 在Django Console中校驗  自動搭建測試環境

In[2]:from app01 import views

In[3]:form_obj = views.MyForm({'username':'zhang','password':'123','email':'110117@qq.com'})

In[4]:form_obj.is_valid
Out[4]: <bound method BaseForm.is_valid of <MyForm bound=True, valid=Unknown, fields=(username;password;email)>>
    
In[5]:form_obj.is_valid()
Out[5]: True  # 只有當你傳入的所有數據都符合校驗規則的狀況下才爲True,不然爲False
# 查看不符合規則的字段和錯誤理由
In[6]:form_obj.errors
Out[6]: {}
# 符合規則的數據,不符合的會被排除掉
In[7]:form_obj.cleaned_data
Out[7]: {'username': 'zhang', 'password': '123', 'email': '110117@qq.com'}
    
# 若是出入的數據不符合定義字段指定的長度,錯誤的字段會放在form_obj.errors中;好比:
In[8]:form_obj.errors
Out[8]:{'password': ['Ensure this value has at least 3 characters (it has 2).'],'email': ['Enter a valid email address.']
                 }

# 傳數據的時候,多傳不報錯
In[9]:form_obj = views.MyForm({'username':'zhang','password':'123','email':'110117@qq.com','gender':'male'})
In[10]form_obj.is_valid()
Out[10]: True
# 少傳對應的字段就不行
In[11]:form_obj = views.MyForm({'username':'zhang','password':'123'})
In[12]:form_obj.is_valid()
Out[13]: False

forms組件渲染標籤

forms組件只會幫你渲染獲取用戶輸入的標籤 不會幫你渲染提交按鈕 須要你本身手動添加

字段類括號內的label能夠修改input框前面的註釋(提示信息)

from django import forms
class MyForm(forms.Form):
    username = forms.CharField(max_length=8,min_length=3,label='用戶名')  # label 修改用戶輸入框前面的提示信息
    password = forms.CharField(max_length=8,min_length=3,label='密碼')
    email = forms.EmailField(label='郵箱')

def index(request): 
    form_obj = MyForm(request.POST)
    return render(request, 'index.html', locals())

forms組件渲染標籤方式1

封裝程度態高 不推薦使用 可是能夠用在本地測試

<p>forms組件渲染標籤方式1</p>
{{ form_obj.as_p }}  <!--自動渲染全部input框  -->
{{ form_obj.as_ul }}
{{ form_obj.as_table }}

forms組件渲染標籤方式2

不推薦使用 寫起來太煩了

<p>forms組件渲染標籤方式2</p>
<!--獲取input框前面的Username     獲取的是一個input輸入框-->
{{ form_obj.username.label }}{{ form_obj.username }}
{{ form_obj.username.label }}{{ form_obj.password }}
{{ form_obj.username.label }}{{ form_obj.email }}

forms組件渲染標籤方式3

推薦使用

<p>forms組件渲染標籤方式3:推薦使用 </p>
{% for form in form_obj %}
<p>{{ form.label }}{{ form }}</p>  <!--form 等價於你方式2中的對象點字段名,是一個input輸入框-->
{% endfor %}

form表單展現信息

from django import forms

class MyForm(forms.Form):
 username = forms.CharField(max_length=8,min_length=3,label='用戶名',
                               error_messages={
                                   'max_length':'用戶名不能超過八位',
                                   'min_length':'用戶名不能少於三位',
                                   'required':'用戶名不能爲空',
                               }

                               )  # label 修改用戶輸入框前面的提示信息
    password = forms.CharField(max_length=8,min_length=3,label='密碼',
                               error_messages={
                                   'max_length': '密碼不能超過八位',
                                   'min_length':'密碼不能少於三位',
                                   'required': '密碼不能爲空',
                               }
                               )
    email = forms.EmailField(label='郵箱',
                             error_messages={
                                 'required':'郵箱不能爲空!',
                                 'invalid':'郵箱格式錯誤',
                             })

def index(request):
    form_obj = MyForm()
    print(request.POST)  # <QueryDict: {'username': ['zhang'], 'password': ['123456'], 'email': ['1101172195@qq.com']}>
    if request.method=='POST':
        form_obj = MyForm(request.POST)  # MyForm類須要傳入一個字典,而request.POST獲取的用戶的輸入偏偏也是字典
        if form_obj.is_valid():  # 跟上面的變量名要一致,由於兩次不衝突,都要拿到html文件中給同一份代碼渲染
            print(form_obj.cleaned_data)
            return HttpResponse('輸入真確!')
        else:
            print(form_obj.errors)

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

novalidate 在form標籤內寫上它,告訴前端不讓其對用戶輸入的數據作校驗

forms.errors 獲取後端校驗後錯誤的報錯信息,傳給前端;

forms.errors.0 改變報錯信息在input輸入框後面展現

forms.label input框前面的提示信息

forms 獲取一個input輸入框

{#novalidate讓前端不對輸入信息是否合法作校驗#}
<form action="" method="post" novalidate>
    <p>form組件的渲染方式3:</p>  <!--推薦使用-->
    {% for forms in form_obj %}
        <p>
{#            獲取提示信息和input輸入框#}
            {{ forms.label }}{{ forms }}
{#            這裏forms.errors也能拿到後端的報錯信息(在input框下面),渲染到瀏覽器,forms.errors.0把報錯信息的展現到input框後面#}
            <span>{{ forms.errors.0 }}</span>
        </p>
    {% endfor %}
    <input type="submit">
</form>

form組件自定義校驗

內置的校驗器(RegexValidator)

字段類括號內能夠連續寫多個參數

from django.core.validators import RegexValidator
 
class MyForm(Form):
    user = fields.CharField(
        validators=[RegexValidator(r'^[0-9]+$', '請輸入數字'), RegexValidator(r'^159[0-9]+$', '數字必須以159開頭')],
    )

鉤子函數(HOOK)

當你以爲上面的全部的校驗還不可以知足你的需求 ,你能夠考慮使用鉤子函數,是一個函數 函數體內你能夠寫任意的校驗代碼。

鉤子函數執行條件:在類內部定義的字段括號設定的條件都知足以後纔會走到鉤子函數;若是前面的條件都不知足,不會走到鉤子函數。

用法:直接在類內部定義django給咱們定義好的函數,根據須要本身選擇。包括:

clean、clean_confirm_password、clean_email、clean_password、clean_username

局部鉤子:校驗單個字段

全局鉤子:校驗多個字段

# 使用forms組件的第一步 必須先寫一個類
from django import forms
class MyForm(forms.Form):
    # username字段 最少三位 最多八位
    username = forms.CharField(max_length=8,min_length=3,label='用戶名',initial='默認值',
                               error_messages={
                                   'max_length':'用戶名最長八位',
                                   'min_length':'用戶名最短三位',
                                   'required':'用戶名不能爲空'
                               },required=False,
                               widget=forms.widgets.TextInput({'class':'form-control c1 c2','username':'jason'})
                               )
    # password字段 最少三位  最多八位
    password = forms.CharField(max_length=8,min_length=3,label='密碼',
                               error_messages={
                                   'max_length': '密碼最長八位',
                                   'min_length': '密碼最短三位',
                                   'required': '密碼不能爲空'
                               },widget=forms.widgets.PasswordInput()
                               )
    confirm_password = forms.CharField(max_length=8, min_length=3, label='確認密碼',
                               error_messages={
                                   'max_length': '確認密碼最長八位',
                                   'min_length': '確認密碼最短三位',
                                   'required': '確認密碼不能爲空'
                               },
                               )
    # email字段 必須是郵箱格式
    email = forms.EmailField(label='郵箱',error_messages={
                            'required':'郵箱不能爲空',
                            'invalid':'郵箱格式錯誤'
    })
     # 校驗用戶名中不能含有666     局部鉤子
    def clean_username(self):
        username = self.cleaned_data.get('username')
        if '666' in username:
            # 給username所對應的框展現錯誤信息
            # self.add_error('username','光喊666是不行的')
            raise ValidationError('到底對不對啊')
        # 將username數據返回
        return username

    # 校驗密碼 確認密碼是否一致     全局鉤子
    def clean(self):
        password = self.cleaned_data.get("password")
        confirm_password = self.cleaned_data.get("confirm_password")
        if not password == confirm_password:
            self.add_error('confirm_password','兩次密碼不一致')
        # 將全局的數據返回
        return self.cleaned_data

其餘字段及參數

label       input對應的提示信息
initial     input框默認值
required    默認爲True控制字段是否必填
widget      給input框設置樣式及屬性,也能設置input輸入框的類型(type)
widget=forms.widgets.TextInput({'class':'form-control c1 c2','username':'li'})  # 增長屬性
widget=forms.widgets.TextInput(attrs={'class':'form-control c1c2','username':'li'})

widget=forms.widgets.TextInput()  # 默認普通文本類型

widget=forms.widgets.PasswordInput()  # 密文密碼

radioSelect

單radio值爲字符串

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="密碼")
    gender = forms.fields.ChoiceField(
        choices=((1, "男"), (2, "女"), (3, "保密")),
        label="性別",
        initial=3,
        widget=forms.widgets.RadioSelect()
    )

單選Select

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

多選Select

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

單選checkbox

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

多選checkbox

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

choice字段注意事項

在使用選擇標籤時,須要注意choices的選項能夠配置從數據庫中獲取,可是因爲是靜態字段 獲取的值沒法實時更新,須要重寫構造方法從而實現choice實時更新。

方式一:

from django.forms import Form
from django.forms import widgets
from django.forms import fields
 
class MyForm(Form):
    user = fields.ChoiceField(
        # choices=((1, '上海'), (2, '北京'),),
        initial=2,
        widget=widgets.Select
    )
    def __init__(self, *args, **kwargs):
        super(MyForm,self).__init__(*args, **kwargs)
        # self.fields['user'].choices = ((1, '上海'), (2, '北京'),)
        # 或
   self.fields['user'].choices=models.Classes.objects.all().values_list('id','caption')

方式二:

from django import forms
from django.forms import fields
from django.forms import models as form_model

 
class FInfo(forms.Form):
    authors = form_model.ModelMultipleChoiceField(queryset=models.NNewType.objects.all())  # 多選
    # authors = form_model.ModelChoiceField(queryset=models.NNewType.objects.all())  # 單選

Django Form全部內置字段

Field
    required=True,               默認爲空,True不能爲空,False容許爲空
    widget=None,                 HTML插件
    label=None,                  用於修改input框前面的提示信息
    initial=None,                input框初始值
    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,              最小值
 
FloatField(IntegerField)
    ...
 
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
 
DurationField(Field)            時間間隔:%d %H:%M:%S.%f
    ...
 
RegexField(CharField)
    regex,                      自定製正則表達式
    max_length=None,            最大長度
    min_length=None,            最小長度
    error_message=None,         忽略,錯誤信息使用 error_messages={'invalid': '...'}
 
EmailField(CharField)      
    ...
 
FileField(Field)
    allow_empty_file=False     是否容許空文件
 
ImageField(FileField)      
    ...
    注:須要PIL模塊,pip3 install Pillow
    以上兩個字典使用時,須要注意兩點:
        - form表單中 enctype="multipart/form-data"
        - view函數中 obj = MyForm(request.POST, request.FILES)
 
URLField(Field)
    ...
 
 
BooleanField(Field)  
    ...
 
NullBooleanField(BooleanField)
    ...
 
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= ''            空值的默認值
 
MultipleChoiceField(ChoiceField)
    ...
 
TypedMultipleChoiceField(MultipleChoiceField)
    coerce = lambda val: val   對選中的每個值進行一次轉換
    empty_value= ''            空值的默認值
 
ComboField(Field)
    fields=()                  使用多個驗證,以下:即驗證最大長度20,又驗證郵箱格式
                               fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),])
 
MultiValueField(Field)
    PS: 抽象類,子類中能夠實現聚合多個字典去匹配一個值,要配合MultiWidget使用
 
SplitDateTimeField(MultiValueField)
    input_date_formats=None,   格式列表:['%Y--%m--%d', '%m%d/%Y', '%m/%d/%y']
    input_time_formats=None    格式列表:['%H:%M:%S', '%H:%M:%S.%f', '%H:%M']
 
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內置字段
相關文章
相關標籤/搜索