가오리의 코딩일기
Ep04. 블로그 웹 애플리케이션 개발(1) 본문
🔗 파일위치 확인
💡 로그인을 위한 준비
🧩 POST 방식
→ 회원가입에서는 새로운 유저를 추가하고 로그인에서는 아이디와 비밀번호 정보가 맞는지 확인해서 맞으면 로그인 시켜준다.
→ 회원가입과 로그인 모두 폼에서 사용자의 데이터를 받아오는 과정이 있는데 폼으로부터 데이터를 POST 방식으로 받아와야 한다.
→ 그래서 login.html과 signup.html의 Main Content에 있는 <form id=”contactForm” method=”POST”>로 수정해준다.
→ 만약 두 곳 다 POST라면 생략하면 된다.
🔗 응답 상태 코드
→ 응답 코드는 5개의 클래스(분류)로 구분된다.
→ 1XX : 정보, 요청을 받았으며 프로세스를 계속한다
→ 2XX : 성공, 요청을 성공적으로 받았으며 인식했고 수용했다
→ 3XX : 리다이렉션, 요청 완료를 위해 추가 작업 조치가 필요하다
→ 4XX : 클라이언트 오류, 요청의 문법이 잘못되었거나 요청을 처리할 수 없다
→ 5XX : 서버 오류, 서버가 명백히 유효한 요청에 대해 충족을 실패했다
🧩 회원가입
🧩 ORM: Object Relational Mapping
→ 객체와 관계형 데이터베이스의 데이터를 자동으로 매핑(연결)해주는 것
→ 데이터베이스의 데이터 ←매핑→ objcet 필드
→ 장점 : DBMS에 대한 종속성이 줄어들고 재사용 및 유지보수의 편리성이 증가한다
→ 단점 : 완벽한 ORM으로만 서비스를 구현하기 어렵다
🧩 flask-SQLAlchemy
→ 파이썬의 클래스와 데이터베이스를 매핑해줄 수 있도록 하는 파이썬 모듈
→ 클래스를 데이터베이스의 테이블로 만드는 작업을 대신 할 예정
# blog/__init__.py
def create_app():
# from .models import User
app = Flask(__name__) # Flask app 만들기
app.config['SECRET_KEY'] = "IFP"
app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{DB_NAME}'
db.init_app(app)
from .views import views
app.register_blueprint(views, url_prefix="/")
from .auth import auth
app.register_blueprint(auth, url_prefix='/auth')
return app
# blog/__init__.py
def create_database(app):
if not path.exists("blog/"+DB_NAME):
db.create_all(app=app)
🧩 DB 생성하는 함수 호출하기
# blog/models.py
from . import db
from flask_login import UserMixin
from sqlalchemy.sql import func
class User(db.Model, UserMixin):
id = db.Column(db.Integer,primary_key=True)
email = db.Column(db.String(150), unique=True)
username = db.Column(db.String(150), unique=True)
password = db.Column(db.String(150))
created_at = db.Column(db.DateTime(timezone=True), default=func.now())
# blog/__init__.py/create_app 밑에 추가
from .models import User
create_database(app)
🧩 __init__.py
→ flask_login : 플라스크에서 로그인 기능을 쉽게 구현할 수 있도록 도와주는 라이브러리
# blog/__init__.py/create_app()에 추가
login_manager= LoginManager() # LoginManager() 객체를 만들어준다
login_manager.login_view = " auth.login"
login_manager.init_app(app)
@login_manager.user_loader
def load_user_by_id(id):
return User.query.get(int(id))
return app
🧩 라이브러리 설치
→ pip install flask-wtf : 플라스크 프레임워크의 폼 검증 모듈, 쉽게 폼을 작성할 수 있으며 json 데이터 상호작용을 위한 검증 도구로도 사용된다
→ pip install email-validator : 이메일 유효성 검사 지원
# blog/forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, TextAreaField, PasswordField, EmailField
from wtforms.validators import DataRequired, Email, Length, EqualTo
class SignupForm(FlaskForm):
email = EmailField('email', validators = [DataRequired(), Email()])
username = StringField('username', validators=[DataRequired(), Length(4,30)])
password1 = PasswordField('password', validators= [DataRequired(), Length(8,30), EqualTo("password2", message="Password must match...")])
password2 = PasswordField('password again', validators=[DataRequired()])
# blog/auth.py
@auth.route("/sign-up", methods=['GET','POST'])
def signup():
form = SignupForm()
if request.method == "POST" and form.validate_on_submit():
signup_user = User(
email = form.email.data,
username = form.username.data,
password = generate_password_hash(form.password1.data),
)
email_exists = User.query.filter_by(email=form.email.data).first()
username_exists = User.query.filter_by(username=form.username.data).first()
if email_exists:
flash('Email is already in use...', category='error')
elif username_exists:
flash('Username is already in use...', category='error')
else:
db.session.add(signup_user)
db.session.commit()
flash("User created!!!")
return redirect(url_for("views.home"))
return render_template("signup.html", form=form, user=current_user)
<!--login.html-->
<form id="contactForm" method="POST">
{{ form.csrf_token}}
{# 없으면 폼 작동 안 함 #}
<div class="form-floating">
🧩 비밀번호 해싱하기(auth.py)
# blog/auth.py
from werkzeug.sercurity import generate_password_hash, check_password_hash
→ generate_password_hash : 문자열을 암호화된 해시로 바꿔준다
→ check_password_hash : 암호화된 해시와 문자열을 비교해 이 문자열이 동일한 해시를 갖는 경우 참을 반환
🧩 로그인
# blog/forms.py
class LoginForm(FlaskForm):
email =EmailField('email', validators=[DataRequired(), Email()])
password = PasswordField('password', validators = [DataRequired()])
# blog/auth.py
@auth.route("/login", methods=['GET','POST'])
def login():
form = LoginForm()
if request.method == "POST" and form.validate_on_submit():
password = form.password.data
user= User.query.filter_by(email=form.email.data).first()
if user:
if check_password_hash(user.password, password):
flash("Logged in!", category ='success')
login_user(user,remember=True)
return redirect(url_for('views.home'))
else:
flash("Password is incorrect!", category='error')
else:
flash("Email does not exist...", category='error')
return render_template("login.html", form=form, user=current_user)
🧩 로그아웃
# blog/auth.py
@auth.route("/logout")
def logout():
logout_user()
return redirect(url_for("views.home"))
🧩 오류메시지 나타내기
<!--base.html/Navigation 주석 바로 위에-->
{% with message = get_flashed_messages(with_categories=True) %}
{% if messages %}
{% for category, message in messages %}
{# 카테고리 == error이라면, 실패 메시지를 출력 #}
{% if category =="error" %}
<div class="alert alert-danger alert-dismissable fade show" role="alert" style="text-align: center">
{{message}}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{# 그렇지 않다면, 성공 메시지를 출력 #}
{% else %}
<div class="alert alert-success alert-dismissable fade show" role="alert" style="text-align: center">
{{message}}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endif %}
{% endfor %}
{% endif %}
{% endwith %}
🧩 동적으로 변화하는 navbar 만들기
# # blog/views.py
from flask import Blueprint, render_template
from flask_login import current_user
views = Blueprint("views", __name__)
@views.route("/")
@views.route("/home")
def home():
return render_template("index.html" , user=current_user)
@views.route("/about")
def about():
return render_template("about.html", user=current_user)
@views.route("/categories-list")
def categories_list():
return render_template("categories_list.html", user=current_user)
@views.route("/post-list")
def post_list():
return render_template("post_list.html", user=current_user)
@views.route('posts/<int:id>')
def post_detail():
return render_template("post_detail.html", user=current_user)
@views.route("/contact")
def contact():
return render_template("contact.html", user=current_user)
# blog/auth.py
import logging
from flask_login import login_user, logout_user, current_user, login_required
from . import db
from .forms import SignupForm, LoginForm
from .models import User
from flask import Blueprint, render_template, request, url_for, flash
from werkzeug.utils import redirect
from werkzeug.security import generate_password_hash, check_password_hash
auth = Blueprint("auth", __name__)
@auth.route("/login", methods=['GET','POST'])
def login():
form = LoginForm()
if request.method == "POST" and form.validate_on_submit():
password = form.password.data
user= User.query.filter_by(email=form.email.data).first()
if user:
if check_password_hash(user.password, password):
flash("Logged in!", category ='success')
login_user(user,remember=True)
return redirect(url_for('views.home'))
else:
flash("Password is incorrect!", category='error')
else:
flash("Email does not exist...", category='error')
return render_template("login.html", form=form, user=current_user)
@auth.route("/logout")
def logout():
logout_user()
return redirect(url_for("views.home"))
@auth.route("/sign-up", methods=['GET','POST'])
# 회원가입에서 POST 요청을 처리해야 함
def signup():
form = SignupForm()
if request.method == "POST" and form.validate_on_submit():
# 폼으로부터 검증된 데이터 받아오기
signup_user = User(
email = form.email.data,
username = form.username.data,
password = generate_password_hash(form.password1.data),
)
# 폼에서 받아온 데이터가 데이터베이스에 이미 존재하는지 확인
email_exists = User.query.filter_by(email=form.email.data).first()
username_exists = User.query.filter_by(username=form.username.data).first()
# 이메일 중복 검사
if email_exists:
flash('Email is already in use...', category='error')
# 유저네임 중복 검사
elif username_exists:
flash('Username is already in use...', category='error')
# 위의 모든 과정을 통과한다면, 폼에서 받아온 데이터를 새로운 유저로서 저장
else:
db.session.add(signup_user)
db.session.commit()
flash("User created!!!")
return redirect(url_for("views.home"))
# 저장이 완료된 후 home으로 리다이렉트
# GET 요청을 보낸다면 회원가입 템플릿을 보여줌
return render_template("signup.html", form=form, user=current_user)
<!--base.html Navigation 안의 뒤에서 4번째 즈음부터-->
{% if user.is_authenticated %}
<li class="nav-item">
<a class="nav-link px-lg-3 py-3 py-lg-4" style="color: red" href="#">
welcome, {{ user.username }}!</a>
</li>
<li class="nav-item">
<a class="nav-link px-lg-3 py-3 py-lg-4" style="color: red" href="{{ url_for('auth.logout') }}">
Logout</a>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link px-lg-3 py-3 py-lg-4" href="{{ url_for('auth.signup') }}">
Sign Up</a>
</li>
<li class="nav-item">
<a class="nav-link px-lg-3 py-3 py-lg-4" href="{{ url_for('auth.login') }}">
Login</a>
</li>
{% endif %}
'Python > flask' 카테고리의 다른 글
Ep08. Flask-RESTful로 Animal CRUD API 구축하기 (0) | 2022.08.31 |
---|---|
Ep07. Flask로 첫 번째 HTTP API 구축하기 (0) | 2022.08.20 |
Ep03. 블로그 웹 애플리케이션 개발 (0) | 2022.07.15 |
Ep02. python DB API, sqlite3 (0) | 2022.07.10 |
Ep02. 라우팅, 변수 규칙, 후행 슬래시, url 구축, http 메소드 (0) | 2022.07.09 |