[Django] POST API 분석, generics.CreateAPIView

서재환·2022년 9월 13일
0

Django

목록 보기
29/40

CreatePartsRequestViewSet

해당 API에 대한 내용이 길어 쪼개서 보시죠.

Part1. DRF의 request와 CreateAPIView의 get_permissions를 통한 인증처리

class CreatePartsRequestViewSet(generics.CreateAPIView):
    """
    부품 요청 등록

    ---
    """

    serializer_class = CreatePartsRequestSerialize

    def get_permissions(self):
        created_type = self.request.data.get("created_type", None)

        if created_type == RequestType.SELFCLAIM:
            return [permissions.AllowAny()]

        return [permissions.IsAuthenticated()]
  • generics.CreateAPIViewGenericAPIView를 상속받기 때문에 속성인 serializer_class를 가져다 사용 할 수 있다. 해당 변수에 시리얼라이저를 지정해줌으로써 유효성검사, 직렬화 및 역직렬화 기능을 사용할 수 있다. 지정하지 않을 경우 get_serializer_class() 메서드를 오버라이드해서 사용해야한다.

  • DRF를 통해 request를 받을 때 request.data를 통해 프론트에서 보낸 데이터에 접근한다. 위 코드를 통해 프론트에서 보낸 데이터의 keycreated_type에 접근해 값을 얻어 오는데 값이 없을 경우 None으로 지정한다.
    DRF에서의 request immutable 객체인데 프론트에서 보낸 값을 바꿀 수 있다???

  • 해당 api 호출자가 RequestType 객체에 정의되어 있는 SELFCLAIM 변수 값과 일치하면 인증 처리를 거치지 않고 그렇지 않을 경우 인증처리 시행한다.

  • get_permissions 함수는 CreateAPIView 가 가지고 있는 메서드이다. 아래를 보면 알 수 있겠지만 리스트를 반환하는데 이를 오버라이드해서 사용하고 있다. 오버라이드인 이유는 CreateAPIView가 가지고 있는 메서드는 아래인데 메서드와 다르게 생겼기 때문이다.

"""
원함수(오버라이드 전)
"""
    def get_permissions(self):
        """
        Instantiates and returns the list of permissions that this view requires.
        """
        return [permission() for permission in self.permission_classes]
"""
사용자함수(오버라이드 후)
"""
    def get_permissions(self):
        created_type = self.request.data.get("created_type", None)

        if created_type == RequestType.SELFCLAIM:
            return [permissions.AllowAny()]

        return [permissions.IsAuthenticated()]
  • 반환 값 또한 permissions.IsAuthenticated를 통해 permissions.IsAuthenticated 객체를 인스턴스화 해서 리스트 안에 담아 리턴하였다. IsAuthenticated의 형태다. 반환값을 통해 해당 api 요청자가 존재해야 하고 인증된 요청자여야 한다.
class IsAuthenticated(BasePermission):
    """
    Allows access only to authenticated users.
    """

    def has_permission(self, request, view):
        return bool(request.user and request.user.is_authenticated)

DRF에서의 인증과 권한

Authentication 유저가 인증되었는지 아닌지에 대해 식별
Permissions 각 요청에 대한 허용 또는 거부
Throttling 일정 기간 동안에 허용 할 최대 요청 수

Django에선 인증 및 권한에 대해 위의 3가지 사항으로 처리하고 있다. 위 코드는 각 요청에 IsAuthenticated에 해당하며 인증된 요청에 한해서 해당 뷰 호출을 허용한다(로그인이 되어 있어야만 접근 허용)

DRF의 인증처리

: 현재 요청에 대한 허용/거부를 결정, APIView 단위로 지정이 가능하다.

AllowAny (디폴트 전역 설정) : 인증 여부에 상관없이 뷰 호출을 허용
IsAuthenticated : 인증된 요청에 한해서 뷰 호출 허용 (로그인이 되어있어야만 접근 허용)
IsAdminUser : Staff 인증 요청에 한해서 뷰 호출 허용
IsAuthenticatedOrReadOnly : 비인증 요청에게는 읽기 권한만 허용 (로그인이 되어 있지않아도 조회는 가능)
DjangoModelPermissons : 인증된 요청에 한하여 뷰 호출 허용, 추가로 장고 모델단위 Permissions 체크
DjangoModelPermissionsOrAnonReadOnly : DjangoModelPermissions와 유사, 비인증 요청에게는 읽기만 허용
DjangoObjectPermissons : 비인증 요청은 거부, 인증된 요청은 Object에 대한 권한 체크 수행

인증 설정 방법

APIView를 상속받기 때문에 permission_classes 변수에 list 형태로 위에서 적용하고 싶은 객체를 선택해서 할당해주면된다. 또는 위에서처럼 CreateAPIView 가지고 있는 메서드인 get_permissions 함수를 오버라이드해서 커스터마이징 해서 사용한다.

DRF 인증 처리 참고

Part2. swagger 추가 설정 및 사용자 메서드 post 전반

swagger

  • operation_summary
    해당 필드를 통해 해당 api가 무슨 api인지 간략하게 설명
  • operation_description
    해당 api에 대한 추가 이해를 위해 사용
  • responses
    수동으로 시리얼라이저를 기입함으로써 정상 호출 시 받는 데이터 형태를 기입한다.

post

CreateAPIView 내부에 있는 post 메서드를 오버라이딩 하여 작성하였다.

"""
원함수
"""
    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)
"""
사용자함수
"""
    @transaction.atomic
    @swagger_auto_schema(
        operation_summary="summary",
        operation_description="content",
        responses={status.HTTP_200_OK: PartsRequestWithMatchSerializer},
    )
    def post(self, request, *args, **kwargs):
        data = request.data

        serializer = self.get_serializer(data=data)
        serializer.is_valid(raise_exception=True)

        created_type = data.get("created_type", None)
        parts_agent_id = None

        if created_type == RequestType.SELFCLAIM:
            parts_req_manager = PartsRequestManage()
            server_env = settings.SERVER_ENVIROMENT
            member = parts_req_manager.get_selfclaim_created_by(server_env=server_env)
            parts_agent_id = parts_req_manager.get_selfclaim_agent_parts(
                server_env=server_env
            ).agent_id

        else:
            member = request.user

        serializer.validated_data["created_by"] = member
        serializer.validated_data["created_by_name"] = member.name

        parts_request = serializer.save()

        if "agent_parts" in data:
            parts_agent_id = data["agent_parts"]["id"]

요청한 유저가 셀프클레임일 때의 if 로직

if created_type == RequestType.SELFCLAIM:
            parts_req_manager = PartsRequestManage()
            server_env = settings.SERVER_ENVIROMENT
            member = parts_req_manager.get_selfclaim_created_by(server_env=server_env)
            parts_agent_id = parts_req_manager.get_selfclaim_agent_parts(
                server_env=server_env
            ).agent_id
else:
    member = request.user

serializer.validated_data["created_by"] = member
serializer.validated_data["created_by_name"] = member.name

parts_request = serializer.save()

PartsRequestManage 해당 객체는 접수된 요청 사항과 관련한 여러개의 메서드를 갖고 있다. 부품사 매칭상태변환 공업사 매칭상태변환 부품 배송완료시 설정 부품 요청업체변경 등등 즉 접수된 요청과 관련한 매니징을 하고 싶을 때 해당 객체를 불러와 사용한다.

get_selfclaim_created_by 메서드와 get_selfclaim_agent_parts 메서드는 셀프클레임이 들어왔을 때 local, dev, prod 환경에 따라 요청한 서비스에 대해 요청자의 member 모델부품사의 아이디를 지정해주는 작업이다.

member 모델의 경우 관리자 아이디를 가지고 오고 부품사의 경우 해당하는 부품사를 찾아 부품사 아이디를 가지고 온다.

셀프 클레임이 아닌 요청에 대해선 그냥 요청자의 정보를 담은 member 모델을 반환한다.

이후 시리얼라이저에서 필드에 접근해 값을 할당하는 과정을 거친다. 그 전에 이 serializer 를 불러들이는 코드를 볼 필요가 있다.

serializer 들고오기 편

serializer를 들고 올 때 아래와 같이 들고온다. 여기서 get_serializer_classget_serializer 의 차이를 알면 유용하다.

serializer = self.get_serializer(data=data)

get_serializer_class

전자는 해당 메서드를 가진 객체 내부에 serializer_class 변수에 값이 할당되어 있을 때 serializer_class를 반환한다.

    def get_serializer_class(self):
        """
        Return the class to use for the serializer.
        Defaults to using `self.serializer_class`.

        You may want to override this if you need to provide different
        serializations depending on the incoming request.

        (Eg. admins get full serialization, others get basic serialization)
        """
        assert self.serializer_class is not None, (
            "'%s' should either include a `serializer_class` attribute, "
            "or override the `get_serializer_class()` method."
            % self.__class__.__name__
        )

        return self.serializer_class

get_serializer

후자는 해당 객체가 serializer_class시리얼라이저가 할당되어 있다는 전제하에 해당 매개변수 kwargsget_serializer_context의 딕셔너리 형태의 반환 값을 붙인 kwargsserializer_class 객체의 인자로 넣어 인스턴스화 한 결과를 반환한다.

    def get_serializer(self, *args, **kwargs):
        """
        Return the serializer instance that should be used for validating and
        deserializing input, and for serializing output.
        """
        serializer_class = self.get_serializer_class()
        kwargs.setdefault('context', self.get_serializer_context())
        return serializer_class(*args, **kwargs)

get_serializer_context

    def get_serializer_context(self):
        """
        Extra context provided to the serializer class.
        """
        return {
            'request': self.request,
            'format': self.format_kwarg,
            'view': self
        }

serializer 정리

  • serializer_class에 CreatePartsRequestSerialize 를 지정한다.

  • 프론트에서 보낸 데이터에 get_serializer 메서드를 통해 key값을 contextvalueget_serializer_context메서드 반환값으로 한 딕셔너리 형태의 데이터를 추가한 kwargs(딕셔너리 형태의 데이터)를 serializer_class인 CreatePartsRequestSerialize 에 인자로 전달한 인스턴스를 반환한다.

  • is_valid 메서드를 통해 유효성 검사를 할 때 raise_exception=True 를 is_valid 뒤에 추가해 주면 raise된 에러를 클라이언트에 전달한다.

raise_exception=True 참고 사이트

post 흐름 요약

  1. 사용자가 요청한 데이터에 접근해서 직렬화 시킨 후 유효성 검사를 실시한다.
  2. 사용자가 요청한 요청건에 관련한 값을 가져온다. [ REQUEST, CANCELED, GIVEUP, SEND_ESTIMATE, CONFIRM_ORDER, COMPLETE_DELIVERY, VEHICLE_RELEASE ] 중에서
  3. 사용자의 요청이 셀프클레임인 경우 필요한 로직을 수행하고 셀프클레임이 아닌 경우 요청한 사용자의 정보를 가져와 member 변수에 담는다.
  4. 유효성 검사를 실시한 시리얼라이저의 필드에 접근해 사용자의 정보와 사용자의 이름을 각각의 변수에 할당한 후 저장한다.
  5. 사용자가 요청한 정보에 agent_parts 필드가 있으면 해당 값의 아이디를 추출해 해당 변수에 담는다.

serializer 객체 접근하는 법
request.user 의 정체

Part3. 사용자 메서드 post 후반

        # 매칭 생성
        requestPartsMatch = RequestPartsMatch()
        match = requestPartsMatch.requestPartsMatch(
            partsRequest=parts_request, member=member, parts_agent_id=parts_agent_id
        )
        notiLogic = PartsRequestNotiLogic()
        notiLogic.publishMatchSuccess(match=match)
        if created_type == RequestType.SELFCLAIM:
            booking_date = parts_request.booking_date.strftime("%Y년%m월%d일%H시%M분")

            data = {
                "name": parts_request.booking_owner_name,
                "phone": parts_request.booking_owner_phone,
                "date": booking_date[:11],
                "time": booking_date[11:],
                "car_number": parts_request.car_number,
                "agent_name": parts_request.req_agent.name,
                "agent_location": parts_request.req_agent.agent_carcenter.address,
            }
            coolsms_helper = CoolSmsHelper.reserve_complete(data)
            logging.info(coolsms_helper)

        send_not_confirm_match.apply_async(args=[match.id], countdown=60 * 30)

        parts_req_serializer = PartsRequestWithMatchSerializer(parts_request)

        return Response(parts_req_serializer.data, status=status.HTTP_200_OK)

RequestPartsMatch

RequestPartsMatch 객체는 서비스 접수 후 (보험사, 공업사, 부품사) 안에서 3자 또는 2자간 매칭이 되었을 때 필요한 로직들을 수행하는 메서드를 들고있는 객체이다. 메서드의 경우 필요한 값을 해당 객체에 할당하는 작업이다.

해당 객체의 인스턴스를 통해 매칭에 필요한 메서드를 수행하고 PartsBrokMatch 객체를 반환한다. 해당 객체는 접수받은 요청과 관련해서 전반적인 데이터를 제공하는 모델이다.

PartsRequestNotiLogic

PartsRequestNotiLogic 객체는 서비스 접수 후 3자 또는 2자가 연결되었을 때 서비스에 참여한 당사자에게 메세지를 보내는 메서드를 갖고 있는 객체이다.

publishMatchSuccess 메서드가 매개변수로 PartsBrokMatch 객체를 전달 받기에 아래 requestPartsMatch 메서드 반환 값의 데이터 타입은 PartsBrokMatch 이다.

match = requestPartsMatch.requestPartsMatch(
    partsRequest=parts_request, member=member, 
    parts_agent_id=parts_agent_id
    )
    
notiLogic = PartsRequestNotiLogic()
notiLogic.publishMatchSuccess(match=match)

CoolSmsHelper.reserve_complete

CoolSmsHelper 객체의 경우 아래의 외부 모듈을 들고와서 외부 api를 사용한다.

from sdk.api.message import Message
from coolsms.src.lib import message

reserve_complete 해당 메서드는 해당 api를 이용하기 위해 data를 규격에 맞게 변환해 주는 메서드이고 데이터를 담은 객체를 반환해서 서버에 로그가 찍히도록 하는 역할을 수행한다.

이로서 서버에 로그가 찍히게 하는 것이 중요하다는 것을 알 수 있다.

coolsms_helper = CoolSmsHelper.reserve_complete(data)
logging.info(coolsms_helper)

send_not_confirm_match.apply_async

RabbitMQ와 Celery를 활용하여 만일 주문확정이 30분 동안 되지 않았을 경우 관리자에게 요청이 되지 않았다고 메세지가 보내지고 서버에 주문 건이 확정되지 않았다고 로그가 기록된다.

send_not_confirm_match.apply_async(args=[match.id], countdown=60 * 30)

0개의 댓글