본문 바로가기

개발일지/TIL

TIL 23-05-20 django - urls re_path사용하기, view 통합하기

1. django - urls re_path사용하기, view 통합하기 

 문제점

사용하지 않는 매개변수를 전달받는 문제

urls.py

path(
        "<int:article_id>/comments/<int:comment_id>/",
        views.CommentDetailView.as_view(),
        name="comment_view",
    ),

urls를 통해서 article_id와 comment_id를 view로 전달하는 중 

 

views.py

class CommentDetailView(APIView):
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]

    def put(self, request, article_id, comment_id):
        comment = Comment.objects.get(pk=comment_id)
        if request.user == comment.user:
            serializer = CommentCreateSerializer(comment, data=request.data)
            if serializer.is_valid():
                serializer.save()
                return Response(serializer.data, status=status.HTTP_200_OK)
            else:
                return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
        else:
            return Response("권한이 없습니다!", status=status.HTTP_403_FORBIDDEN)

사용하지 않지만 article_id라는 변수를 입력받고 있는 view존재 

 

urls.py

path(
        "<int:article_id>/comments/", views.CommentView.as_view(), name="comment_view"
    ),

 

views.py

class CommentView(APIView):
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]
    
    def post(self, request, article_id):
        serializer = CommentCreateSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save(
                user=request.user, article=Article.objects.get(pk=article_id)
            )
            return Response(serializer.data, status=status.HTTP_200_OK)
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

위의 view와 다르게 article_id를 사용하지만 article_id를 이용하지 않아도 가능할 것으로 보임 

 시도해 본 것들

CommentView에서 article_id를 대체하기 

최초 작성 시에는 article_id를 comment에서 알 수 없으므로 request에서 찾아보기 

dir(request)와 공식문서 찾아보기 

For example, if the WSGIScriptAlias for your application is set to "/minfo", 
then path might be "/minfo/music/bands/the_beatles/" and 
path_info would be "/music/bands/the_beatles/".

path와 path_info에 대한 차이를 설명해 줬고, path_info는 urls에서 추가로 붙는 경로만 보여주는 것으로 이해했으나 직접 찍어보니 차이점을 모르겠다. 

print(request.path_info) # /users/profile/1/
print(request.path) # /users/profile/1/
print(request.get_full_path_info()) # /users/profile/1/
print(request.get_full_path()) # /users/profile/1/

내가 확인한 urls에서는 똑같은 결과를 보여줘서 각각의 정확한 사용법을 잘 모르겠다. 

stream

print(request.stream) <WSGIRequest: POST '/2/comments/'>
print(request.stream) None

url 경로를 포함하면서 조금 다른 형식을 띠었다. 

POST인 댓글 작성은 뭔가 가져오고, GET인 user정보 조회는 None이 돌아왔다.

그리고 공식문서에서 정보 찾는 것을 실패함 drf 공식문서였다면 찾았을 텐데 django 공식문서는 어렵다.

 

path로 article_id 대체하기 

기존 코드 

class CommentView(APIView):
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]

    def post(self, request, article_id):
        serializer = CommentCreateSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save(
                user=request.user, article=Article.objects.get(pk=article_id)
            )
            return Response(serializer.data, status=status.HTTP_200_OK)
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

 

class CommentView(APIView):
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]

    def post(self, request, article_id):
        serializer = CommentCreateSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save(
                user=request.user,
                article=Article.objects.get(pk=request.path.split("/")[1]),
            )
            return Response(serializer.data, status=status.HTTP_200_OK)
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

split()을 이용해서 path에서 필요한 값만 넣어주자 정상적으로 댓글 작성 성공 

하지만, 전달받는 매개변수에서 article_id 제거 시 에러 발생

TypeError: CommentView.post() got an unexpected keyword argument 'article_id'

view에서는 입력받지 않고 있지만, urls에서 view로 요청을 보내면서 <int:article_id>가 view로 변수를 전달 중 

 

Urls에서 <int:article_id>제거하기

정규표현식 사용

path(r"\d/comments/", views.CommentView.as_view(), name="comment_view"),

<int:article_id>가 view로 전달하는 기능을 가지고 있으므로 정규 표현식에서 숫자에 해당하는 \d사용 404 에러 발생

path( r"[0-9]+/comments/", views.CommentView.as_view(), name="comment_view"),

0~9까지의 수를 1번 이상 반복을 뜻하는 정규표현식 사용 404 에러 발생 

 

re_path사용 

https://docs.djangoproject.com/en/4.2/ref/urls/#re-path

 

Django

The web framework for perfectionists with deadlines.

docs.djangoproject.com

 

re_path(r"^bio/(?P<username>\w+)/$", views.bio, name="bio")
(?P<username>\w+)

이렇게 변수명을 지정하면 username이라는 인자로 view에 전달이 되고 아니라면 positional arguments로 사용된다고 하는데 아직도 잘 모르겠다. 

re_path 자체는 정규표현식과 일치하는 url을 연결된 view로 전달하는 역할을 한다고 하니 <int:article_id>를 대체할 수 있을 것 같다.

 

re_path(
        r"[0-9]+/comments/",
        views.CommentView.as_view(),
        name="comment_view",
    ),

re_path()를 통해서 0~9까지의 수가 1번 이상 반복되는/comments/를 CommentView로 전달하기 

 

view 통합하기 

re_path(
        r"[0-9]+/comments/(?P<comment_id>[0-9]+)/",
        views.CommentView.as_view(),
        name="comment_view",
    ),

comment_id가 필요한 수정, 삭제는 (? P)로 정규표현식을 사용하고 [0-9]+이라는 조건에 맞으면 <comment_id>라는 이름으로 view로 전달 

하지만 

CommentView.put() missing 1 required positional argument: 'comment_id'

에러 발생 

 

댓글 작성 연결 re_path 주석 처리 

# re_path(
    #     r"[0-9]+/comments/",
    #     views.CommentView.as_view(),
    #     name="comment_view",
    # ),
    re_path(
        r"[0-9]+/comments/(?P<comment_id>[0-9]+)/",
        views.CommentView.as_view(),
        name="comment_view",
    ),

정상 작동 확인

혹시나 했던 위에서 comment_id가 없는 re_path로 빠져서 발생하는 에러였다. 

 

시작과 끝에 ^,$ 추가 

re_path(
        r"^[0-9]+/comments/$",
        views.CommentView.as_view(),
        name="comment_view",
    ),
re_path(
        r"^[0-9]+/comments/(?P<comment_id>[0-9]+)/$",
        views.CommentView.as_view(),
        name="comment_view",
    ),

re_path 작동 확인

알지 못하는 걸 빼고 쓰면서 누락된 ^,$를 추가하며 정규표현식의 시작과 끝을 지정했다.

위의 re_path에서 $로 닫아주지 않아서 앞의 정규표현식/comments/로 이루어진 url이 모두 위쪽을 통하고 있었다. 

 해결 방법

article_id 대신 request.path.split("/")[1] 사용하기 
<int:article_id> 대신 re_path(r"[0-9]+/") 사용하기

 

urls.py

    re_path(
        r"^[0-9]+/comments/$",
        views.CommentView.as_view(),
        name="comment_view",
    ),
    re_path(
        r"^[0-9]+/comments/(?P<comment_id>[0-9]+)/$",
        views.CommentView.as_view(),
        name="comment_view",
    ),

 

views.py

class CommentView(APIView):
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]

    def post(self, request):
        serializer = CommentCreateSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save(
                user=request.user,
                article=Article.objects.get(pk=request.path.split("/")[1]),
            )
            return Response(serializer.data, status=status.HTTP_200_OK)
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def put(self, request, comment_id):
        comment = Comment.objects.get(pk=comment_id)
        if request.user == comment.user:
            serializer = CommentCreateSerializer(comment, data=request.data)
            if serializer.is_valid():
                serializer.save()
                return Response(serializer.data, status=status.HTTP_200_OK)
            else:
                return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
        else:
            return Response("권한이 없습니다!", status=status.HTTP_403_FORBIDDEN)

    def delete(self, request, comment_id):
        comment = get_object_or_404(Comment, pk=comment_id)
        if request.user == comment.user:
            comment.delete()
            return Response("삭제완료", status=status.HTTP_204_NO_CONTENT)
        else:
            return Response("권한이 없습니다!", status=status.HTTP_403_FORBIDDEN)

기존에 분리됐던 view를 하나로 합치고 필요 없는 매개변수까지 제거할 수 있었다. 

comment_id까지 없어도 된다면 urls도 줄겠지만 그건 가능한지 잘 모르겠다. 

 알게 된 점

공식문서에서 정보를 찾는다는 게 처음 읽을 때는 정말 어렵다는 것을 알게 됐다. 정보가 있는 페이지를 찾아가는 것도, 페이지를 찾고서 정보를 찾는 것도 상당한 시간이 소요됐다. 

강의나 가공된 참고자료 없이 원하는 결과를 찾을 수 있었다.