专注细节
努力进步

Flask-Profile page and avatars

Profile page and avatars

First, we will create the user profile page, which shows the user’s information and more recent blog posts, then we will create a web form for users to edit their profiles.

User Profile Page

Create a new view function “user” and its accompanying HTML template.

@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 different than the previous ones. In this case, we have an argument in it, which is indicated as . his translates into an argument of the same name added to the view function. When the client requests.
The logic of this user function is simple. First, we try to load the user from the database use the nickname. If that doesn’t work, we just redirect to the main page with an error message.

Our user.html template:

<!-- 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 %}

Now, a link to it does not exist anywhere in the web site. We add it in the 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>

Avatars

Instead of having to deal with a possibly large collection of uploaded images in our server, we wil rely on yhe Gravatar service to provide our user avatars.

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 requested size in pixels.

Now, we can incorporate them into our profile page layout.

<!-- 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 %}

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.

<!-- 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 %}

Reusing at the sub-template level

We designed the user profile page so that it displays the posts written by the user. Our index page also displays posts, this time of any user. So now we have two templates that will need to display posts made by users. We could just copy/paste the portion of the template that deals with the rendering of a post, but that is really not ideal, because when we decide to change the layout of a post we’ll have to remember to go update all the templates that have posts in them.

Instead, we are going to make a sub-template that just renders a post, then we’ll include it in all the templates that need it.

To start, we create a post sub-template:

<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>

Invoke this sub-template from our user template using Jinjia2’s include command:

<!-- 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 %}
      {% include 'post.html' %}
  {% endfor %}
{% endblock %}

More interesting profiles

If we want to add some information about me in our profile page(Self-introduction and last visit time).

To add this, we have to modify our database.

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    nickname = db.Column(db.String(64), index=True, unique=True)
    email = db.Column(db.String(120), index=True, unique=True)
    posts = db.relationship('Post', backref='author', lazy='dynamic')
    about_me = db.Column(db.String(140))
    last_seen = db.Column(db.DateTime)

Once, we modify the database, run db_migrate.py to apply these changes.

Next, let’s modify our profile template to show these fields.

<!-- 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>
          {% if user.about_me %}<p>{{ user.about_me }}</p>{% endif %}
          {% if user.last_seen %}<p><i>Last seen on: {{ user.last_seen }}</i></p>{% endif %}
      </td>
    </tr>
  </table>
  <hr>
  {% for post in posts %}
    {% include 'post.html' %}
  {% endfor %}
{% endblock %}

The last_seen field is pretty easy to support. Remember that in a previous chapter we created a before_request handler, to register the logged in user with the flask.g global, as g.user. That is the perfect time to update our database with the last access time for a user:

from datetime import datetime
# ...
@app.before_request
def before_request():
    g.user = current_user
    if g.user.is_authenticated():
        g.user.last_seen = datetime.utcnow()
        db.session.add(g.user)
        db.session.commit()

To display the user’s about me information we have to give them a place to enter it, and the proper place for this is in the edit profile page.

Editing the profile information

from flask.ext.wtf import Form
from wtforms import StringField, BooleanField, TextAreaField
from wtforms.validators import DataRequired, Length

class EditForm(Form):
    nickname = StringField('nickname', validators=[DataRequired()])
    about_me = TextAreaField('about_me', validators=[Length(min=0, max=140)])

Then in the view template:

<!-- extend base layout -->
{% extends "base.html" %}

{% block content %}
  <h1>Edit Your Profile</h1>
  <form action="" method="post" name="edit">
      {{form.hidden_tag()}}
      <table>
          <tr>
              <td>Your nickname:</td>
              <td>{{ form.nickname(size=24) }}</td>
          </tr>
          <tr>
              <td>About yourself:</td>
              <td>{{ form.about_me(cols=32, rows=4) }}</td>
          </tr>
          <tr>
              <td></td>
              <td><input type="submit" value="Save Changes"></td>
          </tr>
      </table>
  </form>
{% endblock %}

Finally we write the view function:

from forms import LoginForm, EditForm

@app.route('/edit', methods=['GET', 'POST'])
@login_required
def edit():
    form = EditForm()
    if form.validate_on_submit():
        g.user.nickname = form.nickname.data
        g.user.about_me = form.about_me.data
        db.session.add(g.user)
        db.session.commit()
        flash('Your changes have been saved.')
        return redirect(url_for('edit'))
    else:
        form.nickname.data = g.user.nickname
        form.about_me.data = g.user.about_me
    return render_template('edit.html', form=form)

Add a link in the user template:

<!-- 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>
              {% if user.about_me %}<p>{{ user.about_me }}</p>{% endif %}
              {% if user.id == g.user.id %}<p><a href="{{ url_for('edit') }}">Edit</a></p>{% endif %}
              {% if user.last_seen %}<p><i>Last seen on: {{ user.last_seen }}</i></p>{% endif %}
          </td>
      </tr>
  </table>
  <hr>
  {% for post in posts %}
      {% include 'post.html' %}
  {% endfor %}
{% endblock %}
分享到:更多 ()