Django入门教程——员工数据管理
第四章 员工数据管理
教学目的
- 了解模型表单创建表单编辑界面的意义
- 理解模版表单的常用属性和使用方法
- 理解通过模型表单实现数据的添加和编辑方法
- 理解表单验证的方法
从模型创建表单
模型表单简介
如果您正在构建一个数据库驱动的应用程序,那么您很有可能会用到与Django模型密切相关的表单。Django 提供了一个辅助类让你可以从一个 Django 模型创建一个表单,就是将Model与Form进行绑定,Form有自动生成表单的作用。参考:https://docs.djangoproject.com/zh-hans/5.1/topics/forms/modelforms/
每个生成的表单字段的属性设置如下:
- 如果模型字段设置了 blank=True ,那么表单字段的 required 属性被设置为 False ,否则required=True 。
- 表单字段的 label 设置为模型字段的 verbose_name ,并且首字母大写。
- 如果模型字段设置了
choices
,那么表单字段的widget
会被设置为Select
,其选项来自模型字段的choices
。
员工添加表单
-
创建forms文件夹,用于存放表单类
-
创建
employee_form.py
,用于存放员工相关表单。-
创建表单类
from django import forms from archives.models import Employee class employee_add_form(forms.ModelForm):class Meta:model = Employeefields = ['name','depart','job_number','gender','birthday','phone']
-
人员添加视图函数
# 员工添加 def employee_add(request):if request.method == "GET":form = employee_form.employee_add_form()for field in form:print(field,"==字段控件")print(field.label,"==字段标签")print(field.name,"==字段名称")print(field.field.required,"==字段是否必输")return render(request,"archives/employee_add.html",{"form":form,'title':'添加员工'})
-
人员添加模板页面
{%extends 'common/layout.html'%} {% block head%} <div class="layui-card-header" style="font-weight: bold;">{{title}}</div> {% endblock %} {% block content %} <form class="layui-form" method="post" novalidate>{% csrf_token %}{% for field in form %}<div class="layui-form-item"><label class="layui-form-label">{{ field.label }}:</label><div class="layui-input-block">{{ field }}</div></div>{%endfor%}<div class="layui-form-item"><div class="layui-input-block"><button type="submit" class="layui-btn" >确认</button></div></div> </form> {% endblock %}
{% for field in form %}
可以循环输出表单所有元素,{{ field }}
为对应的控件,{{ field.label }}
为模型字段的verbose_name
model
内部类Meta的model属性设置了表单对应的数据模型fields
属性决定了表单中的元素以及元素的显示顺序,fields = '__all__'
可以设置为模型中的所有字段widgets
属性是一个字典,定制界面显示方式(文本框、选择框) -
页面样式优化
-
给表单字段批量添加layui样式,
class="layui-input"
,修改表单对象from django import forms from archives.models import Employee class employee_add_form(forms.ModelForm):class Meta:model = Employeefields = ['name','depart','job_number','gender','birthday']def __init__(self,*args,**kwargs):super().__init__(*args,**kwargs)for name,field in self.fields.items():field.widget.attrs={"class":"layui-input","placeholder":"输入"+field.label}
-
对必须输入字段前面加
*
号-
定义标签样式表
/* 必须字段 */.label-required-next:after {top: 6px;right: 5px;color: red;content: '*';position: absolute;margin-left: 4px;font-weight: 700;line-height: 1.8em;}
参考:https://www.runoob.com/cssref/sel-after.html
:after 选择器
:向选定元素的最后子元素后面插入内容。使用content
属性来指定要插入的内容。 -
修改表单模板页面
<form class="layui-form" method="post" novalidate>{% csrf_token %}{% for field in form %}<div class="layui-form-item">{% if field.field.required %}<label class="layui-form-label label-required-next">{{ field.label }}:</label>{% else %}<label class="layui-form-label">{{ field.label }}:</label>{% endif %}<div class="layui-input-block">{{ field }}</div></div>{% endfor %}<div class="layui-form-item"><div class="layui-input-block"><button type="submit" class="layui-btn" lay-submit>确认提交</button></div></div> </form>
-
数据保存
form = employee_form.employee_add_form(data=request.POST)
if form.is_valid():print(form.cleaned_data) #打印form提交的所有数据form.instance.hiredate=datetime.now()form.save()
else:print(form.errors) #打印form提交的错误信息
return HttpResponse("添加员工")
data=request.POST
创建表单,数据值为提交的数据
is_valid()
调用表单的这个函数实现表单的验证,验证后的数据可以通过cleaned_data
获取数据字典,验证没有通过通过errors
获得错误信息
instance
获取当前表单的模型对象,form.instance.hiredate=datetime.now()
设置模型中入职时间为当前时间
save()
方法实现数据的保存,可以是添加或者修改表单对象的属性和方法总结:
is_bound
——是否已经绑定数据is_valid()
——表单是否已经通过验证cleaned_data
——访问表单验证后的数据as_p()/as_ul()/as_table()
——渲染表单errors
——表单验证后的错误信息fields
——表单中的字段initial
——初始化数据字段常用的核心参数:
required
——是否为必填,默认为Truelabel
——label标签(输入框前的文字描述)initial
——初始化数据attrs
——定义表单对象的属性widget
——定制界面显示方式(文本框、选择框)help_text
——帮助文字error_messages
——覆盖字段引发异常后的错误显示localize
——本地化,根据用户所在地区格式进行显示disabled
——禁用表单,界面上不可操作has_changed()
——值是否发生了改变Django的内置字段:
文本/字符串:
CharField
——字符串输入TextInput
——文本框输入DateInput
——日期输入EmailField
——邮件地址输入URLField
——URL地址输入UUIDField
——uuid字符串输入数值(整数、小数)
FloatField
——浮点数输入IntegerField
——整数输入DecimalField
——小数输入(更精确)选择
ChoiceField
——单选MultipleChoiceField
——多选TypedChoiceField
——高级选择(支持结果转换类型)
员工信息展示页面
-
员工信息展示页面
{%extends 'common/layout.html'%} {% block content %} <a href="/employee/add/" class="layui-btn">添加员工</a> <table class="layui-table" lay-even><thead><tr><th>工号</th><th>姓名</th><th>部门</th><th>性别</th><th>入职时间</th><th>操作</th></tr> </thead><tbody>{% if not data_list %}<tr><td colspan="6" style="color: #CCC; text-align: center;font-size: 10px;">---------暂无数据---------</td></tr>{%endif%}{% for obj in data_list %}<tr><td>{{ obj.job_number }}</td><td>{{ obj.name }}</td><td>{{ obj.depart.name }}</td><td>{{ obj.get_gender_display }}</td><td>{{ obj.hiredate }}</td><td><a href="#" class="layui-btn layui-btn-xs">编辑</a><a href="javascript:void(0)" class="layui-btn layui-btn-danger layui-btn-xs" lay-on="delete" data="{{obj.id}}">删除</a></td></tr>{% endfor %}</tbody> </table> {% endblock %}
-
选择性字段的显示
-
后台显示:
- 显示数据库保存的原始值:
obj.字段名
- 显示翻译后的文本:
obj.get_字段名_display()
- 显示数据库保存的原始值:
-
前台(模板)显示
- 显示数据库保存的原始值:
obj.字段名
- 显示翻译后的文本:
obj.get_字段名_display
区别就是不要括号
- 显示数据库保存的原始值:
-
-
级联表字段的显示
- 显示原始值:obj.字段名
- 显示翻译过的值:obj.字段名.关联表字段名
-
日期字段的显示
- 后台格式化:
obj.日期.strftime('%Y-%m-%d %H:%M:%S')
- 前台模板:
obj.日期|date:'Y-m-d'
注意没有%号
- 后台格式化:
-
-
员工信息列表视图函数
# 员工数据表格 def employee_list(request):data_list = Employee.objects.all()return render(request,"archives/employee_list.html",{"data_list":data_list})
-
改造添加返回,以及表单数据验证
if form.is_valid():print(form.cleaned_data) #打印form提交的所有数据form.instance.hiredate=datetime.now()form.save()return redirect("/employee/list/") else:print(form.errors) #打印form提交的错误信息return render(request,"archives/employee_add.html",{"form":form,'title':'添加员工'})
<div class="layui-input-block">{{ field }}<span style="color: red">{{ field.errors.0 }}</span></div>
-
手机号码验证
clean_xx()
钩子函数,xx是表单中需要校验字段名称。- 校验顺序是
class Meta
中fileds
列表的顺序。 - 在
form.is_valid()
函数调用的时候会触发自定义的clean_xxx
- 函数返回值,是最终表单验证后该字段的值
def clean_phone(self):phone = self.cleaned_data['phone']if phone:if not re.match(r'^1[3-9]\d{9}$', phone):raise forms.ValidationError('请输入有效的手机号码。') return phone
正则表达式匹配规则
^1
表示以1开头[3-9]
限制了第二位数字的范围\d{9}
表示接下来的9位都是数字$
表示整个模式到此为止,结束标志
raise
是python中手动抛出异常的一种语句forms.ValidationError
是Django中验证错误提醒的语句,并且附加到该字段上 - 校验顺序是
建立通用数据编辑模板
-
使用通用模板
form_edit.html
,改造部门添加编辑 -
使用layui组件实现出生日期的选择
{%extends 'common/form_edit.html'%} {% block js %} <script>layui.use(function(){var laydate = layui.laydate;// 渲染laydate.render({elem: '#id_birthday'});}) </script> {% endblock %}
-
修改出生日期的默认值
ModelForm
的构造函数参数:data
:用于接受通过表单提交的数据,一般为request.POST
instance
:接收一个已经存在的 Model 实例,如果使用 instance 参数,save() 将更新这个实例;如果不使用,save() 将创建一个新的 Model 实例。initial
:接收一个字典,用于设置表单的初始值,没有设置将采用数据模型的默认值。
if request.method == "GET":init_data = {'gender':2,'birthday':(datetime.now()-timedelta(days=365 * 22)).strftime("%Y-%m-%d"),}form = employee_form.employee_add_form(initial=init_data)
-
完善员工编辑功能
# 员工数据编辑 def employee_edit(request,nid):row_obj = Employee.objects.filter(id=nid).first()if request.method == "GET":form = employee_form.employee_add_form(instance=row_obj)return render(request,"archives/employee_add.html",{"form":form,'title':'编辑员工'})form = employee_form.employee_add_form(data=request.POST,instance=row_obj)if form.is_valid():form.save()return redirect("/employee/list/")print(form.errors,"===表单校验错误")return render(request,"archives/employee_add.html",{"form":form,'title':'编辑员工'})
-
修改表单,添加
widgets
属性,解决日期字段编辑格式错误问题widgets = {'birthday':forms.DateInput(format='%Y-%m-%d'), }
-
练习任务:完善家庭成员信息的展示、添加、编辑功能
复习类和星号运算符
类
基本语法:
#基类定义
class ClassName:class_suite #类体#子类定义
class 派生类名(基类名):class_suite #类体
class Employee:'所有员工的基类'empCount = 0def __init__(self, name, salary):self.name = nameself.salary = salaryEmployee.empCount += 1def __str__(self):return f"姓名 : {self.name}, 薪金: {self.salary}" def displayCount(self):print ("总共人员数:",Employee.empCount) def displayEmployee(self):print("姓名 : ",self.name,", 薪金: ",self.salary) employ1 = Employee("张三", 2000)
employ2 = Employee("李四", 3000)
employ1.displayEmployee()
employ2.displayEmployee()
employ1.displayCount()
print(employ2)
empCount
变量是一个类变量,它的值将在这个类的所有实例之间共享。你可以在内部类或外部类使用类名直接访问Employee.empCount
访问。- 第一种方法
__init__()
方法是一种特殊的方法,被称为类的构造函数或初始化方法,当创建了这个类的实例时就会调用该方法。__str__
也是python类中一种特殊的方法,返回一个对实例描述的字符串。当执行print函数等打印实例时,调用该函数。self
代表类的实例,self 在定义类的方法时是必须有的,虽然在调用时不必传入相应的参数。- 以self为前缀的变量都可以供类中的所有方法使用。
- 方法重写:父类方法的功能不能满足你的需求,可以在子类重写你父类的方法。子类重写父类方法,方法的函数名要与父类相同。
super()
函数,可以通过子类调用父类中的函数getattr(obj,'name')
内置函数,获取obj对象的name属性值
星号运算符
在Python中,*
和**
是两个重要的运算符,它们具有不同的用途。
*
(星号)用于解包列表,将其元素分配给函数的参数或在列表、元组等数据结构中进行拼接。**
(双星号)用于解包字典,将其键值对传递给函数的参数或在字典中进行拼接。
传递可变数量的参数(形参)
-
解包列表
def sum_values(*args):total = 0for num in args:total += numreturn total result = sum_values(1, 2, 3, 4, 5) print("===调用函数结果:",result) # 输出:15 # 示例:拼接列表 numbers1 = [1, 2, 3, 4, 5] numbers2 = [6, 7, 8] result = [*numbers1, *numbers2] #实现列表合并 print("===合并后的列表:",result) # 输出:[1, 2, 3, 4, 5, 6, 7, 8]
-
解包字典
# 使用**解包字典 # 示例1:传递可变数量的关键字参数 def print_info(**kwargs):for key, value in kwargs.items():print(key, ":", value)name = kwargs.get("name", "")age = kwargs.get("age", 0)print(f"{name}有{age}岁")print_info(name="李天明", age=20, city="九江") # 输出: # name : 李天明 # age : 20 # city : 九江 # 李天明有20岁# 示例:拼接字典 defaults = {"color": "red", "size": "medium"} user_preferences = {"size": "large", "theme": "dark"} result = {**defaults, **user_preferences} print(result) # 输出:{'color': 'red', 'size': 'large', 'theme': 'dark'}
传递实参
-
列表,列表作为函数的实际参数
def multiply(a, b, c):return a * b * cnumbers = [2, 3, 4] result = multiply(*numbers) print("====计算结果:",result) # 输出:24
-
字典作为函数的实际参数
def print_info(name, age, city):print(f"{name}来自{city},今年{age}岁")print_info("张三", 20, "北京") #普通调用 print_info(age=20, name="王五", city="上海") user_info={"name":"李四", "city":"九江","age":22,} print_info(**user_info) #字典解包