<<<<<<< HEAD <<<<<<< HEAD from django.contrib.auth.models import AbstractUser ======= import logging import re from abc import abstractmethod from django.conf import settings from django.core.exceptions import ValidationError >>>>>>> hyt_branch from django.db import models from django.urls import reverse from django.utils.timezone import now from django.utils.translation import gettext_lazy as _ <<<<<<< HEAD from djangoblog.utils import get_current_site # Create your models here. class BlogUser(AbstractUser): nickname = models.CharField(_('nick name'), max_length=100, blank=True) creation_time = models.DateTimeField(_('creation time'), default=now) last_modify_time = models.DateTimeField(_('last modify time'), default=now) source = models.CharField(_('create source'), max_length=100, blank=True) def get_absolute_url(self): return reverse( 'blog:author_detail', kwargs={ 'author_name': self.username}) def __str__(self): return self.email def get_full_url(self): ======= from mdeditor.fields import MDTextField from uuslug import slugify from djangoblog.utils import cache_decorator, cache from djangoblog.utils import get_current_site logger = logging.getLogger(__name__) class LinkShowType(models.TextChoices): """ 链接显示类型选择 定义友情链接在网站中的显示位置 """ I = ('i', _('index')) # 首页显示 L = ('l', _('list')) # 列表页显示 P = ('p', _('post')) # 文章页显示 A = ('a', _('all')) # 所有页面显示 S = ('s', _('slide')) # 幻灯片显示 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) # 最后修改时间 def save(self, *args, **kwargs): """ 重写保存方法 处理文章浏览量更新和自动生成slug """ # 检查是否为文章视图更新操作(优化性能,避免完整保存) 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(URL友好字符串) if 'slug' in self.__dict__: slug = getattr( self, 'title') if 'title' in self.__dict__ else getattr( self, 'name') setattr(self, 'slug', slugify(slug)) # 调用父类保存方法 super().save(*args, **kwargs) def get_full_url(self): """获取完整的URL地址(包含域名)""" >>>>>>> hyt_branch site = get_current_site().domain url = "https://{site}{path}".format(site=site, path=self.get_absolute_url()) return url class Meta: <<<<<<< HEAD ordering = ['-id'] verbose_name = _('user') verbose_name_plural = verbose_name get_latest_by = 'id' ======= from django.conf import settings from django.db import models from django.utils.timezone import now from django.utils.translation import gettext_lazy as _ from blog.models import Article # 评论模型,存储用户对文章的评论及评论间的嵌套关系 class Comment(models.Model): body = models.TextField('正文', max_length=300) # 评论内容,限制最大长度300字符 creation_time = models.DateTimeField(_('creation time'), default=now) # 评论创建时间,默认当前时间 last_modify_time = models.DateTimeField(_('last modify time'), default=now) # 评论最后修改时间,默认当前时间 author = models.ForeignKey( settings.AUTH_USER_MODEL, # 关联Django内置用户模型,便于扩展用户系统 verbose_name=_('author'), on_delete=models.CASCADE) # 级联删除:用户删除时,其评论也会被删除 article = models.ForeignKey( Article, verbose_name=_('article'), on_delete=models.CASCADE) # 级联删除:文章删除时,其下所有评论也会被删除 parent_comment = models.ForeignKey( 'self', # 自关联,实现评论嵌套回复功能 verbose_name=_('parent comment'), blank=True, null=True, # 允许为空,表示该评论是顶级评论(不是回复) on_delete=models.CASCADE) # 级联删除:父评论删除时,其所有子评论也会被删除 is_enable = models.BooleanField(_('enable'), default=False, blank=False, null=False) # 评论是否启用(可用于审核功能) class Meta: ordering = ['-id'] # 默认按ID降序排列,最新评论显示在前面 verbose_name = _('comment') verbose_name_plural = verbose_name get_latest_by = 'id' # 指定通过id字段获取最新记录 def __str__(self): return self.body >>>>>>> zh_branch ======= 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')) # 文章内容,使用Markdown编辑器 pub_time = models.DateTimeField(_('publish time'), blank=False, null=False, default=now) # 发布时间 # 状态字段 status = models.CharField(_('status'), max_length=1, choices=STATUS_CHOICES, 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) # 浏览次数 # 关联字段 author = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('author'), blank=False, null=False, 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) # 是否显示目录 category = models.ForeignKey('Category', verbose_name=_('category'), on_delete=models.CASCADE, blank=False, null=False) # 分类 tags = models.ManyToManyField('Tag', verbose_name=_('tag'), blank=True) # 标签,多对多关系 def body_to_string(self): """将文章内容转换为字符串""" return self.body def __str__(self): """对象的字符串表示""" return self.title class Meta: ordering = ['-article_order', '-pub_time'] # 默认按排序和发布时间降序排列 verbose_name = _('article') # 单数显示名称 verbose_name_plural = verbose_name # 复数显示名称 get_latest_by = 'id' # 获取最新记录的依据字段 def get_absolute_url(self): """获取文章的绝对URL,包含年月日信息用于SEO""" return reverse('blog:detailbyid', kwargs={ 'article_id': self.id, 'year': self.creation_time.year, 'month': self.creation_time.month, 'day': self.creation_time.day }) @cache_decorator(60 * 60 * 10) # 缓存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): """保存文章,调用父类保存逻辑""" super().save(*args, **kwargs) def viewed(self): """增加文章浏览量,使用update_fields优化性能""" 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: logger.info('get article comments:{id}'.format(id=self.id)) return value else: # 获取已启用的评论并按ID降序排列 comments = self.comment_set.filter(is_enable=True).order_by('-id') cache.set(cache_key, comments, 60 * 100) # 缓存100分钟 logger.info('set article comments:{id}'.format(id=self.id)) 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) # 缓存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) # 缓存100分钟 def prev_article(self): """获取上一篇文章(按ID顺序)""" return Article.objects.filter(id__lt=self.id, status='p').first() def get_first_image_url(self): """ 从文章内容中提取第一张图片的URL 用于文章列表的缩略图显示 """ # 使用正则表达式匹配Markdown图片语法 match = re.search(r'!\[.*?\]\((.+?)\)', self.body) if match: return match.group(1) return "" class Category(BaseModel): """ 文章分类模型 用于组织和管理博客文章的类别,支持多级分类结构 """ 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) # URL友好名称 index = models.IntegerField(default=0, verbose_name=_('index')) # 分类排序索引 class Meta: ordering = ['-index'] # 按索引降序排列 verbose_name = _('category') # 单数显示名称 verbose_name_plural = verbose_name # 复数显示名称 def get_absolute_url(self): """获取分类的绝对URL地址,使用slug作为URL参数""" return reverse('blog:category_detail', kwargs={'category_name': self.slug}) def __str__(self): """对象的字符串表示""" return self.name @cache_decorator(60 * 60 * 10) # 缓存10小时 def get_category_tree(self): """ 递归获得分类目录的父级 返回从当前分类到根分类的路径,用于面包屑导航 """ categorys = [] def parse(category): categorys.append(category) if category.parent_category: parse(category.parent_category) parse(self) return categorys @cache_decorator(60 * 60 * 10) # 缓存10小时 def get_sub_categorys(self): """ 获得当前分类目录所有子集 返回所有子分类的列表 """ categorys = [] all_categorys = Category.objects.all() def parse(category): if category not in categorys: categorys.append(category) childs = all_categorys.filter(parent_category=category) for child in childs: if category not in categorys: categorys.append(child) parse(child) parse(self) return categorys class Tag(BaseModel): """文章标签模型""" name = models.CharField(_('tag name'), max_length=30, unique=True) # 标签名称,唯一 slug = models.SlugField(default='no-slug', max_length=60, blank=True) # URL友好名称 def __str__(self): return self.name def get_absolute_url(self): """获取标签的绝对URL,使用slug作为URL参数""" return reverse('blog:tag_detail', kwargs={'tag_name': self.slug}) @cache_decorator(60 * 60 * 10) # 缓存10小时 def get_article_count(self): """获取该标签下的文章数量,使用distinct去重""" return Article.objects.filter(tags__name=self.name).distinct().count() class Meta: 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) # 显示顺序,唯一 is_enable = models.BooleanField(_('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) # 最后修改时间 class Meta: ordering = ['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')) # 侧边栏内容(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'] # 按顺序升序排列 verbose_name = _('sidebar') # 单数显示名称 verbose_name_plural = verbose_name # 复数显示名称 def __str__(self): return self.name class BlogSettings(models.Model): """博客全局配置模型,使用单例模式确保只有一份配置""" # 网站基本信息 site_name = models.CharField(_('site name'), max_length=200, null=False, blank=False, default='') # 网站名称 site_description = models.TextField(_('site description'), max_length=1000, null=False, blank=False, default='') # 网站描述 site_seo_description = models.TextField(_('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) # 是否显示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) # 是否开启全站评论 comment_need_review = models.BooleanField('评论是否需要审核', default=False, null=False) # 评论是否需要审核 # 页面布局 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='') # ICP备案号 show_gongan_code = models.BooleanField('是否显示公安备案号', default=False, null=False) # 是否显示公安备案 gongan_beiancode = models.TextField('公安备案号', max_length=2000, null=True, blank=True, default='') # 公安备案号 # 统计代码 analytics_code = models.TextField("网站统计代码", max_length=1000, null=False, blank=False, default='') # 网站统计代码 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): """保存配置并清除缓存,确保配置变更立即生效""" super().save(*args, **kwargs) from djangoblog.utils import cache cache.clear() # 清除所有缓存 >>>>>>> hyt_branch