Chapter 8: Followers (v0.8)
This commit is contained in:
		
							parent
							
								
									f54844763e
								
							
						
					
					
						commit
						b5778cd75b
					
				| 
						 | 
					@ -5,6 +5,13 @@ from flask_login import UserMixin
 | 
				
			||||||
from werkzeug.security import generate_password_hash, check_password_hash
 | 
					from werkzeug.security import generate_password_hash, check_password_hash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					followers = db.Table(
 | 
				
			||||||
 | 
					    'followers',
 | 
				
			||||||
 | 
					    db.Column('follower_id', db.Integer, db.ForeignKey('user.id')),
 | 
				
			||||||
 | 
					    db.Column('followed_id', db.Integer, db.ForeignKey('user.id'))
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class User(UserMixin, db.Model):
 | 
					class User(UserMixin, db.Model):
 | 
				
			||||||
    id = db.Column(db.Integer, primary_key=True)
 | 
					    id = db.Column(db.Integer, primary_key=True)
 | 
				
			||||||
    username = db.Column(db.String(64), index=True, unique=True)
 | 
					    username = db.Column(db.String(64), index=True, unique=True)
 | 
				
			||||||
| 
						 | 
					@ -13,6 +20,11 @@ class User(UserMixin, db.Model):
 | 
				
			||||||
    posts = db.relationship('Post', backref='author', lazy='dynamic')
 | 
					    posts = db.relationship('Post', backref='author', lazy='dynamic')
 | 
				
			||||||
    about_me = db.Column(db.String(140))
 | 
					    about_me = db.Column(db.String(140))
 | 
				
			||||||
    last_seen = db.Column(db.DateTime, default=datetime.utcnow)
 | 
					    last_seen = db.Column(db.DateTime, default=datetime.utcnow)
 | 
				
			||||||
 | 
					    followed = db.relationship(
 | 
				
			||||||
 | 
					        'User', secondary=followers,
 | 
				
			||||||
 | 
					        primaryjoin=(followers.c.follower_id == id),
 | 
				
			||||||
 | 
					        secondaryjoin=(followers.c.followed_id == id),
 | 
				
			||||||
 | 
					        backref=db.backref('followers', lazy='dynamic'), lazy='dynamic')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __repr__(self):
 | 
					    def __repr__(self):
 | 
				
			||||||
        return '<User {}>'.format(self.username)
 | 
					        return '<User {}>'.format(self.username)
 | 
				
			||||||
| 
						 | 
					@ -28,6 +40,25 @@ class User(UserMixin, db.Model):
 | 
				
			||||||
        return 'https://www.gravatar.com/avatar/{}?d=identicon&s={}'.format(
 | 
					        return 'https://www.gravatar.com/avatar/{}?d=identicon&s={}'.format(
 | 
				
			||||||
            digest, size)
 | 
					            digest, size)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def follow(self, user):
 | 
				
			||||||
 | 
					        if not self.is_following(user):
 | 
				
			||||||
 | 
					            self.followed.append(user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def unfollow(self, user):
 | 
				
			||||||
 | 
					        if self.is_following(user):
 | 
				
			||||||
 | 
					            self.followed.remove(user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def is_following(self, user):
 | 
				
			||||||
 | 
					        return self.followed.filter(
 | 
				
			||||||
 | 
					            followers.c.followed_id == user.id).count() > 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def followed_posts(self):
 | 
				
			||||||
 | 
					        followed = Post.query.join(
 | 
				
			||||||
 | 
					            followers, (followers.c.followed_id == Post.user_id)).filter(
 | 
				
			||||||
 | 
					                followers.c.follower_id == self.id)
 | 
				
			||||||
 | 
					        own = Post.query.filter_by(user_id=self.id)
 | 
				
			||||||
 | 
					        return followed.union(own).order_by(Post.timestamp.desc())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@login.user_loader
 | 
					@login.user_loader
 | 
				
			||||||
def load_user(id):
 | 
					def load_user(id):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -96,3 +96,35 @@ def edit_profile():
 | 
				
			||||||
        form.about_me.data = current_user.about_me
 | 
					        form.about_me.data = current_user.about_me
 | 
				
			||||||
    return render_template('edit_profile.html', title='Edit Profile',
 | 
					    return render_template('edit_profile.html', title='Edit Profile',
 | 
				
			||||||
                           form=form)
 | 
					                           form=form)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@app.route('/follow/<username>')
 | 
				
			||||||
 | 
					@login_required
 | 
				
			||||||
 | 
					def follow(username):
 | 
				
			||||||
 | 
					    user = User.query.filter_by(username=username).first()
 | 
				
			||||||
 | 
					    if user is None:
 | 
				
			||||||
 | 
					        flash('User {} not found.'.format(username))
 | 
				
			||||||
 | 
					        return redirect(url_for('index'))
 | 
				
			||||||
 | 
					    if user == current_user:
 | 
				
			||||||
 | 
					        flash('You cannot follow yourself!')
 | 
				
			||||||
 | 
					        return redirect(url_for('user', username=username))
 | 
				
			||||||
 | 
					    current_user.follow(user)
 | 
				
			||||||
 | 
					    db.session.commit()
 | 
				
			||||||
 | 
					    flash('You are following {}!'.format(username))
 | 
				
			||||||
 | 
					    return redirect(url_for('user', username=username))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@app.route('/unfollow/<username>')
 | 
				
			||||||
 | 
					@login_required
 | 
				
			||||||
 | 
					def unfollow(username):
 | 
				
			||||||
 | 
					    user = User.query.filter_by(username=username).first()
 | 
				
			||||||
 | 
					    if user is None:
 | 
				
			||||||
 | 
					        flash('User {} not found.'.format(username))
 | 
				
			||||||
 | 
					        return redirect(url_for('index'))
 | 
				
			||||||
 | 
					    if user == current_user:
 | 
				
			||||||
 | 
					        flash('You cannot unfollow yourself!')
 | 
				
			||||||
 | 
					        return redirect(url_for('user', username=username))
 | 
				
			||||||
 | 
					    current_user.unfollow(user)
 | 
				
			||||||
 | 
					    db.session.commit()
 | 
				
			||||||
 | 
					    flash('You are not following {}.'.format(username))
 | 
				
			||||||
 | 
					    return redirect(url_for('user', username=username))
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,8 +8,13 @@
 | 
				
			||||||
                <h1>User: {{ user.username }}</h1>
 | 
					                <h1>User: {{ user.username }}</h1>
 | 
				
			||||||
                {% if user.about_me %}<p>{{ user.about_me }}</p>{% endif %}
 | 
					                {% if user.about_me %}<p>{{ user.about_me }}</p>{% endif %}
 | 
				
			||||||
                {% if user.last_seen %}<p>Last seen on: {{ user.last_seen }}</p>{% endif %}
 | 
					                {% if user.last_seen %}<p>Last seen on: {{ user.last_seen }}</p>{% endif %}
 | 
				
			||||||
 | 
					                <p>{{ user.followers.count() }} followers, {{ user.followed.count() }} following.</p>
 | 
				
			||||||
                {% if user == current_user %}
 | 
					                {% if user == current_user %}
 | 
				
			||||||
                <p><a href="{{ url_for('edit_profile') }}">Edit your profile</a></p>
 | 
					                <p><a href="{{ url_for('edit_profile') }}">Edit your profile</a></p>
 | 
				
			||||||
 | 
					                {% elif not current_user.is_following(user) %}
 | 
				
			||||||
 | 
					                <p><a href="{{ url_for('follow', username=user.username) }}">Follow</a></p>
 | 
				
			||||||
 | 
					                {% else %}
 | 
				
			||||||
 | 
					                <p><a href="{{ url_for('unfollow', username=user.username) }}">Unfollow</a></p>
 | 
				
			||||||
                {% endif %}
 | 
					                {% endif %}
 | 
				
			||||||
            </td>
 | 
					            </td>
 | 
				
			||||||
        </tr>
 | 
					        </tr>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,33 @@
 | 
				
			||||||
 | 
					"""followers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Revision ID: ae346256b650
 | 
				
			||||||
 | 
					Revises: 37f06a334dbf
 | 
				
			||||||
 | 
					Create Date: 2017-09-17 15:41:30.211082
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					from alembic import op
 | 
				
			||||||
 | 
					import sqlalchemy as sa
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# revision identifiers, used by Alembic.
 | 
				
			||||||
 | 
					revision = 'ae346256b650'
 | 
				
			||||||
 | 
					down_revision = '37f06a334dbf'
 | 
				
			||||||
 | 
					branch_labels = None
 | 
				
			||||||
 | 
					depends_on = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def upgrade():
 | 
				
			||||||
 | 
					    # ### commands auto generated by Alembic - please adjust! ###
 | 
				
			||||||
 | 
					    op.create_table('followers',
 | 
				
			||||||
 | 
					    sa.Column('follower_id', sa.Integer(), nullable=True),
 | 
				
			||||||
 | 
					    sa.Column('followed_id', sa.Integer(), nullable=True),
 | 
				
			||||||
 | 
					    sa.ForeignKeyConstraint(['followed_id'], ['user.id'], ),
 | 
				
			||||||
 | 
					    sa.ForeignKeyConstraint(['follower_id'], ['user.id'], )
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    # ### end Alembic commands ###
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def downgrade():
 | 
				
			||||||
 | 
					    # ### commands auto generated by Alembic - please adjust! ###
 | 
				
			||||||
 | 
					    op.drop_table('followers')
 | 
				
			||||||
 | 
					    # ### end Alembic commands ###
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,92 @@
 | 
				
			||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					from datetime import datetime, timedelta
 | 
				
			||||||
 | 
					import unittest
 | 
				
			||||||
 | 
					from app import app, db
 | 
				
			||||||
 | 
					from app.models import User, Post
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UserModelCase(unittest.TestCase):
 | 
				
			||||||
 | 
					    def setUp(self):
 | 
				
			||||||
 | 
					        app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://'
 | 
				
			||||||
 | 
					        db.create_all()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def tearDown(self):
 | 
				
			||||||
 | 
					        db.session.remove()
 | 
				
			||||||
 | 
					        db.drop_all()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_password_hashing(self):
 | 
				
			||||||
 | 
					        u = User(username='susan')
 | 
				
			||||||
 | 
					        u.set_password('cat')
 | 
				
			||||||
 | 
					        self.assertFalse(u.check_password('dog'))
 | 
				
			||||||
 | 
					        self.assertTrue(u.check_password('cat'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_avatar(self):
 | 
				
			||||||
 | 
					        u = User(username='john', email='john@example.com')
 | 
				
			||||||
 | 
					        self.assertEqual(u.avatar(128), ('https://www.gravatar.com/avatar/'
 | 
				
			||||||
 | 
					                                         'd4c74594d841139328695756648b6bd6'
 | 
				
			||||||
 | 
					                                         '?d=identicon&s=128'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_follow(self):
 | 
				
			||||||
 | 
					        u1 = User(username='john', email='john@example.com')
 | 
				
			||||||
 | 
					        u2 = User(username='susan', email='susan@example.com')
 | 
				
			||||||
 | 
					        db.session.add(u1)
 | 
				
			||||||
 | 
					        db.session.add(u2)
 | 
				
			||||||
 | 
					        db.session.commit()
 | 
				
			||||||
 | 
					        self.assertEqual(u1.followed.all(), [])
 | 
				
			||||||
 | 
					        self.assertEqual(u1.followers.all(), [])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        u1.follow(u2)
 | 
				
			||||||
 | 
					        db.session.commit()
 | 
				
			||||||
 | 
					        self.assertTrue(u1.is_following(u2))
 | 
				
			||||||
 | 
					        self.assertEqual(u1.followed.count(), 1)
 | 
				
			||||||
 | 
					        self.assertEqual(u1.followed.first().username, 'susan')
 | 
				
			||||||
 | 
					        self.assertEqual(u2.followers.count(), 1)
 | 
				
			||||||
 | 
					        self.assertEqual(u2.followers.first().username, 'john')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        u1.unfollow(u2)
 | 
				
			||||||
 | 
					        db.session.commit()
 | 
				
			||||||
 | 
					        self.assertFalse(u1.is_following(u2))
 | 
				
			||||||
 | 
					        self.assertEqual(u1.followed.count(), 0)
 | 
				
			||||||
 | 
					        self.assertEqual(u2.followers.count(), 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_follow_posts(self):
 | 
				
			||||||
 | 
					        # create four users
 | 
				
			||||||
 | 
					        u1 = User(username='john', email='john@example.com')
 | 
				
			||||||
 | 
					        u2 = User(username='susan', email='susan@example.com')
 | 
				
			||||||
 | 
					        u3 = User(username='mary', email='mary@example.com')
 | 
				
			||||||
 | 
					        u4 = User(username='david', email='david@example.com')
 | 
				
			||||||
 | 
					        db.session.add_all([u1, u2, u3, u4])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # create four posts
 | 
				
			||||||
 | 
					        now = datetime.utcnow()
 | 
				
			||||||
 | 
					        p1 = Post(body="post from john", author=u1,
 | 
				
			||||||
 | 
					                  timestamp=now + timedelta(seconds=1))
 | 
				
			||||||
 | 
					        p2 = Post(body="post from susan", author=u2,
 | 
				
			||||||
 | 
					                  timestamp=now + timedelta(seconds=4))
 | 
				
			||||||
 | 
					        p3 = Post(body="post from mary", author=u3,
 | 
				
			||||||
 | 
					                  timestamp=now + timedelta(seconds=3))
 | 
				
			||||||
 | 
					        p4 = Post(body="post from david", author=u4,
 | 
				
			||||||
 | 
					                  timestamp=now + timedelta(seconds=2))
 | 
				
			||||||
 | 
					        db.session.add_all([p1, p2, p3, p4])
 | 
				
			||||||
 | 
					        db.session.commit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # setup the followers
 | 
				
			||||||
 | 
					        u1.follow(u2)  # john follows susan
 | 
				
			||||||
 | 
					        u1.follow(u4)  # john follows david
 | 
				
			||||||
 | 
					        u2.follow(u3)  # susan follows mary
 | 
				
			||||||
 | 
					        u3.follow(u4)  # mary follows david
 | 
				
			||||||
 | 
					        db.session.commit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # check the followed posts of each user
 | 
				
			||||||
 | 
					        f1 = u1.followed_posts().all()
 | 
				
			||||||
 | 
					        f2 = u2.followed_posts().all()
 | 
				
			||||||
 | 
					        f3 = u3.followed_posts().all()
 | 
				
			||||||
 | 
					        f4 = u4.followed_posts().all()
 | 
				
			||||||
 | 
					        self.assertEqual(f1, [p2, p4, p1])
 | 
				
			||||||
 | 
					        self.assertEqual(f2, [p2, p3])
 | 
				
			||||||
 | 
					        self.assertEqual(f3, [p3, p4])
 | 
				
			||||||
 | 
					        self.assertEqual(f4, [p4])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == '__main__':
 | 
				
			||||||
 | 
					    unittest.main(verbosity=2)
 | 
				
			||||||
		Loading…
	
		Reference in New Issue