diff --git a/models(1).py b/models(1).py new file mode 100644 index 0000000..93a72df --- /dev/null +++ b/models(1).py @@ -0,0 +1,391 @@ +import logging +import re +from abc import abstractmethod + +from django.conf import settings +from django.core.exceptions import ValidationError +from django.db import models +from django.urls import reverse +from django.utils.timezone import now +from django.utils.translation import gettext_lazy as _ +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): + 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: + 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): + site = get_current_site().domain + url = "https://{site}{path}".format(site=site, + path=self.get_absolute_url()) + return url + + class Meta: + abstract = True + + @abstractmethod + def get_absolute_url(self): + pass + + +class Article(BaseModel): + """鏂囩珷""" + #文章状态:草稿(d)、已发布(p),默认已发布 + STATUS_CHOICES = ( + ('d', _('Draft')), + ('p', _('Published')), + ) + # 评论状态:开放(o)、关闭(c),默认开放 + COMMENT_STATUS = ( + ('o', _('Open')), + ('c', _('Close')), + ) + # 文章类型:普通文章(a)、独立页面(p,如关于页),默认普通文章 + TYPE = ( + ('a', _('Article')), + ('p', _('Page')), + ) + # 文章标题:最大长度200,唯一不重复 + title = models.CharField(_('title'), max_length=200, unique=True) + # 文章内容:markdown富文本字段,支持排版格式 + body = MDTextField(_('body')) + # 发布时间:非空,默认当前时间 + pub_time = models.DateTimeField( + _('publish time'), blank=False, null=False, default=now) + # 文章状态:关联STATUS_CHOICES,存储单字符标识 + status = models.CharField( + _('status'), + max_length=1, + choices=STATUS_CHOICES, + default='p') + # 评论状态:关联COMMENT_STATUS,存储单字符标识 + comment_status = models.CharField( + _('comment status'), + max_length=1, + choices=COMMENT_STATUS, + default='o') + # 文章类型:关联TYPE,存储单字符标识 + type = models.CharField(_('type'), max_length=1, choices=TYPE, default='a') + # 阅读量:正整数,默认0,记录文章被查看次数 + views = models.PositiveIntegerField(_('views'), default=0) + # 作者:关联Django用户模型,级联删除(作者删除时文章也删除),非空 + author = models.ForeignKey( + settings.AUTH_USER_MODEL, + verbose_name=_('author'), + blank=False, + null=False, + on_delete=models.CASCADE) + # 文章排序:整数,默认0,值越大在列表中越靠前 + article_order = models.IntegerField( + _('order'), blank=False, null=False, default=0) + # 是否显示目录:布尔值,默认不显示,控制详情页是否展示目录 + show_toc = models.BooleanField(_('show toc'), blank=False, null=False, default=False) + # 文章分类:关联Category模型,级联删除,非空(文章必须归属一个分类) + category = models.ForeignKey( + 'Category', + verbose_name=_('category'), + on_delete=models.CASCADE, + blank=False, + null=False) + # 文章标签:多对多关联Tag模型,允许为空(文章可无标签) + 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): + 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) + 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): + 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: + comments = self.comment_set.filter(is_enable=True).order_by('-id') + cache.set(cache_key, comments, 60 * 100) + logger.info('set article comments:{id}'.format(id=self.id)) + return comments + + def get_admin_url(self): + 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): + # 涓嬩竴绡? + return Article.objects.filter( + id__gt=self.id, status='p').order_by('id').first() + + @cache_decorator(expiration=60 * 100) + def prev_article(self): + # 鍓嶄竴绡? + 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: + """ + 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) + 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): + 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 = [] + + def parse(category): + categorys.append(category) + if category.parent_category: + parse(category.parent_category) + + parse(self) + return categorys + + @cache_decorator(60 * 60 * 10) + def get_sub_categorys(self): + """ + 鑾峰緱褰撳墠鍒嗙被鐩綍鎵鏈夊瓙闆? + :return: + """ + 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) + + def __str__(self): + return self.name + + def get_absolute_url(self): + 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'] + 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): + """渚ц竟鏍?,鍙互灞曠ず涓浜沨tml鍐呭""" + 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) + + class Meta: + ordering = ['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='') + 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='') + 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_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='') + beian_code = models.CharField( + '澶囨鍙?', + max_length=2000, + null=True, + blank=True, + default='') + analytics_code = models.TextField( + "缃戠珯缁熻浠g爜", + max_length=1000, + null=False, + blank=False, + default='') + show_gongan_code = models.BooleanField( + '鏄惁鏄剧ず鍏畨澶囨鍙?', default=False, null=False) + gongan_beiancode = models.TextField( + '鍏畨澶囨鍙?', + max_length=2000, + null=True, + blank=True, + default='') + comment_need_review = models.BooleanField( + '璇勮鏄惁闇瑕佸鏍?', 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): + super().save(*args, **kwargs) + from djangoblog.utils import cache + cache.clear()