<<<<<<< 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
