저번 시간에는 Django, 장고 프로젝트를 만들어보았고, 장고에서 쓸 모델을 우리가 직접 만들어보았고, urls.py, views.py에서 설정들을 해주면서 회원가입과 로그인 기능들을 만들어보았다.
그에 이어 회원가입과 로그인에 대해 조금 더 좋은 모습으로 업그레이드를 해주기 위한 작업들을 거치고자 한다.
우선 장고에서 기본으로 제공해주는 사용자 모델과 내가 만든 User 모델을 비교해보자.

auth_user는 장고에서 기본으로 제공하는 데이터베이스 테이블이고, my_user는 저번 시간에 ORM을 사용하여 직접 만든 데이터베이스 테이블이다.
확실히 장고에서 기본으로 제공하는 모델이 내가 만든 모델보다 더 많은 데이터베이스를 가지고 있는 것을 확인하였다.
여기서 두 테이블들이 합쳐지게 된다면 두 테이블의 장점을 모두 사용할 수 있게 된다.
이제 해당 모델 업그레이드를 위해 내가 만든 User 모델과 장고의 기본 모델을 합쳐 해당 모델을 업그레이드해주었다.

우선 User 모델에서 장고의 기본 모델과 내가 추가하고자 하는 정보만 합쳐질 수 있도록 겹치는 정보들은 주석처리를 해주었다.

여기서 추가 및 수정된 부분들은 다음과 같다.
: 추가 :
from django.contrib.auth.models import AbstractUser : 장고에서 제공하는 기본 유저 모델을 사용하겠다.
: 수정 :
(models.Model) → (AbstractUser) : 장고에서 제공하는 기본 유저 모델과 연동하겠다.
이제 myDjangoSns의 settings.py 하단에 AUTH_USER_FIELD = 'user.UserModel'을 적어준다.

해당 코드의 의미는 해당 장고 프로젝트에서 기본 사용자 모델을 우리가 만든 User 모델로 사용하겠다는 의미이다.
이제 모델이 변경되었다면 변경된 모델의 내용을 수정하고 반영해주는 절차를 밟아야 한다.
수정 및 반영을 위한 절차는 저번 시간에 사용했었던 makemigrations와 migrate를 터미널에 적어주면 된다.

만약 정상적으로 수정 및 반영이 되었다면 별다른 에러 메시지 없이 정상적으로 수행되는 것을 볼 수 있다.
이렇게 모델을 상속해준다면 다음과 같은 형태로 우리가 만든 my_user 테이블에 기본으로 장고가 제공해주던 테이블의 값이 추가된 것을 알 수 있다.

이제 우리가 만든 User 모델의 업그레이드가 끝났다면 다음으로는 회원가입 기능에 살을 좀 덧붙여보고자 한다.
user의 views.py 안에 해당 코드들을 추가해주었다.
from django.contrib.auth import get_user_model # 사용자가 있는지 검사하는 함수
~
# filter : 장고에서 테이블 조회 시 필요한 데이터만 조회한다.
fail_user = get_user_model().objects.filter(username=username)
# 사용자가 존재하면 페이지를 다시 로드한다.
if fail_user:
return render(request, 'user/signup.html')
else:
UserModel.objects.create_user(username=username, password=password, bio=bio)
return redirect('/sign-in')
원래는 중복 검사가 되지 않아 똑같은 정보를 지닌 아이디를 생성해도 추가가 되는 모습을 볼 수 있었다.
그래서 from django.contrib.auth import get_user_model로 사용자가 존재하는지 검사를 해주는 함수를 사용하여 입력한 해당 사용자가 존재하면 if를, 존재하지 않으면 else를 실행해주도록 하였다.
UserModel을 불러오고 해당 모델에서 username, password, bio의 값들 저장(save)하는 형식으로 5줄 정도의 코드를 이루고 있었다.
하지만 UserModel.objects.create_user(username=username, password=password, bio=bio)로 입력하려는 값을 1줄의 코드로 간결하게 줄여주었다.

회원가입 기능을 고쳐주고 회원가입을 해준 아이디로 로그인 시도를 하자 로그인이 안 되는 모습을 볼 수 있었다.
이 문제는 바로 장고가 기본적으로 패스워드를 자동으로 암호화시켜주기 때문이다.


암호화된 패스워드를 확인하기 위해 다음과 같은 코드로 암호화가 된 패스워드의 값을 확인할 수 있다.
# authenticate : 암호화된 비밀번호와 입력된 비밀번호가 일치하는지, 추가로 사용자와 맞는지 확인해준다.
me = auth.authenticate(request, username=username, password=password)
if me is not None: # 위에서 이미 authenticate로 체크했기 때문에 있는지 없는지만 확인하면 된다.
auth.login(request, me) # 장고가 알아서 관리할 수 있도록 로그인 정보를 넣어준다.
return redirect('/')
else:
return redirect('/sign-in')
elif request.method == 'GET':
return render(request, 'user/signin.html')
return render(request, 'user/signin.html')
me 안에 auth.authenticate를 사용하여 암호화된 데이터들의 값들을 넣어주었고 authenticate로 확인한 데이터가 있는지 없는지만 확인한 뒤 있으면 로그인 다음 페이지로 넘어가도록 해 주고, 없으면 다시 로그인 페이지를 로드하도록 하였다.

이제 회원가입과 로그인 기능에 대한 수정을 마쳤다.
하지만 원래는 로그인 시 다음 페이지로 넘어갈 때 게시글이 있는 모습을 띄워줘야 하지만 확인을 하기 위해 로그인한 유저의 아이디가 표시되어 있는 페이지로만 넘어가진다.
게시글 작성 페이지로 넘어가기 위해 tweet이라는 글쓰기 페이지를 만들고 그에 맞는 설정을 해주고자 한다.
우선 글쓰기 페이지 화면을 보여주기 위한 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 %}
이렇게 html이 준비가 됐다면 해당 html과 연결을 해주기 위해 tweet의 views.py를 설정해주었다.
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')
그리고 해당 설정된 내용을 연결하기 위해 tweet의 urls.py에 해당 내용을 추가해주었다.

로그인한 해당 유저가 맞다면 다음 글쓰기 페이지로 넘어갈 수 있도록 user의 views.py에 인증된 유저가 맞다면 '/'로 redirect 해주도록 수정해주었다.
elif request.method == 'GET':
user = request.user.is_authenticated
if user:
return redirect('/')
else:
return render(request, 'user/signin.html')
return render(request, 'user/signin.html')
그러면 이제 정상적으로 글쓰기 페이지로 넘어가는 것을 볼 수 있었다.

해당 글쓰기 페이지로 잘 넘어가지만 어떤 유저가 로그인을 한 것인지 확인할 방법이 없었다.
그렇기에 상단의 nav바와 글쓰기 페이지에서 좌측 Card title 부분을 지우고 로그인된 유저의 정보를 넣어주기로 하였다.
우선 base.html과 tweet의 home.html의 왼쪽 칼럼 부분을 수정해주었다.
base.html
{% 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 %} {# 아니라면 #}
{{ user.username }} 님 반갑습니다.
{% endif %}
##########################################################
home.html
{# 왼쪽 칼럼 #}
<div class="col-md-3">
<div class="card">
<div class="card-body">
<h5 class="card-title">{{ user.username }}</h5>
<p class="card-text">{{ user.bio }}</p>
</div>
</div>
</div>
상단 nav바에는 로그인된 유저 이름을, 왼쪽 칼럼에는 로그인된 username과 bio가 표시될 수 있도록 해당 코드와 같이 고쳐주었다.

이제 해당 페이지 내에서 로그인된 유저가 누구인지 알아볼 수 있게 되었다.
하지만 로그아웃을 할 수 없어 로그인된 유저 한 명의 정보만 알 수 있고 다른 아이디로 접속하는 것이 불가능하기에 로그아웃 버튼을 만들어보고자 한다.
우선 base.html에 로그아웃 버튼을 추가하고 user의 views.py에서 다음과 같은 코드를 추가하였다.
{% 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 %} {# 아니라면 #}
<ul class="navbar-nav mr-auto">
<li class="nav-item disabled">
<span class="nav-link">
{{ user.username }} 님 반갑습니다.
</span>
</li>
<li class="nav-item active">
<a class="nav-link" href="/logout"> Logout </a>
</li>
</ul>
{% endif %}
from django.contrib.auth.decorators import login_required # 로그인이 되어있는지 확인하는 함수
~
@login_required # 로그인이 되어있어야만 접근이 가능하도록 해주는 함수
def logout(request):
auth.logout(request)
return redirect('/')
로그인과 로그아웃 기능은 장고에서 기본적으로 제공해주기 때문에 다음과 같이 간결하게 코드를 구현할 수 있었다.

이제 로그인 기능을 수정해주고 글쓰기 페이지를 구현해주었다.
다음으로는 글쓰기 페이지에서 해당 유저가 게시글을 작성하기 위한 코드를 작성해보고자 한다.
<form action="/tweet/" method="post">
{% csrf_token %}
<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>
글쓰기 페이지에서 POST를 이용하여 작성을 해주고자 html 코드에 해당 기능을 먼저 적어주었다.
여기서 주의 깊게 볼 부분은 <form action="/tweet/" method="post"> 부분과 textarea 부분의 name='my-content'이다.
저번 글과 마찬가지로 {% csrf_token %}는 CSRF 공격이 들어오면 장고에서 csrf_token이라는 토큰을 사용하여 해당 공격을 방지하는 것이다.
def tweet(request):
if request.method == 'POST':
user = request.user # 현재 로그인되어 있는 사용자의 모든 정보를 가져온다.
my_tweet = TweetModel()
my_tweet.author = user
my_tweet.content = request.POST.get('my-content', '')
my_tweet.save()
return redirect('/tweet')
이제 해당 페이지에서 글을 작성하고 버튼을 누르면 데이터베이스에 해당 값이 들어가는 것을 볼 수 있었다.

이제 데이터베이스에 넣어주는 작업을 했으니 GET 요청 방식을 사용하여 해당 데이터들을 표시해주도록 해주었다.
tweet의 POST 윗부분에 해당 코드를 추가해주었다.
def tweet(request):
if request.method == 'GET':
user = request.user.is_authenticated
if user:
# TweetModel.objects.all() : TweetModel에 있는 모든 데이터를 불러온다.
# order_by() : 데이터 생성된 시간을 역순으로 출력
all_tweet = TweetModel.objects.all().order_by('-created_at') # - : 역순 정렬
return render(request, 'tweet/home.html', {'tweet': all_tweet})
else:
return redirect('/sign-in')
elif request.method == 'POST':
user = request.user # 현재 로그인되어 있는 사용자의 모든 정보를 가져온다.
my_tweet = TweetModel()
my_tweet.author = user
my_tweet.content = request.POST.get('my-content', '')
my_tweet.save()
return redirect('/tweet')
이렇게 GET으로 저장된 값들을 불러오고, POST로 작성을 할 수 있도록 만들어주었다.

이제 DELETE를 사용하여 만들어진 게시물을 삭제할 수 있도록 기능을 추가로 넣어주었다.
{% if cm.author == user %}
<div style="float: right">
<a href="/tweet/comment/delete/{{ cm.id }}">
<span class="badge bg-danger">삭제</span>
</a>
</div>
{% endif %}
# 게시글 삭제하기
@login_required # 로그인이 되어있어야만 접근이 가능하도록 해주는 함수
def delete_tweet(request, id):
my_tweet = TweetModel.objects.get(id=id)
my_tweet.delete()
return redirect('/tweet')
로그인된 유저가 해당 게시물을 작성한 유저와 일치하면 삭제 버튼이 활성화되도록 만들어주었다.
그리고 views.py에는 이미 logout 기능을 구현할 때 login_required를 import 해주었기 때문에 해당 코드에서는 제외했다.

이제 마지막으로 작성된 해당 게시글에 댓글을 작성할 수 있도록 tweet의 models.py에 댓글 모델을 추가해주었다.
# 게시글 코멘트
class TweetComment(models.Model):
class Meta:
db_table = 'comment'
tweet = models.ForeignKey(TweetModel, on_delete=models.CASCADE)
author = models.ForeignKey(UserModel, on_delete=models.CASCADE)
comment = models.CharField(max_length=256)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
작성된 게시글과 작성한 사람은 tweet 게시글과 연관이 되어야 하기 때문에 ForeignKey를 사용해주었다.
모델이 수정되었으니 makemigrations와 migrate를 사용하여 모델이 수정되었다는 것을 알려주고 해당 모델을 반영시켜주었다.

작성된 댓글을 볼 수 있는 페이지인 tweet_detail.html을 만들어 주었다.
왼쪽 칼럼과 오른쪽 칼럼은 tweet 페이지와 동일하게 만들어 주었고, 댓글을 달아주기 위한 해당 게시글과 댓글 작성 및 삭제를 위한 코드를 만들어주었다.
{% extends 'base.html' %}
{% block title %}
게시글 자세히보기
{% endblock %}
{% 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">{{ user.username }}</h5>
<p class="card-text"> {{ user.bio }}</p>
</div>
</div>
</div>
{# 오른쪽 칼럼 #}
<div class="col-md-7">
<div class="row">
<div class="col-md-12 mb-2">
<div class="card">
<div class="card-body">
{% if tweet.author == user %}
<div style="text-align: right">
<a href="/tweet/delete/{{ tweet.id }}">
<span class="badge rounded-pill bg-danger">삭제</span>
</a>
</div>
{% endif %}
<div class="media">
<div class="media-bod">
<h5 class="mt-0">{{ tweet.content }}</h5>
</div>
<div style="text-align: right">
<span style="font-size: small">{{ tweet.author.username }}-{{ tweet.created_at|timesince }} 전</span>
</div>
</div>
</div>
</div>
</div>
</div>
{# 댓글 작성하는 곳 #}
<form class="input-group mb-3" action="/tweet/comment/{{ tweet.id }}" method="post">
{% csrf_token %}
<input type="text" class="form-control" id='comment' name='comment' placeholder="댓글을 작성 해 주세요"/>
<button class="btn btn-outline-secondary" type="submit">작성</button>
</form>
<hr>
{# 작성된 댓글 불러오기 #}
{% for cm in comment %}
<div class="row">
<div class="col-md-12">
<div class="media">
<div class="media-body">
<h5 class="mt-0"> {{ cm.comment }} </h5>
<span> {{ cm.author }} </span>
<span> - {{ cm.created_at | timesince }} 전</span>
</div>
{% if cm.author == user %}
<div style="float: right">
<a href="/tweet/comment/delete/{{ cm.id }}">
<span class="badge bg-danger">삭제</span>
</a>
</div>
{% endif %}
</div>
</div>
</div>
<hr>
{% endfor %}
</div>
</div>
</div>
{% endblock %}
이제 tweet의 views.py로 돌아가 댓글을 보여주고 작성하고 삭제할 수 있는 코드를 구현해주었다.
그리고 그 뒤에 tweet의 urls.py에 댓글 관련 url을 연결해주었다.
# 작성된 댓글들을 보여주기 위한 공간
@login_required
def detail_tweet(request, id):
my_tweet = TweetModel.objects.get(id=id)
tweet_comment = TweetComment.objects.filter(tweet_id=id).order_by('-created_at')
return render(request, 'tweet/tweet_detail.html', {'tweet': my_tweet, 'comment': tweet_comment})
# 댓글 작성하기
@login_required
def write_comment(request, id):
if request.method == 'POST':
comment = request.POST.get("comment", "")
current_tweet = TweetModel.objects.get(id=id)
TC = TweetComment()
TC.comment = comment
TC.author = request.user
TC.tweet = current_tweet
TC.save()
return redirect('/tweet/' + str(id))
# 댓글 삭제하기
@login_required
def delete_comment(request, id):
comment = TweetComment.objects.get(id=id)
current_tweet = comment.tweet.id
comment.delete()
return redirect('/tweet/' + str(current_tweet))
from django.urls import path
from . import views
urlpatterns = [
path('', views.home, name='home'),
path('tweet/', views.tweet, name='tweet'),
path('tweet/delete/<int:id>', views.delete_tweet, name='delete-tweet'),
path('tweet/<int:id>', views.detail_tweet, name='detail-tweet'),
path('tweet/comment/<int:id>', views.write_comment, name='write-comment'),
path('tweet/comment/delete/<int:id>', views.delete_comment, name='delete-comment'),
]
이렇게 해서 해당 게시글에 대한 댓글을 보여주고, 등록 및 삭제를 할 수 있는 코드를 구현해주었다.


-
오늘은 회원가입과 로그인 기능을 다듬고, 게시글과 게시글에 관련된 댓글 기능을 추가해주었다.
처음에는 장고의 기능들에 대해 이해하기 어려웠지만 다양한 기능들을 추가해보면서 장고에 대해 알아가니 괜찮아진 것 같다.
확실히 많이 해보면 해볼수록 어떻게 해야 할지 알 수 있는 것 같다는 느낌을 받았고, 경험이 중요하다는 생각을 가져보는 계기가 되었다.
이제 장고 기초 강의가 끝나가고 있다.
이 앞에 기다리고 있는 장고 프로젝트를 생각하면서 더 열심히 공부해야겠다고 생각했다.
:P
'TIL 및 WIL > TIL (Today I Learned)' 카테고리의 다른 글
| [TIL] 2022.06.02 (Django 추천 시스템 팀 프로젝트1) (0) | 2022.06.03 |
|---|---|
| [TIL] 2022.05.31 (Django 기초3) (0) | 2022.05.31 |
| [TIL] 2022.05.27 (Django 기초1) (0) | 2022.05.27 |
| [TIL] 2022.05.26 (Django 기초0, Python 문법 복습) (1) | 2022.05.26 |
| [TIL] 2022.05.25 (사물인식 머신러닝 - 팀 프로젝트(끝)) (1) | 2022.05.25 |