form组件使用案例

django内置的form组件使用案例

form组件可以自动生成表单标签,也能做数据校验,适用于前后端不分离的项目,前端使用form表单来提交数据的都可以使用form组件来完成. 而drf的serializers序列化器适用于前后端分离的项目,适用于前端通过ajax向后端提交数据/获取数据的情形下使用

  1. form代码

from django import forms
from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator
import re
from app.models import *

# 批量给表单添加bootstrap的form-control类样式
class BootstrapForm(forms.Form):
    # 指定哪些字段不使用form-control样式
    no_use_bootstrap_fields= []
    def __init__(self,*args,**kwargs):
        super().__init__(*args,**kwargs)
        for name,field in self.fields.items():
            if name not in self.no_use_bootstrap_fields:
                field.widget.attrs['class']=field.widget.attrs.get("class","")+" form-control"

class UserForm(BootstrapForm):
    no_use_bootstrap_fields = ['icon',"hobby",'sex','skill']
    
   	'''
        特别注意的大坑!!! 真正确定input表单的类型和样式的是字段对象的widget属性,而不是xxxField字段对象的类型
        每个xxxfield字段对象都有自己默认的widget,如果要修改表单类型,可以通过重置widget属性的属性值来实现.
        widget的属性值既可以是类也可以是实例对象
    '''   
    name=forms.CharField(label='用户名',widget=forms.TextInput(attrs={'style':"color:red"}),initial="匿名",
                         max_length=16,min_length=2,strip=True,)

    password = forms.CharField(label='密码', max_length=20, min_length=5,widget=forms.PasswordInput,
                               validators=[RegexValidator(r"^[a-zA-Z_]\w{5,10}$","密码必须是以字母或下划线开头的6-11位字符")])

    # 注意数据库里面没有repassword字段,所以在数据校验成功之后必须要form.cleaned_data.pop("repassword")删除该字段.
    repassword = forms.CharField(label='确认密码', max_length=20, min_length=5, widget=forms.PasswordInput,
                               validators=[RegexValidator(r"^[a-zA-Z_]\w{5,10}$","密码必须是以字母或下划线开头的6-11位字符")])

    email=forms.EmailField(label='qq邮箱',widget=forms.EmailInput(attrs={'type':'email'}))
    age=forms.IntegerField(label="年龄",widget=forms.TextInput(attrs={"placeholder":"请输入年龄"}),required=False)

    # widget中以Select开头的都是下拉选择框,否则不是下拉选择框
    sex=forms.ChoiceField(label="性别",widget=forms.RadioSelect,choices=[(0,"男"),(1,"女")],initial=1,)

    # !!!特别重要:(推荐写法)从数据库中导入数据列表直接赋值给字段的choices属性
    hobby=forms.MultipleChoiceField(label='喜欢的文章类型',widget=forms.SelectMultiple,
                                    choices=Category.objects.all().values_list('id','name'),initial=[2,4],)

    # 不推荐写法,可以忘掉:ModelxxxChoiceField字段,从数据库取出queryset赋值给字段的queryset属性
    # hobby2=forms.ModelMultipleChoiceField(label='喜欢的文章类型',widget=forms.CheckboxSelectMultiple,
    #                                 initial=[2,4],queryset=Category.objects.all())

    skill = forms.MultipleChoiceField(label='技能*', widget=forms.CheckboxSelectMultiple,
                                      # chioces列表中元素的第一个值是标签中实际显示的值,第二个值是value值,两者可以不同
                                      choices=[('python', 'python'), ('java', 'java'), ('rust', 'rust'), ('js', 'js')],
                                      initial=["python", 'rust'], )
    # !!!时间类型的表单的widget必须指定type="date",否则显示不出效果来,至于field字段类型和widget插件类形随便写都可以
    # 以下两种方式设置birth都可以,效果完全一样
    birth=forms.DateField(label="生日",widget=forms.DateInput(attrs={'type':'date'}),)
    # birth=forms.CharField(label="生日",widget=forms.TextInput(attrs={'type':'date'}),)

    icon=forms.ImageField(label="用户头像",widget=forms.FileInput,required=False,)
    xxx=forms.CharField(label='只读字段',initial="哈哈哈哈哈",disabled=True)
    # widget=forms.Textarea显示为多行输入文本框。 widget=forms.TextInput显示为单行输入框,是默认的插件类型
    profiles=forms.CharField(label="自我介绍",widget=forms.Textarea,required=False,)

    # 局部钩子函数 clean_字段()
    def clean_email(self):
        email=self.cleaned_data["email"]
        # print(tel)
        pattern=r"^\d{6,30}@qq.com$"
        if not re.match(pattern,email):
            # 添加错误信息的方式1:raise主动抛出错误,被异常处理拦截后调用add_error('字段名或None',"错误信息")
            # raise ValidationError("请输入6到30位的qq邮箱")

            # (推荐!)添加错误信息的方式2:self.add_error('字段名或None','错误信息'),
            # 通过 "form对象['email'].erros[0]" 获取email字段的第一个错误信息
            # 如果是add_error(None,'错误信息')则需要通过 "form对象.non_field_error()" 获取错误信息列表
            self.add_error('email',"必须是6到30位数字开头的qq邮箱")
        return email

    # 全局钩子函数
    def clean(self):
        data=self.cleaned_data
        password=data.get('password')
        repassword=data.get('repassword')

        if password != repassword:
            # 给某个字段添加错误信息 add_error()
            self.add_error("repassword","两次密码输入不一致") #指定给repassword字段添加错误信息
            # 以下两种写法效果一样都是添加全局的错误信息
            # self.add_error(None,"两次密码输入不一致") #添加全局的错误信息,错误信息不能通过字段对象获取,只能通过form对象获取
            # raise forms.ValidationError("请输入6到13位数字的qq邮箱")
        else:
            return data
  1. views代码

    from django.shortcuts import render,HttpResponse
    from app.myforms import *
    
    
    def register(request):
        if request.method=='GET':
            form_obj=UserForm()
            return render(request,'注册页面.html',locals())
        elif request.method=="POST":
            form_obj = UserForm(data=request.POST,files=request.FILES,)
            if form_obj.is_valid():
                print("打印cleaned_data=",form_obj.cleaned_data)
                # 方式1:form.save()自动保存数据到数据库(但必须是继承自ModelForm),会自动保存外键字段数据,且多传了数据库没有的字段也不会报错
                # form_obj.save()
                # 方式2:通过orm手动保存数据到数据库,不能保存多对多的外键字段数据,会报错,需要手动保存
                from django.db import transaction
                with transaction.atomic():
                    hobbys=form_obj.cleaned_data.pop("hobby") # 取出多对多字段,创建好user对象之后再调用user.hobby.set(hobbys)
                    form_obj.cleaned_data.pop("repassword") # 删除数据库中不需要的字段
                    form_obj.cleaned_data.pop("xxx") # 删除数据库中不需要的字段
                    user=User.objects.create(**form_obj.cleaned_data)
                    # 模型类ManyToManyField字段只能使用 "tableobj.外键字段.set(数据列表)"来保存!且不能调用tableobj.外键字段.save()
                    user.hobby.set(hobbys) # 会直接保存数据到user_hobby表中,不能再调用user.hobby.save()会报错user对象没有hobby属性
                    # user.hobby.save() # AttributeError: 'User' object has no attribute 'bobby'
                    # user.hobby=hobbys 错误写法!user表实际上并没有hobby字段,只有一对一和一对多字段的数据能这样写入
                    # Direct assignment to the forward side of a many-to-many set is prohibited. Use hobby.set() instead
                return HttpResponse('数据插入成功!')
            else:
                return render(request,'注册页面.html',{'form_obj':form_obj})
        return HttpResponse("请求不允许!")
    
  2. html代码
    <!DOCTYPE html>
    <html lang="en">
    <head>
        {% load static %}
        <script src="{% static 'jquery-3.6.0.min.js' %}"></script>
        <script src="{% static 'bootstrap.min.js' %}"></script>
        <link rel="stylesheet" href="{% static 'bootstrap.min.css' %}">
        <meta charset="UTF-8">
        <style>
            ul{overflow: hidden}
            ul li{float:left;margin:0 10px;list-style: none;}
            .form-group,button{margin:20px 0;}
    
        </style>
        <title>注册页面</title>
    
    </head>
    <body>
    <!--
        {#<form action="" method="post" class="container">#}
        	{# form对象.as_xxx 可以一次性生成所有表单标签和错误信息,字段错误信息会显示在字段的上方 #}
        {#    {{ form_obj.as_p }}#}
        {#    {{ form_obj.as_table }}#}
        {#    {{ form_obj.as_ul }}#}
        {#    <button type="submit">提交哈哈哈</button>#}
        {#</form>#}
    -->
    
    <div class="container">
        <form action="" method="post" class="row" enctype="multipart/form-data">
            {% for field in form_obj %}
                <div class="col-sm-8">
                    <div class="form-group">
                        <label for="{{ field.id_for_label }}"
                               class="col-sm-3">{{ field.label }}:</label>
                        <div class="col-sm-9">
                            {{ field }}
                            {# 只显示单个字段的某一条错误信息:field.errors.索引值 #}
                            {# <span class="text-danger">{{ field.errors.0 }}</span>#}
    
                            {# 显示单个字段的所有错误信息:field.errors.as_text 全部在一行显示 #}
                            <span class="text-danger">{{ field.errors.as_text }}</span>
                        </div>
                    </div>
                </div>
            {% endfor %}
    
            {# 显示全局的错误信息form_obj.non_field_errors,因为错误信息涉及到多个字段,所以不能通过单个字段对象来获取 #}
            {% for error in form_obj.non_field_errors %}
                <p class="text-danger col-md-8">{{ error }}</p>
            {% endfor %}
            <p class=" col-sm-8 text-center"><button type="submit" class="btn-primary">提交</button></p>
        </form>
    </div>
    </body>
    </html>
    
  3. 提交注册未通过时的页面显示效果图

    在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值