티스토리 뷰

반응형

장고 auth 앱

https://github.com/django/django/tree/main/django/contrib/auth

 

회원가입 및 로그인 커스텀 페이지를 아예 앱으로 만들어놓은 게 있다.

 

User 모델 사용

user 모델을 사용하려면 수정해줘야 한다.

from django.contrib.auth.models import User

User.objects.all()

from django.contrib.auth import get_user_model

User = get_user_model()

author = models.ForeignKey('auth.User')

from django.conf import settings
author = models.ForeignKey(settings.AUTH_USER_MODEL)

from django.conf import settings
author = models.ForeignKey(setting.AUTH_USER_MODEL)

가장 마지막 방법이 추천하는 방법이다.

내가 별도로 변수 값을 읽어다가 사용하고 싶기 때문에, AUTH_USER_MODEL 값만 setting.py에서 바꾸면 된다.

 

User 모델 변경

  • custom user 모델을 생성한다.
  • django.contrib.auth.models.AbstractUser 상속
  • Custom User 모델 설정 : '앱이름.모델명'
  • settings.AUTH_USER_MODEL 값으로 지정한다.(기본값 : 'auth.User')

 

뷰에서 User 사용

FBV : request.user - 로그인하면 유저 인스턴스, 로그인하지 않으면 ananimous 인스턴스가 들어가 있다.

CBV : self.request.user

템플릿 : context_processors를 통해 user 제공 - 현재 사용자에 대한 정보가 필요하다.

 

User 타입

로그인 상태 : settings.AUTH_USER_MODEL

로그아웃 상태 : django.contirb.auth.models.AnonymousUser

 

layout.html에서 User를 사용하려면, 

<body>
    <nav class="navbar navbar-default">
        <div class="container">
            <div id="navbar" class="collapse navbar-collapse">
                <ul class="nav navbar-nav">
                    <li class="active"><a href="/blog/">Home</a></li>
                    <li><a href="#">About</a></li>
                    <li><a href="#">Contact</a></li>
                </ul>
                <ul class="nav navbar-nav navbar-right">
                    {% if not user.is_authenticated %}
                        <li><a href="#">회원 가입 </a> </li>
                        <li><a href="#">로그인</a></li>
                    {% else %}
                        <li><a href="">{{user}}님!</a> </li> 
                        <li><a href="/blog/profile/">내정보</a> </li>
                        <li><a href="#">로그 아웃</a></li>
                    {% endif %}
                 </ul>
                </div>
        </div>
    </nav>

{{user}}를 쓰면 들어간다.

user.is_authenticated는 ananimous 일 경우에는 true이기 때문에, 해당으로 not이나 아니냐를 판단한다.

 

context_processors 등록

mysite/settings.py에 있는 template에 이미 등록되어 있다.

 "context_processors": [
     "django.template.context_processors.debug",
     "django.template.context_processors.request",
     "django.contrib.auth.context_processors.auth",
     "django.contrib.messages.context_processors.messages",
 ],

위에 있는 user.is_authenticated가 확인해준다.

뷰에서 사용한 객체를 template에서 쓸 수 있도록 넘겨주는 게 context이다.

응답이 나갈 때 얘네들이 실행된다.

 

ctrl+auth를 클릭하면 auth 함수로 이동하는데, return 함수가 user와 permwrapper(user)를 리턴한다.

request.user에 user 인스턴스가 들어 있다.

만약에 없다면 anonymoususer를 넣어준다.

 

로그인 / 로그아웃

accounts 앱 생성

장고쉘에서 accounts 앱을 만들어준다.

mysite> python manage.py startapp accounts

setting py에 앱 등록도 해준다.

 

https://github.com/django/django/blob/main/django/contrib/auth/urls.py

 

auth 앱의 기본적인 urls 양식이 있다.

# The views used below are normally mapped in the AdminSite instance.
# This URLs file is used to provide a reliable view deployment for test purposes.
# It is also provided as a convenience to those who want to deploy these URLs
# elsewhere.

from django.contrib.auth import views
from django.urls import path

urlpatterns = [
    path("login/", views.LoginView.as_view(), name="login"),
    path("logout/", views.LogoutView.as_view(), name="logout"),
    path(
        "password_change/", views.PasswordChangeView.as_view(), name="password_change"
    ),
    path(
        "password_change/done/",
        views.PasswordChangeDoneView.as_view(),
        name="password_change_done",
    ),
    path("password_reset/", views.PasswordResetView.as_view(), name="password_reset"),
    path(
        "password_reset/done/",
        views.PasswordResetDoneView.as_view(),
        name="password_reset_done",
    ),
    path(
        "reset/<uidb64>/<token>/",
        views.PasswordResetConfirmView.as_view(),
        name="password_reset_confirm",
    ),
    path(
        "reset/done/",
        views.PasswordResetCompleteView.as_view(),
        name="password_reset_complete",
    ),
]

 

해당하는 걸 참고해서, accounts url을 등록해준다.

mysite/urls.py

앱이 추가되었으므로 추가

 path("accounts/", include('accounts.urls')),

 

accounts/urls.py

from django.urls import path
from django.contrib.auth import views as auth_views

urlpatterns = [
    path("login/", auth_views.LoginView.as_view(), name="login"),
    path("logout/", auth_views.LogoutView.as_view(), name="logout"),
]

일단 기본적으로 로그인과 로그아웃만 추가해준다.

 

그리고 accounts 앱에서 사용하는 환경변수 값을 변경해준다.

mysite/settings.py

LOGIN_REDIRECT_URL = '/blog/'

LOGOUT_REDIRECT_URL = '/blog/'

 

로그인 페이지도 만들어준다.

이때 폴더는 templates/registration/login.html로 한다.

{% extends "layout.html" %}

{% block content %}
<form action="" method="post">
    {% csrf_token %}
    <table>
        {{ form.as_table}}
    </table>
    <input type="submit" />
</form>

{% endblock %}

 

해당 로그인과 로그아웃에 대해, url 'login'을 지정하면, 위에서 accounts/urls.py에서 name으로 지정한 url의 view가 실행된다.

 <ul class="nav navbar-nav navbar-right">
   {% if not user.is_authenticated %}
   <li><a href="#">회원 가입 </a></li>
   <li><a href="{% url 'login' %}?next={{request.path}}">로그인</a></li>
   {% else %}
   <li><a href="">{{user}}님!</a></li>
   <li><a href="/blog/profile/">내정보</a></li>
   <li><a href="{% url 'logout' %}?next={{request.path}}">로그 아웃</a></li>
   {% endif %}

 

로그인 / 로그아웃 작업 후 이전화면으로 이동하려면 request.path를 넣어준다.

작업하던 그 경로로 돌아간다.

 

회원 가입

회원 가입 form도 django auth에 이미 있다.

가져다 쓰면 된다.

다만 view는 만들어야 한다.

 

accounts/templates/registration/signup.html

{% extends "layout.html" %}
{% block title %}  
    회원가입
{% endblock %}
{% block content %}
    <h1> 회원 가입 </h1>
    <form action="" method="post">
        {% csrf_token %}
        <table>
            {{ form.as_table }}
        </table>
        <input type="submit" />
    </form>
{% endblock %}

회원 가입 폼을 만들고, 회원가입 뷰를 작성한다.

 

accounts/views.py

from django.shortcuts import render, redirect
from django.contrib.auth.forms import UserCreationForm
from django.conf import settings

def signup(request):
    if request.method == 'POST':
        form = UserCreationForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect(settings.LOGIN_URL)
    else:
        form = UserCreationForm()

    return render(request, 'registration/signup.html',{'form':form})

post면 signupform에서 확인하고, form을 save한다.

아니라면 signupform 페이지를 연다.

 

해당에서 login url은 setting에 지정해줘야 한다.

 

mysite/settings.py

LOGIN_URL = '/accounts/login/'

 

회원가입 url도 지정해준다.

accounts/urls.py

from django.urls import path
from django.contrib.auth import views as auth_views
from . import views

urlpatterns = [
    path("login/", auth_views.LoginView.as_view(), name="login"),
    path("logout/", auth_views.LogoutView.as_view(), name="logout"),
    path('signup/', views.signup, name='signup'),
]

 

한가지 주의할 점은, auth_views와 views가 다르다는 점.

저 views는 자신이 직접 지정한 view기 때문에 직접 불러와줘야 한다.

당연히 회원 가입 페이지도 url에 등록해줬기 때문에, layout.html에 등록해줘야 클릭해서 들어가진다.

 <li><a href="{% url 'signup' %}">회원 가입 </a></li>

 

accounts/forms.py를 이용해서 회원가입을 해준다.

from django.contrib.auth.forms import UserCreationForm 

class SignupForm(UserCreationForm):

    class Meta(UserCreationForm.Meta):
        fields = UserCreationForm.Meta.fields + ('email', )

 

 

UsercreationForm 상속

signup form을 상속받아서, signup을 고쳐준다.

from django.shortcuts import render, redirect
from django.contrib.auth.forms import UserCreationForm
from django.conf import settings
from .forms import SignupForm

def signup(request):
    if request.method == 'POST':
        form = SignupForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect(settings.LOGIN_URL)
    else:
        form = SignupForm()

    return render(request, 'registration/signup.html',{'form':form})

 

이러면 이제 회원가입에서 email이 들어갈 수 있다.

 

accounts/models.py에서, user와 1:1 관계를 가지는 profile 모델을 정의한다.

from django.db import models
from django.conf import settings

class Profile(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    phone_number = models.CharField(max_length=20)
    address = models.CharField(max_length=50)

 

그 후에는 migrations를 해줘야 한다.

python manage.py makemigrations accounts
python manage.py migrate accounts

 

최종 signupform은 다음과 같다.

accounts/forms.py

from django.contrib.auth.forms import UserCreationForm 
from django import forms 
from .models import Profile

class SignupForm(UserCreationForm):
    phone_number = forms.CharField()
    address = forms.CharField()

    class Meta(UserCreationForm.Meta):
        fields = UserCreationForm.Meta.fields + ('email', )

    def save(self):
        user = super().save() 
        Profile.objects.create(user=user, 
                               phone_number=self.cleaned_data['phone_number'],
                               address=self.cleaned_data['address'])
        return user

 

이제 회원가입을 하면, 데이터베이스에 추가가 되는 걸 볼 수 있다.

Email

https://docs.djangoproject.com/en/4.2/topics/email/

 

django에서 이메일은 해당 라이브러리로 쓴다.

global_settings.py에 이미 들어가 있다.

EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"

 

Email Naver SMTP 연동

Backend에다가 smtp.emailbackend해서 네이버 이멜, 비밀번호만 주고 send_mail만 해주면 이메일 전송을 해준다.

네이버 메일에서 pop3를 활용해야 한다.

 

mysite/settings.py

EMAIL_BACKEND= 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST= 'smtp.naver.com'
EMAIL_PORT= 465
EMAIL_HOST_USER= 이메일
EMAIL_HOST_PASSWORD= 비밀번호
EMAIL_USE_SSL= True

 

이런 거 깃허브에 올릴때는 조심해야 한다.

해커들이 실시간으로 수집하는 중

 

Console에서 Email 전송 Test하기

mysite/settings.py

EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"

한 후에, 장고쉘에서

from django.core.mail import send_mail

하고, send_mail 함수로

send_mail('hello', 'i miss you' , 'admin@sky.com', ['amy@gmail.com'], fail_silently=False)

하면, 실제 이메일이 날아간다.

 

Signals

이메일을 보내는 건 알았는데, 실제 이메일을 보낼 때는 어떠한 회원이 회원가입한 때이다.

회원가입한 때 보낼 수 있게 하는 게 바로 signal이다.

 

https://docs.djangoproject.com/en/4.2/ref/signals/

 

Model signals

인스턴스를 생성할 때 내부적으로 생성자 호출

pre_init

post_init - init 메소드가 끝남

pre_save - save 메소드 실행되기 전

post_save - save 메소드 실행된 후

pre_delete

post_delete

m2m_changed

class_prepared

 

Managemnet signals

pre_migrate

post_migrate

 

Request/response signals

request_started

request_finished

got_request_exception

 

Test signals

setting_changed

template_rendered

 

회원가입이 완료된 후에 쓰고 싶으면, post_save 후에 하는 게 맞다.

이메일 전송해주는 함수를 어디선가 하나 만들어놓고, post_save가 될 때 해당 함수를 실행시켜달라고 하면 된다.

 

signal 클래스에 connect라는 메소드가 있는데, 

실행할 함수하고 연결해준다.

receiver = 해당 시그널을 감지했을 때 실행할 함수

 

post_save

시그널마다 이벤트가 발생했을 때 보내주는 신호가 다르다.

post_save가 될 때 동작하면서 넘어가는 argument들이 있는데, argument들은 다음과 같다.

 

sender - 모델의 클래스 정보. 

 

instance - 이메일 값이나 그런 입력된 정보들

 

created - 만약에 생성되었다면 True

 

회원가입 후 환영 Email 보내기

 

accounts/models.py

from django.db import models
from django.conf import settings
from django.core.mail import send_mail 
from django.db.models.signals import post_save


class Profile(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    phone_number = models.CharField(max_length=20)
    address = models.CharField(max_length=50)


def on_send_mail(sender, **kwargs):
    if kwargs['created'] :
        user = kwargs['instance']
        send_mail('가입인사','가입을 환영합니다.', 'admin@sky.com', [user.email], fail_silently=False)
        

post_save.connect(on_send_mail, sender=settings.AUTH_USER_MODEL)

 

회원가입을 하고, 콘솔을 확인해보면 된다.

 

Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
Subject: =?utf-8?b?6rCA7J6F7J247IKs?=
From: admin@sky.com
To: kkkk@gmail.com
Date: Thu, 23 May 2024 05:51:25 -0000
Message-ID: <171644348510.1672.3743033087074139615@DESKTOP-F1O3Q8D>

가입을 환영합니다.

이 과정은 mysite/settings.py에서 emailbackend가 설정되어 있어야 작동한다.

 

Messages

딱 한번 보여주는 메세지이다.

Csrfmiddelware가 이 메세지와 같다.

MIDDLEWARE = [

    "django.contrib.messages.middleware.MessageMiddleware",
]

 

메세지 미들웨어가 있다.

MiddlewareMixin을 상속받는다.

만약에 미들웨어를 만들고 싶으면 해당 클래스를 상속받으면 된다.

view가 실행되기 전, 클라이언트가 응답을 나갈 때 작동한다.

 

그 중간에 실행되는 건 process_request이다.

 

암호 변경

accounts/urls.py를 다음과 같이 고쳐준다.

 

from django.urls import path
from django.contrib.auth import views as auth_views
from . import views 
from django.views.generic import TemplateView

urlpatterns = [
    path('login/', auth_views.LoginView.as_view(), name='login'),
    path('logout/', auth_views.LogoutView.as_view(), name='logout'),
    path('signup/', views.signup, name='signup'),

    path(
        "password_change/", views.MyPasswordChangeView.as_view(), name="password_change"
    ),
    path(
        "password_change/done/",
        auth_views.PasswordChangeDoneView.as_view(),
        name="password_change_done",
    ),

    path('profile/', TemplateView.as_view(template_name='registration/profile.html'), name='profile'  ),

    path('cookie/<code>/', views.cookie_test),
    path('session/<int:code>', views.session_test),

]

 

success_url = password_change_done이 reverse_lazy안에 들어 있다.

해당 설정은 auth앱에 있는 passwordchangeview에 들어 있다.

 

PasswordChangeDoneView.as_view()로 완료 되었다는 걸 불러온다.

만약 싫다면 succes_url을 바꾸면 된다.

 

내정보 페이지 - profile.html을 만든다.

{% extends "layout.html" %}

{% block content %}
    <h1> 내정보 </h1>
    <ul>
        <li> name : {{user}}</li>
        <li> email : {{user.email}}</li>
        <li> tel : {{user.profile.phone_number}}</li>
        <li> address : {{user.profile.address}}</li>
        <li>{{user.date_joined}}</li>
    </ul>
    <a href="{% url 'password_change' %}">[암호 변경하기]</a>

{% endblock %}

 

layout에 로그인했을 때, 자기 profile이 보이게도 해준다.

<li><a href="{% url 'profile' %}">내정보</a></li>

해당 url 링크는 단순히 내정보 창으로 가게 한다.

 

그러나 암호 변경하기는 안에서 django의 공식 암호 변경 페이지로 이동해서, 암호를 변경해준다.

 

message 추가

views.py에 message를 보내는 기능을 추가해준다.

class MyPasswordChangeView(PasswordChangeView) :
    success_url = reverse_lazy('profile')

    def form_valid(self, form):
        messages.info(self.request, '암호 변경을 완료했습니다!')
        return super().form_valid(form)

메세지를 info라는 함수로 만들면, 그냥 하나의 메세지를 출력해주는 코드가 된다.

 

contenxt processer에서 messages라는 context가 있다.

messages라는 이름으로 모든 메세지를 가지고 있는 객체가 있다.

그 message를 get 해서 뿌려준다.

 

layout.html에서 messages도 등록해준다.

 <div class="container">
   <div class="row">
     <div class="col-sm-12">
       {% if messages %}
         <ul>
               {% for message in messages %}
                 <li>{{ message}}</li>
               {% endfor %}
         </ul>
       {% endif %}
       
       {% block content %} 
       {% endblock %}
     
   </div>
   </div>
 </div>

 

여기까지 하면, 암호 변경을 완료 했을 때, 내정보의 화면에 암호 변경 메시지가 뜬다.

쿠키

쿠키는 request에 있는 쿠키 파일에서 쓴다.

 

accounts/views.py

def cookie_test(request, code):
    response = render(request, 'registration/cookieTest.html')
    if code == 'add':
        response.set_cookie('model', 'A001')
        response.set_cookie('prod', 'EV9')
    elif code == 'get' :
        model = request.COOKIES.get('model')
        prod = request.COOKIES.get('prod')
        print(model, prod)
    elif code == 'del' :
        response.delete_cookie('model')
        response.delete_cookie('prod')
    return response

 

쿠키 이름은 알아서 지정해주면 된다.

 

urls.py에 해당 path를 추가해주면, 쿠키를 볼 수 있다.

 

 path('cookie/<code>/', views.cookie_test),

 

cookieTest를 위한 html을 만든다.

{% extends "layout.html" %}

{% block content %}
   
    <h1> 저장된 Cookie </h1>
    <h2 style="color:blue">
        {{request.COOKIES}}
    </h2>
  
{% endblock %}

 

그러면, http://127.0.0.1:8000/accounts/cookie/add/

http://127.0.0.1:8000/accounts/cookie/get/

http://127.0.0.1:8000/accounts/cookie/del/

후에 다시

http://127.0.0.1:8000/accounts/cookie/get/
에서 결과를 볼 수 있다.

 

Session

http 프로토콜은 무상태(stateless)라는 특성을 가지고 있다.

한번 요청하고 응답하면 연결이 끊긴다.

요청-응답 단위로 매겨진다.

 

하나의 커넥션에서 썼던 데이터를 다른 커넥션에서 쓸 수 없다.

데이터가 일회용이다.

 

원하는 데이터를 유지하고 싶다면, 이 데이터를 어딘가에 저장해놔야 하는데, 그게 클라이언트 쪽이면 쿠키이고, 서버 쪽이면 세션이다.

 

중복 로그인이 되어 있으면 안된다.

해당하는 것을 위해서 cotrib/sessions/backend/db.py를 사용한다.

 

세션 test view code

accounts/views.py

from django.contrib.sessions.backends.db import SessionStore

# Session Test
def session_test(request, code):
    response = render(request, 'registration/sessionTest.html')
    session = request.session
    if code == 1:
        user = request.user        
        print(user,':', session)
    elif code == 2 :        
        session['model'] = 'A001'
        session['prod'] = 'EV9'
        print('session 데이터 등록')
    elif code == 3:
        model = session.get('model')
        prod = session.get('prod')
        print('session 데이터 추출')
        print(model, prod)
    elif code == 4:
        session.pop('model')
        print('session 데이터 삭제')
        session.pop('prod')
    return response

 

sessionTest.html도 만들어주고, accounts/urls.py에도 path를 추가해준다.

path('session/<int:code>', views.session_test),

 

{% extends "layout.html" %}

{% block content %}
   
    <h1> Session Test </h1>
    <h2 style="color:green">
        {{request.user}}
    </h2>
    <h2 style="color:red">
        {{request.session.session_key}}
    </h2>
    
{% endblock %}

 

이제 해당하는 창으로 가서 테스트한다.

 

http://127.0.0.1:8000/accounts/session/1 - 로그아웃상태

http://127.0.0.1:8000/accounts/session/1 - 로그인 상태

1~4로 데이터 등록과 삭제를 할 수 있다.

 

중복 로그인 방지

 

세션 ID 값을 할당되었을 때, 같은 세션이 할당되었다면 해당 세션이 중복이 되는지를 확인하고 생성한다.

 

models.py에 유저 세션을 저장하기 위한 테이블을 추가한다.

accounts/models.py

class UserSession(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    session_key = models.CharField(max_length=40)
    created_at = models.DateTimeField(auto_now_add=True)

def block_duplicate_login(sender, request, user, **kwargs):
    login_user_list = UserSession.objects.filter(user=user)
    for user_session in login_user_list:
        session = SessionStore(user_session.session_key)
        session.delete()
      #  session['blocked'] = True
      #  session.save()
    
    session_key = request.session.session_key
    UserSession.objects.create(user=user, session_key = session_key)

user_logged_in.connect(block_duplicate_login)

 

model이 만들어졌으므로 migration은 습관처럼

python manage.py makemigrations accounts
python manage.py migrate accounts

 

두 개의 창을 열면, 아직까지는 로그인이 된다.

이제는 delete를 해주지 않고, 서버에 저장해서 해당하는걸 가져온다.

 

models.py

def block_duplicate_login(sender, request, user, **kwargs):
    login_user_list = UserSession.objects.filter(user=user)
    for user_session in login_user_list:
        session = SessionStore(user_session.session_key)
    #    session.delete()
        session['blocked'] = True
        session.save()
    
    session_key = request.session.session_key
    UserSession.objects.create(user=user, session_key = session_key)

user_logged_in.connect(block_duplicate_login)

 

그리고 중복 실행시 메시지를 보이기 위한 미들웨어 파일을 추가해준다.

 

accounts//middleware.py

from django.conf import settings
from django.utils.deprecation import MiddlewareMixin
from django.contrib import messages
from django.contrib.auth import logout as auth_logout
from django.shortcuts import redirect

class BlockedMiddleware(MiddlewareMixin):
    
    def process_request(self, request):
        blocked = request.session.pop('blocked', None)
        if blocked:
            messages.info(request,'다른 기기에서 동일아이디로 로그인되어 자동으로 로그아웃 되었습니다.')
            auth_logout(request)
            return redirect(settings.LOGIN_URL)

 

middleware가 만들어졌으니, settings.py에서 middleware란에도 추가해준다.

 

"accounts.middleware.BlockedMiddleware"

 

이러면 두 개의 창에서 차례대로 로그인 작업을 했을 시에, 처음 로그인창이 로그아웃되며 활성화된다.

 

반응형