IT world

[Django] 24.02.27 Django CBV 실습 본문

모두의 연구소(오름캠프)/AI 모델 활용 백엔드 개발 과정

[Django] 24.02.27 Django CBV 실습

엄킹 2024. 2. 27. 16:51

오늘은 CBV를 통한 게시물 리스트 CRUD 실습을 진행했다. 

 

우선 django에서 urls.py에 들어갈 함수나 클래스 등은 views.py에서 정의한다. views.py를 만들때에는 FBV와 CBV 두가지 선택지가 존재하고 두가지 모두 같은 기능을 하는 View이며 차이는 로직을 클래스로 구현할 지 함수로 구현할 것인지에 대한 차이이다. 오늘의 실습은 CBV를 사용한 실습을 진행한 것이다.

 

클래스 기반 뷰(CBV, Class Based View)

클래스 기반 뷰(CBV)란 django가 제공하는 클래스를 활용해 구현하는 방법으로, 웹 개발 시 반복적으로 많이 구현하는 것들을 클래스로 미리 구현해서 사용자에게 제공하는 것이다. django는 자주 쓰는 기능을 클래스로 미리 구현해서 서비스를 제공하고 클래스 기반 뷰는 상속과 믹스인 기능을 이용하여 코드를 재사용하고 뷰를 체계적으로 구성할 수 있다. 쉽게 말해 뷰를 작성할때(views.py를 작성할때) 클래스 형식으로 작성하는 방식이다.

 

장점으로는 확장과 재사용이 용이하며, 다중상속이 가능하고 HTTP 메소드가 클래스 안에서 나누어 처리가 가능하다. 상속과 오버라이딩을 사용하여 중복되는 코드를 최소화시켜 가독성과 효율성을 높일 수 있다.

 

단점은 코드 흐름이 암시적이어서 읽기 어려우며 상속되고 믹스되면서 코드 이해를 위해 찾아다녀야 한다.

즉 코드를 해석하고 읽기 어렵고, 상속 및 믹스인(Mixin)으로 인해 코드를 이해하기 위해 여러 곳을 확인해봐야한다.

 

CBV 사용 방법은 용도에 따라 상속받을 제네릭 뷰를 선택하고, 해당 제네릭 뷰에서 제공하는 메소드를 용도에 맞게 상속해 쓰거나 오버라이딩하여 사용하면 된다.

 

실습

우선 admin page에서 게시글 리스트를  출력하기 위해 3개의 게시글을 생성했다. 최근 등록한 게시글이 상위에 출력되도록 역순 정렬을 설정했다. 역순으로 출력되는 것은 아래 views.py 설명에서 작성하겠다.

[게시글 3개 추가]

# blog > models.py

from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()
    head_image = models.ImageField(
        upload_to='blog/images/%Y/%m/%d/', blank=True) # 이미지 업로드
    file_upload = models.FileField(
        upload_to='blog/files/%Y/%m/%d/', blank=True) # 동영상 업로드
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateField(auto_now=True)

    def __str__(self):
        return self.title
# blog > admin.py

from django.contrib import admin
from .models import Post

admin.site.register(Post)

 

URL 접속에 맞는 뷰 페이지의 설계는 아래 처럼 작성했다.

[해당 경로 접속 시 출력할 페이지 기획]

 

해당 URL 접속 시 view 연결 방법은 아래 처럼 작성했다.

from django.urls import path
from . import views

urlpatterns = [
    path("", views.blog_list, name="blog_list"), # main 페이지
    path("<int:pk>/", views.blog_details, name="blog_details"), # detail 페이지
    path("write/", views.blog_write, name="blog_write"), # 데이터 생성 페이지
    path("edit/<int:pk>/", views.blog_edit, name="blog_edit"), # 수정 페이지
    path("delete/<int:pk>/", views.blog_delete, name="blog_delete"), # 삭제 페이지
]

 

CBV 제네릭 뷰를 상속받기 위해 해당 코드를 작성했다.

# blog > views.py

from django.views.generic import (
    ListView,
    DetailView,
    CreateView,
    UpdateView,
    DeleteView,
)

1. 게시글 리스트 출력 Page(post_list)

해당 페이지에서는 저장된 게시글의 리스트를 출력하며, 최근 작성한 내용이 가장 상위에 노출되도록 설정했고 게시글의 제목을 출력한다. 

아래 코드는 views.py에서 게시글 리스트를 출력하기 위한 코드를 작성한 것이다. PostList class는 ListView 제네릭 뷰를 상속받았고, ordering을 통해 역순으로 정럴했다. 추가로 검색기능을 위한 작업으로 get_queryset(self) 함수를 사용하여 검색된 내용을 호출하고 출력하는 코드를 작성했다.

 

# blog > views.py

class PostList(ListView): # BlogList class는 ListView를 상속 받음
    model = Post 
    ordering = "-pk" # 내림차순 정렬
    # template_name = "blog/내가_원하는_파일명.html" # 기본값: blog/post_form.html
    
    def get_queryset(self): 
        queryset = super().get_queryset() 
        q = self.request.GET.get('q', '') 
        if q:
            queryset = queryset.filter(Q(title_icontains=q) | Q(content_icontains=q)).distinct() 
        return queryset
        
 blog_list = PostList.as_view()

 

여기서 CBV를 호출하여 페이지를 랜더링하려면 as_view() 메소드는 호출해야한다. 

blog_list는 urls.py에서 호출한 views 객체로 as_view() 메소드를 사용하여 작업을 진행한다.

해당 메소드는 지정된 클래스의 인스턴스를 생성한 후에 인스턴스의 dispatch 메소드를 호출하고 어떤 메서드로(GET, POSt)로 요청되었는지 확인한다. 검사 후 요청에 맞는 동작을 진행하고 request를 전달하고 해당 뷰를 연결한다.

 

여기서 연결되는 뷰 페이지는 기본적으로 post_form.html이 연결된다. 따라서 templates에 별도로 생성한 html 파일이 있다면 작성해주면 되고 templates_name을 할당하지 않았다면 자동으로 post_form.html을 출력하도록 수행한다.

# templates > blog > post_form.html

{% comment %} create와 update 둘다 여기서 수행 {% endcomment %}
<h2>Create / Update</h2>
<form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">저장</button>
</form>

[게시글 목록 출력 페이지]

 

2. 게시글 세부 정보 출력 Page(post_detail)

해당 페이지에서는 해당 게시글의 세부 정보가 출력되고 아래 코드는 views.py에서 게시글 세부 정보를 출력하기 위한 코드를 작성한 것이다. PostDetail class는 DetailView 제네릭 뷰를 상속받았고, 별도의 template_name 선언이 없다면 기본값으로 post_detail.html을 출력한다. 출력되는 내용은 게시글의 title과 content 내용이다.

# blog > views.py

class PostDetail(DetailView): # import한 DetailView 상속
    model = Post 
    
blog_details = PostDetail.as_view()
# templates > blog > post_detail.html

<h2>{{ object.title }}</h2>
<p>{{ object.content }}</p>

[게시글 세부 정보 출력]

 

3. 게시글 데이터 생성 Page(post_form)

해당 페이지에서는 새로운 게시글을 추가하기 위한 페이지로 아래 코드는 views.py에서 새로운 게시글을 저장하기 위한 코드를 작성한 것이다. PostCreate class는 CreateView 제네릭 뷰를 상속받았고, 별도의 template_name 선언이 없다면 기본값으로 post_form.html을 출력한다. 여기서 reverse_lazy() 사용을 확인하자!

 

reverse_lazy()의 사용 이유는 object가 생성되고 나서 url로 이동하는 과정에서 reverse() 함수를 사용하는 경우 함수가 실행되는 시점에 url로 바로 이동하게 된다. 따라서 DB내역이 생성되고 url 경로로 이동하게 하기 위해서 기다렸다가 해당 경로로 이동하겠다는 함수로 reverse_lazy()를 사용한다.

 

아래 코드에서 성공적으로 데이터가 저장되었다면 blog_list를 호출하여 게시글 목록을 출력하는 페이지로 이동하게 된다.

# blog > urls.py

class PostCreate(CreateView):
    model = Post
    fields='__all__' # DB의 모든 컬럼을 가져온다
    # template_name = "blog/내가_원하는_파일명.html" # 기본값: blog/post_form.html
    success_url=reverse_lazy('blog_list')
    
blog_write = PostCreate.as_view()
# templates > blog > post_form.html

{% comment %} create와 update 둘다 여기서 수행 {% endcomment %}
<h2>Create / Update</h2>
<form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">저장</button>
</form>

[새로운 데이터 생성]

 

4. 게시글 수정 Page(post_form)

해당 페이지에서는 기존 게시글을 수정하기 위한 페이지로 아래 코드는 views.py에서 기존 게시글을 수정하고 저장하기 위한 코드를 작성한 것이다. PostUpdate class는 UpdateView 제네릭 뷰를 상속받았고, 별도의 template_name 선언이 없다면 기본값으로 post_form.html을 출력한다. 저장과 동일하게 별도의 template_name 선언이 없다면 기본값으로 post_form.html을 출력하고 성공적으로 데이터가 저장되었다면 blog_list를 호출하여 게시글 목록을 출력하는 페이지로 이동하게 된다.

# blog > urls.py 

class PostUpdate(UpdateView):
    model = Post 
    fields='__all__'
    success_url=reverse_lazy('blog_list')
    
blog_edit = PostUpdate.as_view()

 

5. 게시글 삭제 Page(post_form)

해당 페이지에서는 기존 게시글을 삭제하기 위한 페이지로 아래 코드는 views.py에서 기존 게시글을 삭제하기 위한 코드를 작성한 것이다. PostDelete class는 DeleteView 제네릭 뷰를 상속받았고, 별도의 template_name 선언이 없다면 기본값으로 post_confirm_delete.html을 출력한다. 

# blog > urls.py

class PostDelete(DeleteView):
    model = Post 
    success_url=reverse_lazy('blog_list') # 삭제되고 다 완료되지 않은 상태에서 blog_list로 넘어가지 않도록 하기 위해서 reverse_lazy를 사용
    
blog_delete = PostDelete.as_view()
# templates > blog > post_confirm_delete

<h2>Delete</h2>
<form method="post">
    {% csrf_token %}
    <p>"{{ object.title }}"을 정말로 삭제하시겠습니까?</p>
    <a href="{% url 'blog_details' object.pk %}">취소</a>
    <button type="submit">삭제</button>
</form>

[삭제 페이지]

 

Comments