1. 팔로워 관련 테이블 생성

# vim app/models.py

# ...

# 팔로워 테이블 폼
# (외래 키 이외의 데이터가 없는 보조 테이블이기 때문에 모델 클래스 없이 생성함)
followers = db.Table('followers',
    db.Column('follower_id', db.Integer, db.ForeignKey('user.id')),
    db.Column('followed_id', db.Integer, db.ForeignKey('user.id'))
)

# ...

 

 

 

2. 다 대 다 팔로워 관계 설정

class User(UserMixin, db.Model):
    # ...
    
    # 다 대 다 팔로워 관계
    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')

 

 

 

3. 데이터베이스 변경사항 새 데이터베이스 마이그레이션에 기록

$ flask db migrate -m "followers"

$ flask db upgrade

 

 

 

4. 팔로워 추가 및 제거 함수 작성

# vim app/models.py

# ...

class User(UserMixin, db.Model):
    # ...
    
    # user가 self에 follow하기
    def follow(self, user):
        if not self.is_following(user): # follow 상태가 아닌지 확인
            self.followed.append(user)  # user가 self에 follow
    # user가 self에 되어있는 follow 제거
    def unfollow(self, user):
        if self.is_following(user):     # follow 상태인지 확인
            self.followed.remove(user)  # user가 self에 follow
    # 두 사용자 간의 링크가 존재하는지 확인
    def is_following(self, user):
        return self.followed.filter(
            followers.c.followed_id == user.id).count() > 0

# ...

 

 

 

5. 사용자의 게시물 + 사용자의 팔로워 게시물 가져오기

# vim app/models.py

# ...

Class User(UserMixin, db.Model):
    # ...
    # 글 게시자를 팔로우하는 유저들을 찾아 그 유저들 중 '나'인 게시글들 + 내 게시글들>을 시간 순서로 가져옴
    def followed_posts(self):
        # 내가 팔로우하는 사람의 게시글 followed에 넣기
        followed =  Post.query.join(
            followers, (followers.c.followed_id == Post.user_id)).filter(
                followers.c.follower_id == self.id)
        # 내 게시글들 own에 넣기
        own = Post.query.filter_by(user_id=self.id)
        return followed.union(own).order_by(Post.timestamp.desc())
        
# ...

 

 

 

6. 각종 기능들 test 파일 작성

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)

 

 

 

7. 각종 기능들 test 파일 실행

$ python tests.py

 

 

 

8. (언)팔로우 요청 처리

# vim app/routes.py

# ...

# 팔로우 routes 설정
# username을 팔로우 시도
@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))
# 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))

 

 

 

9. follow / unfollow 버튼 추가

user 웹페이지 설정하여 follow 관련 버튼 추가

아래 스크린샷은 추가된 부분이며, 전체 코드는 코드박스를 참고해주세요.

<!-- vim app/templates/user.html -->

{% extends "base.html" %}

{% block content %}
    <table>
        <tr valign="top">
            <td><img src="{{ user.avatar(128) }}"></td>
            <!-- 유저명에 + 유저 정보와 마지막 접속 시간 정보 출력 -->
            <td>
                <!-- 유저명 출력 -->
                <h1>User: {{ user.username }}</h1></td>
                {% 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 == 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>
    </table>
    <hr>
    {% for post in posts %}
        <!-- post 서브 템플릿 사용 -->
        {% include '_post.html' %}
    {% endfor %}
{% endblock %}

 

 

 

 

 

+ Recent posts