博客系统开发-2
博客系统开发-2
博客系统首页
前面章节已将一级和二级URL配置文件中关于博客系统首页的URL配置列举出来。
博客首页通用视图函数
博客系统的首页主要展示用户发表的文章,采用列表的方式把文章的题目、简介、分类、评论数、阅读数显示出来。
from django.shortcuts import render,redirect,HttpResponse,get_object_or_404
from . import models
from comments.forms import CommentForm
from django.views.generic import ListView,DetailView
from django.contrib.auth.models import auth
from django.db.models import Q
class indexview(ListView):
# 指定数据模型(数据库表),默认取全部记录
model = models.Blog
# 指定模板文件
template_name = 'blog/index.html'
# 指定传递给模板文件的模板变量名,保存着model指定的数据模型的记录集合
context_object_name = 'blog_list'
# 每页显示的记录数
paginate_by = 5
def get_context_data(self, **kwargs):
# 在普通视图函数中将模板变量传递给模板,通过render函数实现
# 比如render(request,'blog/index.html',context={'Blog_list':blog_list})
# 或者render(request,'blog/index.html',{'Blog_list':blog_list})
# 在通用视图中如果不是获取数据模型的全部记录需要重写该方法获得条件过滤后的记录
# 注意本视图代码并没有过滤,目的是利用父类的方法返回值中有关分页的数值
# 利用这些分页相关数值进行自定义分页代码的编写,在复写方法时还可增加自定义的模板变量
# 首先获得父类生成的传递给模板的字典。
context = super().get_context_data(**kwargs)
# 父类(ListView)生成的字典(context)中已有paginator、pageobj、is_paginated三个键值对
# paginator是Paginator的一个实例;pageobj是Page的一个实例;is_paginated是一个布尔变量,用以指示是否已分页。
paginator = context.get('paginator')
pageobj = context.get('page_obj')
is_paginated = context.get('is_paginated')
#设置每页显示的页码标签数
show_pagenumber=7
# 调用自定义 get_page_data 方法获得显示分页导航条需要的数据
page_data = self.get_page_data(is_paginated, paginator, pageobj, show_pagenumber)
# 将page_data变量更新到 context 中,注意 page_data 是一个字典。
context.update(page_data)
# 传递标识值,如果这个值为firsttab,表示显示“首页”链接被选中
context['tabname'] = 'firsttab'
# 将更新后的 context 返回,以便 ListView 使用这个字典中的模板变量去渲染模板。
# 注意此时 context 字典中已有了显示分页导航条所需的数据。
return context
# 自定义方法,返回当前页的前面页码标签的个数以及后面页码标签的个数。
def get_page_data(self,is_pageinated,paginator,pageobj,show_pagenumber):
# 如果没有分页,返加空字典
if not is_pageinated:
return {}
"""
分页数据有三部分组成,
左边用left存页码,右边用right存页码,中间部分就是当前页page_obje.number
lefe,right都初始列表
"""
left=[]
right=[]
#当前页面数值的获取,得用户当前请求的页码号
cur_page=pageobj.number
print('cur_page',cur_page)
#取出分页中最后的页码
total=paginator.num_pages
#得到显示页数的一半,‘//’可以取得两数相除的商的整数部分,show_pagenumber是页码标签的个数
half=show_pagenumber//2
print(half)
#取出当前页面前(letf)显示页标签个数,注意range()如:range(start, stop)用法,计数从 start 开始, 计数到 stop 结束但不包括 stop
for i in range(cur_page - half,cur_page):
#数值大于等于1时,才取数值放到left列表中
if i>=1:
left.append(i)
# 取出当前页后前(right)显示页标签个数,再次提示注意range()用法
for i in range(cur_page+1, cur_page + half +1):
# 数值小于等于页数的最大页数时,才取数值放到right列表中
if i <= total:
right.append(i)
page_data={
'left':left,
'right':right,
}
return page_data
说明:
get_page_data函数主要生成前端页面分页所需的数据。我们想实现一页显示7个页码标签,当前页码放在中间。
其导航条可分为三个部分,一个是当前页码,一个是当前页码前面的页码标签,一个是当前页码后面的页码标签。当前页码可以从通用类视图的page_obj属性中得到( page_obj.number ),因此我们只需知道当前页码前面与后面的页码个数与列表。
get_page_data中定义了left、right两个列表类型的变量,分别储存当前页码前面的3个标签(有可能是0,1,2个)和后面的标签。
博客首页模板文件
index.html继承于base.html:
{% extends 'base.html' %}
{% block main %}
<!--如果传入的参数error_mag有值则显示其中的内容,视图把错误信息放在这个变量中-->
{% if error_msg %}
<p>{{ error_msg }}</p>
{% endif %}
<!--通过for循环从blog_list中取出每个Blog实例对象,存到blog-->
{% for blog in blog_list %}
<!--引用Bootstrap媒体组件-->
<div class="media">
<div class="media-left">
<!--指定单击头像重定向到的路径-->
<a href="{% url 'blog:authorindex' blog.author.id %}">
<img class="media-object" src="/media/{{ blog.author.head_img }}" style="width:100px;height:100px;"
alt="点击头像显示此作者的博客文章列表">
</a>
</div>
<div class="media-body">
<!--指定了文章的标题,并指定单击标题重定向到的URL-->
<h3 class="media-heading"><a href="{{ blog.get_absolute_url }}">{{ blog.title }}</a></h3>
<!--文章摘要,用了safe过滤器-->
<p>{{ blog.excerpt|safe }}...</p>
<div class="entry-meta">
<!--以下5个span分别显示文章的分类、发表时间、作者、评论数、阅读数-->
<span class="blog-category"><a href="#">{{ blog.category.name }}</a></span>
<span class="blog-date"><a href="#"><time class="entry-date"
datetime="{{ blog.created_time }}">{{ blog.created_time }}</time></a></span>
<span class="blog-author"><a href="#">{{ blog.author.nikename }}</a></span>
<span class="comments-link"><a href="#">{{ blog.comment_set.count }} 评论</a></span>
<span class="views-count"><a href="#">{{ blog.views }} 阅读</a></span>
</div>
</div>
{% empty %}
<div class="no-post">暂时还没有发布的文章!</div>
{% endfor %}
<!-- 简单分页效果
<div class="pagination-simple">
<a href="#">上一页</a>
<span class="current">第 6 页 / 共 11 页</span>
<a href="#">下一页</a>
</div>
-->
<!--判断是否分页-->
{% if is_paginated %}
<!--引用Bootstrap分页组件-->
<nav aria-label="Page navigation">
<ul class="pagination">
{% if left %}
<li>
<a href="?page={{ page_obj.previous_page_number }}" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
{% else %}
<li class="disabled">
<a href="#" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
{% endif %}
<!--把当前页码前面的页码通过循环从left变量中取出来-->
{% for i in left %}
<li><a href="?page={{ i }}">{{ i }}</a></li>
{% endfor %}
<!--当前页URL、页码设置-->
<li class="active"><a href="?page={{ page_obj.number }}">{{ page_obj.number }} <span class="sr-only">(current)</span></a>
</li>
{% for i in right %}
<li><a href="?page={{ i }}">{{ i }}</a></li>
{% endfor %}
{% if right %}
<li>
<a href="?page={{ page_obj.next_page_number }}" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
{% else %}
<li class="disabled">
<a href="#" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
{% endblock main %}
说明:
以上文件中的分页根据传入的left、right变量进行设置。
先判断left是否有值,如果有则说明当前页码前面有页码。首先给“上一页”(<<)链接URL赋值,然后通过循环从left变量中取出页码,依次放在“上一页”页码之后,并给每一个页码设置URL,再把当前页码放上,设置为激活状态,并设置当前页码、URL,组后判断right对否有值,根据情况来确定当前页码后面页码的排列。如果left无值,首先把“上一页”(<<)页码设为不可用,其后放置当前页码,后面判断right是否有值。
头像链接功能
在index.html模板文件中,在每篇博客文章左侧有个头像,这是用户注册时上传的头像。在首页上。用户的头像就放在其发布的文章的左侧。
我们在每个头像上建立一个链接,单击这个头像,列表显示这个作者发表的所有文章。
<!-- 通过{% url %}根据URL配置项名、URL参数,反向解析出URL-->
<a href="{% url 'blog:authorindex' blog.author.id %}">
<!-- 注意img标签的src属性中的/media/是URL的前缀 -->
<img class="media-object" src="/media/{{ blog.author.head_img }}" style="width:100px;height:100px;" alt="单击头像显示此作者的博客文章列表"
</a>
在/test_blog/blog/urls.py中的配置项如下,可以推导出对应视图为authorindex。
path('authorindex/<int:id>/',views/authorindex.as_view(),name='authorindex'),
视图authorindex继承于ListView,因此可通过属性设置获取数据模型、模板文件、模板变量、用get_queryset()方法从数据模型中获取定制的数据或者过滤数据,可以重写get_context_data()方法增加模板变量。
class authorindex(ListView):
model = models.Blog
template_name = 'blog/index.html'
context_object_name = 'blog_list'
def get_queryset(self):
# 根据URL参数从loguser中选择用户对象
user = get_object_or_404(models.loguser,pk=self.kwargs.get('id'))
# 调用父类get_queryset()方法得到model.Blog中的数据
# 然后通过filter过滤再排序
return super(authorindex,self).get_queryset().filter(author=user).order_by('-created_time')
def get_context_data(self, *, object_list=None, **kwargs):
context = super(authorindex,self).get_context_data(**kwargs)
# 增加一个模板变量tabname,模板文件根据这个变量值设置导航条的链接被选中
context['tabname']='firsttab'
return context
博客系统检索功能
母版有一个导航条,导航条上有一个查询文本框,通过输入字符串可以查询到包含该字符串的文章,这个功能相当于一个简单的搜索引擎。本项目将实现的检索功能是:能够根据用户输入的搜索关键词对文章进行列表显示,并在文章中高亮显示搜索关键词。
安装Django Haystack
Django Haystack是一个提供搜索功能的Django模块,本项目中我们给Haystack配置上Whoosh搜索引擎,配合著名的中文自然语言处理库jieba分词,实现博客系统检索功能。
安装他们:pip install whoosh django-haystack jieba
更改Django Haystack分词器
Haystack默认使用Whoosh作为搜索引擎,Whoosh的分词器是英文分词器,中文搜索效果不好。这个项目中我们把Haystack的分词器更换成jieba。首先找到Haystack安装目录,这个目录一般是Python安装目录下的/Lib/site-packages/haystack/目录。把haystack/backends/whoosh_backends.py文件复制到blog/下,重命名为whoosh_backends_cn.py,对其进行修改:
# encoding: utf-8
# 加入以下语句,导入jieba中文分词器相关模块
from jieba.analyse import ChineseAnalyzer
...
else:
# 以下是源代码语句
schema_fields[field_class.index_fieldname] = TEXT(stored=True, analyzer=StemmingAnalyzer(), field_boost=field_class.boost, sortable=True)
# 以下是修改的
schema_fields[field_class.index_fieldname] = TEXT(stored=True, analyzer=ChineseAnalyzer(), field_boost=field_class.boost, sortable=True)
配置Django Haystack
在settings中把Django Haystack加入INSTALLED_APPS。
在settings加入以下代码:
HAYSTACK_CONNECTIONS = {
'default':{
#指定了Django Haystack要使用的搜索引擎
'ENGINE':'blog.whoosh_backend_cn.WhooshEngine',
# 指定索引文件存放的位置
'PATH':os.path.join(BASE_DIR,'whoosh_index')
},
}
# 指定搜索结果分页方式为每页6条记录
HAYSTACK_SEARCH_RESULTS_PER_PAGE = 6
# 指定实时更新索引,当有数据改变时自动更新索引
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
建立索引类
数据处理主要是建立一个索引类,这个类必须写在文件名为search_indexes.py的文件中。而且对哪个应用程序中的数据进行检索,该文件就放在哪个应用程序目录下,因此本项目中的这个文件放在test_blog的blog文件夹下:
from haystack import indexes
from .models import Blog
# 建立索引类名,名字规定,为“modelnameIndex”形式,其中modelname是数据模型名
# 要为哪个数据模型建立索引类就用哪个数据模型名
# 类必须继承indexes.SearchIndex,indexes.Indexable
class BlogIndex(indexes.SearchIndex,indexes.Indexable):
"""
定义一个字段,字段名约定为text,设置这个字段的document=True
设置Django Haystack和搜索引擎将使用此字段的内容作为索引进行检索
use_template=True允许我们使用数据模板去建立搜索引擎要使用的索引文件
数据模板的路径形式一般为templates/search/indexes/yourapp/modelname_text.txt
templates是在settings中设定的模板文件目录
modelname指的是要检索数据的数据模型
"""
text = indexes.CharField(document=True,use_template=True)
# 重写方法,返回相应的数据模型,这个方法必须有
def get_model(self):
return Blog
# 重写方法,返回数据模型需要检索的记录
def index_queryset(self, using=None):
return self.get_model().objects.all()
说明:
建立一个字段text,设置字段的document=True,指定搜索引擎将使用此字段的内容作为索引进行检索;设置字段的use_template=True允许我们使用数据模板去建立搜索引擎要使用的索引文件,也就是说我们可以在这个文件中放置数据模型需要检索的字段,搜索引擎索引的文件一般为templates/search/indexes/yourapp/modelname_text.txt。重写get_model方法,返回要检索的数据模型,这是指定从哪个数据模型进行检索,这个方法必须有。重写index_queryset方法,返回数据模型需要检索的记录。
templates/search/indexes/blog/blog_text.txt文件:
{{ object.title }}
{{ object.body }}
以上内容指定数据模型Blog中,在title、body两个字段中进行关键字检索。
URL配置
Django Haystack搜索视图函数和URL表达式的关系是封装好的,只需在urls中导入即可,加入path('search/',include(haystack.urls)),haystack_search是Django封装的URL配置项名称,直接调用即可。
母版文件中的对应内容:
<form class="navbar=form navbar-left" method="get" action="{% url 'haystack_search' %}">
<div class="form-group">
<input type="text" class="form-control" name="q" placeholds="搜索" required>
</div>
<button type="submit" class="btn btn-default">
搜索
</button>
</form>
创建search.html
由于Django Haystack对视图函数与URL配置做了封装,其视图函数将搜索结果默认传递给/template/search/search.html:
{% extends 'base.html' %}
<!-- 导入Django Haystack自定义模板标签 -->
{% load highlight %}
{% block main %}
<!--query变量保存搜索关键词,如果存在query就循环取出每条记录对象 -->
{% if query %}
<!-- page.object_list保存BlogIndex类传给每页的对象集合-->
{% for result in page.object_list %}
<article class="blog">
<header class="entry-header">
<h1 class="entry-title">
<!--文章标题如有搜索关键词则高亮显示 -->
<a href="{{ result.object.get_absolute_url }}">{% highlight result.object.title with query %}</a>
</h1>
<div class="entry-meta-detail">
<span class="blog-category">
<a href="{% url 'blog:category' result.object.category.pk %}">
{{ result.object.category.name }}</a></span>
<span class="blog-date"><a href="#">
<time class="entry-date" datetime="{{ result.object.created_time }}">
{{ result.object.created_time }}</time></a></span>
<span class="blog-author"><a href="#">{{ result.object.author }}</a></span>
<span class="comments-link">
<a href="{{ result.object.get_absolute_url }}#comment-area">
{{ result.object.comment_set.count }} 评论</a></span>
<span class="views-count"><a
href="{{ result.object.get_absolute_url }}">{{ result.object.views }} 阅读</a></span>
</div>
</header>
<div class="entry-content clearfix">
<p>{% highlight result.object.body with query %}</p>
</div>
</article>
{% empty %}
<div class="no-blog">没有搜索到你想要的结果!</div>
{% endfor %}
<!--Django Haystack自动对搜索结果进行分页处理,并将其传给模板一个page对象,下面是一个简单分页的代码 -->
{% if page.has_previous or page.has_next %}
<div>
{% if page.has_previous %}
<a href="?q={{ query }}&page={{ page.previous_page_number }}">{% endif %}« Previous
{% if page.has_previous %}</a>{% endif %}
|
{% if page.has_next %}<a href="?q={{ query }}&page={{ page.next_page_number }}">{% endif %}Next
»{% if page.has_next %}</a>{% endif %}
</div>
{% endif %}
{% else %}
请输入搜索关键词
{% endif %}
{% endblock main %}
说明:
Django Haystack自动对搜索结果进行分页,并将其传给模板的变量是一个page对象。因此我们可以通过{% for result in page.object_list %}循环语句取出本页包含记录信息的对象,其中result.object代表一条记录,然后通过模板变量显示文章相关的数据。
{% highlight result.object.title with query %}、{% highlight result.object.body with query %}对文章的标题和内容中的关键词进行高亮处理,{% hightlight %}是Django Haystack自定义模板标签,query是它设置的模板变量,用来保存搜索关键词,这些都是封装好的,直接用就行。高亮处理方式是给文本中的搜索关键词外面加上一个span标签并添加highlighted样式。
创建索引文件
相关代码编写完成后需要建立索引文件:python manage.py rebuild_index
文章发布
本项目所有发布的文章都是通过Django Admin管理后台输入的,由于在数据模型Blog中定义的一个字段是RichTextUploadingField,因此可以在后台使用富文本编辑器。
访问后台打开文档管理表页面,单击增加再保存即可发布文章。
文章评论
博客文章后面一般都有评论,这样便于博客作者与读者进行交流。文章评论一般由登录用户发表,未登录用户只能查看文章与评论,不能发表意见。
创建评论应用程序
本项目创建comments应用程序并在settings注册。
评论系统的数据模型
from django.db import models
class Comment(models.Model):
name = models.CharField(max_length=32)
email = models.EmailField(max_length=60)
text = models.TextField()
# auto_now_add=True自动取出本记录保存时的时间
created_time = models.DateTimeField(auto_now_add=True)
# 一条评论只能属于一篇文章,一篇文章可有多条评论
blog = models.ForeignKey('blog.Blog',on_delete=models.CASCADE)
def __str__(self):
return self.text[:20]
终端输入生成数据库表命令。
文章评论表单
comments下新建form.py:
from django import forms
from .models import Comment
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ['text']
定义了一个字段,数据模型comment中的其他字段可以从登录用户传过来的信息中取得。
文章评论URL配置
comments下新建urls.py:
from django.urls import path
from . import views
app_name = 'comment'
urlpatterns=[
path('comment/post/<int:blog_pk>/',views.blog_comment,name='blog_comment'),
]
一级urls中加入:path('comment/',include('comments.urls')),
文章评论视图函数
from django.shortcuts import render,get_object_or_404,redirect
from blog.models import Blog
from . forms import CommentForm
# blog_pk是URL实名参数
def blog_comment(request,blog_pk):
# get_object_or_404作用是当要获取文章(Blog)存在时,则获取,否则返回404页面
blog = get_object_or_404(Blog,pk=blog_pk)
if request.method == 'POST':
# request.POST是一个字典类型对象,表单提交的数据也保存在这个对象
# 因此可以用request.POST给CommentForm对象赋值
form = CommentForm(request.POST)
if form.is_valid():
# 由于form是ModelForm类型,可以直接调用save方法保存数据到数据库表
# 但不立刻保存数据到数据库表
comment = form.save(commit=False)
# 用户登录后,request.user保存用户的nikename、email等值
comment.name = request.user.nikename
comment.email = request.user.email
# 通过外键关系将评论和被评论的文章关联起来
comment.blog = blog
# 真正保存到数据库表
comment.save()
# redirect(blog)调用数据模型Blog的实例对象blog的get_absolute_url()方法
# 然后重定向到其返回的URL
return redirect(blog)
else:
# 数据校验不通过则重新渲染页面
# 需要传递3个模板变量给detail.html,文章blog、评论列表、表单对象form
comment_list = blog.comment_set.all()
context = {'blog':blog,'form':form,'comment_list':comment_list}
return render(request,'blog/detail.html',context=context)
return redirect(blog)
文章评论模板
blog下的detail.html:
{% extends 'base.html' %}
{% block main %}
<article class="blog">
<header class="entry-header">
<h1 class="entry-title">{{ blog.title }}</h1>
<div class="entry-meta-detail">
<span class="blog-category"><a href="#">{{ blog.category.name }}</a></span>
<span class="blog-date"><a href="#"><time class="entry-date"
datetime="{{ blog.created_time }}">{{ blog.created_time }}</time></a></span>
<span class="blog-author"><a href="#">{{ blog.author.nikename }}</a></span>
<span class="comments-link"><a href="#">共 <span>{{ comment_list | length }}</span> 条评论</a></span>
<span class="views-count"><a href="#">{{ blog.views }} 阅读</a></span>
</div>
</header>
<div class="entry-content clearfix">
{{ blog.body| safe }}
</div>
</article>
{% if request.user.username %}
<section class="comment-area">
<h3>发表评论</h3>
<hr>
<form action="{% url 'comments:blog_comment' blog.pk %}" method="post">
{% csrf_token %}
<div class="form-group">
<label class="col-md-2">名字:</label>
{{ request.user.nikename }}
</div>
<div class="form-group">
<label class="col-md-2">邮箱:</label>
{{ request.user.email }}
</div>
<div class="form-group">
<label for="{{ form.text.id_for_label }}" class="col-md-2">评论:</label>
{{ form.text }}
{{ form.text.errors.0 }}
</div>
<div class="form-group">
<div class="col-md-offset-2">
<button type="submit" class="btn btn-default">发表</button>
</div>
</div>
{% endif %}
<div class="panel panel-default">
<div class="panel-heading">
<h5>评论列表,共 <span>{{comment_list|length}}</span> 条评论</h5>
</div>
<div class="panel-body">
<ul class="comment-list list-unstyled">
{% for comment in comment_list %}
<li>
<span style="color: #777;font-size: 14px;">{{ comment.name }} · </span>
<time style="color: #777;font-size: 14px;">{{ comment.created_time }}</time>
<div style="padding-top: 5px;font-size: 16px;">
{{ comment.text }}
</div>
</li>
{% empty %}
暂无评论
{% endfor %}
</ul>
</div>
</div>
</section>
{% endblock main %}
文章详细页面
博客系统首页展示的是所有文章的列表,当单击文章的标题后会跳转到文章的详细页面。Django开发流程是首先配置URL,URL与视图函数绑定在一起建立对应关系,然后在视图函数编写逻辑代码,视图函数把模板变量传给模板文件,对模板文件进行渲染显示。
文章详细页面URL配置
在项目首页HTML代码中每个文章标题是一个链接,单击标题可进入文章详细页面:
<a href="{{ blog.get_absolute_url }}">{{ blog.title }}</a>
上面href属性值是blog的get_absolute_url方法返回的URL,是通过反向解析detail的配置项得到的。
def get_absolute_url(self):
return reverse('blog:detail',kwargs={'pk':self.pk})
在blog/urls中加入:
re_path('blog/(?P<pk>[0-9]+)/',views.blogdetailview.as_view(),name='detail'),
文章详细页面视图
视图blogdetailview继承于通用类视图DetailView,这个类返回单条记录:
class blogdetailview(DetailView):
model = models.Blog
template_name = 'blog/detail.html'
# 指定主键,'pk'为配置文件中的URL参数名
pk_url_kwarg = 'pk'
# 重写父类的get_object(),可以返回主键等于URL参数值的记录对象
def get_object(self, queryset=None):
blog = super(blogdetailview, self).get_object(queryset=None)
blog.increase_views()
return blog
def get_context_data(self, **kwargs):
context = super(blogdetailview, self).get_context_data(**kwargs)
form = CommentForm()
comment_list = self.object.comment_set.all()
context.update({
'form':form,
'comment_list':comment_list,
})
return context
说明:pk_url_kwarg指定从哪个URL参数中取值,pk_url_kwarg属性与model属性共同确定一条记录。当model=models.Blog和pk_url_kwarg = 'pk'时,网页地址为http://127.0.0.1:8000/blog/19/,视图函数则会通过models.Blog.objects.get(id=19)取得一条记录,并把这条记录保存在模板变量blog中(context_object_name='blog')。
代码重写了get_object()方法,返回指定的主键值的记录对象,由于可以得到数据模型对象实例,我们可以在这个方法里对实例对象进行操作,包括调用它的方法。
代码重写了get_context_data方法,一般用它来增加模板变量,增加了form、comment_list分贝保存CommentForm表单对象、文章评论记录。
文章详细页模板文件
{% extends 'base.html' %}
{% block main %}
<article class="blog">
<header class="entry-header">
<h1 class="entry-title">{{ blog.title }}</h1>
<div class="entry-meta-detail">
<span class="blog-category"><a href="#">{{ blog.category.name }}</a></span>
<span class="blog-date"><a href="#"><time class="entry-date"
datetime="{{ blog.created_time }}">{{ blog.created_time }}</time></a></span>
<span class="blog-author"><a href="#">{{ blog.author.nikename }}</a></span>
<span class="comments-link"><a href="#">共 <span>{{ comment_list | length }}</span> 条评论</a></span>
<span class="views-count"><a href="#">{{ blog.views }} 阅读</a></span>
</div>
</header>
<div class="entry-content clearfix">
{{ blog.body| safe }}
</div>
</article>
{% if request.user.username %}
<section class="comment-area">
<h3>发表评论</h3>
<hr>
<form action="{% url 'comments:blog_comment' blog.pk %}" method="post">
{% csrf_token %}
<div class="form-group">
<label class="col-md-2">名字:</label>
{{ request.user.nikename }}
</div>
<div class="form-group">
<label class="col-md-2">邮箱:</label>
{{ request.user.email }}
</div>
<div class="form-group">
<label for="{{ form.text.id_for_label }}" class="col-md-2">评论:</label>
{{ form.text }}
{{ form.text.errors.0 }}
</div>
<div class="form-group">
<div class="col-md-offset-2">
<button type="submit" class="btn btn-default">发表</button>
</div>
</div>
{% endif %}
<div class="panel panel-default">
<div class="panel-heading">
<h5>评论列表,共 <span>{{comment_list|length}}</span> 条评论</h5>
</div>
<div class="panel-body">
<ul class="comment-list list-unstyled">
{% for comment in comment_list %}
<li>
<span style="color: #777;font-size: 14px;">{{ comment.name }} · </span>
<time style="color: #777;font-size: 14px;">{{ comment.created_time }}</time>
<div style="padding-top: 5px;font-size: 16px;">
{{ comment.text }}
</div>
</li>
{% empty %}
暂无评论
{% endfor %}
</ul>
</div>
</div>
</section>
{% endblock main %}