2

我们现在开始设计发布问答的界面与功能:

clipboard.png

现在点击它还是没有反应的,我们要设计一个问答界面的html,然后把发布问答链接过去。
首先编写一个视图函数如下:

@app.route('/question/')
def question():
    return render_template('question.html')

base.html中为发布问答添加链接:

<li><a href="{{ url_for('question') }}">发布问答</a></li>

问答页面的html首先也要继承导航条,然后主要界面设计如下:

clipboard.png

这里还是利用cssBootstrap样式完成的,问题描述区域是一个textarea,就不演示代码了,我个人的经验是,对于html的排版,要理解盒子模型,借助border来查看元素的边框,去看看marginpadding的宽度,排版好了之后把边框去掉。
接下来是内容的提交,为视图函数提交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对象时,不仅需要titlecontent,还需要带上question对应的author_id,因为当初创建模型时这就是个非空字段。
代码中我们直接用session中的usernameUsers模型中检索,来得到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对象是属于哪次请求的。


Harpsichord1207
538 声望44 粉丝

前路漫漫