[REAL Python – Django] – “Django – Category & Post & Slug & URL 연동(1)”

[REAL Python – Django] – “Django – Category & Post & Slug & URL 연동(1)”

4월 30, 2022

구현하고자 하는 것

예컨대 블로그 포스트가 “파이썬”의 자식 카테고리인 “기본 파이썬”에 “파이썬의 변수” 라는 제목을 가지고 있고, 이 게시물의 pk값은 1이라고 가정하겠습니다. 이러한 경우 제가 원하는 것은 포스트의 게시물의 URL "blog/python/basicpython/1+파이썬의+변수" 처럼 표시되는 것입니다.

FBV 방식보다는 CBV 방식으로

urlpatterns = [
    path('<str:first_category_slug>/', views.postByFirstCategoryListView),
    path('<str:first_category_slug>/<str:second_category_slug>/', views.postBySecondCategoryListView),  path('<str:first_category_slug>/<str:second_category_slug>/<str:third_category_slug>/', views.postByThirdCategoryListView),

사실 원하는 것은 자식 카테고리의 갯수가 무한정 있더라도 그에 상응하는 url들이 계속 만들어지는 것이었습니다. 하지만 어차피 블로그 특성상 아주 깊은 깊이의 자식 카테고리가 필요하지 않기도 하고 하여, 최대 자식 카테고리가 2개( 부모 카테고리 합 3) 까지 있을 때를 처리해 보기로 했습니다. ( – 사실은 실력의 부족..)

def postByFirstCategoryListView(request, first_category_slug):
    category = Category.objects.get(slug=first_category_slug)
    first_category_post_list = Post.objects.all()

    return render(
        request,
        'blog/first_category_post_list.html',
        {
            'post_list':first_category_post_list,
            'category':category,
        }
    )

이제 뷰를 작성해봤습니다. 세 개의 URL패턴 중 하나의 패턴에 있던 함수인데, 이런 식으로 데이터를 가져오고 템플릿을 지정해 줌으로서 화면을 띄울 수 있었죠. 하지만 이전의 포스트에서도 그랬듯이 장고에서 제공하는 클래스 기반 뷰를 활용하는 것이 더 좋다고 생각했습니다.

from django.db.models import Q
from django.views.generic import ListView, DetailView

from .models import Post, Category


class BasePostListView(ListView):
    '''
    /blog/ 로 들어오면 데이터베이스에 존재하는 모든 게시물이 표시되어야 함
    템플릿의 맨 처음에, "All Posts"
    '''
    model = Post
    ordering = '-pk'
    paginate_by = 9
    template_name = 'blog/base_post_list.html'



class PostByFirstCategoryListView(BasePostListView):
    pass


class PostBySecondCategoryListView(BasePostListView):
    pass


class PostByThirdCategoryListView(BasePostListView):
    pass


class PostDetailView(DetailView):
    pass

일단은 이런 식으로 뷰를 작성해 보도록 하겠습니다. BasePostListView 는 포스트의 전체 리스트 출력을 담당하는 뷰입니다. 카테고리별로 글들을 필터링해서 보여줄 뷰들은 기본적으로 모델이 같으므로 BasePostListView 를 상속해 만들겠습니다.

이제 슬러그와 포스트 리스트의 연동을 위해서 요구사항을 정리해 보겠습니다.

요구사항을 정리한 것.

카테고리의 부모 카테고리 얻기 (django-mptt)

위의 요구사항 중, 3번을 해결할 겁니다. 다행히 카테고리에서 django-mptt를 사용해 모델을 작성하였기 때문에, get_ancestors() 메서드를 통해서 부모 카테고리를 얻을 수 있습니다.

views.py에 아래의 코드를 작성한 후 서버를 실행해 보겠습니다.

의도대로, 카테고리의 부모 카테고리가 출력되어야 합니다.
터미널에 부모 카테고리가 나타나는 것을 알 수 있습니다.

각 카테고리의 부모 카테고리가 쿼리셋으로 반환되는 것을 볼 수 있네요. 쿼리셋은 해당 모델의 객체 목록인데, 객체 하나를 반환해야지 그것에 대한 속성을 접근할 수 있겠다고 생각했습니다.(물론 쿼리셋은 이터러블 객체이므로 for문 등을 활용해 객체를 얻어낼 수(?) 있습니다.) filter() 대신 get() 을 사용해서, 하나의 포스트의 모든 부모 카테고리를 출력해 보겠습니다.

이렇게 해 보면, 해당 포스트의 모든 부모 카테고리의 구조가 출력됩니다.
위와 같이 잘 출력됨!

이제 해야 할 작업은 (부모 카테고리의 부모 카테고리 이름의 슬러그 == URL 인자로 들어온 슬러그) 를 만족하는 게시물들을 가져오는 겁니다.

class PostByFirstCategoryListView(BasePostListView):
    # 한 포스트의 카테고리의 부모 카테고리의 부모 카테고리(할아버지 카테고리) 가 존재한다 -> 해당 포스트의 할아버지 카테고리 == 1차 카테고리가 됨
    # 3차 카테고리에만 글을 쓸 수 있도록 할 것이므로, 포스트의->카테고리의->부모카테고리의->부모카테고리 == URL 인자인 포스트들 뽑아와야 함
    def get_queryset(self):
        first_category_from_url = self.kwargs['first_category_slug']
        selected_post_list = Post.objects.filter(
            Q(category__parent__parent__name=first_category_from_url) | Q(category__parent__parent__slug=first_category_from_url)
            # URL 인자 == 할아버지 카테고리이거나 URL인자 == 할아버지 카테고리의 slug인 포스트들을 가져옴.
        ).distinct()
        return selected_post_list

위처럼 filter() 를 사용해서 원하는 게시글들만 가져와 쿼리셋을 작성할 수 있습니다. 사실 이것을 짜는 것에 있어서 시간이 굉장히 많이 걸렸는데.. 짜고 나니 생각보다 간단하게 구현이 되었네요. ( 사실 맞는 것인지는 약간 헷갈립니다. ) 2차, 3차 카테고리는 다음에 구현하는 것으로..

Leave A Comment

Avada Programmer

Hello! We are a group of skilled developers and programmers.

Hello! We are a group of skilled developers and programmers.

We have experience in working with different platforms, systems, and devices to create products that are compatible and accessible.