diff --git a/software-engineering-methodology-djq-branch(1)/software-engineering-methodology/src/DjangoBlog-master(1)/DjangoBlog-master/blog/models.py b/software-engineering-methodology-djq-branch(1)/software-engineering-methodology/src/DjangoBlog-master(1)/DjangoBlog-master/blog/models.py index 083788b..f53cebb 100644 --- a/software-engineering-methodology-djq-branch(1)/software-engineering-methodology/src/DjangoBlog-master(1)/DjangoBlog-master/blog/models.py +++ b/software-engineering-methodology-djq-branch(1)/software-engineering-methodology/src/DjangoBlog-master(1)/DjangoBlog-master/blog/models.py @@ -18,6 +18,7 @@ logger = logging.getLogger(__name__) class LinkShowType(models.TextChoices): + # 定义友情链接显示类型的枚举类,分别对应首页、列表页、文章页、所有页面、轮播 I = ('i', _('index')) L = ('l', _('list')) P = ('p', _('post')) @@ -26,98 +27,118 @@ class LinkShowType(models.TextChoices): class BaseModel(models.Model): - id = models.AutoField(primary_key=True) - creation_time = models.DateTimeField(_('creation time'), default=now) - last_modify_time = models.DateTimeField(_('modify time'), default=now) + """ + 基础模型类,为其他模型提供通用的字段和方法 + """ + id = models.AutoField(primary_key=True) # 自增主键 + creation_time = models.DateTimeField(_('creation time'), default=now) # 创建时间 + last_modify_time = models.DateTimeField(_('modify time'), default=now) # 最后修改时间 def save(self, *args, **kwargs): + """ + 重写save方法,处理slug字段(如果模型有slug和title/name字段),并调用父类save方法 + 同时处理仅更新views字段的特殊情况 + """ is_update_views = isinstance( self, Article) and 'update_fields' in kwargs and kwargs['update_fields'] == ['views'] if is_update_views: Article.objects.filter(pk=self.pk).update(views=self.views) else: + # 如果模型有slug字段,生成slug(基于title或name字段) if 'slug' in self.__dict__: - slug = getattr( - self, 'title') if 'title' in self.__dict__ else getattr( - self, 'name') - setattr(self, 'slug', slugify(slug)) + slug_source = getattr(self, 'title') if 'title' in self.__dict__ else getattr(self, 'name') + setattr(self, 'slug', slugify(slug_source)) super().save(*args, **kwargs) def get_full_url(self): + """ + 获取模型对象的完整URL(包含域名) + """ site = get_current_site().domain url = "https://{site}{path}".format(site=site, path=self.get_absolute_url()) return url class Meta: - abstract = True + abstract = True # 抽象模型,不生成数据库表 @abstractmethod def get_absolute_url(self): + """ + 抽象方法,子类必须实现,用于获取模型对象的绝对URL + """ pass class Article(BaseModel): - """文章""" + """ + 文章模型类,存储文章的相关信息 + """ + # 文章状态:草稿、已发布 STATUS_CHOICES = ( ('d', _('Draft')), ('p', _('Published')), ) + # 评论状态:开启、关闭 COMMENT_STATUS = ( ('o', _('Open')), ('c', _('Close')), ) + # 文章类型:文章、页面 TYPE = ( ('a', _('Article')), ('p', _('Page')), ) - title = models.CharField(_('title'), max_length=200, unique=True) - body = MDTextField(_('body')) + title = models.CharField(_('title'), max_length=200, unique=True) # 文章标题,唯一 + body = MDTextField(_('body')) # 文章内容,使用MDTextField支持markdown pub_time = models.DateTimeField( - _('publish time'), blank=False, null=False, default=now) + _('publish time'), blank=False, null=False, default=now) # 发布时间 status = models.CharField( _('status'), max_length=1, choices=STATUS_CHOICES, - default='p') + default='p') # 文章状态 comment_status = models.CharField( _('comment status'), max_length=1, choices=COMMENT_STATUS, - default='o') - type = models.CharField(_('type'), max_length=1, choices=TYPE, default='a') - views = models.PositiveIntegerField(_('views'), default=0) + default='o') # 评论状态 + type = models.CharField(_('type'), max_length=1, choices=TYPE, default='a') # 文章类型 + views = models.PositiveIntegerField(_('views'), default=0) # 文章浏览量 author = models.ForeignKey( settings.AUTH_USER_MODEL, verbose_name=_('author'), blank=False, null=False, - on_delete=models.CASCADE) + on_delete=models.CASCADE) # 文章作者,外键关联用户模型 article_order = models.IntegerField( - _('order'), blank=False, null=False, default=0) - show_toc = models.BooleanField(_('show toc'), blank=False, null=False, default=False) + _('order'), blank=False, null=False, default=0) # 文章排序序号 + show_toc = models.BooleanField(_('show toc'), blank=False, null=False, default=False) # 是否显示目录 category = models.ForeignKey( 'Category', verbose_name=_('category'), on_delete=models.CASCADE, blank=False, - null=False) - tags = models.ManyToManyField('Tag', verbose_name=_('tag'), blank=True) + null=False) # 文章分类,外键关联Category模型 + tags = models.ManyToManyField('Tag', verbose_name=_('tag'), blank=True) # 文章标签,多对多关联Tag模型 def body_to_string(self): + """将文章内容转换为字符串返回""" return self.body def __str__(self): + """自定义字符串表示,返回文章标题""" return self.title class Meta: - ordering = ['-article_order', '-pub_time'] + ordering = ['-article_order', '-pub_time'] # 排序规则:先按article_order降序,再按pub_time降序 verbose_name = _('article') verbose_name_plural = verbose_name get_latest_by = 'id' def get_absolute_url(self): + """获取文章的绝对URL,用于生成文章详情页链接""" return reverse('blog:detailbyid', kwargs={ 'article_id': self.id, 'year': self.creation_time.year, @@ -127,19 +148,26 @@ class Article(BaseModel): @cache_decorator(60 * 60 * 10) def get_category_tree(self): + """ + 获取文章分类的树形结构(包含当前分类及其所有父级分类),并缓存 + """ tree = self.category.get_category_tree() names = list(map(lambda c: (c.name, c.get_absolute_url()), tree)) - return names def save(self, *args, **kwargs): + """重写save方法,调用父类save方法""" super().save(*args, **kwargs) def viewed(self): + """文章被浏览时,浏览量加1并保存""" self.views += 1 self.save(update_fields=['views']) def comment_list(self): + """ + 获取文章的评论列表,优先从缓存获取,缓存不存在则查询数据库并缓存 + """ cache_key = 'article_comments_{id}'.format(id=self.id) value = cache.get(cache_key) if value: @@ -152,24 +180,25 @@ class Article(BaseModel): return comments def get_admin_url(self): + """获取文章在admin后台的编辑URL""" info = (self._meta.app_label, self._meta.model_name) return reverse('admin:%s_%s_change' % info, args=(self.pk,)) @cache_decorator(expiration=60 * 100) def next_article(self): - # 下一篇 + """获取下一篇文章(id大于当前文章且已发布的第一篇),并缓存""" return Article.objects.filter( id__gt=self.id, status='p').order_by('id').first() @cache_decorator(expiration=60 * 100) def prev_article(self): - # 前一篇 + """获取前一篇文章(id小于当前文章且已发布的第一篇),并缓存""" return Article.objects.filter(id__lt=self.id, status='p').first() def get_first_image_url(self): """ - Get the first image url from article.body. - :return: + 从文章内容中提取第一张图片的URL + 通过正则表达式匹配markdown图片语法中的图片链接 """ match = re.search(r'!\[.*?\]\((.+?)\)', self.body) if match: @@ -178,35 +207,38 @@ class Article(BaseModel): class Category(BaseModel): - """文章分类""" - name = models.CharField(_('category name'), max_length=30, unique=True) + """ + 文章分类模型类 + """ + name = models.CharField(_('category name'), max_length=30, unique=True) # 分类名称,唯一 parent_category = models.ForeignKey( 'self', verbose_name=_('parent category'), blank=True, null=True, - on_delete=models.CASCADE) - slug = models.SlugField(default='no-slug', max_length=60, blank=True) - index = models.IntegerField(default=0, verbose_name=_('index')) + on_delete=models.CASCADE) # 父分类,自关联 + slug = models.SlugField(default='no-slug', max_length=60, blank=True) # 分类的slug,用于URL + index = models.IntegerField(default=0, verbose_name=_('index')) # 分类排序序号 class Meta: - ordering = ['-index'] + ordering = ['-index'] # 按index降序排序 verbose_name = _('category') verbose_name_plural = verbose_name def get_absolute_url(self): + """获取分类的绝对URL,用于生成分类页链接""" return reverse( 'blog:category_detail', kwargs={ 'category_name': self.slug}) def __str__(self): + """自定义字符串表示,返回分类名称""" return self.name @cache_decorator(60 * 60 * 10) def get_category_tree(self): """ - 递归获得分类目录的父级 - :return: + 递归获取分类的树形结构(当前分类及其所有父级分类),并缓存 """ categorys = [] @@ -221,8 +253,7 @@ class Category(BaseModel): @cache_decorator(60 * 60 * 10) def get_sub_categorys(self): """ - 获得当前分类目录所有子集 - :return: + 递归获取当前分类的所有子分类,包括子分类的子分类等,并缓存 """ categorys = [] all_categorys = Category.objects.all() @@ -241,136 +272,156 @@ class Category(BaseModel): class Tag(BaseModel): - """文章标签""" - name = models.CharField(_('tag name'), max_length=30, unique=True) - slug = models.SlugField(default='no-slug', max_length=60, blank=True) + """ + 文章标签模型类 + """ + name = models.CharField(_('tag name'), max_length=30, unique=True) # 标签名称,唯一 + slug = models.SlugField(default='no-slug', max_length=60, blank=True) # 标签的slug,用于URL def __str__(self): + """自定义字符串表示,返回标签名称""" return self.name def get_absolute_url(self): + """获取标签的绝对URL,用于生成标签页链接""" return reverse('blog:tag_detail', kwargs={'tag_name': self.slug}) @cache_decorator(60 * 60 * 10) def get_article_count(self): + """获取该标签下的文章数量,并缓存""" return Article.objects.filter(tags__name=self.name).distinct().count() class Meta: - ordering = ['name'] + ordering = ['name'] # 按名称排序 verbose_name = _('tag') verbose_name_plural = verbose_name class Links(models.Model): - """友情链接""" - - name = models.CharField(_('link name'), max_length=30, unique=True) - link = models.URLField(_('link')) - sequence = models.IntegerField(_('order'), unique=True) + """ + 友情链接模型类 + """ + name = models.CharField(_('link name'), max_length=30, unique=True) # 链接名称,唯一 + link = models.URLField(_('link')) # 链接URL + sequence = models.IntegerField(_('order'), unique=True) # 排序序号,唯一 is_enable = models.BooleanField( - _('is show'), default=True, blank=False, null=False) + _('is show'), default=True, blank=False, null=False) # 是否显示 show_type = models.CharField( _('show type'), max_length=1, choices=LinkShowType.choices, - default=LinkShowType.I) - creation_time = models.DateTimeField(_('creation time'), default=now) - last_mod_time = models.DateTimeField(_('modify time'), default=now) + default=LinkShowType.I) # 显示类型,关联LinkShowType枚举 + creation_time = models.DateTimeField(_('creation time'), default=now) # 创建时间 + last_mod_time = models.DateTimeField(_('modify time'), default=now) # 最后修改时间 class Meta: - ordering = ['sequence'] + ordering = ['sequence'] # 按sequence排序 verbose_name = _('link') verbose_name_plural = verbose_name def __str__(self): + """自定义字符串表示,返回链接名称""" return self.name class SideBar(models.Model): - """侧边栏,可以展示一些html内容""" - name = models.CharField(_('title'), max_length=100) - content = models.TextField(_('content')) - sequence = models.IntegerField(_('order'), unique=True) - is_enable = models.BooleanField(_('is enable'), default=True) - creation_time = models.DateTimeField(_('creation time'), default=now) - last_mod_time = models.DateTimeField(_('modify time'), default=now) + """ + 侧边栏模型类,用于展示自定义HTML内容 + """ + name = models.CharField(_('title'), max_length=100) # 侧边栏标题 + content = models.TextField(_('content')) # 侧边栏内容(HTML) + sequence = models.IntegerField(_('order'), unique=True) # 排序序号,唯一 + is_enable = models.BooleanField(_('is enable'), default=True) # 是否启用 + creation_time = models.DateTimeField(_('creation time'), default=now) # 创建时间 + last_mod_time = models.DateTimeField(_('modify time'), default=now) # 最后修改时间 class Meta: - ordering = ['sequence'] + ordering = ['sequence'] # 按sequence排序 verbose_name = _('sidebar') verbose_name_plural = verbose_name def __str__(self): + """自定义字符串表示,返回侧边栏标题""" return self.name class BlogSettings(models.Model): - """blog的配置""" + """ + 博客配置模型类,存储网站的各种配置信息 + """ site_name = models.CharField( _('site name'), max_length=200, null=False, blank=False, - default='') + default='') # 网站名称 site_description = models.TextField( _('site description'), max_length=1000, null=False, blank=False, - default='') + default='') # 网站描述 site_seo_description = models.TextField( - _('site seo description'), max_length=1000, null=False, blank=False, default='') + _('site seo description'), max_length=1000, null=False, blank=False, default='') # 网站SEO描述 site_keywords = models.TextField( _('site keywords'), max_length=1000, null=False, blank=False, - default='') - article_sub_length = models.IntegerField(_('article sub length'), default=300) - sidebar_article_count = models.IntegerField(_('sidebar article count'), default=10) - sidebar_comment_count = models.IntegerField(_('sidebar comment count'), default=5) - article_comment_count = models.IntegerField(_('article comment count'), default=5) - show_google_adsense = models.BooleanField(_('show adsense'), default=False) + default='') # 网站关键词 + article_sub_length = models.IntegerField(_('article sub length'), default=300) # 文章摘要长度 + sidebar_article_count = models.IntegerField(_('sidebar article count'), default=10) # 侧边栏文章数量 + sidebar_comment_count = models.IntegerField(_('sidebar comment count'), default=5) # 侧边栏评论数量 + article_comment_count = models.IntegerField(_('article comment count'), default=5) # 文章评论数量 + show_google_adsense = models.BooleanField(_('show adsense'), default=False) # 是否显示Google广告 google_adsense_codes = models.TextField( - _('adsense code'), max_length=2000, null=True, blank=True, default='') - open_site_comment = models.BooleanField(_('open site comment'), default=True) - global_header = models.TextField("公共头部", null=True, blank=True, default='') - global_footer = models.TextField("公共尾部", null=True, blank=True, default='') + _('adsense code'), max_length=2000, null=True, blank=True, default='') # Google广告代码 + open_site_comment = models.BooleanField(_('open site comment'), default=True) # 是否开启网站评论 + global_header = models.TextField("公共头部", null=True, blank=True, default='') # 公共头部HTML + global_footer = models.TextField("公共尾部", null=True, blank=True, default='') # 公共尾部HTML beian_code = models.CharField( '备案号', max_length=2000, null=True, blank=True, - default='') + default='') # 网站备案号 analytics_code = models.TextField( "网站统计代码", max_length=1000, null=False, blank=False, - default='') + default='') # 网站统计代码 show_gongan_code = models.BooleanField( - '是否显示公安备案号', default=False, null=False) + '是否显示公安备案号', default=False, null=False) # 是否显示公安备案号 gongan_beiancode = models.TextField( '公安备案号', max_length=2000, null=True, blank=True, - default='') + default='') # 公安备案号 comment_need_review = models.BooleanField( - '评论是否需要审核', default=False, null=False) + '评论是否需要审核', default=False, null=False) # 评论是否需要审核 class Meta: verbose_name = _('Website configuration') verbose_name_plural = verbose_name def __str__(self): + """自定义字符串表示,返回网站名称""" return self.site_name def clean(self): + """ + 模型验证方法,确保只能有一个配置实例 + 如果存在其他配置实例(排除当前实例),则抛出验证错误 + """ if BlogSettings.objects.exclude(id=self.id).count(): raise ValidationError(_('There can only be one configuration')) def save(self, *args, **kwargs): + """ + 重写save方法,保存后清除缓存(使配置变更立即生效) + """ super().save(*args, **kwargs) from djangoblog.utils import cache - cache.clear() + cache.clear() \ No newline at end of file