目录
上节回顾
在上一章中,我们已经完成了登录系统,因此我们可以使用昵称或邮件登录以及登出。
今天,我们将要完成个人信息页。首先,我们将创建用户信息页,显示用户信息以及最近的 blog。作为其中一部分,我们将会学习到显示用户头像。接着,我们将要用户 web 表单用来编辑用户信息
一、用户信息首页
创建一个用户信息不需要引入新的概念。我们只要创建一个新的视图函数以及与它配套的 HTML 模版。
添加用户信息类,并定义用户信息字段(修改forms.py文件):
class AboutMeForm(Form):
describe = TextAreaField('about me', validators=[
Required(), Length(max=140)])
submit = SubmitField('YES!')
添加用户新信息的视图函数(文件 app/views.py
)
@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
)
{% 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)
<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>
试试应用程序吧。注册登陆后点击导航栏中的个人资料链接,会把你带到用户信息页。但是我们还没有添加博客发布页面,所以可能会出现:the guy is so lazy.....
![10012523_DvKy.png](https://static.oschina.net/uploads/img/201709/10012523_DvKy.png)
二、博客发布
好的,我们就下来就来实现 publish blogs 的功能。
首先在 forms.py
文件中添加博客内容的字段:
class PublishBlogForm(Form):
body = TextAreaField('blog content', validators=[Required()])
submit = SubmitField('Submit')
并我们需要在app/views.py中加入如下函数:
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....
来处理这些问题,提高适用性。
publish的页面十分的简单,只是需要一个TextAreaFiled,和submit就行了。(app/publish.html)
{% 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 %}
效果图如下:
![10012524_emZm.png](https://static.oschina.net/uploads/img/201709/10012524_emZm.png)
三、在子模板中重用
1. 非常实用的小技巧
我们已经实现了用户信息页,它能够显示用户的 blog。我们的首页也应该显示任何一个用户这个时候的 blog 。这样我们有两个页需要显示用户的 blog。当然我们可以直接拷贝和复制处理渲染 blog 的模板,但这不是最理想的。因为当我们决定要修改 blog 的布局的时候,我们要更新所有使用它的模板。
相反,我们将要制作一个渲染 blog 的子模板,我们在使用它的模板中包含这个子模板。
我们创建一个 blog 的子模板,这是一个再普通不过的模板(文件 /app/templates/post.html):
<table>
<tr valign="top">
<td>![]({{post.author.avatar(50)}})</td><td><i>{{post.author.nickname}} says:</i><br>{{post.body}}</td>
</tr>
</table>
接着我们使用 Jinja2 的 include 命令在我们的用户模板中调用这个子模板(文件app/templates/user.html)
{% 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 %}
一旦我们有一个功能上完全实现的首页,我们将会调用这个子模板,但是现在不准备这么做,将会把它留在后面的章节。
四、更多有趣的信息
尽然我们现在已经有一个不错的用户信息页,我们还有更多的信息需要在上面显示。像用户自我说明可以显示在用户信息页上,因此我们将会让用户写一些自我介绍,并将它们显示在用户资料页上。我们也将追踪每个用户访问页面的最后一次的时间,因此我们将会把它显示在用户信息页上。
为了增加这些,我们必须开始修改数据库。更具体地说,我们必须在我们的 User 类上增加两个字段(文件 app/models.py)
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)
前面的章节我们已经讲述过数据库的迁移。因此为了增加这两个新字段到数据库,需要运行升级脚本
python db_migrate.py
脚本会返回如下信息:
New migration saved as db_repository/versions/003_migration.py
Current database version: 3
我们的两个新字段加入到我们的数据库。如果我们没有迁移的支持,我们必须手动地编辑数据库,最差的方式就是删除表再重新创建。接着,让我们修改用户信息页模板来展示这些字段(文件 app/templates/user.html):
{% 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:
<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:
@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的长度,并进行相应的处理,最后跳转回用户主页面。
![10012525_TGht.png](https://static.oschina.net/uploads/img/201709/10012525_TGht.png)