Chapter 8: Followers (v0.8)
This commit is contained in:
		
							parent
							
								
									36a393c816
								
							
						
					
					
						commit
						e09c956da0
					
				| 
						 | 
				
			
			@ -5,6 +5,13 @@ from flask_login import UserMixin
 | 
			
		|||
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):
 | 
			
		||||
    id = db.Column(db.Integer, primary_key=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')
 | 
			
		||||
    about_me = db.Column(db.String(140))
 | 
			
		||||
    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):
 | 
			
		||||
        return '<User {}>'.format(self.username)
 | 
			
		||||
| 
						 | 
				
			
			@ -28,6 +40,25 @@ class User(UserMixin, db.Model):
 | 
			
		|||
        return 'https://www.gravatar.com/avatar/{}?d=identicon&s={}'.format(
 | 
			
		||||
            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
 | 
			
		||||
def load_user(id):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -96,3 +96,35 @@ def edit_profile():
 | 
			
		|||
        form.about_me.data = current_user.about_me
 | 
			
		||||
    return render_template('edit_profile.html', title='Edit Profile',
 | 
			
		||||
                           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>
 | 
			
		||||
                {% if user.about_me %}<p>{{ user.about_me }}</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 %}
 | 
			
		||||
                <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 %}
 | 
			
		||||
            </td>
 | 
			
		||||
        </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