TOP
class="layout-aside-left paging-number">
본문 바로가기
[파이썬 Projects]/<파이썬 웹개발>

[파이썬] 플라스크(Flask) - 플라스크 프로젝트 - 메모앱(6)

by 기록자_Recordian 2024. 8. 15.
728x90
반응형
시작에 앞서
해당 내용은 <가장 빠른 풀스택을 위한 Flask & FastAPI>, Dave Lee 지음. BJ Public 출판.
내용을 토대로 작성되었습니다. 보다 자세한 사항은 해당 교재를 참고하시기 바랍니다.

이전 내용(메모앱 1단계 - 3단계)

[파이썬] 플라스크(Flask) - 플라스크 프로젝트 - 메모앱(1)

시작에 앞서해당 내용은 , Dave Lee 지음. BJ Public 출판.내용을 토대로 작성되었습니다. 보다 자세한 사항은 해당 교재를 참고하시기 바랍니다.메모앱 만들기 - 1단계: 애플리케이션 생성 플라스크

puppy-foot-it.tistory.com

이전 내용(메모앱 4단계)

[파이썬] 플라스크(Flask) - 플라스크 프로젝트 - 메모앱(2)

시작에 앞서해당 내용은 , Dave Lee 지음. BJ Public 출판.내용을 토대로 작성되었습니다. 보다 자세한 사항은 해당 교재를 참고하시기 바랍니다.이전 내용(메모앱 1단계 - 3단계) [파이썬] 플라스크(Fl

puppy-foot-it.tistory.com

이전 내용(메모앱 5단계)

[파이썬] 플라스크(Flask) - 플라스크 프로젝트 - 메모앱(3)

시작에 앞서해당 내용은 , Dave Lee 지음. BJ Public 출판.내용을 토대로 작성되었습니다. 보다 자세한 사항은 해당 교재를 참고하시기 바랍니다.이전 내용(메모앱 1단계 - 3단계) [파이썬] 플라스크(Fl

puppy-foot-it.tistory.com

이전 내용(메모앱 6단계)

[파이썬] 플라스크(Flask) - 플라스크 프로젝트 - 메모앱(4)

시작에 앞서해당 내용은 , Dave Lee 지음. BJ Public 출판.내용을 토대로 작성되었습니다. 보다 자세한 사항은 해당 교재를 참고하시기 바랍니다.이전 내용(메모앱 1단계 - 3단계) [파이썬] 플라스크(Fl

puppy-foot-it.tistory.com

이전 내용(메모앱 7단계)

[파이썬] 플라스크(Flask) - 플라스크 프로젝트 - 메모앱(5)

시작에 앞서해당 내용은 , Dave Lee 지음. BJ Public 출판.내용을 토대로 작성되었습니다. 보다 자세한 사항은 해당 교재를 참고하시기 바랍니다.이전 내용(메모앱 1단계 - 3단계) [파이썬] 플라스크(Fl

puppy-foot-it.tistory.com


8단계: MVC 패턴 적용

 
★ MVC 패턴(Model-View-Controller)

[웹개발] MVC(Model-View-Controller) 패턴이란?

MVC(Model-View-Controller) 패턴이란? 소프트웨어 개발을 하다 보면 복잡한 애플리케이션을 보다 효율적으로 관리하고 유지 보수하기 위해서 구조적인 접근이 필요하다. 그중 가장 널리 알려진 디자

puppy-foot-it.tistory.com

(보다 자세한 내용은 상단의 링크 참고)
 
▶ 모델-뷰-컨트롤러(model–view–controller, MVC)는 소프트웨어 공학에서 사용되는 소프트웨어 디자인 패턴이다. 이 패턴을 성공적으로 사용하면, 사용자 인터페이스로부터 비즈니스 로직을 분리하여 애플리케이션의 시각적 요소나 그 이면에서 실행되는 비즈니스 로직을 서로 영향 없이 쉽게 고칠 수 있는 애플리케이션을 만들 수 있다.
 
[구성요소]

  • Model(모델): 애플리케이션의 데이터 구조와 비즈니스 로직을 담당한다. 데이터의 상태 변경이나 저장, 업데이트 등의 작업을 수행한다. 모델의 상태에 변화가 있을 때 컨트롤러와 뷰에 이를 통보한다. 
  • View(뷰): 사용자에게 보여지는 화면을 담당한다. 모델의 데이터를 기반으로 화면에 출력되며, 사용자로부터 입력을 받는다. 사용자가 볼 결과물을 생성하기 위해 모델로부터 정보를 얻어 온다.
  • Controller(컨트롤러): 모델과 뷰를 연결하는 중간 다리 역할을 한다. 사용자의 입력을 받아 모델을 업데이트하고, 변경된 데이터를 뷰에 전달한다. 모델에 명령을 보냄으로써 모델의 상태를 변경할 수 있다.

[장점]

  • 유지 보수 용이성: 모델, 뷰, 컨트롤러가 각각 독립적으로 관리되므로, 특정 부분을 수정할 때 다른 부분에 영향을 최소화할 수 있다.
  • 재사용성: 각 컴포넌트가 독립적이기 때문에 다른 프로젝트나 부분에서 재사용할 수 있다.
  • 개발 속도 향상: 개발팀이 각 컴포넌트에 집중할 수 있어, 동시다발적인 개발이 가능해진다.

◆ 현재 app.py 파일 구조
현재의 app.py 구조는 플라스크 프레임워크를 사용하여 기본적인 MVC 패턴을 따르고 있으나, 명확한 구조적 분리를 통해 MVC 패턴을 더욱 강화할 수 있다.
 
[현재 파일 구조를 MVC 패턴에 맞춰 재구성하는 방법]
 
1. 모델
app.py 내에 정의된 Memo와 User 클래스는 데이터 모델을 나타낸다. 이 클래스들을 별도의 파일로 분리하여 모델을 명확하게 구분할 수 있다.예) models.py 파일을 생성하고 Memo와 User 클래스를 이 파일로 옮긴다.
 
2. 뷰
뷰는 사용자 인터페이스를 관리하며, 현재 templates/ 디렉터리에 home.html 과 memos.html 파일로 구성되어 있다. (그대로 유지)
 
3. 컨트롤러
컨트롤러는 사용자의 요청을 처리하고 모델과 뷰 사이의 상호 작용을 관리한다.예) 현재 app.py 파일에 컨트롤러 로직이 포함되어 있어 이를 개선하기 위해 라우팅 및 요청 처리 로직을 controllers.py 파일로 분리
 
[MVC 패턴 적용 전 후]

적용 전

적용 후

◆ MVC 패턴 적용하기
앞서 설명했듯, app.py을 app.py / controllers.py / login_manager.py / models.py 파일로 분리하고 구성하는 작업을 시행한다. 각 파일에 대한 대략적인 설명, 코드 및 각 특징은 다음과 같다.
 
1. login_manager.py
login_manager를 여러 파일에서 참조함에 따라 순환 참조 문제로 에러가 발생하는 것을 막기위해 생성한다.

# login_manager.py
from flask_login import LoginManager

login_manager = LoginManager()

 
[특징]
login_manager.py 파일은 Flask-Login의 LoginManager 인스턴스를 정의한다.
이 분리는 다음과 같은 이점을 가진다.

  • 순환 참조 방지: 여러 파일에서 login_manager를 참조할 때 발생하는 순환 참조 문제를 방지한다. 순환 참조는 모듈 간 종속성으로 인해 발생할 수 있으며, 이를 방지하기 위해 login_manager를 별도의 모듈로 분리한다.
  • 재사용성 향상: login_manager가 별도의 모듈에 있으면 다른 모듈에서 쉽게 재사용할 수 있다. 이는 애플리케이션의 다양한 부분에서 일관된 사용자 인증 방식을 유지하는 데 도움이 된다.
  • 기능 추가 시 유연성: 향후 사용자 인증 관련 기능을 추가하거나 수정할 때 login_manager.py만 수정하면 된다. 이는 기능 확장을 쉽게 만들어 주며, 다른 모듈에 영향을 주지 않는다.

 
2. app.py
app.py는 플라스크 애플리케이션을 초기화하고 설정을 구성하는 역할을 담당한다.

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from models import db
from login_manager import login_manager
from controllers import setup_routes

app = Flask(__name__)

# 데이터베이스 설정
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:본인비밀번호@localhost:3306/my_memo_app'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SECRET_KEY'] = 'mysecretkey'

# 데이터베이스 및 로그인 관리자 초기화
db.init_app(app)
login_manager.init_app(app)
login_manager.login_view = 'login'

# 라우팅 설정
setup_routes(app)

if __name__ == '__main__':
    with app.app_context():
        db.create_all()
    app.run()

 
[특징]
app.py 파일은 플라스크 애플리케이션의 초기화 및 기본 설정을 담당한다. 이 구조의 주요 이점은 다음과 같다.

  • 중앙화된 설정: 모든 기본 설정이 한곳에 모여 있어 관리가 용이하다. 예를 들어 데이터베이스 연결 문자열, 애플리케이션의 비밀 키 등을 중앙에서 관리할 수 있다.
  • 애플리케이션 초기화의 명확성: 플라스크 인스턴스와 필요한 확장 기능의 초기화가 app.py에서 명확하게 이루어진다. 이는 애플리케이션의 구조를 이해하기 쉽게 만들어준다.
  • 기능 확장 용이성: 새로운 기능이나 확장을 추가할 때 app.py 파일만 수정하면 된다. 이는 애플리케이션의 확장성을 높여 준다.

 
3. models.py
models.py는 데이터 모델과 관련된 클래스와 설정을 포함한다.

from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash

db = SQLAlchemy()

class Memo(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    title = db.Column(db.String(100), nullable=False)
    content = db.Column(db.String(1000), nullable=False)

    def __repr__(self):
        return f'<Memo {self.title}>'

# 데이터베이스 모델 정의
class User(UserMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(100), unique=True, nullable=False)
    email = db.Column(db.String(100), unique=True, nullable=False)
    password_hash = db.Column(db.String(512), nullable=False)

    def set_password(self, password):
        self.password_hash = generate_password_hash(password)

    def check_password(self, password):
        return check_password_hash(self.password_hash, password)

 
[특징]
models.py 파일은 데이터베이스 모델을 정의한다. 이 구조는 다음과 같은 이점을 제공한다.

  • 모델 중심의 설계: 데이터 모델은 애플리케이션의 핵심 구성 요소이며, 이를 별도의 모듈로 분리함으로써 모델 중심의 설계를 장려한다.
  • 데이터 무결성 및 관계 관리: 데이터베이스 테이블과 그 관계를 명확하게 정의하여 데이터 무결성을 유지하는 데 도움이 된다.
  • 모듈화 및 재사용성: 모델을 별도의 파일로 분리하면 코드의 재사용성이 향상되며, 다른 애플리케이션 부분과의 결합도가 낮아진다.

 
 
4. controllers.py
controllers.py에는 사용자 요청을 처리하는 라우팅 및 관련 로직이 포함된다.

from flask import Flask, render_template, request, jsonify, abort, redirect, url_for
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
from models import db, User, Memo
from login_manager import login_manager

def setup_routes(app):
    
    @login_manager.user_loader
    def load_user(user_id):
        return User.query.get(int(user_id))
    
    #기존 라우트
    @app.route('/')
    def home():
        return render_template('home.html')

    @app.route('/about')
    def about():
        return '이것은 My Memo 앱의 소개 페이지입니다'
    
    @app.route('/login', methods=['GET', 'POST'])
    def login():
        if request.method == 'POST':
            user = User.query.filter_by(username=request.form['username']).first()
            if user and user.check_password(request.form['password']):
                login_user(user)
                return jsonify({'message': '로그인을 성공하였습니다. 메모 페이지로 이동합니다.'}), 200
            return jsonify({'error': '아이디가 없거나 패스워드가 다릅니다.'}), 401
        return render_template('home.html')  # Corrected here

    @app.route('/logout')
    @login_required
    def logout():
        logout_user()
        return redirect(url_for('home')) #로그아웃 후 메인 페이지로 리다이렉트
    
    @app.route('/signup', methods=['GET', 'POST'])
    def signup():
        if request.method == 'POST':
            username = request.form['username']
            email = request.form['email']
            password = request.form['password']

            existing_user = User.query.filter((User.username == username) | (User.email == email)).first()
            if existing_user:
                return jsonify({'error': '사용자 이름 또는 이메일이 이미 사용 중 입니다.'}), 400

            user = User(username=username, email=email)
            user.set_password(password)

            db.session.add(user)
            db.session.commit()

            return jsonify({'message': '회원가입이 성공하였습니다. 가입한 아이디와 패스워드로 로그인할 수 있습니다.'}), 201
        return redirect(url_for('home')) # 비정상 요청의 경우 리다이렉트
    
    # 메모 조회
    @app.route('/memos', methods=['GET'])
    @login_required
    def list_memos():
        memos = Memo.query.filter_by(user_id=current_user.id).all()
        return render_template('memos.html', memos=memos, username=current_user.username)
    
    # 메모 생성
    @app.route('/memos/create', methods=['POST'])
    @login_required
    def create_memo():
        title = request.json['title']
        content = request.json['content']
        new_memo = Memo(user_id=current_user.id, title=title, content=content) #현재 로그인한 사용자의 ID 추가
        db.session.add(new_memo)
        db.session.commit()
        return jsonify({'message': 'Memo Created'}), 201
    
    # 메모 업데이트
    @app.route('/memos/update/<int:id>', methods=['PUT'])
    @login_required
    def update_memo(id):
        memo = Memo.query.filter_by(id=id, user_id=current_user.id).first() #현재 사용자의 메모만 선택
        if memo:
            memo.title = request.json['title']
            memo.content = request.json['content']
            db.session.commit()
            return jsonify({'message': 'Memo updated'}), 200
        else:
            abort(404, description="Memo not found or not authorized")
            
    # 메모 삭제
    @app.route('/memos/delete/<int:id>', methods=['DELETE'])
    @login_required
    def delete_memo(id):
        memo = Memo.query.filter_by(id=id, user_id=current_user.id).first()
        if memo:
            db.session.delete(memo)
            db.session.commit()
            return jsonify({'message': 'Memo deleted'}), 200
        else:
            abort(404, description="Memo not found or not authorized")

 
[특징]
controllers.py 파일은 사용자의 요청을 처리하는 컨트롤러 로직을 포함한다. 이 구조의 이점은 다음과 같다.

  • 관심사의 분리: 컨트롤러 로직을 별도로 분리함으로써 라우팅 및 요청 처리와 관련된 코드를 한 곳에 집중할 수 있다. 이는 코드의 가독성과 유지보수성을 높여준다.
  • 확장성 및 유지보수성: 새로운 라우트나 컨트롤러를 추가하거나 수정할 때, 다른 모듈에 영향을 주지 않고 독립적으로 작업할 수 있다.
  • 기능별 구성: 각 라우트와 관련된 기능들을 명확하게 구붕하여 기능별로 코드를 관리할 수 있다. 이는 특히 대규모 애플리케이션에서 유용하다. 

전체 웹 서비스 사용자 시나리오

 
1. VS Code 에서 flask run 입력 후, http://127.0.0.1:5000 링크 ctrl+클릭 하여 접속

 

2. <회원가입> 버튼을 누르고 관련 정보 입력 후 회원가입 하기

 
3. 새로 생성한 계정 정보로 로그인하기

4. 메모 제목 및 내용을 작성하고, 메모 추가하기

매모 목록을 더 추가할 수 있다.

로그아웃 버튼을 누르면 메인 페이지로 자동 이동된다.
 

728x90
반응형