Class Based Views
앞서 배운 함수 기반 뷰(Function Based Views, FBV)는 Django에서 뷰를 함수로 작성하는 방식입니다. 반면 Class Based Views(CBV)는 뷰를 클래스로 작성하는 방식입니다. 각 구현 방식은 장단점이 있습니다. 함수 기반 뷰는 간단하고 직관적이지만 코드 중복이 발생할 수 있습니다. 반면 클래스 기반 뷰는 코드 재사용성이 높고 구조화된 코드를 작성할 수 있지만 학습 곡선이 가파르고 코드가 복잡해질 수 있습니다. 또한 클래스기반 뷰는 많은 기능이 '추상화'되어 있기 때문에 사용하기는 쉽지만, 어떻게 동작하는지 이해하기 어려울 수 있습니다.
'추상화'는 앞으로도 프레임워크를 만날 때, SW 개발할 때 자주 등장하게 되는 개념이므로 한 번 설명을 하고 가도록 하겠습니다. 자동차를 예를 들어, 우리는 자동차 엔진의 원리를 파악하지 않더라도 엑셀을 밟고 앞으로 나아갈 수 있습니다. 엔진의 기능은 엑셀로 '추상화'되어 있다고 말할 수 있습니다. 물론 이렇게 단순한 비유로 SW의 '추상화'를 모두 설명할 수는 없지만 핵심은 불필요한 세부 정보나 로직을 숨기는 것을 얘기합니다.
1. CBV 소개
1.1 CBV란?
Class Based Views는 Django에서 뷰를 클래스로 작성하는 방식입니다. FBV가 단일 함수로 HTTP 요청을 처리하는 반면, CBV는 객체 지향 접근 방식을 사용하여 코드를 구조화하고 재사용성을 높입니다.
1.2 CBV의 장점
FBV에 중복되었던 여러 코드를 클래스로 묶어서 재사용성을 높일 수 있습니다. 또한 CBV는 Django의 제네릭 뷰를 사용하여 CRUD(Create, Read, Update, Delete) 기능을 쉽게 구현할 수 있습니다. 이런 객체들은 모두 클래스로 되어 있어 상속을 통해 쉽게 확장할 수 있습니다. 또한 CBV는 HTTP 메서드를 별도의 메서드로 처리할 수 있어 코드를 더욱 구조화할 수 있습니다.
2. 기본 CBV 사용법
아래 코드를 복사해서, 작업할 폴더의 터미널에 shift + insert
합니다.
mkdir 05_2_CBV
cd 05_2_CBV
python -m venv venv
.\venv\Scripts\activate
pip install django
pip freeze > requirements.txt
django-admin startproject config .
python manage.py migrate
mkdir 05_2_CBV
cd 05_2_CBV
python -m venv venv
.\venv\Scripts\activate
pip install django
pip freeze > requirements.txt
django-admin startproject config .
python manage.py migrate
2.1 간단한 CBV 예제
다음은 간단한 CBV 예제입니다.
# config/views.py
from django.views import View
from django.http import HttpResponse
class MyView(View):
def get(self, request):
return HttpResponse("Hello, GET!")
# config/views.py
from django.views import View
from django.http import HttpResponse
class MyView(View):
def get(self, request):
return HttpResponse("Hello, GET!")
이 뷰를 URL에 연결하려면 다음과 같이 작성합니다.
# config/urls.py
from django.urls import path
from .views import MyView
urlpatterns = [
path('hello/', MyView.as_view(), name='my-view'),
]
# config/urls.py
from django.urls import path
from .views import MyView
urlpatterns = [
path('hello/', MyView.as_view(), name='my-view'),
]
서버를 실행시킵니다.
python manage.py runserver
python manage.py runserver
테스트는 VSC에 extention인 Thunder Client를 설치하고, 다음과 같이 요청을 보내봅니다. 원하는 응답이 들어오는 것을 확인할 수 있습니다.
GET http://127.0.0.1:8000/hello/
GET http://127.0.0.1:8000/hello/
2.2 CBV의 HTTP 메서드 처리
CBV에서는 각 HTTP 메서드에 대해 별도의 메서드를 정의할 수 있습니다.
# config/views.py
from django.views import View
from django.http import HttpResponse
class MyView(View):
def get(self, request):
return HttpResponse("GET request!")
def post(self, request):
return HttpResponse("POST request!")
def put(self, request):
return HttpResponse("PUT request!")
def delete(self, request):
return HttpResponse("DELETE request!")
# config/views.py
from django.views import View
from django.http import HttpResponse
class MyView(View):
def get(self, request):
return HttpResponse("GET request!")
def post(self, request):
return HttpResponse("POST request!")
def put(self, request):
return HttpResponse("PUT request!")
def delete(self, request):
return HttpResponse("DELETE request!")
이번에도 VSC에 extention인 Thunder Client로 다음과 같이 요청을 보내봅니다. GET을 제외하면 원하는 응답값이 들어오지 않는 것을 확인할 수 있습니다. 이유는 CSRF 토큰이 없기 때문입니다.
GET http://127.0.0.1:8000/hello/
POST http://127.0.0.1:8000/hello/
PUT http://127.0.0.1:8000/hello/
DELETE http://127.0.0.1:8000/hello/
GET http://127.0.0.1:8000/hello/
POST http://127.0.0.1:8000/hello/
PUT http://127.0.0.1:8000/hello/
DELETE http://127.0.0.1:8000/hello/
이를 해결하기 위해서는 특별히 이 뷰에서 CSRF 토큰을 비활성화해야 합니다. 이를 위해 @csrf_exempt
데코레이터를 사용합니다. 다만 보안상 실무에서는 사용하지 않는 것이 좋습니다.
from django.views import View
from django.http import HttpResponse
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
@method_decorator(csrf_exempt, name='dispatch')
class MyView(View):
def get(self, request):
return HttpResponse("GET request!")
def post(self, request):
return HttpResponse("POST request!")
def put(self, request):
return HttpResponse("PUT request!")
def delete(self, request):
return HttpResponse("DELETE request!")
from django.views import View
from django.http import HttpResponse
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
@method_decorator(csrf_exempt, name='dispatch')
class MyView(View):
def get(self, request):
return HttpResponse("GET request!")
def post(self, request):
return HttpResponse("POST request!")
def put(self, request):
return HttpResponse("PUT request!")
def delete(self, request):
return HttpResponse("DELETE request!")
이러한 View는 \venv\Lib\site-packages\django\views\generic\base.py
에 정의 되어 있습니다. 여기서 응답값은 HttpResponse
로 되어 있는데 django.http
에 있습니다. 이 HttpResponse
는 HttpResponseBase
를 상속받아 구현되어 있습니다. 이 HttpResponseBase
는 딕셔너리 형태로 접근 가능한 헤더를 가진 HTTP 응답을 만들어냅니다.
3. 일반적인 CBV 패턴
Django는 다양한 일반적인 작업을 위한 기능을 제공합니다. 이를 제네릭 뷰라 합니다. 제네릭 뷰는 모두 View를 상속받아 제공합니다. 이러한 제네릭 뷰를 사용하면 반복적인 코드 작성을 줄이고 빠르게 개발할 수 있습니다.
3.1 ListView
ListView는 객체 목록을 표시하는 데 사용됩니다. 우선 blog 앱에 Post 모델을 만들고 migrate를 하고 게시물 3개를 생성하도록 하겠습니다.
# shell
python manage.py startapp blog
# config/settings.py
INSTALLED_APPS = [
...
'blog',
]
# shell
python manage.py startapp blog
# config/settings.py
INSTALLED_APPS = [
...
'blog',
]
블로그 앱에 Post
모델을 만들고 마이그레이션을 합니다.
# blog/models.py
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
status = models.CharField(max_length=10, default='draft')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
# blog/models.py
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
status = models.CharField(max_length=10, default='draft')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
DB에 반영합니다.
python manage.py makemigrations
python manage.py migrate
python manage.py makemigrations
python manage.py migrate
그리고 게시물 3개를 생성합니다. admin에 접속하지 않고 생성하도록 하겠습니다.
python manage.py shell
>>> from blog.models import Post
>>> Post.objects.create(title='Post 1', content='Content 1')
>>> Post.objects.create(title='Post 2', content='Content 2')
>>> Post.objects.create(title='Post 3', content='Content 3')
>>> exit()
python manage.py shell
>>> from blog.models import Post
>>> Post.objects.create(title='Post 1', content='Content 1')
>>> Post.objects.create(title='Post 2', content='Content 2')
>>> Post.objects.create(title='Post 3', content='Content 3')
>>> exit()
views.py를 수정하여 목록을 볼 수 있는 ListView를 사용하도록 하겠습니다.
# blog/views.py
from django.views.generic import ListView
from .models import Post
class PostListView(ListView):
model = Post
template_name = 'post_list.html'
context_object_name = 'posts'
paginate_by = 10
# blog/views.py
from django.views.generic import ListView
from .models import Post
class PostListView(ListView):
model = Post
template_name = 'post_list.html'
context_object_name = 'posts'
paginate_by = 10
이렇게 하면 모델은 Post
, 템플릿은 post_list.html
, 템플릿 안에서 사용할 객체는 posts
, 한 페이지에 보여줄 객체 수는 10개로 설정하게 하는 것입니다. 이제 Post.objects.all()을 사용하지 않아도 되는 것입니다. 이 클래스는 \venv\Lib\site-packages\django\views\generic\list.py
에 있습니다. 다만 FBV처럼 모든 기능을 다 파악할 필요는 없습니다. 자주 사용되는 속성을 아래 표로 정리해두었습니다. 입력되었을 때, 입력되지 않았을 때를 나누어 설명하겠습니다.
속성 | 입력되었을 때 | 입력되지 않았을 때 |
---|---|---|
model | 지정된 모델의 모든 객체를 조회합니다. | 오류가 발생합니다. model 또는 queryset 중 하나는 반드시 지정해야 합니다. |
queryset | 지정된 쿼리셋을 사용하여 객체를 조회합니다. | model이 지정된 경우 model.objects.all()을 사용합니다. |
template_name | 지정된 템플릿을 사용합니다. | '<app_name>/<model_name>_list.html' 형식의 기본 템플릿을 사용합니다. |
context_object_name | 지정된 이름으로 템플릿에 객체 리스트를 전달합니다. | 'object_list'라는 이름으로 템플릿에 객체 리스트를 전달합니다. |
paginate_by | 지정된 수만큼 객체를 페이지별로 나눕니다. | 페이지네이션을 사용하지 않고 모든 객체를 한 페이지에 표시합니다. |
ordering | 지정된 필드로 객체를 정렬합니다. | 모델의 기본 정렬 순서를 사용합니다. |
템플릿은 간단하게 만들어보도록 하겠습니다. 간단하게 만들더라도 폴더 구조는 지켜주시기 바랍니다.
<!-- blog/templates/blog/post_list.html -->
<!DOCTYPE html>
<html lang="ko">
<head>
<title>Post List</title>
</head>
<body>
<h1>Post List</h1>
<ul>
{% for post in posts %}
<li>{{ post.title }}</li>
{% endfor %}
</ul>
</body>
</html>
<!-- blog/templates/blog/post_list.html -->
<!DOCTYPE html>
<html lang="ko">
<head>
<title>Post List</title>
</head>
<body>
<h1>Post List</h1>
<ul>
{% for post in posts %}
<li>{{ post.title }}</li>
{% endfor %}
</ul>
</body>
</html>
작동되는 것을 확인하기 위해 URL을 설정합니다.
# config/urls.py
from django.urls import path
from blog.views import PostListView
urlpatterns = [
path('', PostListView.as_view(), name='post-list'),
]
# config/urls.py
from django.urls import path
from blog.views import PostListView
urlpatterns = [
path('', PostListView.as_view(), name='post-list'),
]
아래 명령어로 서버를 구동하고, http://127.0.0.1:8000/ 로 접속하면 게시물 목록을 확인할 수 있습니다.
python manage.py runserver
python manage.py runserver
3.2 DetailView
DetailView는 단일 객체의 세부 정보를 표시하는 데 사용됩니다. 예를 들어, 블로그 게시물의 세부 정보를 표시할 때 사용할 수 있습니다. 안에 들어가는 속성은 ListView와 비슷합니다.
# blog/views.py
from django.views.generic import ListView, DetailView
from .models import Post
class PostListView(ListView):
model = Post
template_name = "post_list.html"
context_object_name = "posts"
paginate_by = 10
class PostDetailView(DetailView):
model = Post
template_name = "blog/post_detail.html"
context_object_name = "post"
# blog/views.py
from django.views.generic import ListView, DetailView
from .models import Post
class PostListView(ListView):
model = Post
template_name = "post_list.html"
context_object_name = "posts"
paginate_by = 10
class PostDetailView(DetailView):
model = Post
template_name = "blog/post_detail.html"
context_object_name = "post"
템플릿은 다음과 같이 만들어보도록 하겠습니다.
<!-- templates/blog/post_detail.html -->
<!DOCTYPE html>
<html lang="ko">
<head>
<title>{{ post.title }}</title>
</head>
<body>
<h1>{{ post.title }}</h1>
<p>{{ post.content }}</p>
</body>
</html>
<!-- templates/blog/post_detail.html -->
<!DOCTYPE html>
<html lang="ko">
<head>
<title>{{ post.title }}</title>
</head>
<body>
<h1>{{ post.title }}</h1>
<p>{{ post.content }}</p>
</body>
</html>
URL을 설정합니다.
# config/urls.py
from django.urls import path
from blog.views import PostListView, PostDetailView
urlpatterns = [
path('', PostListView.as_view(), name='post-list'),
path('post/<int:pk>/', PostDetailView.as_view(), name='post-detail'),
]
# config/urls.py
from django.urls import path
from blog.views import PostListView, PostDetailView
urlpatterns = [
path('', PostListView.as_view(), name='post-list'),
path('post/<int:pk>/', PostDetailView.as_view(), name='post-detail'),
]
서버를 구동한 상태에서 http://127.0.0.1:8000/post/1/ 로 접속하면 첫 번째 게시물의 세부 정보를 확인할 수 있습니다.
3.3 CreateView
CreateView는 새 객체를 생성하는 폼을 처리합니다.
from django.views.generic import ListView, DetailView, CreateView
from django.urls import reverse_lazy
from .models import Post
class PostListView(ListView):
model = Post
template_name = "post_list.html"
context_object_name = "posts"
paginate_by = 10
class PostDetailView(DetailView):
model = Post
template_name = "blog/post_detail.html"
context_object_name = "post"
class PostCreateView(CreateView):
model = Post
fields = ["title", "content"]
template_name = "blog/post_form.html"
success_url = reverse_lazy("post-list")
from django.views.generic import ListView, DetailView, CreateView
from django.urls import reverse_lazy
from .models import Post
class PostListView(ListView):
model = Post
template_name = "post_list.html"
context_object_name = "posts"
paginate_by = 10
class PostDetailView(DetailView):
model = Post
template_name = "blog/post_detail.html"
context_object_name = "post"
class PostCreateView(CreateView):
model = Post
fields = ["title", "content"]
template_name = "blog/post_form.html"
success_url = reverse_lazy("post-list")
여기서는 fields
속성을 사용하여 폼에 표시할 필드를 지정합니다. success_url
속성은 객체 생성 후 이동할 URL을 지정합니다. 앞서 사용한 ListView
나 DetailView
와 조금 다른 점이 있기 때문에 표로 정리해보도록 하겠습니다. 또한 success_url은 객체 생성 후 이동을 해야하기 때문에 reverse()
가 아닌 reverse_lazy()
를 사용합니다. 이 함수는 앞서 작업한 것을 마치고 이동할 수 있게 해줍니다.
속성 | 입력되었을 때 | 입력되지 않았을 때 |
---|---|---|
model | 지정된 모델을 사용하여 폼을 생성합니다. | 오류가 발생합니다. model은 반드시 지정해야 합니다. |
fields | 지정된 필드만 폼에 포함됩니다. | 오류가 발생합니다. |
template_name | 지정된 템플릿을 사용합니다. | '<app_name>/<model_name>_form.html' 형식의 기본 템플릿을 사용합니다. |
success_url | 객체 생성 후 지정된 URL로 리다이렉트됩니다. | 생성된 객체의 get_absolute_url() 메서드를 호출한 결과로 리다이렉트됩니다. get_absolute_url 함수가 없으면 애러가 발생합니다. |
form_class | 지정된 폼 클래스를 사용합니다. | 모델 폼을 자동으로 생성하여 사용합니다. |
initial | 폼의 초기 데이터를 지정합니다. | 폼의 필드가 비어있는 상태로 표시됩니다. |
context_object_name | 지정된 이름으로 템플릿에 폼을 전달합니다. | 'form'이라는 이름으로 템플릿에 폼을 전달합니다. |
템플릿은 다음과 같이 만들어보도록 하겠습니다.
<!-- templates/post_form.html -->
<!DOCTYPE html>
<html lang="ko">
<head>
<title>Create Post</title>
</head>
<body>
<h1>Create Post</h1>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Create</button>
</form>
</body>
</html>
<!-- templates/post_form.html -->
<!DOCTYPE html>
<html lang="ko">
<head>
<title>Create Post</title>
</head>
<body>
<h1>Create Post</h1>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Create</button>
</form>
</body>
</html>
URL을 설정합니다.
# config/urls.py
from django.urls import path
from blog.views import PostListView, PostDetailView, PostCreateView
urlpatterns = [
path('', PostListView.as_view(), name='post-list'),
path('post/<int:pk>/', PostDetailView.as_view(), name='post-detail'),
path('post/new/', PostCreateView.as_view(), name='post-create'),
]
# config/urls.py
from django.urls import path
from blog.views import PostListView, PostDetailView, PostCreateView
urlpatterns = [
path('', PostListView.as_view(), name='post-list'),
path('post/<int:pk>/', PostDetailView.as_view(), name='post-detail'),
path('post/new/', PostCreateView.as_view(), name='post-create'),
]
서버를 구동한 상태에서 http://127.0.0.1:8000/post/new/ 로 접속하면 새 게시물을 생성할 수 있는 폼을 확인할 수 있습니다.
3.4 UpdateView
UpdateView는 기존 객체를 수정하는 폼을 처리합니다.
from django.views.generic import ListView, DetailView, CreateView, UpdateView
from django.urls import reverse_lazy
from .models import Post
class PostListView(ListView):
model = Post
template_name = "post_list.html"
context_object_name = "posts"
paginate_by = 10
class PostDetailView(DetailView):
model = Post
template_name = "blog/post_detail.html"
context_object_name = "post"
class PostCreateView(CreateView):
model = Post
fields = ["title", "content"]
template_name = "blog/post_form.html"
success_url = reverse_lazy("post-list")
class PostUpdateView(UpdateView):
model = Post
fields = ["title", "content"]
template_name = "blog/post_form.html"
success_url = reverse_lazy("post-list")
from django.views.generic import ListView, DetailView, CreateView, UpdateView
from django.urls import reverse_lazy
from .models import Post
class PostListView(ListView):
model = Post
template_name = "post_list.html"
context_object_name = "posts"
paginate_by = 10
class PostDetailView(DetailView):
model = Post
template_name = "blog/post_detail.html"
context_object_name = "post"
class PostCreateView(CreateView):
model = Post
fields = ["title", "content"]
template_name = "blog/post_form.html"
success_url = reverse_lazy("post-list")
class PostUpdateView(UpdateView):
model = Post
fields = ["title", "content"]
template_name = "blog/post_form.html"
success_url = reverse_lazy("post-list")
CreateView
와 비슷하지만, UpdateView
는 기존 객체를 수정하는 폼을 처리합니다. 템플릿은 CreateView
와 동일하게 사용할 수 있습니다.
URL을 설정합니다.
# config/urls.py
from django.urls import path
from blog.views import PostListView, PostDetailView, PostCreateView, PostUpdateView
urlpatterns = [
path('', PostListView.as_view(), name='post-list'),
path('post/<int:pk>/', PostDetailView.as_view(), name='post-detail'),
path('post/new/', PostCreateView.as_view(), name='post-create'),
path('post/<int:pk>/edit/', PostUpdateView.as_view(), name='post-update'),
]
# config/urls.py
from django.urls import path
from blog.views import PostListView, PostDetailView, PostCreateView, PostUpdateView
urlpatterns = [
path('', PostListView.as_view(), name='post-list'),
path('post/<int:pk>/', PostDetailView.as_view(), name='post-detail'),
path('post/new/', PostCreateView.as_view(), name='post-create'),
path('post/<int:pk>/edit/', PostUpdateView.as_view(), name='post-update'),
]
서버를 구동한 상태에서 http://127.0.0.1:8000/post/1/edit/ 로 접속하면 첫 번째 게시물을 수정할 수 있는 폼을 확인할 수 있습니다.
3.5 DeleteView
DeleteView는 객체 삭제를 처리합니다.
from django.views.generic import (
ListView,
DetailView,
CreateView,
UpdateView,
DeleteView,
)
from django.urls import reverse_lazy
from .models import Post
class PostListView(ListView):
model = Post
template_name = "post_list.html"
context_object_name = "posts"
paginate_by = 10
class PostDetailView(DetailView):
model = Post
template_name = "blog/post_detail.html"
context_object_name = "post"
class PostCreateView(CreateView):
model = Post
fields = ["title", "content"]
template_name = "blog/post_form.html"
success_url = reverse_lazy("post-list")
class PostUpdateView(UpdateView):
model = Post
fields = ["title", "content"]
template_name = "blog/post_form.html"
success_url = reverse_lazy("post-list")
class PostDeleteView(DeleteView):
model = Post
template_name = "blog/post_confirm_delete.html"
success_url = reverse_lazy("post-list")
from django.views.generic import (
ListView,
DetailView,
CreateView,
UpdateView,
DeleteView,
)
from django.urls import reverse_lazy
from .models import Post
class PostListView(ListView):
model = Post
template_name = "post_list.html"
context_object_name = "posts"
paginate_by = 10
class PostDetailView(DetailView):
model = Post
template_name = "blog/post_detail.html"
context_object_name = "post"
class PostCreateView(CreateView):
model = Post
fields = ["title", "content"]
template_name = "blog/post_form.html"
success_url = reverse_lazy("post-list")
class PostUpdateView(UpdateView):
model = Post
fields = ["title", "content"]
template_name = "blog/post_form.html"
success_url = reverse_lazy("post-list")
class PostDeleteView(DeleteView):
model = Post
template_name = "blog/post_confirm_delete.html"
success_url = reverse_lazy("post-list")
DeleteView
는 객체 삭제를 처리합니다. 템플릿은 post_confirm_delete.html
을 사용합니다.
<!-- templates/post_confirm_delete.html -->
<!DOCTYPE html>
<html lang="ko">
<head>
<title>Delete Post</title>
</head>
<body>
<h1>Delete Post</h1>
<p>게시물 "{{ object }}" 를 삭제하시겠습니까? 리얼리?</p>
<form method="post">
{% csrf_token %}
<button type="submit">Delete</button>
</form>
</body>
</html>
<!-- templates/post_confirm_delete.html -->
<!DOCTYPE html>
<html lang="ko">
<head>
<title>Delete Post</title>
</head>
<body>
<h1>Delete Post</h1>
<p>게시물 "{{ object }}" 를 삭제하시겠습니까? 리얼리?</p>
<form method="post">
{% csrf_token %}
<button type="submit">Delete</button>
</form>
</body>
</html>
URL을 설정합니다.
# config/urls.py
from django.urls import path
from blog.views import (
PostListView,
PostDetailView,
PostCreateView,
PostUpdateView,
PostDeleteView,
)
urlpatterns = [
path("", PostListView.as_view(), name="post-list"),
path("post/<int:pk>/", PostDetailView.as_view(), name="post-detail"),
path("post/new/", PostCreateView.as_view(), name="post-create"),
path("post/<int:pk>/edit/", PostUpdateView.as_view(), name="post-update"),
path("post/<int:pk>/delete/", PostDeleteView.as_view(), name="post-delete"),
]
# config/urls.py
from django.urls import path
from blog.views import (
PostListView,
PostDetailView,
PostCreateView,
PostUpdateView,
PostDeleteView,
)
urlpatterns = [
path("", PostListView.as_view(), name="post-list"),
path("post/<int:pk>/", PostDetailView.as_view(), name="post-detail"),
path("post/new/", PostCreateView.as_view(), name="post-create"),
path("post/<int:pk>/edit/", PostUpdateView.as_view(), name="post-update"),
path("post/<int:pk>/delete/", PostDeleteView.as_view(), name="post-delete"),
]
서버를 구동한 상태에서 http://127.0.0.1:8000/post/1/delete/ 로 접속하면 첫 번째 게시물을 삭제할 수 있는 폼을 확인할 수 있습니다.
4. Mixins 사용하기
Mixins은 여러 클래스에서 공통으로 사용되는 메서드나 속성을 정의하는 클래스입니다. CBV에서 Mixins을 사용하면 코드 재사용성을 높일 수 있습니다.
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import CreateView
from .models import Post
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
fields = ['title', 'content']
template_name = 'blog/post_form.html'
success_url = reverse_lazy('post-list')
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import CreateView
from .models import Post
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
fields = ['title', 'content']
template_name = 'blog/post_form.html'
success_url = reverse_lazy('post-list')
이 예제에서 LoginRequiredMixin
은 로그인한 사용자만 뷰에 접근할 수 있도록 합니다. 믹스인은 상속의 순서가 중요합니다. LoginRequiredMixin
은 항상 다른 클래스보다 먼저 상속되어야 합니다.
Django에서 사용할 수 있는 Mixins은 아래와 같습니다.
Mixin | 설명 |
---|---|
LoginRequiredMixin | 로그인한 사용자만 뷰에 접근할 수 있도록 합니다. |
PermissionRequiredMixin | 특정 권한을 가진 사용자만 뷰에 접근할 수 있도록 합니다. |
UserPassesTestMixin | 사용자 정의 함수를 사용하여 사용자의 접근 권한을 제어합니다. |
FormMixin | 폼을 처리하는 데 필요한 메서드를 제공합니다. |
ModelFormMixin | 모델 폼을 처리하는 데 필요한 메서드를 제공합니다. |
SingleObjectMixin | 단일 객체를 처리하는 데 필요한 메서드를 제공합니다. |
MultipleObjectMixin | 객체 목록을 처리하는 데 필요한 메서드를 제공합니다. |
5. CBV 커스터마이징
CBV의 동작을 커스터마이징하려면 기본 메서드를 오버라이드하거나 새로운 메서드를 추가할 수 있습니다. 여기서 get_queryset
는 객체 목록을 반환하는 메서드입니다. 이 메서드를 오버라이드하여 특정 조건에 맞는 객체 목록을 반환할 수 있습니다. get_context_data
는 템플릿에 전달할 컨텍스트 데이터를 반환하는 메서드입니다. 이 메서드를 오버라이드하여 컨텍스트 데이터를 추가하거나 수정할 수 있습니다.
from django.views.generic import ListView
from .models import Post
class PostListView(ListView):
model = Post
template_name = 'blog/post_list.html'
context_object_name = 'posts'
def get_queryset(self):
return Post.objects.filter(status='published')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['total_posts'] = Post.objects.count()
return context
from django.views.generic import ListView
from .models import Post
class PostListView(ListView):
model = Post
template_name = 'blog/post_list.html'
context_object_name = 'posts'
def get_queryset(self):
return Post.objects.filter(status='published')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['total_posts'] = Post.objects.count()
return context
마치며
Class Based Views는 Django에서 강력하고 유연한 뷰 작성 방법을 제공합니다. 다만 FBV와 CBV가 어떤 것이 우위에 있다 말하기 힘듭니다. 각각의 장단점이 있기 때문입니다. 프로젝트의 복잡성에 따라 FBV와 CBV를 적절히 선택하여 사용하는 것이 중요합니다.
여기서 CBV를 모두 다루진 않았습니다. Django에서 제공하는 일부 CBV 목록은 아래와 같습니다.
Base Views (뷰 클래스 생성)
View
: 최상위 뷰, 모든 클래스 기반 뷰의 기본이 되는 뷰TemplateView
: 템플릿을 렌더링하는 뷰RedirectView
: 다른 URL로 리다이렉트하는 뷰
Generic Display Views (정보를 보여주는 뷰)
DetailView
: 단일 객체의 상세 정보를 보여주는 뷰ListView
: 객체 목록을 보여주는 뷰
Generic Editing Views (CRUD 중 CUD를 제공하는 뷰)
FormView
: 폼을 처리하는 뷰CreateView
: 새 객체를 생성하는 폼을 제공하는 뷰UpdateView
: 기존 객체를 수정하는 폼을 제공하는 뷰DeleteView
: 객체를 삭제하는 기능을 제공하는 뷰
Generic Date Views (날짜 기반 뷰)
ArchiveIndexView
: 날짜별로 정렬된 객체 목록의 최상위 인덱스를 보여주는 뷰YearArchiveView
: 특정 연도의 객체들을 보여주는 뷰MonthArchiveView
: 특정 월의 객체들을 보여주는 뷰WeekArchiveView
: 특정 주의 객체들을 보여주는 뷰DayArchiveView
: 특정 일의 객체들을 보여주는 뷰TodayArchiveView
: 오늘 날짜의 객체들을 보여주는 뷰DateDetailView
: 특정 날짜의 특정 객체 상세 정보를 보여주는 뷰
Authentication Views (인증 관련 뷰)
LoginView
: 사용자 로그인을 처리하는 뷰LogoutView
: 사용자 로그아웃을 처리하는 뷰PasswordChangeView
: 비밀번호 변경을 처리하는 뷰PasswordResetView
: 비밀번호 재설정 프로세스를 시작하는 뷰PasswordResetDoneView
: 비밀번호 재설정 이메일이 전송되었음을 알리는 뷰PasswordResetConfirmView
: 새 비밀번호를 설정하는 뷰PasswordResetCompleteView
: 비밀번호 재설정이 완료되었음을 알리는 뷰
Permission and Authorization Views
PermissionRequiredMixin
: 특정 권한이 필요한 뷰에 사용되는 믹스인UserPassesTestMixin
: 사용자 정의 테스트를 통과해야 접근 가능한 뷰에 사용되는 믹스인LoginRequiredMixin
: 로그인이 필요한 뷰에 사용되는 믹스인
더 많은 CBV, CBV의 고급 기능과 커스터마이징 방법을 더 깊이 이해하려면 Django 공식 문서를 참고하시기 바랍니다. 다양한 상황에서 CBV를 실제로 사용해보며 경험을 쌓는 것도 중요합니다. 즐거운 Django 개발 되시길 바랍니다.