본문 바로가기
부트캠프TIL, WIL/AI웹개발(스파르타코딩클럽)

[AI웹개발][47일차TIL] FBV 프로젝트와, CBV 프로젝트 비교

by 우지uz 2023. 5. 18.
FBV에서 urls.py
urlpatterns = [
    path('api/posting/', views.posting_view, name='posting'),
]
CBV에서 urls.py
urlpatterns = [
    path('signup/', views.SignupView.as_view(), name='sign_up_view'), # /users/signup/
]

CBV에서는 SignupView 자체가 함수가 될 수 없기 때문에, as_view()라는 것을 문법처럼 붙여준다. 

as_view()라는 함수는 어떤 함수일까 ??

@classmethod
def as_view(cls, **initkwargs):
    """
    as_view 함수는 클래스 기반 뷰를 함수로 변환하고, 원래 클래스 정보를 뷰 함수에 저장하는 역할을 합니다.
    """
    이게 중요한 로직인데, 인스턴스가 맞
    if isinstance(getattr(cls, 'queryset', None), models.query.QuerySet):
        def force_evaluation():
            raise RuntimeError(
                'Do not evaluate the `.queryset` attribute directly, '
                'as the result will be cached and reused between requests. '
                'Use `.all()` or call `.get_queryset()` instead.'
            )
        cls.queryset._fetch_all = force_evaluation

    view = super().as_view(**initkwargs)
    view.cls = cls
    view.initkwargs = initkwargs

    # Note: session based authentication is explicitly CSRF validated,
    # all other authentication is CSRF exempt.
    return csrf_exempt(view)

 

Class based View

# ====================== 프로필 상세보기 ================================
class ProfileView(APIView):
    def get_object(self, user_id):
        return get_object_or_404(Users, id=user_id)

    # 프로필 상세보기, 권한이 없어도 됨.
    def get(self, request, user_id):
        user = self.get_object(user_id)
        serializer = UserProfileSerializer(user)
        print(serializer.data)
        print(request.data)
        return Response(serializer.data, status=status.HTTP_200_OK)

    # 프로필 수정, 권한이 있어야함.
    def patch(self, request, user_id):
        user = self.get_object(user_id)
        if user == request.user:
            serializer = UserProfileSerializer(
                user, data=request.data, partial=True)
            if serializer.is_valid():
                serializer.save()
                return Response({"message": "수정완료!"}, status=status.HTTP_200_OK)
            else:
                return Response({"message": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
        else:
            return Response({"message": "권한이 없습니다!"}, status=status.HTTP_403_FORBIDDEN)
# 이미지 업로드, 교체 가능, 삭제는 없음.

Function based View

def posting_view(request):
    if request.method == 'GET':
        user = request.user.is_authenticated
        if user:
            return render(request, 'posting/posting.html')
        else:
            return redirect('/api/sign-in')

    elif request.method == 'POST':
        posting_user = request.user
        author = posting_user
        title = request.POST.get('title', '')
        thumbnail = request.POST.get('thumbnail', '')
        content = request.POST.get('content', '')

        if title == '':
            return render(request, 'posting/posting.html')
        elif content == '':
            return render(request, 'posting/posting.html')
        elif thumbnail == '':
            thumbnail = 'https://velog.velcdn.com/images/e_elin/post/393c51bc-9fef-48a8-ae11-f47bb3e57bbc/image.png'
            posting_ = PostingModel.objects.create(
                author=author, title=title, thumbnail=thumbnail, content=content)
            return redirect('/api/posting-detail/' + str(posting_.id))
        else:
            posting_ = PostingModel.objects.create(
                author=author, title=title, thumbnail=thumbnail, content=content)
            return redirect('/api/posting-detail/' + str(posting_.id))

FBV를 이용하면, 간단하거나 단순한 작업들에 대해서는 직관적으로 표현할 수 있지만
함수마다 HTTP Method가 get일때, post일때, patch일때, delete일때 등등 
로직을 구성해서 표현해야 하기 때문에 
코드를 재사용하지 못하고, 모든 함수마다 로직을 일일이 구현해야 하는 아쉬움이 있다. 

그러나 ,CBV를 이용하면 코드의 재사용이 가능하고
이미 Django에서 제공되는, 내장되어 있는 클래스들이 있는데

예를 들어서 APIView를 상속받아서 클래스를 만들게 되면, 

class APIView(View):
# The following policies may be set at either globally, or per-view.
    renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
    parser_classes = api_settings.DEFAULT_PARSER_CLASSES
    authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
    throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
    permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
    content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
    metadata_class = api_settings.DEFAULT_METADATA_CLASS
    versioning_class = api_settings.DEFAULT_VERSIONING_CLASS

    # Allow dependency injection of other settings to make testing easier.
    settings = api_settings

    schema = DefaultSchema()
    
    @classmethod
    def as_view(cls, **initkwargs):
        
        if isinstance(getattr(cls, 'queryset', None), models.query.QuerySet):
            def force_evaluation():
                raise RuntimeError(
                    'Do not evaluate the `.queryset` attribute directly, '
                    'as the result will be cached and reused between requests. '
                    'Use `.all()` or call `.get_queryset()` instead.'
                )
            cls.queryset._fetch_all = force_evaluation

        view = super().as_view(**initkwargs)
        view.cls = cls
        view.initkwargs = initkwargs

        return csrf_exempt(view)

    @property
    def allowed_methods(self):
        
        return self._allowed_methods()

    @property
    def default_response_headers(self):
        headers = {
            'Allow': ', '.join(self.allowed_methods),
        }
        if len(self.renderer_classes) > 1:
            headers['Vary'] = 'Accept'
        return headers

    def http_method_not_allowed(self, request, *args, **kwargs):
        
        raise exceptions.MethodNotAllowed(request.method)

    def permission_denied(self, request, message=None, code=None):
        
        if request.authenticators and not request.successful_authenticator:
            raise exceptions.NotAuthenticated()
        raise exceptions.PermissionDenied(detail=message, code=code)

APIView라는 기본 클래스를 상속 받게 되면

HTTP 요청 메서드들 - get, post, put, patch, delete등등을 기본적으로 제공하고
request, response 객체 자체를 기본적으로 처리해주고, 데이터를 처리하거나 응답을 생성하고 수정할 수 있다.

이외에 인증, 권한 검사(다양한 그룹들에 대해 권한을 다 다르게 적용하는..)
Serializer 및 DeSerializer 지원
에러처리 및 상속 및 오버라이딩으로 코드 유지보수에 유리하게 됩니다. 

 

음 ... 결론적으로는

FBV보다는 CBV를 사용하는 이유가 
Django Rest Framework 및 Django 자체적으로 제공하는 기본 클래스와 기능들이
CBV라서가 아닐까 생각됩니다.

지금까지 팀 프로젝트를 진행한 것들을 보면
장고와 DRF에서 제공하는 Class를 상속받지 않고, 
스스로 Class를 정의 내린 적이 없던 것 같습니다 ㅎㅎ

user면 user관련 클래스를 상속받았고
Article이면 관련 클래스를
댓글이면 댓글 관련 ... 

장고에서 기본적으로 제공하는 클래스와 기능들 때문이라
더더욱 그렇게 느껴지게 되었습니다 !