Skip to content

Commit

Permalink
研究了下ORM中的N+1问题和加了些测试
Browse files Browse the repository at this point in the history
  • Loading branch information
enjoy-binbin committed Jun 26, 2019
1 parent 628d793 commit b28413a
Show file tree
Hide file tree
Showing 8 changed files with 127 additions and 47 deletions.
4 changes: 2 additions & 2 deletions blog/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ class Meta:
# ModelForm中 元Meta中的 fields, 和Model 相关联,可以进行save操作写入Model
class CommentForm(forms.ModelForm):
""" 前端评论框form """
name = forms.CharField(label='名称', required=True, widget=forms.HiddenInput)
email = forms.EmailField(label='邮箱', required=True, widget=forms.HiddenInput)
# name = forms.CharField(label='名称', required=True, widget=forms.HiddenInput)
# email = forms.EmailField(label='邮箱', required=True, widget=forms.HiddenInput)
parent_comment_id = forms.IntegerField(widget=forms.HiddenInput, required=False)

class Meta:
Expand Down
28 changes: 14 additions & 14 deletions blog/templatetags/blog_tags.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import random

from django import template
from django.template.defaultfilters import stringfilter
from django.utils.safestring import mark_safe
Expand Down Expand Up @@ -52,17 +54,16 @@ def queryset_filter_tag(queryset, **kwargs):
@register.inclusion_tag('blog/tags/sidebar.html', takes_context=True)
def inclusion_sidebar_tag(context):
""" 引入站点右侧侧边栏 """
all_articles = Article.objects.all()
hot_articles = all_articles.order_by('-views')[:setting.sidebar_article_count] # 最热文章
new_articles = all_articles.order_by('-add_time')[:setting.sidebar_article_count] # 最新文章
all_categorys = Category.objects.all() # 全部分类
count = setting.sidebar_article_count # 站点设置中侧边栏显示记录条数

# 相关上下文变量设置
all_articles = Article.objects.only('title', 'views', 'add_time') # only只查这些字段
hot_articles = all_articles.order_by('-views')[:count] # 最热文章
new_articles = all_articles.order_by('-add_time')[:count] # 最新文章
all_categories = Category.objects.all() # 全部分类
all_sidebars = SideBar.objects.filter(is_enable=True).order_by('-order') # 侧边栏
all_links = Link.objects.filter(is_enable=True) # 友情链接
all_comments = Comment.objects.filter(is_enable=True).order_by('-add_time')[:setting.sidebar_article_count]

# github设置
github_user = setting.github_user
github_repository = setting.github_repository
all_comments = Comment.objects.filter(is_enable=True).order_by('-add_time')[:count] # 新的评论

# 标签云,根据标签被引用的次数,增大标签的字体大小
# 算法: 单个标签字体大小 = 单个标签出现次数 / 所有标签被引用的总次数 / 标签个数 * 增长幅度 + 最小字体大小
Expand All @@ -77,15 +78,14 @@ def inclusion_sidebar_tag(context):
# 标签被引用的总次数 / 标签总数, 当文章总引用数为0时, 将fre置为1
frequency = (tag_ref_count / len(all_tags)) or 1

all_tags = list(
map(lambda x: (x[0], x[1], (x[1] / frequency) * increment + min_pt), tag_ref_list)) # tag,count,size
# import random
# random.shuffle(all_tags) # 将标签再随机排序
# tag,count,size
all_tags = list(map(lambda x: (x[0], x[1], (x[1] / frequency) * increment + min_pt), tag_ref_list))
random.shuffle(all_tags) # 将标签再随机排序

return {
'hot_articles': hot_articles,
'new_articles': new_articles,
'all_categorys': all_categorys,
'all_categories': all_categories,
'all_sidebars': all_sidebars,
'all_links': all_links,
'all_tags': all_tags,
Expand Down
66 changes: 58 additions & 8 deletions blog/tests.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
from django.test import TestCase, Client
from django.contrib.auth import get_user_model
from django.urls import reverse

from user.models import UserProfile
from blog.models import Category, Article
from blog.models import Category, Article, Comment

User = get_user_model()


# 命令行调用: python manage.py test app [--keepdb参数: 保留测试数据库, 这样反复测试的时候速度快]
Expand All @@ -13,21 +16,68 @@

class ArticleModelTest(TestCase):
def setUp(self):
self.user = UserProfile.objects.create_superuser('test_admin', 'admin@qq.com', 'aa123456')
self.category = Category.objects.create(name='category')
self.client = Client()
self.client = Client() # 普通客户端
self.client_user = Client() # 普通用户登录用的客户端
self.client_superuser = Client() # 管理员登录用的客服端
self.superuser = User.objects.create_superuser('test_admin', 'admin@qq.com', 'aa123456')
self.user = User.objects.create_user('test_user', 'user@qq.com', 'aa123456')

self.client_user.login(username='test_user', password='aa123456')
self.client_superuser.login(username='test_admin', password='aa123456')

self.category = Category.objects.create(name='test_category')
self.article = Article.objects.create(category=self.category, author=self.superuser,
title='test_article', content='test_article_content')

def test_category(self):
category_res = self.client.get(self.category.get_absolute_url())
self.assertEqual(category_res.status_code, 200)

def test_article(self):
# 这里就简单的测试创建5篇文章
for i in range(1, 6):
article = Article()
article.category = self.category
article.author = self.user
article.author = self.superuser
article.title = 'title' + str(i)
article.content = 'content' + str(i)
article.save()

self.assertEqual(len(Article.objects.all()), 5)
self.assertEqual(len(Article.objects.all()), 5 + 1)
article_detail_res = self.client.get(Article.objects.first().get_absolute_url())
self.assertEqual(article_detail_res.status_code, 200)
# 测试用例是真的可以写很多呀...

def test_comment(self):
comment_url = reverse('blog:comment', kwargs={'article_id': self.article.id})
comment_res = self.client_user.post(comment_url, data={'content': 'test_comment'})

self.assertEqual(Comment.objects.first().content, 'test_comment')
self.assertEqual(comment_res.status_code, 302)

def test_page404(self):
page404_res = self.client.get('/彬彬冲呀66666666')
self.assertEqual(page404_res.status_code, 404)

page404_res = self.client.get('/refresh')
self.assertEqual(page404_res.status_code, 301) # 未登录用户访问会先跳转到登录页

def test_refresh(self):
# 301永久重定向, 客户端可以对结果进行缓存, 当访问原地址会直接进行本地302跳转, 两次请求先一次301再一次302
# 302临时重定向, 客户端必须请求原链接
page301_res = self.client_superuser.get('/refresh') # '/', 这个会先进行301, 然后在302
page302_res = self.client_superuser.get('/refresh/') # 管理员refresh后会302到首页
self.assertEqual(page301_res.status_code, 301)
self.assertEqual(page302_res.status_code, 302)

page403_res = self.client_user.get('/refresh/') # 这里需要加'/', 不然会先进行301的
self.assertEqual(page403_res.status_code, 403) # 普通用户访问是403, 没有权限

page302_res = self.client.get('/refresh/')
self.assertEqual(page302_res.status_code, 302) # 未登录访问302到登录页

def test_feed_and_sitemap(self):
feed_res = self.client.get('/feed/') # /feed是301, 需要跟url里的匹配
self.assertEqual(feed_res.status_code, 200)

sitemap_res = self.client.get('/sitemap.xml')
self.assertEqual(sitemap_res.status_code, 200)
28 changes: 19 additions & 9 deletions blog/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from blog.models import Article, Category, Tag, Comment, Photo, GuestBook
from blog.forms import CommentForm, GuestBookForm

# from blog.tasks import test_add

# 日志器, 一般都用__name__作为日志器的名字, 在本例中日志器名称为 blog.views
Expand Down Expand Up @@ -60,6 +61,15 @@ class ArticleDetailView(DetailView):
context_object_name = 'article'
object = None # 当前文章对象, 感觉可以用property

def get_queryset(self):
# 在detail页有调用article.author和article.category和article.tags操作会出现N+1问题
# 使用select_related(一对一, 多对一)本质是inner join, 经过测试会少两条sql语句(author, category)
# python manage.py shell -->> print(Article.objects.all().select_related('author').query)
# 不过一般又不建议使用inner join操作, 因为会涉及到高并发和死锁, TOLearn.
# 使用prefetch_related(多对多, 一对多)是分别查询两张表, 然后再使用python处理, N+1次查询 -> 2次查询
# 把子查询/join查询分成两次, 虽然会花费更多的cpu时间, 但是避免了系统的死锁, 提高了并发响应能力
return super().get_queryset().select_related('author', 'category').prefetch_related("tags")

def get_object(self, queryset=None):
obj = super().get_object()
obj.add_views() # 文章阅读量加一
Expand All @@ -68,15 +78,15 @@ def get_object(self, queryset=None):

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['prev_article'] = self.object.prev_article
context['next_article'] = self.object.next_article
context['prev_article'] = self.object.prev_article()
context['next_article'] = self.object.next_article()

comment_form = CommentForm()
user = self.request.user

if user.is_authenticated:
comment_form.fields['email'].initial = user.email # 直接设置initial->value, 前端中这两个字段是hidden的
comment_form.fields["name"].initial = user.username
# if user.is_authenticated:
# comment_form.fields['email'].initial = user.email # 直接设置initial->value, 前端中这两个字段是hidden的
# comment_form.fields["name"].initial = user.username

article_comments = self.object.get_comment_list()

Expand Down Expand Up @@ -258,10 +268,10 @@ def form_invalid(self, form):
article_id = self.kwargs['article_id']
article = get_object_or_404(Article, id=article_id)

if self.request.user.is_authenticated:
user = self.request.user
form.fields["email"].initial = user.email
form.fields["name"].initial = user.username
# if self.request.user.is_authenticated:
# user = self.request.user
# form.fields["email"].initial = user.email
# form.fields["name"].initial = user.username

return self.render_to_response({
'form': form,
Expand Down
20 changes: 20 additions & 0 deletions log/binblog_error.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[2019-06-26 14:36:38,129] ERROR [blog.views.page_not_found:297 views] {'status': '404', 'path': '彬彬冲呀66666666'}
[2019-06-26 15:02:53,486] ERROR [blog.views.page_not_found:297 views] {'status': '404', 'path': '彬彬冲呀66666666'}
[2019-06-26 15:09:18,624] ERROR [blog.views.page_not_found:297 views] {'status': '404', 'path': '彬彬冲呀66666666'}
[2019-06-26 15:11:39,731] ERROR [blog.views.page_not_found:297 views] {'status': '404', 'path': '彬彬冲呀66666666'}
[2019-06-26 15:15:52,296] ERROR [blog.views.page_not_found:297 views] {'status': '404', 'path': '彬彬冲呀66666666'}
[2019-06-26 15:19:11,627] ERROR [blog.views.page_not_found:297 views] {'status': '404', 'path': '彬彬冲呀66666666'}
[2019-06-26 15:21:36,616] ERROR [blog.views.page_not_found:297 views] {'status': '404', 'path': '彬彬冲呀66666666'}
[2019-06-26 15:22:02,558] ERROR [blog.views.page_not_found:297 views] {'status': '404', 'path': '彬彬冲呀66666666'}
[2019-06-26 15:32:02,024] ERROR [blog.views.page_not_found:297 views] {'status': '404', 'path': '彬彬冲呀66666666'}
[2019-06-26 15:47:25,781] ERROR [blog.views.page_not_found:297 views] {'status': '404', 'path': '彬彬冲呀66666666'}
[2019-06-26 15:52:20,272] ERROR [blog.views.page_not_found:297 views] {'status': '404', 'path': '彬彬冲呀66666666'}
[2019-06-26 15:57:10,149] ERROR [blog.views.page_not_found:297 views] {'status': '404', 'path': '彬彬冲呀66666666'}
[2019-06-26 15:58:52,871] ERROR [blog.views.page_not_found:297 views] {'status': '404', 'path': '彬彬冲呀66666666'}
[2019-06-26 15:59:38,855] ERROR [blog.views.page_not_found:297 views] {'status': '404', 'path': '彬彬冲呀66666666'}
[2019-06-26 16:01:30,389] ERROR [blog.views.page_not_found:297 views] {'status': '404', 'path': '彬彬冲呀66666666'}
[2019-06-26 16:02:13,540] ERROR [blog.views.page_not_found:297 views] {'status': '404', 'path': '彬彬冲呀66666666'}
[2019-06-26 16:12:39,628] ERROR [blog.views.page_not_found:297 views] {'status': '404', 'path': '彬彬冲呀66666666'}
[2019-06-26 16:13:11,439] ERROR [blog.views.page_not_found:297 views] {'status': '404', 'path': '彬彬冲呀66666666'}
[2019-06-26 16:13:57,928] ERROR [blog.views.page_not_found:297 views] {'status': '404', 'path': '彬彬冲呀66666666'}
[2019-06-26 16:14:53,794] ERROR [blog.views.page_not_found:297 views] {'status': '404', 'path': '彬彬冲呀66666666'}
3 changes: 1 addition & 2 deletions templates/blog/article_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
{% endfor %}
<meta name="description" content="{{ article.content|article_markdown|striptags|truncatewords:1 }}"/>
{% if article.tags %}
<meta name="keywords" content="{{ article.tags.all|join:"," }}"/>
<meta name="keywords" content="{{ article.tags.all|join:',' }}"/>
{% else %}
<meta name="keywords" content="{{ SITE_KEYWORDS }}"/>
{% endif %}
Expand Down Expand Up @@ -51,7 +51,6 @@ <h3 class="comment-meta">您还没有登录,请您
</div>
{% endif %}
</div>

{% endblock %}

{% block sidebar %}
Expand Down
15 changes: 8 additions & 7 deletions templates/blog/tags/post_comment.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ <h3 id="reply-title" class="comment-reply-title">发表评论</h3>
{{ comment_form.content }}
{{ comment_form.content.errors }}
</p>
<p class="comment-form-author">
{{ comment_form.name }}
{{ comment_form.name.errors }}
<p class="comment-form-email">
{{ comment_form.email }}
{{ comment_form.email.errors }}
</p>
{# <p class="comment-form-author">#}
{# {{ comment_form.name }}#}
{# {{ comment_form.name.errors }}#}
{# </p>#}
{# <p class="comment-form-email">#}
{# {{ comment_form.email }}#}
{# {{ comment_form.email.errors }}#}
{# </p>#}
{{ comment_form.parent_comment_id }}
<div class="form-submit">
<span class="comment-markdown" style="padding: 10px"> 支持markdown</span>
Expand Down
10 changes: 5 additions & 5 deletions templates/blog/tags/sidebar.html
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@ <h3 class="widget-title">欢迎您star或者fork本站源代码</h3>
{% else %}
{{ comment.author.username }}
{% endif %}
</span>发表在
</span>{{ comment.add_time|time_filter }}发表在
<a href="{{ comment.article.get_absolute_url }}#comment-{{ comment.id }}">
{{ comment.article.title }}
</a>
{{ comment.article.title }}
</a>- {{ comment }}
</li>
{% endfor %}
</ul>
Expand All @@ -89,10 +89,10 @@ <h3 class="widget-title">欢迎您star或者fork本站源代码</h3>
{% endif %}

{# 分类目录 #}
{% if all_categorys %}
{% if all_categories %}
<aside class="widget"><h3 class="widget-title">分类目录</h3>
<ul>
{% for category in all_categorys %}
{% for category in all_categories %}
<li class="cat-item">
<a href='{{ category.get_absolute_url }}' title="{{ category.name }}">{{ category.name }}</a>
</li>
Expand Down

0 comments on commit b28413a

Please sign in to comment.