[Django] 데코레이터 사용하기

sudog·2023년 9월 10일
1

Django

목록 보기
8/13

장고의 뷰 함수를 작성하다 보면 공통적으로 사용되어야 하는 기능들이 있다. 한 예시로, 특정 기능들은 보안이나 권한에 대한 검증이 항상 필요할 수 있다.

게시판을 만들었다고 생각해보자. 게시판의 글을 보는것은 로그아웃된 상태에서도 가능하지만 게시판에 글을 쓰거나 추천을 하고, 자신의 글을 수정, 삭제하는 것은 항상 로그인된 상태에서만 가능해야 한다. 다음 코드를 보자.

# post/views.py

def detail_view(request, id):
    if request.method == "GET":
        post = get_object_or_404(PostModel, id=id)
        comments = post.comment.annotate(count_likes=Count("likes")).order_by(
            "-count_likes", "-created_at"
        )
        return render(request, "post/detail.html", {"post": post, "comments": comments})
    else:
        return HttpResponseNotAllowed(["GET"])
        

def create_view(request):
    if request.method == "GET":
    	if request.user.is_authenticated:
        	return render(request, "post/form.html")
        else:
        	return redirect(reverse("accounts:sign-in"))
    elif request.method == "POST":
    	if not request.user.is_authenticated:
        	return redirect(reverse("accounts:sign-in"))
        ...
        return redirect(reverse("post:home"))
    else:
        return HttpResponseNotAllowed(["GET", "POST"])
        

def update_view(request, id):
    if request.method == "GET":
    	if not request.user.is_authenticated:
        	return redirect(reverse("accounts:sign-in"))
        post = get_object_or_404(PostModel, id=id)
        if post.author == request.user:
            return render(request, "post/form.html", {"post": post})
        else:
            return redirect(reverse("post:detail", args=[id]))
    elif request.method == "POST":
    	if not request.user.is_authenticated:
        	return redirect(reverse("accounts:sign-in"))
        ...
        return redirect(reverse("post:detail", args=[id]))
    else:
        return HttpResponseNotAllowed(["GET", "POST"])

위 코드를 보면 detail_view함수는 상관이 없지만 create_view, update_view는 매번 유저의 인증을 검증하고 로그인된 사용자가 아니라면 sign-in페이지로 리다이렉트하는 코드를 구현해야 한다.

이러한 방식은 가독성도 떨어지고 코드의 중복이 빈번해서 권장되지 않는다. 그래서 장고에서는 이런 상황을 위한 데코레이터를 제공하는데, @login_required는 로그인된 사용자만 해당 뷰 함수에 접근 가능한 기능을 추가해 준다. 이것을 적용하여 위 코드를 리팩토링해 보겠다.

# post/views.py
...
from django.contrib.auth.decorators import login_required

def detail_view(request, id):
    if request.method == "GET":
        post = get_object_or_404(PostModel, id=id)
        comments = post.comment.annotate(count_likes=Count("likes")).order_by(
            "-count_likes", "-created_at"
        )
        return render(request, "post/detail.html", {"post": post, "comments": comments})
    else:
        return HttpResponseNotAllowed(["GET"])
        

@login_required
def create_view(request):
    if request.method == "GET":
    	return redirect(reverse("accounts:sign-in"))
    elif request.method == "POST":
        ...
        return redirect(reverse("post:home"))
    else:
        return HttpResponseNotAllowed(["GET", "POST"])
        

@login_required
def update_view(request, id):
    if request.method == "GET":
        post = get_object_or_404(PostModel, id=id)
        if post.author == request.user:
            return render(request, "post/form.html", {"post": post})
        else:
            return redirect(reverse("post:detail", args=[id]))
    elif request.method == "POST":
        ...
        return redirect(reverse("post:detail", args=[id]))
    else:
        return HttpResponseNotAllowed(["GET", "POST"])

이전 코드에 비해 요구사항과 제공하는 기능이 분리되어 훨씬 명확하게 함수의 역할을 파악할 수 있다. 이제 @login_required 데코레이터가 붙어있는 함수는 모두 로그인이 필요한 기능이라는 것을 알리면서 내부 구현은 제공하는 기능에만 집중하면 되는 것이다.

그럼 @login_required 데코레이터는 로그인되지 않은 사용자의 요청이 들어왔을 때 어떻게 처리할까? 우리가 이전에 구현한 것처럼, 특정 URL로 리다이렉트한다. 이 URL은 기본적으로 LOGIN_URL = "/accounts/login/"로 세팅되어 있다. 따라서 다른 URL로 변경하고 싶다면 settings.py파일에 명시해 주어야 한다.

# settings.py

# login 대신 signin을 사용할 경우
LOGIN_URL = "accounts:signin"
profile
안녕하세요

1개의 댓글

comment-user-thumbnail
2023년 9월 11일

데코레이터를 사용하면 좀 더 편리하게 관리할 수가 있겠네요~!

답글 달기