Django-Form组件和ModelForm

Django的Form主要具有一下几大功能:

  • 生成HTML标签
  • 验证用户数据(显示错误信息)
  • HTML Form提交保留上次提交数据
  • 初始化页面显示内容

Form组件

小试牛刀

1、创建Form类

from django.forms import Form
from django.forms import widgets
from django.forms import fields
 
class MyForm(Form):
    user = fields.CharField(
        widget=widgets.TextInput(attrs={'id': 'i1', 'class': 'c1'})
    )
 
    gender = fields.ChoiceField(
        choices=((1, ''), (2, ''),),
        initial=2,
        widget=widgets.RadioSelect
    )
 
    city = fields.CharField(
        initial=2,
        widget=widgets.Select(choices=((1,'上海'),(2,'北京'),))
    )
 
    pwd = fields.CharField(
        widget=widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True)
    )

2、View函数处理

from django.shortcuts import render, redirect
from .forms import MyForm
 
 
def index(request):
    if request.method == "GET":
        obj = MyForm()
        return render(request, 'index.html', {'form': obj})
    elif request.method == "POST":
        obj = MyForm(request.POST, request.FILES)
        if obj.is_valid():
            values = obj.clean()
            print(values)
        else:
            errors = obj.errors
            print(errors)
        return render(request, 'index.html', {'form': obj})
    else:
        return redirect('http://www.google.com')

3、生成HTML

<form action="/" method="POST" enctype="multipart/form-data">
    <p>{{ form.user }} {{ form.user.errors }}</p>
    <p>{{ form.gender }} {{ form.gender.errors }}</p>
    <p>{{ form.city }} {{ form.city.errors }}</p>
    <p>{{ form.pwd }} {{ form.pwd.errors }}</p>
    <input type="submit"/>
</form>
<form method="POST" enctype="multipart/form-data">
        {% csrf_token %}
        
            {{ form.xxoo.label }}
            {{ form.xxoo.id_for_label }}
            {{ form.xxoo.label_tag }}
            {{ form.xxoo.errors }}
            <p>{{ form.user }} {{ form.user.errors }}</p>
            <input type="submit" />
    </form>

Form类

创建Form类时,主要涉及到 【字段】 和 【插件】,字段用于对用户请求数据的验证,插件用于自动生成HTML;

1、Django内置字段如下:

Field

    required=True,               是否允许为空

    widget=None,                 HTML插件

    label=None,                  用于生成Label标签或显示内容

    initial=None,                初始值

    help_text='',                帮助信息(在标签旁边显示)

    error_messages=None,         错误信息 {'required': '不能为空', 'invalid': '格式错误'}

    show_hidden_initial=False,   是否在当前插件后面再加一个隐藏的且具有默认值的插件(可用于检验两次输入是否一直)

    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类型

    ...

注:UUID是根据MAC以及当前时间等创建的不重复的随机字符串

>>> import uuid

    # make a UUID based on the host ID and current time
    >>> uuid.uuid1()    # doctest: +SKIP
    UUID('a8098c1a-f86e-11da-bd1a-00112444be1e')

    # make a UUID using an MD5 hash of a namespace UUID and a name
    >>> uuid.uuid3(uuid.NAMESPACE_DNS, 'python.org')
    UUID('6fa459ea-ee8a-3ca4-894e-db77e160355e')

    # make a random UUID
    >>> uuid.uuid4()    # doctest: +SKIP
    UUID('16fd2706-8baf-433b-82eb-8c7fada847da')

    # make a UUID using a SHA-1 hash of a namespace UUID and a name
    >>> uuid.uuid5(uuid.NAMESPACE_DNS, 'python.org')
    UUID('886313e1-3b8a-5372-9b90-0c9aee199e5d')

    # make a UUID from a string of hex digits (braces and hyphens ignored)
    >>> x = uuid.UUID('{00010203-0405-0607-0809-0a0b0c0d0e0f}')

    # convert a UUID to a string of hex digits in standard form
    >>> str(x)
    '00010203-0405-0607-0809-0a0b0c0d0e0f'

    # get the raw 16 bytes of the UUID
    >>> x.bytes
    b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f'

    # make a UUID from a 16-byte string
    >>> uuid.UUID(bytes=x.bytes)
    UUID('00010203-0405-0607-0809-0a0b0c0d0e0f')

2、Django内置插件:

TextInput(Input)

NumberInput(TextInput)

EmailInput(TextInput)

URLInput(TextInput)

PasswordInput(TextInput)

HiddenInput(TextInput)

Textarea(Widget)

DateInput(DateTimeBaseInput)

DateTimeInput(DateTimeBaseInput)

TimeInput(DateTimeBaseInput)

CheckboxInput

Select

NullBooleanSelect

SelectMultiple

RadioSelect

CheckboxSelectMultiple

FileInput

ClearableFileInput

MultipleHiddenInput

SplitDateTimeWidget

SplitHiddenDateTimeWidget

SelectDateWidget

常用选择插件

# 单radio,值为字符串

# user = fields.CharField(

#     initial=2,

#     widget=widgets.RadioSelect(choices=((1,'上海'),(2,'北京'),))

# )

 

# 单radio,值为字符串

# user = fields.ChoiceField(

#     choices=((1, '上海'), (2, '北京'),),

#     initial=2,

#     widget=widgets.RadioSelect

# )

 

# 单select,值为字符串

# user = fields.CharField(

#     initial=2,

#     widget=widgets.Select(choices=((1,'上海'),(2,'北京'),))

# )

 

# 单select,值为字符串

# user = fields.ChoiceField(

#     choices=((1, '上海'), (2, '北京'),),

#     initial=2,

#     widget=widgets.Select

# )

 

# 多选select,值为列表

# user = fields.MultipleChoiceField(

#     choices=((1,'上海'),(2,'北京'),),

#     initial=[1,],

#     widget=widgets.SelectMultiple

# )

 

 

# 单checkbox

# user = fields.CharField(

#     widget=widgets.CheckboxInput()

# )

 

 

# 多选checkbox,值为列表

# user = fields.MultipleChoiceField(

#     initial=[2, ],

#     choices=((1, '上海'), (2, '北京'),),

#     widget=widgets.CheckboxSelectMultiple

# )
View Code

在使用选择标签时,需要注意choices的选项可以从数据库中获取,但是由于是静态字段 ***获取的值无法实时更新***,那么需要自定义构造方法从而达到此目的。

方式一:

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.ChoiceField(

        # choices=((1, '上海'), (2, '北京'),),

        initial=2,

        widget=widgets.Select

    )

 

    def __init__(self, *args, **kwargs):

        super(MyForm,self).__init__(*args, **kwargs)

        # self.fields['user'].widget.choices = ((1, '上海'), (2, '北京'),)

        #

        self.fields['user'].widget.choices = models.Classes.objects.all().value_list('id','caption')
View Code

方式二:

使用django提供的ModelChoiceField和ModelMultipleChoiceField字段来实现

from django import forms

from django.forms import fields

from django.forms import widgets

from django.forms import models as form_model

from django.core.exceptions import ValidationError

from django.core.validators import RegexValidator

 

class FInfo(forms.Form):

    authors = form_model.ModelMultipleChoiceField(queryset=models.NNewType.objects.all())

    # authors = form_model.ModelChoiceField(queryset=models.NNewType.objects.all())
View Code 

自定义验证规则

RegexValidator验证器

from django.forms import fields, widgets, Form
from django.core.validators import RegexValidator
phone = fields.CharField(
        label='手机号码',
        error_messages={
            'required': '不能为空',
        },
        validators=[
            RegexValidator(r'^[0-9]+$', '请输入数字!'),
            RegexValidator(r'^1[3|4|5|6|7|8][0-9]{9}$', '号码格式不正确!')
        ],
        widget=widgets.TextInput(
            attrs={
                'class': 'form-control',
                'placeholder': "激活账号需要短信验证"
            },
        )
    )
View Code 

自定义验证函数

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'邮箱'}))
View Code

局部HOOK&全局HOOK

from django.forms import fields, widgets, Form
from django.core.validators import RegexValidator
from django.core.exceptions import ValidationError
from blog import models


class MyForm(Form):
    # 邮箱
    email = fields.EmailField(
        label='邮  箱',
        error_messages={
            'required': '不能为空',
            'invalid': '邮箱格式不正确',
        },
        widget=widgets.EmailInput(
            attrs={
                'class': 'form-control',
                'placeholder': "jane.doe@example.com"
            }
        )
    )
    phone = fields.CharField(
        label='手机号码',
        error_messages={
            'required': '不能为空',
        },
        validators=[
            RegexValidator(r'^[0-9]+$', '请输入数字!'),
            RegexValidator(r'^1[3|4|5|6|7|8][0-9]{9}$', '号码格式不正确!')
        ],
        widget=widgets.TextInput(
            attrs={
                'class': 'form-control',
                'placeholder': "激活账号需要短信验证"
            },
        )
    )
    username = fields.CharField(
        label='登录名称',
        max_length=10,
        min_length=4,
        error_messages={
            'required': '不能为空',
            'max_length': '不得超过10个字符',
            'min_length': '不得少于4个字符'
        },
        widget=widgets.TextInput(
            attrs={
                'class': 'form-control',
                'placeholder': "登录用户名,不少于4个字符"
            }
        )
    )
    nickname = fields.CharField(
        label='显示名称',
        max_length=10,
        min_length=2,
        error_messages={
            'required': '不能为空',
            'max_length': '不得超过10个字符',
            'min_length': '不得少于2个字符'

        },
        widget=widgets.TextInput(
            attrs={
                'class': 'form-control',
                'placeholder': "即昵称,不少于2个字符"
            }
        )
    )
    password = fields.CharField(
        label='登录密码',
        max_length=30,
        min_length=8,
        error_messages={
            'required': '不能为空',
            'max_length': '不得超过30个字符',
            'min_length': '不得少于8个字符'

        },
        widget=widgets.PasswordInput(
            attrs={
                'class': 'form-control',
                'placeholder': "登录密码,至少8位",
            },
            render_value=True
        )
    )

    re_password = fields.CharField(
        label='确认密码',
        error_messages={
            'required': '密码不能为空'
        },
        widget=widgets.PasswordInput(
            attrs={
                'class': 'form-control',
                'placeholder': "请输入确认密码",
            },
            render_value=True
        )
    )

    '''
        局部HOOK:
        Fom类中定义 clean_字段名() 方法,就能够实现对特定字段进行校验。
    '''
    # 验证邮箱是否已存在
    def clean_email(self):
        email = self.cleaned_data.get('email', None)
        if email is not None:
            is_exist = models.User.objects.filter(email=email).count()
            if is_exist > 0:
                raise ValidationError('该邮箱已注册!')
        return email

    # 验证用户名是否已存在
    def clean_username(self):
        username = self.cleaned_data.get('username', None)
        if username is not None:
            is_exist = models.User.objects.filter(username=username).count()
            if is_exist > 0:
                raise ValidationError('用户名已被占用!')
        return username

    # 验证手机号码是否已存在
    def clean_phone(self):
        phone = self.cleaned_data.get('phone', None)
        if phone is not None:
            is_exist = models.User.objects.filter(phone=phone).count()
            if is_exist > 0:
                raise ValidationError('该手机号码已注册!')
        return phone

    '''
        全局HOOK:
        在Fom类中定义 clean() 方法,就能够实现对字段进行全局校验。
    '''
    # 验证确认密码是否正确
    def clean(self):
        pwd = self.cleaned_data.get('password')
        re_pwd = self.cleaned_data.get('re_password')
        if pwd == re_pwd or pwd is None:
            return self.cleaned_data
        else:
            self.add_error('re_password', '两次密码不一致!')
            raise ValidationError('两次密码不一致!')
View Code

批量添加样式

class LoginForm(forms.Form):
    username = forms.CharField(
        min_length=8,
        label="用户名",
        initial="张三",
        error_messages={
            "required": "不能为空",
            "invalid": "格式错误",
            "min_length": "用户名最短8位"
        }
    ...

    def __init__(self, *args, **kwargs):
        super(LoginForm, self).__init__(*args, **kwargs)
        for field in iter(self.fields):
            self.fields[field].widget.attrs.update({
                'class': 'form-control'
            })
View Code

 

 

ModelForm

这是一个神奇的组件,通过名字我们可以看出来,这个组件的功能就是把model和form组合起来,通常在Django项目中,我们编写的大部分都是与Django 的模型紧密映射的表单。 举个例子,你也许会有个Book 模型,并且你还想创建一个form表单用来添加和编辑书籍信息到这个模型中。 在这种情况下,在form表单中定义字段将是冗余的,因为我们已经在模型中定义了那些字段。

基于这个原因,Django 提供一个辅助类来让我们可以从Django 的模型创建Form,这就是ModelForm。

 

创建ModelForm

#首先导入ModelForm

from django.forms import ModelForm
#在视图函数中,定义一个类,比如就叫StudentList,这个类要继承ModelForm,在这个类中再写一个原类Meta(规定写法,并注意首字母是大写的)
#在这个原类中,有以下属性(部分):

class StudentList(ModelForm):
    class Meta:
        model =Student #对应的Model中的类
        fields = "__all__" #字段,如果是__all__,就是表示列出所有的字段
        exclude = None #排除的字段
        #error_messages用法:
        error_messages = {
        'name':{'required':"用户名不能为空",},
        'age':{'required':"年龄不能为空",},
        }
        #widgets用法,比如把输入用户名的input框给为Textarea
        #首先得导入模块
        from django.forms import widgets as wid #因为重名,所以起个别名
        widgets = {
        "name":wid.Textarea(attrs={"class":"c1"}) #还可以自定义属性
        }
        #labels,自定义在前端显示的名字
        labels= {
        "name":"用户名"
        }
View Code

对应view视图

def student(request):

    if request.method == 'GET':
        student_list = StudentList()
        return render(request,'student.html',{'student_list':student_list})
View Code

 模板:

<body>
<div class="container">
    <h1>student</h1>
    <form method="POST" novalidate>
        {% csrf_token %}
        {# {{ student_list.as_p }}#}
        {% for student in student_list %}
            <div class="form-group col-md-6">
                {# 拿到数据字段的verbose_name,没有就默认显示字段名 #}
                <label class="col-md-3 control-label">{{ student.label }}</label>
                <div class="col-md-9" style="position: relative;">{{ student }}</div>
            </div>
        {% endfor %}
        <div class="col-md-2 col-md-offset-10">
            <input type="submit" value="提交" class="btn-primary">
        </div>
    </form>
</div>
</body>
View Code

class Meta下常用参数:

model = models.Book  # 对应的Model中的类
fields = "__all__"  # 字段,如果是__all__,就是表示列出所有的字段
exclude = None  # 排除的字段
labels = None  # 提示信息
help_texts = None  # 帮助提示信息
widgets = None  # 自定义插件
error_messages = None  # 自定义错误信息
View Code

添加和编辑数据

from django.shortcuts import render,redirect
from django.forms import ModelForm,widgets as wid
from app01 import models

# Create your views here.
class BookForm(ModelForm):
    class Meta:
        model = models.Book
        fields = '__all__'
        widgets = {
            'publishDate':wid.DateInput(attrs={'type':'date'})
        }

def book(request):
    book_list = models.Book.objects.all()
    print(book_list[0].authors.first())
    return render(request, 'book.html', locals())


def addbook(request):
    if request.method=='POST':
        obj = BookForm(request.POST)
        if obj.is_valid():
            obj.save()
            return redirect('/book/')
    obj = BookForm()
    return render(request, 'addbook.html', locals())


def editbook(request,id):
    book_obj = models.Book.objects.filter(pk = id).first()
    if request.method=='POST':
        obj = BookForm(request.POST,instance=book_obj)
        if obj.is_valid():
            obj.save()
            return redirect('/book/')
    obj = BookForm(instance=book_obj)
    return render(request, 'editbook.html', locals())

 总结:

添加数据:只需要save()即可,而不用逐个传值。

编辑数据:加上instance=一条数据的对象参数,在显示页面时就可以显示之前的数据,而不用像以前那样逐个赋上原有的值。保存的时候要注意,一定要注意有这个对象(instance=obj),否则不知道更新哪一个数据

 从上边可以看到ModelForm用起来是非常方便的,比如增加修改之类的操作。但是也带来额外不好的地方,model和form之间耦合了。如果不耦合的话,mf.save()方法也无法直接提交保存。 但是耦合的话使用场景通常局限用于小程序,写大程序就最好不用了。

 

 

 

 

 

 

 

 

 

posted @ 2019-03-18 22:00  小夏02  阅读(97)  评论(0)    收藏  举报