
보통은 아래 회원가입 뷰(=AccountCreateView) 처럼, django에서 지원하는 클래스 뷰를 적절히 상속받아서 필요에 맞는 클래스 뷰를 만들고
# accountapp > views.py
from django.contrib.auth.forms import UserCreationForm
from django.views.generic import CreateView
from django.urls import reverse_lazy
# 회원가입 뷰
class AccountCreateView(CreateView):
model = User
form_class = UserCreationForm
success_url = reverse_lazy('accountapp:hello_world')
template_name = 'accountapp/create.html'
urls.py에서 url과 뷰의 매핑을 정의할 때 아래와 같이 설정해주는 걸 볼 수 있다.
참고로 path의 두 번째 인자에는 뷰 함수를 받거나, 뷰 클래스.as_view()를 받는다. as_view() 메서드는 해당 클래스를 django의 url 라우터가 이해하고 사용할 수 있는 호출 가능한 뷰 함수로 변환한다고 한다.
# accountapp > urls.py
from django.urls import path
from accountapp.views import AccountCreateView
app_name = "accountapp"
urlpatterns = [
path('create/', AccountCreateView.as_view(), name='create'),
]
그런데 위 처럼 django 기본 뷰 클래스를 상속받아 새로운 클래스를 만들지 않고, django에서 만든 뷰 클래스 자체를 그대로 사용하는 경우도 있다. 아래 뷰 클래스를 사용하는 실습을 했다.
LoginView : 로그인 할 때 사용LogoutView : 로그아웃 할 때 사용LoginView, LogoutView 클래스)LoginView를 그대로 사용할 경우, 따로 클래스 뷰를 생성해줄 필요가 없다.
하지만 urls.py에서 뷰와 url 매핑 작업은 필요하다.
# accountapp > urls.py
from django.contrib.auth.views import LoginView, LogoutView
from django.urls import path
from accountapp.views import hello_world, AccountCreateView
app_name = "accountapp"
urlpatterns = [
path('hello_world/', hello_world, name='hello_world'),
path('create/', AccountCreateView.as_view(), name='create'),
# 여기가 새로 추가된 부분 !!!!!!!!!!!!!!!!!!!
path('login/', LoginView.as_view(template_name='accountapp/login.html'), name='login'),
path('logout/', LogoutView.as_view(), name='logout'),
]
path('login/', LoginView.as_view(template_name='accountapp/login.html'), name='login'):path('logout/', LogoutView.as_view(), name='logout'):간단한 로그인 페이지
<!-- login.html -->
{% extends "base.html" %}
{% block content %}
<div style="text-align: center">
<div>
<h4>Login</h4>
</div>
<div>
<form action="" method="post">
{% csrf_token %}
{{ form }}
<input type="submit" class="btn btn-primary">
</form>
</div>
</div>
{% endblock %}

그런데 여기서 로그인을 하면 아래와 같이 별도로 정의하지도 않았는데 ~/profile url로 접근하려고 하는 것을 볼 수 있다. 존재하지 않는 url로 접근하려니 404 not found 에러가 발생했다.

LoginView, LogoutView는 로그인 혹은 로그아웃을 수행하고 난 뒤 가야할 경로를 아래와 같은 우선순위로 얻는다고 한다.next 인자LOGIN_REDIRECT_URL, LOGOUT_REDIRECT_URL (settings.py 파일 내 정의)Defaultnext 인자를 받은 경우 로그인 혹은 로그아웃이 수행되었을 때, 해당 next 경로로 redirect 한다. 만약 next 인자를 받지 못한 경우엔 settings.py에 정의된 LOGIN_REDIRECT_URL(로그인의 경우), LOGOUT_REDIRECT_URL(로그아웃의 경우)에 정의된 경로로 redirect 한다. 이마저도 없다면 Default로 설정된 값으로 redirect 되는데, 여기서는 next도 LOGIN_REDIRECT_URL도 설정된 값이 없어서 /profile이라는 Default 경로로 redirect를 하게 된 것이다. next인자 또는 LOGIN_REDIRECT_URL, LOGOUT_REDIRECT_URL 를 설정하면 된다.< 변경 전 (header.html) >
<div class="pragmatic_header">
<div>
<h1 class="pragmatic_logo">Pragmatic</h1>
</div>
<div>
<span>nav1</span>
<span>nav2</span>
<span>nav3</span>
<span>nav4</span>
</div>
</div>
< 변경 후 (header.html) >
<div class="pragmatic_header">
<div>
<h1 class="pragmatic_logo">Pragmatic</h1>
</div>
<div>
<span>nav1</span>
<span>nav2</span>
<span>nav3</span>
{% if not user.is_authenticated %}
<a href="{% url 'accountapp:login' %}?next={{ request.path }}">
<span>login</span>
</a>
{% else %}
<a href="{% url 'accountapp:logout' %}?next={{ request.path }}">
<span>login</span>
</a>
{% endif %}
</div>
</div>
?next={{ request.path }}:next 인자 테스트
/account/hello_world 경로에서 헤더 부분의 login 버튼을 클릭한다.
이 때, /account/login 경로로 next 인자를 전달한다. (next="/account/hello_world")

next 인자를 받은 상태에서 로그인을 하게 되면

아까처럼 /profile 경로가 아닌 next 경로로 redirect 된 걸 볼 수 있다.
그런데 로그아웃을 했더니 405 에러코드가 발생했다.
이는 로그아웃 호출시 적절하지 않은 Method를 사용한 것으로 보인다.
현상에 대해서 찾아보다 해당 링크를 발견했다.
(장고 5부터 LogoutView로의 요청은 POST 방식만 허용)
GET 방식도 허용이 됐다.<a>태그는 GET Method 방식으로만 보낸다.<a>태그를 이용해서 LogoutView를 요청했고, 결과적으로 GET 방식으로 요청한 꼴이 되었다.POST 방식으로 보내야 한다.<a>태그가 아니라 <form>태그를 사용해서 POST 방식으로 보내는 방법을 구현해봐야 할 것 같다. < header.html 변경 전 >
<div class="pragmatic_header">
<div>
<h1 class="pragmatic_logo">Pragmatic</h1>
</div>
<div>
<span>nav1</span>
<span>nav2</span>
<span>nav3</span>
{% if not user.is_authenticated %}
<a href="{% url 'accountapp:login' %}?next={{ request.path }}">
<span>login</span>
</a>
{% else %}
<a href="{% url 'accountapp:logout' %}?next={{ request.path }}">
<span>login</span>
</a>
{% endif %}
</div>
</div>
< header.html 변경 후 >
<div class="pragmatic_header">
<div>
<h1 class="pragmatic_logo">Pragmatic</h1>
</div>
<div>
<span>nav1</span>
<span>nav2</span>
<span>nav3</span>
{% if not user.is_authenticated %}
<a href="{% url 'accountapp:login' %}?next={{ request.path }}">
<span>login</span>
</a>
{% else %}
<form style="display: inline" action="{% url 'accountapp:logout' %}?next={{ request.path }}" method="post">
{% csrf_token %}
<input type="submit" class="btn" value="logout">
</form>
{% endif %}
</div>
</div>
로그아웃 부분에서 태그를 변경해주었더니 더이상 에러는 발생하지 않았다.
LOGIN_REDIRECT_URL, LOGOUT_REDIRECT_URL 설정하기아직 남아있는 문제가 있다. 로그인 페이지에 직접적으로 접근을 한 뒤, 로그인을 하는 경우이다.
문제가 되는 이유는, 헤더의 login 버튼을 눌러서 로그인 페이지로 접근한 것이 아니기 때문에 next 인자를 전달하지 않고, LOGIN_REDIRECT_URL도 따로 설정된 것이 없으므로 Default(=/profile) 경로로 redirect 되기 때문이다.
따라서, 위처럼 next인자를 받지 않은 케이스에 대비하기 위해 settings.py 파일 내부에 LOGIN_REDIRECT_URL 값을 설정해주면 된다.
LOGIN_REDIRECT_URL 설정하는 김에, LOGOUT_REDIRECT_URL도 같이 설정해준다.
< settings.py >
LOGIN_REDIRECT_URL = reverse_lazy('accountapp:hello_world')
LOGOUT_REDIRECT_URL = reverse_lazy('accountapp:login')
FastAPI 프레임워크만 사용해보다가 이제 막 Django를 처음으로 입문하고 있는데,
확실히 Django가 러닝 커브가 좀 높은 것 같다.
FastAPI의 경우엔 소스코드만 봐도 어떤 순서로 어떻게 작동할 지 대충 예상이 가는데
Django는 소스코드만 봐서는 알기 어렵다.
Django 문서좀 들락날락 거리고, 계속 익숙해지려고 노력하는 수 밖에....
빛이 있으려면 어둠이 있어야 한다는 밥아저씨 명언처럼,
지금은 Django로 개발하는 것이 너무 어둡고 막막하지만, 이 시기를 잘 견뎌내면 나도 어엿한 Django 고수가 되어있기를 바래본다.