auth_user
이고, 내가 만든 사용자 모델은 my_user
이다. auth_user
의 경우, 내가 직접 만들었던 user model보다 더 많은 속성을 가지고 있다.my_user
model을 만들어 사용자 관리에 들어가게 되면 session
을 사용하는 등의 과정을 거쳐 보안을 적용해야 하지만, auth_user
model을 사용하게 되면 그런 과정을 거치지 않아도 된다.만약 class B에서 class C의 기능을 사용하고 싶다면, 추가적인 코드를 작성하지 않고 class B(C)
라고 작성하여 class C의 기능을 상속받아 사용할 수 있다.
이미 이전 주차에서 작성했었던 user/models.py
에서 class의 상속이 있었다.
class UserModel(models.Model)
를 보면 UserModel
에서 models.Model
의 기능을 상속받아 사용하겠다는 뜻이다.
#user/models.py
from django.db import models
class UserModel(models.Model):
# DB 테이블의 이름을 지정하는 등 데이터베이스의 정보를 넣어주는 역할
class Meta:
db_table = "my_user"
auth_user
와 연동되는 AbstractUser
를 상속받아 사용하기 때문에 이전에 직접 작성하였던 것중 auth_user
의 내용과 중복되는 것들을 삭제해 주었다. ##### user/models.py #####
from django.db import models
# 우리는 django에서 기본적으로 제공하는 user 모델을 사용하겠다.
# AbstractUser는 django에서 기본적으로 제공하는 user model
from django.contrib.auth.models import AbstractUser
class UserModel(AbstractUser):
# DB 테이블의 이름을 지정하는 등 데이터베이스의 정보를 넣어주는 역할
class Meta:
db_table = "my_user"
# 원래 이전에 작성했던 usename, password, created_at, updated_at은 이미 auth_user에 존재
# 그러므로 그것들은 다 지우고, 우리가 추가하고 싶은 bio만 작성해주면 됨!
# 상세 정보
bio = models.CharField(max_length=256, default='')
mySpartaSns/settings.py
# mySpartaSns/settings.py
# django의 기본 모델에 다른 정보를 끼워넣어 그것을 우리의 User model로 사용하기로 했기 때문에
# 그것을 django에게 알려주어야 한다.
AUTH_USER_MODEL = 'user.UserModel'
$ python manage.py makemigrations
$ python manage.py migrate
my_user
에서 auth_user
의 내용을 상속받아 사용하면서 기존 내용을 삭제하는 변화가 일어났기 때문에 아래의 메세지들에서 알 수 있듯이 변화가 일어나고 그 변화를 적용해준 것을 볼 수 있다.# 사용자가 데이터베이스 안에 있는지 검사하는 함수
from django.contrib.auth import get_user_model
이미 데이터베이스에 있는 user
인지 판별하는 부분을 수정하도록 하겠다. # 위에서 import한 get_user_model을 사용해 이미 있는 user인지 판별해줌
not_new_user = get_user_model().objects.filter(username=username)
# 이미 정보를 입력한 user가 존재한다면?
# filter 기능을 통해서 같은 이름이 있는지 걸러준다.
# not_new_user = UserModel.objects.filter(username=username)
if not_new_user:
return render(request, 'user/signup.html')
else:
UserModel.objects.create_user(username=username, password=password, bio=bio)
# new_user = UserModel()
# new_user.username = username
# new_user.password = password
# new_user.password2 = password2
# new_user.bio = bio
# 위에서 받아온 정보들을 저장하는 과정을 거처야 함 꼭!
# new_user.save()
# 위와 같이 회원정보를 성공적으로 저장하게 되면 로그인 페이지로 리다이렉트해준다.
return redirect('/sign-in')
해싱
이 되어 있지 않다. 그러므로 현재 코드를 그대로 사용하게 된다면 해싱된 비밀번호와 해싱되지 않은 비밀번호를 계속 대조하는 것이므로 절대로 로그인에 성공할 수 없을 것이다. authenticate
모듈을 활용해 사용자 인증을 해준다.# authenticate : 입력한 비밀번호와 해싱된 비밀번호가 일치하는지, 그것과 사용자가 일치하는지 한 번에 확인해줌
me= auth.authenticate(request, username=username, password=password)
# 앞에 있는 username은 UserModel 안에 있는 username이다. 뒤에 있는 것은 우리가 입력한 username이다.
# me = UserModel.objects.get(username=username)
if me.password == password:
request.session['user'] = me.username
⬇️
if me is not None:
auth.login(request, me)
from django.shortcuts import render, redirect
def home(request):
# user가 로그인되어 있는지 확인할 수 있음
user = request.user.is_authenticated
if user: # user가 로그인되어 있으면
return redirect('/tweet') # /tweet 으로 리다이렉트 해주고
else: # 로그인되어 있지 않다면
return redirect('/sign-in') # 로그인 페이지로 리다이렉트 해준다.
def tweet(request):
if request.method == 'GET':
return render(request, 'tweet/home.html')
# 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 함수 연결
]
http://127.0.0.1:8000/
로 들어가게 되면 다음과 같은 이쁜 화면이 나오게 된다.mySpartaSns
에 포함되어 있는 내 django 프로젝트의 모든 세팅과 url의 정보가 담겨 있는 파일들을 수정하지 않음을 알게 되었다. 그러므로 수정을 시작해보겠다.mySpartaSns/urls.py
page not found
가 되버린다.from django.contrib import admin
from django.urls import path, include
# 현재 내가 있는 파일에서 views.py를 불러옴
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'),
# spartaSns와 user의 url이 연결되었다!!
path('', include('user.urls')),
path('', include('tweet.urls')),
]
if me is not None:
auth.login(request, me)
return redirect('/')
user의 정보
가 하나도 보이지 않는다. Sign in
과 Sign Up
버튼이 계속 보여지고 있다.template 문법
을 통해 개인 정보를 표시할 것이며, 해당 수정은 templates/tweet/home.html
에서 이루어졌다. auth_model
과 우리가 만든 user_model
을 합친 모델에서 우리에게 필요한 정보인 username
과 bio
를 가져와 반영시켜주었다. <!-- base.html을 기본으로 가져가고, 남는 공간에 content를 붙여 넣겠다. -->
{% 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">{{ user.username }}</h5>
<p class="card-text">{{ user.bio }}</p>
</div>
</div>
</div>
Sign in
과 Sign Up
버튼 부분을 수정해주도록 하겠다. template 문법
을 사용하며, 이번에는 조건문을 사용하기 위해{% .... %}
를 사용하도록 하겠다. <form class="form-inline my-2 my-lg-0">
<!-- 유저가 인증이 되었니?(로그인이 완료된 사용자니?)에 대해 묻는 조건문 -->
<!-- sign in과 sign up 버튼이 보이려면 로그인이 되어 있지 않아야 한다. -->
<!-- 만약 사용자 인증이 되어 있지 않다면 -->
{% 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 }}님 반갑습니다~!
<!-- if문 종료 -->
{% endif %}
</form>
시크릿 모드
를 통해 우리의 http://127.0.0.1:8000/tweet
으로 들어가게 되면 아래의 화면이 나오게 된다. 로그인을 하지 않은 상태에서도 해당 페이지에 갈 수 있도록 길이 나있는 것
같다.tweet/urls.py
로 먼저 들어가 보았다. /tweet
이라는 url이 존재하고, 해당 url에 접속하게 되면 tweet/views.py
에 있는 tweet
함수가 실행된다는 것을 알 수 있었다. 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 함수 연결
]
tweet/views.py
로 들어가 tweet
함수를 찾았고, 해당 내용을 수정하기로 하였다. def tweet(request):
if request.method == 'GET':
# 사용자 인증에 대한 정보를 user 변수에 대입
user = request.user.is_authenticated
# 만약 사용자 인증이 되어 있다면
if user:
# tweet/home.html 로 사용자가 갈 수 있도록 함
return render(request, 'tweet/home.html')
else:
# 사용자 인증이 되지 않았으므로 로그인 화면으로 보내줌
return redirect('/sign-in')
사용자 인증 기능
을 추가했고, 아래의 사진처럼 로그인이 되지 않은 사람을 걸러내 로그인 시도를 먼저
할 수 있도록 해줄 수 있었다.이미 로그인한 사용자는 굳이 회원가입
과 로그인
페이지에 접속할 필요가 없다. 현재의 상태로는 url을 만지지 않고 흘러가는데로 html 안에서만 과정을 진행한다면 문제될 것이 없었다. 그런데 url을 만지는 순간 로그인을 했는데도 페이지가 이동되는 것을 알 수 있었다.
해당 문제를 해결하기 위해 회원가입
과 로그인
이 이루어지는 user/views.py
를 수정해보도록 하겠다.
앞에서 수정했던 부분은 그대로 가져가면서, method == 'GET'
인 부분의 코드를 수정해 보았다.
redirect('/')
를 한 이유는 /
를 담당하는 함수는 tweet/views.py
의 home
함수이고, 해당 함수에서 사용자 인증에 성공하면 /tweet
으로 redirect를 해주기 때문에 그냥 /
를 사용했다.
아래의 tweet/urls.py
를 확인해 보면, url이 없는 경우에 즉 http://127.0.0.1:8000/
인 경우에 home
함수가 실행이 되는 것을 알 수 있다.
그 다음 사진을 확인해 보면, home
함수에서 사용자 인증이 되어 있다면 /tweet
으로 url을 설정해주는 것을 알 수 있다.
# 뒷부분 생략
def sign_up_view(request):
if request.method == 'GET':
# user 변수에 사용자 인증 정보를 담는다.
user = request.user.is_authenticated
# 만약 사용자 인증이 되어 있다면
if user:
# home으로 바로 이동시켜주고
return redirect('/')
# 만약 사용자 인증이 되어 있지 않다면
else:
# 회원가입 페이지로 보내준다.
return render(request, 'user/signup.html')
# 앞부분 생략
def sign_in_view(request):
elif request.method == 'GET':
user = request.user.is_authenticated
if user:
return redirect('/')
else:
return render(request, 'user/signin.html')
user
와 관련된 기능이기 때문에 user/views.py
를 수정해주도록 하겠다. def logout(request):
auth.logout(request)
return redirect('/')
# 다른 부분 생략
from django.contrib.auth.decorators import login_required
# 사용자가 로그인이 되어있어야만 접근 가능하다고 알려주는 함수
@login_required
def logout(request):
auth.logout(request)
return redirect('/')
user/urls.py
에서 url을 설정하도록 하겠다. from django.urls import path
from . import views
urlpatterns = [
# sign-up/ 으로 들어가면 views.py에 있는 sign_up_view 함수가 실행된다. (해당 함수는 signup.html로 render 해준다.)
path('sign-up/', views.sign_up_view, name='sign-up'),
# sign-in/ 으로 들어가면 views.py에 있는 sign_in_view 함수가 실행된다. (해당 함수는 signin.html로 render 해준다.)
path('sign-in/', views.sign_in_view, name='sign-in'),
path('logout/', views.logout, name='logout'),
]
base.html
에 작업을 해주었다. <!-- 윗부분 및 아랫부분 생략 -->
<form>
<!-- 사용자 인증이 완료된 사용자라면 이름을 보여줘 -->
{% else %}
<ul class="navbar-nav mr-auto">
# 해당 부분이 선택되지 않도록 disabled 작성
<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>
<!-- if문 종료 -->
{% endif %}
</form>
textarea
가 존재한다. 해당 부분에 글을 입력하고 버튼을 누르게 되면 게시글이 POST 요청이 되어 db에 저장되는 부분까지 진행해 보겠다. home.html
을 열어 구조를 확인하고 수정을 하도록 하겠다.<form>
태그 안의 내용이 버튼을 누르면 전송될 수 있도록 method와 action을 설정해 주었다. <!-- 글을 작성 하는 곳 -->
<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 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>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
tweet/views.py
all_tweet
이라는 변수를 만들어, 해당 변수 안에 사용자가 입력한 TweetModel 안에 있는 모든 정보를 저장하게 된다. # 뒷 부분 생략
def tweet(request):
if request.method == 'GET':
# 사용자 인증에 대한 정보를 user 변수에 대입
user = request.user.is_authenticated
# 만약 사용자 인증이 되어 있다면
if user:
# tweet 모델에 저장한 모든 데이터를 불러올 거야. 그런데 순서는 시간 순서가 아닌 최신순으로 불러와줘.
all_tweet = TweetModel.objects.all().order_by('-created_at')
# tweet/home.html 로 사용자가 갈 수 있도록 함
return render(request, 'tweet/home.html', {'tweet': all_tweet})
else:
# 사용자 인증이 되지 않았으므로 로그인 화면으로 보내줌
return redirect('/sign-in')
template/tweet/home.html
tw.author.username
는 tweet의 models.py에서 볼 수 있듯이 author는 외래키
로 UserModel의 값을 받아와 쓰고 있다. 그러므로 username을 명시하지 않으면 UserModel의 모든 값이 나오게 되므로 명시를 해줘야 한다.<!-- 작성 된 글이 나오는 곳 -->
<div class="row">
<!-- views.py에서 게시글의 모든 정보를 tweet으로 넘겨줬음 -->
{% for tw in tweet %}
<div class="col-md-12">
<div class="card">
<div class="card-body">
<div class="media">
<div class="media-body">
<h5 class="mt-0">{{ tw.content }}</h5>
</div>
<div style="text-align: right;">
<!-- tw.author.username인 이유는 tweet의 models.py에서 볼 수 있듯이 author는 외래키로 UserModel
의 값을 받아와 쓰고 있다. 그러므로 username을 명시하지 않으면 UserModel의 모든 값이 나오게 되므로 명시를 해줘야 한다. -->
<!-- tw.created_at은 그냥 가져오게 되면 몇월 몇일 몇시 몇분까지 다 들고오게 되므로, timesince(django 기본제공 함수)를 적용해준다.-->
<span style="font-size: small">{{ tw.author.username }} - {{ tw.created_at|timesince }} 전</span>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
어떤 게시글인지 지정
해주는 작업이 필요하다.http://127.0.0.1:8000/tweet/delete/게시글의id
tweet/views.py
id
값을 가지고 TweetModel
에서 같은 id인 게시글을 my_tweet 변수에 저장하고, 이것을 삭제한 후 /tweet
으로 리다이렉트해준다. # 앞 부분 생략
# 로그인을 한 사용자만이 함수를 실행할 수 있도록 도와주는 기능
from django.contrib.auth.decorators import login_required
@login_required
def delete_tweet(request, id):
my_tweet = TweetModel.objects.get(id=id)
my_tweet.delete()
return redirect('/tweet')
tweet/urls.py
로 가서 url을 생성해주도록 하겠다. # 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 함수 연결
path('tweet/delete/<int:id>', views.delete_tweet, name='delete-tweet'),
]
home.html
에서 삭제 버튼을 만들도록 하겠다. <!-- 작성 된 글이 나오는 곳 -->
<div class="row">
<!-- views.py에서 게시글의 모든 정보를 tweet으로 넘겨줬음 -->
{% for tw in tweet %}
<div class="col-md-12">
<div class="card">
<div class="card-body">
<!-- 만약 이 글을 쓴 작성자가 현재 로그인한 사용자라면-->
{% if tw.author == user %}
<div style="text-align: right">
<!-- a 태그를 사용해 url로 이동할 수 있게 해줌 -->
<a href="/tweet/delete/{{ tw.id }}">
<span class="badge rounded-pill bg-danger">삭제</span>
</a>
</div>
{% endif %}
<!-- 만약 현재 글을 보고 있는 사람이 로그인한 사용자가 아니라면 -->
<div style="text-align: right">
<a href="#">
<span class="badge rounded-pill bg-success">보기</span>
</a>
</div>
<!-- 아래 부분 생략-->