본문 바로가기
  • 실행력이 모든걸 결정한다
프로젝트 연습

[구현 완료] 간식 창고 (DRF)

by 김코더 김주역 2023. 3. 13.
반응형

Github 링크

https://github.com/datamaker-kr/pilot-project

 

GitHub - datamaker-kr/pilot-project: 백엔드 신입 개발자 김주역님의 파일럿 프로젝트 입니다.

백엔드 신입 개발자 김주역님의 파일럿 프로젝트 입니다. . Contribute to datamaker-kr/pilot-project development by creating an account on GitHub.

github.com

 

 

 

프로젝트 소개

  • 프로젝트 이름 : 간식 창고
  • 프로젝트 설명 : 직원들은 간식을 신청할 수 있고, 관리자는 직원들이 신청한 간식들을 관리할 수 있는 서비스다. 이전에 순수 Django로만 구현했었는데, 이를 DRF로 전환했다.
  • 기술 스택 : Django, DRF
  • 진행 인원 및 작업 기간 : 1인, 2023.02.20~2023.03.10
  • 사용 에디터 : PyCharm
  • 버전 관리 툴 : Git

 

 

 

프로젝트 내용

<개요>

  • 이전 프로젝트
  • 요구 사항
  • 구현 내용
  • 화면 구성 변동 사항

 

 

1. 이전 프로젝트

이전에 순수 Django로만 구현했던 간식 창고 프로젝트다. 겹치는 부분은 이번 포스팅에서 생략할 것이다.

https://kimcoder.tistory.com/594

 

[구현 완료] 간식 창고 (Django)

Github 링크 https://github.com/datamaker-kr/pilot-project GitHub - datamaker-kr/pilot-project: 백엔드 신입 개발자 김주역님의 파일럿 프로젝트 입니다. 백엔드 신입 개발자 김주역님의 파일럿 프로젝트 입니다. . Con

kimcoder.tistory.com

 

 

 

2. 요구 사항

1) 기술 요청

- 순수 Django -> DRF로 전환
- Django Admin을 사용하지 않고도 관리가 이루어질 수 있도록 할 것

 

 

2) 간식 신청 게시판

- 한 번 등록된 간식은 간식 모델로 저장되고 다음 간식 신청 때 선택지에서 선택할 수 있다.

- 주문 대기중 상태에서 자신이 등록한 간식 요청을 수정할 수 있다.

- 아직 주문되지 않은 간식 중, 신청된 간식이 있을 경우에 중복된 간식을 신청할 수 없다. 간식을 구분하는 유일값은 간식의 이름이다.

- 신청된 간식에 좋아요/싫어요를 선택할 수 있으며, 싫어요가 좋아요보다 많으면 간식을 주문할 수 없다. 또한, 좋아요 비율 순으로 정렬한다.

 

 

 

3. 구현 내용

1) Snack측 모델 수정

- 기존에는 Snack 모델만 존재했었는데 SnackRequest, SnackEmotion 클래스를 추가했다.

- 기존의 Snack 모델에 있던 간식 신청 정보(신청자, 설명, 승인 여부, 승인 날짜, 신청일)를 SnackRequest로 옮겼다. 그리고 좋아요, 싫어요 수를 표시하기 위한 필드를 @property로 추가했다.

- 좋아요/싫어요 기능을 위한 SnackEmotion 모델에는 감정 이름, 사용자 이름, SnackRequest 외래키를 필드로 넣었다.

- 좋아요 비율 순으로 정렬하기 위해 SnackRequestQueryset 클래스를 생성하고 쿼리셋을 반환하는 함수를 작성했다.

 

 

2) User측 REST API

(1) 회원 가입

 

(2) 유저 목록

 

(3) 로그인

[3) Token 기반 인증 방식]에서 자세히 설명하도록 하겠다.

 

(4) 일반 유저를 관리자로 업그레이드

- 관리자 권한이 필요하다.

 

(5) 관리자를 일반 유저로 다운그레이드

- 관리자 권한이 필요하다.

 

(6) 회원 탈퇴

- 일반 유저 권한이 필요하다.

 

 

3) Token 기반 인증 방식

- Token 기반 인증 방식은 기본적으로 암호화를 사용하는데, 암호화 키로 settings.py의 SECRET_KEY를 사용한다.

 

(1) rest_framework.authtoken 앱 추가

INSTALLED_APPS = [
    ...,
    'rest_framework.authtoken',
    ...
]

 

(2) 프로젝트가 토큰 방식을 사용하도록 설정

REST_FRAMEWORK = {
    ...,
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
    ]
}

 

(3) REST API 권한 지정

- 다음과 같이 get_permissions 메소드를 오버라이딩하는 방법을 사용했다.

def get_permissions(self):
    if self.action in ['list', 'retrieve', 'monthly_snack_list', 'legacy_list']:
        self.permission_classes = []
    elif self.action in ['partial_update', 'destroy']:
        self.permission_classes = [IsSnackRequestOwnerOrAdmin]
    elif self.action in ['manage']:
        self.permission_classes = [IsAdminUser]
    else:
        self.permission_classes = [IsAuthenticated]
    return super().get_permissions()

- @action 메소드를 사용하는 경우에는 @action 데코레이터의 permission_classes 옵션에 지정하는 방법도 있다.

 

(4) 토큰 발급

- 토큰은 로그인시 발급한다.

class LoginView(generics.GenericAPIView):
    serializer_class = LoginSerializer

    def post(self, request):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        token = serializer.validated_data
        return Response(
            {
                "username": token.user.username,
                "token": token.key,
            },
            status=status.HTTP_200_OK
        )

 

(5) 토큰 저장 및 삭제

- 토큰 저장 및 삭제는 자바스크립트에서 이루어진다.

- 로그인시 로컬 스토리지라는 웹 브라우저 자체 저장소에 저장한다.

// JavaScript
localStorage.setItem("snack_token", token);

- 로그아웃 및 회원탈퇴시 토큰을 삭제한다.

// JavaScript
localStorage.removeItem("snack_token");

 

(6) REST API 요청에 토큰 포함

var token = localStorage.getItem("snack_token"); // 토큰 가져오기

$.ajax({
    url: "...",
    type: "...",
    headers: {
      "Authorization": "Token "+token,
    },
}).done(function(res) {
    ...
}).fail(function(request, status, error) {
    ...
})

 

(7) DRF 서버 내에서 유저 객체 가져오기

- 토큰을 받은 DRF 서버는 알아서 request.user에 유저 정보를 담아준다. 또, 다음과 같이 시리얼라이저에서 request 객체를 가져올 수도 있다.

def create(self, validated_data):
    request = self.context.get('request', None)
    ...

 

 

4) Snack측 REST API

(1) 간식 신청 전체 목록

- 페이징 기능을 넣었기 때문에 결과가 count, next, previous와 함께 표시된다.

 

(2) 월별 비치 예정 목록

- 페이징 기능은 넣지 않았다.

 

(3) 이전 간식 목록

 

(4) 새로운 간식 신청

- 일반 유저 권한이 필요하다.

 

(5) 기존 간식 신청

- 일반 유저 권한이 필요하다.

- 이미 주문 대기중인 간식은 신청이 불가능하다. 해당 간식을 주문했다면 다시 주문이 가능하다.

 

(6) 간식 신청 관리

- 관리자 권한이 필요하다.

 

(7) 간식 신청 수정

- 소유자 또는 관리자 권한이 필요하다.

class IsSnackRequestOwnerOrAdmin(permissions.BasePermission):

    def has_object_permission(self, request, view, obj):
        if request.method in permissions.SAFE_METHODS:
            return True
        return obj.user == request.user or request.user.is_superuser

 

(8) 간식 신청 삭제

- 소유자 또는 관리자 권한이 필요하다.

 

(9) 이전 6개월 + 현재 달 + 이후 6개월 연-월 리스트

 

(10) 감정 표현

- 일반 유저 권한이 필요하다.

- 자세한 내용은 [5) 좋아요/싫어요 기능]에서 설명할 것이다.

 

 

5) 좋아요/싫어요 기능

- 일반 유저 권한을 가진 사용자는 간식 신청에 대해 좋아요 또는 싫어요를 누를 수 있다.

class SnackEmotionViewSet(mixins.CreateModelMixin,
                          mixins.ListModelMixin,
                          viewsets.GenericViewSet):
    queryset = SnackEmotion.objects.all()
    serializer_class = SnackEmotionSerializer

    def get_permissions(self):
        if self.action in ['create']:
            self.permission_classes = [IsAuthenticated]
        else:
            self.permission_classes = []
        return super().get_permissions()

- 이미 좋아요가 눌러진 상태에서 또 좋아요를 누르거나 싫어요로 바꾸면 좋아요가 취소된다.

class SnackEmotionSerializer(serializers.ModelSerializer):
    class Meta:
        model = SnackEmotion
        fields = ['snack_request', 'name']

    def create(self, validated_data):
        request = self.context.get('request', None)
        validated_name = validated_data['name']
        emotion, is_created = SnackEmotion.objects.get_or_create(snack_request=validated_data['snack_request'], user=request.user, defaults={'name': validated_name}) # defaults는 생성시에만 동작
        if not is_created:
            if emotion.name == validated_name:
                emotion.delete()
            else:
                emotion.name = validated_name
                emotion.save()
        return emotion

- 좋아요 비율 순으로 정렬하기 위한 쿼리셋을 기본 쿼리셋으로 지정한다.

class SnackRequestViewSet(mixins.CreateModelMixin,
                          mixins.ListModelMixin,
                          mixins.UpdateModelMixin,
                          mixins.DestroyModelMixin,
                          viewsets.GenericViewSet):

    queryset = SnackRequest.objects.order_by_like_proportion()
    ...

 

 

 

4. 화면 구성 변동 사항

1) 간식 목록

- 좋아요 또는 싫어요를 누를 수 있고, 간식 신청에 대한 수정 또는 관리가 가능하다.

 

 

2) 이전 간식 신청 목록

- 이전 간식 신청 목록을 확인할 수 있다.

 

 

 

3) 유저 목록

- 유저 목록을 조회할 수 있으며, 권리자 권한을 부여하거나 해제할 수 있다. 물론 권한은 관리자만이 관리할 수 있다.

 

 

4) 간식 신청

- 기존에 승인된 적이 있는 간식을 다시 신청할 수 있다.

- 목록에 없다면 [간식이 목록에 없나요?] 버튼을 눌러서 새 간식을 신청할 수 있다. 

 

 

 

피드백

- 중요도가 높을수록 로직을 깊은(DB와 가까운) 곳에 작성할 것

- Endpoint는 최소화할 것

- 중복 코드를 최소화할 것

 

반응형

댓글