안녕세계
[Django] 회원가입 인증 메일 보내기 [2/2] (gmail SMTP) 본문
[Django] 회원가입 인증 메일 보내기 [2/2] (gmail SMTP)
Junhong Kim 2018. 4. 30. 18:00장고에서 회원가입 인증 메일 보내기 [2/2] (gmail SMTP)
NOTE:
지난 포스팅에서 진행한gmail 계정 셋팅
과장고 settings.py 설정
하신후 진행하셔야 합니다.
설정 후 장고 쉘에서 테스트로 메일을 전송 해보신 후 진행하시는 것을 추천드립니다!
우선, user
테이블을 생성합시다.
user/models.py
user 테이블에 필수적인 속성만 추가합니다.
from django.db import models
from utils.common.cipher import AESCipher
class User(models.Model):
class Meta:
db_table = "users"
created_at = models.DateTimeField(auto_now_add=True)
updated_ay = models.DateTimeField(auto_now=True)
email = models.CharField(max_length=128, unique=True)
password = models.CharField(max_length=255)
active = models.BooleanField(default=False)
db_table="users"
: 테이블명을 users로 정의created_at
: 생성 시간updated_at
: 수정 시간email
: 아이디 겸 이메일로 유일password
: 비밀번호active
: 인증 여부(=활성화 여부)
장고에서 API 2개를 만들어 봅시다.
POST /users/signup
: 회원가입 후 인증 메일 전송GET /users/activate
: 인증 메일 클릭시 해당 아이디를 활성화
/user/urls.py
from django.urls import path
from . import views
app_name = 'user'
urlpatterns= [
path('signup', views.SignUp.as_view(), name='signup')
path('activate/<str:uidb64>/<str:token>', views.UserActivate.as_view(), name='activate')
]
1. 회원 가입 및 이메일 전송 API
일반적인 POST API와 동일하지만 내부적으로 하는 일이 다릅니다.
회원 가입에 성공하면 인증 이메일을 전송합니다.
serializer.is_valid()는 UserSerializer 클래스의 create()를 실행합니다.
/user/views.py
class SignUp(APIView):
def post(self, request):
"""
사용자 데이터 생성
"""
serializer = UserSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data), status=status.HTTP_200_OK)
return Response(serializer.errors), status=status.HTTP_400_BAD_REQUEST)
/user/serializers.py
user
테이블에 해당하는serializer
를 만듭시다.
from django.contrib.sites.shortcuts import get_current_site
from django.core.mail import EmailMessage
from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode
from django.template.loader import render_to_string
from rest_framework import serializers
from .models import User
from .tokens import account_activation_token
from utils.common.cipher import AESCipher
class UserSerializer(serializers.ModelSerializer):
created_by = serializers.CharField(max_length=64, required=False)
updated_by = serializers.CharField(max_length=64, required=False)
email = serializers.EmailField()
class Meta:
model = User
fields = '__all__'
def to_internal_value(self, data):
"""
POST/PUT과 같이 데이터 변경이 있을 때 데이터를 저장하기 전에 핸들링 할수 있는 함수입니다.
"""
ret = super(UserSerializer, self).to_internal_value(data)
# 비밀번호를 암호화하기 위한 과정으로 예제에서는 생략해도 됩니다.
cipher = AESCipher()
ret['password'] = cipher.encrypt_str(ret['password'])
return ret
def to_representation(self, obj):
"""
GET/POST/PUT과 같이 데이터 변경이 있고 난 후 serializer.data로 접근할 때 값을 변환하여 보여줍니다.
"""
ret = super(UserSerializer, self).to_representation(obj)
return ret
def validate_email(self, value):
"""
이메일이 데이터베이스에 존재하는지 확인합니다.
"""
if User.objects.filter(email=value).exists():
raise serializers.ValidationError("이메일이 이미 존재합니다.")
return value
def validate_password(self, value):
"""
패스워드가 8글자 이하인지 확인합니다.
"""
if len(value) < 8:
raise serializers.ValidationError("패스워드는 최소 %s자 이상이어야 합니다." % 8)
return value
def create(self, validated_data):
"""
데이터를 저장할 때 필요한 과정을 구현합니다.
"""
user = User.objects.create(
email=validated_data['username'],
password=validated_data['password'],
)
user.active = False
user.save()
# current_site = get_current_site(self.context['request'])
message = render_to_string('user/account_activate_email.html', {
'user': user,
'domain': 'localhost:8000',
'uid': urlsafe_base64_encode(force_bytes(user.pk)).decode('utf-8'),
'token': account_activation_token.make_token(user)
})
"""
이메일 전송 과정
"""
mail_subject = 'test'
to_email = '아이디@gmail.com'
# EmailMessage(제목, 본문, 받는이)
email = EmailMessage(mail_subject, message, to=[to_email])
email.send()
return validated_data
코드가 길지만 우리는 message
변수에 대해서만 잘 알면 됩니다.
user
: 생성한 사용자 객체domain
: 도메인 주소(예제는 localhost)uid
: 생성한 사용자 객체의 PK를 암호화한 값token
: 생성한 사용자 객체를 통해 생성한 token 값
/user/tokens.py
token을 생성할 때는 다음과 같은 파일을 생성후 진행합니다.
from django.contrib.auth.tokens import PasswordResetTokenGenerator
from django.utils import six
class AccountActivationTokenGenerator(PasswordResetTokenGenerator):
def _make_hash_value(self, user, timestamp):
return (
six.text_type(user.pk) + six.text_type(timestamp) + six.text_type(user.active)
)
account_activation_token = AccountActivationTokenGenerator()
위에서 만든 4가지 값을 account_activate_email.html
로 넘겨주고 렌더링할 수 있도록 message
에 담습니다.
이메일 전송 과정에서 알아야할 것은 EmailMessage()
가 전부입니다.EmailMessage()
에 들어가는 인자는 제목, 본문, 받는이 순서 입니다.
예제에서 제목은 test
이고 본문은 message 변수에 담겨진 html 페이지
입니다.
email 객체를 민들었다면 이제 send() 하기면 하면 메일이 전송됩니다.
그 전에 account_activate_email.html
페이지에 대해 알아봅시다.
/user/templates/user/account_activate_email.html
html 페이지는 별게 없습니다. 넘겨 받은 값을 html 페이지에 뿌려주기만 하면 됩니다.
{{ user.name }}님, 아래 링크를 클릭해서 계정을 활성화해주세요.
http://{{ domain }}{% url 'user:activate' uidb64=uid token=token %}
여기까지가 회원가입 및 이메일 전송 API의 전부입니다.
다음으로는 이제 전송된 메일을 확인하여 사용자를 활성화 시키면 됩니다.
2. 인증 메일을 확인한 사용자 활성화 API
/user/views.py
class UserActivate(APIView):
permission_classes = (permissions.AllowAny, )
def get(self, request, uidb64, token):
try:
uid = force_text(urlsafe_base64_decode(uidb64.encode('utf-8')))
user = User.objects.get(pk=uid)
except(TypeError, ValueError, OverflowError, User.DoesNotExist):
user = None
try:
if user is not None and account_activation_token.check_token(user, token):
user.active = True
user.save()
return Response(user.email + ' 계정이 활성화 되었습니다.', status=status.HTTP_200_OK)
else:
return Response('만료된 링크입니다.', status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
print(traceback.format_exc())
우리가 보낸 이메일을 확인을 누르면 activate API를 호출하게 됩니다. 사용자가 호출한 api에 암호화된 uid와 token이 있는데 이를 복호화가 정상적으로 되면 사용자의 active 속성을 True로 변경하면 됩니다.
[참고]
https://jamiejang.blogspot.kr/2018/01/blog-post_51.html
https://jamiejang.blogspot.kr/2018/01/rest-api.html
https://wikidocs.net/10373
https://docs.djangoproject.com/ko/2.0/intro/tutorial03/
'Server > Django' 카테고리의 다른 글
[Django] Docker 컨테이너 배포 (nginx & gunicorn) (0) | 2019.01.01 |
---|---|
[Django] 회원가입 인증 메일 보내기 [1/2] (gmail SMTP) (0) | 2018.04.30 |
[Django] Custom command 만들기 (0) | 2018.04.30 |
[장고 AtoZ] Django REST Framework - FBV(@api_view) (0) | 2018.02.08 |
[장고 AtoZ] Django REST Framework - CBV(APIView) (0) | 2018.02.07 |