01. Mini Project 1

Flask와 Jinja를 활용한 사용자 관리 웹 애플리케이션을 만들어 볼 것이다.

1. 개요

Flask와 Jinja 템플릿을 사용하여 사용자 관리 기능을 갖춘 웹 애플리케이션을 개발하는 실습이다.
사용자 목록을 표시할 뿐만 아니라, 사용자 추가, 수정, 삭제 기능을 제공해야 한다.

2. 목표

  • 사용자 목록 표시, 추가, 수정, 삭제 (CRUD)기능을 포함하는 웹 애플리케이션 구축하기
  • Flask 라우트 및 Jinja 템플릿을 사용한 동적 웹 페이지 생성하기
  • 사용자 입력을 처리하고 데이터를 서버에서 관리하기

3. 요구 사항

  1. Flask 애플리케이션 및 라우트 설정
    • 사용자 목록을 표시하는 라우트를 포함하여, 사용자 추가('/add'), 수정('/edit/<username>'), 삭제('/delete/<username>') 기능을 수행하는 라우트를 설정한다.
    • 사용자 데이터는 메모리 내에서 관리하거나 임시 데이터베이스를 사용한다.
  2. Jinja 템플릿 작성 및 웹 페이지 디자인
    • 사용자 목록, 추가, 수정, 삭제 기능을 위한 HTML 템플릿을 작성한다.
    • 사용자 추가 및 수정을 위한 폼을 포함한다.
    • 각 사용자에 대해 수정 및 삭제 옵션을 제공한다.
  3. 폼 데이터 처리 및 서버 측 로직 구현
    • 사용자 추가 및 수정에 대한 폼 데이터를 처리하는 서버측 로직을 구현한다.
    • 사용자 데이터의 유효성을 검증하고, 오류가 있을 경우 적절한 피드백을 제공한다.
  4. 애플리케이션 실행 및 테스트
    • Flask 애플리케이션을 실행하고, 사용자 추가, 수정, 삭제 기능을 포함한 전체적인 기능을 테스트한다.

4. 예시 코드

[파일 구조]

flask_login/
│
├── app.py
└── templates/
    ├── index.html
    ├── add_user.html
    └── edit_user.html
  • app.py

    from flask import Flask, render_template, request, redirect, url_for
    
    app = Flask(__name__)
    
    # 임시 사용자 데이터
    users = [
      {"username": "traveler", "name": "Alex"},
      {"username": "photographer", "name": "Sam"},
      {"username": "gourmet", "name": "Chris"}
    ]
    
    @app.route('/')
    def index():
      return render_template('index.html', users=users)
      
    # 사용자 추가, 수정, 삭제 라우트 및 함수 작성...
    
    if __name__ == '__main__':
      app.run(debug=True)
  • templates

    • index.html
      <!DOCTYPE html>
      <html>
      <head>
          <title>User Management</title>
      </head>
      <body>
          <h1>User List</h1>
          <ul>
          {% for user in users %}
              <li>{{ user.name }} ({{ user.username }})
                  <a href="/edit/{{ user.username }}">Edit</a>
                  <a href="/delete/{{ user.username }}">Delete</a>
              </li>
          {% endfor %}
          </ul>
          <a href="/add">Add User</a>
      </body>
      </html>
      
    • add_user.html
      <!DOCTYPE html>
      <html>
      <head>
          <title>Add User</title>
      </head>
      <body>
          <h1>Add User</h1>
          <form method="post">
              <label for="username">Username:</label>
              <input type="text" id="username" name="username">
              <label for="name">Name:</label>
              <input type="text" id="name" name="name">
              <input type="submit" value="Add">
          </form>
          <a href="{{ url_for('index') }}">Back</a>
      </body>
      </html>
      
    • edit_user.html
      <!DOCTYPE html>
      <html>
      <head>
          <title>Edit User</title>
      </head>
      <body>
          <h1>Edit User</h1>
          <form method="post">
              <label for="name">Name:</label>
              <input type="text" id="name" name="name" value="{{ user.name }}">
              <input type="submit" value="Update">
          </form>
          <a href="{{ url_for('index') }}">Back</a>
      </body>
      </html>
      			```

5. 해설

  • app.py

    from flask import Flask, render_template, request, redirect, url_for
    
    app = Flask(__name__)
    
    # 임시 사용자 데이터
    users = [
      {"username": "traveler", "name": "Alex"},
      {"username": "photographer", "name": "Sam"},
      {"username": "gourmet", "name": "Chris"}
    ]
    
    @app.route('/')
    def index():
      # 사용자 목록을 보여주는 루트 뷰
      return render_template('index.html', users=users)
      
    @app.route('/add', methods=['GET', 'POST'])
    def add_user():
      # 사용자 추가 뷰
      if request.method == 'POST':
          username = request.form['username']
          name = request.form['name']
          users.append({'username': username, 'name': name})
          return redirect(url_for('index'))
      return render_template('add_user.html')
      
    @app.route('/edit/<username>', methods=['GET', 'POST'])
    def edit_user(username):
      # 사용자 수정 뷰
      user = next((user for user in users if user['username'] == username), None)
      if not user:
          return redirect(url_for('index'))
    
      if request.method == 'POST':
          user['name'] = request.form['name']
          return redirect(url_for('index'))
    
      return render_template('edit_user.html', user=user)
      
    @app.route('/delete/<username>')
    def delete_user(username):
      # 사용자 삭제 뷰
      global users
      users = [user for user in users if user['username'] != username]
      return redirect(url_for('index'))
      
    if __name__ == '__main__':
      app.run(debug=True)
  • Flask 애플리케이션 (app.py)
    : Flask 애플리케이션을 설정하고, 사용자 목록을 표시하며, 사용자를 추가, 수정, 삭제할 수 있는 라우트를 정의한다.

  • Jinja 템플릿 (index.html, add_user.html, edit_user.html)
    : 사용자 목록을 표시하고, 사용자를 추가하거나 수정할 수 있는 폼을 제공하는 HTML 템플릿을 작성한다.

  • 사용자 추가/수정/삭제 기능 (CRUD)
    : 사용자를 추가하는 add_user 뷰, 사용자를 수정하는 edit_user 뷰, 사용자를 삭제하는 delete_user 뷰를 구현한다. 데이터는 간단한 리스트 구조로 관리된다.


02. Mini Project 2

Python과 Flask를 사용하여 간단한 인스타그램 REST API를 만들어 볼 것이다.

1. 유저 데이터

users = [
    {
        "username": "leo",
        "posts": [{"title": "Town House", "likes": 120}]
    },
    {
        "username": "alex",
        "posts": [{"title": "Mountain Climbing", "likes": 350}, {"title": "River Rafting", "likes": 200}]
    },
    {
        "username": "kim",
        "posts": [{"title": "Delicious Ramen", "likes": 230}]
    }
]

2. 요구 사항

1. 사용자 조회

/users 경로에 GET 요청을 보내면, 모든 사용자의 목록을 JSON 형태로 반환해야 한다.

*힌트: JSON 형태의 응답을 반환하기 위해 jsonify 함수를 활용할 수 있다.

2. 사용자 생성

/users 경로에 POST 요청을 보내면, 새로운 사용자를 생성하고, 생성된 사용자 정보를 JSON 형태로 반환해야 한다.
(각 사용자는 고유한 username과 초기 게시물 목록을 가져야 한다.)

3. 게시물 추가

/users/post/<username> 경로에 POST 요청을 보내면, 지정된 사용자의 게시물 목록에 새 게시물을 추가하고, 추가된 게시물 정보를 JSON 형태로 반환해야 한다.

4. 사용자별 게시물 조회

/users/post/<username> 경로에 GET 요청을 보내면, 지정된 사용자의 모든 게시물을 JSON 형태로 반환해야 한다.

5. 특정 게시물의 좋아요 수 증가

/users/post/like/<username>/<title> 경로에 PUT 요청을 보내면, 지정된 사용자의 특정 게시물의 좋아요 수를 1 증가시키고, 업데이트된 게시물 정보를 JSON 형태로 반환해야 한다.

6. 사용자 삭제

/users/<username> 경로에 DELETE 요청을 보내면, 해당 사용자를 삭제하고, 삭제되었다는 메시지를 JSON 형태로 반환해야 한다.

+) List Comprehenstion

파이썬에서 리스트를 생성하기 위한 간결하고 직관적인 방법을 리스트 컴프리핸션이라 한다.
기존 리스트를 기반으로 새 리스트를 만들거나, 조건을 충족하는 특정 요소들만을 선택하여 새 리스트를 구성할 때 사용한다.
리스트 컴프리핸션의 기본 구조는 다음과 같다.

[표현식 for 항목 in 반복가능객체 if 조건]
ex) user for user in users if user['username'] == username	# 우리가 자주 사용하던 그 코드!
  1. 기본 예시
    : 각 숫자의 제곱 리스트 생성

    numbers = [1, 2, 3, 4, 5]
    squared = [n ** 2 for n in numbers]	# 여기에선 조건이 없다.
    # 결과: [1, 4, 9, 16, 25]
  2. 조건을 포함한 예시
    : 홀수만 선택하여 두 배로 만들기

    numbers = [1, 2, 3, 4, 5]
    doubled_odds = [n * 2 for n in numbers if n % 2 == 1]
    # 결과: [2, 6, 10]
  3. 문자열 처리
    : 모든 단어를 대문자로 변환

    words = ["hello", "world", "python"]
    uppercased = [word.upper() for word in words]
    # 결과: ['HELLO', 'WORLD', 'PYTHON']
  4. 복잡한 조건
    : 짝수는 제곱하고, 홀수는 두 배로 만들기

    numbers = [1, 2, 3, 4, 5]
    processed = [n ** 2 if n % 2 == 0 else n * 2 for n in numbers]	# 조건을 먼저 작성해도 무관하다.
    # 결과: [2, 4, 6, 16, 10]
  5. 문자열 필터링
    : 특정 글자로 시작하는 단어만 선택

    words = ["apple", "banana", "cherry", "date"]
    a_words = [word for word in words if word.startswith('a')]
    # 결과: ['apple']

3. 파일 구조

insta_app/
│
├── app.py
├── user_model.py
├── user_routes.py
└── templates/
    └── index.html

4. 해설

  • app.py

    from flask import Flask, request, render_template
    
    app = Flask(__name__)
    
    
    # 사용자 데이터를 저장하는 리스트
    users = [
      {
          "username": "leo",
          "posts": [{"title": "Town House", "likes": 120}]
      },
      {
          "username": "alex",
          "posts": [{"title": "Mountain Climbing", "likes": 350}, {"title": "River Rafting", "likes": 200}]
      },
      {
          "username": "kim",
          "posts": [{"title": "Delicious Ramen", "likes": 230}]
      }
    ]
    
    @app.get("/")
    def index():
      # 모든 사용자 정보를 JSON 형태로 반환
      return render_template('index.html')
      
    @app.get("/users")
    def get_users():
      # 모든 사용자 정보를 JSON 형태로 반환
      return {"users": users}
      
    @app.post("/users")
    def create_user():
      # 클라이언트로부터 받은 JSON 데이터
      request_data = request.get_json()
      # 새 사용자 객체 생성
      new_user = {"username": request_data["username"], "posts": [{"title": "My First Post", "likes": 0}]}
      # 사용자 리스트에 새 사용자 추가
      users.append(new_user)
      # 생성된 사용자 정보와 HTTP 상태코드 201 반환
      return new_user, 201
      
    @app.post("/users/post/<string:username>")
    def add_post(username):
      # 클라이언트로부터 받은 JSON 데이터
      request_data = request.get_json()
      # 해당 사용자를 찾아 게시물 추가
      for user in users:
          if user["username"] == username:
              new_post = {"title": request_data["title"], "likes": request_data["likes"]}
              user["posts"].append(new_post)
              return new_post
          
      # 사용자를 찾지 못한 경우 오류 메시지 반환
      return {"message": "User not found"}, 404
      
    @app.get("/users/post/<string:username>")
    def get_posts_of_user(username):
      # 특정 사용자의 모든 게시물 반환
      for user in users:
          if user["username"] == username:
              return {"posts": user["posts"]}
      # 사용자를 찾지 못한 경우 오류 메시지 반환
      return {"message": "User not found"}, 404
    @app.put("/users/post/like/<string:username>/<string:title>")
    def like_post(username, title):
      # 특정 게시물의 좋아요 수 증가
      for user in users:
          if user["username"] == username:
              for post in user["posts"]:
                  if post["title"] == title:
                      post["likes"] += 1
                      return post
      # 게시물을 찾지 못한 경우 오류 메시지 반환
      return {"message": "Post not found"}, 404
      
    app.delete("/users/<string:username>")
    def delete_user(username):
      # 전역 users 리스트에서 특정 사용자 삭제
      global users
      users = [user for user in users if user["username"] != username]
      # 삭제 성공 메시지와 HTTP 상태코드 200 반환
      return {"message": "User deleted"}, 200
    
    # 애플리케이션 실행
    if __name__ == '__main__':
      app.run(debug=True)

이렇게 app.py에 모든 코드를 넣는 방법도 있지만 아래와 같이 user_routes.py, user_model.py 파일로 나누어 구조를 관리하는 방법도 있다. 아래와 같이 나눠서 관리할 경우 app.py에는 자세한 코드는 생략되고 routes에서 코드를 불러와 실행하는 방법으로 코드를 간결하게 만들 수 있다.

  • user_routes.py
    : 사용자 관련 라우트 정의

    from flask import request
    from user_model import users, add_user, add_post_to_user, get_user_posts, like_user_post, delete_user
    
    def register_routes(app):
    @app.route("/users", methods=["GET", "POST"])
      def users_route():
          if request.method == "GET":
              return {"users": users}
          elif request.method == "POST":
              request_data = request.get_json()
              return add_user(request_data)
    
      @app.route("/users/post/<string:username>", methods=["POST"])
      def add_post(username):
          request_data = request.get_json()
          return add_post_to_user(username, request_data)
    
      @app.route("/users/post/<string:username>", methods=["GET"])
      def get_posts(username):
          return get_user_posts(username)
    
      @app.route("/users/post/like/<string:username>/<string:title>", methods=["PUT"])
      def like_post(username, title):
          return like_user_post(username, title)
    
      @app.route("/users/<string:username>", methods=["DELETE"])
      def delete(username):
          return delete_user(username)
  • user_model.py
    : 사용자 데이터 및 관련 함수 정의

    users = [
      {"username": "leo", "posts": [{"title": "Town House", "likes": 120}]},
      {"username": "alex", "posts": [{"title": "Mountain Climbing", "likes": 350}, {"title": "River Rafting", "likes": 200}]},
      {"username": "kim", "posts": [{"title": "Delicious Ramen", "likes": 230}]}
    ]
    
    def add_user(request_data):
      new_user = {"username": request_data["username"], "posts": []}
      users.append(new_user)
      return new_user, 201
      
    def add_post_to_user(username, request_data):
      for user in users:
          if user["username"] == username:
              new_post = {"title": request_data["title"], "likes": 0}
              user["posts"].append(new_post)
              return new_post, 201
      return {"message": "User not found"}, 404
      
    def get_user_posts(username):
      for user in users:
          if user["username"] == username:
              return {"posts": user["posts"]}
      return {"message": "User not found"}, 404
      
    def like_user_post(username, title):
      for user in users:
          if user["username"] == username:
              for post in user["posts"]:
                  if post["title"] == title:
                      post["likes"] += 1
                      return post, 200
      return {"message": "Post not found"}, 404
      
    def delete_user(username):
      global users
      users = [user for user in users if user["username"] != username]
      return {"message": "User deleted"}, 200
  • templates/index.html

    <!DOCTYPE html>
    <html>
      <head>
        <title>Instagram API Test</title>
        <script>
          function fetchUsers() {
            fetch("/users")
              .then((response) => response.json())
              .then((data) => {
                document.getElementById("users").innerHTML = JSON.stringify(
                  data,
                  null,
                  2
                );
              })
              .catch((error) => console.error("Error:", error));
          }
    
          function createUser() {
            const username = document.getElementById("newUsername").value;
            fetch("/users", {
              method: "POST",
              headers: {
                "Content-Type": "application/json",
              },
              body: JSON.stringify({ username: username }),
            })
              .then((response) => response.json())
              .then((data) => {
                document.getElementById("createUserResult").innerHTML =
                  JSON.stringify(data, null, 2);
                fetchUsers(); // 사용자 목록 갱신
              })
              .catch((error) => console.error("Error:", error));
          }
    
          function addPostToUser() {
            const username = document.getElementById("postUsername").value;
            const title = document.getElementById("postTitle").value;
            fetch(`/users/post/${username}`, {
              method: "POST",
              headers: {
                "Content-Type": "application/json",
              },
              body: JSON.stringify({ title: title, likes: 0 }),
            })
              .then((response) => response.json())
              .then((data) => {
                document.getElementById("addPostResult").innerHTML = JSON.stringify(
                  data,
                  null,
                  2
                );
              })
              .catch((error) => console.error("Error:", error));
          }
    
          function deleteUser() {
            const username = document.getElementById("deleteUsername").value;
            fetch(`/users/${username}`, {
              method: "DELETE",
            })
              .then((response) => response.json())
              .then((data) => {
                document.getElementById("deleteUserResult").innerHTML =
                  JSON.stringify(data, null, 2);
                fetchUsers(); // 사용자 목록 갱신
              })
              .catch((error) => console.error("Error:", error));
          }
        </script>
      </head>
      <body>
        <h1>Instagram API Test</h1>
    
        <button onclick="fetchUsers()">Fetch Users</button>
        <pre id="users"></pre>
    
        <h2>Create User</h2>
        <input type="text" id="newUsername" placeholder="Username" />
        <button onclick="createUser()">Create User</button>
        <pre id="createUserResult"></pre>
    
        <h2>Add Post to User</h2>
        <input type="text" id="postUsername" placeholder="Username" />
        <input type="text" id="postTitle" placeholder="Post Title" />
        <button onclick="addPostToUser()">Add Post</button>
        <pre id="addPostResult"></pre>
    
        <h2>Delete User</h2>
        <input type="text" id="deleteUsername" placeholder="Username" />
        <button onclick="deleteUser()">Delete User</button>
        <pre id="deleteUserResult"></pre>
      </body>
    </html>

03. Mini Project 3

Flask 기반의 JWT 인증과 Todo 앱을 구축해본다.

1. 개요

Flask와 Flask-Smorest를 사용하여 RESTful API를 구축하는 실습이다. 이 API는 JWT 인증을 사용하여 사용자 인증을 처리하고, Todo 애플리케이션의 기능을 제공한다.

2. 목표

  • JWT 인증을 통한 사용자 인증 및 관리 구현
  • Todo 애플리케이션의 RESTful API 구축
  • Flask-Smorest를 활용하여 API 문서화 및 관리

3. 요구 사항

  1. 사용자 인증 구현
    • 사용자 모델 및 데이터베이스를 설정한다.
    • Flask-JWT-Extended를 사용하여 JWT 인증 시스템 을 구현한다.
    • 로그인 및 사용자 인증을 위한 API 엔드포인트를 제공한다.
  2. Todo 애플리케이션 API 구축
    • Todo 모델 정의 및 데이터베이스를 설정한다.
    • Todo 항목에 대한 CRUD API를 구현한다.
    • Todo API에 JWT 인증을 적용한다.
  3. API 문서화 및 테스트
    • Flask-Smorest를 사용하여 API 문서화 및 테스트 가능하게 설정한다.
    • OpenAPI(Swagger) 문서를 제공한다.

4. 예시 코드

[파일 구조]

todo_app/
│
├── app.py
├── db.py
├── models.py
└── instance/
    └── app.db
└── routes/
	├── auth.py
    └── todo.py
└── templates/
    └── index.html

※ db라는 파일 확장자를 VSC에서 보려면 SQLite 또는 SQLite Viewer를 설치하고 보는 것을 권장한다.

  • app.py

    # app.py
    from flask import Flask, render_template
    from flask_jwt_extended import JWTManager
    from flask_smorest import Api
    
    from db import db
    from flask_migrate import Migrate
    
    app = Flask(__name__)
    
    app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///app.db"
    app.config["JWT_SECRET_KEY"] = "super-secret-key"
    
    db.init_app(app)
    migrate = Migrate(app, db)
    
    jwt = JWTManager(app)
    api = Api(app)
    
    if __name__ == "__main__":
      app.run(debug=True)
  • db.py
    :db 설정

  • models.py

    # models.py
    from db import db
    from werkzeug.security import generate_password_hash, check_password_hash
    
    class User(db.Model):
      # 사용자 모델 정의
      
    class Todo(db.Model):
      # Todo 모델 정의
  • routes/auth.py
    : 사용자 인증 로직 구현

  • todo.py
    : Todo 관련 API 구현

  • 정리

    • 사용자 모델과 Todo 모델을 정의하고, 각각에 대한 CRUD API를 구현한다.
    • 사용자 인증을 위해 JWT 토큰을 발급하고 검증하는 로직을 구현한다.
    • Todo 항목은 인증된 사용자만이 접근할 수 있도록 설정한다.
    • Flask-Smorest를 사용하여 API 문서화 및 테스트를 가능하게 한다.

5. 해설

이 실습은 Flask와 Flask-Smorest를 사용하여 사용자 인증 및 Todo 앱의 API를 구축하는 방법이다. JWT 인증은 사용자의 로그인을 처리하고, 인증된 사용자만이 Todo 항목을 관리할 수 있도록 한다.

1. 프로젝트 및 데이터베이스 설정

  • app.py
    : Flask 애플리케이션과 JWT, SQLAlchemy 설정을 포함한다.

    from flask import Flask, render_template
    from flask_sqlalchemy import SQLAlchemy
    from flask_jwt_extended import JWTManager
    from flask_smorest import Api
    from db import db
    from flask_migrate import Migrate
    
    app = Flask(__name__)
    
    # 데이터베이스 및 JWT 설정
    app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db' # local db => 파일 형태의 간단한 데이터 베이스
    app.config['JWT_SECRET_KEY'] = 'super-secret-key'
    app.config['API_TITLE'] = 'Todo API'
    app.config['API_VERSION'] = 'v1'
    app.config['OPENAPI_VERSION'] = '3.0.2'
    
    # db = SQLAlchemy(app)
    db.init_app(app)
    migrate = Migrate(app, db)
    
    jwt = JWTManager(app)
    api = Api(app)
    
    # 모델 및 리소스 불러오기 (이후에 정의)
    from models import User, Todo
    from routes.auth import auth_blp
    from routes.todo import todo_blp
    
    # API에 Blueprint 등록
    api.register_blueprint(auth_blp)
    api.register_blueprint(todo_blp)
    
    @app.route("/")
    def index():
      return render_template('index.html')
      
    if __name__ == '__main__':
      app.run(debug=True)
  • db.py
    : db를 전역으로 만들고 db관련 로직을 처리할 수 있도록 정의한다.

    from flask_sqlalchemy import SQLAlchemy
    
    db = SQLAlchemy()
  • models.py
    : 사용자와 Todo 항목을 위한 데이터베이스 모델을 정의한다.

    from db import db
    from werkzeug.security import generate_password_hash, check_password_hash
    
    class User(db.Model):
      id = db.Column(db.Integer, primary_key=True)
      username = db.Column(db.String(50), unique=True, nullable=False)
      password_hash = db.Column(db.String(128))
      
      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)
          
    class Todo(db.Model):
      id = db.Column(db.Integer, primary_key=True)
      title = db.Column(db.String(100), nullable=False)
      completed = db.Column(db.Boolean, default=False)
      user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)

2. 사용자 인증 및 JWT 구현

  • routes/auth.py
    : 사용자 인증 로직과 JWT 토큰 발급을 처리하는 Blueprint를 구현한다.

    from flask import request, jsonify
    from flask_smorest import Blueprint
    from flask_jwt_extended import create_access_token
    from models import User
    from werkzeug.security import check_password_hash
    
    auth_blp = Blueprint('auth', 'auth', url_prefix='/login', description='Operations on todos')
    
    @auth_blp.route('/', methods=['POST'])
    def login():
      if not request.is_json:
          print('if not request.is_json')
          return jsonify({"msg": "Missing JSON in request"}), 400
    
      username = request.json.get('username', None)
      password = request.json.get('password', None)
      if not username or not password:
          print('if not username or not password')
          return jsonify({"msg": "Missing username or password"}), 400
    
      user = User.query.filter_by(username=username).first()
      print('user 여기는오나', user)
      print('user 여기는오나', check_password_hash(user.password_hash, password))
      if user and check_password_hash(user.password_hash, password):
          access_token = create_access_token(identity=username)
          print('access_token', access_token)
          return jsonify(access_token=access_token)
      else:
          return jsonify({"msg": "Bad username or password"}), 401

3. Todo API 구현

  • routes/todo.py
    : Todo 항목에 대한 CRUD API 구현. JWT 인증을 요구한다.

    from flask import request, jsonify
    from flask_smorest import Blueprint
    from flask_jwt_extended import jwt_required, get_jwt_identity
    from models import Todo, User, db
    
    todo_blp = Blueprint('todo', 'todo', url_prefix='/todo', description='Operations on todos')
    
    @todo_blp.route('/', methods=['POST'])
    @jwt_required()
    def create_todo():
      if not request.is_json:
          return jsonify({"msg": "Missing JSON in request"}), 400
    
      title = request.json.get('title', None)
      if not title:
          return jsonify({"msg": "Missing title"}), 400
    
      username = get_jwt_identity()
      user = User.query.filter_by(username=username).first()
    
      new_todo = Todo(title=title, user_id=user.id)
      db.session.add(new_todo)
      db.session.commit()
    
      return jsonify({"msg": "Todo created", "id": new_todo.id}), 201
      
    # Todo 조회 (GET)
    @todo_blp.route('/', methods=['GET'])
    @jwt_required()
    def get_todos():
      username = get_jwt_identity()
      user = User.query.filter_by(username=username).first()
      todos = Todo.query.filter_by(user_id=user.id).all()
      return jsonify([{"id": todo.id, "title": todo.title, "completed": todo.completed} for todo in todos])
      
    # Todo 수정 (PUT)
    @todo_blp.route('/<int:todo_id>', methods=['PUT'])
    @jwt_required()
    def update_todo(todo_id):
      todo = Todo.query.get_or_404(todo_id)
      if 'title' in request.json:
          todo.title = request.json['title']
      if 'completed' in request.json:
          todo.completed = request.json['completed']
      db.session.commit()
      return jsonify({"msg": "Todo updated", "id": todo.id})
      
    # Todo 삭제 (DELETE)
    @todo_blp.route('/<int:todo_id>', methods=['DELETE'])
    @jwt_required()
    def delete_todo(todo_id):
      todo = Todo.query.get_or_404(todo_id)
      db.session.delete(todo)
      db.session.commit()
      return jsonify({"msg": "Todo deleted", "id": todo_id})

4. 실행 및 테스트

  • 데이터베이스를 초기화한다.

    > flask db init
    > flask db migrate
    > flask db upgrade
  • 유저를 생성한다.
    : 생성하기 위해서 python이라는 shell script로 이동해야한다.

    python
    >>> 

    >>>이 보인 이후 아래 코드를 한 줄 씩 입력해주면 된다.
    (넣을 때는 shift+ins를 누르면 들어간다.) 그리고 입력시 들여쓰기에 대해서도 꼭 명심하길 바란다.

    from db import db
    from app import app
    from models import User
    
    with app.app_context():
    		new_user = User(username='newuser', password_hash='password')
    		new_user.set_password('user123') # 비밀번호를 해쉬화하는 부분이 추가되어야 함.
    		db.session.add(new_user)
          db.session.commit()
          user = User.query.filter_by(username='newuser').first()
      print(user)
  • 로그인 API를 사용하여 JWT 토큰을 받는다.

  • 받은 토큰을 사용하여 Todo API에 접근한다.


[5일차 후기]

실습 세번째가 잘 구동이 안되서 화난,,,, 특별 과제로 잘 마무리 해봐야겠다 ㅠㅠ


[참고 자료]

  • [오즈스쿨 스타트업 웹 개발 초격차캠프 백엔드 Flask 강의]
profile
백엔드 코린이😁

0개의 댓글