为了方便维护,大概研究了一下基于类的通用视图ListView和DetailView

解决了以下问题

  • 首页实现了分页功能
  • 把视图修改成类的通用视图ListView和DetailView

通用视图ListView

博客首页、分类、归档、标签等和首页类似的页面,都是同理的,都是一样的格式,无非是需要显示的文章的列表不一样。

关于类的通用视图的介绍,可以参考基于类的通用视图:ListView 和 DetailView

首先是针对首页的ListView

1
2
3
4
5
6
7
8
9
blog/views.py
...
from django.views.generic import ListView
...
class IndexView(ListView):
model = Post
template_name = 'blog/index.html'
context_object_name = 'post_list'
...

同时,URL需要这样配置:

1
2
3
4
5
6
7
blog/urls.py

app_name = 'blog'
urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'),
...
]

如果没有特殊需求,这样就足够了,就会显示所有的文章。

然后是分类页面Category的ListView

1
2
3
4
5
6
...
class CategoryView(IndexView):
def get_queryset(self):
cate = get_object_or_404(Category, pk=self.kwargs.get('pk'))
return super(CategoryView, self).get_queryset().filter(category=cate)
...

CategoryView可以直接继承IndexView,因为它们有一些代码是相同的,可以减少代码的重复量。

URL配置如下:

1
2
3
4
5
6
7
blog/urls.py

app_name = 'blog'
urlpatterns = [
...
url(r'^category/(?P<pk>[0-9]+)/$', views.CategoryView.as_view(), name='category'),
]

再然后是归档Archives的ListView

1
2
3
4
5
6
7
8
9
10
...
class ArchivesView(IndexView):
def get_queryset(self):
year = self.kwargs.get('year')
month = self.kwargs.get('month')
return super(ArchivesView, self).get_queryset().filter(
created_time__year=year,
created_time__month=month,
)
...

这个和CategoryView是类似的。
同理,URL配置需要这样修改:

1
2
3
4
5
6
7
8
blog/urls.py

app_name = 'blog'
urlpatterns = [
...
url(r'^archives/(?P<year>[0-9]{4})/(?P<month>[0-9]{1,2})/$', views.ArchivesView.as_view(), name='archives'),

]

标签TagView不再重复

道理是一样的。

还有内容页的视图DetailView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
blog/views.py

from django.views.generic import ListView, DetailView

# 记得在顶部导入 DetailView
class PostDetailView(DetailView):
# 这些属性的含义和 ListView 是一样的
model = Post
template_name = 'blog/detail.html'
context_object_name = 'post'

def get(self, request, *args, **kwargs):

response = super(PostDetailView, self).get(request, *args, **kwargs)
self.object.increase_views()
return response

def get_object(self, queryset=None):
# 覆写 get_object 方法的目的是因为需要对 post 的 body 值进行渲染
post = super(PostDetailView, self).get_object(queryset=None)
renderer = HighlightRenderer()
markdown = mistune.Markdown(renderer=renderer, hard_wrap=True)
blog.text = markdown(blog.text)
return post
# 因为本系统还未开通评论功能,所以以下省略掉。

然后URL配置需要修改一下:

1
2
3
4
5
6
7
8
blog/urls.py

app_name = 'blog'
urlpatterns = [
...
url(r'^detail/(?P<pk>[0-9]+)/$', views.BlogView.as_view(), name='detail'),
...
]

这样设置以后,就可以正常使用了。

但是我遇到了以下的坑

  • 我的首页,不只是有blog_list这一个字典,还有网页的标题。
  • 分页功能还没有加入进来。
  • 内容页面,我还需要上一篇和下一篇这样的功能。

于是做以下修改

ListView里面复写get_context_data函数,就可以传递额外的参数,我这样加入网页标题和分页功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
...
class IndexView(ListView):
template_name = 'blog/index.html'
model = Post
context_object_name = 'post_list'
# 每页显示的文章数量
paginate_by = 10

def get_context_data(self, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
# 更新文章标题字典
context.update({
'PageTitle': self.get_page_title() + ' - 唐僧肉片',
})
paginator = context.get('paginator')
page = context.get('page_obj')
is_pagianted = context.get('is_paginated')
page_data = self.pagination_data(paginator, page, is_pagianted)
# 更新分页功能字典
context.update(page_data)
return context

def pagination_data(self, paginator, page, is_pagenated):
if not is_pagenated:
return {}
left = []
right = []
left_has_more = False
right_has_more = False
first = False
last = False
page_number = page.number
total_pages = paginator.num_pages
page_range = list(paginator.page_range)
if page_number == 1:
right = page_range[page_number:page_number + 2]
if right[-1] < total_pages - 1:
right_has_more = True
if right[-1] < total_pages:
last = True

elif page_number == total_pages:
left = page_range[(page_number - 3) if (page_number - 3) > 0 else 0:page_number - 1]
if left[0] > 2:
left_has_more = True
if left[0] > 1:
first = True
else:
left = page_range[(page_number - 3) if (page_number - 3) > 0 else 0:page_number - 1]
right = page_range[page_number:page_number + 2]
if right[-1] < total_pages - 1:
right_has_more = True
if right[-1] < total_pages:
last = True

if left[0] > 2:
left_has_more = True
if left[0] > 1:
first = True
data = {
'left': left,
'right': right,
'left_has_more': left_has_more,
'right_has_more': right_has_more,
'first': first,
'last': last,
}
return data

# 下面的函数可以被重写,被重写以后,就可以改变网页的标题
def get_page_title(self):
return '博客首页'
...

这里的坑有:

  • 如果是自己输入代码,一定要注意拼写,拼写错误真的很难发现。
  • get_page_title函数是后来加的,在我看到这个函数是必不可少的。因为后面的CategoryView,ArchivesView,TagView都是继承的IndexViewIndexView设置网页标题的函数在get_context_data部分,如果不用get_page_title函数,那么IndexView派生的类,都需要重写这个get_context_data函数来更改网页标题,这个函数还包含着分页功能部分,需要全部复制过来。而使用get_page_title只是为了设置文章标题,派生类只需要重写get_page_title这个函数即可。
  • 分页部分基本上是复制的Django Pagination 完善分页,没有什么变化。
  • 分页部分还要在index.html里面加入下面代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
...
{% if is_paginated %}
<div class="pagination">
{% if first %}
<a href="?page=1">1</a>
{% endif %}
{% if left %}
{% if left_has_more %}
<span>...</span>
{% endif %}
{% for i in left %}
<a href="?page={{ i }}">{{ i }}</a>
{% endfor %}
{% endif %}
<a href="?page={{ page_obj.number }}" style="color: red">{{ page_obj.number }}</a>
{% if right %}
{% for i in right %}
<a href="?page={{ i }}">{{ i }}</a>
{% endfor %}
{% if right_has_more %}
<span>...</span>
{% endif %}
{% endif %}
{% if last %}
<a href="?page={{ paginator.num_pages }}">{{ paginator.num_pages }}</a>
{% endif %}
</div>
{% endif %}
...
  • 为了分页显示的好看,需要加入样式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
...
.pagination {margin-top: 25px; text-align: center; width: 100%;}
.pagination a {
display: inline-block;
line-height: 38px;
padding: 0 15px;
margin-right: 4px;
text-align: center;
background-color: #fff;
user-select: none;
cursor: pointer;
font-size: 14px;
border: 1px solid #d7dde4;
border-radius: 4px;
transition: all .2s ease-in-out;
}
.pagination a:hover {
color: #d2691e;
border: 1px solid #d2691e;
}
.pagination .current-page {
display: inline-block;
color: #d2691e;
font-size: 14px;

}
.pagination span {
display: inline-block;
font-size: 20px;
line-height: 38px;
padding: 0 8px;
margin-right: 4px;
}

所以派生类变成了这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
...
class ArchivesView(IndexView):
def get_queryset(self):
year = self.kwargs.get('year')
month = self.kwargs.get('month')
return super(ArchivesView, self).get_queryset().filter(
created_time__year=year,
created_time__month=month,
)

def get_page_title(self):
year = self.kwargs.get('year')
month = self.kwargs.get('month')
return year + '年' + month + '月博客'


class CategoryView(IndexView):
def get_queryset(self):
cate = get_object_or_404(Category, pk=self.kwargs.get('pk'))
return super(CategoryView, self).get_queryset().filter(category=cate)

def get_page_title(self):
cate = get_object_or_404(Category, pk=self.kwargs.get('pk'))
return '分类:' + cate.name


class TagView(IndexView):
def get_queryset(self):
tag = get_object_or_404(Tag, pk=self.kwargs.get('pk'))
return super(TagView, self).get_queryset().filter(tags=tag)

def get_page_title(self):
tag = get_object_or_404(Tag, pk=self.kwargs.get('pk'))
return '标签:' + tag.name
...

每个派生类都重写了get_queryset来获取文章列表,重写了get_page_title更新页面标题。

博客详情页面也要修改

复制过来的代码,是没有上一篇和下一篇这样的功能的,于是需要自己写进去。无非就是把以前的代码更新一下写法就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
...
# 在PostDetailView下面接着写
def get_context_data(self, **kwargs):
pk = self.kwargs.get('pk')
blog = get_object_or_404(Post, pk=pk)
context = super(BlogView, self).get_context_data(**kwargs)
prev = super(BlogView, self).get_queryset().filter(
pk__gt=pk,
category=blog.category,
).order_by('pk')
if prev.count() > 0:
prev = prev[0]
else:
prev = None
next_blog = super(BlogView, self).get_queryset().filter(
pk__lt=pk,
category=blog.category,
).order_by('-pk')
if next_blog.count() > 0:
next_blog = next_blog[0]
else:
next_blog = None
context.update({
'prev': prev,
'next': next_blog,
})
return context
...

大功告成。

PS:写的有点乱,将就着看吧。