3월 13, 2022

차담화 프로젝트 - 내가 담당하지 않았던 기능들

 제가 담당하지 않았던 기능들에 대해서 리뷰하기 위해 타 팀원들의 코드를 분석한 것임을 밝힙니다.


  • 회원가입, 로그인
이전 위스타그램 세션때 한번 다루어보았던 기억을 바탕으로 주석을 달아보았습니다.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import json, bcrypt, jwt

from django.http  import JsonResponse
from django.views import View
from django.core.exceptions import ValidationError

from my_settings  import SECRET, ALGORITHM
from users.models import User
from utils.login_required  import login_required
from utils.validation import validate_email, validate_password #이메일 비밀번호 정규식 따로 빼서 관리

class SignUpView(View):
    def post(self, request):
        try:
            data = json.loads(request.body)

            username = data['username']
            email    = data['email'] 
            password = data['password']
            address  = data['address']
            point    = data.get('point', 100000)
            
            hashed_password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8') #str화 하여 db에 저장

            validate_email(email)
            validate_password(password)

            if User.objects.filter(email = email).exists():
                return JsonResponse ({'message' : 'EMAIL_ALREADY_EXIST'}, status=400)

            if User.objects.filter(username = username).exists():
                return JsonResponse ({"message" : "USERNAME_ALREADY_EXIST"}, status=400)

            User.objects.create(
                username = username,
                email    = email,
                password = hashed_password,
                address  = address,
                point    = point         
            )

            return JsonResponse({'message' : 'SIGNUP_SUCCESS'}, status=201)

        except ValidationError as e:
            return JsonResponse({'message': e.message }, status=400)

        except json.JSONDecodeError:        
            # json.loads를 쓸때 발생, 받는 값이 없거나 , deserialize(string->object)(23번 줄) 되고 있는 값이 json형식이 아닐 때 에러가 난다. 
            # json 형식을 통과할 때 읽을 수 없기 때문에 발생하는 에러
            return JsonResponse({'message': 'JSON_DECODE_ERROR'}, status=400)

        except KeyError:
            return JsonResponse ({'message' : 'KEY_ERROR'}, status=400)
 

class SignInView(View):
    def post(self, request):
        try:
            data     = json.loads(request.body)
            
            email    = data['email']
            password = data['password']

            if not User.objects.filter(email=email).exists():
                return JsonResponse({'message':'INVALID_USER'}, status=400)

            user_password = User.objects.get(email=email).password # 이메일 확인되면 이메일에 해당하는 Password가져옴

            if not bcrypt.checkpw(password.encode('utf-8'), user_password.encode('utf-8')): 
                #bytes타입인 데이터(json으로 들어온 암호 / db에 저장된 암호) 두가지를 받아서 비밀번호 맞는지 확인
                return JsonResponse({'message':'INVALID_USER})



로그인 데코레이터의 경우는 다음과 같습니다
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
def login_required(func):  # 함수명은 동사가 올바른 컨벤션 네이밍
    def wrapper(self, request, *args, **kwargs):
        try: 
            token        = request.headers.get("Authorization", None)  #request의 헤더에 오는 'Authorization"을 받아서
            payload      = jwt.decode(token, SECRET, ALGORITHM) #이를 token을 decode해서 payload에 {'id':1} 이런 형식으로 담음
            request.user = User.objects.get(id=payload['id']) # request의 user 변수에 id가 payload의 id값인 user객체를 담고 이를 리턴해줌

            return func(self, request, *args, **kwargs) # 함수에 request를 담아서 리턴

        except jwt.exceptions.DecodeError:       #  token deocde시 에러 발생                        
            return JsonResponse({"message" : "INVALID_TOKEN" }, status=400)

        except User.DoesNotExist:   # 14번째줄에서 매치하는 user가 없으면 발동
            return JsonResponse({"message" : "INVALID_USER"}, status=400)
            
    return wrapper 


  • 장바구니 기능 
  • 장바구니 추가 - 사용자가 제품과 수량 선택시 장바구니에 들어갑니다
get_or_create를 사용하였습니다
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@login_required
    def post(self, request): #장바구니 추가 
        try:
            data = json.loads(request.body)
            
            user     = request.user #로그인 데코레이터에서 리턴받은 user
            quantity = data['quantity']
            drink_id = data['drink_id']

            if not Drink.objects.filter(id = drink_id).exists(): # 해당하는 drink없을시 
                return JsonResponse({'message' : 'DRINK_NOT_EXIST'}, status = 400)

            cart, created = Cart.objects.get_or_create(
                user     = user,
                drink_id = drink_id,
                quantity = quantity
            )
            # get or create -> (object,created) 의 튜플 형식 반환 - created는 boolean flag(True or False)
            # True면 인스턴스가 get_or_create 메서드에 의해 생성된 것
            # False면 인스턴스가 DB에서 꺼내진것(기존에 있던것)
            # cart는 object(모델의 인스턴스) 이기에 save()를 통해 저장시켜줌

            cart.save()

            return JsonResponse({'message' : 'CART_CREATED'}, status = 201)

        except json.JSONDecodeError:
            return JsonResponse({'message' : 'JSON_DECODE_ERROR'}, status = 400)  

        except Cart.DoesNotExist: 
            return JsonResponse({'message' : 'CART_NOT_FOUND'}, status = 400)  
        
        except KeyError:
            return JsonResponse({'message' : 'KEY_ERROR'}, status = 400)
  • 장바구니 조회 - 사용자가 장바구니 내에 있는 데이터를 조회합니다
select_related를 사용하여 정참조한 객체를 가져올때 db가 hit되는 횟수를 줄여줍니다.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
    @login_required
    def get(self, request): #장바구니 조회 

        carts  = Cart.objects.select_related('drink').filter(user = request.user) # 하나의 쿼리셋을 가져올 때, 연관돼 있는 objects들을 미리 불러옴
        #1대 N 관계에서 N이 사용(정방향 참조에서의 join에 유리하게 사용)
        # SQL join을 통해 데이터를 즉시 가져옴(Eager Loading) / 추가 쿼리 X(prefetch_related)
        # 이렇게 불러온 데이터들은 result_cache에 cahce됨 
        # 미리 db에서 객체를 얻어내므로 db에 액세스 하는 횟수를 줄일 수 있다
        #cart는 drink정참조
        
        result = [
            {
            'cart_id'    : cart.id,
            'drink_name' : cart.drink.name,
            'quantity'   : cart.quantity,
            'price'      : cart.drink.price
            }
            for cart in carts] # 리스트컴프리헨션 사용하여 리스트안에 각 카트에 해당하는 딕셔너리 생성 

        return JsonResponse({'result' : result}, status = 200)

  • 장바구니 수량 변경
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
    @login_required
    def patch(self, request): # 장바구니 수량 변경 crud 중 u
        #update에는 put과 patch가 있는데,
        #patch의 경우에는 변경하고자 하는 속성의 키와 값만 전달하면 됨 (일부 속성만 유연하게 수정 가능)
        # put의 경우에는 그 자원이 가지는 모든 속성 값을 전달해야 함
        try:
            data = json.loads(request.body)

            cart          = Cart.objects.get(id = data['cart_id']) #json으로 온 cart_id에 해당하는 cart객체를 변수에 저장
            cart.quantity = data['quantity'] # cart의 quantity를 변경
            
            cart.save()
            
            return JsonResponse({'message' : 'ITEM_QUANTITY_CHANGED', 'quantity' : cart.quantity}, status = 200)

        except Cart.DoesNotExist:
            return JsonResponse({'message' : 'CART_NOT_FOUND'}, status = 400)  

        except KeyError:
            return JsonResponse({'message' : 'KEY_ERROR'}, status = 400)

  • 장바구니 삭제
path parameter를 활용하여 장바구니에 대한 정보를 받아줍니다
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
    @login_required
    def delete(self, request, cart_id): # 장바구니 삭제  # cart_id를 인자로 받음(path_parameter)
        try:
            data = json.loads(request.body)

            user = request.user

            if not Cart.objects.filter(user = user, id = cart_id).exists(): # user,cart_id에 일치하는 쿼리셋 없을시 
                return JsonResponse({'message' : 'CART_DOES_NOT_EXIST'}, status = 400) 

            Cart.objects.filter(user = user, id = cart_id).delete() #있으면 Cart에서 삭제함

            return JsonResponse({'message' : 'CART_DELETED'}, status = 204)

        except KeyError:
            return JsonResponse({'message' : 'KEY_ERROR'}, status = 400)

  • 리뷰 기능 
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class CommentView(View):
    @login_required
    def post(self, request, drink_id):
        try:
            data    = json.loads(request.body)

            rating  = data['rating']
            comment = data['comment']
            user_id = request.user.id  # 로그인 데코레이터에서 반환된 user
            drink   = Drink.objects.get(id=drink_id) # drink_id는 path parameter로 받음
        
            Review.objects.update_or_create(  # 인스턴스가 존재한다면 값을 업데이트하고 , 존재하지 않는다면 인스턴스를 생성 
            # get_or_create와 마찬가지로 (object, created)의 튜플을 리턴함 
                user_id  = user_id,
                drink = drink,
                defaults = {'comment':comment, 'rating':rating}  
            )

            return JsonResponse({'message':'review_posting_success'}, status=201)
        
        except KeyError:
            return JsonResponse({'message':'Key_error'}, status=400)

path_parameter로 들어온 drink_id로 update_or_create를 통해 리뷰 코멘트 작성 혹은 업데이트를 수행합니다