개발기록장

[Django] 인스타그램 클론 코딩(1) - 회원가입 기능 구현 본문

TIL/Django

[Django] 인스타그램 클론 코딩(1) - 회원가입 기능 구현

yangahh 2021. 2. 2. 16:02

 

[다음 글]

[Django] 인스타그램 클론 코딩(2) - 로그인 기능 구현

 

[Django] 인스타그램 클론 코딩(2) - 로그인 기능 구현

인스타그램은 전화번호, 사용자 이름, 이메일 중 하나와 패스워드로 로그인을 할 수 있다. 이를 views.py에 LoginView에 작성하였다. views.py - LoginView 클래스에서 로그인 기능 처리 import jwt import json i..

devvvyang.tistory.com

 

 

1. 구현해야 할 기능

- 사용자와 관련된 기능을 처리할 app생성 (User 이름으로 생성함)

- 회원가입 시 이메일, 사용자이름(닉네임), 전화번호는 필수로 입력 받는다

- 이메일에는 반드시 '@'과 '.'이 들어가있어야 하며 이를 만족하지 않을 시 에러를 반환한다. (정규표현식 사용하기)

- 패스워드는 반드시 8자리 이상이여야하며 이를 만족하지 않을 시 에러를 반환한다. 

 

 

2. user/models.py

from django.db   import models

class User(models.Model):
    email      = models.EmailField(max_length=50, unique=True) 
    name       = models.CharField(max_length=20, unique=True) 
    phone      = models.CharField(max_length=15, unique=True)
    password   = models.CharField(max_length=300)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = 'users'

  • 회원 정보를 관리하는 테이블을 User라는 이름의 클래스를 통해 만들었다.

  • email(이메일), name(닉네임), phone(전화번호)는 중복될 수 없으므로 unique=True 옵션을 사용했다.

  • password는 암호화된 문자열로 저장되어야 하기 때문에 max_length를 300으로 잡아주었다.

  • 데이터의 생성 시점을 관리하는 필드인 created_at에는 auto_now_add 옵션을 주어서 데이터가 들어오면 자동으로 그 시점을 저장할 수 있게 하였다.

  • 데이트의 수정 시점을 관리하는 필드인 updated_at에는 auto_now 옵션을 주어 데이터가 수정되면 자동으로 그 시점을 저장할 수 있게 하였다.

 

3. user/views.py (전체 로직 설명)

import re
import json
import bcrypt

from django.views           import View
from django.http            import JsonResponse
from django.db.models       import Q

from .models                import User

MINIMUM_PASSWORD_LENGTH = 8

def validate_email(email):
    pattern = re.compile('^.+@+.+\.+.+$')
    if not pattern.match(email):
        return False
    return True

def validate_password(password):
    if len(password) < MINIMUM_PASSWORD_LENGTH:
        return False
    return True

def validate_phone(phone):
    pattern = re.compile('^[0]\d{2}\d{3,4}\d{4}$')
    if not pattern.match(phone):
        return False
    return True

class SignupView(View):
    def post(self, request):
        data     = json.loads(request.body)
        email    = data.get('email', None)
        name     = data.get('name', None)
        phone    = data.get('phone', None)
        password = data.get('password', None)

        # KEY_ERROR check
        if not(password and email and name and phone):
            return JsonResponse({'message': 'KEY_ERROR'}, status=400)

        # validation check
        if not validate_email(email):
            return JsonResponse({'message': 'EMAIL_VALIDATION_ERROR'}, status=422)

        if not validate_password(password):
            return JsonResponse({'message': 'PASSWORD_VALIDATION_ERROR'}, status=422)

        if not validate_phone(phone):
            return JsonResponse({'message': 'PHONE_VALIDATION_ERROR'}, status=422)
        
        # unique check
        if User.objects.filter(Q(email=email) | Q(name=name) | Q(phone=phone)).exists():
            return JsonResponse({'message': 'USER_ALREADY_EXISTS'}, status=409)

        User.objects.create(
            email    = email,
            name     = name,
            phone    = phone,
            password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
        )
        return JsonResponse({'message': 'SUCCESS'}, status=200)

        

 

  • SignupView에서 http post 메소드로 요청이 들어올 시 회원가입 처리

  • http resquest에서 이메일(email), 사용자 이름(name), 전화번호(phone), 비밀번호(password)를 입력받아서 회원가입이 되도록 구현

  • 이메일, 전화번호, 비밀번호는 내가 정의한 validate 함수를 통해 특정 포맷을 따르지 않을 시 에러를 반환한다.

  • 이미 가입되어 있는 이메일 또는 사용자 이름 또는 전화번호로는 가입이 안되도록 구현하였다.

  • bcrypt.hashpw()메소드를 이용하여 사용자가 입력한 비밀번호를 평문이 아닌 암호화된 문자로 DB에 저장하도록 구현하였다.
    ** 암호화에 대한 설명은 이곳을 참고!!

 

4. 발생할 수 있는 에러와 처리 방법

1) Key Error

class SignupView(View):
    def post(self, request):
        data     = json.loads(request.body)
        email    = data.get('email', None)     # request body에 'email'이라는 키 값이 없으면 None으로 처리
        name     = data.get('name', None)      # request body에 'name'이라는 키 값이 없으면 None으로 처리
        phone    = data.get('phone', None)     # request body에 'phone'이라는 키 값이 없으면 None으로 처리
        password = data.get('password', None)  # request body에 'password'이라는 키 값이 없으면 None으로 처리

        # KEY_ERROR check
        if not(password and email and name and phone):
            return JsonResponse({'message': 'KEY_ERROR'}, status=400)

        (생략..)
  • http request body에서 email, name, phone, password라는 key 값이 들어오지 않은 값 default로 None값을 세팅하도록 구현하였다.

  • 회원가입 시 email, name, phone, password는 필수로 입력받아야 하는 값이기 때문에 넷 중 하나라도 None이라면 key error를 return하여 key error를 처리하였다.

 

2) 유효성 검사

import re

(생략..)

MINIMUM_PASSWORD_LENGTH = 8

def validate_email(email):
    pattern = re.compile('^.+@+.+\.+.+$')
    if not pattern.match(email):
        return False
    return True

def validate_password(password):
    if len(password) < MINIMUM_PASSWORD_LENGTH:
        return False
    return True

def validate_phone(phone):
    pattern = re.compile('^[0]\d{2}\d{3,4}\d{4}$')
    if not pattern.match(phone):
        return False
    return True
    
    
class SignupView(View):
    def post(self, request):

        (생략..)
        
        # validation check
        if not validate_email(email):
            return JsonResponse({'message': 'EMAIL_VALIDATION_ERROR'}, status=422)

        if not validate_password(password):
            return JsonResponse({'message': 'PASSWORD_VALIDATION_ERROR'}, status=422)

        if not validate_phone(phone):
            return JsonResponse({'message': 'PHONE_VALIDATION_ERROR'}, status=422)
        
        (생략..)
            
            
  • 이메일, 패스워드, 전화번호는 특정 포맷을 따라야하므로 유효성 체크를 해야한다.

  • django에서 제공하는 validation 함수도 있지만, 여기서는 내가 직접 validation 함수를 작성하여 사용하였다.

  • 만일 이 views.py 외의 다른 곳에서 이러한 함수가 쓰일 경우에는 반드시 이러한 함수만 모아서 별도의 파일로 저장하여 import로 불러와서 쓸 수 있게 해야한다. 하지만 지금 상황처럼 이곳에서만 쓰인다면 별도의 파일로 만드는 것은 선택사항이다. (나는 별도의 파일로 저장하는게 더 깔끔하고 좋은 것 같다.)

  • email은 '@'앞에는 아무 문자가 제한 없이 들어올 수 있으며, '@'과 '.' 사이, 그리고 '.' 뒤에도 아무 문자열이 제한 없이 들어올 수 있게 하는 패턴을 파이썬 정규표현식을 이용하여 구현하였다.

  • password는 8자리 이상인지만 체크한다. 이때 8이라는 상수는 변수로 지정해서 쓰는 것이 바람직하다.

  • 전화번호의 경우는 '-'없이 숫자만 입력하도록 하였으며, 이것도 파이썬 정규표현식을 이용하여 구현하였다.

  • 각 표현식에 맞는지를 체크하여 포맷에 맞지 않은 경우, SignupView에서 각 상황에 맞는 에러 메세지와 에러 코드를 반환하도록 처리하였다.

 

3) 중복 데이터 검사

class SignupView(View):
    def post(self, request):

        (생략..)
        
        # unique check
        if User.objects.filter(Q(email=email) | Q(name=name) | Q(phone=phone)).exists():
            return JsonResponse({'message': 'USER_ALREADY_EXISTS'}, status=409)
            
        (생략..)
  • 회원가입 시 이미 등록되어 있는 이메일, 사용자 이름, 전화번호로는 가입할 수 없으므로 이미 등록된 이메일 또는 사용자 이름 또는 전화번호를 입력했을 시 이미 등록된 사용자라는 에러를 반환하도록 구현하였다.

  • 여기서 에러처리를 함으로서 DB에서도 중복 데이터 에러가 나지 않도록 할 수 있다.