Contents
django 10 - 사용자 관리 (회원 앱) 및 로그인
   2022년06월21일     9분정도면 다 읽어요     - Comments

[카카오 클라우드 스쿨] 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도 만들지 않아도 됨
  • 이 친구는 기본 앱에 들어 있음
    • img_74
  • 따라서 맨 처음 migration 할 때부터 들어가 있음
    • img_75
# 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 . 중간결과

  • img_72
  • 참고로 settings.py에서 시간 및 날짜 설정하면
  • img_73

장고 언어 및 시간대 설정하기

  • 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 . 키는 암호화 됨

  • img_76

6 . 회원가입 클래스 상속?

  • DB를 보면 이메일, 미들네임, 스텝여부 등 추가 column 이 있다
  • 상속을 통해 항목을 추가 가능하다



10-3. 로그인의 방식


로그인을 검증하는 두 가지 방식

  1. 세션(서버 측에 로그인 정보를 저장)
    • img_60
    • 서버 컴퓨터의 HDD or 메모리(쿠키)
    • django에서 일반적으로 쓰는 방식
  2. 토큰(클라이언트 측에 로그인 정보를 저장함)
    • 클라이언트에 저장하는 경우 JWT를 씀(사용자가 알아 볼 수 없는 암호화된 값)
    • 클라이언트에서 확인하기 위해 복호화가 수행되며, 클라이언트의 메모리/연산이 필요함
    • 리액트에서 일반적으로 쓰는 방식

세션 방식에서, 로드밸런싱 되는 각 컴퓨터에 세션을 어떻게 유지할까?

  1. 로드밸런싱 되는 컴퓨터 전부에 로그인 세션 정보를 모두 저장한다? No!
  2. 세션만 저장하는 컴퓨터를 따로 둔다! 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 . 로그인 확인해 보기

  • img_77
    • 세션 방식이므로 쿠키를 확인해 보면 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 . 테스트

  • img_84
  • img_85
  • img_86



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 . 다음과 같이 뜨는 경우

  • img_87
  • id title contents create_date 네 가지 속성이 있었는데 새로 추가된 writer 속성(컬럼)에는 뭘 넣을지 물어보는 것임
    1. 지금 기본값을 지정을 하되, 아무것도 쓰지 않으면 null값이 기본으로 지정될 것이다
    2. 나중에 수동으로 설정을 하겠다(공백)
  • 1번 선택지 -> 기본키 1인 사용자
  • DB에 다음과 같이 칼럼이 추가 됨
    • img_88

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 } }&nbsp;&nbsp;&nbsp;
        작성자: { { post.writer } }&nbsp;&nbsp;&nbsp;
        제목: { { post.title } }&nbsp;&nbsp;&nbsp;
        <a href="../delete/{ {post.id} }"> 삭제 </a>&nbsp;&nbsp;&nbsp;
        <a href="../update/{ {post.id} }"> 수정 </a>&nbsp;&nbsp;&nbsp;
        <a href="../list">      목록 </a>&nbsp;&nbsp;&nbsp;
        <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">
        글번호&nbsp;&nbsp;|&nbsp;&nbsp;작성자&nbsp;&nbsp;|&nbsp;&nbsp;제목 <hr>
    </div> <hr>

    { % for post in posts % }
    <div>
        { {post.id} } &nbsp; &nbsp; &nbsp; &nbsp; |{ {post.writer} } &nbsp; &nbsp; &nbsp; &nbsp;
        <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 블로그 특성항 템플릿 언어 { {, { % 등등이 적용이 안되어 불가피하게 공백을 삽입했어요
원래는 공백 있으면 안돼요