Flask와 Jinja를 활용한 사용자 관리 웹 애플리케이션을 만들어 볼 것이다.
Flask와 Jinja 템플릿을 사용하여 사용자 관리 기능을 갖춘 웹 애플리케이션을 개발하는 실습이다.
사용자 목록을 표시할 뿐만 아니라, 사용자 추가, 수정, 삭제 기능을 제공해야 한다.
'/add'
), 수정('/edit/<username>'
), 삭제('/delete/<username>'
) 기능을 수행하는 라우트를 설정한다.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
<!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>
<!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>
<!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>
```
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
뷰를 구현한다. 데이터는 간단한 리스트 구조로 관리된다.
Python과 Flask를 사용하여 간단한 인스타그램 REST API를 만들어 볼 것이다.
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}]
}
]
/users
경로에 GET 요청을 보내면, 모든 사용자의 목록을 JSON 형태로 반환해야 한다.
jsonify
함수를 활용할 수 있다./users
경로에 POST 요청을 보내면, 새로운 사용자를 생성하고, 생성된 사용자 정보를 JSON 형태로 반환해야 한다.
(각 사용자는 고유한 username
과 초기 게시물 목록을 가져야 한다.)
/users/post/<username>
경로에 POST 요청을 보내면, 지정된 사용자의 게시물 목록에 새 게시물을 추가하고, 추가된 게시물 정보를 JSON 형태로 반환해야 한다.
/users/post/<username>
경로에 GET 요청을 보내면, 지정된 사용자의 모든 게시물을 JSON 형태로 반환해야 한다.
/users/post/like/<username>/<title>
경로에 PUT 요청을 보내면, 지정된 사용자의 특정 게시물의 좋아요 수를 1 증가시키고, 업데이트된 게시물 정보를 JSON 형태로 반환해야 한다.
/users/<username>
경로에 DELETE 요청을 보내면, 해당 사용자를 삭제하고, 삭제되었다는 메시지를 JSON 형태로 반환해야 한다.
파이썬에서 리스트를 생성하기 위한 간결하고 직관적인 방법을 리스트 컴프리핸션이라 한다.
기존 리스트를 기반으로 새 리스트를 만들거나, 조건을 충족하는 특정 요소들만을 선택하여 새 리스트를 구성할 때 사용한다.
리스트 컴프리핸션의 기본 구조는 다음과 같다.
[표현식 for 항목 in 반복가능객체 if 조건]
ex) user for user in users if user['username'] == username # 우리가 자주 사용하던 그 코드!
기본 예시
: 각 숫자의 제곱 리스트 생성
numbers = [1, 2, 3, 4, 5]
squared = [n ** 2 for n in numbers] # 여기에선 조건이 없다.
# 결과: [1, 4, 9, 16, 25]
조건을 포함한 예시
: 홀수만 선택하여 두 배로 만들기
numbers = [1, 2, 3, 4, 5]
doubled_odds = [n * 2 for n in numbers if n % 2 == 1]
# 결과: [2, 6, 10]
문자열 처리
: 모든 단어를 대문자로 변환
words = ["hello", "world", "python"]
uppercased = [word.upper() for word in words]
# 결과: ['HELLO', 'WORLD', 'PYTHON']
복잡한 조건
: 짝수는 제곱하고, 홀수는 두 배로 만들기
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]
문자열 필터링
: 특정 글자로 시작하는 단어만 선택
words = ["apple", "banana", "cherry", "date"]
a_words = [word for word in words if word.startswith('a')]
# 결과: ['apple']
insta_app/
│
├── app.py
├── user_model.py
├── user_routes.py
└── templates/
└── index.html
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>
Flask 기반의 JWT 인증과 Todo 앱을 구축해본다.
Flask와 Flask-Smorest를 사용하여 RESTful API를 구축하는 실습이다. 이 API는 JWT 인증을 사용하여 사용자 인증을 처리하고, Todo 애플리케이션의 기능을 제공한다.
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 구현
정리
이 실습은 Flask와 Flask-Smorest를 사용하여 사용자 인증 및 Todo 앱의 API를 구축하는 방법이다. JWT 인증은 사용자의 로그인을 처리하고, 인증된 사용자만이 Todo 항목을 관리할 수 있도록 한다.
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)
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
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})
데이터베이스를 초기화한다.
> 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에 접근한다.
실습 세번째가 잘 구동이 안되서 화난,,,, 특별 과제로 잘 마무리 해봐야겠다 ㅠㅠ