Merge branch 'master' of https://bdgit.educoder.net/pla2kwvs6/Django
commit
21b7b9bebf
@ -1,13 +0,0 @@
|
||||
R
|
||||
^R
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
:wq
|
||||
|
||||
x
|
||||
|
||||
|
||||
Binary file not shown.
@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class BlogConfig(AppConfig):
|
||||
name = 'blog'
|
||||
@ -0,0 +1,43 @@
|
||||
import logging
|
||||
|
||||
from django.utils import timezone
|
||||
|
||||
from djangoblog.utils import cache, get_blog_setting
|
||||
from .models import Category, Article
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def seo_processor(requests):
|
||||
key = 'seo_processor'
|
||||
value = cache.get(key)
|
||||
if value:
|
||||
return value
|
||||
else:
|
||||
logger.info('set processor cache.')
|
||||
setting = get_blog_setting()
|
||||
value = {
|
||||
'SITE_NAME': setting.site_name,
|
||||
'SHOW_GOOGLE_ADSENSE': setting.show_google_adsense,
|
||||
'GOOGLE_ADSENSE_CODES': setting.google_adsense_codes,
|
||||
'SITE_SEO_DESCRIPTION': setting.site_seo_description,
|
||||
'SITE_DESCRIPTION': setting.site_description,
|
||||
'SITE_KEYWORDS': setting.site_keywords,
|
||||
'SITE_BASE_URL': requests.scheme + '://' + requests.get_host() + '/',
|
||||
'ARTICLE_SUB_LENGTH': setting.article_sub_length,
|
||||
'nav_category_list': Category.objects.all(),
|
||||
'nav_pages': Article.objects.filter(
|
||||
type='p',
|
||||
status='p'),
|
||||
'OPEN_SITE_COMMENT': setting.open_site_comment,
|
||||
'BEIAN_CODE': setting.beian_code,
|
||||
'ANALYTICS_CODE': setting.analytics_code,
|
||||
"BEIAN_CODE_GONGAN": setting.gongan_beiancode,
|
||||
"SHOW_GONGAN_CODE": setting.show_gongan_code,
|
||||
"CURRENT_YEAR": timezone.now().year,
|
||||
"GLOBAL_HEADER": setting.global_header,
|
||||
"GLOBAL_FOOTER": setting.global_footer,
|
||||
"COMMENT_NEED_REVIEW": setting.comment_need_review,
|
||||
}
|
||||
cache.set(key, value, 60 * 60 * 10)
|
||||
return value
|
||||
@ -0,0 +1,19 @@
|
||||
import logging
|
||||
|
||||
from django import forms
|
||||
from haystack.forms import SearchForm
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BlogSearchForm(SearchForm):
|
||||
querydata = forms.CharField(required=True)
|
||||
|
||||
def search(self):
|
||||
datas = super(BlogSearchForm, self).search()
|
||||
if not self.is_valid():
|
||||
return self.no_query_found()
|
||||
|
||||
if self.cleaned_data['querydata']:
|
||||
logger.info(self.cleaned_data['querydata'])
|
||||
return datas
|
||||
@ -0,0 +1,42 @@
|
||||
import logging
|
||||
import time
|
||||
|
||||
from ipware import get_client_ip
|
||||
from user_agents import parse
|
||||
|
||||
from blog.documents import ELASTICSEARCH_ENABLED, ElaspedTimeDocumentManager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OnlineMiddleware(object):
|
||||
def __init__(self, get_response=None):
|
||||
self.get_response = get_response
|
||||
super().__init__()
|
||||
|
||||
def __call__(self, request):
|
||||
''' page render time '''
|
||||
start_time = time.time()
|
||||
response = self.get_response(request)
|
||||
http_user_agent = request.META.get('HTTP_USER_AGENT', '')
|
||||
ip, _ = get_client_ip(request)
|
||||
user_agent = parse(http_user_agent)
|
||||
if not response.streaming:
|
||||
try:
|
||||
cast_time = time.time() - start_time
|
||||
if ELASTICSEARCH_ENABLED:
|
||||
time_taken = round((cast_time) * 1000, 2)
|
||||
url = request.path
|
||||
from django.utils import timezone
|
||||
ElaspedTimeDocumentManager.create(
|
||||
url=url,
|
||||
time_taken=time_taken,
|
||||
log_datetime=timezone.now(),
|
||||
useragent=user_agent,
|
||||
ip=ip)
|
||||
response.content = response.content.replace(
|
||||
b'<!!LOAD_TIMES!!>', str.encode(str(cast_time)[:5]))
|
||||
except Exception as e:
|
||||
logger.error("Error OnlineMiddleware: %s" % e)
|
||||
|
||||
return response
|
||||
@ -0,0 +1,376 @@
|
||||
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):
|
||||
"""文章"""
|
||||
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'))
|
||||
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):
|
||||
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):
|
||||
"""侧边栏,可以展示一些html内容"""
|
||||
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(
|
||||
"网站统计代码",
|
||||
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()
|
||||
@ -0,0 +1,13 @@
|
||||
from haystack import indexes
|
||||
|
||||
from blog.models import Article
|
||||
|
||||
|
||||
class ArticleIndex(indexes.SearchIndex, indexes.Indexable):
|
||||
text = indexes.CharField(document=True, use_template=True)
|
||||
|
||||
def get_model(self):
|
||||
return Article
|
||||
|
||||
def index_queryset(self, using=None):
|
||||
return self.get_model().objects.filter(status='p')
|
||||
|
Before Width: | Height: | Size: 221 B After Width: | Height: | Size: 221 B |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue