7. Django 로그인 이후 기능
전까지는 로그인, 회원가입, admin(관리자) 페이지를 만들었다면 이제는 로그인 후의 페이지를 만들어야 된다.
쉽게 말해서 '/'경로의 페이지를 만들어주는 과정을 진행할 것이다.
1. templates/tweet/home.html
<!-- templates/tweet/home.html -->
{% extends 'base.html' %}
{% block content %}
<div class="container timeline-container">
<div class="row">
<!-- 왼쪽 컬럼 -->
<div class="col-md-3">
<div class="card">
<div class="card-body">
<h5 class="card-title">Card title</h5>
<h6 class="card-subtitle mb-2 text-muted">Card subtitle</h6>
<p class="card-text">Some quick example text to build on the card title and make up the bulk of
the card's content.</p>
</div>
</div>
</div>
<!-- 오른 쪽 컬럼-->
<div class="col-md-7">
<!-- 글을 작성 하는 곳 -->
<div class="row mb-2">
<div class="col-md-12">
<div class="card">
<div class="card-body">
<div class="media">
<div class="media-body">
<h5 class="mt-0">나의 이야기를 적어주세요</h5>
<p>
<form>
<div class="form-group mb-2">
<textarea class="form-control" style="resize: none" name='my-content' id="my-content"></textarea>
</div>
<button type="submit" class="btn btn-primary" style="float:right;">작성하기</button>
</form>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
<hr>
<!-- 작성 된 글이 나오는 곳 -->
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-body">
<div class="media">
<div class="media-body">
<h5 class="mt-0">Media heading</h5>
<p>Will you do the same for me? It's time to face the music I'm no longer your
muse.
Heard it's
beautiful, be the judge and my girls gonna take a vote. I can feel a phoenix
inside
of me.
Heaven is
jealous of our love, angels are crying from up above. Yeah, you take me to
utopia.</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-2"></div>
</div>
</div>
{% endblock %}
이건 사람마다 다를태니 알아서 하도록 하자
2. app - tweet 설정
항상 순서를 기억하자! view설정 -> urls 설정 -> Project urls설정
#tweet/views.py
from django.shortcuts import render, redirect
# Create your views here.
def home(request):
user = request.user.is_authenticated #사용자가 로그인 되어있는지 확인
if user:
return redirect('/tweet')
else:
return redirect('/sign-in')
def tweet(request):
if request.method == 'GET':
return render(request, 'tweet/home.html')
Attribute/class models.User/is_authenticated
Read-only attribute which is always True (as opposed to AnonymousUser.is_authenticated which is always False). This is a way to tell if the user has been authenticated. This does not imply any permissions and doesn’t check if the user is active or has a valid session. Even though normally you will check this attribute on request.user to find out whether it has been populated by the AuthenticationMiddleware (representing the currently logged-in user), you should know this attribute is True for any User instance.
<출처 Django 공식 문서 - https://docs.djangoproject.com/en/4.0/ref/contrib/auth/>
해석을 해보자면 항상 True일때만 실행이 되며 유저가 인증되어있는지를 알려준다. 유저가 유효한 세션 또는 활성된 상태일때는 허가에 대한 검사나 질문을 하지 않는다. 비록 일반적으로 이 속성을 request.user에서 확인함으로써 (현재 로그인된 유저를 나타내는)AuthenticationMiddleware에서 덧붙여진(?)것인지를 찾겠지만, 해당 속성은 모든 user 객체에 True임을 인지해야 한다.
# tweet/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.home, name='home'), # 127.0.0.1:8000 과 views.py 폴더의 home 함수 연결
path('tweet/', views.tweet, name='tweet') # 127.0.0.1:8000/tweet 과 views.py 폴더의 tweet 함수 연결
]
#project/urls.py
from django.contrib import admin
from django.urls import path, include
from . import views
urlpatterns = [
path('admin/', admin.site.urls),
path('test/', views.base_response, name='first_test'),
path('first/', views.first_view, name='first_view'),
path('', include('user.urls')),
#추가한 부분
path('', include('tweet.urls')),
]
정상 작동된다.
아직 끝난것이 아니다. 저번에 로그인 후 아이디가 나오고 로그인에 성공함을 띄어주었는데 이제는 로그인이 되었으면 해당 페이지 '/'로 접속하게 해줘야한다.
#user/views.py
if me is not None:
auth.login(request, me) #login은 Django가 user id를 session에 저장하게 해준다
#return HttpResponse(f'{me.username} 로그인에 성공하셨습니다!')
return redirect('/')
하면서 안 사실인데 Flask로 작업할때는 GET없이 render_templates를 하는 경우가 꽤 있었다. 보통 GET을 활용한다면 db에서 데이터를 받고 이를 jinja를 활용하여 render_templates를 할때였다.
Django의 경우는 GET을 통해 띄우는 행위가 대부분인데 이유를 조심스럽게 추론해보자면 html을 render하는것 자체를 서버에서 받는다는 개념으로 받아들여지는게 아닌가 싶다.
3. home.html 수정
현재 위와 같이 로그인된 상태인데도 sign in / sign up이 나오는게 보인다. 또한 옆의 카드도 유저 데이터가 반영되어 있지 않다. 이러한 자잘한 상황들을 수정해줄 것이다.
1) 카드
tweet/home.html
수정 전
<h5 class="card-title">Card title</h5>
<h6 class="card-subtitle mb-2 text-muted">Card subtitle</h6>
<p class="card-text">Some quick example text to build on the card title and make up the bulk of
the card's content.</p>
수정 후
<h5 class="card-title">{{ user.username }}</h5>
<p class="card-text">{{ user.bio }}</p>
눈에 많이 익은 친구가 나오는데 바로 중괄호 두번이다.
이는 jinja2에서도 사용되는 방식으로 그냥 템플렛 문법이다 정도로만 이해하면 된다.
여기서 의문점은 이게 도대체 어디서 user라는걸 받아오냐는건데 jinja2의 경우 render_templates를 할때 데이터를 넣어준다. ex) render_templates('home.html', user=user_info)
Django의 경우 따로 넣어주는 부분은 보이지 않고 tweet/home.html에서 다른 app인 user의 정보를 받아오는걸 봐서는 Project/urls.py에 작성을 하면 Project/settings.py에 INSTALLED_APPS에서 받아오는게 아닌가 싶다.
정상 작동됨을 확인할 수 있다.
2) SignIn/SignUp
base.html
수정 전
<form class="form-inline my-2 my-lg-0">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="/sign-in"> Sign In <span class="sr-only"></span></a>
</li>
<li class="nav-item active">
<a class="nav-link" href="/sign-up"> Sign Up <span class="sr-only"></span></a>
</li>
</ul>
</form>
수정 후
<form class="form-inline my-2 my-lg-0">
{% if not user.is_authenticated %}
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="/sign-in"> Sign In <span class="sr-only"></span></a>
</li>
<li class="nav-item active">
<a class="nav-link" href="/sign-up"> Sign Up <span class="sr-only"></span></a>
</li>
</ul>
{% else %}
hello {{ user.username }}! Welcome to SNS.
{% endif %}
</form>
jinja2랑 같은걸 볼 수 있다. is_authenticated는 위에서 이미 보였으므로 생략하겠다.
마찬가지로 정상적으로 적용했다.
3) 로그인이 안되도 tweet이 보이는 문제
로그인이 되지 않아도 url에 /tweet을 치면 들어가진다.
tweet/views.py를 수정하자.
def tweet(request):
if request.method == 'GET':
user = request.user.is_authenticated
if user:
return render(request, 'tweet/home.html')
else:
return redirect('/sign-in')
is_authenticated가 또 나온다. 잊지 말자. 유저 정보가 있으면 True를 반환해주는 함수이다.
다시 해보면 로그인 화면으로 가게 된다.
4) 로그인이 되어있는데(세션에 정보가 있는데) /sign-in /sign-up이 가지는 현상
위랑 똑같이 하면 된다.
user/views.py
from django.shortcuts import render, redirect
from .models import UserModel
from django.http import HttpResponse
from django.contrib import auth
# Create your views here.
def sign_up_view(request):
if request.method == 'GET':
user = request.user.is_authenticated
if not user:
return render(request, 'user/signup.html')
else:
return redirect('/')
elif request.method == 'POST':
username = request.POST.get('username', None)
password = request.POST.get('password', None)
password2 = request.POST.get('password2', None)
bio = request.POST.get('bio', None)
if password != password2:
return render(request, 'user/signup.html')
else:
old_user = auth.get_user_model().objects.filter(username=username)
if old_user:
return render(request, 'user/signup.html')
else:
UserModel.objects.create_user(
username=username,
password=password,
bio=bio
)
return redirect('/sign-in')
def sign_in_view(request):
if request.method == 'POST':
username = request.POST.get('username', None)
password = request.POST.get('password', None)
me = auth.authenticate(request, username=username, password=password) #authenticate 암호화된 비빌번호와 입력된 비밀번호가 맞는지 그리고 사용자와 맞는지 확인을 해준다.
if me is not None:
auth.login(request, me) #login은 Django가 user id를 session에 저장하게 해준다
return redirect('/')
else:
return redirect('/sign-in')
elif request.method == 'GET':
user = request.user.is_authenticated
if not user:
return render(request, 'user/signin.html')
else:
return redirect('/')
5) 로그아웃 구현
#user/views.py
from django.contrib.auth.decorators import login_required
@login_required #사용자가 로그인이 되어있어야만 작동할 수 있게끔 해준다.
def logout(request):
auth.logout(request)
#원래는 세션에서 확인을 하고 제거를 하는 복잡한 과정이 필요로 하지만 장고에서는
#한번에 처리를 해준다.
return redirect('/')
근데 굳이 session을 사용하는 이유가 뭘까? cookie가 session의 단점을 보완해서 나온걸로 알고 있다.
Django에서 session만 호환을 해서 그런건지 의문을 가지게 된다. 또한 참고 자료에서는 auth.logout이 줄을 많이 줄여준다고 했는데 cookie를 쓴다면 똑같이 js에 함수 하나만 만들고 js import만 해주면 된다. 개인적으로는 큰차이가 없어보인다.
이후 url을 추가해줘야한다.
#user/urls.py
path('logout/', views.logout, name='logout'),
하지만 아직 이 함수를 실행할 요소가 html에 없다. 따라서 base.html에서 수정을 할 것이다.
생성 위치는
여기 오른쪽에다가 할것이다.
base.html
{% else %}
<ul class="navbar-nav mr-auto">
<li class="nav-item disabled">
<span class="nav-link">hello {{ user.username }}!</span>
</li>
<li class="nav-item active">
<a class="nav-link" href="/logout"> Logout </a>
</li>
</ul>
{% endif %}
확인을 하면 로그아웃 기능이 정상 작동한다!