我们现在开始设计发布问答的界面与功能:
现在点击它还是没有反应的,我们要设计一个问答界面的html
,然后把发布问答链接过去。
首先编写一个视图函数如下:
@app.route('/question/')
def question():
return render_template('question.html')
在base.html
中为发布问答添加链接:
<li><a href="{{ url_for('question') }}">发布问答</a></li>
问答页面的html
首先也要继承导航条,然后主要界面设计如下:
这里还是利用css
加Bootstrap
样式完成的,问题描述区域是一个textarea
,就不演示代码了,我个人的经验是,对于html
的排版,要理解盒子模型,借助border
来查看元素的边框,去看看margin
和padding
的宽度,排版好了之后把边框去掉。
接下来是内容的提交,为视图函数提交POST
方法,并把提交的内容写入数据库,修改question
视图函数,如下:
@app.route('/question/', methods=['GET', 'POST'])
def question():
if request.method == 'GET':
return render_template('question.html')
else:
question_title = request.form.get('question_title')
question_desc = request.form.get('question_desc')
author_id = Users.query.filter(Users.username == session.get('username')).first().id
new_question = Questions(title=question_title, content=question_desc, author_id=author_id)
db.session.add(new_question)
db.session.commit()
return redirect(url_for('home'))
这里其实与用户注册的原理是一致的,此时已经能把填写的问题内容保存到数据库了。需要注意的就是,新建一个question
对象时,不仅需要title
和content
,还需要带上question
对应的author_id
,因为当初创建模型时这就是个非空字段。
代码中我们直接用session
中的username
去Users
模型中检索,来得到author_id
,看着很麻烦,而且在很多视图函数中,我们都需要用到当前登录用户的信息,因此可以使用@app.before_request
这个钩子函数,看其名字就很好理解,是在request
之前会自动运行的,我们在每次请求之前(或者说每次运行视图函数之前),都通过钩子函数来得到当期登录用户的User
对象(而不是仅仅是session
中的username
),然后在需要的地方使用它,代码如下:
@app.before_request
def my_before_request():
username = session.get('username')
if username:
g.user = Users.query.filter(Users.username == username).first()
这个钩子函数,从session
中获取当前登陆的username
,如果获取到了,再去检索Users
模型,把返回的user
对象存入到g
对象中,在视图函数中我们就可以直接使用这个user
对象的id/register_time
等字段了。此时前面的视图函数中的
author_id = Users.query.filter(Users.username == session.get('username')).first().id
可以修改成
author_id = g.user.id
此外,发布问题也需要用户先登录才可以,如果用户未登录,@app.before_request
无法获取到session
中的username
,此时g
对象就没有user
这个属性,因此我们再次把question
视图修改如下:
@app.route('/question/', methods=['GET', 'POST'])
def question():
if request.method == 'GET':
return render_template('question.html')
else:
if hasattr(g, 'user'):
question_title = request.form.get('question_title')
question_desc = request.form.get('question_desc')
author_id = g.user.id
new_question = Questions(title=question_title, content=question_desc, author_id=author_id)
db.session.add(new_question)
db.session.commit()
return redirect(url_for('home'))
else:
flash('请先登录')
return redirect(url_for('login'))
如果提交的时候未登录,则跳转到登陆页面,并且flash
'请先登录'的提示。其实也可以直接在get
方法的时候就直接跳转,避免用户写完了内容,又发现未登录,页面跳转导致内容丢失。我们先把框架搭起来,以后再逐步完善细节。
再注意到我们之前写的@app.context_processor
上下文管理器,也是从session
中取对象的,此时我们可以直接借用钩子函数中的数据里,因此将其改写如下:
@app.context_processor
def my_context_processor():
if hasattr(g, 'user'):
return {'login_user': g.user}
return {}
我们之前说过因为g
对象不能跨请求使用,因此在上下文管理器中用的是session
,为什么这里又用了g
对象呢?原因是现在有了钩子函数,每次请求都会执行钩子函数,向g
对象中写入user
,所以上下文管理器一直都能从g
对象中取到user
,不管这个g
对象是属于哪次请求的。