二十三、Django之Form组件

Bruce / 2024-02-12 / 原文

Django的Form:
1、对用户请求的验证
2、生成HTML代码

a、创建一个类
b、类中创建字段(包含正则表达式)
c、Get
   a) Obj = Fr()
      obj.user=> 自动生成HTML
d、POST
   a) Obj = Fr(request,POST)
      i. If obj.is_valid():
            Obj.cleaned_data
         Else:
            Obj.errors
           Return .....obj

1、Form的使用

from django.shortcuts import render, HttpResponse
from django import forms
from django.forms import fields
from django.forms import widgets

class TestForm(forms.Form):
    user=fields.CharField(
        required=True, #是否必填
        max_length=12, #最大长度
        min_length=3, #最小长度
        error_messages={
            'required':'用户名不能为空',
            'max_length':'太长了',
            'min_length':'太短了',
        }, #错误提示
        # widget=widgets.Select(), #定制HTML插件
        label='用户名',
        initial='xx',
        help_text='helptext',
        # show_hidden_initial=True,
        # validators=[], #自定制验证规则
        # disabled=False,
        label_suffix='->',
    )
    age=fields.IntegerField(
        label='年龄',
        max_value=12,
        min_value=5,
    )
    email=fields.EmailField(
        label='邮箱',
    )

    img=fields.ImageField()

    city=fields.ChoiceField(
        choices=[(1,'北京'),(2,'上海'),(3,'深圳')],
        initial=3, #默认值
    )

    city2=fields.CharField(
        widget=widgets.Select(choices=[(1,'北京'),(2,'上海'),(3,'深圳')])
    )

    city3 = fields.IntegerField(
        widget=widgets.Select(choices=[(1, '北京'), (2, '上海'), (3, '深圳')])
    )

    hobby=fields.MultipleChoiceField(
        choices=[(1,'跑'),(2,'跳'),(3,'游'),(4,'飞')],
        initial=[1,3,4],
    )

    anotherCity=fields.TypedChoiceField(
        coerce=lambda x:int(x), # 设置coerce函数,将输出的值转换成需要的类型
        choices=[(1, '北京'), (2, '上海'), (3, '深圳')],
        initial=3,  # 默认值
    )

    fp=fields.FilePathField(
        path='app01'
    )

def test(request):
    if request.method=='GET':
        obj=TestForm()
        return  render(request,'test.html',{'obj':obj})
    else:
        obj=TestForm(request.POST,request.FILES)
        obj.is_valid()
        print(obj.cleaned_data)
        return render(request,'test.html',{'obj':obj})
<body>
  <form action="/test/" method="post" novalidate enctype="multipart/form-data">
    <p>{{ obj.user.label }}{{ obj.user }}</p>
    <p>{{ obj.age.label }}{{ obj.age }}{{ obj.errors.age.0 }}</p>
    <p>{{ obj.email.label }}{{ obj.email }}</p>
    <p>{{ obj.img.label }}{{ obj.img }}</p>
    <p>{{ obj.city.label }}{{ obj.city }}</p>
    <p>{{ obj.city2.label }}{{ obj.city2 }}</p>
    <p>{{ obj.city3.label }}{{ obj.city3 }}</p>
    <p>{{ obj.hobby.label }}{{ obj.hobby }}</p>
    <p>{{ obj.anotherCity.label }}{{ obj.anotherCity }}</p>
    <p>{{ obj.fp.label }}{{ obj.fp }}</p>
    <input type="submit" value="提交">
  </form>
</body>

form元素的novalidate标识:取消浏览器对数据的验证,交由后台验证数据。
models.UserInfo.objects.create(fm_obj.cleaned_data) 😗*

# Form与Model的字段名保持一致
from django.db import models

class UserInfo(models.Model):
    username=models.CharField(max_length=32)
    email=models.EmailField(max_length=32)
-----------
from  django import forms as dforms
from django.forms import fields

class UserForm(dforms.Form):
    username=fields.CharField()
    email=fields.EmailField()

-----------
# form数据与model数据就方便转换
def add_user(request):
    if request.method == 'GET':
        fr_obj = UserForm()
        return render(request,'add_user.html',{'fm_obj':fr_obj})
    else:
        fm_obj = UserForm(request.POST)
        if fm_obj.is_valid():
            models.UserInfo.objects.create(**fm_obj.cleaned_data)
            return redirect('/users/')
        else:
            return render(request,'add_user.html',{'fm_obj':fm_obj})
def edit_user(request,nid):
    if request.method == 'GET':
        data = models.UserInfo.objects.filter(id=nid).first()
        fm_obj = UserForm({'username':data.username,'email':data.email})
        return render(request, 'edit_user.html', {'obj': fm_obj, 'nid': nid})
    else:
        obj = UserForm(request.POST)
        if obj.is_valid():
            models.UserInfo.objects.filter(id=nid).update(**obj.cleaned_data)
            return redirect('/users/')
        else:
            return render(request,'edit_user.html',{'obj':obj,'nid':nid})

2、进阶

# 表单数据实时更新
from app01 import models
from django.forms.models import ModelChoiceField
class LoveForm(forms.Form):
    # price user_id 为静态字段,在程序加载时创建。
    # 因此程序启动之后,UserInfo数据库的数据如果有变化,user_id的选项并不会实时变化。
    # 必须重启后台程序。这样设计是不合理的。
    price=forms.IntegerField()
    user_id=forms.ChoiceField(
        #重写了__init__方法后,这里choices不需要赋值了
        # choices=models.UserInfo.objects.values_list('id','username')
    )

    # 继承初始化方法,在每次网络请求时创建Form对象的时候通过self.fields['user_id'].widget.choices重新从数据库取值
    def __init__(self,*args,**kwargs):
        super(LoveForm,self).__init__(*args,**kwargs)

        self.fields['user_id'].widget.choices=models.UserInfo.objects.values_list('id','username')
实时更新方式2(不推荐)
方式2:ModelChoiceField。
不推荐,耦合度高,需要写__str__方法来定选择框显示的内容
...
    user_id2=ModelChoiceField(
        queryset=models.UserInfo.objects.all(),
        to_field_name='id'
    )
...
class UserInfo(models.Model):
    ...
    def __str__(self):
        return self.username

3、Form的数据验证

class AjaxForm(forms.Form):
    username=forms.CharField()
    user_id=forms.ChoiceField(
        choices=[(0,'Java'),(1,'PHP'),(2,'Python'),(3,'GO')]
    )

#   自定义clean_字段名方法
    # 必须返回值self.cleadned_data['username']
    # 如果出错:raise ValidationError
    def clean_username(self):
        v = self.cleaned_data['username']
        if models.UserInfo.objects.filter(username=v).count():
            # 报错
            # 自己详细错误信息
            from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
            raise ValidationError('用户名已存在')
        return v
#	数据整体验证 抛出的错误会放在__all__列表中,而非某字段名的列表中。
    def clean(self):
        value_dict=self.cleaned_data
        v1=value_dict.get('username')
        v2=value_dict.get('user_id')
        if v1=='root'and v2=='1':
            from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
            raise ValidationError('整体错误信息')
        return self.cleaned_data

def ajax(request):
    if request.method=='GET':
        obj=AjaxForm()
        return render(request,'ajax.html',{'obj':obj})
    else:
        import json
        obj=AjaxForm(request.POST)
        if obj.is_valid():
            return HttpResponse(json.dumps({'status':100,'message':obj.cleaned_data}))
        else:
            return HttpResponse(json.dumps({'status':111,'message':obj.errors}))

从Form的is_vaild()函数进入源码,可知有clean_***、clean等数据验证函数提供重写自定义。

4、as_p、as_ul、as_table

<body>
  // {{ form_obj.as_p }}
  // {{ form_obj.as_ul }}
  {{ form_obj.as_table }} 
</body>

这几种方式都可以直接生成页面。但推荐的是:

<body>
  <form action="/edit_user-{{ nid }}/" method="post" novalidate>
        <p>{{ obj.username }}{{ obj.errors.username.0 }}</p>
        <p>{{ obj.email }}{{ obj.errors.email.0 }}</p>
        <input type="submit" value="提交"/>
    </form>
</body>

5、FileField、ImageField

使用这两个字段时,
form表单中 enctype="multipart/form-data",
view函数中 obj = MyForm(request.POST, request.FILES)

6、ComboField

多个验证
a = fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),])

7、FilePathField(但不建议使用):