完美的项目从完美的表开始

表结构设计是一个项目的基石,组织好各种数据之间的逻辑关系,往往能够使开发事半功倍。

一、权限控制表结构设计

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)

发布了726 篇原创文章 · 获赞 402 · 访问量 31万+

猜你喜欢

转载自blog.csdn.net/weixin_43336281/article/details/105276216