表结构设计
表结构设计是一个项目的基石,组织好各种数据之间的逻辑关系,往往能够使开发事半功倍。
一、权限控制表结构设计
UserInfo
任何项目都要用人使用才有价值,因此设计好用户信息表是第一步。
Django 提供了一个 AbstractUser 类,可以在这个类的基础之上定制我们需要的 model。
我们来看一下这个类的部分源码:
username :用户名
username = models.CharField(
_('username'),
max_length=150,
unique=True,
help_text=_('Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.'),
validators=[username_validator],
error_messages={
'unique': _("A user with that username already exists."),
},
)
first_name:名、last_name :姓
first_name = models.CharField(_('first name'), max_length=30, blank=True)
last_name = models.CharField(_('last name'), max_length=150, blank=True)
email :邮箱
email = models.EmailField(_('email address'), blank=True)
is_staff :是否为员工
is_staff = models.BooleanField(
_('staff status'),
default=False,
help_text=_('Designates whether the user can log into this admin site.'),
)
is_active :是否处于活动状态
is_active = models.BooleanField(
_('active'),
default=True,
help_text=_(
'Designates whether this user should be treated as active. '
'Unselect this instead of deleting accounts.'
),
)
date_joined :加入日期
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
除了 AbstractUser 帮我们定义好的这些属性之外,我们还要自定义一些属性:
gender :成员性别
gender = models.IntegerField(verbose_name='性别', choices=((1, '男'), (2, '女')), default=1)
avatar:成员头像
avatar = models.ImageField(upload_to='avatars/', default='avatars/default.png')
telephone :成员手机号
telephone = models.CharField(max_length=11, null=True, unique=True)
roles:成员所拥有的角色
一个成员可以拥有多个角色,一个角色也可使赋予多个成员,因此 roles 字段应该是多对多的结构。
roles = models.ManyToManyField(verbose_name='拥有的所有角色', to="Role", blank=True)
depart :成员所属部门
一个department只能属于一个部门,根据角色的不同可以拥有该部门负责事务内的权限,而一个部门可以拥有多名成员,因此成员与部门之间是一对多的关系。
department = models.ForeignKey(verbose_name='部门', to="Department", on_delete=models.PROTECT)
organize :成员所属组织
一个成员只能属于一个组织,而一个组织可以拥有多名成员,因此成员与组织之间是一对多的关系。
organize = models.ForeignKey(verbose_name="组织", to="Organize", null=True, blank=True, on_delete=models.PROTECT)
最后,我们的目的想在做这个项目的同时开发出通用的、达到对象级别的权限控制组件,因此我们只借用 AbstractUser 字段而不继承它,确定 UserInfo model 为:
# RBAC/models.py
class UserInfo(models.Model):
"""成员信息"""
username = models.CharField(verbose_name="用户名", max_length=150, unique=True)
first_name = models.CharField(verbose_name="名", max_length=30, blank=True)
last_name = models.CharField(verbose_name="姓", max_length=150, blank=True)
email = models.EmailField(verbose_name="邮箱", blank=True)
score = models.IntegerField(verbose_name="积分", default=10)
grade = models.IntegerField(verbose_name="等级", choices=((1, "M1"), (2, "M2"), (3, "M3"), (4, "M4"), (5, "M5")),
default=1)
gender = models.IntegerField(verbose_name='性别', choices=((1, '男'), (2, '女')), default=1)
avatar = models.ImageField(verbose_name="头像", upload_to='avatars/', default='avatars/default.png')
telephone = models.CharField(verbose_name='手机号', max_length=11, null=True, unique=True)
date_joined = models.DateTimeField(verbose_name="加入日期", default=timezone.now)
roles = models.ManyToManyField(verbose_name='拥有的所有角色', to="Role", blank=True)
team = models.ForeignKey(verbose_name="组", to="Team", null=True, blank=True, on_delete=models.PROTECT)
department = models.ForeignKey(verbose_name='部门', to="Department", null=True, blank=True, on_delete=models.PROTECT)
def __str__(self):
return self.username
ScoreRecord
工作室共成员分为5个等级,由个人等级积分及相关条件确定,详情见下表:
等级 | 积分要求 | 其他要求 | 备注 |
M1 (white) | 10 | 无 | 新成员初始积分10分 |
M2 (blue) | 100 | 通过基础考试 | 线上答题,随时可尝试 |
M3 (yellow) | 1000 | 至少参与3个项目 | 工作室项目或自己的项目 |
M4 (orange) | 10000 | 能够带队完成项目 | 限定工作室项目 |
M5 (red) | 100000 | 有开源框架贡献 | 在GitHub、CSDN、博客园等社区有一定知名度 |
# RBAC/models.py
class ScoreRecord(models.Model):
"""积分记录"""
score = models.IntegerField(verbose_name="处理分值")
reason = models.TextField(verbose_name="理由")
member = models.ForeignKey(verbose_name="成员", to="UserInfo", on_delete=models.PROTECT)
referee = models.ForeignKey(verbose_name="执行人", to="UserInfo", on_delete=models.PROTECT)
Attendance
class Attendance(models.Model):
"""出勤记录"""
subject = models.CharField(verbose_name="主题", max_length=32)
member = models.ForeignKey(verbose_name="成员", to="UserInfo", on_delete=models.PROTECT)
record = models.CharField("记录", choices=[("check", "全勤"), ("vacate", "请假"), ("late", "迟到"), ("lack", "缺勤")])
score = models.IntegerField(verbose_name="处理分值")
referee = models.ForeignKey(verbose_name="执行人", to="UserInfo", on_delete=models.PROTECT)
def __str__(self):
return "%s-%s" % (self.subject, self.member.username)
Matrix工作室划分为6个部门、4个小组,每位成员可同时拥有所属部门和所属分组。
Team
工作室共分四个组:1. 算法组、2. 前端组、3. 后端组、4. AI组
# RBAC/models.py
class Team(models.Model):
"""分组信息"""
teamName = models.CharField(verbose_name="Team名称", max_length=32, unique=True)
introduce = models.TextField(verbose_name="Team介绍")
# 一个组内可以拥有多名角色,但一个角色只能属于一个组
hasRoles = models.ForeignKey(verbose_name="组内拥有的角色", to="Role", null=True, blank=True, on_delete=models.PROTECT)
def __str__(self):
return self.teamName
Department
工作室下设六个部门:
1. 项目商谈部
由各组组长组成,负责与甲方商谈项目的需求功能与出价,整理出具体的需求分析报告或导图。
2. 项目开发部
负责每个项目的进度监督、成员安排、整体架构设计和技术解决方案,合理调配各组成员。
3. UI设计部
与项目开发人员沟通,负责前端、移动端页面设计,负责工作室宣传海报、视频的制作。
4. 学院联系部
负责与学院相关部门建立联系,维护工作室为学院制作的有关项目,并负责各种比赛的报名与培训安排。
5. 成员管理部
统一管理各组成员,每月团建,同时负责新成员的培训与学习监督,各组成员之间的流动。
6. 技术委员会
由各组组长和工作室M4、M5级别成员组成,为工作室提供技术支持和技术评审。
# RBAC/models.py
class Department(models.Model):
"""部门信息"""
departmentName = models.CharField(verbose_name="部门名称", max_length=32, unique=True)
duty = models.TextField(verbose_name="部门职责")
# 一个部门可以拥有多名角色,但一个角色只能属于一个部门
hasRoles = models.ForeignKey(verbose_name="组内拥有的角色", to="Role", null=True, blank=True, on_delete=models.PROTECT)
def __str__(self):
return self.departmentName
Role
通过角色将成员与权限之间关联起来,不同的成员拥有不同的角色,不同的角色拥有不同的权限。
# RBAC/models.py
class Role(models.Model):
"""角色"""
roleName = models.CharField(verbose_name="角色名称", max_length=32)
permissions = models.ManyToManyField(verbose_name="角色所拥有权限", to='Permission', null=True, blank=True)
def __str__(self):
return self.roleName
Permission
权限其实就是成员是否具有访问某个 URL 的资格,因此权限的主要字段其实就是 URL。
# RBAC/models.py
class Permission(models.Model):
"""权限"""
url = models.CharField(verbose_name="权限URL正则表达式", max_length=256)
permissionName = models.CharField(verbose_name="权限名称", max_length=32)
alias = models.CharField(verbose_name="权限URL别名", max_length=32, unique=True)
icon = models.CharField(verbose_name="权限图标", max_length=32)
menu = models.ForeignKey(verbose_name="所属菜单", to="Menu", null=True, blank=True, on_delete=models.PROTECT,
help_text="如果为 null 表示该权限不是菜单,否则为二级菜单")
parentPermission = models.ForeignKey(verbose_name="父权限", to="Permission", null=True, blank=True,
related_name="parentPermission", on_delete=models.PROTECT,
help_text="非菜单权限需要一个二级菜单的父权限做默认展开和选中")
def __str__(self):
return self.permissionName
Menu
菜单用于侧边栏展示。
# RBAC/models.py
class Menu(models.Model):
"""菜单"""
menuName = models.CharField(verbose_name="菜单名称", max_length=32)
icon = models.CharField(verbose_name="菜单图标", max_length=32)
def __str__(self):
return self.menuName
二、业务表结构设计
User
对于非工作室人员注册的账号,其实就是游客,他们也需要一个账号,另外,工作室内部成员有时也是游客,此时,需要为游客创建一张用户表,这时候,我们就可以直接用 AbstractUser 类做继承了:
class User(AbstractUser):
"""用户"""
avatar = models.ImageField(upload_to='avatars/', default='avatars/default.png')
telephone = models.CharField(max_length=11, null=True, blank=True, unique=True)
def __str__(self):
return self.username
Course
工作室 M4、M5等级的成员可以开设课程,为方便管理,需要创建一张 Course 表。
# index/models.py
class Course(models.Model):
"""课程"""
courseName = models.CharField(verbose_name="课程名称", max_length=32)
sketch = models.TextField(verbose_name="课程简述")
price = models.PositiveIntegerField(verbose_name="学费", help_text="游客学习收费,工作室成员学习免费")
cover = models.ImageField(verbose_name="课程封面", upload_to='courseCover/', default='courseCover/default.png')
grade = models.IntegerField(verbose_name="课程等级", choices=((1, "M1"), (2, "M2"), (3, "M3"), (4, "M4"), (5, "M5")))
category = models.IntegerField(verbose_name="课程分类", choices=((1, "算法"), (2, "前端"), (3, "后端"), (4, "AI"), (5, "其它")))
teacher = models.ForeignKey(verbose_name="开课老师", to="UserInfo", on_delete=models.PROTECT,
help_text="开课老师限制为M4、M5等级成员")
assistant = models.ManyToManyField(verbose_name="助教", to="UserInfo",
help_text="助教限定为M3等级成员")
def __str__(self):
return self.courseName
Classes
每一门课程对应一个班级,用于存储一些课程资料、学生交流和老师答疑。
# index/models.py
class Classes(models.Model):
"""班级"""
startDate = models.DateField(verbose_name="开课日期")
QQ = models.IntegerField(verbose_name="班级QQ群")
graduateDate = models.DateField(verbose_name="结业日期", null=True, blank=True)
explain = models.TextField(verbose_name="说明", null=True, blank=True)
course = models.ForeignKey(verbose_name="课程", to="Course", on_delete=models.PROTECT)
classTeacher = models.ForeignKey(verbose_name="班主任", to="UserInfo", on_delete=models.PROTECT,
help_text="班主任为成员管理部成员,负责督促老师课程制作进度和学生学习进度")
def __str__(self):
return "%s-%s" % (self.course.courseName, self.QQ)
Student
class Student(models.Model):
"""学生表"""
student = models.OneToOneField(verbose_name="学生信息", to="User", on_delete=models.PROTECT)
QQ = models.CharField(verbose_name="学生QQ", max_length=32)
telephone = models.IntegerField(verbose_name="学生手机号", max_length=32)
classList = models.ManyToManyField(verbose_name="已报班级", to="Classes", null=True, blank=True)
state = models.IntegerField(verbose_name="学生状态", choices=[(1, "审核"), (2, "在读"), (3, "毕业")], default=1)
remark = models.TextField(verbose_name="备注")
def __str__(self):
return "%s-%s" % (self.student.username, self.classList.course.name)
Project
工作室承接项目,需要一个项目表存储项目记录。
class Project(models.Model):
"""项目"""
name = models.CharField(verbose_name="项目名称", max_length=32)
contactName = models.CharField(verbose_name="联系人姓名", max_length=32)
contactInformation = models.CharField(verbose_name="联系人联系方式", max_length=64, help_text="QQ/WeChat/Phone")
price = models.IntegerField(verbose_name="项目报价")
introduce = models.TextField(verbose_name="项目介绍")
superintendent = models.ForeignKey(verbose_name="项目负责人", to="UserInfo",
null=True, blank=True, on_delete=models.PROTECT)
startDate = models.DateField(verbose_name="接取日期", null=True, blank=True)
completeDate = models.DateField(verbose_name="完结日期", null=True, blank=True)
def __str__(self):
return self.name
ProjectRecord
为了保证项目能够保质保量的按时完成,每个项目需要一名项目开发部的成员进行监督和跟进。
class ProjectRecord(models.Model):
"""项目跟进记录"""
content = models.TextField(verbose_name="跟进内容")
date = models.DateField(verbose_name="跟进日期", default=timezone.now)
project = models.ForeignKey(verbose_name="跟进项目", to="Project", on_delete=models.PROTECT)
superintendent = models.ForeignKey(verbose_name="跟进人", to="UserInfo", on_delete=models.PROTECT)
def __str__(self):
return "%s-%s" % (self.project.name, self.date)
PaymentRecord
class PaymentRecord(models.Model):
"""账单记录"""
user = models.OneToOneField(verbose_name="付款人", to="User", on_delete=models.PROTECT)
type = models.IntegerField(verbose_name="账单类型", choices=[(1, "学费"), (2, "项目款"), (3, "其它")])
price = models.IntegerField(verbose_name="金额")
date = models.DateTimeField(verbose_name="账单日期", default=timezone.now)
state = models.IntegerField(verbose_name="状态", choices=[(1, "审核中"), (2, "确认"), (3, "驳回")])
confirmDate = models.DateTimeField(verbose_name="确认日期", null=True, blank=True)
confirmUser = models.ForeignKey(verbose_name="审批人", to="UserInfo", null=True, blank=True, on_delete=models.PROTECT)
remark = models.TextField(verbose_name="备注", null=True, blank=True)
def __str__(self):
return "%s-%s-%s" % (self.user.username, self.type, self.price)