[카카오 클라우드 스쿨] django 10 - 사용자 관리 (회원 앱) 및 로그인
사용자 관리 (회원 앱) 및 로그인
회원가입: DB에 회원 정보를 create한다
회원가입: DB에 회원 정보를 수정한다
- 이번에는 장고 내장 모델을 쓸 것
- forms, class, db 구성이 불필요하다
- 있는거 그냥 가져다 쓰면 됨
- 필요시(닉네임 등) 상속한 뒤 추가하면 됨
10-1. 환경 구성
1 . 앱 설치
- python manage.py startapp user
2 . 앱 등록
- settings.py
3 . URI 만들기
- path(‘user/signup’, reply.views.signup),
10-2. 회원가입 폼 만들기
1 . views에 함수 만들기(일단 get 양식 우선 만들어 보기)
- model 클래스를 생성할 필요가 없이 UserCreationForm() 폼을 가져다 쓰면 됨
- 따라서 forms.py도 만들지 않아도 됨
- 이 친구는 기본 앱에 들어 있음
- 따라서 맨 처음 migration 할 때부터 들어가 있음
# Create your views here.
def signup(request):
if request.method == "GET":
# 회원가입 폼: 장고 내장
signupForm = UserCreationForm()
# 전달할 때는 항상 딕셔너리 형태
context = {'signupForm': signupForm}
return render(request, 'user/signup.html', context)
elif request.method == "POST":
return render(request, 'user/signup.html')
2 . 템플릿 만들기 signup.html
<body>
<form method ="post">
{ % csrf_token % }
{ { signupForm } }
<button>
회원가입
</button>
</form>
</body>
3 . 중간결과
- 참고로 settings.py에서 시간 및 날짜 설정하면
-
장고 언어 및 시간대 설정하기
- settings.py
- LANGUAGE_CODE = ‘ko-kr’
- TIME_ZONE = ‘Asia/Seoul’
4 . views 마저 작성하기
...
elif request.method == "POST":
# 기본 모델로부터 객체 생성함
signupForm = UserCreationForm(request.POST)
# 데이터 검사
if signupForm.is_valid():
user = signupForm.save(commit=False)
user.save()
# 처리 이후 context를 더 줄 필요가 없으니까 게시판으로 redirect 시켜버리자
# return render(request, 'user/signup.html')
return redirect('/boardpan/listGet')
else:
return HttpResponse("잘못된 비밀번호입니다")
5 . 키는 암호화 됨
6 . 회원가입 클래스 상속?
- DB를 보면 이메일, 미들네임, 스텝여부 등 추가 column 이 있다
- 상속을 통해 항목을 추가 가능하다
10-3. 로그인의 방식
로그인을 검증하는 두 가지 방식
- 세션(서버 측에 로그인 정보를 저장)
- 서버 컴퓨터의 HDD or 메모리(쿠키)
- django에서 일반적으로 쓰는 방식
- 토큰(클라이언트 측에 로그인 정보를 저장함)
- 클라이언트에 저장하는 경우 JWT를 씀(사용자가 알아 볼 수 없는 암호화된 값)
- 클라이언트에서 확인하기 위해 복호화가 수행되며, 클라이언트의 메모리/연산이 필요함
- 리액트에서 일반적으로 쓰는 방식
세션 방식에서, 로드밸런싱 되는 각 컴퓨터에 세션을 어떻게 유지할까?
- 로드밸런싱 되는 컴퓨터 전부에 로그인 세션 정보를 모두 저장한다? No!
- 세션만 저장하는 컴퓨터를 따로 둔다! Yes!
<hr>
10-4. 로그인 폼 만들기
로그인: 게시글 조회처럼, 입력받은 양식을 통해 데이터가 DB에서 조회되는 것임(원리)
1 . URI 만들기
- path(‘user/login’, reply.views.login),
2 . views.py에서, login은 회원가입하고 비슷하니 일반 복붙해서 조금만 수정해봐
- 마찬가지로 model 클래스를 생성할 필요가 없이 AuthenticationForm() 폼을 가져다 쓰면 됨
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth import login as auth_login
# 모듈명과 겹쳐서 그냥 모듈명에 별명 붙임(auth_login)
def login(request):
if request.method == "GET":
# 로그인 폼: 장고 내장
loginForm = AuthenticationForm()
# 전달할 때는 항상 딕셔너리 형태
context = {'loginForm': loginForm}
return render(request, 'user/login.html', context)
elif request.method == "POST":
# 기본 모델로부터 객체 생성함
loginForm = AuthenticationForm(request, request.POST) # 두개 다 넣어야 함
# ID, PW가 일치할 경우 (내장 모델의 폼 사용)
# 예전같은 경우 일일이 DB 정보를 가져와 if문으로 일치여부를 비교해야 했음
if loginForm.is_valid():
auth_login(request, loginForm.get_user()) # 함수명이 겹쳐서 다른이름으로 import 했음
# 처리 이후 context를 더 줄 필요가 없으니까 게시판으로 redirect 시켜버리자
# return render(request, 'user/signup.html')
return redirect('/boardpan/listGet')
# 로그인 실패 시
else:
return HttpResponse("잘못된 비밀번호입니다")
누누이 말했지만, 폼코드 한줄은 아래 두 줄을 쉽게 수행해준다
auth_login(request, loginForm.get_user())
## ->
username = request.POST.get('username')
password = request.POST.get('password')
3 . login.html
<body>
<form method ="post">
{ % csrf_token % }
{ { loginForm } }
<button>
로그인
</button>
</form>
</body>
4 . 로그인 확인해 보기
- 세션 방식이므로 쿠키를 확인해 보면 sessionid를 가지고 있는 것을 확인할 수 있다
- 근데 아직 로그인 여부를 페이지에서 확인할 수가 없음
5 . boardpan list 페이지에 로그인 여부 확인용 표시해 주기
boardpan/list.html <body> 상단부에 다음 코드만 추가!
<body>
{ % if request.user.is_authenticated % }
{ { request.user } } 님 환영합니다
{ % endif % }
...
6 . 이거 중요해요?
- 장고에서는 중요해요 하지만,,
- 리액트에서는 { % if request.user.is_authenticated % } 이런 템플릿 언어 안 씁니다
10-5. 로그아웃
메모리에 저장되어 있는 세션 정보를 지운다
- 세션: 로그인 할 때 복잡하고 로그아웃할 때 간단함
- JWT 토큰: 로그아웃 할 때 복잡함
1 . user/views.py
def logout(request):
# GET만 필요(로그아웃할래!)
auth_logout(request)
return redirect('/boardpan/listGet')
2 . URI 만들기
- path(‘user/logout’, user.views.logout),
3 . 테스트
10-6. User와 Boardpan 사이에 관계를 생성한다.
- 로그인한 사람만 게시글을 작성할 수 있도록 하려 함
- User와 관계를 맺을 것 1(유저): N(댓글)
- 한 명의 유저는 여러 개의 게시물을 쓸 수 있다
1:n 관계의 경우, 관계를 맺는 설정은 n측에 넣어 준다
따라서 게시물, 사용자 와의 관계 모두 REPLY 측에 설정하면 됨
1 . 관계 설정 (boardpan/models.py)
- cascade: 오직 user에 있는 값(존재하는 유저) 혹은 NULL값만 쓸 수 있다.
# Create your models here.
# 클래스 생성, 제목 내용 시간 작성자
class Post(models.Model):
title = models.CharField(max_length=100)
contents = models.TextField()
create_date = models.DateTimeField(auto_now_add=True)
# User와 관계를 맺어 줌, cascade: 유저가 회원 탈퇴시 댓글 제거 (존재하는 유저의 댓글만 저장가능)
writer = models.ForeignKey(User, on_delete=models.CASCADE)
2 . 클래스가 수정되었으니 마이그레이션 수행
python ./manage.py makemigrations boardpan
python ./manage.py migrate
3 . 다음과 같이 뜨는 경우
- id title contents create_date 네 가지 속성이 있었는데 새로 추가된 writer 속성(컬럼)에는 뭘 넣을지 물어보는 것임
- 지금 기본값을 지정을 하되, 아무것도 쓰지 않으면 null값이 기본으로 지정될 것이다
- 나중에 수동으로 설정을 하겠다(공백)
- 1번 선택지 -> 기본키 1인 사용자
- DB에 다음과 같이 칼럼이 추가 됨
4 . 로그인 한 사용자만 게시글 작성, 수정, 삭제할 수 있도록 막기(로그인 확인)
- 데코레이터를 달아 준다: @login_required
- 추가로 게시물 작성자 정보를 추가해서 저장: post.writer = request.user
boardpan/views.py - create()
from django.contrib.auth.decorators import login_required
# 데코레이터(@)를 통해 로그인 한 경우에만 쓸 수 있도록 함, 달아주기만 하면 해당 기능이 함수에 추가됨
# login_url을 통해 login이 되어있지 않은 경우 login페이지로 이동시켜준다
@login_required(login_url='/user/login')
def create(request):
# request가 GET 방식이면 수행하는 코드(입력 페이지를 클라이언트에 전달해 줌)
if request.method == "GET":
postForm = PostForm()
context = {'postForm': postForm}
return render(request, 'boardpan/create.html', context)
# request가 POST 방식이면 수행하는 코드(입력 양식을 받아감)
elif request.method == "POST":
# 값을 Form에 전달
postForm = PostForm(request.POST)
# 폼에 전달받은 값 검증
if postForm.is_valid():
post = postForm.save(commit=False)
# 게시글 작성자 정보 추가
post.writer = request.user
post.save()
return redirect('/boardpan/read/' + str(post.id))
else:
return HttpResponse("값이 올바르지 않습니다 ")
(수정, 삭제 상단에도 똑같이 달아주고, list랑 read는 로그인을 안 해도 볼 수 있도록 달지 않는다)
5 . delete와 update의 경우, 추가적으로 작성자만 수행할 수 있도록 하기
boardpan/views.py - deleteGet()
@login_required(login_url='/user/login')
def deleteGet(request, bid):
# q는 여기서 안써도 됨
post = Post.objects.get(id=bid) # 삭제할 ID 담기
# 작성자가 아니면 삭제 못함
if request.user != post.writer:
return redirect('/boardpan/read/' + str(post.id))
post.delete()
...
update도 똑같이 @login_required(login_url=’/user/login’) 달아준다
6 . forms에 입력받지 않을 값(사용자) 추가시키기
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ('title', 'contents')
# 자동으로 로그인한 유저가 저장되게 할 거라 전달받지 않을 값!
exclude = ('writer',)
7 . 게시글 목록과 게시물에 작성자 표시하기
게시글
<body>
{ % if post % }
글번호: { { post.id } }
작성자: { { post.writer } }
제목: { { post.title } }
<a href="../delete/{ {post.id} }"> 삭제 </a>
<a href="../update/{ {post.id} }"> 수정 </a>
<a href="../list"> 목록 </a>
<hr/>
{ { post.contents } }
{ % endif % }
</body>
목록
<body>
{ % if request.user.is_authenticated % }
{ { request.user } } 님 환영합니다
{ % endif % }
<a href="create"> 글쓰기 </a>
<br><br>
<div style="background-color:yellow">
글번호 | 작성자 | 제목 <hr>
</div> <hr>
{ % for post in posts % }
<div>
{ {post.id} } |{ {post.writer} }
<a href="read/{ {post.id} }"> { {post.title} } </a>
</div> <hr>
{ % endfor % }
</body>
8 . 결과
- 로그인 하지 않은 사용자의 삭제 수정 등록이 제한됨
- 작성할 때의 사용자가 아닌 경우 수정이나 삭제가 불가능해짐
10-7. (번외) view 이름 바꾸기
- (이건 번외)아 그리고 POST와 GET을 합쳤으니까, views의 함수명, URL 수정하기
- 나중을 위한 사전 작업! (굳이 Get과 Post를 나눌 필요가 없음)
- deleteGet -> delete..
- URL, views, Template 전부 수정해야 함..
- user 앱에서도 로그인 후 게시판 앱으로 리다이렉트 시, listGet -> list 등 수정 필요
참고: jekyll 블로그 특성항 템플릿 언어 { {, { % 등등이 적용이 안되어 불가피하게 공백을 삽입했어요
원래는 공백 있으면 안돼요