Skip to content

Commit

Permalink
Chapter 10: User profiles (10a)
Browse files Browse the repository at this point in the history
  • Loading branch information
miguelgrinberg committed Jun 9, 2019
1 parent 97e2ef9 commit d578133
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 7 deletions.
13 changes: 7 additions & 6 deletions app/auth/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@

@auth.before_app_request
def before_request():
if current_user.is_authenticated \
and not current_user.confirmed \
and request.endpoint \
and request.blueprint != 'auth' \
and request.endpoint != 'static':
return redirect(url_for('auth.unconfirmed'))
if current_user.is_authenticated:
current_user.ping()
if not current_user.confirmed \
and request.endpoint \
and request.blueprint != 'auth' \
and request.endpoint != 'static':
return redirect(url_for('auth.unconfirmed'))


@auth.route('/unconfirmed')
Expand Down
9 changes: 8 additions & 1 deletion app/main/views.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
from flask import render_template
from flask import render_template, abort
from . import main
from ..models import User


@main.route('/')
def index():
return render_template('index.html')


@main.route('/user/<username>')
def user(username):
user = User.query.filter_by(username=username).first_or_404()
return render_template('user.html', user=user)
10 changes: 10 additions & 0 deletions app/models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from datetime import datetime
from werkzeug.security import generate_password_hash, check_password_hash
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
from flask import current_app
Expand Down Expand Up @@ -74,6 +75,11 @@ class User(UserMixin, db.Model):
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
password_hash = db.Column(db.String(128))
confirmed = db.Column(db.Boolean, default=False)
name = db.Column(db.String(64))
location = db.Column(db.String(64))
about_me = db.Column(db.Text())
member_since = db.Column(db.DateTime(), default=datetime.utcnow)

This comment has been minimized.

Copy link
@ezebunandu

ezebunandu Sep 10, 2020

I have had to gone back twice to double-check that I didn't miss out on anything. I noticed that the member_since attribute on the user profile always default to the current date. I was wondering if we could have somehow instead stored the datetime of user account creation and then retrieved this for the user profile view?

This comment has been minimized.

Copy link
@miguelgrinberg

miguelgrinberg Sep 10, 2020

Author Owner

That's exactly what this is. This member will be set when the account is created.

This comment has been minimized.

Copy link
@ezebunandu

ezebunandu Sep 11, 2020

I must have missed something then. The member_since on mine keeps resetting to whatever the date I'm running the app is.

This comment has been minimized.

Copy link
@miguelgrinberg

miguelgrinberg Sep 11, 2020

Author Owner

Try replacing your model definition with mine to see if that makes any difference.

This comment has been minimized.

Copy link
@ezebunandu

ezebunandu Sep 11, 2020

My code seems to match with yours. I created a new User today. I'll check to see what the member_since for that User will be by tomorrow.

last_seen = db.Column(db.DateTime(), default=datetime.utcnow)

def __init__(self, **kwargs):
super(User, self).__init__(**kwargs)
Expand Down Expand Up @@ -156,6 +162,10 @@ def can(self, perm):
def is_administrator(self):
return self.can(Permission.ADMIN)

def ping(self):
self.last_seen = datetime.utcnow()
db.session.add(self)

This comment has been minimized.

Copy link
@rocadev

rocadev Oct 7, 2020

just a question about 'ping' method: shouldn't be there a db.session.commit() to update last_seen field?

This comment has been minimized.

Copy link
@miguelgrinberg

miguelgrinberg Oct 7, 2020

Author Owner

Here you have two options. If you want to make sure every time the user sends a request the last_seen is updated, then yes, you should add a commit here. But this will generate a lot of additional commits. So you can also say that you are going to update the last_seen time and leave it in the session, so that it is committed if the request commits the session. It's really up to you and the needs of your application to decide which option works best.

This comment has been minimized.

Copy link
@rocadev

rocadev Oct 7, 2020

Thanks for the fast response and the informative reply, in most cases it makes sense to implement it along with the successful login request

This comment has been minimized.

Copy link
@rocadev

rocadev Oct 7, 2020

to have a bit more flexible ping method:

def ping(self, commit=False):
        self.last_seen = datetime.utcnow()
        db.session.add(self)
        if commit:
            db.session.commit()

def __repr__(self):
return '<User %r>' % self.username

Expand Down
3 changes: 3 additions & 0 deletions app/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a href="{{ url_for('main.index') }}">Home</a></li>
{% if current_user.is_authenticated %}
<li><a href="{{ url_for('main.user', username=current_user.username) }}">Profile</a></li>
{% endif %}
</ul>
<ul class="nav navbar-nav navbar-right">
{% if current_user.is_authenticated %}
Expand Down
22 changes: 22 additions & 0 deletions app/templates/user.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{% extends "base.html" %}

{% block title %}Flasky - {{ user.username }}{% endblock %}

{% block page_content %}
<div class="page-header">
<h1>{{ user.username }}</h1>
{% if user.name or user.location %}
<p>
{% if user.name %}{{ user.name }}{% endif %}
{% if user.location %}
from <a href="http://maps.google.com/?q={{ user.location }}">{{ user.location }}</a>
{% endif %}
</p>
{% endif %}
{% if current_user.is_administrator() %}
<p><a href="mailto:{{ user.email }}">{{ user.email }}</a></p>
{% endif %}
{% if user.about_me %}<p>{{ user.about_me }}</p>{% endif %}
<p>Member since {{ moment(user.member_since).format('L') }}. Last seen {{ moment(user.last_seen).fromNow() }}.</p>
</div>
{% endblock %}
34 changes: 34 additions & 0 deletions migrations/versions/d66f086b258_user_information.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""user information
Revision ID: d66f086b258
Revises: 56ed7d33de8d
Create Date: 2013-12-29 23:50:49.566954
"""

# revision identifiers, used by Alembic.
revision = 'd66f086b258'
down_revision = '56ed7d33de8d'

from alembic import op
import sqlalchemy as sa


def upgrade():
### commands auto generated by Alembic - please adjust! ###
op.add_column('users', sa.Column('about_me', sa.Text(), nullable=True))
op.add_column('users', sa.Column('last_seen', sa.DateTime(), nullable=True))
op.add_column('users', sa.Column('location', sa.String(length=64), nullable=True))
op.add_column('users', sa.Column('member_since', sa.DateTime(), nullable=True))
op.add_column('users', sa.Column('name', sa.String(length=64), nullable=True))
### end Alembic commands ###


def downgrade():
### commands auto generated by Alembic - please adjust! ###
op.drop_column('users', 'name')
op.drop_column('users', 'member_since')
op.drop_column('users', 'location')
op.drop_column('users', 'last_seen')
op.drop_column('users', 'about_me')
### end Alembic commands ###
19 changes: 19 additions & 0 deletions tests/test_user_model.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import unittest
import time
from datetime import datetime
from app import create_app, db
from app.models import User, AnonymousUser, Role, Permission

Expand Down Expand Up @@ -137,3 +138,21 @@ def test_anonymous_user(self):
self.assertFalse(u.can(Permission.WRITE))
self.assertFalse(u.can(Permission.MODERATE))
self.assertFalse(u.can(Permission.ADMIN))

def test_timestamps(self):
u = User(password='cat')
db.session.add(u)
db.session.commit()
self.assertTrue(
(datetime.utcnow() - u.member_since).total_seconds() < 3)
self.assertTrue(
(datetime.utcnow() - u.last_seen).total_seconds() < 3)

def test_ping(self):
u = User(password='cat')
db.session.add(u)
db.session.commit()
time.sleep(2)
last_seen_before = u.last_seen
u.ping()
self.assertTrue(u.last_seen > last_seen_before)

0 comments on commit d578133

Please sign in to comment.