플라스크 MVC 디렉토리

자훈·2023년 9월 19일
0

개인프로젝트

목록 보기
5/10
post-thumbnail

📌Changing Modeling MVC

📌MVC?

Model, view, controller의 약자로 정답처럼 무조건 사용해야하는 형식이 아닌 사람들이 보았을 때, 코드를 유지보수하기 쉬운 형식으로 자리잡은 하나의 매커니즘 같은 것이다. 이렇게 코드를 구성하여 각각의 모듈과 변수들을 저장하게 되었을 때, 타인이 보아도 보기 쉽고, 내가 보아도 보기 쉬우며, 코드의 오류 수정 및 기능 추가 등등의 다양한 작업을 보다 협업할 때 쉽게 사용하기 용이하다는 장점이 있다.

📌Model

데이터를 관리하고 조작하며, 데이터 변경에 따른 애플리케이션의 상태 업데이트를 담당합니다. 모델은 주로 데이터베이스와 상호 작용하거나 데이터를 다루는 메서드 및 함수를 정의합니다. 데이터베이스의 형태에 따른 클래스를 정의해 객체의 형태로 변환하고
Repository => Model 을 통해 데이터베이스에 직접적으로 접근하여 값을 추가, 변환, 삭제, 혹은 받아오는 작업을 수행한다.

📌View

  • 웹페이지와 동작하며 입출력 과정 및 결과를 보여주기 위한 역할 ( UI 담당 )
  • 입출력의 순서나 데이터 양식은 Controller에 종속되어 결정된다. 이때 - View는 도메인 모델의 상태를 변환하거나, 받아서 템플릿 렌더링하는 역할을 합니다.
  • 객체를 전달받아 상태를 바로 출력하는 역할만을 담당해야 합니다.
  • view에서는 도메인 객체의 상태를 따로 저장하고 관리하는 클래스 변수나 인스턴스 변수가 필요 없습니다.
  • 입력을 받아 controller에게 전달하기도 한다.

📌📌Controller

  • modelview를 연결시켜주는 다리 역할을 한다.
  • 도메인 객체들의 조합을 통해 프로그램의 작동 순서나 방식을 제어합니다.
  • viewmodel이 각각 어떤 역할과 책임이 있는지 알고 있어야합니다.
  • View로부터 사용자의 action을 받아 Model에게 어떤 작업을 해야하는지 알려주거나 Model의 데이터 변화를 View에게 전달하여 View를 어떻게 업데이트할지 알려줍니다.
  • Model, View에게 직접 지시 가능

html 화면은 파이썬의 존재를 모르고, 플라스크의 존재를 모른다. 그렇기 때문에 html 을 통해 렌더링을하게되면 동적 접근이 아닌 정적 접근을 하게 된다. 지금의 구현은 아직 정적 방식이나 동적 방식도 충분히 고려해볼만하다.

아래는 나의 현재 디렉토리 구조이다. 분리하는 작업에 있으며 최적화를 천천히 하나씩 해나가고 있다. 복잡해 보이는 URL들을 분리시키고, 최대한 고정적인 변수들은 선언자체를 초기에 미리 하고 있다.

RiotApiProject/

├── app/
│ ├── controller.py
│ ├── dict.py
│ ├── model.py
│ ├── URL.py
│ └── view.py

├── templates/
│ └── index.html

└── main.py

main.py 에서 blueprint를 사용하여 다른 모듈들을 작동하게 할 것이다.

📌What is Blueprint?

블루프린트에 대해 설명하기에 앞서 블루프린트와 라우트의 관계에 대해 우리는 짚고 넘어갈 필요가 있다. 내가 공부하면서 알게 된 사실은

  1. app.route() 선언으로 특정 경로를 설정한 후 밑에 함수를 만들었을 때는, redierect(url_for('함수명'))을 통해 함수를 호출할 때, 해당 함수명이 링크로 들어간다는 것이다.
#url_test.py
from flask import Flask 

app = Flask(__name__)

app.route('/')
def index():
	return redirect(url_for('kr'))
	#이 지점에서의 url이 이미 localhost/'함수명' 으로 자리잡는다. 
    
app.route('/kr')
def kr():
	return 'hello, app.route'
    
if __name__ == '__main__':
	app.run(debug=True)

간단한 예시코드를 작성해보았다. 이 플라스크 앱을 실행하게되면

  1. 기본적인 로컬호스트에서 index함수가 실행
  2. url_for('함수명')으로 url이 localhost/'함수명'
  3. 그렇기에 초기함수 말고 다른 함수를 실행하려면, 함수명을 경로에 입력해야한다는 것이다.

근데 이런 방식으로 하게되면 나중에 기능과 로직이 많아졌을 때, app.route의 url 설정 길이가 터질 수 있다...

그래서 또 다른 방식인 BluePrint

#test_view.py
from flask import Flask 

bp = blueprints('main', __name__, url_prefix= '/')


bp.route('/')
def hello_pybo():
	return 'hello, pybo!'

이런 블루 프린트 코드가 있을 경우...

#main.py
from flask import Flask 

app = Flask(__name__)

from .test import test_view
app.register_blueprint(test_view.py)

if __name__ == 'main':
	app.run(debug=True)

블루프린트를 객체를 생성하여 메인에서 불러오고 그 블루프린트 객체는 해당 함수의 블루프린트 라우트로 이동하여 함수를 작동시키는 원리이다.
이는 URL 충돌을 사전에 방지가 가능하다. 일종의 교통정리를 위한 도로를 만들어주며 신호등도 같이 제시해주는 역할이다.
이제 각각의 기능과 역할에 대해 설명하겠다.

별칭

  • 'main'에 해당하는 부분이 별칭이다. 특정 블루프린트에 대한 URL을 생성할 때 url_for('블루프린트이름.라우트이름') 형식으로 사용가능하며, 이를 통해 더 쉽게 이해하고 관리가능하다.

  • URL간 규칙충돌을 방지하기 위해서도 호출을 통해 사용가능하고, 블루 프린트 이름을 변경할 때, 발생하는 코드 리팩토링이 쉬워진다.

  • 위의 url_test.py를 예시로 들면
    app이 아닌 bp = blueprint로 설정이 되어있을 때,
    reture url_for('bp.kr') 형식으로 별칭을 통해 URL의 쉬운 생성이 가능하다

  • 여러개의 블루프린트를 사용하고, 메인 view가 정해져있을 때는, main view 의 url_prefix로 url_for 사용없이 그냥 redirect('/') 형식으로 설정해주자.

url_prefix

  • url의 접두사를 설정하는 것이다.

  • url_prefix='/example' 일 경우에는 bp의 모든 url 접두사가 /example로 시작하게 된다. hello pybo의 라우트 url은 /example/이다.

📌routing difference in app and bp

만약 위의 app 경로 설정에 대해 제대로 읽었다면 다음과 같은 의문을 가질 수 있을 것이다.

어 그러면 url_prefix와 route함수 설정이 충돌할 수 있는거 아닌가?

다행히도 아니다. blueprint로 하는 라우트에 대한 접근은 함수명을 URL로 저장하지 않기 때문에, 우리가 알고 있는 선에서 충돌이 일어나지 않는다. 함수명을 URL로 저장하며 접근하는 것은 app.route로 설정했을 때 일어나는 일이다. 그렇기 때문에 함수명을 불렀을 때, 함수명을 URL에 넣으면서 접근하는 것이 아니라 라우터에 등록된 곳으로 가기때문에, 전혀 문제없이 사용할 수 있다는 것.

blueprint를 이용하여 경로를 나누는 것은 매우 편리함!endpoint의 출발점을 따로 설정해준다.

##__init__.py
from flask import Flask, url_for, redirect
from RiotApiProject.app import views

app = Flask(__name__)
app.register_blueprint(views.bp)


@app.route('/')
def create():
    print('hello site')
    return redirect(url_for('main.search'))
    #bp함수로 호출할 수 있다 이렇게하면.




if __name__ == '__main__':
    app.run(debug=True)

디렉토리를 분리해야하지만 아직 거기까지 디버깅으로 분리를 안해봤기때문에, 추후 분리예정 현재는 app이라는 디렉토리에서 같이 동작중

  • 적용하며 알게된 사실: URL과 같이 변수명을 저장하여 사용하는 모듈은 내가 작동하고자 하는 실행파일과 동일선상에 있어야한다. 안그러면 불러올 때 오류가 나더라. 또 다른 방법이 있는지는 찾아봐야겠다. 현재는 그렇다.

-viewcontroller에 대한 아직까지 정확한 구분으로 나누지 않았기 때문에 optimize가 완벽하다고 보기는 어려운 것 같다. 나눔이 최적화 되면 기능 구현도 추가적으로 실행할 예정이다. 현재는 계속해서 최적화중이다.


모듈과 파일을 구분하였고, init.py를 파일명을 수정하여 run이다.

#views.py
from flask import render_template, request, redirect, url_for, Blueprint
from RiotApiProject.app.URL import Api, profile, summonerV4, leagueV4, matchnumL, matchL
from RiotApiProject.app.dict import rank
import requests

# 블루 프린트 사용해보기
# controller 각 엔드포인트의 요청에 대한 응답만 정의함.
# 변수명을 반드시 확인하도록
# 키값 벨류값 설정
# 블루프린트로 __init__에서 실행하는 건 안되는데, 일단 url_fix링크에 들어가서 확인해보면 플라스크 돌아감을 확인.
# 플라스크 동적 템플릿 생성방법에 대해 공부


bp = Blueprint('main',__name__, template_folder="../templates", url_prefix='/kr')
api_key = Api


#url_prefix에 의해 endpoit시작은 고정. 그렇기에 충돌없이 실행한다. 
@bp.route('/', methods=['POST', 'GET'])
def search():
    if request.method == 'POST':
        summoner_name = request.form['summoner_name']
        match_type = request.form['match_type']
        return redirect(url_for('main.result', summoner_name=summoner_name, match_type=match_type))
    return render_template('index.html', summoner_info=None, rank=None, icon_url=None, match_data=None)


def image(profile_id):
    base_url = profile
    return f'{profile}{profile_id}.png'


@bp.route('/<summoner_name>/<match_type>')
def result(summoner_name, match_type):
    summonerV4_url = summonerV4
    url = f'{summonerV4_url}{summoner_name}?api_key={api_key}'
    response = requests.get(url)
    summoner_info = response.json()  # summoner_info 저장
    summoner_puuid = summoner_info['puuid']

    burl = leagueV4
    leagueV4_url = f'{burl}{summoner_info["id"]}?api_key={api_key}'
    response = requests.get(leagueV4_url)

    if response.status_code == 200:
        summoner_rank = response.json()  # 리그데이터 접근
    else:
        return render_template('index.html', summoner_info=None, rank=None, icon_url=None, match_data=None)

    for entry in summoner_rank:
        rank['queue_type'] = entry["queueType"]
        rank['tier'] = entry["tier"]
        rank['match'] = entry["rank"]
        rank['leaguepoints'] = entry["leaguePoints"]
        rank['wins'] = entry["wins"]
        rank['losses'] = entry["losses"]

    icon_url = image(summoner_info['profileIconId'])

    # 랭크 데이터 저장하는 곳 시작
    if match_type == '솔로랭크':
        match_type = 420  # 솔로랭크 엔드포인트
    else:
        match_type = 440  # 자유랭크 엔드포인트

    match_url = f'{matchnumL}{summoner_puuid}/ids?queue={match_type}&type=ranked&start=0&count=10&api_key={api_key}'
    response = requests.get(match_url)
    summoner_match = response.json()
    print(summoner_match)
    match_data = []

    for m in summoner_match:
        base_url = f'{matchL}{m}?api_key={api_key}'
        r = requests.get(base_url)
        Match_Dto = r.json()
        Match_info = Match_Dto['info']
        Participants = Match_info['participants']

        for participant in Participants:
            if participant['puuid'] == summoner_puuid:
                w = participant['win']
                k = participant['kills']
                d = participant['deaths']
                a = participant['assists']
                win = 'Win' if w else 'Loss'
                # zero divisionerror
                try:
                    kda = (k + a) / d
                    kda = round(kda, 2)
                except ZeroDivisionError:
                    kda = 999
                match_data.append({'Match_id': m, 'Win': win, 'Kills': k, 'Deaths': d, 'Assists': a, 'Kda': kda})
    return render_template('index.html', summoner_info=summoner_info, rank=rank, icon_url=icon_url,
                           match_data=match_data)

#if __name__ == '__main__':
   # app.run(debug=True)


실행화면이다 문제없이 작동된다. 이렇게까지 나누며 구현하는데 몇 일 동안 고민하고 정보들을 찾아보며, URL간의 충돌에 대한 이해를 위해 이것저것 고민해본 것 같다. 긴 시간이 걸리긴 했지만 한 번 하고나니, 머릿속에 이미 각 선언 별 기능정리가 완료되어 다음번에 적용할 땐 조금 더 수월하게 할 수 있을 것 같다.

실행 시 초기화면

실행 후 화면

p.s. 그림은 크기 조정을 걸와놔도 안줄어듦...화남.. 그리고 지금 뷰에 다 모아놨는데, 컨트롤러와 뷰 기능 분리하기 하고있음 조만간 업데이트 할 예정. 분리하고 나면 본격적으로 기능추가 및 분석 db까지 구현하여 나만의 분석 모델 만들기.

0개의 댓글