Creating a user profile page does not really require any new major concepts to be introduced. We just need to create a new view function and its accompanying HTML template.
Here is the view function (file app/views.py):
@app.route('/user/<nickname>')
@login_required def user(nickname):
user = User.query.filter_by(nickname=nickname).first() if user == None:
flash('User %s not found.' % nickname) return redirect(url_for('index')) posts = [
{'author': user, 'body': 'Test post #1'}, {'author': user, 'body': 'Test post #2'}
]
return render_template('user.html', user=user, posts=posts)
The @app.route decorator that we used to declare this view function looks a little bit different than the previous ones. In this case we have an argument in it, which is indicated as <nickname>. This translates into an argument of the same name added to the view function. When the client requests, say, URL /user/miguel the view function will be invoked with nickname set to 'miguel'.
The implementation of the view function should have no surprises. First we try to load the user from the database, using the nickname that we received as argument. If that doesn't work then we just redirect to the main page with an error message, as we have seen in the previous chapter.
Once we have our user, we just send it in the render_template call, along with some fake posts.
Note that in the user profile page we will be displaying only posts by this user, so our fake posts have the author field correctly set.
Our initial view template will be extremely simple (file app/templates/user.html):
<!-- extend base layout -->
{% extends "base.html" %}
{% block content %}
<h1>User: {{ user.nickname }}!</h1>
<hr>
{% for post in posts %}
<p>
{{ post.author.nickname }} says: <b>{{ post.body }}</b>
</p>
{% endfor %}
{% endblock %}
The profile page is now complete, but a link to it does not exist anywhere in the web site. To make it a bit more easy for a user to check his or her own profile, we are going to add a link to it in the
navigation bar at the top (file `app/templates/base.html'):
<div>Microblog:
<a href="{{ url_for('index') }}">Home</a>
{% if g.user.is_authenticated() %}
| <a href="{{ url_for('user', nickname=g.user.nickname) }}">Your Profile</a>
| <a href="{{ url_for('logout') }}">Logout</a>
{% endif %}
</div>
Note how in the url_for function we have added the required nickname argument.
Give the application a try now. Clicking on the Your Profile link at the top should take you to your user page. Since we don't have any links that will direct you to an arbitrary user profile page you will need to type the URL by hand if you want to see someone else. For example, you would type http://localhost:5000/user/miguel to see the profile of user miguel.
Avatars
I'm sure you agree that our profile pages are pretty boring. To make them a bit more interesting, let's add user avatars.
Instead of having to deal with a possibly large collection of uploaded images in our server, we will rely on the Gravatar service to provide our user avatars.
Since returning an avatar is a user related task, we will be putting it in the User class (file app/models.py):
from hashlib import md5
# ...
class User(db.Model):
# ...
def avatar(self, size):
return 'http://www.gravatar.com/avatar/%s?d=mm&s=%d' % (md5(self.email.encode('utf-8')).hexdigest(), size)
The avatar method of User returns the URL of the user's avatar image, scaled to the requested size in pixels.
Turns out with the Gravatar service this is really easy to do. You just need to create an md5 hash of the user email and then incorporate it into the specially crafted URL that you see above. After the md5 of the email you can provide a number of options to customize the avatar. The d=mm determines what placeholder image is returned when a user does not have an Gravatar account. The mm option returns the "mystery man" image, a gray silhouette of a person. The s=N option requests the avatar scaled to the given size in pixels.
The Gravatar's website has documentation for the avatar URL.
Now that our User class knows how to return avatar images, we can incorporate them into our profile page layout (file app/templates/user.html):
<!-- extend base layout -->
{% extends "base.html" %}
{% block content %}
<table>
<tr valign="top">
<td><img src="{{ user.avatar(128) }}"></td>
<td><h1>User: {{ user.nickname }}</h1></td>
</tr>
</table>
<hr>
{% for post in posts %}
<p>
{{ post.author.nickname }} says: <b>{{ post.body }}</b>
</p>
{% endfor %}
{% endblock %}
The nice thing about making the User class responsible for returning avatars is that if some day we decide Gravatar avatars are not what we want, we just rewrite the avatar method to return different URLs (even ones that points to our own web server, if we decide we want to host our own avatars), and all our templates will start showing the new avatars automatically.
We have added the user avatar to the top of the profile page, but at the bottom of the page we have posts, and those could have a little avatar as well. For the user profile page we will of course be showing the same avatar for all the posts, but then when we move this functionality to the main page we will have each post decorated with the author's avatar, and that will look really nice.
To show avatars for the posts we just need to make a small change in our template (file
`app/templates/user.html'):
<!-- extend base layout -->
{% extends "base.html" %}
{% block content %}
<table>
<tr valign="top">
<td><img src="{{ user.avatar(128) }}"></td>
<td><h1>User: {{ user.nickname }}</h1></td>
</tr>
</table>
<hr>
{% for post in posts %}
<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>
{% endfor %}
{% endblock %}
Here is how our profile page looks at this point: