基于flask的microBlog开发笔记(五)

5.用户首页和发布博客

我们已经完成了登录系统,则可以使用昵称和邮件登录,接下来要完成用户个人界面信息,在此之前先将数据库清空:

1
2
3
4
5
6
7
8
9
>>> users = User.query.all()
>>> for u in users:
... db.session.delete(u)
...
>>> posts = Post.query.all()
>>> for p in posts:
... db.session.delete(p)
...
>>> db.session.commit()

我们将创建用户信息页,显示用户信息以及最近的 blog。作为其中一部分,我们将会学习到显示用户头像。接着,我们将要用户 web 表单用来编辑用户信。

1.用户信息首页

创建一个用户信息不需要引入新的概念,只要创建一个新的视图函数以及与它配套的 HTML 模版。添加用户信息类,并定义用户信息字段修改(forms.py)文件

1
2
3
4
class AboutMeForm(Form):
describe = TextAreaField('about me', validators=[
Required(), Length(max=140)])
submit = SubmitField('YES!')

添加用户新信息的视图函数(app/views.py)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from forms import LoginForm,  SignUpForm, AboutMeForm

@app.route('/user/<int:user_id>', methods=["POST", "GET"])
@login_required
def users(user_id):
form = AboutMeForm()
user = User.query.filter(User.id == user_id).first()
if not user:
flash("The user is not exist.")
redirect("/index")
blogs = user.posts.all()

return render_template(
"user.html",
form=form,
user=user,
blogs=blogs)

用于这个视图函数的装饰器与之前的有些不同,在这个例子中,我们有一个参数在里面,用 <int: user_id> 来表示。这将转化为一个同名的参数添加到视图函数。比如当客户端以URL /user/1 请求的时候,视图函数将收到一个 user_id = 1 参数从而而被调用。
视图函数的实现没有让人惊喜的。首先,我们使用接收到参数 user_id 试着从数据库载入用户。如果没有找到用户的话,我们将会抛出错误信息,重定向到主页,我们还添加了@login_required装饰器,如果没有登陆的用户,向通过URL直接访问该页面,那么我们会直接在页面上报错,阻止其访问。若找到用户,将其传入到 render_template 调用,并且传入user.posts.all()找出的该用户的blogs;若如果没有找到用户,模板会显示小小的提示The user is not exist!,并跳转到主页。

用户信息页,创建文件app/templates/user.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{% extends "base.html" %}

{% block content %}
<p>Name: {{ user.nickname }}</p>
<p>Email: {{ user.email }}</p>

<hr>
{% if blogs | length %}
{% for blog in blogs %}
<p>{{ blog.body }}</p>
<p>{{ blog.timestamp.strftime("%a, %d %b %Y %H:%M:%S") }}</p>
<hr />
{% endfor %}
{% else %}
<p style="color:blue;">the guy is so lazy.....</p>
{% endif %}
{% endblock %}

用户信息页现在已经完成了,但是缺少对它的链接。为了让用户很容易地检查他的或者她的信息,我们直接把用户信息页的链接放在导航栏中修改文件( app/templates/base.html)

1
2
3
4
5
6
7
8
9
10
<div>Microblog:
<a href="{{ url_for('index') }}">Home</a>
{% if not current_user.is_authenticated() %}
| <a href="{{ url_for('login') }}">Log in</a>
or <a href="{{ url_for('sign_up') }}">Sign up</a>
{% else %}
| <a href="{{ url_for('users', user_id = current_user.id) }}">Profile</a>
| <a href="{{ url_for('logout') }}">Logout</a>
{% endif %}
</div>

2.发布博客

首先在forms.py文件中添加博客内容的字段:

1
2
3
class PublishBlogForm(Form):
body = TextAreaField('blog content', validators=[Required()])
submit = SubmitField('Submit')

而且要在app/views.py中加入如下函数

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
from string import strip
import datetime

from forms import LoginForm, SignUpForm, AboutMeForm, PublishBlogForm

@app.route('/publish/<int:user_id>', methods=["POST", "GET"])
@login_required
def publish(user_id):
form = PublishBlogForm()
posts = Post()
if form.validate_on_submit():
blog_body = request.form.get("body")
if not len(strip(blog_body)):
flash("The content is necessray!")
return redirect(url_for("publish", user_id=user_id))
posts.body = blog_body
posts.timestamp = datetime.datetime.now()
posts.user_id = user_id

try:
db.session.add(posts)
db.session.commit()
except:
flash("Database error!")
return redirect(url_for("publish", user_id=user_id))

flash("Publish Successful!")
return redirect(url_for("publish", user_id=user_id))

return render_template(
"publish.html",
form=form)

接收当前用户的user_id用于填充Post表的user_id字段,以便在用户主页显示该用户所属的blogs。为了防止blog内容为空,除了在forms.py里添加validator的限制外,我们还要在后台再一次对输入数据的验证,strip(blog_body)就是为了防止用户只输入空格的情况,它会将字符串两边的空格去掉,如果内容仅仅为空格的话,那么长度肯定是为0的,一旦这种事情发生了,就立即报错,并刷新当前页面。

将数据库的对应的字段赋值完毕之后,使用db.session.add(posts),db.session.commint()将值写入数据库中,因为操作数据库的时候可能会出现一些意想不到的问题,所以我们应该用try….except….来处理这些问题,提高适用性(app/publish.html)

1
2
3
4
5
6
7
8
9
{% extends "base.html" %}

{% block content %}
<form action="{{ url_for("publish", user_id=current_user.id) }}" method="POST" name="publish">
{{ form.hidden_tag() }}
<p>{{ form.body }}</p>
<p>{{ form.submit }}</p>
</form>
{% endblock %}

3.子模版

已经实现了用户信息页,它能够显示用户的 blog。首页也应该显示任何一个用户这个时候的 blog 。这样就需要有两个页需要显示用户的 blog,即要制作一个渲染 blog 的子模板,我们在使用它的模板中包含这个子模板(/app/templates/post.html)

1
2
3
4
5
<table>
<tr valign="top">
<td><img src="{{post.author.avatar(50)}}"></td><td><i>{{post.author.nickname}} says:</i><br>{{post.body}}</td>
</tr>
</table>

接着我们使用include命令在我们的用户模板中调用这个子模板(app/templates/user.html)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{% extends "base.html" %}

{% block content %}
<table>
<tr valign="top">
<td><h1>User: {{ user.nickname }}</h1></td>
<td><h1>User: {{ user.email }}</h1></td>
</tr>
</table>
<hr>
{% for post in posts %}
{% include 'post.html' %}
{% endfor %}
{% endblock %

4.用户自我介绍

用户自我说明可以显示在用户信息页上,因此用户会写一些自我介绍,并将它们显示在用户资料页上。也可以追踪每个用户访问页面的最后一次的时间,将把它显示在用户信息页上。为了增加这些,就必须开始修改数据库。更具体地说,就是必须在User 类上增加两个字段(app/models.py)

1
2
3
4
5
6
7
8
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
nickname = db.Column(db.String(15), index=True, unique=True)
email = db.Column(db.String(128), index=True, unique=True)
role = db.Column(db.SmallInteger, default=ROLE_USER)
posts = db.relationship('Post', backref='author', lazy='dynamic')
about_me = db.Column(db.String(140))
last_seen = db.Column(db.DateTime)

前面已经写过数据库的迁移,因此为了增加这两个新字段到数据库,需要运行升级脚本,若没有迁移的支持,也可以手动地编辑数据库,最差的方式就是删除表再重新创建。接着,修改用户信息页模板来展示这些字段(app/templates/user.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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
{% extends "base.html" %}

{% block content %}
<p>Name: {{ user.nickname }}</p>
<p>Email: {{ user.email }}</p>

{% if user.about_me %}
<p onclick="about_me()">about me: {{ user.about_me }}</p>
{% else %}
<p style="color:#4499EE;" onclick="about_me()">about me: I'm a person. ---- this info from the system.</p>
{% endif %}

<div id="aboutMe" style="display:none;">
<form action="{{ url_for('about_me', user_id=current_user.id) }}" method="POST">
{{ form.hidden_tag() }}
{{ form.describe }}
{{ form.submit }}
</form>
</div>

<p style="color:#4c4c4c;">last log: {{ user.last_seen.strftime("%a, %d %b %Y %H:%M:%S") }}</p>

<a href="{{ url_for('publish', user_id=user.id) }}">Want to publish blogs?</a>

<hr />
{% if blogs | length %}
{% for blog in blogs %}
<p>{{ blog.body }}</p>
<p>{{ blog.timestamp.strftime("%a, %d %b %Y %H:%M:%S") }}</p>
<hr />
{% endfor %}
{% else %}
<p style="color:blue;">the guy is so lazy.....</p>
{% endif %}

{% endblock %}

{% block js %}
<script>
function about_me() {
target = document.getElementById("aboutMe");
if (target.style.display == "block") {
target.style.display = "none";
} else {
target.style.display = "block";
}
}
</script>
{% endblock %}

在user.html中多出了一段js代码,这段js代码作用是点击about me的时候,弹出一个编辑框以便我们修改自己的个人描述,当然要在base.html中添加一个block:

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
<html>
<head>
{% if title %}
<title>{{title}} - microblog</title>
{% else %}
<title>microblog</title>
{% endif %}
</head>
<body>
<div>Microblog:
<a href="{{ url_for('index') }}">Home</a>
{% if not current_user.is_authenticated() %}
| <a href="{{ url_for('login') }}">Log in</a>
or <a href="{{ url_for('sign_up') }}">Sign up</a>
{% else %}
| <a href="{{ url_for('users', user_id = current_user.id) }}">Profile</a>
| <a href="{{ url_for('logout') }}">Logout</a>
{% endif %}
</div>
<hr />
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</body>

{% block js %}{% endblock %}
</html>

最后,当要输入新的个人信息时,击yes后,能将够刷新当前页面并且显示新的个人描述,则修改views.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@app.route('/user/about-me/<int:user_id>', methods=["POST", "GET"])
@login_required
def about_me(user_id):
user = User.query.filter(User.id == user_id).first()
if request.method == "POST":
content = request.form.get("describe")
if len(content) and len(content) <= 140:
user.about_me = content
try:
db.session.add(user)
db.session.commit()
except:
flash("Database error!")
return redirect(url_for("users", user_id=user_id))
else:
flash("Sorry, May be your data have some error.")
return redirect(url_for("users", user_id=user_id))

这里和原来写的不太一样,原来的表单提交都是在当前页面进行处理的,当点击yes后,会通过post的方式将数据发送到/user/about-me/2页面上去处理,所以使用request.method == “POST”进行判定之后,获取表单数据,当然也要判断content的长度,并进行相应的处理,最后跳转回用户主页面。