Github 링크
https://github.com/datamaker-kr/pilot-project
프로젝트 소개
- 프로젝트 이름 : 간식 창고
- 프로젝트 설명 : 직원들은 간식을 신청할 수 있고, 관리자는 직원들이 신청한 간식들을 관리할 수 있는 서비스다. 이전에 순수 Django로만 구현했었는데, 이를 DRF로 전환했다.
- 기술 스택 : Django, DRF
- 진행 인원 및 작업 기간 : 1인, 2023.02.20~2023.03.10
- 사용 에디터 : PyCharm
- 버전 관리 툴 : Git
프로젝트 내용
<개요>
- 이전 프로젝트
- 요구 사항
- 구현 내용
- 화면 구성 변동 사항
1. 이전 프로젝트
이전에 순수 Django로만 구현했던 간식 창고 프로젝트다. 겹치는 부분은 이번 포스팅에서 생략할 것이다.
https://kimcoder.tistory.com/594
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는 최소화할 것
- 중복 코드를 최소화할 것
'프로젝트 연습' 카테고리의 다른 글
[구현 완료] 간식 창고 (Django) (0) | 2023.02.20 |
---|---|
[구현 완료] 주식 웹어플리케이션 (0) | 2021.05.19 |
[교내 수상작] 후방 충돌방지 자동차 (1) | 2020.08.29 |
[구현 완료] Java 기차표 예약 시스템 (5) | 2020.08.29 |
댓글