본문 바로가기
파이썬/파이썬 플라스크

ep 11. flask로 instagram Clone 코딩 - 2

by L_SU 2022. 10. 9.

models.py 작업하기

시작하기에 앞서 전 시간에 작업한 마이그레이션 파일과 데이터베이스를 삭제한다.

user.py에 다음과 같이 코드를 짜준다.

from ..db import db


followers = db.Table(
    'followers',
    db.Column('follower_id', db.Integer, db.ForeignKey('User.id', ondelete='CASCADE'), primary_key=True), # 나를 팔로우하는 사람들의 id
    db.Column('followed_id', db.Integer, db.ForeignKey('User.id', ondelete='CASCADE'), primary_key=True) # 내가 팔로우한 사람들의 id
)

class UserModel(db.Model):
    __tablename__ = "User"
    
    id = db.Column(db.Integer, primary_key=True) # 기본 키
    username = db.Column(db.String(80), nullable=False, unique=True) # 80자 제한, 중복 X, 필수
    password = db.Column(db.String(80), nullable=False) # 80자 제한, 필수
    email = db.Column(db.String(80), nullable=False, unique=True) # 중복 X, 필수
    created_at = db.Column(db.DateTime, server_default=db.func.now()) # 가입 날짜

    followed = db.relationship(                             # 본인이 팔로우한 유저들
        'UserModel',                                        # User 모델 스스로를 참조
        secondary=followers,                                # 연관 테이블 이름을 지정
        primaryjoin=(followers.c.follower_id==id),          # followers 테이블에서 특정 유저를 팔로우하는 유저들을 찾음
        secondaryjoin=(followers.c.followed_id==id),        # followers 테이블에서 특정 유저가 팔로우한 모든 유저들을 찾음
        backref=db.backref('follower_set', lazy='dynamic'), # 역참조 관계 설정
        lazy='dynamic' 
    )
    
    def follow(self, user): # 팔로우
        if not self.is_following(user):
            self.followed.append(user)
            return self

    def unfollow(self, user): # 언팔로우

        if self.is_following(user):
            self.followed.remove(user)
            return self

    def is_following(self, user): # 현재 사용자의 특정 사용자 팔로우 여부 반환 (True or False)
        return self.followed.filter(followers.c.followed_id == user.id).count() > 0
    
    @classmethod
    def find_by_username(cls, username): # db에서 이름으로 사용자 찾기

        return cls.query.filter_by(username=username).first()
    
    @classmethod
    def find_by_id(cls, id): # db에서 id 로 사용자 찾기   
        return cls.query.filter_by(id=id).first()
    
    def save_to_db(self): # 사용자를 db에 저장

        db.session.add(self)
        db.session.commit()
        
    def delete_from_db(self): # 사용자를 db에서 삭제

        db.session.delete(self)
        db.session.commit()
    
    def __repr__(self):
        return f'<User Object : {self.username}>'

 

post.py에는 아래와 같이 코드를 짜준다.

from ..db import db
from sqlalchemy.sql import func


class PostModel(db.Model):

    __tablename__ = "Post"

    id = db.Column(db.Integer, primary_key=True) # 기본 키
    title = db.Column(db.String(150)) # 제목, 150자 제한
    content = db.Column(db.String(500)) #내용, 500자 제한
    created_at = db.Column(db.DateTime(timezone=True), default=func.now()) #생성일, 현재로 저장
    updated_at = db.Column(db.DateTime(timezone=True), default=func.now(), onupdate=func.now()) #업데이트 날짜, 수정될 때마다 그 시간으로 수정됨
    author_id = db.Column(db.Integer, db.ForeignKey("User.id", ondelete="CASCADE"), nullable=False) # 글쓴이, 외래키, 외래키가 삭제되면 글이 같이 삭제됨, 필수
    author = db.relationship("UserModel", backref="post_author") 
    comment_set = db.relationship("CommentModel", backref="post", passive_deletes=True) # 댓글

    @classmethod
    def find_by_id(cls, id): # id로 검색
        return cls.query.filter_by(id=id).first()

    @classmethod
    def find_all(cls):
        return cls.query.all()

    def save_to_db(self): # 저장
        db.session.add(self)
        db.session.commit()

    def delete_from_db(self): #삭제
        db.session.delete(self)
        db.session.commit()

    def __repr__(self):
        return f"<Post Object : {self.title}>"

 

comment.py에는 아래와 같이 코드를 짜준다.

from ..db import db
from sqlalchemy.sql import func


class CommentModel(db.Model):

    __tablename__ = "Comment"
       
    id = db.Column(db.Integer, primary_key=True) # 기본 키
    content = db.Column(db.Text(), nullable=False) # 내용, 필수
    created_at = db.Column(db.DateTime(timezone=True), default=func.now()) #생성일, 현재로 저장
    updated_at = db.Column(db.DateTime(timezone=True), onupdate=func.now()) #업데이트 일, 업데이트 시점 저장
    author_id = db.Column(db.Integer, db.ForeignKey('User.id', ondelete='CASCADE'), nullable=False) #댓글 쓴 사람, 외래키, 외래키 삭제시 같이 삭제됨, 필수
    author = db.relationship("UserModel", backref="comment_author")
    post_id = db.Column(db.Integer, db.ForeignKey('Post.id', ondelete='CASCADE'), nullable=False) #댓글의 본문, 외래키, 외래키 삭제시 같이 삭제, 필수
    
    def save_to_db(self): # 저장
        db.session.add(self)
        db.session.commit()
        
    def delete_from_db(self): #삭제
        db.session.delete(self)
        db.session.commit()
        
    @classmethod
    def find_by_id(cls, id): #id로 검색   
        return cls.query.filter_by(id=id).first()
        
    def __repr__(self):
        return f'<Comment Object : {self.content}>'

 

이렇게 user, post, comment 3개의 모델을 생성해주었다.

이제 api/__init__.py에 작성한 3개의 모델을 등록해줬다.

 

작업하기에 앞서 데이터베이스와 마이그레이션 파일을 삭제했기 때문에 flask db init, flask db migrate 명령으로 생성합니다.

위의 작업이 완료 되었다면 flask db upgrade 명령을 통해 테이블들을 데이터베이스에 적용합니다.

 

게시글 목록 조회 API

첫번째로 게시글 목록을 조회할 수 있는 API를 구현해보겠습니다.

조회를 구현하는 것이기 때문에 method는 GET을 사용하겠습니다.

 

먼저 schemas/ 아래에 __init__.py 와 post.py 를 생성해줍니다.

그리고 ma.py 파일에 다음과 같이 코드를 추가해줍니다

스카마를 작성해주겠습니다.

post.py

SQLAlchemyAutoSchema 를 상속받아 class를 작성했고,

meta class에서 모델을 직렬화 하겠다고 명시했습니다.

 

다음으로 Resources/post.py 를 작성합니다.

함수들은 차차 구현해 나갈 것이기 때문에 현재는 틀만 생성해주고 그 안의 내용은 pass로 비워놨습니다.

여기서 PostList class에선 get 함수를 구현해놨습니다.

return으로 값을 반환해주는데, PostModel에서 model을 생성할 때 만들어줬던 함수가 눈에 띕니다.

이 함수로 PostModel.query.all()을 반환해주고, 이를 post_list_schema.dump로 직렬화 해서 반환 해줬습니다.

 

다음으로 __init__.py 에 import 해줍니다.

그리고 마지막으로 리소스를 등록해줍니다.

그리고 서버를 실행시켜 /posts/로 이동해보면

데이터베이스에 게시물이 존재하지 않아 게시물들이 나오지 않지만, 다음과 같이 화면에 출력이 되는 것을 확인할 수 있습니다. 보다 정확한 확인을 위해 유저를 생성해 게시글을 생성해보겠습니다.

 

먼저 flask shell 명령으로 shell을 실행시킵니다.

이후 다음과 같이 usermodel을 import 해 데이터를 작성하고 전에 만들어뒀던 함수로 데이터베이스에 저장합니다.

마지막으로 user을 입력해 제대로 저장이 되었는지 확인합니다.

또한 DB Browser for SQLite를 통해서도 확인할 수 있습니다.

 

같은 방법으로 post도 작성해줍니다.

똑같이 DB Browser로도 확인 해줍니다.

그리고 다시 한번 flask run 해서 주소를 확인해보면 짜잔 글씨가 깨집니다.

당황하지 않고, 한글이 깨지는 것이기 때문에 __init__.py에 아래와 같이 코드를 추가해줍니다.

그리고 확인을 해보면 잘 나오는 것을 확인할 수 있습니다 :)

게시글 작성 API

이제 게시글을 단순히 조회해보는 것이 아닌 작성을 할 수 있게 만들어봅시다.

스카마에 다음과 같은 내용을 추가합니다. 여기서 include_fk 는 외래키 포함여부를 load_instance는 모델 객체를 로드할 것인지 설정해주는 것입니다.

 

그리고 다음으로 아까 pass로 틀만 잡아뒀던 post 함수를 구현합니다.

post_json이란 이름으로 우리가 보낸 데이터를 get_json()으로 받아 저장합니다. 그리고 new_post란 이름으로 게시물 instance로 변환합니다. 이를 마지막 줄에서 save_to_db()로 데이터베이스에 저장합니다.

 

이대로 구현을 마친다면 두가지 디테일이 아쉽게 됩니다. 

첫번째로는 게시글이 저장될 때 글쓴이의 이름이 아닌 id로 저장되기 때문에 글쓴이의 정보가 id로 출력되는 점

두번째로는 게시글을 작성할 때 저자의 id를 직접 입력해주는 점입니다.

 

첫번째 아쉬운 부분을 해결해보도록 하겠습니다.

 

먼저 ma.py를 다음과 같이 수정합니다.

 

그리고 schemas/post.py의 import 내용도 다음과 같이 수정해줍니다.

 

스카마의 내용도 수정해줍니다.

그럼 이렇게 글쓴이의 이름이 출력되는 것을 확인할 수 있습니다.

post 함수에 예외처리를 추가했습니다.

 

이제 PostList에서 벗어나 아까 틀만 만들어놨던 Post 리소스를 구현해보겠습니다.

먼저 get입니다. 게시물을 id를 통해 찾은 후 찾았을 경우 게시물과 함께 200을 리턴하고,

찾지 못했을 경우 없다는 말과 함께 404를 리턴합니다.

 

delete도 get과 비슷합니다.

 

다음으로 put입니다.

post_json으로 json 데이터를 받습니다. 이후 post라는 이름으로 id로 게시글을 찾습니다. 찾았다면 제목과 내용을

받은 데이터로 저장, 찾지 못했다면 에러 메시지와 함께 400을 리턴합니다. 이후 저장된 post 데이터를 데이터베이스에 저장하고, 200과 함께 리턴해줍니다.

 

이후 __init__.py에 리소스를 추가해줍니다.

 

이번 에피소드에선 이렇게 게시글 CRUD를 구현해봤습니다. 다음시간에 봬용 :)