Django Form组件
From的功能实际有三个,Form验证,From添加标签,Form保留返回值
保留返回值的功能还可用ajax发送
一、Form验证
对用户的请求的数据进行验证
使用类
从前端获取的数据的name属性的名字要和定义的class类中的字段一致
二、From添加标签
1 自己在前端写标签
自己在前端写input标签,在提交后在后台获取name属性的值进行验证,user要和LoginForm 中的字段名一致,
之后obj.is_valid()会自动验证,验证通过(通过后正确信息是obj.cleaned_data,
),返回True,不通过返回false(错误信息obj.errors),通常在前端获取,如obj.errors.user.0
这里的问题是提交后不能保存信息
<form id="f1" method="POST" action="/login/">
{% csrf_token %}
<p>
用户名:<input type="text" name="user">{{ obj.errors.user.0}}
</p>
<p>
密码:<input type="password" name="pwd">{{ obj.errors.pwd.0 }}
</p>
<input type="submit" value="提交">
<a href="" onclick="submitForm();">ajax提交</a>
</form>
2 Form在前端生成标签
在前端生成的关键是在提交GET请求的时候传递obj到前端,这个obj是LoginForm实例化的对象,内部不加参数
def login(request):
if request.method == "GET":
obj = LoginForm() # 此处的obj不加参数
return render(request, 'login.html', {'obj': obj})
return render(request, 'login.html')
else:
obj = LoginForm(request.POST)
if obj.is_valid():
print(obj.cleaned_data)
return redirect('http://www.baidu.com')
return render(request, 'login.html', {'obj': obj})
前端代码
第一个obj.user是Form生成的标签,第二个是post请求显示的错误信息
<form id="f1" method="POST" action="/login/">
{% csrf_token %}
<p>
用户名:{{ obj.user }}{{ obj.errors.user.0}}
</p>
<p>
密码:{{ obj.pwd }}{{ obj.errors.pwd.0 }}
</p>
<input type="submit" value="提交">
<a href="" onclick="submitForm();">ajax提交</a>
</form>
三、Form保留返回值
通过from生成标签能够保留表单已经填的通过验证的信息,不能验证通过的不保留
密码的位置加上了 widget=widgets.PasswordInput()
,输入的密码就是隐藏的
1 定义Form类,继承Form
from django.shortcuts import render, redirect, HttpResponse
from django.forms import Form
from django.forms import fields
from django.forms import widgets
import json
class LoginForm(Form):
user = fields.CharField(
max_length=18,
min_length=6,
required=True,
error_messages={
'max_length': '用户名太长了',
'min_length': '用户名太短了',
'required': '用户名不能为空',
},
)
pwd = fields.CharField(
min_length=16,
required=True,
error_messages={
'min_length': '密码太短了',
'required': '密码不能为空',
},
widget=widgets.PasswordInput(), # 密码格式,将输入的内容隐藏
)
2 GET请求和POST请求都发送obj
GET请求的时候需要实例化一个对象obj = LoginForm()
def login(request):
if request.method == "GET":
obj = LoginForm() # 此处的obj不加参数
return render(request, 'login.html', {'obj': obj})
else:
obj = LoginForm(request.POST)
if obj.is_valid():
print(obj.cleaned_data)
return redirect('http://www.baidu.com')
return render(request, 'login.html', {'obj': obj})
3 前端
现在的浏览器默认会在前端进行验证,在测试的时候加上novalidate,之后删除
<form id="f1" method="POST" action="/login/" novalidate>
{% csrf_token %}
<p>
用户名:{{ obj.user }}{{ obj.errors.user.0}}
</p>
<p>
密码:{{ obj.pwd }}{{ obj.errors.pwd.0 }}
</p>
<input type="submit" value="提交">
</form>
ajax保留返回值
1 后台代码
关键点:
- 设置字典,没有通过验证把status设置成false并把obj.errors通过json发送到后台
def ajax_login(request):
ret = {'status': True, 'msg': None}
obj = LoginForm(request.POST)
if obj.is_valid():
print(obj.cleaned_data)
else:
# print(obj.errors)
ret['status'] = False
ret['msg'] = obj.errors
v = json.dumps(ret) # 把错误的数据发送到后台
return HttpResponse(v)
2 前端代码
- 在绑定事件的a标签上不要加href,自己点击的时候会默认刷新
- 点击的时候清除上一次的错误信息
- ajax的data发送的的时候会把字典打包成字符串。通过ajax的serialize
打包获取数据 - 错误信息的内容是通过字符串拼接
<form id="f1" method="POST" action="/login/" novalidate>
{% csrf_token %}
<p>
用户名:{{ obj.user }}{{ obj.errors.user.0}}
</p>
<p>
密码:{{ obj.pwd }}{{ obj.errors.pwd.0 }}
</p>
<input type="submit" value="提交">
<a onclick="submitForm();">ajax提交</a>
</form>
<script src="/static/jquery-3.2.1.js"></script>
<script>
function submitForm() {
$('.c1').remove();//每次点击清除上一次的错误信息
$.ajax({
url: '/ajax_login/',
type: 'POST',
data: $('#f1').serialize(),//获取form的所有数据并打包 包含了csrf的随机字符串
dataType: 'JSON',
success: function (arg) {
console.log(arg);
if (arg.status) {
} else {
$.each(arg.msg, function (index, value) {
console.log(index, value);
var tag = document.createElement('span');
tag.innerHTML = value[0];
tag.className = 'c1';//给错误信息添加class
$('#f1').find('input[name="' + index + '"]').after(tag);
/*拼接字符串*/
})
}
}
})
}
</script>
四、使用form实现学生、班级、教师管理
1 班级管理
1.1 班级form验证
班级的名字以班级开头 后面加数字
from django.shortcuts import render, redirect, HttpResponse
from django.forms import Form
from django.forms import fields
from django.forms import widgets
import json
from app01 import models
class ClassForm(Form):
"""
添加班级的验证规则 以班级开头
"""
title = fields.RegexField('班级\d+') # 这个名字和数据库的字段名一致
** 1.2 班级列表**
从数据库获取班级的全部信息,并发送到前端页面
def class_list(request):
cls_list = models.Classes.objects.all()
return render(request, 'class_list.html', {'cls_list': cls_list})
班级列表前端是直接循环列出班级列表,row.title
<h1>班级列表</h1>
<div>
<a href="/add_class/">添加班级</a>
</div>
<ul>
{% for row in cls_list %}
<li>{{ row.title }}</li>
{% endfor %}
</ul>
1.3 添加班级列表
- obj = ClassForm() # 创建标签
- 通过验证后 obj.cleaned_data 是字典类型
- 开始定义和数据库中的字段名字一致,直接在下面用**对字典的数据进行接收
- 没有通过验证返回错误的信息
def add_class(request):
if request.method == "GET":
obj = ClassForm() # 创建标签
return render(request,'add_class.html',{'obj':obj})
else:
obj = ClassForm(request.POST)
if obj.is_valid():
"""
通过验证后 obj.cleaned_data 是字典类型
在数据库添加一条数据
models.Classes.objects.create(title=obj.cleaned_data['xxx'])
如果字段的名字是xxx 就需要自己去取出来
所以开始定义和数据库中的字段名字一致,直接在下面用**对字典的数据进行接收
"""
print(obj.cleaned_data)
models.Classes.objects.create(**obj.cleaned_data) # 使用**接收
return redirect('/class_list/')
return render(request,'add_class.html',{'obj':obj}) # 没有通过验证返回错误的信息
前端:
- obj.title 创建标签
- obj.errors.title.0 显示错误信息
<h1>添加班级</h1>
<form method="POST" action="/add_class/">
{% csrf_token %}
{{ obj.title }} {{ obj.errors.title.0 }}
{# {{ obj }}#}
<input type="submit" value="提交">
</form>
1.4 编辑班级
- url 中的正则加上(\d+)
- 页面的nid 是通过在class_list上点击的时候获取的,把row.id传递
<a class="btn btn-primary" href="/edit_class/{{ row.id }}">编辑</a>
- 在后台函数中用参数nid进行接收
- 使用initial进行参数传递
- 编辑是用update对数据库进行更新
def edit_class(request, nid):
"""
nid 负责接收 class页面中的row.id中的值
在URL中设置edit_class/(\d+)/
:param request:
:param nid:
:return:
"""
if request.method == "GET":
row = models.Classes.objects.filter(id=nid).first()
# obj = ClassForm(initial={'title': row.title}) # 这里是显示默认值 默认的是data 会进行验证 initial 不会进行验证
obj = ClassForm(initial={'title': row.title})
return render(request, 'edit_class.html', {'nid': nid, 'obj': obj})
else:
obj = ClassForm(request.POST)
if obj.is_valid():
models.Classes.objects.filter(id=nid).update(**obj.cleaned_data)
return redirect('/class_list/')
return render(request, 'edit_class.html', {'obj': obj})
2 学生管理
2.1学生管理的后端
- 单选的时候用的而是插件Select中的choice
- choice中的参数是列表,列表中的元素是元组
- value_list操作中获得是元组对象
class StudentForm(Form):
name = fields.CharField(
min_length=2,
max_length=6,
# widget = widgets.TextInput(attr)
)
email = fields.EmailField()
age = fields.IntegerField(min_value=18, max_value=100)
cls_id = fields.IntegerField(
# widget = widgets.Select(choices=[(1,'上海'),(1,'北京')]) # choices = 列表的内部是元组的形式
widget=widgets.Select(choices=models.Classes.objects.values_list('id', 'title'))
# choice 是应用于单选 value_list 获取的就是列表中有元组
# 注意此处有bug 添加班级后在这里没有新添加的班级,下面进行修复
)
def student_list(request):
stu_list = models.Students.objects.all()
return render(request, 'student_list.html', {'stu_list': stu_list})
def add_student(request):
if request.method == "GET":
obj = StudentForm()
return render(request, 'add_student.html', {'obj': obj})
else:
obj = StudentForm(request.POST)
if obj.is_valid():
models.Students.objects.create(**obj.cleaned_data)
return redirect('/student_list/')
return render(request, 'add_student.html', {'obj': obj})
BUG修复 动态更新数据
cls_id = fields.IntegerField(
# widget = widgets.Select(choices=[(1,'上海'),(1,'北京')]) # choices = 列表的内部是元组的形式
# widget=widgets.Select(choices=models.Classes.objects.values_list('id', 'title'),
# attrs={'class': 'form-control'})
# choice 是应用于单选 value_list 获取的就是列表中有元组
# 注意此处有bug 添加班级后在这里没有新添加的班级,下面进行修复
# choices=models.Classes.objects.values_list('id', 'title'),
widget=widgets.Select(
attrs={'class': 'form-control'})
)
def __init__(self, *args, **kwargs):
super(StudentForm, self).__init__(*args, **kwargs)
self.fields['cls_id'].widget.choices = models.Classes.objects.values_list('id', 'title') # 动态查询
# 注意单选的时候用的而是widget.choice
# 后面的多选没有用
2.2 前端处理
- 有外键的可以直接点出来
row.cls.title
学生列表
<h1>学生列表</h1>
<a href="/add_student/">添加</a>
<ul>
{% for row in stu_list %}
<li>
{{ row.name }}-{{ row.email }}-{{ row.age }}-{{ row.cls_id }}-{{ row.cls.title }}
<a href="/edit_student/{{ row.id }}">编辑</a>
</li>
{% endfor %}
</ul>
添加学生,标签通过后台生成
<h1>添加学生</h1>
<form action="/add_student/" method="POST">
{% csrf_token %}
<p>
姓名:{{ obj.name }} {{ obj.errors.name.0 }}
</p>
<p>
邮箱: {{ obj.email }} {{ obj.errors.email.0 }}
</p>
<p>
年龄: {{ obj.age }} {{ obj.errors.age.0 }}
</p>
<p>
班级 {{ obj.cls_id }} {{ obj.errors.cls_id.0 }}
</p>
<input type="submit" value="提交">
</form>
编辑学生
- 通过student_list 中获取了row.id
- row.id 传递到后台
- initial接收的是字典对象
- 正好values获取的是字典对象
def edit_student(request,nid):
"""
获取编辑学生的的信息并展现在输入框中 使用initial
:param request:
:param nid:
:return:
"""
if request.method == "GET":
row = models.Students.objects.filter(id=nid).values(
'name','email','age','cls_id'
).first() # 使用valuse和first获取的是字典对象
obj = StudentForm(initial=row) # row接收的是字典对象
return render(request,'edit_student.html',{'nid':nid,'obj':obj})
else:
obj = StudentForm(request.POST)
if obj.is_valid():
models.Students.objects.filter(id=nid).update(**obj.cleaned_data)
return redirect('/student_list/')
return render(request,'edit_student.html',{'obj':obj})
前端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="/static/plugins/bootstrap-3.3.7-dist/css/bootstrap.css">
</head>
<body>
<div style="width: 500px; margin: 80px auto;">
<form method="POST" action="/edit_student/{{ nid }}/">
{% csrf_token %}
<div class="form-group">
<label for="exampleInputEmail1">姓名</label>
{{ obj.name }}
</div>
<div class="form-group">
<label for="exampleInputPassword1">邮箱</label>
{{ obj.email }}
</div>
<div class="form-group">
<label for="exampleInputPassword1">年龄</label>
{{ obj.age }}
</div>
<div class="form-group">
<label for="exampleInputPassword1">班级</label>
{{ obj.cls_id }}
</div>
<input type="submit" value="提交">
</form>
</div>
</body>
</html>
3 教师管理
form的执行过程:
1.找到所有的的字段 tname xx(这些都是静态字段)
2.把字段放到self.fields中{}是字典的形式
3.程序从上到下执行,启动后从数据库获取一次数据,然后就不变了,导致数据不能动态刷新
def teacher_list(request):
tea_list = models.Teacher.objects.all()
return render(request, 'teacher_list.html', {'tea_list': tea_list})
class TeacherForm(Form):
"""
form的执行过程:
1.找到所有的的字段 tname xx(这些都是静态字段)
2.把字段放到self.fields中{}是字典的形式
3.程序从上到下执行,启动后从数据库获取一次数据,然后就不变了,导致数据不能动态刷新
"""
tname = fields.CharField(min_length=2)
xx = fields.MultipleChoiceField(
# choice=models.Classes.objects.values_list('id', 'title'), # 启动的时候查询
widget=widgets.SelectMultiple
)
"""
自己重写的方法
1.从self.fields的字典中找xx,然后找xx的choice,choice=[(),()]的形式
2.源码中会循环所有的字段,把有Field的加入self。Fields中,因为都是继承Fields
"""
def __init__(self, *args, **kwargs):
super(TeacherForm, self).__init__(*args, **kwargs)
self.fields['xx'].choices = models.Classes.objects.values_list('id', 'title') # 动态查询 多选的时候不用widget
添加班级
def add_teacher(request):
if request.method == "GET":
obj = TeacherForm()
return render(request, 'add_teacher.html', {'obj': obj})
else:
obj = TeacherForm(request.POST)
if obj.is_valid():
xx = obj.cleaned_data.pop('xx') # 把验证通过的数据删除班级ID的信息 只留下教师表中的信息
row = models.Teacher.objects.create(**obj.cleaned_data) # 向教师表添加数据 row代表教师表的对象
row.c2t.add(*xx) # [1,2] 通过教师表 向ManyTomany的第三张表中添加数据 班级ID是列表的形式 用*接收
return redirect('/teacher_list/')
return render(request, 'add_teacher.html', {'obj': obj})
总结
Select框:
单选:单选是用CharField IntegerField 和Select搭配
ChoiceField也是用于单选框,但是choice写在外面
cls_id = fields.IntegerField(
# widget=widgets.Select(choices=[(1,'上海'),(2,'北京')])
widget=widgets.Select(choices=models.Classes.objects.values_list('id','title'),attrs={'class': 'form-control'})
)
cls_id = fields.ChoiceField(
choices=models.Classes.objects.values_list('id','title'),
widget=widgets.Select(attrs={'class': 'form-control'})
)
obj = FooForm({'cls_id':1})
多选:
多选只能用MultipleChoiceField ,且choices放在外面。最后重写init方法后就不用了
xx = fields.MultipleChoiceField(
choices=models.Classes.objects.values_list('id','title'),
widget=widgets.SelectMultiple
)
obj = FooForm({'cls_id':[1,2,3]})
通过Bootstrap进行界面美化
bootstrap 的关键是给相应的标签添加class属性
如:添加form-control
<p>
测试:<input type="text" class="form-control"> {# 使用bootstrap #}
</p>
对于自己生成的标签,是通过Form组件的插件添加属性的 widgets
attrs后面对应的是字典类型
class StudentForm(Form):
name = fields.CharField(
min_length=2,
max_length=6,
widget = widgets.TextInput(attrs={'class':'form-control'}) # 给输入框添加属性
)
email = fields.EmailField(
widget=widgets.TextInput(attrs={'class': 'form-control'}) # 给输入框添加属性
)
age = fields.IntegerField(
min_value=18, max_value=100,
widget = widgets.TextInput(attrs={'class': 'form-control'}) # 给输入框添加属性
)
cls_id = fields.IntegerField(
# widget = widgets.Select(choices=[(1,'上海'),(1,'北京')]) # choices = 列表的内部是元组的形式
widget=widgets.Select(choices=models.Classes.objects.values_list('id', 'title'),attrs={'class': 'form-control'})
# choice 是应用于单选 value_list 获取的就是列表中有元组
# 注意此处有bug 添加班级后在这里没有新添加的班级,下面进行修复
)