页面功能开发
之前有说过,该在线问答系统分为两种用户功能,一个是注册用户,一个游客
注册用户:回答,发布问题,评论,编辑我的.
游客:浏览页面功能
今天就让我们先来实现游客所能浏览的页面,问题列表页面和问题详情页面
1. 问题列表页面功能开发(分页功能未完成)
1.1 使用模板语法将详情页改造
这个我们之前已经改造了的
1.2 将需要显示的问题列表信息从数据库种取出
qa-online/templates/qa/view.py
@qa.route('/follow')
def follow():
""" 关注 """
qa_list = Question.query.filter_by(is_valid=True).all()# 从question表中去有效数据
# 不要忘记在导入:from model import Question
return render_template('follow.html',qa_list=qa_list)# 将取出的问题列表传入`follow.html`页面中
1.3 页面显示
页面显示只显示主要功能开发
qa-online/templates/qa/templates/follow.html
<div class="qa-ls">
{% for question in qa_list %}
<div class="qa-item">
<a href="{{ url_for('qa.detail',q_id = question.id) }}" class="title">{{ question.title }}</a>
<div class="desc">
{% if question.img %}
<div class="left">
<img src="{{ question.img }}" alt="" srcset="">
</div>
{% endif %}
<div class="right">
<p class="show-desc">{{ question.desc |d("暂无描述",True)}}...
<span class="more">阅读原文<i class="glyphicon glyphicon-menu-down"></i></span>
</p>
<p class="show-all hidden">
{% autoescape false %}
{{ question.content }}
{% endautoescape %}
<span class="more">收起<i class="glyphicon glyphicon-menu-up"></i></span>
</p>
</div>
</div>
<div class="qa-footer">
<div>
<button type="button" class="btn btn-info btn-sm">关 注</button>
</div>
<div class="txt-menu"><i class="glyphicon glyphicon-comment"></i> {{ question.comment_count|d('0',True) }}条评论</div>
<div class="txt-menu"><i class="glyphicon glyphicon-send"></i>分享</div>
<div class="txt-menu"><i class="glyphicon glyphicon-flag"></i>举报</div>
<div class="txt-menu btn-group">
<i class="glyphicon glyphicon-option-horizontal dropdown-toggle" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false"></i>
<ul class="dropdown-menu">
<li><a href="#">不感兴趣</a></li>
</ul>
</div>
</div>
</div>
{% endfor %}
</div>
- 关闭自动转义
自动转义是指自动对特殊字符进行转义。特殊字符是指 HTML ( 或 XML 和 XHTML )中的 & 、 > 、 < 、 " 和 ’ 。因为这些特殊字 符代表了特殊的意思,所以如果要在文本中使用它们就必须把它们替换为“实 体”。如果不转义,那么用户就无法使用这些字符,而且还会带来安全问题。
-
有三种方法可以控制自动转义
- 在 Python 代码中,可以在把 HTML 字符串传递给模板之前,用 Markup 对象封装。一般情况下推荐使用这个方法。
- 在模板中,使用 |safe 过滤器显式把一个字符串标记为安全的 HTML
{{question.content|safe}}
- 临时关闭整个系统的自动转义。
在模板中关闭自动转义系统可以使用 {% autoescape %} 块:
{% autoescape false %} {{ question.content }} {% endautoescape %}
- 评论数量显示
<div class="txt-menu"><i class="glyphicon glyphicon-comment"></i> {{ question.comment_count|d('0',True) }}条评论</div>
在上述代码中涉及了question.comment_count
,那个它是怎么来的呢?不要着急,请看下面
qa-online/model.py
class Question(db.Model):
""" 问题 """
__tablename__ = 'qa_question'
id = db.Column(db.Integer, primary_key=True) # 主键
# 问题标题
title = db.Column(db.String(128), nullable=False)
# 问题描述
desc = db.Column(db.String(256))
# 问题图片
img = db.Column(db.String(256))
# 问题详情
content = db.Column(db.Text, nullable=False)
# 浏览人数
view_count = db.Column(db.Integer, default=0)
# 逻辑删除
is_valid = db.Column(db.Boolean, default=True)
# 排序
reorder = db.Column(db.Integer, default=0)
# 创建时间
created_at = db.Column(db.DateTime, default=datetime.now)
# 最后修改的时间
updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now)
# 关联用户
user_id = db.Column(db.Integer, db.ForeignKey('accounts_user.id'))
# 建立与用户的一对多属性,user.question_list
user = db.relationship('User', backref=db.backref('question_list', lazy='dynamic'))
@property
def comment_count(self):
""" 评论数量 """
return self.question_comment_list.filter_by(is_valid=True).count()
class AnswerComment(db.Model):
""" 回答的评论 """
__tablename__ = 'qa_answer_comment'
id = db.Column(db.Integer, primary_key=True) # 主键
# 评论内容
content = db.Column(db.String(512), nullable=False)
# 赞同人数
love_count = db.Column(db.Integer, default=0)
# 评论是否公开
is_public = db.Column(db.Boolean, default=True)
# 逻辑删除
is_valid = db.Column(db.Boolean, default=True)
# 创建时间
created_at = db.Column(db.DateTime, default=datetime.now)
# 最后修改的时间
updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now)
# 回复ID
reply_id = db.Column(db.Integer, db.ForeignKey('qa_answer_comment.id'), nullable=True)
# 关联用户
user_id = db.Column(db.Integer, db.ForeignKey('accounts_user.id'))
# 关联答案
answer_id = db.Column(db.Integer, db.ForeignKey('qa_answer.id'))
# 关联问题
q_id = db.Column(db.Integer, db.ForeignKey('qa_question.id'))
# 建立与用户的一对多属性
user = db.relationship('User', backref=db.backref('answer_comment_list', lazy='dynamic'))
# 建立与答案的一对多属性
answer = db.relationship('Answer', backref=db.backref('answer_comment_list', lazy='dynamic'))
# 建立与问题的一对多属性
question = db.relationship('Question', backref=db.backref('question_comment_list', lazy='dynamic'))
我们需要在ORM模型model.py
中填写comment_count
def comment_count(self):
return self.question_comment_list.filter_by(is_valid_.count()# 其中`question_comment_list`是我们之前建立数据表是绑定外键关系,
2. 问题详情页面功能开发
问题详情页面比问题列表页面稍微复杂一点点.这里我们就进行简写
和问题列表页面一样,我们的第一步是将详情页面使用模板语法进行改造,这一步我们是已经完成了的
2.1 将需要展示评论,关注的信息从数据库中取出
如果我们需要查看一个问题的详情,首先的知道是那个问题,所以我们写问题列表
页面是有这样一个操作
<a href="{{ url_for('qa.detail',q_id = question.id) }}" class="title">{{ question.title }}</a>
所以
qa-online/templates/qa/view.py
@qa.route('/detail/<int:q_id>')
def detail(q_id):
""" 问题详情 """
# 1. 查询问题信息
qa_info = Question.query.get(q_id)# 根据主键取出问题信息
if not qa_info.is_valid:
abort(404)# 不要忘记导入abort :from falsk import abort
return render_template('detail.html',question=qa_info)
```python
qa-online/templates/qa/templates/detail.html
```html
<h3>{{ question.title }}</h3>
<div class="show-desc ">
{{ question.desc|d('暂无', True) }}…
<span class="btn-show-more">显示全部<i class="glyphicon glyphicon-menu-down"></i></span>
</div>
<div class="show-all hidden">
{% autoescape false %}
{{ question.content|safe }}
{% endautoescape %}
<span class="btn-show-less">收起<i class="glyphicon glyphicon-menu-up"></i></span>
</div>
<!-- 菜单栏 -->
<div class="qa-footer">
<div>
<button type="button" class="btn btn-info btn-sm">关 注</button>
</div>
<div>
<button type="button" class="btn btn-info btn-sm" data-toggle="modal" data-target="#addComment">
写回答
</button>
</div>
<div class="txt-menu"><i class="glyphicon glyphicon-comment"></i> {{ question.comment_count|d('0',True) }}条评论
</div>
<div class="txt-menu"><i class="glyphicon glyphicon-send"></i>分享</div>
<div class="txt-menu"><i class="glyphicon glyphicon-flag"></i>举报</div>
<div class="txt-menu"></div>
<div class="txt-menu btn-group">
<i class="glyphicon glyphicon-option-horizontal dropdown-toggle" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false"></i>
<ul class="dropdown-menu">
<li><a href="#">不感兴趣</a></li>
</ul>
</div>
</div>
<!-- //菜单栏 -->
</div>
<div class="col-md-3">
<div class="que-stats-box">
<!-- 关注者 -->
<div>
<span>关注者</span>
<strong>{{ question.follow_count|number_split }}</strong>
</div>
<!-- //关注者 -->
<!-- 被浏览 -->
<div>
<span>被浏览</span>
<strong>{{ question.view_count|number_split }}</strong>
</div>
<!-- //被浏览 -->
</div>
</div>
- 关注者数量
关注者数量,我们可以qa_question_follow
中来取得,因为它和question
表有进行外键管理和反向,
# 关联问题
q_id = db.Column(db.Integer, db.ForeignKey('qa_question.id'))
# 建立与问题的一对多属性
question = db.relationship('Question', backref=db.backref('question_follow_list', lazy='dynamic'))
所以我们可以通过question_follow_list
来进行操作,在 question表下写入
:
@property
def follow_count(self):
""" 关注数量 """
return self.question_follow_list.filter_by(is_valid=True).count()
- 关注数量和浏览次数的数字格式化
其中,我们的关注数量和浏览次数在页面显示情况应该是:比如:1234566
===>1,234,566
所以在这里我们说使用python中str.format()格式数字
来进行操作
print("{:,2f}".format(3.14159265))# 3.14 ==> 保留小数点两位
通过上面str.format
可知道如何来进行数字格式化
<span>被浏览</span>
<strong>{{ "{:,}".format(question.view_count) }}</strong>
为了方便操作,我们将其封装到工具类中utils
中的filter.py
qa-online/utils/filter.py
def number_split(num):
"""
数字格式化
12345678 => 12,345,678
:param num: 需要格式化的数字
:return: 格式化后的字符串
"""
return '{:,}'.format(int(num))
但是,光这样还不行,应该在我们的detail.html
中无法知道number_split()
这个方法是怎么来的?
所以我们需要在app.py
中进行注册过滤器
from utils.filter import number_split
# 注册
app.jinja_env.filters['number_split']=number_split
所以这样,我们就可以在detail.html
页面中写
<!-- 关注者 -->
<div>
<span>关注者</span>
<strong>{{ question.follow_count|number_split }}</strong>
</div>
<!-- //关注者 -->
<!-- 被浏览 -->
<div>
<span>被浏览</span>
<strong>{{ question.view_count|number_split }}</strong>
</div>
<!-- //被浏览 -->
而下一部分:主要是显示该问题的会带,但是在这里我们只显示第一条问答和第一条回答的评论内容.
2.2 取出第一条回答内容并展示剩余多少回答
- 首先我们得从输出库中取出该问题的第一条回答.
view.py
answer =qa_info.answer_list.filter_by(is_valid=True).first()#
return render_template('detail.html',question=qa_info,answer=answer)
detail.html
2. 展示剩余多少条回答
<a class="link-more" href="#">查看全部 {{ question.answer_count|number_split }}个回答</a>
- answer_count
@property
def answer_count(self):
return self.answer_list.filter_by(is_valid=True).count()
- 第一条回答显示
detail.html
<div class="answer-content box-wrap">
<div class="user-info">
<div class="avater">
{% if answer.user.avatar %}
<img src="{{ answer.user.avatar }}" alt="用户头像">
{% else %}
<img src="/assets/home/qa/user_head.jpg" alt="用户头像">
{% endif %}
</div>
<div class="desc">
<h4>{{ answer.user.nickname }}</h4>
<p>《互联网营销人实战手记》新书上市; 公众号:舒大克。</p>
</div>
</div>
<div class="answer-stats">5,550 人赞同了该回答</div>
<div class="answer-txt">
{% autoescape false %}
{{ answer.content|safe }}
{% endautoescape %}
</div>
<div class="answer-time">发布于{{ answer.updated_at|dt_format_show}}</div>
<!-- 底部菜单 -->
<div class="qa-footer">
<div>
<button type="button" class="btn btn-info btn-sm">
<i class="glyphicon glyphicon-thumbs-up"></i> 赞同 1780
</button>
<button type="button" class="btn btn-info btn-sm"><i
class="glyphicon glyphicon-thumbs-down"></i></button>
</div>
<div class="txt-menu"><i
class="glyphicon glyphicon-comment"></i> {{ answer.answer_comment_count |d('0',True)}}条评论
</div>
<div class="txt-menu"><i class="glyphicon glyphicon-send"></i>分享</div>
<div class="txt-menu"><i class="glyphicon glyphicon-heart"></i>收藏</div>
<div class="txt-menu"><i class="glyphicon glyphicon-flag"></i>举报</div>
<div class="txt-menu"></div>
<div class="txt-menu btn-group">
<i class="glyphicon glyphicon-option-horizontal dropdown-toggle" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false"></i>
<ul class="dropdown-menu">
<li><a href="#">不感兴趣</a></li>
</ul>
</div>
</div>
- 用户头像,昵称
为什么我们取到到时第一条回答的内容,怎么去显示用户头像和昵称等用户的信息呢?
这里我也不知道怎么来解释,大概就是因为我们Answer
表它有绑定user.id的外键,直接上代码吧
# 关联用户
user_id = db.Column(db.Integer, db.ForeignKey('accounts_user.id'))
# 建立与用户的一对多属性
user = db.relationship('User', backref=db.backref('answer_list', lazy='dynamic'))# 因为backref,所以我们在answer表是有了user这个键,通过它在去访问`user`表中的信息.
比如
<div class="user-info">
<div class="avater">
{% if answer.user.avatar %}
<img src="{{ answer.user.avatar }}" alt="用户头像">
{% else %}
<img src="/assets/home/qa/user_head.jpg" alt="用户头像">
{% endif %}
</div>
<div class="desc">
<h4>{{ answer.user.nickname }}</h4>
- 时间格式化
这里的时间格式化和我们上面讲的不同,它的具体显是:几分钟前,1小时前
这里我们使用timeago
# 1. 下载timeago
pip install timegao
# 2. 使用,导入
import timeago # timeago的用法这里不详细讲了哈
我们讲格式化的时间的函数和之前一样都卸载utils
工具类中的filter.pyt
def dt_format_show(dt):
"""
日期和时间,格式化显示
3分钟前
1小时前
:param dt: datetime 时间
"""
now = datetime.now()
return timeago.format(dt, now, 'zh_CN')
同样的,我们需要在app.py
中进行注册
from utils.filter import dt_format_show
# 注册过滤器
app.jinja_env.filters['dt_format_show'] = dt_format_show
3. 页面展示
-
问题列表页面
-
问题详情页面