본문으로 건너뛰기
Advertisement

FBV vs CBV — 함수/클래스 기반 뷰

Django는 **함수 기반 뷰(FBV)**와 클래스 기반 뷰(CBV) 두 가지 방식을 지원합니다. 상황에 따라 적절히 선택합니다.


함수 기반 뷰 (FBV)

# products/views.py
from django.http import JsonResponse, HttpResponse
from django.views.decorators.http import require_http_methods
from django.contrib.auth.decorators import login_required
import json

from .models import Product


# 단순한 뷰는 FBV가 명확하고 읽기 쉬움
def product_list(request):
if request.method == "GET":
products = Product.objects.filter(is_active=True).values(
"id", "name", "price", "stock"
)
return JsonResponse(list(products), safe=False)
elif request.method == "POST":
data = json.loads(request.body)
product = Product.objects.create(
name=data["name"],
price=data["price"],
owner=request.user,
)
return JsonResponse({"id": product.id, "name": product.name}, status=201)


# 데코레이터로 기능 추가
@require_http_methods(["GET", "POST"])
@login_required
def create_product(request):
data = json.loads(request.body)
product = Product.objects.create(owner=request.user, **data)
return JsonResponse({"id": product.id}, status=201)


# 커스텀 데코레이터
from functools import wraps


def admin_required(view_func):
@wraps(view_func)
def wrapper(request, *args, **kwargs):
if not request.user.is_staff:
return JsonResponse({"error": "관리자 권한 필요"}, status=403)
return view_func(request, *args, **kwargs)
return wrapper


@admin_required
def admin_stats(request):
count = Product.objects.count()
return JsonResponse({"total_products": count})

클래스 기반 뷰 (CBV)

from django.views import View
from django.http import JsonResponse
import json

from .models import Product


class ProductListView(View):
"""GET /products/ — 목록, POST /products/ — 생성"""

def get(self, request):
products = list(
Product.objects.filter(is_active=True).values("id", "name", "price")
)
return JsonResponse(products, safe=False)

def post(self, request):
data = json.loads(request.body)
product = Product.objects.create(owner=request.user, **data)
return JsonResponse({"id": product.id}, status=201)


class ProductDetailView(View):
"""GET/PUT/DELETE /products/{pk}/"""

def get_object(self, pk):
try:
return Product.objects.get(pk=pk)
except Product.DoesNotExist:
return None

def get(self, request, pk):
product = self.get_object(pk)
if not product:
return JsonResponse({"error": "Not found"}, status=404)
return JsonResponse({"id": product.id, "name": product.name, "price": float(product.price)})

def put(self, request, pk):
product = self.get_object(pk)
if not product:
return JsonResponse({"error": "Not found"}, status=404)
data = json.loads(request.body)
for key, value in data.items():
setattr(product, key, value)
product.save()
return JsonResponse({"id": product.id, "name": product.name})

def delete(self, request, pk):
product = self.get_object(pk)
if not product:
return JsonResponse({"error": "Not found"}, status=404)
product.delete()
return HttpResponse(status=204)

제네릭 CBV — 코드 재사용

from django.views.generic import (
ListView, DetailView, CreateView, UpdateView, DeleteView
)
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.urls import reverse_lazy
from .models import Product
from .forms import ProductForm


# ListView: 목록 페이지
class ProductListView(ListView):
model = Product
template_name = "products/list.html"
context_object_name = "products"
paginate_by = 20

def get_queryset(self):
qs = super().get_queryset().filter(is_active=True)
q = self.request.GET.get("q")
if q:
qs = qs.filter(name__icontains=q)
return qs

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["total_count"] = self.get_queryset().count()
return context


# DetailView: 상세 페이지
class ProductDetailView(DetailView):
model = Product
template_name = "products/detail.html"
context_object_name = "product"


# CreateView: 생성
class ProductCreateView(LoginRequiredMixin, CreateView):
model = Product
form_class = ProductForm
template_name = "products/form.html"
success_url = reverse_lazy("product-list")

def form_valid(self, form):
form.instance.owner = self.request.user
return super().form_valid(form)


# UpdateView: 수정
class ProductUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model = Product
form_class = ProductForm
template_name = "products/form.html"

def test_func(self):
return self.get_object().owner == self.request.user

def get_success_url(self):
return self.object.get_absolute_url()


# DeleteView: 삭제
class ProductDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
model = Product
template_name = "products/confirm_delete.html"
success_url = reverse_lazy("product-list")

def test_func(self):
return self.get_object().owner == self.request.user

Mixin 패턴

from django.views import View
from django.http import JsonResponse
import json


# 재사용 가능한 Mixin
class JSONResponseMixin:
"""JSON 응답을 편리하게 반환"""

def render_json(self, data, status=200):
return JsonResponse(data, status=status)

def render_error(self, message, status=400):
return JsonResponse({"error": message}, status=status)


class LoginRequiredMixin:
"""비로그인 시 403 반환"""

def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return JsonResponse({"error": "로그인 필요"}, status=401)
return super().dispatch(request, *args, **kwargs)


class OwnerRequiredMixin:
"""소유자만 접근"""

def dispatch(self, request, *args, **kwargs):
obj = self.get_object()
if obj.owner != request.user:
return JsonResponse({"error": "권한 없음"}, status=403)
return super().dispatch(request, *args, **kwargs)


# Mixin 조합
class ProductAPIView(JSONResponseMixin, LoginRequiredMixin, View):
def get(self, request, pk):
try:
product = Product.objects.get(pk=pk)
return self.render_json({"id": product.id, "name": product.name})
except Product.DoesNotExist:
return self.render_error("상품 없음", status=404)

FBV vs CBV 선택 기준

FBV 선택 (함수 기반)
✅ 간단한 단일 로직 (webhook, 콜백)
✅ 조건 분기가 복잡한 경우
✅ 팀이 FBV에 익숙한 경우
✅ DRF ViewSet을 쓸 때 FBV APIView 선호

CBV 선택 (클래스 기반)
✅ CRUD 패턴 반복 (제네릭 뷰 활용)
✅ 공통 동작을 Mixin으로 재사용
✅ 대형 프로젝트에서 일관성 유지

정리

뷰 타입적합한 상황
FBV + 데코레이터단순 로직, 빠른 구현
CBV (View)HTTP 메서드별 명확한 분리
제네릭 CBVCRUD 패턴 (ListView, CreateView 등)
Mixin공통 기능 재사용 (인증, 권한, 로깅)

Django REST Framework의 **APIViewViewSet**이 실무에서 가장 많이 사용됩니다.

Advertisement