Django框架11 /form组件、modelForm组件

1. form组件介绍

  • form组件的主要功能如下:

    1. 生成页面可用的HTML标签
    2. 对用户提交的数据进行校验
    3. 保留上次输入内容
  • 简单代码示例:

    view.py

    from django.shortcuts import render,HttpResponse,redirect
    from django import forms class Auth(forms.Form):
    username = forms.CharField(
    label='用户名',
    )
    password = forms.CharField(
    label='密 码',
    # 控制前端显示样式
    widget=forms.widgets.PasswordInput.attrs.update({'class':'c1'})
    ) def index(request):
    u_obj = Auth()
    return render(request,'index.html',{'u_obj':u_obj})

    index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
    .c1{
    background-color:darkgrey;
    }
    </style>
    </head> <body>
    <h1>测试页面</h1>
    <div>
    {{ u_obj.username.label }} {{ u_obj.username }}
    </div>
    <div>
    {{ u_obj.password.label }} {{ u_obj.password }}
    </div>
    </body> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
    <script></script>
    </html>

2. 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="密码")
  • password

    class LoginForm(forms.Form):
    ...
    pwd = forms.CharField(
    min_length=6,
    label="密码",
    widget=forms.widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True)
    ) # 这个密码字段和其他字段不一样,默认在前端输入数据错误的时候,点击提交之后,默认是不保存的原来数据的,但是可以通过这个render_value=True让这个字段在前端保留用户输入的数据
  • RadioSelect --- 单radio值为字符串

    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(
    choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),
    label="爱好",
    initial=3,
    widget=forms.widgets.Select()
    ) # 注意,单选框下拉框用的是ChoiceField,并且里面的插件是Select,不然验证的时候会报错, Select a valid choice的错误。
    # 样式是单选下拉框
  • Select --- 多选

    class LoginForm(forms.Form):
    ...
    hobby = forms.fields.MultipleChoiceField(
    choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),
    label="爱好",
    initial=[1, 3],
    widget=forms.widgets.SelectMultiple()
    ) # 多选框的时候用MultipleChoiceField,并且里面的插件用的是SelectMultiple,不然验证的时候会报错。
    # 样式是多选下拉框
  • checkbox --- 单选

    class LoginForm(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()
    ) # 样式是多选小方块
  • date类型

    from django import forms
    from django.forms import widgets
    class BookForm(forms.Form):
    date = forms.DateField(widget=widgets.TextInput(attrs={'type':'date'})) # 必须指定type,不然不能渲染成选择时间的input框
  • 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 = models.Classes.objects.all().values_list('id','caption') # 注意:重写init方法的时候,*args和**kwargs一定要给人家写上,不然会出问题,并且验证总是不能通过,还不显示报错信息

    方式二:

    from django import forms
    from django.forms import fields
    from django.forms import models as form_model class FInfo(forms.Form):
      # 多选
    authors = forms.ModelMultipleChoiceField(queryset=models.NNewType.objects.all())
    # 多选 -- 或者下面这种方式,通过forms里面的models中提供的方法也是一样的。
    authors = form_model.ModelMultipleChoiceField(queryset=models.NNewType.objects.all())
    # 单选
    authors = form_model.ModelChoiceField(queryset=models.NNewType.objects.all())
    # 单选
    authors = forms.ModelChoiceField(queryset=models.Publisth.objects.all(),
    widget=forms.widgets.Select()) authors = forms.ModelMultipleChoiceField(queryset=models.Author.objects.all(),
    widget = forms.widgets.Select(attrs={'class': 'form-control'})) # 如果用这种方式,别忘了model表中的__str__方法要写上,不然选择框里面会是一个个的object对象

3. 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, # 最小值 FloatField(IntegerField)
    ... DecimalField(IntegerField)
    max_value=None, # 最大值
    min_value=None, # 最小值
    max_digits=None, # 总长度
    decimal_places=None, # 小数位长度 DateField(BaseTemporalField) # 格式:2015-09-01 RegexField(CharField)
    regex, # 自定制正则表达式
    max_length=None, # 最大长度
    min_length=None, # 最小长度
    error_message=None, # 忽略,错误信息使用 error_messages={'invalid': '...'} EmailField(CharField)
    ... FileField(Field)
    allow_empty_file=False # 是否允许空文件 ChoiceField(Field)
    ...
    choices=(), # 选项,如:choices = ((0,'上海'),(1,'北京'),)
    required=True, # 是否必填
    widget=None, # 插件,默认select插件
    label=None, # Label内容
    initial=None, # 初始值
    help_text='', # 帮助提示

4. form字段校验

  • RegexValidator验证器

    from django.forms import Form
    from django.forms import widgets
    from django.forms import fields
    from django.core.validators import RegexValidator class MyForm(Form):
    user = fields.CharField(
    validators=[RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头')],
    )
  • 自定义验证函数

    import re
    from django.core.exceptions import ValidationError # 自定义验证规则
    def mobile_validate(value):
    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 PublishForm(forms.Form):
    # 使用自定义验证规则
    phone = forms.CharField(validators=[mobile_validate, ],
    error_messages={'required': '手机不能为空'},
    widget=widgets.TextInput(attrs={'class': "form-control",
    'placeholder': u'手机号码'}))

5. formHook钩子方法

  • 局部钩子

    # 我们在Fom类中定义 clean_字段名() 方法,就能够实现对特定字段进行校验。
    
    class LoginForm(forms.Form):
    username = forms.CharField(
    min_length=8,
    label="用户名",
    initial="张三",
    error_messages={
    "required": "不能为空",
    "invalid": "格式错误",
    "min_length": "用户名最短8位"
    },
    widget=forms.widgets.TextInput(attrs={"class": "form-control"})
    )
    ...
    # 定义局部钩子,用来校验username字段,之前的校验规则还在,给你提供了一个添加一些校验功能的钩子
    def clean_username(self):
    value = self.cleaned_data.get("username")
    if "666" in value:
    raise ValidationError("光喊666是不行的")
    else:
    return value
  • 全局钩子

    # 在Fom类中定义 clean() 方法,就能够实现对字段进行全局校验,字段全部验证完,局部钩子也全部执行完之后,执行这个全局钩子校验
    
    class LoginForm(forms.Form):
    ...
    password = forms.CharField(
    min_length=6,
    label="密码",
    widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}, render_value=True)
    )
    re_password = forms.CharField(
    min_length=6,
    label="确认密码",
    widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}, render_value=True)
    )
    ...
    # 定义全局的钩子,用来校验密码和确认密码字段是否相同,执行全局钩子的时候,cleaned_data里面肯定是有了通过前面验证的所有数据
    def clean(self):
    password_value = self.cleaned_data.get('password')
    re_password_value = self.cleaned_data.get('re_password')
    if password_value == re_password_value:
    return self.cleaned_data #全局钩子要返回所有的数据
    else:
    # 第一种方式:
    self.add_error('re_password', '两次密码不一致') #在re_password这个字段的错误列表中加上一个错误
    # 第二种方式:
    raise ValidationError('两次密码不一致') #将错误添加到全局

6. form批量添加属性样式

  • 代码示例:

    # 批量添加属性样式
    def __init__(self,*args,**kwargs):
    super().__init__(*args,**kwargs)
    for field_name,field in self.fields.items(): #orderdict(('username',charfield对象))
    field.widget.attrs.update({'class':'form-control'}

7. form代码示例

  • form字段/插件/钩子代码示例

    from django.shortcuts import render,HttpResponse,redirect
    
    from django import forms
    
    from django.core.validators import RegexValidator
    from django.core.exceptions import ValidationError # 自定义验证规则
    import re
    def mobile_validate(value):
    mobile_re = re.compile(r'^3')
    if not mobile_re.match(value):
    raise ValidationError('手机号格式错误') #自定义验证规则的时候,如果不符合你的规则,需要自己发起错误 class Auth(forms.Form):
    username = forms.CharField(
    # 前面信息
    label='用户名',
    # 默认值
    initial='张三',
    # 设置最小长度
    min_length=6,
    # 设置最大长度
    max_length=8,
    # #不允许为空,值为False就可以为空
    required=True,
    # 对错误信息重命名
    error_messages={
    'required':'不能为空',
    'min_length':'最低6位',
    }, # 直接写自定义规则验证
    validators=[RegexValidator(r'^a','必须以a开头')] # 引用外部正则函数验证
    validators=[mobile_validate] #[]中括号内写自定义的正则函数
    widget=forms.TextInput(attrs={'class':'c2'})
    ) # 密码
    password = forms.CharField(
    label='密 码',
    widget=forms.widgets.PasswordInput(attrs={'class':'c1'},render_value=True),
    )
    render_value=True # 将密码设置成再显示,密码一般输错不会再显示 # 确认密码
    re_password = forms.CharField(
    label='确认密码',
    widget=forms.widgets.PasswordInput(attrs={'class':'c1'},render_value=True),
    ) # 性别--单选
    sex = forms.ChoiceField(
    choices=((1,'男'),(2,'女')),
    # widget=forms.Select, #默认 单选下拉框
    widget=forms.RadioSelect,
    ) # 爱好--多选
    hobby = forms.MultipleChoiceField(
    choices=((1, '抽烟'), (2, '喝酒'), (2, '烫头')),
    # widget=forms.SelectMultiple, #默认 多选下拉框
    widget=forms.CheckboxSelectMultiple,
    ) # 记住我--单选方块
    remember_me = forms.ChoiceField(
    label='记住我',
    widget=forms.CheckboxInput
    ) # 日期格式 # 日期类型必须加上日期属性
    bday = forms.DateField(
    label='出生日期',
    widget=forms.DateInput(attrs={'type':'date'})
    ) # 局部钩子
    def clean_username(self):
    value = self.cleaned_data.get('username')
    if 'ergou' in value:
    raise ValidationError('含有敏感词汇')
    else:
    return value # 全局钩子
    def clean(self):
    password = self.cleaned_data.get('password')
    re_password = self.cleaned_data.get('re_password')
    if password == re_password:
    return self.cleaned_data
    else:
    self.add_error('re_password','两次密码不一致!!') def index(request):
    if request.method == 'GET':
    u_obj = Auth()
    return render(request,'index.html',{'u_obj':u_obj})
    else:
    u_obj = Auth(request.POST)
    print(u_obj.fields)
    if u_obj.is_valid(): #校验用户提交的数据,全部校验成功返回True,任意一个失败都返回False
    print('正确数据',u_obj.cleaned_data) #校验成功之后的数据cleaned_data
    return HttpResponse('ok')
    else:
    print('错误信息',u_obj.errors)
    return render(request,'index.html',{'u_obj':u_obj})
  • form组件应用代码示例:

    view.py

    from django import forms
    
    class AddBookForm(forms.Form):
    title = forms.CharField(
    label='书名',
    )
    price=forms.DecimalField(
    label='价格',
    max_digits=5,
    decimal_places=2 , #999.99
    )
    publishDate=forms.CharField(
    label='出版日期',
    widget=forms.TextInput(attrs={'type':'date'}), )
    # 单选方式一:
    # publishs_id = forms.ChoiceField(
    # choices=models.Publish.objects.all().values_list('id','name'), #[(),()]
    # )
    # 多选方式一:
    # authors=forms.MultipleChoiceField(
    # choices=models.Author.objects.all().values_list('id','name')
    # ) # 单选方式二:
    publishs = forms.ModelChoiceField(
    queryset=models.Publish.objects.all(),
    )
    # 多选方式二:
    authors = forms.ModelMultipleChoiceField(
    queryset=models.Author.objects.all(),
    ) # csrfmiddlewaretoken = forms.ChoiceField
    # 批量添加属性样式
    def __init__(self,*args,**kwargs):
    super().__init__(*args,**kwargs)
    for field_name,field in self.fields.items(): #orderdict(('username',charfield对象))
    field.widget.attrs.update({'class':'form-control'}) def addbook(request):
    if request.method == 'GET':
    book_obj = AddBookForm()
    return render(request,'addbook.html',{'book_obj':book_obj})
    else:
    book_obj = AddBookForm(request.POST)
    print(request.POST)
    if book_obj.is_valid():
    print(book_obj.cleaned_data)
    data = book_obj.cleaned_data
    authors = data.pop('authors')
    new_book = models.Book.objects.create(
    **data
    # publishs = 对象
    )
    new_book.authors.add(*authors) # return HttpResponse('ok')
    return redirect('showbooks')
    else:
    return render(request,'addbook.html',{'book_obj':book_obj})

    addbook.html

    {% load static %}
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}">
    </head>
    <body>
    <h1>添加书籍</h1>
    <div class="container">
    <div class="row">
    <div class="col-md-8 col-md-offset-2">
    <form action="" method="post" novalidate>
    {% csrf_token %}
    {% for field in book_obj %}
    <div class="form-group">
    {{ field.label }}
    {{ field }}
    <span class="text-danger">{{ field.errors.0 }}</span>
    </div>
    {% endfor %}
    <button type="submit" class="btn btn-success pull-right">提交</button>
    </form>
    </div>
    </div>
    </div>
    </body>
    </html>

7. modelForm

  • 定义:form与model的终极结合,会根据model中的字段转换成对应的form字段,并且生成对应的标签等操作

8. modelform类的写法/class Meta下常用参数

  • modelform类的写法

    class BookForm(forms.ModelForm):
    
        class Meta:
    model = models.Book
    fields = "__all__"
    labels = {
    "title": "书名",
    "price": "价格"
    }
    widgets = {
    "password": forms.widgets.PasswordInput(attrs={"class": "c1"}),
    "publishDate": forms.widgets.DateInput(attrs={"type": "date"}),
    }
  • class Meta下常用参数

    model = models.Book  # 对应的Model中的类
    fields = "__all__" # 字段,如果是__all__,就是表示列出所有的字段
    exclude = None # 排除的字段
    labels = None # 提示信息
    help_texts = None # 帮助提示信息
    widgets = None # 自定义插件
    error_messages = None # 自定义错误信息
    error_messages = {
    'title':{'required':'不能为空',...} # 每个字段的所有的错误都可以写,...是省略的意思
    }

9. modelform钩子/批量添加

  • 代码示例:

    class BookForm(forms.ModelForm):
    password = forms.CharField(min_length=10) # 优先级高
    # 可以重写字段,会覆盖modelform中的这个字段,那么modelform下面关于这个字段的设置就会被覆盖,比如果设置插件啊,error_messages啊等等,
    r_password = forms.CharField()
    # 想多验证一些字段可以单独拿出来写,按照form的写法,写在Meta的上面或者下面都可以
    class Meta:
    model = models.Book
    # fields = ['title','price'] # 指定字段生成form
    fields = "__all__"
    # exclude=['title',] #排除字段
    labels = {
    "title": "书名",
    "price": "价格"
    }
    error_messages = {
    'title':{'required':'不能为空',} # 每个字段的错误都可以写
    }
    # 如果models中的字段和咱们需要验证的字段对不齐时,比如注册时,咱们需要验证密码和确认密码两个字段数据,但是后端数据库就保存一个数据就行,那么验证是两个,数据保存是一个,就可以再接着写form字段
    r_password = forms.CharField()
    # 同样的,如果想做一些特殊的验证定制,那么和form一样,也是那两个钩子(全局和局部),写法也是form那个的写法,直接在咱们的类里面写: # 局部钩子:
    def clean_title(self):
    pass   # 全局钩子
    def clean(self):
    pass # 批量操作
    def __init__(self,*args,**kwargs):
    super().__init__(*args,**kwargs)
    for field in self.fields:
    # field.error_messages = {'required':'不能为空'}
    # 批量添加错误信息,这是都一样的错误,不一样的还是要单独写。
    self.fields[field].widget.attrs.update({'class':'form-control'})

10. modelForm验证/保存

  • modelForm验证

    1. 与普通的Form表单验证类型类似,ModelForm表单的验证在调用is_valid() 或访问errors 属性时隐式调用。
    2. 可以像使用Form类一样自定义局部钩子方法和全局钩子方法来实现自定义的校验规则。
    3. 如果不重写具体字段并设置validators属性的话,ModelForm是按照模型中字段的validators来校验的。
  • save()方法--添加、编辑

    # 每个ModelForm还具有一个save()方法。 这个方法根据表单绑定的数据创建并保存数据库对象。
    # ModelForm的子类可以接受现有的模型实例作为关键字参数instance;
    # 如果提供此功能,则save()将更新该实例。
    # 如果没有提供,save() 将创建模型的一个新实例。 >>> from myapp.models import Book
    >>> from myapp.forms import BookForm # 根据POST数据创建一个新的form对象
    >>> form_obj = BookForm(request.POST) # 创建书籍对象
    >>> new_ book = form_obj.save() # 基于一个书籍对象创建form对象
    >>> edit_obj = Book.objects.get(id=1)
    # 使用POST提交的数据更新书籍对象
    >>> form_obj = BookForm(request.POST, instance=edit_obj)
    >>> form_obj.save()

11. modelform代码示例

  • view.py

    def edit_book(request,n):
    book_obj = models.Book.objects.filter(pk=n).first()
    if request.method == 'GET':
    # all_authors = models.Author.objects.all()
    # all_publish = models.Publish.objects.all() form = BookForm(instance=book_obj) return render(request,'edit_book.html',{'form':form,'n':n})
    # 传递的这个n参数是给form表单提交数据的是的action的url用的,因为它需要一个参数来识别是更新的哪条记录 else:
    form = BookForm(request.POST,instance=book_obj)
    # 必须指定instance,不然我们调用save方法的是又变成了添加操作
    if form.is_valid():
    form.save()
    return redirect('show')
    else:
    return render(request,'edit_book.html',{'form':form,'n':n})

    edit.html

    {% load static %}
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="{% static 'bootstrap-3.3.0-dist/dist/css/bootstrap.min.css' %}">
    </head>
    <body> <h1>编辑页面</h1>
    <div class="container-fluid">
    <div class="row">
    <div class="col-md-6 col-md-offset-3">
    <form action="{% url 'edit_book' n %}" novalidate method="post">
    {% csrf_token %}
    {% for field in form %}
    <div class="form-group">
    <label for="{{ field.id_for_label }}">{{ field.label }}</label>
    {{ field }}
    <span class="text-danger">{{ field.errors.0 }}</span>
    </div>
    {% endfor %} <div class="form-group">
    <input type="submit" class="btn btn-primary pull-right">
    </div>
    </form>
    </div>
    </div>
    </div> </body>
    <script src="{% static 'bootstrap-3.3.0-dist/dist/jQuery/jquery-3.1.1.js' %}"></script>
    <script src="{% static 'bootstrap-3.3.0-dist/dist/js/bootstrap.min.js' %}"></script>
    </html> <!-- field.id_for_label 会和{{ field }}标签的id相同 -->
05-08 15:26