diff --git a/app/forms.py b/app/forms.py
index f85a757..5f3cfe6 100644
--- a/app/forms.py
+++ b/app/forms.py
@@ -46,3 +46,7 @@ class EditProfileForm(FlaskForm):
user = User.query.filter_by(username=self.username.data).first()
if user is not None:
raise ValidationError('Please use a different username.')
+
+
+class EmptyForm(FlaskForm):
+ submit = SubmitField('Submit')
diff --git a/app/models.py b/app/models.py
index 7e3d48c..b160ccb 100644
--- a/app/models.py
+++ b/app/models.py
@@ -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.about_me }} Last seen on: {{ user.last_seen }} {{ user.followers.count() }} followers, {{ user.followed.count() }} following.
+ User: {{ user.username }}
{% if user.about_me %}
+
+ {% endif %} diff --git a/migrations/versions/ae346256b650_followers.py b/migrations/versions/ae346256b650_followers.py new file mode 100644 index 0000000..31db09e --- /dev/null +++ b/migrations/versions/ae346256b650_followers.py @@ -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 ### diff --git a/tests.py b/tests.py new file mode 100755 index 0000000..ea99d5a --- /dev/null +++ b/tests.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python +import os +os.environ['DATABASE_URL'] = 'sqlite://' + +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): + self.app_context = app.app_context() + self.app_context.push() + db.create_all() + + def tearDown(self): + db.session.remove() + db.drop_all() + self.app_context.pop() + + 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)