Python入门自学进阶-Web框架——18FormModelForm

Posted kaoa000

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python入门自学进阶-Web框架——18FormModelForm相关的知识,希望对你有一定的参考价值。

Form:强大的数据验证,对前端的请求进行数据验证

基本的Form使用:

from django.shortcuts import render,HttpResponse
from django import forms
from django.forms import fields  # 一般引入forms就可以,但是字段类型是保存在forms下的fields中的,可以直接引入fields

class UserForm(forms.Form):
    # username = forms.CharField(label='用户名')  # 可以直接用forms引用字段
    username = fields.CharField()  # 也可以用fields引用字段
    email = fields.EmailField(label='邮箱')  # label=参数在前端生成标签时,输入框前面的标签就为这个label的值,如果不设置label,就是字段名,如上面的username

def index(req):
    if req.method == "GET":
        obj = UserForm()
        return render(req,'fm.html','obj':obj)
    elif req.method == "POST":
        obj = UserForm(req.POST)
        # 对请求的数据进行验证:
        # obj.is_valid()
        # data = obj.clean()
        # obj.errors
        return render(req,'fm.html','obj':obj)

以上是Form类和views函数。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
     obj.username 
     obj.email 
    <!-- 如果字段比较多,上面的写法要写很多obj.xxx,可以用下面的方法 -->
    <hr>
    <!-- 列出所有字段,并包裹在p标签中 -->
     obj.as_p 
    <hr>
    <!-- 列出所有字段,并包裹在li标签中 -->
    <ul>
     obj.as_ul 
    </ul>
    <hr>
    <!-- 列出所有字段,并包裹在table标签中 -->
    <table>
         obj.as_table 
    </table>

</body>
</html>

前端,根据form自动生成表单输入框。

Form中,Form对象的is_valid()执行规则的验证,查看这个is_valid()的源码链:

is_valid()——>self.errors——>full_clean()——>_clean_fields()——>看这个的源代码

    def _clean_fields(self):
        for name, field in self.fields.items():
            # value_from_datadict() gets the data from the data dictionaries.
            # Each widget type knows how to retrieve its own data, because some
            # widgets split data over several HTML fields.
            if field.disabled:
                value = self.get_initial_for_field(field, name)
            else:
                value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
            try:
                if isinstance(field, FileField):
                    initial = self.get_initial_for_field(field, name)
                    value = field.clean(value, initial)
                else:
                    value = field.clean(value)
                self.cleaned_data[name] = value
                if hasattr(self, 'clean_%s' % name):
                    value = getattr(self, 'clean_%s' % name)()
                    self.cleaned_data[name] = value
            except ValidationError as e:
                self.add_error(name, e)

从这段代码看,先执行了value=field.clean(),这应该是系统的规则验证,验证无错误,添加到field.clean()中,系统验证完毕后,判断这个Form对象是否包含有clean_%s name这个属性,name就是字段名,我们这里有username和email,也就是判断是否有clean_username和clean_email,有的话就执行,没有的话就跳过,然后结束,我们要添加我们自己定义的验证,就可以增加clean_username和clean_email函数,定义自己校验。

至于校验错误时,系统是抛出异常,即ValidationError异常,然后通过add_error()添加到errors中。所以我们自定义的校验,如果出现校验错误的话,可以抛出一个ValidationError异常,相应的错误也就增加到errors中了。

class UserForm(forms.Form):
    # username = forms.CharField(label='用户名')  # 可以直接用forms引用字段
    username = fields.CharField()  # 也可以用fields引用字段
    email = fields.EmailField(label='邮箱')  # label=参数在前端生成标签时,输入框前面的标签就为这个label的值,如果不设置label,就是字段名,如上面的username

    # 数据验证的过程,主要看源代码,先是经过自身的规则验证,然后再执行clean_字段名()这个函数
    def clean_username(self):
        value = self.cleaned_data['username']
        if value == 'root':
            return value
        else:
            raise ValidationError('输入的不是root。。。')
def index(req):
    if req.method == "GET":
        obj = UserForm()
        return render(req,'fm.html','obj':obj)
    elif req.method == "POST":
        obj = UserForm(req.POST)
        # 对请求的数据进行验证:
        obj.is_valid()
        data = obj.clean()
        print(obj.errors)
        return render(req,'fm.html','obj':obj)

我们在UserForm中增加一个clean_username函数,前端:

 <form action="fm.html" method="post">
    <table>
         obj.as_table 
        % csrf_token %
    </table>
        <input type="submit" value="提交">
    </form>

然后提交一个不是root的用户名,最后打印:

<ul class="errorlist"><li>username<ul class="errorlist"><li>输入的不是root。。。</li></ul></li><li>email<ul class="errorlist"><li>Enter a valid email address.</li></ul></li></ul>

前端:

 以上是对单一字段的自定义验证,如果想对一组字段进行组合自定义验证,那写在哪里呢?跟踪源代码,在full_clean()中有:
self._clean_fields()
self._clean_form()
self._post_clean()

self._clean_fields() 进入刚才的单个字段的验证,self._clean_form() 是形成clean()结果,self._post_clean()中是pass,这就是一个钩子,自定义的组合验证可以通过重写这个方法来实现:

    def _post_clean(self):
        v1 = self.cleaned_data['username']
        v2 = self.cleaned_data['email']
        if v1 == 'root' and v2 == "root@com.cn":
            pass
        else:
            self.add_error("__all__",ValidationError('用户名或邮箱错误。。。'))

运行结果:<ul class="errorlist"><li>__all__<ul class="errorlist nonfield"><li>用户名或邮箱错误。。。</li></ul></li></ul>

这里的关键一点是,添加错误信息时的字段只能是系统已有的,如“__all__”,如果是自己随便定义的是会出错的。也可以写None,即self.add_error(None,ValidationError('用户名或邮箱错误。。。')),这个None的错误最后会被系统加到“__all__”下。

这里是有BUG的,在获取v1和v2时,默认是前面已经通过验证了,即cleaned_data中有相关字段的值,否则,就会报错,如username输入其他值:

 在self._clean_form()中也有一个钩子:看其源代码

    def _clean_form(self):
        try:
            cleaned_data = self.clean()
        except ValidationError as e:
            self.add_error(None, e)
        else:
            if cleaned_data is not None:
                self.cleaned_data = cleaned_data

其中的self.clean():

    def clean(self):
        """
        Hook for doing any extra form-wide cleaning after Field.clean() has been
        called on every field. Any ValidationError raised by this method will
        not be associated with a particular field; it will have a special-case
        association with the field named '__all__'.
        """
        return self.cleaned_data

wom也可以重写clean,它抛出异常,_clean_form中也能捕获。

    def clean(self):
        v1 = self.cleaned_data['username']
        v2 = self.cleaned_data['email']
        if v1 == 'root' and v2 == "root@com.cn":
            pass
        else:
            raise ValidationError('用户名或邮箱错误。。。')
        return self.cleaned_data

这样也是可以自定义验证。但是这里有一个问题是:当重写了clean()方法后,并抛出了异常,就不会执行return self.cleaned_data,这时,主程序中获取请求的数据就不能使用obj.clean(),直接使用cleaned_data就行了。这里抛出的异常,被加入到错误中时,字段是“__all__”。

ModelForm:适当的数据库操作、适当的数据验证

Model中也是存在验证的,只是功能相对弱小:

class News(models.Model):
    title = models.CharField(max_length=256,
                             error_messages='c1':'c1所对应的错误提示信息',
                             validators=[
                                 RegexValidator(regex='root_\\d+',message='用户名错误了',code='c1')
                             ])
        def clean(self):
        pass

model中可以重写clean()方法来自定义验证,只不过这个方法没有处理异常,需要自己来处理,否则出现异常程序中断。full_clean ——>字段正则判定——>clean方法(钩子)

对于form在前端生成的下拉选择框,使用ChoiceField,如果只是单纯定义这个字段,前端不会实时更新数据,解决方法有两个:

class UserForm(forms.Form):
    # username = forms.CharField(label='用户名')  # 可以直接用forms引用字段
    username = fields.CharField()  # 也可以用fields引用字段
    email = fields.EmailField(label='邮箱')  # label=参数在前端生成标签时,输入框前面的标签就为这个label的值,如果不设置label,就是字段名,如上面的username

    usertype = fields.ChoiceField(choices=mymodels.UserType.objects.values_list('id','name'))

    usertype2 = models.ModelChoiceField(queryset=mymodels.UserType.objects.all(),
                                        empty_label="请选择类型",
                                        to_field_name='id',  # 这个参数定义选择项的value值,显示的下拉框的值是对象,要对UserType类增加__str__方法
                                        limit_choices_to='id':1)  #第二种解决实时显示数据的方法
    def __init__(self,*args,**kwargs):   # 重写构造函数,解决前端usertype不能实时更新的问题,这是第一种解决方法
        super(UserForm,self).__init__(*args,**kwargs)
        self.fields['usertype'].widget.choices = mymodels.UserType.objects.all().values_list('id','name')

    # 数据验证的过程,主要看源代码,先是经过自身的规则验证,然后再执行clean_字段名()这个函数
    def clean_username(self):
        value = self.cleaned_data['username']
        if value == 'root':
            return value
        else:
            raise ValidationError('输入的不是root。。。')

    def clean(self):
        v1 = self.cleaned_data['username']
        v2 = self.cleaned_data['email']
        if v1 == 'root' and v2 == "root@com.cn":
            pass
        else:
            raise ValidationError('用户名或邮箱错误。。。')
        return self.cleaned_data

 ModelForm就是为了补充Model验证较弱的缺点。

class UserModelForm(forms.ModelForm):
    class Meta:
        model = mymodels.UserInfo   # 指定这个ModelForm对哪个类执行验证等操作
        fields = "__all__"   # 指定这个ModelForm处理Model类中哪些字段,__all__代表所有字段

def mymodelform(req):
    if req.method == "GET":
        obj = UserModelForm()
        return  render(req,'mf.html','obj':obj)  # 注意obj是ModelForm对象,前端可以像Form一样,据此生成标签
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
     obj.email   <!-- 这里的obj是ModelForm,类似Form,也可以生成标签,实现Form功能 -->

</body>
</html>

对于Form和ModelForm,其继承关系如下:

Form:           UserForm——>Form——>BaseForm
ModelForm:    UserModelForm——>ModelForm——>BaseModelForm——>BaseForm

可以看到,最后都是继承于BaseForm。

所以前端可以使用 obj.as_p 生成全部字段

def mymodelform(req):
    if req.method == "GET":
        obj = UserModelForm()
        return  render(req,'mf.html','obj':obj)  # 注意obj是ModelForm对象,前端可以像Form一样,据此生成标签
    elif req.method == "POST":
        obj = UserModelForm(req.POST)
        ret = obj.is_valid()    # Form有的ModelForm都有
        print(ret)
        print(obj.cleaned_data)   # Form有的ModelForm都有
        print(obj.errors)         # Form有的ModelForm都有
        if obj.is_valid():
            print(obj.cleaned_data)
            # mymodels.News.objects.create(**obj.cleaned_data)  # Form时,保存数据,可以通过这样
            obj.save()  # ModelForm可以直接执行save()进行数据保存
        return  render(req,'mf.html','obj':obj)
<body>
     obj.email   <!-- 这里的obj是ModelForm,类似Form,也可以生成标签,实现Form功能 -->
    <hr>
    <form action="mf.html" method="post">
         obj.as_p 
        % csrf_token %
        <input type="submit" value="提交">
    </form>
</body>
</html>

利用ModelForm进行修改操作:

后端:

def mfedit_mf(req,nid):
    if req.method == "GET":
        model_obj = mymodels.News.objects.get(id=nid)  # 通过URL中的nid,找到对象,这个对象是Model对象
        obj = UserModelForm(instance=model_obj)       # 将Model对象赋值给ModelForm对象,这样才能在前端页面生成展示
        return render(req,'mf1.html','obj':obj,'nid':nid)
    elif req.method == "POST":
        model_obj = mymodels.News.objects.get(id=nid)
        obj = UserModelForm(req.POST,instance=model_obj)  # 当更新的时候,除了要获取request提交的数据,还需要设置instance=
        if obj.is_valid():
            print(obj.cleaned_data)
            # mymodels.News.objects.create(**obj.cleaned_data)  # Form时,保存数据,可以通过这样
            obj.save()  # ModelForm可以直接执行save()进行数据保存
        return  render(req,'mf1.html','obj':obj)

urls中增加:
path('mfedit-mf-<int:nid>/',myform.mfedit_mf),

这样可以通过mfedit-mf-1形式直接将要修改的记录的id传递过去,从数据库获取到对于记录的Model对象。

前端:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

    <hr>
    <form action="/mfedit-mf- nid /" method="post">
         obj.as_p 
        % csrf_token %
        <input type="submit" value="提交">
    </form>
</body>
</html>

主要注意点是获取时,是得到的一个Model对象,要想转换为ModelForm对象,在生成的ModelForm对象中使用instance=model_obj将获得的Model对象传递过去。

而在修改时,ModelForm对象中先获取前端的数据,即req.POST,然后必须使用instance=model_obj参数,告诉系统这是一个对相应Model对象的修改。

这里obj.save()完成了数据对表的保存。

这里的save,不仅能保存单表,还能够保存多表,即如果还有多对多字段,将会在第三张表中进行保存,还可以自己定制保存,即自己定制save()方法,是保存一张表,还是保存多张表。

save()有一个参数commit=True的默认参数,默认就是保存所有表,即字段中有多对多的字段,数据会保存多张表。如果commit=False,则不会进行保存表操作,这是mobj = obj.save(commit=False),这时save返回一个Model对象,使用这个对象的save,即mobj.save(),只保存当前主表,没有更新多对多表,要保存多对多表,需要使用mobj.save_多对多字段(),如mobj.save_m2m()。即分两步来分别保存主表和多对多表。

obj.save(commit=True)就等价于:mobj=obj.save(commit=False)   mobj.save()     obj.save_m2m()这三行代码

ModelForm的参数,除了Meta中的model和fields,还有其他的:Fields可以是“__all__”,也可使列表['name','email']格式,还有exclude,排除的字段,labels,标签,labels=‘name’:'用户名',‘enmail’:‘邮箱’,help_texts=,字段后的提示信息,widgets=,指定前端生成的元素

from django.forms import widgets as ws
class UserModelForm(forms.ModelForm):
    class Meta:
        model = mymodels.News   # 指定这个ModelForm对哪个类执行验证等操作
        fields = "__all__"   # 指定这个ModelForm处理Model类中哪些字段,__all__代表所有字段
        fields = ['title','content','url_laiyuan']  # 指定处理的具体字段
        exclude = ['contents',]  # 排除的处理字段
        labels = 'title':'标题','content':'内容'  # 输入框前的标签内容
        help_texts = 'title':'请输入标题','contents':'*'  # 输入框后的提示信息
        widgets = 
            'name': ws.Textarea(attrs='class':'c1',),  # 指定前端生成的元素类型
        error_messages = 
            'title':'required':'必填,不能为空','valid':'格式错误'  # 自定义错误消息
        
        field_classes = 
            'title':forms.EmailField,
             # 默认ModelForm的字段的验证,是使用Model的验证,这里可以使用这个参数,指定Form类型的验证
        localized_fields = ('时间字段名',)  # 本地化,更具不同时区显示数据,与settings.py中的设置有关,需要引入pytz模块

以上是关于Python入门自学进阶-Web框架——18FormModelForm的主要内容,如果未能解决你的问题,请参考以下文章

Python入门自学进阶-Web框架——20Django其他相关知识2

Python入门自学进阶-Web框架——2Django初识

Python入门自学进阶-Web框架——3Django的URL配置

Python入门自学进阶-Web框架——21DjangoAdmin项目应用

Python入门自学进阶-Web框架——21DjangoAdmin项目应用

Python入门自学进阶-Web框架——4HttpRequest和HttpResponse及模板