WeniVooks

검색

Django 베이스캠프

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에 있습니다. 이 HttpResponseHttpResponseBase를 상속받아 구현되어 있습니다. 이 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을 지정합니다. 앞서 사용한 ListViewDetailView와 조금 다른 점이 있기 때문에 표로 정리해보도록 하겠습니다. 또한 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 개발 되시길 바랍니다.

6.2 settings 실습