Files
chunyu_project/learn/models.py
T
2026-08-05 23:59:15 +08:00

220 lines
7.7 KiB
Python

import os
from django.db import models
from django.conf import settings
CATEGORY_CHOICES = [
('frontend', '前端'),
('backend', '后端'),
('tools', '工具'),
('devops', '运维'),
('security', '安全'),
('basic', '基础'),
]
LEVEL_CHOICES = [
('beginner', '入门'),
('advanced', '进阶'),
]
STATUS_CHOICES = [
('published', '已发布'),
('draft', '草稿'),
]
class Course(models.Model):
title = models.CharField(max_length=200, verbose_name='标题')
description = models.TextField(blank=True, default='', verbose_name='描述')
category = models.CharField(max_length=20, choices=CATEGORY_CHOICES, default='frontend')
level = models.CharField(max_length=10, choices=LEVEL_CHOICES, default='beginner')
cover_image = models.ImageField(upload_to='learn/covers/%Y/%m/', blank=True, null=True)
color = models.CharField(max_length=20, default='#667eea', verbose_name='主题色')
icon_name = models.CharField(max_length=50, default='CodeOutlined', verbose_name='图标名称')
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='courses',
verbose_name='作者'
)
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='draft')
sort_order = models.IntegerField(default=0)
is_hot = models.BooleanField(default=False)
is_new = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['-sort_order', '-created_at']
verbose_name = '课程'
verbose_name_plural = '课程'
def __str__(self):
return self.title
class Chapter(models.Model):
VIDEO_SOURCE_CHOICES = [
('bilibili', 'B站视频'),
('local', '本地视频'),
]
course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='chapters', verbose_name='课程')
title = models.CharField(max_length=200)
sort_order = models.IntegerField(default=0)
duration = models.CharField(max_length=50, blank=True, default='', verbose_name='时长')
is_free = models.BooleanField(default=True)
video_url = models.URLField(max_length=500, blank=True, default='', verbose_name='视频链接')
video_source = models.CharField(max_length=20, choices=VIDEO_SOURCE_CHOICES, default='bilibili', verbose_name='视频来源')
video_local = models.FileField(upload_to='learn/videos/%Y/%m/', blank=True, null=True, verbose_name='本地视频文件')
video_poster = models.ImageField(upload_to='learn/video_posters/%Y/%m/', blank=True, null=True, verbose_name='视频封面')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['sort_order']
verbose_name = '章节'
verbose_name_plural = '章节'
def __str__(self):
return self.title
class ChapterContent(models.Model):
chapter = models.OneToOneField(Chapter, on_delete=models.CASCADE, related_name='content', verbose_name='章节')
content_md = models.TextField(blank=True, default='', verbose_name='Markdown内容')
content_html = models.TextField(blank=True, default='', verbose_name='预渲染HTML')
md_file_path = models.CharField(max_length=500, blank=True, default='', verbose_name='MD文件路径')
updated_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = '章节内容'
verbose_name_plural = '章节内容'
def __str__(self):
return f'{self.chapter.title} - 内容'
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
if self.content_md:
try:
import markdown
self.content_html = markdown.markdown(
self.content_md,
extensions=['extra', 'codehilite', 'toc', 'tables']
)
except ImportError:
self.content_html = self.content_md.replace('\n', '<br>')
ChapterContent.objects.filter(pk=self.pk).update(content_html=self.content_html)
self.export_md_file()
def export_md_file(self):
if not self.content_md:
return
course_id = self.chapter.course_id
chapter_id = self.chapter_id
file_path = os.path.join(
settings.MEDIA_ROOT,
'learn', 'courses', str(course_id), 'chapters', f'{chapter_id}.md'
)
os.makedirs(os.path.dirname(file_path), exist_ok=True)
with open(file_path, 'w', encoding='utf-8') as f:
f.write(self.content_md)
relative_path = os.path.relpath(file_path, settings.MEDIA_ROOT).replace('\\', '/')
ChapterContent.objects.filter(pk=self.pk).update(md_file_path=relative_path)
class ChapterRead(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='chapter_reads',
verbose_name='用户'
)
chapter = models.ForeignKey(
Chapter,
on_delete=models.CASCADE,
related_name='chapter_reads',
verbose_name='章节'
)
course = models.ForeignKey(
Course,
on_delete=models.CASCADE,
related_name='chapter_reads',
verbose_name='课程'
)
completed = models.BooleanField(default=True, verbose_name='是否已完成')
completed_at = models.DateTimeField(auto_now_add=True, verbose_name='完成时间')
updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')
class Meta:
verbose_name = '章节学习记录'
verbose_name_plural = '章节学习记录'
unique_together = ['user', 'chapter']
ordering = ['-completed_at']
def __str__(self):
return f'{self.user} read {self.chapter}'
class CourseFavorite(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='course_favorites',
verbose_name='用户'
)
course = models.ForeignKey(
Course,
on_delete=models.CASCADE,
related_name='course_favorites',
verbose_name='课程'
)
created_at = models.DateTimeField(
auto_now_add=True,
verbose_name='创建时间'
)
class Meta:
verbose_name = '课程收藏'
verbose_name_plural = '课程收藏'
unique_together = ['user', 'course']
ordering = ['-created_at']
def __str__(self):
return f'{self.user} favorites {self.course}'
class CourseMaterial(models.Model):
FILE_TYPE_CHOICES = [
('pdf', 'PDF'),
('code', '代码'),
('doc', '文档'),
('video', '视频'),
('dataset', '数据集'),
('zip', '压缩包'),
('other', '其他'),
]
chapter = models.ForeignKey(
Chapter,
on_delete=models.CASCADE,
related_name='materials',
verbose_name='章节'
)
title = models.CharField(max_length=200, verbose_name='资料名称')
file = models.FileField(upload_to='learn/materials/%Y/%m/', verbose_name='文件')
file_type = models.CharField(max_length=20, choices=FILE_TYPE_CHOICES, default='other', verbose_name='文件类型')
file_size = models.PositiveIntegerField(default=0, verbose_name='文件大小(字节)')
download_count = models.PositiveIntegerField(default=0, verbose_name='下载次数')
sort_order = models.IntegerField(default=0, verbose_name='排序')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
class Meta:
ordering = ['sort_order']
verbose_name = '课程资料'
verbose_name_plural = '课程资料'
def __str__(self):
return self.title