crm 通用组件
1、权限 组件 (通用)
- 为什么需要权限控制 不同的人有不同的角色看到不同信息 现实场景需要
- 为什么要开发权限组件 积累 通用功能 用的很多
- web 什么是权限? url路由地址 == 权限 一个人权限 对应 的url多少 多对多关系 权限表结构 迭代表结构设计
第一版 2张表 基于用户的表结构设计 问题:同类用户修改权限比较麻烦 每个人都需要去修改相应的权限,权限过多的时候修改比较麻烦
人 person
uid
name
dep
age
permission = mtm(to='permissions')
权限 permissions
pid=
name=
url =
解决方法,不在以用户为基础,而是以角色(一类人)统一修改,角色的权限修改了,角色对应的一类人权限也会相应修改,基于角色的表结构设计 (第二版)
user
uid
name
age
role = foreignkey role ==》 MTM role 一个人有多个角色, 一个角色有多个人
role
rid
name
permission = MTM permissions
permissions
pid
name
url
user =》 role =》 permissions
RBAC 基于角色的权限控制
project类
大目标:crm系统
- 权限 通用组件
- stark组件
- crm业务
步骤:
1、创建django project,luffy_permission
2、创建两个APP
- rbac 权限公共组件
- web 销售管理系统
3、app: rbac == app01
-- 将权限相关的表编写到此app的models.py中
4、app :web
- 将销售管理系统的表写到此app的models.py中
- 销售系统的业务相关代码 :luffy_permission(示例二)
5、两个app的整合
销售管理系统中的URL:
客户管理
账单管理
5.1 基于admin权限信息的录入
5.2 基于admin权限和角色信息的分配
6. 快速完成一个基本权限控制 在实例代码 开始开发
中间件、ORM、session 视图views
1、登录页面是否有权限访问
2、post请求,用户登录检验是否合法
3、获取当前用户相关的所有权限病放入session
4、再次想服务端发起请求 http:www.xx/xxx,后端编写中间件对用户当前访问url进行权限的判断(是否在session中)== session表中
7、功能的完善 将权限相关功能放到rbac app下,以便以后组件的应用
- 用户登录和权限初始化拆分 权限函数模块封装 services
- 配置文件应用 session_key放在setting中
总结:6、7 属于进行权限控制
8.动态菜单功能
- 一级菜单
- 问题 如何实现动态显示菜单? 结合目前rbac和表结构 拿到对应菜单名、url == 模板展示循环判断,有就加一个,没有就不显示对应的菜单名和url == 怎么拿到对应菜单名? 简单来说直接在权限表中增加标注是否菜单的字段,这样就可以判断是否是菜单
设计
权限 与 菜单的关系 增加/查看可以 编辑/删除不行 权限包含菜单 一个权限 == 一个菜单 其实是一对一关系 直接增加一列判断是否是菜单
session、视图、中间件
写代码
1、models permissions 添加menu字段
2、创建session 保存菜单列表
3、登录拿到权限之后通过session获取显示菜单
4、从模板中拿到对应菜单循环展示
a. 表结构修改 + 录入菜单数据
b. 获取菜单信息并保存到session
c. 模板中显示菜单信息 session
ps:inclusion_tag("class.html")
扩展:访问谁就在谁里面显示 默认选中
null = True blank = True admin中可以为空
default = False
font awesome 图片 icon
- 二级菜单
评论树 父id
a. session中存储的菜单信息的结构: 之前是[{},{}] 循环列表 get(key)
{
1:{
"title":"",
icon:"",
children:[
{'title':'客户列表','url':'/customer/list/'},
{'title':'客户列表','url':'/customer/list/'},
]
},
2:{
"title":"",
icon:"",
children:[
{'title':'个人资料','url':'/userinfo/list/'},
]
},
}
b. 数据库表结构
一级没有url,url在二级,新增一级菜单menu表,二级菜单与一级的关系是一对多,一个一级包含多个二级,
如果二级是null 说明没有一级,不是菜单,如果二级是非null,说明有二级菜单,对应的一级菜单mid
c. 页面显示二级菜单
inclusion_tag中循环显示
开发二级菜单 客户管理之动态 菜单
9、点击非权限菜单的时候显示菜单并选中 默认展开非菜单url
当点击某个不能成为菜单的权限时,指定一个可以成为菜单的权限,让其默认选中以及展开
a. 数据库设计 自关联
b. 思路
- 登录:做权限和菜单的初始化
- 获取菜单信息
- 获取权限信息
导航条
路径导航 路飞线上源码
permissions__title == 显示二级标签 父集 二级关联 第一级
pid 不为null 显示二级标签对应的id
permissions__pid_id pid null
permissions__pid__title 父权限信息 非二级标签 子集 第二级
permissions__pid__url
11、权限粒度控制到按钮级别
修改permissions_list dict 加入key 中间件 values() == model permissions增加name别名列(唯一) 取代url地址 类似url name {% URL name %} 在url中设置name,动态更新
总结:
- 权限控制
- 动态菜单
- 权限分配
问题:以前你是如何做的权限分配?给某个用户分配一个角色?某个人分配某个权限
django admin进行录入
自己实现权限分配 不仅仅CURD 三级菜单
12、权限分配
a.角色管理
知识点:
- ModelForm setting ==》 zh-hans 批量forms添加class
- 根据 namespace 和 name的反向解析URL
- 模板的查找顺序 , rbac目录分开
- 模板重用,编辑 添加 公用 保存默认值 (instance=obj)
b.用户管理
知识点:
- ModelForm
- 字段的自定制
- 钩子方法 二次校验 两次密码验证
- 错误提示(中文) error_message = {"required":"该字段不能为空"}
error_messages = {
'name': {
'max_length': _("This writer's name is too long."),
},
}
- 重写 __init__ 方法,统一给所有字段添加属性 (form-contorl)
c.菜单和权限的管理
d.批量的权限操作
e.分配权限
rbac组件文档 == 主机管理
添加携带原搜索条件,跳转回来也携带原搜索条件 id=xx&c=yy
二级菜单
row.id|safe 转换为字符串,safe ==》渲染不转义,否则会转义渲染标签
<tr class="{% if row.id|safe == menu_id %}active{% endif %}">
三级菜单 -- 权限
url name 模板自定义tag 反向解析
c 菜单和权限管理 多级菜单
讲解
- 一级
- 二级
- 三级
知识点:
- 保留URL中的原搜索条件
- 模板中整型转换字符串 1|safe 做判断
- ModelForm 定制radio
- ModelForm 显示默认值
- ModelForm save之前对其instance进行修改 携带用户输入
- BootstrapModelForm的基类 方便定义统一样式
d.权限批量操作
- formset
- 什么是formset?
form组件或modelForm用于做一个表单验证 html渲染 对应一个表的一行数据 formset用于做多个表单验证组件 == 多行数据
- 应用场景?
批量操作
批量增加
- 如何来使用formset?
批量添加
批量修改
- 自动发现项目中的URL
- 问题:给你一个项目,请帮我获取当前项目中都有哪些URL以及name rbac:permission_list
- 实现思路
错误显示中文 zh-hans
Field.blank¶
如果是 True ,该字段允许为空。默认为 False 。
注意,这与 null 不同。 null 纯属数据库相关,而 blank 则与验证相关。如果一个字段有 blank=True,表单验证将允许输入一个空值。如果一个字段有 blank=False,则该字段为必填字段。
2、stark组件 单张、多张表 CURD 组件 )(通用)
3、crm 业务 30表 CURD 教育 luffy
- 如何来使用formset?
批量添加
批量修改
- 自动发现项目中的URL
- 问题:给你一个项目,请帮我获取当前项目中都有哪些URL以及name rbac:permission_list
- 实现思路
错误显示中文 zh-hans
1、获取项目中所有权限 set1
2、去数据库中获取已经录入的所有权限 set2
情况一:自动发现有 > 数据库无 == 》 实现批量添加 ps:通过name进行对比 set1 - set2 =》 添加 formset
情况二:数据库多 > 自动发现 -》 实现批量删除 name进行对比 set2 - set1 =》 删除 formset
情况三:自动发现 == 数据库 -》 实现批量更新 set3 = set1 & set2 =》 更新 formset
列表生成式 [] 三元表达式 条件成立 if 条件 else 不成立
菜单 == 权限
权限批量操作
- formset (ModelFormSet)
- 自动发现项目URL 递归 、正则
- 唯一约束的错误信息
- 批量添加
权限分配
- 展示用户、角色、权限信息
- 选择用户、角色时,页面上默认权限
- 角色、权限的分配 (保存)
录入权限信息
菜单
能做菜单的权限
不能做菜单的权限
list dict 可变类型 引用 ==》 内存地址
dict if key in dict ==> hash
a标签 get请求 url ?row.id
form button提交 ajax 异步 不刷新提交 action="?type=x1" ==》 隐藏input标签
{% if row.id|safe == user_id %} safe转换为字符串 blog显示标签 safe js注入、 safe CSRF
objects.filter().exist()
知识点总结:
- 数据类型设置引用
- M2M 关系表的更新
权限控制到按钮,templates中的做f判断 分配权限 测试 登录 查看权限
RBAC 编写使用文档 (主机管理程序)
1、将RBAC组件拷贝到项目
2、将RBAC/migrations目录中的数据迁移记录删除
3、进行业务开发
3.1 对于用户表的的处理 o2o 将用户表拆分到两张表中,跟auth组件类似,采用django_admin
缺点:用户表数据分散
优点:利用上rbac中的用户管理的功能
3.2 用户表整合在一张表中【推荐】
class Meta:
# django以后再做数据库迁移时,不再为Userinfo类创建相关的表以及表结构
# 此类可以当做 ”父类“ 被其他Model类继承
abstract = True
优点:将所有用户信息放入到一张表(业务的用户表中)
缺点:在rbac中所有关于用户表的操作,不能使用了
注意:rbac中两处使用了用户表
- 用户管理 【删除】
- 权限分配时用户列表 【读取业务中的用户表即可】
严重提醒:
Role 不要加引号
对于rbac中代码修改:
- 1、在URL中将用户表的增删改查和修改密码功能删除
2、在权限分配时,读取用户表变成通过配置文件来进行指定并导入
3.3 业务开发:
- 用户表的CURD
- 主机表的增删改查
如果要使用rbac中的模板,则需要将模板中的 导航条 + 菜单 去掉,当业务开发完成之后,上线之前在拿回来 a 跳转 href = "" memory_reverse 带上搜索条件
PS:
控制到按钮级别,没有权限就不显示,只有有权限才显示对应功能
用户登录验证 只是把权限信息保存到session,之前做的是auth验证通过登录(自动实现session保存) 否则需要自己做session的增删 == 自己实现session登录操作
流程:
业务系统中用户表结构设计
将业务系统中用户表的路径写到配置文件中
- 用于RBAC分配权限时,读取业务表中的用户信息
业务逻辑开发
- 将所有的路由都设置一个name
- 用于反向生成URL,以及粒度控制到按钮的权限控制
权限信息的录入
- 在URL中添加rbac路由分发 注意 必须设置namespace
- rbac提供的地址进行操作
相关配置:自动发现URL排除的URL列表
编写用户登录逻辑 权限初始化
相关的配置:权限和菜单的session key:
编写一个首页的逻辑
相关配置:需要登录但无需权限的URL
通过中间件进行权限校验
权限校验
白名单,无需登录就可以访问
粒度到按钮的配置
总结:目的希望在任意系统中应用权限系统,
- 用户登录 + 用户首页 + 用户注销
- 项目的业务逻辑开发 注意:开发时灵活的去设置layout.html中的两个inclusion_tag
开发时 去掉 上线时,加回
- 权限信息录入
- 配置文件
- 粒度控制到按钮级别
2、stark组件 单张、多张表 CURD 组件 )(通用)
介绍:是一个帮助开发者加快实现数据库表的增删改查 +
目标:10s中完成一张表的增删改查
前戏:
1、django项目启动时,自定义执行某个py文件
django启动时,且在读取项目中路由加载之前执行某个py文件
在任意app的apps.py中国Config类中定义ready方法,并调用autodiscover_modules 导入模块 ==》执行xxx.py文件
django在启动时,就会去已注册的所有app目录下找 xxxx.py并自动导入
如果执行两次,是因为django内部自动重启导致:
Python manage.py runserver 0.0.0.0:8080 --noreload
提示:
如果xxx.py执行代码向“某个神奇的地方”放入了一些值,之后路由加载时,再去“某个神奇的地方”读取到原来设置的值
2、单例模式 文件导入的方式
单,一个
例,实例、对象
通过利用Python模块导入的特性:python中,如果已经导入过的文件再次被重新导入时,Python不会在重新解释一遍,而是选择从内存中直接将原来导入的值拿来使用
导入多次,使用的都是最开始创建的对象,不会重复导入
提示:
如果以后存在一个单例模式对象,可以先在此对象中放入一些值,然后在其他文件中导入该对象,通过对象再次将值获取到
3、django路由分发的本质 include 函数主要返回有三个元素的元祖
方式一
方式二
方式三 在源码内部,读取路由时 urls.patterns getattr() 直接路由分发
开始:
- 1、创建django project
- 2、创建基础业务表
- 3、对以上的三张表做增删改查
- a.分析
- 为每张表创建4个url
- 为每张表创建4个视图函数
- b 为APP中的每个model类自动创建URL以及相关视图
- a.分析
2个面向对象脚本
1、不同对象封装的self不同,obj封装自己对象的数据
2、继承 基类,相同方法无需重复编写
多态,多种形态
提取基类 自定制显示
前缀、添加url
prev 生成URL前缀
钩子 url选择 只显示几个 再做一次路由分发 url分发
- 修改url
- 增加url
b 为APP中每个model类自动创建URL以及相关视图函数
- 动态生成URL
- 将视图提取到基类
- URL分发 扩展 & 后缀
- 为URL设置别名 name
app名称_表名称_list
注意继承 找到是那个类的对象,先从对应类开始找,没有在找父类的方法
- URL别名进行重新生成
stark组件开发之列表页面定制列
c. 定制页面显示的列
用户对象 __str__ 打印出来 显示列 不是obj
list_display = ['name','age'] 页面上要显示列
1、处理表格的表头 类似admin的获取verbose_name
2、处理表的内容
注意 self 是谁的对象,就从那个类开始查找
- 未定义list_display字段的页面,默认显示对象 头显示表名
- 扩展点 获取页面上应该显示的列 根据用户角色不同显示不同的列
>>> a = [1,2,3]
>>> a.extend(['name','age']) extend() 把列表批量添加新列表中
>>> a
[1, 2, 3, 'name', 'age']
- 为页面显示的列预留一个钩子函数
定制显示编辑和删除按钮 return make_safe('<a>编辑</a>') 以安全的方式显示 == 标签显示 模板中用 |safe 来显示标签 转化字符串 |safe
- 为页面提供自定义函数
函数和方法
函数 Foo.func(self,name) 类.func 函数
方法 obj.func(name) obj.func 方法
- 应用
反向生成url
定制字段:
get_classes_display() model choice 字段显示:
choice字段想显示中文,就必须使用obj.get_表名_display() 3个厂商 choice 阿里云/华为云/ucloud云
页面优化
d.应用模板样式(基于bootstrap)
e.django 分页 Page
根据URL中获取的 page=3
1、根据用户访问的页码,计算出数据库索引位置
2、生成HTML的页码
自定义分页实现
添加列表分页功能
f.页面添加
不同用户显示添加按钮
权限判断
参数 has_add_btn = True
钩子 get_add_btn()
- 如何显示添加按钮
- 添加按钮的URL
根据别名反向生成URL 类似之前 reverse生成对应的url
权限中记录原记录搜索条件
QueryDict(mutable=True) 可修改,默认不能修改
QueryDict.urlencode(safe=None)¶
返回一个查询字符串格式的数据字符串
装饰器@来引用
类中def使用装饰器? 都可以,在类里面也可以自定义装饰器,直接在调用的地方引用也是一样的,注意
@xx ==》 func = xx(func) 可以写成 xx(func)
import types
from functools import wraps
class Profiled:
def __init__(self, func):
wraps(func)(self)
self.ncalls = 0
def __call__(self, *args, **kwargs):
self.ncalls += 1
return self.__wrapped__(*args, **kwargs)
def __get__(self, instance, cls):
if instance is None:
return self
else:
return types.MethodType(self, instance)
@Profiled
def add(x, y):
return x + y
class Spam:
@Profiled
def bar(self, x):
print(self, x)
添加页面
自定义ModelForm clean_all errors[0]
使用ModelForm保存数据之前预留的钩子方法 可以配置显示字段和添加之前设置默认值
编辑
注意get带条件
- 编辑按钮(删除按钮)
- 页面操作 ModelForm instance=obj.pk 当前编辑的默认值填充
删除
CURD
其他常用功能
a. 排序
b. 模糊搜索
- 实现思路:在页面上设置form表单,搜索:以get形式提交请求到后台,后台获取数据进行筛选过滤 (最简单实现方式form表单实现)
后端获取到关键字之后,根据定义的列进行查找 (多列按照或进行查询)
定义钩子方法
1、如果search_list中没有值,则不显示搜索框
2、获取用户提交的关键字
3、构造条件
filter多条件默认and
- Q 对象 多条件查询 与或非
c. 批量操作
- 添加CheckBox列
- 生成批量操作的按钮
自定义套路def get_action_list self.action_list
思路 用if判断,批量删除 == 对应func 批量修改 == 对应func
实际 用函数对象实现 func.text = 批量删除 func.text = 批量编辑
模板中{{ func.text }} 会自动加() 执行
函数对象获取函数名 func.__name__
name="pk" ==> request.post.getlist('pk','')
value = "{{ func.name }}" ==> request.GET.get('aciton') ==> name="action" 获取的是{{ func.name }}值
name定义是get获取的变量,对应name的值就是value的值
如果想要定制执行成功后的返回值,那么就为action定义return 值即可
d. 组合搜索
- 什么是组合搜索
- 如果实现组合搜索
- 实现思路 根据字段找到其关联数据 choice、FK、M2M
- 第一步:配置 默认配置
search_group=[
{'field':'gender','db_condition':{'id__gt':2},}
]
根据权限不同做不同的操作
- 第二步:根据配置获取关联数据
django 1.11 获取字段对应的外键表对象
field_object.rel.model.objects.filter(**item['db_condtion'])
- 第三步:根据配置获取关联数据(含条件)
封装到类中对象实现,不是简单的通过字段 or 元祖进行传值
封装条件 Myoption
- 第四步:在页面上显示组合搜索的按钮
- 将Queryset和元祖进行统一的封装
- 第五步: 为组合搜索按钮生成URL
- 生成URL时,不影响其他组的条件
query_dict.urlencode(),text request.GET.urlencode()
- 条件的筛选
- 多选
stark组件总结:
- 页面: 列表、编辑、添加、删除
- 模糊搜索、批量操作、组合搜索
第三部分 crm 业务
1、项目背景
以教育机构(老男孩)为背景crm项目,undo 系统主要为 销售部、运营部、教质部 提供平台,对他们的工作进行量化
有利于公司信息化建设
销售部,qq群 内部转化 公户私户可以相互踢出
- 公户 公共客户
- 私户 我的客户 <= 150人 + 跟进记录 + 入班申请(财务审批)
运营部,seo sem 转化
- 录入客户信息 (公户)
教质部,
- 考勤
- 学员访谈 升学 转介绍
- 积分管理 问答 工作介绍
- 转班申请 留级
学员 查看信息 CURD
扩展性更强
2、项目开发
2.1.概览
- 基础业务处理
- 校区管理
- 部门管理
- 用户管理
- 课程管理
- 开班管理
- 客户管理
- 公户
- 私户
- 学员管理
- 考勤
- 谈话记录
- 积分
- RBAC组件
先功能,最后在看model,串起来在看
2.2 开发
2.2.1 创建项目 python3 manage.py makemigrations migrate
2.2.2 校区管理 设置stark默认显示,编辑删除在一列显示 CURD 快速实现 setting 中APP注册 rbac 执行顺序,layout.html 模板页面重复读取到菜单报错,调整顺序即可
class School(models.Model):
pass
2.2.3 部门管理
2.2.4 用户管理 开发自己的组件,方便快速实现通用功能
- 用户的基础操作
添加页面确认密码
编辑不显示密码 删除密码字段
密码密文显示
2种方式:
方法一
add_or_change=None
if add_or_change == "add":
xx
elif add_or_change == ‘change’:
xx
方法二
is_add=False
在UserInfoAddModelForm(StarkModelForm)中定义钩子函数
def clean_confiem_password(self):
pass
注意必须加返回值,否则self.clean_data('password')就获取不到值,在UserInfoAddModelForm中添加钩子函数
def clean(self):
password = self.clean_data['password']
self.clean_data['password'] = gen_md5(password)
return self.cleaned_data
重置密码
用form,不是ModelForm 不需要 数据库中的已有数据,密码不需要instance展示 跟数据库无关,采用forms
Auth 替代 request.session.get == 登录验证
密码更新到数据库
通过pk=id 查到对应obj
obj.save()
之前有个问题,如果用bookobj=models.objects.create() 会出现唯一重名的情况 ,要做字段唯一判断,先创建对象
obj = models.books(xx="xx")
obj.save()
用户页面功能增加
- 模糊搜索
- 组合搜索 条件 多选
课程管理和代码拆分
班级管理 基础操作和定制
models中字段属性 不清楚:
related_name = "classes" # 反向关联 参数来覆盖名字entry_set(相当于别名的作用)以起到相同的作用
limit_choice_to={"depart_id__in":[6,7]} # 限制对应字段 ModelForm中显示时候的筛选条件
blank = True 可以为空 blank 是针对表单的,如果 blank=True,表示你的表单填写该字段的时候可以不填,比如 admin 界面下增加 model 一条记录的时候。直观的看到就是该字段不是粗体
null = True 自己字段为空 null 是针对数据库而言,如果 null=True, 表示数据库的该字段可以为空。
显示时间、M2M 字段 ==》 可以定制颜色
基于limit_choice_to 关联FK或M2M 进行筛选
- 班级管理时间插件的应用(stark组件新增datetimepicker组件)
4个步骤:1、引入css 2、引入js 3、js代码块 4、div块
本地化location 只保留中文
- 2.2.7 客户管理
- 公户
公户基础管理:
列表页面展示 注意 重写get_queryset()
录入客户信息 重写 model_form_class = PublicCustomerModelForm
class PublicCustomerModelForm(StarkModelForm):
class meta:
model = models.Customer
exclude = ['consultant',]
查看跟进记录
创建一张表 ConsultRecord(models.Model)
创建url 对应 templates 引用之前带参数的跳转,显示自定义的模板
图标 font awesome bootstrap 其他图标工具 iconfont
申请到私户:批量操作
select xx from xx where xx for update ;
申请 ==》 审批? 直接申请就通过了,把对应公户直接转变成当前用户的私户 都能看到公户客户,自己申请到自己的私户中
数据库锁 行级 表级锁
事务 with transition:
加锁select_for_update()
私户个数限制 < 150 个
session中读取用户id,转变成私户的客户
用户登录,公户申请到私户 crm 114节
- 私户
跟进记录管理
增加跟进进度
v1.py site = StarkSite() # 单例模式
改变url参数 reverse_list_url
form.instance.customer_id = customer_id
form.save()
编辑 删除
value.append(type(self),display_edit_del) 获取子类的方法 子类用就用子类的 没有就用父类的
解决 添加、编辑、删除 漏洞,非法修改URL id
添加漏洞 定义response 显示非法操作
编辑跟进记录 查看到其他用户下的客户的跟进记录
缴费&报名
- 业务分析
- 学员 缴费
- 课程顾问:提交缴费申请
- 财务: 审核(状态更新、入班学习)
- 代码实现
- 表结构设计
- 查看缴费列表
- 添加缴费记录
学员信息 2种方式 每次都填 or 只填一次后面获取
对应课程顾问 和 学员id 自动携带 获取课程顾问对应学生客户的id, 也需要上面的防漏洞
基本的缴费记录 有无学生信息的情况 显示不同 对应2个ModelForm
get_model_form_class()
exists 是否存在 --> first() 查询是否存在,不存在 not exist or not first()
多对多 班级对应课程id
- 缴费审批:
注意:这里应该去拿student表中的name,案例中直接拿的consume中的客户学生对象 通过 def __str__() 显示的对应学生名和电话信息 === 改成从consume对象获取学生表中对应的真实name和电话
缴费记录
客户表
学生表
涉及多张表更新,不能直接更新当前表
self.model_class.objects.filter(id__in=pk_list).update(confirm_status=2)
for pk in pk_list:
pass
2.2.8 学员管理
学生管理
- 已报班级 M2M
- 组合筛选 M2M 字段 重写func_text 字段
- 模糊搜索
积分管理
积分记录表
去除编辑和删除
跟上面私户 客户顾问id == 编辑的学生id
当前登录用户 == 添加分数学生id
上面漏洞问题是否存在?直接通过url进行修改其他学生积分
- 考勤管理
- 上课记录 增删改查 参考 跟进记录
- 考勤记录 学生考勤记录 手动创建 == 批量自动生成
课程管理,对课程,学生端没有
不用create 直接 对象.save() 用于判断是唯一值
循环创建 create 更换为先创建[obj,obj]对象列表,在批量执行
- 批量生成考勤记录
- 批量设置考勤记录
先生成考勤 url
通过url 显示考勤记录
生成多选页面 ModelFormSet 修改字段 'record' 取代 ‘__all__’
modelformset_factory(models.StudyRecord,form=StudyRecordModelForm,extra=0)
rbac formset
{{ form.instance.student }} form.instance 从数据库拿到的数据对象,.student 获取对应字段值 而不是生成input标签
展示formset
批量更新 form
{% csrf_token %}
{{ formset.management_form }}
获取提交数据 request.POST
if formset.is_valid():
formset.save()
form提交报错:form-0-id 不知道对应操作行id 需要加入当前行id {{ form.id }} 隐藏的input框当前行id
PS:可以用弹出框 + ajax提交来实现,替代form跳转 刷新 HTTPResponse返回数据
2.2.9 权限应用
- 基本权限校验
- 参考文档
- 粒度控制到按钮
总结:
- 留存组件:rbac组件、stark组件 31节 代码 1000行代码 * 对比模块9的项目还重要
- crm业务 (讲出来)销售 渠道 保证信息化安全 公户 私户 **
crm练习
从stark开始 ===》 crm 实现 ==》 rbac加入
最后rbac重写
- 3、crm 业务 30表 CURD 教育 luffy