Django Form的使用

时间:2022-07-23
本文章向大家介绍Django Form的使用,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

最近在项目上用到了 Django 的自带的 Form 表单,遇到了一些坑,这里做一个简单的总结,大家可以对号出坑。

Form 基础介绍

首先让我们先来了解下 Django 中 Form 表单的基本用法。Django 中提供了两种 Form 表单类型,一种是 forms.Form ,另外一种是 forms.ModelForm 。很明显,一种是普通的 Form 表单类型,另外一种是和 Model 有关联的表单类型。官方文档中是这样来介绍 ModelForm 的:

If you’re building a database-driven app, chances are you’ll have forms that map closely to Django models. For instance, you might have a BlogComment model, and you want to create a form that lets people submit comments. In this case, it would be redundant to define the field types in your form, because you’ve already defined the fields in your model. For this reason, Django provides a helper class that lets you create a Form class from a Django model.

对于 Django 中的 Form 表单的用法,我们只需要了解以下几点:

  • 它是一个定义一个 Form 类,基类是 django.forms.Forms 或者 django.forms.ModelForm ,在 view 中实例化定义好的 Form 类,在模板中使用 {{ form }} 即可自动生成对应的 form 表单内容。
  • ModelForm 比较简单,它适用于:当你创建的表单内容与某个 Model 内容很相似的情况。如上面文档介绍的一样
  • 在 Form 类中,clean 方法可以在做表单验证,它是一个总的验证方法。clean_xxx 是单个表单验证方法,其中 xxx 是对应的属性名称
  • form.clean_data 是会得到字典类型,key 是对应属性名,value 即为表单输入的值
  • 生成的 form 标签,id 是有特殊规律的,我们可以通过这些 id 进行一些 js 操作

问题总结

在这次项目需求中,我主要遇到的问题是,有几个表单页面,后台使用同一张表去做存储,但是每个页面有许多变化的元素,如果为了存储这些可变的值,每个元素都用数据库一个字段去做存储不太现实,因为需求是一直在变化的。所以我采用的解决办法是提取公共的元素,其他可变的元素用了一个json字段存在数据库中。

而这样导致的问题就是,不能使用 ModelForm ,我选择了使用普通的 forms.Form 。这样遇到了一些问题,总结如下:

  • forms.Form 的初始化

有两种初始化方式:

# 第一种方式:
# 初始化一个空的 form 表单,同时绑定页面上的表单输入值,即能接受页面上的输入值
# 能接受页面上的输入值,这点很重要
form = UserForm(request.POST or None, request.FILES or None)

# 第二种方式:
# 接受一个字典,做赋值初始化
form = UserForm({'name': 'Demon', 'age': 8})

基于这两种做法,我很显然的在 view.py 中写出了这样的代码:

def create_user(request):
    # 根据是否传入 uid 来判断是创建还是编辑
    uid = request.GET.get('uid', '')
    if uid:
        # 如果传入了 uid ,查找当前 uid
        account = Account.objects.filter(id=uid).first()
        # 封装当前 账号 的信息,做表单初始化
        # 为了说明问题,不考虑 account 没有找到的情况
        user_info = {
           'name': account.name,
           'age': account.age
        }
        # 表单初始化
        form = UserForm(user_info)
    else:
        # 如果是新建,则初始化一个空的表单
        form  = UserForm(request.POST or None, request.FILES or None)
    if request.method == 'POST':
        if form.is_valid():
            # do save opertion
            pass
    return render(request, 'xxx/xxx.html', local())

看上去一切安好,代码也十分简单。但问题来了,新建还行,但当我们使用编辑的时候,会发现,没办法做修改,即当代码走到 form.is_valid() 时,它始终做了 dict 的初始化,它不再会接受你新输入的值。

解决思路如下:

每次都初始化一个空的 form ,前端渲染页面时,用 js 去控制页面的展示。这也比较简单,这里不做多的说明。

  • 图片格式编辑页如何获取之前展示的结果

对于图片,新建的时候上传还比较简单,问题是在于如果是编辑,如何带回原来的上传结果。我们都知道 <input type='file' /> 是没办法赋值的。所以表单初始化的时候,也没有办法进行赋值。

解决思路如下:

在表单中新开一个字段,用来存储上传后的图片链接,当图片未上传时,整个 div 隐藏,当图片有值时,整个 div 展示。后端通过判断真正的 file 字段 与 url 字段,来判断是否有新上传文件。最终效果如下:

  • clean_xxx 方法未返回值时,form.clean_data['xxx'] 获取不到值

这是需要比较注意的一点,我们可以通过写 clean_xxx(self) 的方法,来对表单的某个属性做校验,但一旦校验通过,注意一定要返回当前输入的值。正确示例如下:

class UserForm(forms.Form):
    """用户表单"""
    name = forms.IntegerField(label='姓名', required=True)
    age = forms.IntegerField(label='年龄', required=True, min_value=0)

    def clean_name(self):
        """name不超过20个字符"""
        name = self.cleaned_data.get('name')
        if not name:
            raise forms.ValidationError('请输入姓名')
        if name and len(name) > 20:
            raise forms.ValidationError('长度不超过20')
        # 注意一定要返回输入值,否则后端获取不到输入的值
        return account_id

小结

form.Forms 我还是比较喜欢用的,我觉得封装了很多比较好的用法,比如限制必输,限制最小值、最大值等。只要避免一些坑,就会比较得心应手了。

另外我准备一个实现添加用户的小 demo ,基本字段要求如下:

  • 姓名,必输,长度不超过20
  • 年龄,必输,不能为负数
  • 头像,必输,大小不超过 200 K
  • 电话,非必输,仅做数字校验
  • 性别,下拉框,0为未知、1为男、2为女,默认为0
  • 住址,非必输

基本操作如下:

  • 可新增
  • 可编辑
  • 可删除

demo 中分别用 ModelForm 和 Form 来实现这个功能,感兴趣的可添加微信,回复“form demo” 获取。