Skip to content

Latest commit

 

History

History
286 lines (218 loc) · 9.49 KB

08.md

File metadata and controls

286 lines (218 loc) · 9.49 KB

8. Function- And Class-Based Views

8.1 When to Use FBVs or CBVs

flowchart TD
    A[Start Here] --> B[generic class-based views 중 <br /> 하나가 원하는 view와 정확히 맞는다.]
    B -- Yes --> C[class-based view의 attribute를 <br /> 상속하는 것만으로도 view를 만들어낼 수 있다.]
    C -- Yes ---> D{class-based view를 써라!}
    B -- No ---> E[다른 view를 만들기 위해서 <br /> 나의 view를 클래스로 상속할 필요가 있다.]
    E -- No --> G
    E -- Yes --> F[class-based view를 사용하기 위해 <br /> Django 소스 코드를 까봐야한다.]
    F -- No ---> D
    F -- Yes --> G[class-based view로 view를 <br /> 만드는 것이 정말정말로 힘들다. <br /> <br /> ex. view process가 하나의 form 이상을 요구할 때]
    G -- Yes --> H{정 그러면 <br /> function-based view를 써라!}
Loading

Important

대부분의 경우 CBV(Class-based view)를,
FBV(Function-based view)의 경우 custom 에러 view를 만들거나 CBV로 만들기에 복잡한 view를 만들 때 사용해라

8.2 Keep View Logic Out of URLConf

Requests route를 URLConfs로 구성할 수 있다. 해당 모듈은 보통 urls.py로 구성된다. URL을 구성하는 The rules of thumb(경험으로 얻어낸 원칙)은 다음과 같다.

  1. view module은 view logic만 포함해야한다.
  2. URL module은 URL logic만 포함해야한다.

Warning

Don't do this!

# Don't do this!
from django.urls import path
from django.views.generic import DetailView
from tastings.models import Tasting

urlpatterns = [
  path('<int:pk>',
      DetailView.as_view(
        model=Tasting,
        template_name='tastings/detail.html'),
      name='detail'),
  path('<int:pk>/results/',
      DetailView.as_view(
        model=Tasting,
        template_name='tastings/results.html'),
      name='results'),
]

위의 코드는 다음의 규칙들을 위반한다.

  • Loose coupling view와 url과 model이 tight하게 결합되어있다. 즉 view definition을 다시 재사용할 수 없게 만든다.
  • Don't Repeat Yourself CBV들끼리 비슷한 args가 반복되어 사용된다.
  • Infinite flexibility is destroyed: Class 상속이 CBV의 큰 장점인데, 이렇게 코드를 짜면 다시 사용하지 못하게 만든다.
  • 만약 위 view에서 authentication 과정을 추가한다면? 각 URLConf를 decorator로 일일히 감쌀껀가?

8.3 Stick to Loose Coupling in URLConf

다음과 같이 위의 코드를 전환할 수 있다.

tasting/views.py

from django.urls import reverse
from django.views.generic import ListView, DetailView, UpdateView

from .models import Tasting

class TasteListView(ListView):
  model = Tasting

class TasteDetailView(DetailView):
  model = Tasting

class TasteResultsView(TasteDetailView):
  template_name = 'tastings/results.html'

class TasteUpdateView(UpdateView):
  model = Tasting

  def get_success_url(self):
    return reverse('tastings:detail',
               kwargs={'pk': self.object.pk})

tasting/urls.py

from django.urls import path

from . import views

urlpatterns = [
   path(
     route='',
     view=views.TasteListView.as_view(),
     name='list'
  ),
  path(
    route='<int:pk>/',
    view=views.TasteDetailView.as_view(),
    name='detail'
  ),
  path(
     route='<int:pk>/results/',
     view=views.TasteResultsView.as_view(),
     name='results'
  ),
  path(
     route='<int:pk>/update/',
     view=views.TasteUpdateView.as_view(),
     name='update'
  )
]
  • Don't Repeat Yourself Args나 attribute가 view들끼리 공유되지 않는다.
  • Loose coupling model과 template name을 URLConf에서 제외했다. URLConf에서는 URL 관련한 로직만 적고, view에 대한 로직은 views.py에 적는다.
  • URLConfs should do one thing: "Routing" and do it well Routing에 대한 정보만 urls.py에 집중한다.
  • Class inheritance: Class 상속을 통해서 겹치는 코드, 로직을 편하게 작성할 수 있다.

8.4 Use URL Namespaces

URL Namespaces는 app-level, instance level에서의 식별자 역할을 한다. URL Namespace는 쓸모 없는 것처럼 보이지만 한번 써보면 왜 미리 쓰지 않았나라는 생각을 하게 된다. ㅋ

tastings_detail 대신에 tastings:detail으로 쓰면서 URL Namespace를 이용할 수 있다.

rootDir/urls.py

urlpatterns += [
    path('tastings/', include('tastings.urls', namespace='tastings')),
]

최상위 URLConf에서 다음과 같이 작성해줄 수 있다. URL namespace를 이용하면 이전의 views.py 코드를 다음과 같이 바꾸어줄 수 있다.

tastings/views.py

class TasteUpdateView(UpdateView):
  model = Tasting

def get_success_url(self):
  return reverse('tastings:detail',
               kwargs={'pk': self.object.pk})

HTML template에서도 다음과 같이 쓸 수 있다.

taste_list.html

{% extends 'base.html' %}

{% block title %}Tastings{% endblock title %}

{% block content %}
<ul>
  {% for taste in tastings %}
    <li>
      <a href="{% url 'tastings:detail' taste.pk %}">
        {{ taste.title }}
      </a>
      <small>
        (<a href="{% url 'tastings:update' taste.pk %}">update</a>)
      </small>
    </li>
  {% endfor %}
</ul>
{% endblock content %}

8.4.1 Makes for Shorter, More Intuitive, and Don’t Repeat Yourself URL Names

tastings_detail, tastings_results과 같이 app 이름이나 model 이름을 사용한 URL name을 사용하지 말고 detail, results와 같은 간단한 이름을 사용해라.

8.4.2 Increases Interoperability With Third-Party Libraries

<myapp>_detail과 같은 URL name이 가장 큰 문제가 될 때는 app 이름이 변경될 때이다. URL namespace가 이와 같은 문제를 편하게 해결할 수 있게 만들어준다.

8.4.3 Easier Searches, Upgrades, and Refactors

tastings_detail은 view, url이 될 수도 있지만 tastings:detail은 URL name 밖에 될 수 없어서 찾기가 쉽다. Refactoring, Upgrading에서도 app 이름에 대한 걱정 없이 진행할 수 있다.

8.4.4 Allows for More App and Template Reverse Tricks

  • django-debug-toolbar와 같은 Django debug 툴에 대해서도 잘동작한다.
  • namespace를 통해서 같은 코드베이스의 다른 개발자들도 편하게 app을 추가할 수 있게 만든다.

8.5 Try to Keep Business Logic Out of Views

과거에는 View에 많은 buisness logic을 추가했다. 불행하게도, PDF를 만들거나 REST API를 추가하는 과정에서 view에 너무 많은 코드가 들어가고 재사용되었다.

그래서 model method와 model manager, helper function을 사용해서 모델에 많은 코드를 적고 view에 로직을 줄여라 당연히 처음 view를 짤 때부터 이를 고려하기 쉽지 않지만, view에서 business logic이 한번 더이상 사용되는걸 발견한다면 이를 view에서 빼서 따로 code를 작성해라

8.6 Django Views Are Functions

Django view를 간단히 말하면 HTTP request object를 입력으로 받고 HTTP response object를 출력으로 내는 함수이다.

# Django FBV as a function
HttpResponse = view(HttpRequest)

# Deciphered into basic math (remember functions from algebra?)
y = f(x)

# ... and then translated into a CBV example
HttpResponse = View.as_view()(HttpRequest)

8.6.1 The Simplest Views

CBV와 FBV를 위의 개념에서 가장 간단한 형태로 작성하면 아래와 같이 작성해줄 수 있다.

simplest_views.py

from django.http import HttpResponse
from django.views.generic import View

# The simplest FBV
def simplest_view(request):
    # Business logic goes here
    return HttpResponse('FBV')

# The simplest CBV
class SimplestView(View):
    def get(self, request, *args, **kwargs):
        # Business logic goes here
        return HttpResponse('CBV')

위의 개념으로 view를 이해하는 것이 왜 중요한가?

  • 가장 간단한 Django view를 이해하는 것은 우리가 무엇을 실제로 하고 있는지를 더 잘 이해하는 것을 의미한다.
  • 왜 Django FBV와 HTTP method가 중립적인지, 왜 CBV가 특수한 HTTP method declaration을 요구하는지를 묘사해준다.

8.7 Don’t Use locals() as Views Context

locals()는 python class나 program 내에서 사용된 local variables의 이름과 값을 dict 형태로 return 해주는 함수이다. 이를 왠만하면, 절대로 쓰지 마라

Warning

Don't do this!

 # Don't do this!
def ice_cream_store_display(request, store_id):
    store = get_object_or_404(Store, id=store_id)
    date = timezone.now()
    return render(request, 'melted_ice_cream_report.html', locals())

다음 코드를 보면 storedate를 다시 적지 않고 template에서 사용할 수 있게 해주어 좋은 코드가 아닌가 싶다. 하지만 다음 코드가 아래와 같이 바뀐다면?

# Don't do this!
def ice_cream_store_display(request, store_id):
    store = get_object_or_404(Store, id=store_id)
    now = timezone.now()
    return render(request, 'melted_ice_cream_report.html', locals())

차이를 발견했나?

datenow로 바뀌었다. 이건 4줄짜리 코드지만 만일 100줄짜리 거대한 코드라면..?

Important

그러니 locals()를 사용하지 마라

def ice_cream_store_display(request, store_id):
    return render(
       request,
       'melted_ice_cream_report.html',
       {
           'store': get_object_or_404(Store, id=store_id),
           'now': timezone.now()
       }
    )