django基础 -- 10.form , ModelForm ,modelformset

一.生成页面可用的 HTML标签

  1.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, 小数位长度 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类型 复制代码 内置字段

 

 

  2.form常用字段和插件

  ① 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)
    )

 

     ④ radioselect  (单radio值为字符串)

class LoginForm(forms.Form):
    username = forms.CharField(  
#其他选择框或者输入框,基本都是在这个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.fields.ChoiceField(
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),
        label="爱好",
        initial=3,
        widget=forms.widgets.Select()
    )

 

  ⑥多选select

class LoginForm(forms.Form):
    ...
    hobby = forms.fields.MultipleChoiceField(
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),
        label="爱好",
        initial=[1, 3],
        widget=forms.widgets.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()
    )

 

  ⑨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())  # 单选

 

 

二.对用户提交的数据进行校验

  1.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开头')], )

 

  2.自定义验证函数

import re
from django.forms import Form
from django.forms import widgets
from django.forms import fields
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(Form): title = fields.CharField(max_length=20, min_length=5, error_messages={'required': '标题不能为空', 'min_length': '标题最少为5个字符', 'max_length': '标题最多为20个字符'}, widget=widgets.TextInput(attrs={'class': "form-control", 'placeholder': '标题5-20个字符'})) # 使用自定义验证规则 phone = fields.CharField(validators=[mobile_validate, ], error_messages={'required': '手机不能为空'}, widget=widgets.TextInput(attrs={'class': "form-control", 'placeholder': u'手机号码'})) email = fields.EmailField(required=False, error_messages={'required': u'邮箱不能为空','invalid': u'邮箱格式错误'}, widget=widgets.TextInput(attrs={'class': "form-control", 'placeholder': u'邮箱'}))

 

  3.钩子方法

  ① 局部钩子

  我们在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这个字段的错误列表中加上一个错误,并且clean_data里面会自动清除这个re_password的值,
所以打印clean_data的时候会看不到它
raise ValidationError('两次密码不一致')

 

 

三. form 综合示例:

  1.  使用form组件建立HTML标签,可在views.py文件中

   也可在appo1 下建立一个.py文件(更整洁)

 

 

   2.在views.py 文件中

from django.shortcuts import render
from app01 import models
# Create your views here.
from app01 import form_test      #引入form 表单建立验证的文件

def register(request):
    form_obj = form_test.MyForm()   #引入建立form表单的对象
    if request.method == "GET":
        return render(request,'register_page.html',{'form_obj':form_obj})
    else:
        #用户提交过来的数据
        # print(request.POST)
        form_obj = form_test.MyForm(request.POST)
        # print(form_obj.is_valid())
        # print(form_obj.cleaned_data)
        print('>>>>',form_obj.fields)
        if form_obj.is_valid():
            print(form_obj.cleaned_data) #获取验证通过的数据
        # else:
        #     print(form_obj.errors.as_data()) #拿错误信息
        # print(form_obj.errors.as_data())
        # print(form_obj)
        # username = request.POST.get('username')
        # password = request.POST.get('password')
        # print(username,password)
        # if username == ''
        # print(form_obj.cleaned_data)
        return render(request,'register_page.html',{'form_obj':form_obj})

 

   3.在form_test.py 文件中

from django import forms
from django.forms import ValidationError
from django.core.validators import RegexValidator
from app01 import models
import re

def mobile_validate(value):
    mobile_re = re.compile(r'^1[0-9]*$')
    if not mobile_re.match(value):
        raise ValidationError('手机号码格式错误')
        #自定义验证规则的时候,如果不符合你的规则,需要自己发起错误
class MyForm(forms.Form):
    uname = forms.CharField(
        required=True,
        label='用户名:',
        # initial='张三',  #输入框中的默认值
        min_length=6,    #最小长度
        strip=True,      #去掉两侧空白
        max_length=8,     #最大长度
        # validators=[mobile_validate,],
        validators=[RegexValidator(r'^[0-9]+$', '请输入数字'),
RegexValidator(r'^159[0-9]+$', '数字必须以159开头')], # disabled=True, error_messages={ 'required':'用户名不能为空', 'min_length':'你太短了,', 'max_length':'太长了', }, # widget=forms.TextInput(attrs={'class':'form-control'}), help_text='请输入用户名,不能短于6个字符,不能超过8个字符!' ) pword = forms.CharField( label='密码:', # required=False, # widget=forms.PasswordInput(attrs={'class':'form-control'}), widget=forms.PasswordInput(), ) re_pword = forms.CharField( label='确认密码:', # required=False, # widget=forms.PasswordInput(attrs={'class':'form-control'}), widget=forms.PasswordInput(), ) #局部钩子,在自定义的form类里面针对每个字段都可以写一些定制的规则,def clean_字段名(self): def clean_pword(self): data1 = self.cleaned_data.get('pword') if '666' in data1: raise ValidationError('你还不够6,包含敏感词汇') else: return data1 # def clean_uname(self): # # data1 = self.cleaned_data.get('uname') # if '666' in data1: # raise ValidationError('你还不够6,包含敏感词汇') # else: # return data1 # data1 = self.cleaned_data.get('pword') # data2 = self.cleaned_data.get('re_pword') # if data1 == data2:

  #全局钩子 def clean(self): p1 = self.cleaned_data.get('pword') p2 = self.cleaned_data.get('re_pword') if p1 == p2: return self.cleaned_data else: self.add_error('re_pword','和你上面输入的密码不同@@@') # raise ValidationError('两次输入的密码不同!!!') # sex = forms.CharField( # label='请选择性别:', # widget=forms.RadioSelect( # choices=((1,'男'),(2,'女'),('3','二椅子')), # ) # ) # sex = forms.ChoiceField( # # choices=(('1', '男'), ('2', '女'), ('3', '二椅子')), # # widget=forms.RadioSelect, # # widget=forms.CheckboxSelectMultiple, # # widget=forms.CheckboxSelectMultiple, # widget=forms.SelectMultiple, # # ) userinfo = forms.ModelMultipleChoiceField( label='请选择作者:', queryset=models.Author.objects.all(), ) email = forms.EmailField()

 

   4.在 .html 文件中

{% load static %}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Bootstrap 101 Template</title>
    <link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.min.css' %}">

</head>
<body>



<form action="{% url 'register' %}" method="post" novalidate>
  # novalidate : 自定义错误提示
{% csrf_token %} {# {{ form_obj.as_p }}#} {# <p>#} {# 所有字段错误汇总#} {# {{ form_obj.errors }}#} {# </p>#} <div> <div> {{ form_obj.uname.label }}    #引入 '用户民'  {{ form_obj.uname }}       #引入输入框 <span style="color: red;font-size: 12px;"> {{ form_obj.uname.errors.0 }} #错误提示 </span> </div> <div> {{ form_obj.uname.help_text }}    #输入提示 </div> </div> <p> {{ form_obj.pword.label }} {{ form_obj.pword }} <span style="color: red;font-size: 12px;"> {{ form_obj.pword.errors.0 }} </span> </p> <p> {{ form_obj.re_pword.label }}      #确认密码 {{ form_obj.re_pword }} <span style="color: red;font-size: 12px;"> {{ form_obj.re_pword.errors.0 }} </span> </p> <p> ------ {{ form_obj.errors }} </p> {# <p>#} {# {{ form_obj.sex.label }}#} {# {{ form_obj.sex }}#} {# <span style="color: red;font-size: 12px;">#} {# {{ form_obj.sex.errors.0 }}#} {# </span>#} {# </p>#} <p> {{ form_obj.userinfo.label }} {{ form_obj.userinfo }} {{ form_obj.userinfo.errors.0 }} </p> <p> {{ form_obj.email.label }} {{ form_obj.email }} {{ form_obj.email.errors.0 }} </p> <p> <button>提交</button> </p> </form> </body> </html>

 

四. modelform(自动根据字段生成表单)

  1.常用属性

class XXXModelForm(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)
        ...




 

  2.示例

在项目中新建立forms.py 文件


from django import forms

#
注册的form class RegForm(forms.ModelForm): password = forms.CharField(widget=forms.PasswordInput, label='密码', min_length=6) # 重写默认字段 re_password = forms.CharField(widget=forms.PasswordInput, label='确认密码', min_length=6) # 新增字段 class Meta: model = models.UserProfile # 指定model fields = '__all__' #所有字段 ['username','password'] # 指定字段 exclude = ['is_active'] labels = { 'username': '用户名' # 两种设置label方式,另一种是设置 verbose_name=' '

    
     widgets = {
  'username': forms.TextInput(attrs={'class': 'form-control', 'placeholder': '用户名'}),
  # 'password': forms.PasswordInput(attrs={'class': 'form-control'})

          }
   error_messages = {

   'min_length': '不能少于6位'
   }

  def __init__(self, *args, **kwargs):
  super().__init__(*args, **kwargs)
   # 自定义操作
   for field in self.fields.values():
 
   field.widget.attrs.update({'class': 'form-control'})

 def clean(self): # 全局钩子
  pwd = self.cleaned_data.get('password', '')
  re_pwd = self.cleaned_data.get('re_password', '')

  if pwd == re_pwd:
   # 密码加密
   md5 = hashlib.md5()
   md5.update(pwd.encode('utf-8'))
   pwd = md5.hexdigest()

   self.cleaned_data['password'] = pwd
   return self.cleaned_data
  # 两次密码不一致
   self.add_error('re_password', '两次密码不一致!!')
   raise ValidationError('两次密码不一致')
 

 

在view.py中进行实例化(操作更简单)


from crm.froms import RegForm   #引入文件

#
注册 def reg(request): # 判断请求方式 if request.method == 'POST': form_obj = RegForm(request.POST) # 对数据进行校验 if form_obj.is_valid(): form_obj.save() return redirect(reverse('login')) else: form_obj = RegForm() #实例化 return render(request, 'reg.html', {'form_obj': form_obj})

 

 

五.modelformset

  1.对多个对象直接进行编辑和保存

 

 

  2.示例:

①在forms.py 文件中(与modelform相同)

from django.core.exceptions import ValidationError

# BootstropForm
class BSForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# 自定义操作
for field in self.fields.values():
if not isinstance(field, forms.BooleanField):
# field.widget.attrs['class'] = 'form-control'
field.widget.attrs.update({'class': 'form-control'})



# 学习记录的form
class StudyRecordForm(BSForm):
class Meta:
model = models.StudyRecord
fields = "__all__"

def clean_note(self):    #局部钩子
note = self.cleaned_data.get('note', '')
if not note:
note = ''
if '666' in note:
raise ValidationError('不能太6')

return note


 

②在view.py 文件中

from crm.froms import StudyRecordForm
from django.forms import modelformset_factory



#
展示学习记录 def study_record_list(request, course_record_id): FormSet = modelformset_factory(models.StudyRecord, StudyRecordForm, extra=0)                         #注意格式 formset = FormSet(queryset=models.StudyRecord.objects.filter(course_record_id=course_record_id))                         #注意格式 if request.method == 'POST': formset = FormSet(request.POST) if formset.is_valid(): formset.save() return redirect(reverse('study_record_list', args=(course_record_id,))) return render(request, 'teacher/study_record_list.html', {'formset': formset})

 

 ③.在  . html 文件中

 

{% extends 'layout.html' %}


{% block content %}
    {% load my_tags %}
    <div>

    </div>

    <form action="" method="get" class="form-inline pull-right">

        <input type="text" name="query" class="form-control">
        <button class="btn btn-sm btn-primary">搜索</button>

    </form>
    <form action="" method="post" class="form-inline">
        {% csrf_token %}
        {{ formset.management_form }}  #必带

        <table class="table table-bordered table-hover">


            <thead>
            <tr>
                <th>选择</th>
                <th>序号</th>
                <th>学生</th>
                <th>考勤</th>
                <th>成绩</th>
                <th>批语</th>
                <th>备注</th>
            </tr>
            </thead>
            <tbody>

            {% for form in formset %}

                <tr>
                    <td>
                        <input type="checkbox" name="ids" value="{{ course_record.pk }}">
                    </td>
                    {{ form.id }}    #必带
                    <td>{{ forloop.counter }}</td>
                    <td>{{ form.instance.student }}</td>
                    <td>{{ form.attendance }}</td>
                    <td>{{ form.score }}</td>
                    <td>{{ form.homework_note }}</td>
                    <td>{{ form.note }}</td>
                    <span style="display: none"> {{ form.student }} {{ form.course_record }} </span>
                                        #必带
                </tr>

            {% endfor %}

            </tbody>
        </table>
        <button class="btn btn-sm btn-primary">保存</button>
    </form>

{% endblock %}

 

posted @ 2019-03-11 21:14  TNTsc  阅读(400)  评论(0编辑  收藏  举报