🔥 View를 상속받아 Login & Logout구현
1. View를 상속받아 Login & Logout구현
1) is_valid()
- 경로 config/urls.py에서 include를 통해 users/urls.py로 연결시켜주고, View를 상속받은 LoginView와 매핑시켰어요.
- 🔎 config/urls.py :
path("users/", include("users.urls", namespace="users")),
- 🔎 users/urls.py :
path("login/", views.LoginView.as_view(), name="login"),
- View를 get방식과 post방식을 사용할 때, 아래처럼 get, post매서드를 통해 분리합니다. 이를통해, GET방식으로 요청된다면 입력할 수 있는 form을 제공하고, POST방식으로 요청된다면 입력된 데이터를 처리해줄 수 있어요.
- POST 방식으로 전달된 데이터를 확인하기 위해서 2가지(is_valid, cleaned_data) 처리가 필요해요.
- 1) is_valid() : form에서 실시한 유효성 검사를 Bool 형태로 반환
- 2) cleaned_data() : 사용자가 입력한 데이터를 Dict형으로 전달
from django.shortcuts import render
from django.views import View
from . import forms
class LoginView(View):
def get(self, request):
form = forms.LoginForm()
context = {"form": form}
return render(request, "users/login.html", context)
def post(self, request):
form = forms.LoginForm(request.POST)
if form.is_valid():
print(form.cleaned_data)
context = {"form": form}
return render(request, "users/login.html", context)
- Django Form을 간단히 만들어볼께요. 현재 비밀번호가 노출되고 이썽요. 비밀번호를 form에 노출되지 않도록 하기 위해서는 form에서 "widget=forms.PasswordInput"을 사용합니다. widget 기능은 form 다양한 style format을 제공해줍니다.
from django import forms
from . import models
class LoginForm(forms.Form):
"""Login Form Definition"""
email = forms.EmailField()
password = forms.CharField(widget=forms.PasswordInput)
- email로 로그인하는 form을 템플릿에 적용시켜보죠. 템플릿에서 {{form.as_p}}을 위치시키면 입력 form이 나타납니다!
- 서버의 보안을 위해 POST로 데이터를 전송할 때는 {% csrf_token %}을 반드시 넣어줘야해요. 넣지 않으면 에러가 발생합니다.
{% extends "base.html" %}
{% block page_title %}
Login
{% endblock page_title %}
{% block search-bar %} # 👈 search_bar를 덮어씌어 나타나지 않게 해줘요:)
{% endblock search-bar %}
{% block content %}
<form method="post" action="{% url "users:login" %}">
{% csrf_token %} # 👈 POST로 데이터를 전송할 때 보안을 위해 반드시 필요해요!
{{form.as_p}}
<button>Login</button>
</form>
{% endblock content %}
2) clean() & checked_password()
- 사용자가 입력한 데이터를 forms.py으로 가져와 DB에 값과 일치하는지 검증하기위해, "clean" 매서드를 사용합니다. "clean_필드명"통해 필드마다 검증하기 위한 매서드를 만들 수도 있고, 필요에 따라 "clean" 매서드 안에서 모두 처리할 수 있도록 할 수 있습니다.
- check_password()를 사용하면, 입력된 사용자의 비밀번호와 DB의 비밀번호가 일치한지 검증시켜줍니다. 일치하면 View로 값을 return해주고, 일치하지않으면 에러를 발생시켜 줄 수 있도록 처리하였어요.
- clean 매서드는 유효성 검증뿐 아니라 검증 결과에 문제가 있다면, 오류 메시지를 템플릿에 쉽게 전달할 수 있습니다.
- ValidationError()만 사용하면, 오류메시지를 해당 필드에 위치시킬 수 없기 때문에 add_error()를 함께 사용해볼께요.
- 🔎 self.add_error("필드명", forms.ValidationError("error 메시지"))
- clean 매서드로 처리한 뒤 값을 return해주지 않으면, View에서 "cleaned_data"의 값이 None을 가지기 때문에 form에서 처리 후 "self.cleaned_data"를 return해주어야 해요. self.cleaned_data을 return하면, 검증을 마친 정제된 email과 password 값이 View로 전달됩니다!
- 🔎 return self.cleaned_data
from django import forms
from . import models
class LoginForm(forms.Form):
"""Login Form Definition"""
email = forms.EmailField()
password = forms.CharField(widget=forms.PasswordInput)
def clean(self):
email = self.cleaned_data.get("email")
password = self.cleaned_data.get("password")
try:
user = models.User.objects.get(email=email)
if user.check_password(password):
return self.cleaned_data
else:
self.add_error("password", forms.ValidationError("Password is wrong"))
except models.User.DoesNotExist:
self.add_error("email", forms.ValidationError("User does not exist"))
3) authenticate()
- 사용자가 입력한 email과 password가 DB의 데이터와 일치하는지 form에서 검증했기 떄문에, View에서 검증을 마친 정제된 값을 다룰 수 있어요. 이 때, Django의 "authenticate"을 사용하여 인증 기능을 진행합니다.
- authenticate는 아래 위치에 있습니다. login, logout 기능도 사용하기 위해 함께 import해줍니다.
- 🔎 from django.contrib.auth import authenticate, login, logout
- form에서 검증한 email과 password 값을 authenticate의 username과 password 값으로 전달해주면, Django에서 알아서 쿠키를 생성하고 인증을 진행합니다.
- 🔎 user = authenticate(request, username=email, password=password)
- 인증이 되었다면, login 매서드를 사용하여 request와 인증완료된 사용자의 Object를 전달하고, redirect로 hom으로 진입하게 합니다!
- 유효하지 않았을 때, form을 context로 템플릿에 전달하는 이유는 form.py에서 탐지한 에러 메시지를 템플릿으로 전달하기 위함입니다:)
- Logout은 사용자가 Logout 버튼을 클릭할 때 호출되도록 FBC로 만들고, logout함수에 사용자의 요청(request)만 전달하면 쉽게 처리할 수 있어요:)
from django.views import View
from django.shortcuts import render, redirect, reverse
from django.contrib.auth import authenticate, login, logout
from . import forms
class LoginView(View):
def get(self, request):
form = forms.LoginForm()
context = {"form": form}
return render(request, "users/login.html", context)
def post(self, request):
form = forms.LoginForm(request.POST)
if form.is_valid():
email = form.cleaned_data.get("email")
password = form.cleaned_data.get("password")
user = authenticate(request, username=email, password=password)
if user is not None:
login(request, user)
return redirect(reverse("core:home"))
context = {"form": form}
return render(request, "users/login.html", context)
def log_out(request):
logout(request)
return redirect(reverse("core:home"))
- "partials/nav.html"에서 로그인이 되었다면 logout 버튼을,,, 로그아웃이된 상태라면 login 버튼이 나타날 수 있게 해야합니다. 템플릿에서 인증결과에 대해서 어떻게 알 수 있을까요? is_authenticated를 사용하면 됩니다.
- 로그인 여부는 "user.is_authenticated"로 확인할 수 있는데, user을 템플릿으로 render하지 않았는데도 불구하고 사용할 수 있습니다. 이는 Django의 "context processor"가 자동으로 render해줬기 때문입니다:)
<a href="{% url "core:home" %}">Nbnb</a>
<ul>
{% if user.is_authenticated %}
<li><a href="{% url "users:logout" %}">Log out</a></li>
{% else %}
<li><a href="{% url "users:login" %}">Log in</a></li>
{% endif %}
</ul>
- Login & Logout 기능 구현 후 최종적인 urls.py의 상태는 아래와 같습니다.
from django.urls import path
from . import views
app_name = "users"
urlpatterns = [
path("login/", views.LoginView.as_view(), name="login"),
path("logout/", views.log_out, name="logout"),
]
- email 주소를 ID로하여 로그인을 구현하고자한다면 "FormView"를 상속받아 사용하는 것이 View를 사용하는것보다 편리합니다.
- FormView의 위치는 아래와 같아요
- 🔎 from django.views.generic import FormView
- template_name은 render되는 html이에요. GET방식일 때, return되는 "users/login.html" 템플릿과 같죠.
- form_class에믐 사용할 form을 지정해주면 됩니다.
- success_url은 인증을 완료했을 때 이동할 경로를 지정해주면 되요. reverse_laze()는 요청이 들어올 때만 호출되는 게으른 reverse 버전입니다.
- "FormView"를 상속받았다면, 내장된 매서드인 form_valid()를 통해 값의 유효성 검사를 진행합니다.
- View 상속받은 경우 : 🔎 is_valid()
- FormView 상속 시 : 🔎 form_valid()
- "FormView"를 상속받았다면, form_valid 결과에 form을 담아 반환해주면 유효성 검사에 문제가 존재할 경우 에러를 표시해줍니다.
- View 상속받은 경우 : 🔎 return render(request, "users/login.html", context)
- FormView 상속 시 : 🔎 return super().form_valid(form)
from django.views.generic import FormView
from django.urls import reverse_lazy
from django.shortcuts import redirect, reverse
from django.contrib.auth import authenticate, login, logout
from . import forms
class LoginView(FormView):
template_name = "users/login.html"
form_class = forms.LoginForm
success_url = reverse_lazy("core:home")
def form_valid(self, form):
email = form.cleaned_data.get("email")
password = form.cleaned_data.get("password")
user = authenticate(self.request, username=email, password=password)
if user is not None:
login(self.request, user)
return super().form_valid(form)
def log_out(request):
logout(request)
return redirect(reverse("core:home"))
- forms.py는 View를 사용할 때와 완전히 동일해요!
from django import forms
from . import models
class LoginForm(forms.Form):
"""Login Form Definition"""
email = forms.EmailField()
password = forms.CharField(widget=forms.PasswordInput)
def clean(self):
email = self.cleaned_data.get("email")
password = self.cleaned_data.get("password")
try:
user = models.User.objects.get(email=email)
if user.check_password(password):
return self.cleaned_data
else:
self.add_error("password", forms.ValidationError("Password is wrong"))
except models.User.DoesNotExist:
self.add_error("email", forms.ValidationError("User does not exist"))