models.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. import logging
  2. from django.conf import settings
  3. from django.contrib.sites.models import Site
  4. from django.core.exceptions import ValidationError
  5. from django.db import models
  6. from django.db.models.signals import post_save
  7. from django.dispatch import receiver
  8. from django.urls import reverse
  9. from django.utils.functional import cached_property
  10. from django.utils.timezone import now
  11. from django.utils.translation import gettext_lazy as _
  12. from uuslug import slugify
  13. from home import baidu
  14. from mdeditor.fields import MDTextField
  15. from website.utils import cache_decorator, cache
  16. logger = logging.getLogger(__name__)
  17. class BaseModel(models.Model):
  18. slug = models.SlugField(default='no-slug', max_length=160, blank=True)
  19. created_time = models.DateTimeField('创建时间', default=now)
  20. last_mod_time = models.DateTimeField('修改时间', default=now)
  21. def save(self, *args, **kwargs):
  22. from website.blog_signals import article_save_signal
  23. if not self.slug or self.slug == 'no-slug' or not self.id:
  24. slug = self.title if 'title' in self.__dict__ else self.name
  25. self.slug = slugify(slug)
  26. super().save(*args, **kwargs)
  27. # type = self.__class__.__name__
  28. is_update_views = 'update_fields' in kwargs and len(kwargs['update_fields']) == 1 and kwargs['update_fields'][
  29. 0] == 'views'
  30. article_save_signal.send(sender=self.__class__, is_update_views=is_update_views, id=self.id)
  31. def get_full_url(self):
  32. site = Site.objects.get_current().domain
  33. url = "http://{site}{path}".format(site=site, path=self.get_absolute_url())
  34. return url
  35. class Meta:
  36. abstract = True
  37. class Article(BaseModel):
  38. """文章"""
  39. STATUS_CHOICES = (
  40. ('d', '草稿'),
  41. ('p', '发表'),
  42. )
  43. COMMENT_STATUS = (
  44. ('o', '打开'),
  45. ('c', '关闭'),
  46. )
  47. TYPE = (
  48. ('a', '文章'),
  49. ('p', '页面'),
  50. )
  51. title = models.CharField('标题', max_length=200, unique=True)
  52. body = MDTextField('正文', null=True)
  53. pub_time = models.DateTimeField('发布时间', blank=True, null=True, default=now())
  54. status = models.CharField('文章状态', max_length=1, choices=STATUS_CHOICES, default='p')
  55. comment_status = models.CharField('评论状态', max_length=1, choices=COMMENT_STATUS, default='o')
  56. type = models.CharField('类型', max_length=1, choices=TYPE, default='a')
  57. views = models.PositiveIntegerField('浏览量', default=0)
  58. author = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name='作者', on_delete=models.CASCADE)
  59. article_order = models.IntegerField('排序,数字越大越靠前', blank=False, null=False, default=0)
  60. category = models.ForeignKey('Category', verbose_name='分类', on_delete=models.CASCADE, blank=False, null=False)
  61. tags = models.ManyToManyField('Tag', verbose_name='标签集合', blank=True)
  62. def __str__(self):
  63. return self.title
  64. class Meta:
  65. ordering = ['-article_order', '-pub_time']
  66. verbose_name = "文章"
  67. verbose_name_plural = verbose_name
  68. get_latest_by = 'created_time'
  69. def get_absolute_url(self):
  70. return reverse('blog:detailbyid', kwargs={
  71. 'article_id': self.id,
  72. 'year': self.created_time.year,
  73. 'month': self.created_time.month,
  74. 'day': self.created_time.day
  75. })
  76. @cache_decorator(60 * 60 * 10)
  77. def get_category_tree(self):
  78. tree = self.category.get_category_tree()
  79. names = list(map(lambda c: (c.name, c.get_absolute_url()), tree))
  80. return names
  81. def save(self, *args, **kwargs):
  82. if not self.slug or self.slug == 'no-slug' or not self.id:
  83. # Only set the slug when the object is created.
  84. self.slug = slugify(self.title)
  85. super().save(*args, **kwargs)
  86. def viewed(self):
  87. self.views += 1
  88. self.save(update_fields=['views'])
  89. def links_list(self):
  90. cache_key = 'article_links_{id}'.format(id=self.id)
  91. value = cache.get(cache_key)
  92. if value:
  93. logger.info('get article links:{id}'.format(id=self.id))
  94. return value
  95. else:
  96. links = self.links_set.filter(is_enable=True)
  97. cache.set(cache_key, links)
  98. logger.info('set article links:{id}'.format(id=self.id))
  99. return links
  100. def files_list(self):
  101. cache_key = 'article_files_{id}'.format(id=self.id)
  102. value = cache.get(cache_key)
  103. if value:
  104. logger.info('get article files:{id}'.format(id=self.id))
  105. return value
  106. else:
  107. files = self.files_set.filter(is_enable=True)
  108. cache.set(cache_key, files)
  109. logger.info('set article files:{id}'.format(id=self.id))
  110. return files
  111. def comment_list(self):
  112. cache_key = 'article_comments_{id}'.format(id=self.id)
  113. value = cache.get(cache_key)
  114. if value:
  115. logger.info('get article comments:{id}'.format(id=self.id))
  116. return value
  117. else:
  118. comments = self.comment_set.filter(is_enable=True)
  119. cache.set(cache_key, comments)
  120. logger.info('set article comments:{id}'.format(id=self.id))
  121. return comments
  122. def get_admin_url(self):
  123. info = (self._meta.app_label, self._meta.model_name)
  124. return reverse('admin:%s_%s_change' % info, args=(self.pk,))
  125. @cached_property
  126. def next_article(self):
  127. # 下一篇
  128. return Article.objects.filter(id__gt=self.id, status='p').order_by('id').first()
  129. @cached_property
  130. def prev_article(self):
  131. # 前一篇
  132. return Article.objects.filter(id__lt=self.id, status='p').first()
  133. class Category(BaseModel):
  134. """文章分类"""
  135. name = models.CharField('分类名', max_length=30, unique=True)
  136. parent_category = models.ForeignKey('self', verbose_name="父级分类", blank=True, null=True, on_delete=models.CASCADE)
  137. class Meta:
  138. ordering = ['name']
  139. verbose_name = "分类"
  140. verbose_name_plural = verbose_name
  141. def get_absolute_url(self):
  142. return reverse('blog:category_detail', kwargs={'category_name': self.slug})
  143. def __str__(self):
  144. return self.name
  145. @cache_decorator(60 * 60 * 10)
  146. def get_category_tree(self):
  147. """
  148. 递归获得分类目录的父级
  149. :return:
  150. """
  151. categorys = []
  152. def parse(category):
  153. categorys.append(category)
  154. if category.parent_category:
  155. parse(category.parent_category)
  156. parse(self)
  157. return categorys
  158. @cache_decorator(60 * 60 * 10)
  159. def get_sub_categorys(self):
  160. """
  161. 获得当前分类目录所有子集
  162. :return:
  163. """
  164. categorys = []
  165. all_categorys = Category.objects.all()
  166. def parse(category):
  167. if category not in categorys:
  168. categorys.append(category)
  169. childs = all_categorys.filter(parent_category=category)
  170. for child in childs:
  171. if category not in categorys:
  172. categorys.append(child)
  173. parse(child)
  174. parse(self)
  175. return categorys
  176. class Tag(BaseModel):
  177. """文章标签"""
  178. name = models.CharField('标签名', max_length=30, unique=True)
  179. def __str__(self):
  180. return self.name
  181. def get_absolute_url(self):
  182. return reverse('blog:tag_detail', kwargs={'tag_name': self.slug})
  183. @cache_decorator(60 * 60 * 10)
  184. def get_article_count(self):
  185. return Article.objects.filter(tags__name=self.name).distinct().count()
  186. class Meta:
  187. ordering = ['name']
  188. verbose_name = "标签"
  189. verbose_name_plural = verbose_name
  190. class Links(models.Model):
  191. """友情链接"""
  192. # TYPE = (
  193. # ('g', '全局链接'),
  194. # ('p', '文章链接'),
  195. # )
  196. name = models.CharField('链接名称', max_length=30, unique=True)
  197. link = models.URLField('链接地址')
  198. sequence = models.IntegerField('排序')
  199. created_time = models.DateTimeField('创建时间', default=now)
  200. last_mod_time = models.DateTimeField('修改时间', default=now)
  201. # type = models.CharField('类型', max_length=1, choices=TYPE, default='g')
  202. is_enable = models.BooleanField('是否启用', default=True)
  203. article = models.ForeignKey(Article, on_delete=models.DO_NOTHING, null=True, default=None)
  204. #
  205. # def save(self, force_insert=False, force_update=False, using=None,
  206. # update_fields=None):
  207. # if self.article is None:
  208. # self.type = 'g'
  209. # else:
  210. # self.type = 'p'
  211. # return super(Links, self).save(force_insert, force_update, using, update_fields)
  212. class Meta:
  213. ordering = ['sequence']
  214. verbose_name = '友情链接'
  215. verbose_name_plural = verbose_name
  216. def __str__(self):
  217. return self.name
  218. class SideBar(models.Model):
  219. """侧边栏,可以展示一些html内容"""
  220. name = models.CharField('标题', max_length=100)
  221. content = MDTextField("内容")
  222. sequence = models.IntegerField('排序', unique=True)
  223. is_enable = models.BooleanField('是否启用', default=True)
  224. created_time = models.DateTimeField('创建时间', default=now)
  225. last_mod_time = models.DateTimeField('修改时间', default=now)
  226. class Meta:
  227. ordering = ['sequence']
  228. verbose_name = '侧边栏'
  229. verbose_name_plural = verbose_name
  230. def __str__(self):
  231. return self.name
  232. class BlogSettings(models.Model):
  233. '''站点设置 '''
  234. sitename = models.CharField("网站名称", max_length=200, null=False, blank=False, default='')
  235. site_description = models.TextField("网站描述", max_length=1000, null=False, blank=False, default='')
  236. site_seo_description = models.TextField("网站SEO描述", max_length=1000, null=False, blank=False, default='')
  237. site_keywords = models.TextField("网站关键字", max_length=1000, null=False, blank=False, default='')
  238. article_sub_length = models.IntegerField("文章摘要长度", default=300)
  239. sidebar_article_count = models.IntegerField("侧边栏文章数目", default=10)
  240. sidebar_comment_count = models.IntegerField("侧边栏评论数目", default=5)
  241. show_google_adsense = models.BooleanField('是否显示谷歌广告', default=False)
  242. google_adsense_codes = models.TextField('广告内容', max_length=2000, null=True, blank=True, default='')
  243. open_site_comment = models.BooleanField('是否打开网站评论功能', default=True)
  244. beiancode = models.CharField('备案号', max_length=2000, null=True, blank=True, default='')
  245. show_gongan_code = models.BooleanField('是否显示公安备案号', default=False, null=False)
  246. gongan_beiancode = models.TextField('公安备案号', max_length=2000, null=True, blank=True, default='')
  247. resource_path = models.CharField("静态文件保存地址", max_length=300, null=False, default='/var/www/resource/')
  248. class Meta:
  249. verbose_name = '网站配置'
  250. verbose_name_plural = verbose_name
  251. def __str__(self):
  252. return self.sitename
  253. def clean(self):
  254. if BlogSettings.objects.exclude(id=self.id).count():
  255. raise ValidationError(_('只能有一个配置'))
  256. def save(self, *args, **kwargs):
  257. super().save(*args, **kwargs)
  258. from website.utils import cache
  259. cache.clear()
  260. class Files(models.Model):
  261. '''
  262. 文章、博客的附件:图片、pdf、word、excel、html、zip等
  263. '''
  264. file = models.FileField(upload_to="upload/blog/article/%Y/%m/%d", verbose_name='附件')
  265. name = models.CharField(max_length=50, null=True, verbose_name='附件名称', default=file.name)
  266. sequence = models.IntegerField('排序', unique=True, null=True)
  267. created_time = models.DateTimeField('创建时间', default=now)
  268. last_mod_time = models.DateTimeField('修改时间', default=now)
  269. is_enable = models.BooleanField('是否启用', default=True)
  270. article = models.ForeignKey(Article, on_delete=models.CASCADE)
  271. class Meta:
  272. verbose_name = '博客附件'
  273. verbose_name_plural = verbose_name
  274. # @receiver([post_save], sender=Article)
  275. # @receiver([post_save], sender=Category)
  276. # @receiver([post_save], sender=Tag)
  277. # def save_handler(sender, instance, created, **kwargs):
  278. # url = instance.get_full_url()
  279. # bd_type = baidu.EnumBaiDu.create if created else baidu.EnumBaiDu.update
  280. # baidu.push_url2baidu(url, bd_type)
  281. #
  282. #
  283. # @receiver([post_save], sender=Article)
  284. # @receiver([post_save], sender=Category)
  285. # @receiver([post_save], sender=Tag)
  286. # def delete_handler(sender, instance, **kwargs):
  287. # url = instance.get_full_url()
  288. # baidu.push_url2baidu(url, baidu.EnumBaiDu.delete)