[django] 코드 단축 with decorator

Hyeseong·2020년 12월 13일
0

django

목록 보기
7/35

목적

코드 가독성을 높이고 불핀요한 볼륨을 줄여 소스 코드 개선을 위해서는 데코레이터는 필수에 가깝습니다.
여기서는 Update, Delete 화면에 해당하는 유저 프로필 수정과, 삭제에 decorator를 입혀 그 효용을 알아 볼게요.

우선 최종 소스 코드 결과

# import한 클래스와 메소드는 생략 
...
...
...

has_ownership = [account_ownership_required, login_required]

@login_required
def hello_world(request):

    if request.method == 'POST':

        temp = request.POST.get('hello_world_input')

        new_hello_world = HelloWorld()
        new_hello_world.text = temp
        new_hello_world.save()

        return HttpResponseRedirect(reverse('accountapp:hello_world'))
    else:
        hello_world_list = HelloWorld.objects.all()
        return render(request, 'accountapp/helloworld.html', context={'hello_world_list':hello_world_list})


class AccountCreateView(CreateView):
    model = User
    form_class = UserCreationForm

    success_url = reverse_lazy('accountapp:hello_world')
    template_name = 'accountapp/create.html'

@method_decorator(login_required, name='get')
class AccountDetailView(DetailView):
    model = User
    context_object_name = 'target_user'
    template_name = 'accountapp/detail.html'

@method_decorator(has_ownership, name='get')
@method_decorator(has_ownership, name='post')
class AccountUpdateView(UpdateView):
    model = User
    context_object_name = 'target_user'
    form_class = AccountUpdateForm
    success_url = reverse_lazy('accountapp:hello_world')
    template_name = 'accountapp/update.html'


@method_decorator(has_ownership, name='get')
@method_decorator(has_ownership, name='post')
class AccountDeleteView(DeleteView):
    model = User
    context_object_name = 'target_user'
    success_url = reverse_lazy('accountapp:login')
    template_name = 'accountapp/delete.html'


Step by Step

하나. 일반 함수 데코레이터

기존에 우리가 함수 내부에 작성했던 if문과 else문 3줄을 단 한줄 @login_required로 축약하여 표현할 수 있어요.

   if request.user.is_authenticated:
	...
    	...
    else:
        return HttpResponseRedirect(reverse('accountapp:login'))
from django.contrib.auth.decorators import login_required


@login_required
def hello_world():
	
    if request.mothod == 'POST':
    ...
    ...

하지만 여기서는 우리가 클래스 안의 함수가 아닌 밖에 함수에 적용했는데요.

다른 AccountUpdateView, AccountUpdateView는 클래스인데요. 똑같이 머리위에 데코레이터인 @login_required를 적용 할 수 없어요.

둘. @method_decorator(?,name=?)

클래스명 위에 얹을 데코리이터인 @method_decorator(?,name=?)를 사용하면 되요.
그런데 데코레이터가 두개조? get 요청과 post 방식의 요청으로 들어올때 UpdateView의 부모 클래스에서 정의된
get, post 메서드를 지정해서 활용한다는거에요. 그래서 키워드 인자로 둔거고요.


@method_decorator(login_required, name='get')
@method_decorator(login_required, name='post')
class AccountUpdateView(UpdateView):
    model = User
    context_object_name = 'target_user'
    form_class = AccountUpdateForm
    success_url = reverse_lazy('accountapp:hello_world')
    template_name = 'accountapp/update.html'

셋, 내가 만든 데코레이터 적용시키기(customizing)

accountapp/decorators.py라는 파일을 생성하도록 할게요.
데코레이터 적용전에는 get 메소드의 if,else로 소스코드를 작성했조?

그중 우리가 가져올 핵심이자 아직 구현되지 않은 self.get_object() == self.request.user:`입니다.

데코 적용 전

   def get(self, *args, **kwargs):
        if self.request.user.is_authenticated and self.get_object() == self.request.user:
            return super().get(*args, **kwargs)
        else:
            return HttpResponseForbidden()
            
   def post(self, *args, **kwargs):
        if self.request.user.is_authenticated and self.get_object() == self.request.user:
            return super().post(*args, **kwargs)
        else:
            return HttpResponseForbidden()

class안에 get(), post() 사용한것 보다 훨씬더 가벼워진 모습이조?

@method_decorator(login_required, name='get')
@method_decorator(login_required, name='post')
@method_decorator(account_ownership_required, name='get')
@method_decorator(account_ownership_required, name='post')
class AccountUpdateView(UpdateView):
    model = User
    context_object_name = 'target_user'
    form_class = AccountUpdateForm
    success_url = reverse_lazy('accountapp:hello_world')
    template_name = 'accountapp/update.html'

accountapp/decorators.py account_ownership메소드는 우리가 커스터마이징한 데코레이터에요.
기존 User 모델에서 get()메서드로 pk키워드 인자를 지정해서 db에서 해당 pk값에 대응하는 데이터를 가져와요.
그리고 if문으로 클라이언트가 보낸 값과 db에서 보낸 값을 비교해서 값이 일치하지 않으면 HttpResponseForbidden()클래스를 호출하고
반대로 else문인 일치하게 되면 정상적으로 진행되요.

from django.contrib.auth.models import User
from django.http import HttpResponseForbidden


def account_ownership_required(func):
    def decorated(request, *args, **kwargs):
        user = User.objects.get(pk=kwargs['pk']) # db에서 가져온 user
        if not user == request.user: # 우항은 클라이언트가 입력한 user값
            return HttpResponseForbidden()
        return func(request, *args, **kwargs)
    return decorated

그래도 현재 view에서 정의한 데코레이터가 한 클래스에 4개나 붙어 있는데요. 다시 소스코드를 효율적으로 줄일 수 있어요.

넷, 리스트 변수 전달

데코레이터 변수를 담은 리스트를 method_decorator의 첫 번째 인자로 전달해서 순차적으로 실행시키게 하는거에요.
실행순서는 위에서부터 아래로 되요.

그럼 우리가 적용하고자 하는 AccountUpdateView, AccountDeleteView에 이렇게 2개씩 4개를 쓰면 되겠조?

# 생략

has_ownership = [account_ownership_required, login_required]

# 생략

@method_decorator(has_ownership, name='get')
@method_decorator(has_ownership, name='post')
class AccountUpdateView(UpdateView):
    model = User
    context_object_name = 'target_user'
    form_class = AccountUpdateForm
    success_url = reverse_lazy('accountapp:hello_world')
    template_name = 'accountapp/update.html'
profile
어제보다 오늘 그리고 오늘 보다 내일...

0개의 댓글