위코드 3주차. Foundation Week에 들어서면서 본격적으로 Django를 시작했다. 프론트앤드 분들이 만들고 계신 인스타그램의 클론인 westagram
의 백엔드 부분을 이제 Django를 통해 API를 만들어서 다음주에 프론트앤드분들이 만든 부분과 붙여보는 시간이 될거기에 굉장히 중요한 시기라고 생각하고 빨리 가기보단 최대한 이해하고 내 손으로 하나하나 코드를 짜보는 시간으로 만들어보려고 한다. ( 말은 이렇게 해도 잘하시는 분들 보면 조바심도 생기고, 부러운 마음도 생기는건 어쩔 수 없나보다 .)
Guide에 따라서 초기 세팅실시. 초기 세팅부분에서 신경써야 할 부분은 다음과 같다.
상기 사항들을 항상 주의해서 프로젝트 초기 셋팅 신경쓰도록 하자.
프로젝트 생성 및 세팅을 했으니 실제 프로젝트의 기능을 담당할 앱을 생성해주어야 한다. 회원 가입 앱에서 필수적으로 구현해야 할 사항으로 다음과 같은 미션이 주어졌다.
@
와 .
이 필수로 포함되어야 합니다. 해당 조건이 만족되지 않을 시 적절한 에러를 반환해주세요. 이 과정을 email validation이라고 합니다.가장 먼저 모델을 생성해주자. 회원가입을 하기 위해서 필요한 정보는 전화번호 or 사용자 이름 or email 로 주어졌다. 이 중에서 나는 이메일을 선택했는데, 다음 미션에서 이메일의 정규식과 관련된 미션이 있었기에 정규식을 사용해보고 싶어서 이메일을 선택했다. 그래서 다음과 같이 모델링을 설정했다.
from django.db import models
from .validation import validate_email, validate_password
class User(models.Model):
email = models.EmailField(
max_length = 50,
validators = [validate_email],
unique = True,
)
password = models.CharField(
max_length = 300,
validators = [validate_password],
)
django에서 기본적으로 제공하는 Field인 EmailField
와 CharField
를 사용했다. 최대 길이는 이메일과 패스워드를 각각 50, 300으로 지정했고, 비밀번호는 다른 사용자와 같을 수 있지만 이메일이 중복되선 안되기에 option
값으로 unique=True
를 지정해주었다. 다른 옵션으로 지정한 validators
는 이메일과 패스워드를 입력했을 때, 조건에 만족한 값을 사용자가 입력해주는지 검사해주는 유효성 검사를 해주는 함수를 입력했다. 이 함수에 대해서는 Validation
에서 설명하겠다.
유효성 검사를 실제로 실시하는 함수가 들어있는 파일이다. 실제로 validation.py
로 파이썬 파일을 하나 새로 만들어주었고, 그 안에 유효성 검사에 관련된 함수들을 작성한 뒤 모델에서 import
해서 필요한 함수들을 사용했다. validation.py
는 다음과 같이 작성했다.
import re
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _
def validate_email(value):
email_reg = r"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?"
regex = re.compile(email_reg)
if not regex.match(value):
raise ValidationError(
_('%(value)s is not a valid number'),
params = {'value' : value},
)
def validate_password(password):
if len(password) <= 8:
raise ValidationError("password is too short.")
먼저 정규표현식을 사용해야하기 때문에 정규표현식에 관련된 re
를 import
해주었다. 그 뒤에 조건에 만족하지 않으면 ValidationError
를 발생시켜야하기 때문에 해당 부분을 import
해주었다. ugettext_lazy
는 아직 잘 이해하지 못했는데, Django 공식문서에서 주어진 예시에 위와 같이 사용해서 나도 공식문서를 보고 사용했다. 이 부분은 추가적으로 공부가 필요한 부분이다.
이메일 정규표현식은 인터넷에 여러가지가 올라와 있지만 여러가지를 실제로 사용해봤을 때, 길긴 했지만 위의 정규 표현식이 가장 정확했다. @
or .
이 없는 경우를 정확히 찾아주었기에 위의 정규 표현식을 사용했다. 정규 표현식을 컴파일해주고, 이메일의 값과 매칭 시켰을 때, 매칭되지 않으면 ValidationError
를 발생시킨다. params
부분은 에러가 발생했을 때, 사용자가 입력한 값을 보여주면서 메시지를 띄우기 때문에 어떤 부분에서 잘못됐는지 인식하기 훨씬 수월해진다.
패스워드와 관련된 부분은 제약사항이 8자 이하일 경우 에러 메세지를 출력해줘야했기 때문에 비교적 간단하게 작성했다.
import json, traceback
from django.views import View
from django.http import JsonResponse
from django.core.exceptions import ValidationError
from .models import User
class SignupView(View):
def post(self, request):
data = json.loads(request.body)
try:
if not data['email'] or not data['password']:
return JsonResponse({'message' : 'No value entered'}, status = 400)
signup_user = User(
email = data['email'],
password = data['password'],
)
signup_user.full_clean()
signup_user.password = signup_user.password.encode('utf-8')
signup_user.password = bcrypt.hashpw(signup_user.password, bcrypt.gensalt())
signup_user.password = signup_user.password.decode('utf-8')
signup_user.save()
return JsonResponse({'message' : 'SUCCESS'}, status = 200)
except ValidationError as e:
trace_back = traceback.format_exc()
print(f"{e} : {trace_back}")
except KeyError:
return JsonResponse({'message' : 'KEY_ERROR'}, status = 400)
return JsonResponse({'message' : 'Invalid format or Duplicated Email'}, status = 400)
# 유저 리스트
def get(self, request):
user = User.objects.values()
return JsonResponse({'user' : list(user)}, status = 200)
views.py
는 위와 같이 작성했다. 여기서 가장 어려웠던 부분은 full_clean()
과 예외처리에 대한 부분이였다.
먼저 입력받은 데이터를 json
형식의 파일로 받아서 data
라는 변수에 넣어준다. 그 다음의 if
문은 이메일이나 패스워드가 빈값으로 들어왔을 때 반환해 줄 에러 메시지와 관련되어 있다. data['email']
이나 data[password']
값이 없을 경우(Flase) 입력 된 메세지가 없다는 뜻으로 No value entered
라는 메시지와 함께 400
에러를 반환해준다.
또한 프론트앤드와 백엔드가 JSON 방식으로 통신을 하기 때문에 서로의 KEY
값이 같아야 하는데, KEY
값이 같지 않을 경우 ( email이라는 키를 잘못 입력했을경우 ) KeyError
가 발생하는데 except
로 KEY ERROR
가 발생했을 경우 에러코드와 적절한 메세지를 반환해주는 코드를 작성했다.
여기서 400
에러는 잘못된 구문 요청이나, 유효하지 않은 메시지 프레이밍 등의 오류를 감지해 요청을 처리할 수 없거나, 처리하지 않는다는 의미의 에러코드이다.
if
문을 정상적으로 통과했다는것은 데이터가 빈칸이 아닌 어떤 값이 들어왔음을 의미한다.
여기서 이제 full_clean()
을 통해 실제 유효성 검사를 실시한다. 이 부분이 많이 어려웠는데, model에서 생성한 class에 validation
이라는 옵션을 주는것은 보통 form
에 관련된 경우 사용을 한다. 따라서 지금 나의 상황처럼 form
이 아닌 파일에서 유효성 검사를 실제로 실시하지 않는다. 이럴 때 full_clean()
메소드를 사용해주면 form
이 아니여도 유효성 검사를 실시해주는 메소드이다. 따라서 유저가 입력한 정보를 받아서 signup_user
라는 객체를 생성했고, 해당 객체에서 full_clean()
메소드를 사용하여 유효성 검사를 실시했다. 만약 유효성 검사에서 ValidationError가 발생하게 되면 except 구문으로 예외처리에 들어간다. traceback
은 호출 순서와 발생한 예외를 확인할 수 있는 역할을 한다. 사용하는 방법에는 두가지 방법이 있는데 다음의 블로그에서 확인했다.
[Python] Exception 처리와 추적 (디버깅)
나는 2번 방법으로 사용했다. ( 권장되는 방법이라길래.. )
이렇게 처리한 예외를 출력해주도록 하였고, 가장 마지막의 return
문에 JsonResponse
로 하여 400
의 에러코드와 적절한 에러 메세지를 띄워주도록 구현하였다.
만약 유효성 검사에서 문제가 생기지 않아 정상적으로 try
가 작동하였다면 else:
부분으로 넘어가서 받아온 유저 정보를 저장하고, SUCCESS
메시지와 함께 200
코드를 출력해준다.
bcyrpt
를 통해서 암호화 한 부분음 다음에서 확인할 수 있다.
비밀번호 암호화 및 token 발행 ( bcrypt, JWT )
from django.urls import path , include
urlpatterns = [
path('', include('User.urls')),
]
User
앱의 urls
로 연결시켜주었다.
from django.urls import path
from .views import SignupView
urlpatterns = [
path('user', SignupView.as_view()),
]
View
에서 생성한 SignupView
클래스를 임포트해주고 뷰로 보이도록 메소드를 설정했다. 그리고 user
와 관련된 엔드포인트이기 때문에 user
로 경로를 설정해줬다.
공식 문서에 예제로 나와있어서 사용한 케이스. 어떤 기능때문에 사용했는지 정확히 모르기 때문에 추가적인 정리가 필요한 부분.
Translation | Django documentation | Django
현재 알고 있는 내용은 위에서 설명한대로 form
이 모델에서 바로 유효성 검사를 실시하고 싶을 때, 사용하는 메소드이다. 이 메소드를 사용해주어야 실제로 유효성 검사를 실시한다. 이 clean()
과 관련된 메소드는 세가지가 있었는데 이 세가지를 모두 포함하고 있는 메소드가 full_clean()
이였기 때문에 사용했다. 세가지의 내용에 대해서는 다음에서 확인할 수 있다. 나도 추가적인 공부가 필요한 부분이기에 따로 남겨둔다.
Model instance reference | Django documentation | Django
대부분의 리뷰가 Align과 import 여러개 했을 때는 개행을 시켜주는 것이 훨씬 가독성이 좋다는 피드백을 받았다. 그래서 정렬이나 import 개행이 이루어져 있지 않은 파일들에 대해서는 수정을 했다. 그 외 회원가입 관련한 수정사항은 다음과 같다.
Error Exception 이후에 else구문이 오는 것은 다소 어색합니다. signup_user가 저장되는 것은 제일 위 if 즉, key error가 아닐 경우 바로 진행되어도 되므로 try 문 내부로 넣어주세요 :)
try:
signup_user = User(
email = data['email'],
password = data['password'],
)
signup_user.full_clean()
except ValidationError as e:
trace_back = traceback.format_exc()
print(f"{e} : {trace_back}")
else:
signup_user.save()
return JsonResponse({'message' : 'SUCCESS'}, status = 200)
회원 가입 시 이메일과 패스워드를 입력하는데 그 과정에서 유효성 검사를 통과하지 못하면 에러 발생, 통과하면 SUCCESS 문구를 출력해주는 구문이다. 멘토님의 수정사항으로는 try
뒤에 else
가 나오는것이 다소 어색하다고 하셨다. 멘토님의 리뷰를 보고 나서 생각하지 못했던 부분이라 바로 수정에 들어갔다.
try:
signup_user = User(
email = data['email'],
password = data['password'],
)
signup_user.full_clean()
signup_user.save()
return JsonResponse({'message' : 'SUCCESS'}, status = 200)
except ValidationError as e:
trace_back = traceback.format_exc()
print(f"{e} : {trace_back}")
full_clean()
구문에서 유효성 검사를 실행하고 정상적이면 바로 저장하고 SUCCESS 메세지를 반환해준다. 만약 full_clean()
에서 유효성 검사를 통과하지 못하면 바로 except
로 넘어가기 때문에 else
가 필요하지 않았다. 실제로 수정 후 동작을 확인했을때도 정상적으로 동작했다.
당연시 생각했던 부분에 대해 새로운 시각을 길러주시는 것 같다. 🙂