许家辉给blog添加注释

master
杨智鑫2315304452 4 months ago
parent ec87019888
commit cece26dbac

@ -17,18 +17,22 @@ class ArticleForm(forms.ModelForm):
fields = '__all__'
#xjh管理员动作函数 - 发布选中的文章
def makr_article_publish(modeladmin, request, queryset):
queryset.update(status='p')
#xjh管理员动作函数 - 将选中的文章设为草稿
def draft_article(modeladmin, request, queryset):
queryset.update(status='d')
#xjh管理员动作函数 - 关闭文章评论
def close_article_commentstatus(modeladmin, request, queryset):
queryset.update(comment_status='c')
#xjh管理员动作函数 - 打开文章评论
def open_article_commentstatus(modeladmin, request, queryset):
queryset.update(comment_status='o')
@ -40,6 +44,7 @@ open_article_commentstatus.short_description = _('Open article comments')
class ArticlelAdmin(admin.ModelAdmin):
"""xjh文章模型的后台管理配置"""
list_per_page = 20
search_fields = ('body', 'title')
form = ArticleForm
@ -65,6 +70,7 @@ class ArticlelAdmin(admin.ModelAdmin):
open_article_commentstatus]
def link_to_category(self, obj):
"""xjh在文章列表显示分类链接"""
info = (obj.category._meta.app_label, obj.category._meta.model_name)
link = reverse('admin:%s_%s_change' % info, args=(obj.category.id,))
return format_html(u'<a href="%s">%s</a>' % (link, obj.category.name))
@ -72,15 +78,18 @@ class ArticlelAdmin(admin.ModelAdmin):
link_to_category.short_description = _('category')
def get_form(self, request, obj=None, **kwargs):
"""xjh限制作者字段只能选择超级用户"""
form = super(ArticlelAdmin, self).get_form(request, obj, **kwargs)
form.base_fields['author'].queryset = get_user_model(
).objects.filter(is_superuser=True)
return form
def save_model(self, request, obj, form, change):
"""xjh保存文章模型"""
super(ArticlelAdmin, self).save_model(request, obj, form, change)
def get_view_on_site_url(self, obj=None):
"""xjh获取文章在前台的URL"""
if obj:
url = obj.get_full_url()
return url
@ -91,22 +100,27 @@ class ArticlelAdmin(admin.ModelAdmin):
class TagAdmin(admin.ModelAdmin):
"""xjh标签模型的后台管理配置"""
exclude = ('slug', 'last_mod_time', 'creation_time')
class CategoryAdmin(admin.ModelAdmin):
"""xjh分类模型的后台管理配置"""
list_display = ('name', 'parent_category', 'index')
exclude = ('slug', 'last_mod_time', 'creation_time')
class LinksAdmin(admin.ModelAdmin):
"""xjh友情链接模型的后台管理配置"""
exclude = ('last_mod_time', 'creation_time')
class SideBarAdmin(admin.ModelAdmin):
"""xjh侧边栏模型的后台管理配置"""
list_display = ('name', 'content', 'is_enable', 'sequence')
exclude = ('last_mod_time', 'creation_time')
class BlogSettingsAdmin(admin.ModelAdmin):
pass
"""xjh博客设置模型的后台管理配置"""
pass

@ -2,4 +2,5 @@ from django.apps import AppConfig
class BlogConfig(AppConfig):
name = 'blog'
"""xjh博客应用的配置类"""
name = 'blog'

@ -9,6 +9,7 @@ logger = logging.getLogger(__name__)
def seo_processor(requests):
"""xjhSEO上下文处理器为模板提供SEO相关变量"""
key = 'seo_processor'
value = cache.get(key)
if value:
@ -16,6 +17,7 @@ def seo_processor(requests):
else:
logger.info('set processor cache.')
setting = get_blog_setting()
#xjh构建包含SEO和网站配置的上下文字典
value = {
'SITE_NAME': setting.site_name,
'SHOW_GOOGLE_ADSENSE': setting.show_google_adsense,
@ -39,5 +41,6 @@ def seo_processor(requests):
"GLOBAL_FOOTER": setting.global_footer,
"COMMENT_NEED_REVIEW": setting.comment_need_review,
}
#xjh缓存上下文数据10小时
cache.set(key, value, 60 * 60 * 10)
return value
return value

@ -7,9 +7,11 @@ from elasticsearch_dsl.connections import connections
from blog.models import Article
#xjh检查是否启用了Elasticsearch
ELASTICSEARCH_ENABLED = hasattr(settings, 'ELASTICSEARCH_DSL')
if ELASTICSEARCH_ENABLED:
#xjh创建Elasticsearch连接
connections.create_connection(
hosts=[settings.ELASTICSEARCH_DSL['default']['hosts']])
from elasticsearch import Elasticsearch
@ -21,6 +23,7 @@ if ELASTICSEARCH_ENABLED:
try:
c.get_pipeline('geoip')
except elasticsearch.exceptions.NotFoundError:
#xjh创建geoip处理管道用于IP地址地理位置解析
c.put_pipeline('geoip', body='''{
"description" : "Add geoip info",
"processors" : [
@ -34,6 +37,7 @@ if ELASTICSEARCH_ENABLED:
class GeoIp(InnerDoc):
"""xjhIP地理位置信息文档结构"""
continent_name = Keyword()
country_iso_code = Keyword()
country_name = Keyword()
@ -41,21 +45,25 @@ class GeoIp(InnerDoc):
class UserAgentBrowser(InnerDoc):
"""xjh用户代理浏览器信息文档结构"""
Family = Keyword()
Version = Keyword()
class UserAgentOS(UserAgentBrowser):
"""xjh用户代理操作系统信息文档结构"""
pass
class UserAgentDevice(InnerDoc):
"""xjh用户代理设备信息文档结构"""
Family = Keyword()
Brand = Keyword()
Model = Keyword()
class UserAgent(InnerDoc):
"""xjh完整的用户代理信息文档结构"""
browser = Object(UserAgentBrowser, required=False)
os = Object(UserAgentOS, required=False)
device = Object(UserAgentDevice, required=False)
@ -64,6 +72,7 @@ class UserAgent(InnerDoc):
class ElapsedTimeDocument(Document):
"""xjh页面加载耗时记录文档"""
url = Keyword()
time_taken = Long()
log_datetime = Date()
@ -83,8 +92,10 @@ class ElapsedTimeDocument(Document):
class ElaspedTimeDocumentManager:
"""xjh耗时文档管理器"""
@staticmethod
def build_index():
"""xjh构建性能索引"""
from elasticsearch import Elasticsearch
client = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts'])
res = client.indices.exists(index="performance")
@ -93,12 +104,14 @@ class ElaspedTimeDocumentManager:
@staticmethod
def delete_index():
"""xjh删除性能索引"""
from elasticsearch import Elasticsearch
es = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts'])
es.indices.delete(index='performance', ignore=[400, 404])
@staticmethod
def create(url, time_taken, log_datetime, useragent, ip):
"""xjh创建耗时记录"""
ElaspedTimeDocumentManager.build_index()
ua = UserAgent()
ua.browser = UserAgentBrowser()
@ -116,6 +129,7 @@ class ElaspedTimeDocumentManager:
ua.string = useragent.ua_string
ua.is_bot = useragent.is_bot
#xjh使用当前时间戳作为文档ID
doc = ElapsedTimeDocument(
meta={
'id': int(
@ -127,10 +141,11 @@ class ElaspedTimeDocumentManager:
time_taken=time_taken,
log_datetime=log_datetime,
useragent=ua, ip=ip)
doc.save(pipeline="geoip")
doc.save(pipeline="geoip") #xjh保存文档并通过geoip管道处理
class ArticleDocument(Document):
"""xjh文章搜索文档结构"""
body = Text(analyzer='ik_max_word', search_analyzer='ik_smart')
title = Text(analyzer='ik_max_word', search_analyzer='ik_smart')
author = Object(properties={
@ -165,19 +180,23 @@ class ArticleDocument(Document):
class ArticleDocumentManager():
"""xjh文章文档管理器"""
def __init__(self):
self.create_index()
def create_index(self):
"""xjh创建文章索引"""
ArticleDocument.init()
def delete_index(self):
"""xjh删除文章索引"""
from elasticsearch import Elasticsearch
es = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts'])
es.indices.delete(index='blog', ignore=[400, 404])
def convert_to_doc(self, articles):
"""xjh将文章对象转换为搜索文档"""
return [
ArticleDocument(
meta={
@ -202,6 +221,7 @@ class ArticleDocumentManager():
article_order=article.article_order) for article in articles]
def rebuild(self, articles=None):
"""xjh重建文章索引"""
ArticleDocument.init()
articles = articles if articles else Article.objects.all()
docs = self.convert_to_doc(articles)
@ -209,5 +229,6 @@ class ArticleDocumentManager():
doc.save()
def update_docs(self, docs):
"""xjh更新文档"""
for doc in docs:
doc.save()
doc.save()

@ -7,13 +7,15 @@ logger = logging.getLogger(__name__)
class BlogSearchForm(SearchForm):
"""xjh博客搜索表单"""
querydata = forms.CharField(required=True)
def search(self):
"""xjh执行搜索"""
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
return datas

@ -10,14 +10,16 @@ logger = logging.getLogger(__name__)
class OnlineMiddleware(object):
"""xjh在线中间件用于记录页面加载时间和用户访问信息"""
def __init__(self, get_response=None):
self.get_response = get_response
super().__init__()
def __call__(self, request):
''' page render time '''
''' xjh页面渲染时间统计 '''
start_time = time.time()
response = self.get_response(request)
#xjh获取用户IP和User-Agent信息
http_user_agent = request.META.get('HTTP_USER_AGENT', '')
ip, _ = get_client_ip(request)
user_agent = parse(http_user_agent)
@ -25,6 +27,7 @@ class OnlineMiddleware(object):
try:
cast_time = time.time() - start_time
if ELASTICSEARCH_ENABLED:
#xjh记录页面加载耗时到Elasticsearch
time_taken = round((cast_time) * 1000, 2)
url = request.path
from django.utils import timezone
@ -34,9 +37,10 @@ class OnlineMiddleware(object):
log_datetime=timezone.now(),
useragent=user_agent,
ip=ip)
#xjh在页面内容中替换加载时间占位符
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
return response

@ -18,6 +18,7 @@ logger = logging.getLogger(__name__)
class LinkShowType(models.TextChoices):
"""xjh链接显示类型选择"""
I = ('i', _('index'))
L = ('l', _('list'))
P = ('p', _('post'))
@ -26,17 +27,20 @@ class LinkShowType(models.TextChoices):
class BaseModel(models.Model):
"""xjh基础模型类包含公共字段"""
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):
#xjh特殊处理文章浏览量更新
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:
#xjh自动生成slug字段
if 'slug' in self.__dict__:
slug = getattr(
self, 'title') if 'title' in self.__dict__ else getattr(
@ -45,6 +49,7 @@ class BaseModel(models.Model):
super().save(*args, **kwargs)
def get_full_url(self):
"""xjh获取完整URL"""
site = get_current_site().domain
url = "https://{site}{path}".format(site=site,
path=self.get_absolute_url())
@ -55,11 +60,12 @@ class BaseModel(models.Model):
@abstractmethod
def get_absolute_url(self):
"""xjh抽象方法获取绝对URL"""
pass
class Article(BaseModel):
"""文章"""
"""xjh文章模型"""
STATUS_CHOICES = (
('d', _('Draft')),
('p', _('Published')),
@ -118,6 +124,7 @@ class Article(BaseModel):
get_latest_by = 'id'
def get_absolute_url(self):
"""xjh获取文章绝对URL"""
return reverse('blog:detailbyid', kwargs={
'article_id': self.id,
'year': self.creation_time.year,
@ -127,6 +134,7 @@ class Article(BaseModel):
@cache_decorator(60 * 60 * 10)
def get_category_tree(self):
"""xjh获取分类树"""
tree = self.category.get_category_tree()
names = list(map(lambda c: (c.name, c.get_absolute_url()), tree))
@ -136,10 +144,12 @@ class Article(BaseModel):
super().save(*args, **kwargs)
def viewed(self):
"""xjh增加文章浏览量"""
self.views += 1
self.save(update_fields=['views'])
def comment_list(self):
"""xjh获取文章评论列表"""
cache_key = 'article_comments_{id}'.format(id=self.id)
value = cache.get(cache_key)
if value:
@ -152,23 +162,24 @@ class Article(BaseModel):
return comments
def get_admin_url(self):
"""xjh获取文章管理后台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)
def next_article(self):
# 下一篇
"""xjh获取下一篇文章"""
return Article.objects.filter(
id__gt=self.id, status='p').order_by('id').first()
@cache_decorator(expiration=60 * 100)
def prev_article(self):
# 前一篇
"""xjh获取上一篇文章"""
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.
xjh从文章内容中提取第一张图片URL
:return:
"""
match = re.search(r'!\[.*?\]\((.+?)\)', self.body)
@ -178,7 +189,7 @@ class Article(BaseModel):
class Category(BaseModel):
"""文章分类"""
"""xjh文章分类模型"""
name = models.CharField(_('category name'), max_length=30, unique=True)
parent_category = models.ForeignKey(
'self',
@ -205,7 +216,7 @@ class Category(BaseModel):
@cache_decorator(60 * 60 * 10)
def get_category_tree(self):
"""
递归获得分类目录的父级
xjh递归获得分类目录的父级
:return:
"""
categorys = []
@ -221,7 +232,7 @@ class Category(BaseModel):
@cache_decorator(60 * 60 * 10)
def get_sub_categorys(self):
"""
获得当前分类目录所有子集
xjh获得当前分类目录所有子集
:return:
"""
categorys = []
@ -241,7 +252,7 @@ class Category(BaseModel):
class Tag(BaseModel):
"""文章标签"""
"""xjh文章标签模型"""
name = models.CharField(_('tag name'), max_length=30, unique=True)
slug = models.SlugField(default='no-slug', max_length=60, blank=True)
@ -253,6 +264,7 @@ class Tag(BaseModel):
@cache_decorator(60 * 60 * 10)
def get_article_count(self):
"""xjh获取标签下的文章数量"""
return Article.objects.filter(tags__name=self.name).distinct().count()
class Meta:
@ -262,7 +274,7 @@ class Tag(BaseModel):
class Links(models.Model):
"""友情链接"""
"""xjh友情链接模型"""
name = models.CharField(_('link name'), max_length=30, unique=True)
link = models.URLField(_('link'))
@ -287,7 +299,7 @@ class Links(models.Model):
class SideBar(models.Model):
"""侧边栏,可以展示一些html内容"""
"""xjh侧边栏模型可以展示一些html内容"""
name = models.CharField(_('title'), max_length=100)
content = models.TextField(_('content'))
sequence = models.IntegerField(_('order'), unique=True)
@ -305,7 +317,7 @@ class SideBar(models.Model):
class BlogSettings(models.Model):
"""blog的配置"""
"""xjh博客设置模型"""
site_name = models.CharField(
_('site name'),
max_length=200,
@ -367,10 +379,12 @@ class BlogSettings(models.Model):
return self.site_name
def clean(self):
"""xjh验证只能有一个博客配置实例"""
if BlogSettings.objects.exclude(id=self.id).count():
raise ValidationError(_('There can only be one configuration'))
def save(self, *args, **kwargs):
"""xjh保存时清除缓存"""
super().save(*args, **kwargs)
from djangoblog.utils import cache
cache.clear()
cache.clear()

@ -4,10 +4,13 @@ from blog.models import Article
class ArticleIndex(indexes.SearchIndex, indexes.Indexable):
"""xjh文章搜索索引配置"""
text = indexes.CharField(document=True, use_template=True)
def get_model(self):
"""xjh指定搜索模型"""
return Article
def index_queryset(self, using=None):
return self.get_model().objects.filter(status='p')
"""xjh指定索引查询集只索引已发布的文章"""
return self.get_model().objects.filter(status='p')

@ -20,11 +20,13 @@ from oauth.models import OAuthUser, OAuthConfig
# Create your tests here.
class ArticleTest(TestCase):
"""xjh文章相关测试用例"""
def setUp(self):
self.client = Client()
self.factory = RequestFactory()
def test_validate_article(self):
"""xjh测试文章相关功能"""
site = get_current_site().domain
user = BlogUser.objects.get_or_create(
email="liangliangyy@gmail.com",
@ -149,6 +151,7 @@ class ArticleTest(TestCase):
self.client.get('/admin/admin/logentry/1/change/')
def check_pagination(self, p, type, value):
"""xjh检查分页功能"""
for page in range(1, p.num_pages + 1):
s = load_pagination_info(p.page(page), type, value)
self.assertIsNotNone(s)
@ -160,6 +163,7 @@ class ArticleTest(TestCase):
self.assertEqual(response.status_code, 200)
def test_image(self):
"""xjh测试图片上传和处理功能"""
import requests
rsp = requests.get(
'https://www.python.org/static/img/python-logo.png')
@ -183,10 +187,12 @@ class ArticleTest(TestCase):
'https://www.python.org/static/img/python-logo.png')
def test_errorpage(self):
"""xjh测试错误页面"""
rsp = self.client.get('/eee')
self.assertEqual(rsp.status_code, 404)
def test_commands(self):
"""xjh测试管理命令"""
user = BlogUser.objects.get_or_create(
email="liangliangyy@gmail.com",
username="liangliangyy")[0]
@ -229,4 +235,4 @@ class ArticleTest(TestCase):
call_command("create_testdata")
call_command("clear_cache")
call_command("sync_user_avatar")
call_command("build_search_words")
call_command("build_search_words")

@ -5,58 +5,71 @@ from . import views
app_name = "blog"
urlpatterns = [
#xjh首页
path(
r'',
views.IndexView.as_view(),
name='index'),
#xjh首页分页
path(
r'page/<int:page>/',
views.IndexView.as_view(),
name='index_page'),
#xjh文章详情页
path(
r'article/<int:year>/<int:month>/<int:day>/<int:article_id>.html',
views.ArticleDetailView.as_view(),
name='detailbyid'),
#xjh分类目录页
path(
r'category/<slug:category_name>.html',
views.CategoryDetailView.as_view(),
name='category_detail'),
#xjh分类目录分页
path(
r'category/<slug:category_name>/<int:page>.html',
views.CategoryDetailView.as_view(),
name='category_detail_page'),
#xjh作者文章页
path(
r'author/<author_name>.html',
views.AuthorDetailView.as_view(),
name='author_detail'),
#xjh作者文章分页
path(
r'author/<author_name>/<int:page>.html',
views.AuthorDetailView.as_view(),
name='author_detail_page'),
#xjh标签文章页
path(
r'tag/<slug:tag_name>.html',
views.TagDetailView.as_view(),
name='tag_detail'),
#xjh标签文章分页
path(
r'tag/<slug:tag_name>/<int:page>.html',
views.TagDetailView.as_view(),
name='tag_detail_page'),
#xjh文章归档页缓存1小时
path(
'archives.html',
cache_page(
60 * 60)(
views.ArchivesView.as_view()),
name='archives'),
#xjh友情链接页
path(
'links.html',
views.LinkListView.as_view(),
name='links'),
#xjh文件上传接口
path(
r'upload',
views.fileupload,
name='upload'),
#xjh清理缓存接口
path(
r'clean',
views.clean_cache_view,
name='clean'),
]
]

@ -25,6 +25,7 @@ logger = logging.getLogger(__name__)
class ArticleListView(ListView):
"""xjh文章列表视图基类"""
# template_name属性用于指定使用哪个模板进行渲染
template_name = 'blog/article_index.html'
@ -61,7 +62,7 @@ class ArticleListView(ListView):
def get_queryset_from_cache(self, cache_key):
'''
缓存页面数据
xjh缓存页面数据
:param cache_key: 缓存key
:return:
'''
@ -77,7 +78,7 @@ class ArticleListView(ListView):
def get_queryset(self):
'''
重写默认从缓存获取数据
xjh重写默认从缓存获取数据
:return:
'''
key = self.get_queryset_cache_key()
@ -91,7 +92,7 @@ class ArticleListView(ListView):
class IndexView(ArticleListView):
'''
首页
xjh首页视图
'''
# 友情链接类型
link_type = LinkShowType.I
@ -107,7 +108,7 @@ class IndexView(ArticleListView):
class ArticleDetailView(DetailView):
'''
文章详情页面
xjh文章详情页面视图
'''
template_name = 'blog/article_detail.html'
model = Article
@ -163,7 +164,7 @@ class ArticleDetailView(DetailView):
class CategoryDetailView(ArticleListView):
'''
分类目录列表
xjh分类目录列表视图
'''
page_type = "分类目录归档"
@ -202,7 +203,7 @@ class CategoryDetailView(ArticleListView):
class AuthorDetailView(ArticleListView):
'''
作者详情页
xjh作者详情页视图
'''
page_type = '作者文章归档'
@ -228,7 +229,7 @@ class AuthorDetailView(ArticleListView):
class TagDetailView(ArticleListView):
'''
标签列表页面
xjh标签列表页面视图
'''
page_type = '分类标签归档'
@ -260,7 +261,7 @@ class TagDetailView(ArticleListView):
class ArchivesView(ArticleListView):
'''
文章归档页面
xjh文章归档页面视图
'''
page_type = '文章归档'
paginate_by = None
@ -276,6 +277,7 @@ class ArchivesView(ArticleListView):
class LinkListView(ListView):
"""xjh友情链接列表视图"""
model = Links
template_name = 'blog/links_list.html'
@ -284,6 +286,7 @@ class LinkListView(ListView):
class EsSearchView(SearchView):
"""xjhElasticsearch搜索视图"""
def get_context(self):
paginator, page = self.build_page()
context = {
@ -303,7 +306,7 @@ class EsSearchView(SearchView):
@csrf_exempt
def fileupload(request):
"""
该方法需自己写调用端来上传图片该方法仅提供图床功能
xjh文件上传接口该方法需自己写调用端来上传图片该方法仅提供图床功能
:param request:
:return:
"""
@ -344,6 +347,7 @@ def page_not_found_view(
request,
exception,
template_name='blog/error_page.html'):
"""xjh404页面处理视图"""
if exception:
logger.error(exception)
url = request.get_full_path()
@ -355,6 +359,7 @@ def page_not_found_view(
def server_error_view(request, template_name='blog/error_page.html'):
"""xjh500页面处理视图"""
return render(request,
template_name,
{'message': _('Sorry, the server is busy, please click the home page to see other?'),
@ -366,6 +371,7 @@ def permission_denied_view(
request,
exception,
template_name='blog/error_page.html'):
"""xjh403页面处理视图"""
if exception:
logger.error(exception)
return render(
@ -375,5 +381,6 @@ def permission_denied_view(
def clean_cache_view(request):
"""xjh清理缓存视图"""
cache.clear()
return HttpResponse('ok')
return HttpResponse('ok')
Loading…
Cancel
Save