튜토리얼 초반에는 FBV로 예제를 다뤘지만 궁극적으로는 CBV로 기능 구현하는 구조가 많이 사용되므로 개념을 확실히 아는게 좋을 것 같습니다.
우선 함수 기반 뷰 개념을 다시 짚고 가겠습니다.
1. 함수 기반 뷰(Fuction-Based Views)
함수 기반 뷰는 django.views.View 클래스를 상속 받는 대신, 직접 함수를 작성하여 요청을 처리합니다.
Django에서 가장 기본적인 함수 기반 뷰(FBV)는 함수로 HTTP 요청을 처리하는 것입니다.
단순히 Python 함수를 작성하여 Django가 이를 HTTP 요청과 매핑합니다.
from django.http import HttpResponse
def my_basic_view(request):
if request.method == "GET":
return HttpResponse("This is a GET request")
elif request.method == "POST":
return HttpResponse("This is a POST request")
else:
return HttpResponse("Unsupported HTTP method", status=405)
FBV는 간단하고 직관적이지만 장고 프로젝트가 커지고 복잡할 수록 한계점이 드러납니다.
(1) 재사용성과 확장성 부족
FBV는 함수 단위로 동작하기 때문에 코드 재사용이 어렵습니다.
공통적인 로직(예: 인증, 권한 검사 등)을 여러 뷰에 반복적으로 작성해야 하므로, 유지보수성이 떨어집니다.
여러 기능을 조합하거나 확장하려면 함수 내부를 직접 수정해야 하는 경우가 많습니다.
def my_view(request):
if not request.user.is_authenticated:
return HttpResponse("Unauthorized", status=401)
# 비슷한 인증 로직을 다른 함수에도 중복 작성해야 함
(2) 코드 가독성 저하
FBV는 뷰의 모든 로직이 하나의 함수 안에 작성되므로, 복잡한 로직이 추가되면 코드가 길어지고 가독성이 떨어집니다.
여러 HTTP 메서드(GET, POST 등)를 처리하는 경우, 조건문(if-else)으로 분기하므로 코드가 더 복잡해질 수 있습니다.
def my_view(request):
if request.method == "GET":
# GET 요청 처리 로직
pass
elif request.method == "POST":
# POST 요청 처리 로직
pass
else:
# 기타 요청 처리
return HttpResponse("Unsupported method", status=405)
(3) HTTP 메서드별 분리가 불가능
FBV에서는 HTTP 메서드별(GET, POST, PUT 등)로 동작을 나누기 위해 조건문을 사용해야 하며, 메서드별 처리가 명확히 분리되지 않습니다.
반면, CBV(Class-Based View)에서는 HTTP 메서드(get, post 등)를 별도의 메서드로 정의할 수 있어 더 명확합니다.
FBV:
def my_view(request):
if request.method == "GET":
return HttpResponse("GET request")
elif request.method == "POST":
return HttpResponse("POST request")
CBV:
from django.views import View
class MyView(View):
def get(self, request):
return HttpResponse("GET request")
def post(self, request):
return HttpResponse("POST request")
HTTP 메서드별로 코드가 분리되어서 가독성이 좋아짐🤓
2. Django 기본 클래스 기반 뷰(Class-Based Views)
CBV는 View를 최상위 클래스로 가지며, 다양한 기능을 확장합니다.
• View: 모든 CBV의 최상위 부모 클래스.
• Generic View: CRUD 작업을 간소화한 고수준 뷰. (ListView, DetailView 등)
• Mixin: CBV의 동작을 확장하는 도구. (LoginRequiredMixin, PermissionRequiredMixin 등)
2.1 View 클래스
Django의 django.views.View는 가장 기본적인 클래스 기반 뷰로, 가장 기본적인 부모 클래스입니다.
Django의 모든 클래스 기반 뷰는 View를 상속받아 동작하고 HTTP 메서드(GET, POST 등)를 처리할 수 있는 최소한의 기능만 제공합니다.
URLconf에서 View.as_view() 클래스 메서드를 정의하면 뷰함수를 반환하는데요, View 클래스에서 as_view() 메서드는 아래와 같이 정의되어 있습니다.
class View:
@classonlymethod
def as_view(cls, **initkwargs):
"""Main entry point for a request-response process."""
for key in initkwargs:
# Code omitted for clarity
# ...
def view(request, *args, **kwargs):
self = cls(**initkwargs)
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
self.request = request
self.args = args
self.kwargs = kwargs
return self.dispatch(request, *args, **kwargs)
# Code omitted for clarity
# ...
def dispatch(self, request, *args, **kwargs):
if request.method.lower() in self.http_method_names:
handler = getattr(
self, request.method.lower(), self.http_method_not_allowed
)
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)
# Code omitted for clarity
# ...
return view
as_view() 메서드에 의해 렌더링되는 view 함수는 dispatch() 메서드에 request를 전달합니다.
dispatch() 메서드는 HTTP 요청의 메서드 타입(GET, POST, PUT 등)에 따라 적합한 HTTP 메서드를 실행합니다.
즉, request가 POST라면 post() 메서드가, GET이라면 get() 메서드가 실행됩니다.
예시로 클라이언트가 GET 요청을 보냈다고 가정하면,
GET /my-view/ HTTP/1.1
Host: 127.0.0.1:8000
장고는 views.MyView.as_view()를 통해 호출된 dispatch 메서드가 이 요청을 처리하도록 합니다.
# urls.py
from django.urls import path
from .views import MyView
urlpatterns = [
path('my-view/', MyView.as_view(), name='my_view'),
]
MyView 클래스에서 클라이언트 요청에 따라 get 메서드가 실행되도록 연결됩니다.
# views.py
from django.http import JsonResponse
from django.views import View
class MyView(View):
# GET 요청 처리 메서드
def get(self, request, *args, **kwargs):
return JsonResponse({'message': 'This is a GET request'})
# POST 요청 처리 메서드
def post(self, request, *args, **kwargs):
return JsonResponse({'message': 'This is a POST request'})
# dispatch 메서드 재정의 (옵션: 요청 흐름 확인용)
def dispatch(self, request, *args, **kwargs):
print(f"Dispatching {request.method} request") # 콘솔에 요청 타입 출력
return super().dispatch(request, *args, **kwargs)
만약 get, post 메서드를 view에서 정의해두지 않으면 ?👽
dispatch 요청을 처리할 메서드를 찾지 못하고 기본적으로 405 Method Not Allowed 응답을 반환합니다.
2.3 Generic View
Django의 제네릭 뷰(Generic View)는 자주 반복되는 작업(예: 목록, 상세, 생성, 수정, 삭제)을 자동으로 처리하도록 설계된 클래스 기반 뷰입니다.
기본 CBV에 비해 코드가 간결하며, Mixin을 조합해 동작을 확장하거나 커스터마이징하기 쉽고, 페이징 등 추가 기능도 기본적으로 지원하여 생산성과 유지보수성을 크게 향상시킵니다.
🍭Mixin 클래스
장고에서 Mixin은 View에 특정 기능을 추가하는 도구입니다.
장고에서는 다양한 내장 Mixin을 제공하고 View에서 반복되는 코드를 줄이고 필요한 기능만 쉽게 추가 가능합니다.
또 뭔소리냐 싶으시겠지만.. 👽
장고 Mixin은 말 그대로 기능 보강을 위한 작은 조각, 기본 View 클래스는 전체적인 요청-응답 흐름을 처리하는 중심적인 역할을 합니다.
제네릭 뷰는 여러 Mixin 클래스를 조합해 만들어졌습니다.
제네릭 뷰의 종류는 4가지로 나뉘는데요,
Simple Generic Views
• View : 최상위에 있는 부모 제네릭 뷰 클래스. 모든 CBV의 기반
• TemplateView : 정적인 템플릿을 렌더링하는 데 사용
• RedirectView : 지정된 URL로 리다이렉트하는 데 사용
Detail Views
• DetailView : 단일 객체의 세부 정보를 표시하는 뷰. 기본 키(pk) 또는 슬러그(slug)를 통해 객체를 조회
List Views
• ListView : 객체 목록을 표시하는 뷰. 페이징(pagination)을 기본적으로 지원
Editing Views
• FormView : 폼 데이터를 처리하고, 유효성 검사를 수행하는 뷰
• CreateView : 새 객체를 생성하는 폼을 제공하고 처리하는 뷰
• UpdateView : 기존 객체를 수정하는 폼을 제공하고 처리하는 뷰
• DeleteView : 객체를 삭제하고 성공적으로 삭제된 후 리다이렉트하는 뷰
데이터 목록으로 보여주는 ListView 기능을 한번 살펴보겠습니다.
2.3.1 ListView의 상속 구조
ListView
├── MultipleObjectMixin (데이터 처리)
├── BaseListView (기본 뷰)
└── View
2.3.2 MultipleObjectMixin의 역할
MultipleObjectMixin은 다중 객체를 처리하는 데 필요한 기능을 제공하는 Mixin입니다. 목록 조회를 위해 데이터 쿼리셋을 처리하고 이를 뷰와 템플릿에 연결하는 역할을 합니다.
(1) get_queryset(): 데이터를 가져오는 메서드로 기본적으로 model.objects.all()을 반환합니다.
기본적으로 아래와 같이 모델 정의하면 모든 데이터를 가져옵니다.
class MyListView(ListView):
model = MyModel # 연결된 모델 지정
만약 직접 쿼리셋을 정의하고 싶다면 get_queryset() 메서드를 오버라이드합니다.
class CustomListView(ListView):
model = MyModel
def get_queryset(self):
# 특정 조건에 맞는 데이터 필터링
return MyModel.objects.filter(is_active=True)
(2) get_context_data(**kwargs): 컨텍스트 데이터를 생성하여 템플릿으로 전달하는 기능입니다.
class CustomListView(ListView):
model = MyModel
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['extra_info'] = "This is additional context"
return context
get_context_data는 기본적으로 다음과 같은 데이터들이 포함되고 메서드를 재정의하여 데이터를 추가할 수도 있습니다.
{
'object_list': [<MyModel: Object1>, <MyModel: Object2>, ...], # QuerySet
'paginator': None, # 페이지네이션이 없다면 None
'page_obj': None, # 페이지네이션이 없다면 None
'is_paginated': False, # 페이지네이션 여부
'view': <CustomListView>, # 현재 뷰 객체
'extra_info': "This is additional context" # 추가 데이터
}
템플릿에서는 다음과 같이 접근할 수 있겠네요.
- 예시 템플릿
<ul>
{% for obj in object_list %}
<li>{{ obj }}</li>
{% endfor %}
</ul>
<p>{{ extra_info }}</p>
- 렌더링 결과
<ul>
<li>Object1</li>
<li>Object2</li>
</ul>
<p>This is additional context</p>
(3) paginate_queryset(queryset, page_size): 페이지네이션 처리
페이지네이션 사용하려면 paginate_by 속성을 설정하고 queryset을 받아 데이터를 나눕니다.
class PaginatedListView(ListView):
model = MyModel
paginate_by = 10 # 한 페이지에 보여줄 데이터 개수
페이지네이션은 내부적으로 Paginator 클래스를 사용하여 처리됩니다.
👀 MultipleObjectMixin에서 페이지네이션 처리 흐름
데이터 쿼리셋을 Paginator에 전달 👉🏼 현재 요청된 페이지를 기준으로 데이터를 나눔 👉🏼 결과를 컨텍스트 데이터에 추가
👀 템플릿에서 사용하는 페이지네이션 기본 변수
∙is_paginated: 페이지네이션 여부
∙paginator: Paginator 객체
∙page_obj: 현재 페이지의 정보
{% if is_paginated %}
<div>
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}">이전</a>
{% endif %}
<span>{{ page_obj.number }} / {{ paginator.num_pages }}</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">다음</a>
{% endif %}
</div>
{% endif %}
2.3.3 BaseListView 역할
BaseListView는 MultipleObjectMixin을 기반으로 HTTP 메서드 처리와 기본적인 요청 처리를 담당합니다.
∙get() 메서드: GET 요청에 대한 처리를 수행
∙데이터를 MultipleObjectMixin의 메서드와 연결하여, 최종적으로 컨텍스트 데이터를 생성하고 템플릿에 전달
ListView의 전체적인 동작 흐름
1. ListView가 호출되면 dispatch() 메서드로 요청이 처리됩니다.
2. BaseListView가 요청 메서드(GET)를 확인하고 적절한 메서드를 호출합니다.
3. MultipleObjectMixin의 get_queryset()으로 데이터를 가져옵니다.
4. paginate_queryset()으로 페이지네이션을 처리합니다(필요한 경우).
5. get_context_data()로 템플릿에 전달할 데이터를 구성합니다.
6. TemplateResponseMixin이 템플릿을 렌더링하고 응답을 반환합니다.
요약
MultipleObjectMixin은 목록 데이터를 처리하는 데 필요한 기능(쿼리셋 처리, 페이지네이션, 컨텍스트 생성)을 제공합니다.
ListView는 MultipleObjectMixin과 BaseListView를 결합하여 데이터를 목록 형태로 보여줍니다.
마무리
그래서 이 많은 개념으로 뭘 어떻게 쓰란건데! 의문이 많으시겠지만(저도 배우는 단계) 아래와 같이 정리해보면 될 것 같습니다.
1. 작은 프로젝트나 단순한 작업:
• 뷰 로직이 단순한 경우 FBV를 활용
2. 공통 작업이나 확장성을 고려해야 할 때:
• 로그인 제한, 권한 검사, 데이터 목록 출력 등 여러 곳에서 반복되는 작업이 있다면 CBV를 활용
• Generic View와 Mixin을 사용하면 유지보수성과 확장성이 크게 향상됨
3. 프로젝트의 규모와 복잡도에 따라 선택:
• 프로젝트 초기 단계에서 FBV로 시작하고, 필요할 때 CBV로 전환하거나 병행
CBV의 구조적인 장점을 익혀두면 앞으로 Django REST Framework(DRF)를 배울때 수월할 것 같습니다.
다음 파트에서 계속...
'Programming👩🏻💻 > Web framework' 카테고리의 다른 글
[FastAPI] DDD (Domain-Driven Design) pattern (0) | 2024.12.14 |
---|---|
[Django] 공식문서로 익혀보기 part4.1 - 폼(Form) 작성하기 (1) | 2024.11.24 |
[Django] 공식문서로 익혀보기 part3 - views 작성 (2) | 2024.11.19 |
[Django] 공식문서로 익혀보기 part2 - 데이터베이스 설치 (6) | 2024.11.15 |
[Django] 공식문서로 익혀보기 part1 - 첫 시작, 프로젝트와 앱 만들기 (5) | 2024.11.15 |