차담화 프로젝트 - 내가 담당하지 않았던 기능들
제가 담당하지 않았던 기능들에 대해서 리뷰하기 위해 타 팀원들의 코드를 분석한 것임을 밝힙니다.
- 회원가입, 로그인
이전 위스타그램 세션때 한번 다루어보았던 기억을 바탕으로 주석을 달아보았습니다.
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를 통해 리뷰 코멘트 작성 혹은 업데이트를 수행합니다