2020-03-27

Django 09. 로그인, 로그아웃 구현

Django Authentication에서 제공하는 기능을 사용하여 사용자 로그인, 로그아웃을 구현합니다.


1. 로그인 forms.py 작성

Login을 구현하기 위해 사용할 form을 먼저 구현하겠습니다. users appforms.py에 해쉬화된 비밀번호를 검사하는 check_password를 import 한 후 아래의 소스를 입력합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# users/forms.py

class LoginForm(forms.Form):
user_id = forms.CharField(
widget=forms.TextInput(
attrs={'class': 'form-control',}),
error_messages={'required': '아이디을 입력해주세요.'},
max_length=17,
label='아이디'
)
password = forms.CharField(
widget=forms.PasswordInput(
attrs={'class': 'form-control',}),
error_messages={'required': '비밀번호를 입력해주세요.'},
label='비밀번호'
)

def clean(self):
cleaned_data = super().clean()
user_id = cleaned_data.get('user_id')
password = cleaned_data.get('password')

if user_id and password:
try:
user = User.objects.get(user_id=user_id)
except User.DoesNotExist:
self.add_error('user_id', '아이디가 존재하지 않습니다.')
return

if not check_password(password, user.password):
self.add_error('password', '비밀번호가 틀렸습니다.')

LoginForm 클래스를 생성한 뒤 입력필드에 맞는 적절한 widget을 삽입합니다.

clean 함수를 클래스 내부에 추가하고 해당 입력값에 맞는 표준 형식으로 변환하기 위해 cleaned_data로 form에 입력된 user_id와 password 값을 담아두고 검사하여 아이디 존재여부와 비밀번호 오류를 form에서 처리하도록 구현하였습니다.

Django Forms Widgets 공식문서

2. 로그인 views.py 작성

Django에서 제공하는 로그인 기능을 사용하기 위해 contrib.auth의 login, authenticate 와 Generic View인 FormView를 import 한 후 users app의 views.py에 아래의 코드를 입력합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# users/views.py

from .forms import LoginForm
from django.contrib.auth import login, authenticate
from django.views.generic import FormView

@method_decorator(logout_message_required, name='dispatch')
class LoginView(FormView):
template_name = 'users/login.html'
form_class = LoginForm
success_url = '/users/main/'

def form_valid(self, form):
user_id = form.cleaned_data.get("user_id")
password = form.cleaned_data.get("password")
user = authenticate(self.request, username=user_id, password=password)

if user is not None:
self.request.session['user_id'] = user_id
login(self.request, user)

return super().form_valid(form)

마찬가지로 이미 로그인 중인 사용자의 접근을 막기 위해 logout_message_required를 사용하고, 직접 구현한 decorators의 user객체를 비교하기 위해 user_id란 이름의 세션에 로그인한 user_id 값을 추가합니다. form_valid를 통과한 user 객체가 존재한다면 로그인을 한 후 작성되어 있는 main페이지로 이동하게됩니다.

3. 로그아웃 views.py 작성

Django의 logout을 호출하면 현재 요청에 대한 세션이 모두 제거됩니다. views.py에 logout을 import 한 후 아래의 코드를 입력합니다.

1
2
3
4
5
6
7
# users/views.py

from django.contrib.auth import logout

def logout_view(request):
logout(request)
return redirect('/')

4. urls.py 작성

구현한 login, logout view를 연결하기 위해 urls.pyurlpatterns에 아래의 코드를 추가합니다. 로그인 성공시 이동할 main path도 함께 추가합니다. main페이지 구현은 따로 포스팅하지 않겠습니다.

1
2
3
4
5
# users/urls.py

path('main/', views.main_view, name='main'),
path('login/', views.LoginView.as_view(), name='login'),
path('logout/', views.logout_view, name='logout'),

5. templates 작성

templates의 users폴더에 login.html을 생성하고 아래의 코드를 입력합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!-- templates/users/login.html -->

<form method="POST" action="." novalidate>
{% csrf_token %}
<div>
<label name="label_user_id" for="{{ form.user_id.id_for_label }}">
{{ form.user_id.label }}
</label>
{{ form.user_id }}
<script type="text/javascript">
if (document.getElementsByName('user_id')[0].value != '') {
document.getElementsByName('label_user_id')[0].setAttribute('class', 'active');
}
</script>
{% if form.user_id.errors %}
<script type="text/javascript">
document.getElementsByName('user_id')[0].setAttribute('class', 'form-control is-invalid')
</script>
{% for error in form.user_id.errors %}
<div class="invalid-feedback">
{{ error }}
</div>
{% endfor %}
{% endif %}
</div>
</form>

위 소스는 login.html의 user_id 입력 필드입니다. { { form.user_id } }로 forms.py에서 설정한 widget type을 생성하고 add_error가 있다면 erros 값으로 넘어와 오류를 출력하게 됩니다. password 입력 필드 또한 ‘user_id’ 부분을 forms.py에서 지정한 ‘password’로 바꿔 작성하면 로그인 템플릿 구현이 완료됩니다.

6. 결과

django-project-09

*전체 html, css 등은 자세하게 포스팅하지 않습니다. 제 Github에서 소스를 확인하실 수 있습니다.