You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
367 lines
11 KiB
367 lines
11 KiB
import logging
|
|
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', '首页')
|
|
L = ('l', '列表页')
|
|
P = ('p', '文章页面')
|
|
A = ('a', '全站')
|
|
S = ('s', '友情链接页面')
|
|
|
|
|
|
class BaseModel(models.Model):
|
|
id = models.AutoField(primary_key=True)
|
|
created_time = models.DateTimeField('创建时间', default=now)
|
|
last_mod_time = models.DateTimeField('修改时间', 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):
|
|
"""文章"""
|
|
STATUS_CHOICES = (
|
|
('d', '草稿'),
|
|
('p', '发表'),
|
|
)
|
|
COMMENT_STATUS = (
|
|
('o', '打开'),
|
|
('c', '关闭'),
|
|
)
|
|
TYPE = (
|
|
('a', '文章'),
|
|
('p', '页面'),
|
|
)
|
|
title = models.CharField('标题', max_length=200, unique=True)
|
|
body = MDTextField('正文')
|
|
pub_time = models.DateTimeField(
|
|
'发布时间', blank=False, null=False, default=now)
|
|
status = models.CharField(
|
|
'文章状态',
|
|
max_length=1,
|
|
choices=STATUS_CHOICES,
|
|
default='p')
|
|
comment_status = models.CharField(
|
|
'评论状态',
|
|
max_length=1,
|
|
choices=COMMENT_STATUS,
|
|
default='o')
|
|
type = models.CharField('类型', max_length=1, choices=TYPE, default='a')
|
|
views = models.PositiveIntegerField('浏览量', default=0)
|
|
author = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
verbose_name='作者',
|
|
blank=False,
|
|
null=False,
|
|
on_delete=models.CASCADE)
|
|
article_order = models.IntegerField(
|
|
'排序,数字越大越靠前', blank=False, null=False, default=0)
|
|
show_toc = models.BooleanField("是否显示toc目录", blank=False, null=False, default=False)
|
|
category = models.ForeignKey(
|
|
'Category',
|
|
verbose_name='分类',
|
|
on_delete=models.CASCADE,
|
|
blank=False,
|
|
null=False)
|
|
tags = models.ManyToManyField('Tag', verbose_name='标签集合', 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 = "文章"
|
|
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.created_time.year,
|
|
'month': self.created_time.month,
|
|
'day': self.created_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()
|
|
|
|
|
|
class Category(BaseModel):
|
|
"""文章分类"""
|
|
name = models.CharField('分类名', max_length=30, unique=True)
|
|
parent_category = models.ForeignKey(
|
|
'self',
|
|
verbose_name="父级分类",
|
|
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="权重排序-越大越靠前")
|
|
|
|
class Meta:
|
|
ordering = ['-index']
|
|
verbose_name = "分类"
|
|
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('标签名', 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 = "标签"
|
|
verbose_name_plural = verbose_name
|
|
|
|
|
|
class Links(models.Model):
|
|
"""友情链接"""
|
|
|
|
name = models.CharField('链接名称', max_length=30, unique=True)
|
|
link = models.URLField('链接地址')
|
|
sequence = models.IntegerField('排序', unique=True)
|
|
is_enable = models.BooleanField(
|
|
'是否显示', default=True, blank=False, null=False)
|
|
show_type = models.CharField(
|
|
'显示类型',
|
|
max_length=1,
|
|
choices=LinkShowType.choices,
|
|
default=LinkShowType.I)
|
|
created_time = models.DateTimeField('创建时间', default=now)
|
|
last_mod_time = models.DateTimeField('修改时间', default=now)
|
|
|
|
class Meta:
|
|
ordering = ['sequence']
|
|
verbose_name = '友情链接'
|
|
verbose_name_plural = verbose_name
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
|
|
class SideBar(models.Model):
|
|
"""侧边栏,可以展示一些html内容"""
|
|
name = models.CharField('标题', max_length=100)
|
|
content = models.TextField("内容")
|
|
sequence = models.IntegerField('排序', unique=True)
|
|
is_enable = models.BooleanField('是否启用', default=True)
|
|
created_time = models.DateTimeField('创建时间', default=now)
|
|
last_mod_time = models.DateTimeField('修改时间', default=now)
|
|
|
|
class Meta:
|
|
ordering = ['sequence']
|
|
verbose_name = '侧边栏'
|
|
verbose_name_plural = verbose_name
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
|
|
class BlogSettings(models.Model):
|
|
"""blog的配置"""
|
|
sitename = models.CharField(
|
|
"网站名称",
|
|
max_length=200,
|
|
null=False,
|
|
blank=False,
|
|
default='')
|
|
site_description = models.TextField(
|
|
"网站描述",
|
|
max_length=1000,
|
|
null=False,
|
|
blank=False,
|
|
default='')
|
|
site_seo_description = models.TextField(
|
|
"网站SEO描述", max_length=1000, null=False, blank=False, default='')
|
|
site_keywords = models.TextField(
|
|
"网站关键字",
|
|
max_length=1000,
|
|
null=False,
|
|
blank=False,
|
|
default='')
|
|
article_sub_length = models.IntegerField("文章摘要长度", default=300)
|
|
sidebar_article_count = models.IntegerField("侧边栏文章数目", default=10)
|
|
sidebar_comment_count = models.IntegerField("侧边栏评论数目", default=5)
|
|
article_comment_count = models.IntegerField("文章评论数目", default=5)
|
|
show_google_adsense = models.BooleanField('是否显示谷歌广告', default=False)
|
|
google_adsense_codes = models.TextField(
|
|
'广告内容', max_length=2000, null=True, blank=True, default='')
|
|
open_site_comment = models.BooleanField('是否打开网站评论功能', default=True)
|
|
beiancode = models.CharField(
|
|
'备案号',
|
|
max_length=2000,
|
|
null=True,
|
|
blank=True,
|
|
default='')
|
|
analyticscode = models.TextField(
|
|
"网站统计代码",
|
|
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='')
|
|
resource_path = models.CharField(
|
|
"静态文件保存地址",
|
|
max_length=300,
|
|
null=False,
|
|
default='/var/www/resource/')
|
|
|
|
class Meta:
|
|
verbose_name = '网站配置'
|
|
verbose_name_plural = verbose_name
|
|
|
|
def __str__(self):
|
|
return self.sitename
|
|
|
|
def clean(self):
|
|
if BlogSettings.objects.exclude(id=self.id).count():
|
|
raise ValidationError(_('只能有一个配置'))
|
|
|
|
def save(self, *args, **kwargs):
|
|
super().save(*args, **kwargs)
|
|
from djangoblog.utils import cache
|
|
cache.clear()
|