需求抽象與模型設計#
後台開發本質不就是 CRUD 嗎 😂 /doge,因此先從數據模型上進行設計
上文有提到過需求,主要功能包括發布學習內容,學生管理,制定學習計劃,學生打卡
這裡有三種角色,
- 管理員:可以有所有的功能
- 學習委員:每個班級一個學習委員,負責制定本班的學習推送計劃
- 學生:可以接收學習內容,並打卡
因此我們可以思考下,抽象出表
- 學習內容表,記錄學習內容
- 班級表,需要有個學委的角色
- 學生表,三種角色
- 打卡表,每個學生的打卡記錄
- 推送策略,同時關聯學習內容和班級
學習內容表,包含學習內容、標題、簡介、學習鏈接,為了避免直接刪除,加了個是否上線的判斷來標記刪除
class StudyContent(models.Model):
"""
學習內容,包含標題,簡介,學習鏈接
"""
title = models.CharField(max_length=100, verbose_name='標題')
brief = models.TextField(verbose_name='簡介')
url = models.URLField(verbose_name='頁面鏈接')
is_online = models.BooleanField(default=False, verbose_name='學習內容是否上線')
class Meta:
verbose_name = '學習內容'
verbose_name_plural = '學習內容'
def __str__(self):
return self.title
verbose_name 是為了能在後台展示的更清晰,不然就是一個 Object
學生表、班級表,學生表是需要引用班級表的外鍵,並且班級表也需要引用學生表作為外鍵
這裡就遇到了第一個坑,根據上文描述這裡其實存在循環依賴,導致後續新建用戶失敗
因此加上 black=True
來允許創建一個沒有學委的班級,也允許創建一個學生,但是不指定班級
class SchoolClass(models.Model):
"""
班級表,每個學生屬於班級,每個班級一個學委
"""
name = models.CharField(max_length=100, verbose_name='班級名')
study_committee = models.OneToOneField(
'Student',
on_delete=models.SET_NULL,
null=True,
blank=True, # 允許先創建一個沒有學委的班級
related_name='managed_class',
verbose_name='學習委員'
)
class Meta:
verbose_name = '班級管理'
verbose_name_plural = '班級管理'
def __str__(self):
return self.name
class Student(AbstractUser):
"""
角色表
"""
STUDENT = 'ST'
ADMIN = 'AD'
STUDY_COMMITTEE = 'SC'
ROLE_CHOICES = [
(STUDENT, '學生'),
(ADMIN, '管理員'),
(STUDY_COMMITTEE, '學習委員')
]
role = models.CharField(
max_length=2, choices=ROLE_CHOICES, default=STUDENT, verbose_name='角色類型')
class_group = models.ForeignKey(SchoolClass, on_delete=models.SET_NULL, null=True, blank=True, related_name='students', verbose_name='所屬班級')
is_staff = models.BooleanField(default=True) # 默認允許登錄後台
def __str__(self):
return self.username
class Meta:
verbose_name = '用戶管理'
verbose_name_plural = '用戶管理'
為了復用 Django 後台的用戶管理功能,我們讓
Student
類繼承自AbstractUser
類
後續在 admin 中還需要添加如下代碼,這是後話
@admin.register(Student)
class StudentAdmin(UserAdmin):
pass
推送策略表,打卡記錄表
打卡表關聯了學習內容做外鍵
推送策略表同時關聯了學習內容、班級做外鍵
class Checkin(models.Model):
"""
學生打卡表
"""
student = models.ForeignKey(Student, on_delete=models.CASCADE, verbose_name='學生')
study_content = models.ForeignKey(
StudyContent, on_delete=models.CASCADE, related_name='checkins', verbose_name='學習內容')
checkin_time = models.DateTimeField(auto_now_add=True, verbose_name='打卡時間')
is_valid = models.BooleanField(default=True, verbose_name='打卡記錄是否有效') # 默認標記為有效
class Meta:
verbose_name = '學生打卡記錄管理'
verbose_name_plural = '學生打卡記錄管理'
def __str__(self):
return f"{self.student} 在 {self.checkin_time} 打卡了 {self.study_content}"
class PushStrategy(models.Model):
"""
推送策略,外鍵關聯內容,班級、推送頻率和推送時間
"""
study_content = models.ForeignKey(
StudyContent, on_delete=models.CASCADE, related_name='push_strategy', verbose_name='學習內容')
class_group = models.ForeignKey('SchoolClass', on_delete=models.CASCADE, related_name='push_strategies', verbose_name='班級')
frequency = models.CharField(max_length=20, verbose_name='推送頻率')
push_time = models.DateTimeField(verbose_name='推送開始時間')
is_online = models.BooleanField(default=False, verbose_name='推送策略是否上線')
class Meta:
verbose_name = '推送策略管理'
verbose_name_plural = '推送策略管理'
unique_together = [['study_content', 'class_group']] # 保證這兩個鍵唯一
def __str__(self):
return self.study_content.title
至此我們的模型就基本開發完了,每次修改完模型,都需要運行
python manage.py makemigrations
python manage.py migrate