Django 의 generic detail view 에 대해 알아보자

김규민·2025년 5월 7일

Django CBV

목록 보기
2/2

from django.views.generic.detail

detail view 는 원하는 데이터 한 가지에 대한 결과를 조회하는 역할을 수행하는 view 입니다.

하나의 데이터를 반환하는 역할을 수행하기 때문에 웹 사이트의 마이페이지나 제품 상세 페이지 등 단일 객체에 대한 view 로 사용하기 용이합니다.

SingleObjectMixin

class SingleObjectMixin(ContextMixin):
    """
    Provide the ability to retrieve a single object for further manipulation.
    """

    model = None
    queryset = None
    slug_field = "slug"
    context_object_name = None
    slug_url_kwarg = "slug"
    pk_url_kwarg = "pk"
    query_pk_and_slug = False
  • model : 사용할 Django model 클래스를 지정합니다.
  • queryset : 조회에 사용할 쿼리셋을 명시적으로 저장합니다.
  • slug_field = “slug” : slug 기반 조회 시 사용할 필드명을 지정합니다.
  • context_object_name : template context 에 객체를 담을 때 사용할 이름을 명시합니다.
  • slug_url_kwarg = “slug” : URLConf 에서 slug 값을 꺼낼 때 사용할 키 이름입니다.
  • pk_url_kwarg = “pk” : URLConf 에서 pk 값을 꺼낼 때 사용할 키 이름입니다.

slug는 사람이 읽을 수 있고 URL에 적합한 문자열 식별자이며, 일반적으로 데이터의 제목(title) 등을 기반으로 생성되어 웹 주소(URL)의 일부로 사용됩니다.

get_object

Return the object the view is displaying.
Require self.queryset and a pk or slug argument in the URLconf.
Subclasses can override this to return any object.

def get_object(self, queryset=None):
    # Use a custom queryset if provided; this is required for subclasses
    # like DateDetailView
    if queryset is None:
        queryset = self.get_queryset()

    # Next, try looking up by primary key.
    pk = self.kwargs.get(self.pk_url_kwarg)
    slug = self.kwargs.get(self.slug_url_kwarg)
    if pk is not None:
        queryset = queryset.filter(pk=pk)

    # Next, try looking up by slug.
    if slug is not None and (pk is None or self.query_pk_and_slug):
        slug_field = self.get_slug_field()
        queryset = queryset.filter(**{slug_field: slug})

    # If none of those are defined, it's an error.
    if pk is None and slug is None:
        raise AttributeError(
            "Generic detail view %s must be called with either an object "
            "pk or a slug in the URLconf." % self.__class__.__name__
        )

    try:
        # Get the single item from the filtered queryset
        obj = queryset.get()
    except queryset.model.DoesNotExist:
        raise Http404(
            _("No %(verbose_name)s found matching the query")
            % {"verbose_name": queryset.model._meta.verbose_name}
        )
    return obj

지정된 pk 나 slug 를 검색어로 데이터베이스에서 해당하는 데이터를 조회하고, 반환합니다.

만약 pk 나 slug 중 하나도 없다면 에러를 발생시킵니다. 조회된 데이터가 존재하지 않다면 http 404 에러를 발생합니다.


get_queryset

Return the QuerySet that will be used to look up the object.
This method is called by the default implementation of get_object() and
may not be called if get_object() is overridden.

def get_queryset(self):
    if self.queryset is None:
        if self.model:
            return self.model._default_manager.all()
        else:
            raise ImproperlyConfigured(
                "%(cls)s is missing a QuerySet. Define "
                "%(cls)s.model, %(cls)s.queryset, or override "
                "%(cls)s.get_queryset()." % {"cls": self.__class__.__name__}
            )
    return self.queryset.all()

인스턴스에 저장된 쿼리셋이 없다면 설정된 model의 에 대해 쿼리셋을 생성합니다.

만약 model 이 설정되지 않았다면 에러가 발생합니다.

  • queryset

쿼리셋은 조회된 데이터가 아닌 쿼리를 담고 있는 객체입니다.


get_slug_field

def get_slug_field(self):
    """Get the name of a slug field to be used to look up by slug."""
    return self.slug_field

slug_field 를 반환합니다.


get_context_object_name

def get_context_object_name(self, obj):
    """Get the name to use for the object."""
    if self.context_object_name:
        return self.context_object_name
    elif isinstance(obj, models.Model):
        return obj._meta.model_name
    else:
        return None

context_object_name 을 반환합니다.

설정되지 않았다면 자동으로 model 이름을 소문자로 변환하여 사용합니다.


get_context_data

def get_context_data(self, **kwargs):
    """Insert the single object into the context dict."""
    context = {}
    if self.object:
        context["object"] = self.object
        context_object_name = self.get_context_object_name(self.object)
        if context_object_name:
            context[context_object_name] = self.object
    context.update(kwargs)
    return super().get_context_data(**context)

get_object 메소드로 초기화된 self.object 를 context 로 반환합니다.

SingleObjectMixin 는 ContextMixin 를 상속했기 때문에 get_context_data 메소드를 사용할 수 있습니다.


BaseDetailView

class BaseDetailView(SingleObjectMixin, View):
    """A base view for displaying a single object."""

    def get(self, request, *args, **kwargs):
        self.object = self.get_object()
        context = self.get_context_data(object=self.object)
        return self.render_to_response(context)

SingleObjectMixin 의 기능을 view 의 형태로 사용할 수 있도록 View 를 함께 상속한 클래스입니다.

http method 중 get 에 대하여 SingleObjectMixin 의 기능을 제공합니다.

  • render_to_response

BaseDetailView 가 상속한 두 클래스에는 render_to_response 가 선언되지 않았습니다.

따라서 문법적으로 에러가 발생합니다.

다만, 이 클래스는 다른 믹스인과 함께 조합하여 사용되는 전제를 가지고 있기 때문에, 해당 믹스인의 render_to_response 를 사용할 수 있습니다.

Base 라는 이름처럼 다른 view 의 부모 클래스가 되는 추상 클래스이다.

SingleObjectTemplateResponseMixin

Return a list of template names to be used for the request. May not be called if render_to_response() is overridden.
Return the following list:

  • the value of template_name on the view (if provided)
  • the contents of the template_name_field field on the object instance that the view is operating upon (if available)
  • <app_label>/<model_name><template_name_suffix>.html
class SingleObjectTemplateResponseMixin(TemplateResponseMixin):
    template_name_field = None
    template_name_suffix = "_detail"

    def get_template_names(self):
        try:
            names = super().get_template_names()
        except ImproperlyConfigured:
            # If template_name isn't specified, it's not a problem --
            # we just start with an empty list.
            names = []

            # If self.template_name_field is set, grab the value of the field
            # of that name from the object; this is the most specific template
            # name, if given.
            if self.object and self.template_name_field:
                name = getattr(self.object, self.template_name_field, None)
                if name:
                    names.insert(0, name)

            # The least-specific option is the default <app>/<model>_detail.html;
            # only use this if the object in question is a model.
            if isinstance(self.object, models.Model):
                object_meta = self.object._meta
                names.append(
                    "%s/%s%s.html"
                    % (
                        object_meta.app_label,
                        object_meta.model_name,
                        self.template_name_suffix,
                    )
                )
            elif getattr(self, "model", None) is not None and issubclass(
                self.model, models.Model
            ):
                names.append(
                    "%s/%s%s.html"
                    % (
                        self.model._meta.app_label,
                        self.model._meta.model_name,
                        self.template_name_suffix,
                    )
                )

            # If we still haven't managed to find any template names, we should
            # re-raise the ImproperlyConfigured to alert the user.
            if not names:
                raise

        return names
  • get_template_names

TemplateResponseMixin 에 선언된 get_template_names 메소드를 통해 미리 설정된 template name 을 확인합니다.

만약 미리 설정된 template name 이 없다면, 다음 조건에 따라 template name 을 자동으로 설정합니다.

self.objectget_object() 메소드에 의해 설정되는 인스턴스입니다.

  1. self.object 가 존재하고, self.template_name_field 가 설정되어 있는 경우

이 경우는 object 가 template_name_field 속성을 가지고 있는지만 확인합니다.

object 에 template_name_field 에 해당하는 속성이 존재한다면, names 의 첫 번째 인덱스에 self.template_name_field 룰 추가합니다.

  1. self.object 가 Model 클래스의 인스턴스인 경우

self.object 의 meta 정보 중에 앱 이름과 모델 이름, template_name_suffixnames 에 추가합니다.

  1. self.model 속성이 있고, Model 클래스의 자식 클래스인 경우

view 에서 model 변수를 설정한 경우에 실행되는 조건입니다. object 가 설정되지 않은 경우에 안전장치와 같은 역할을 수행하며, 설정된 model 의 meta 정보를 names 에 추가합니다.

template name 을 결정하는 경우는 다음으로 정리됩니다.

  • self.object 가 존재하는지
  • self.object 에 template_name_field 가 있는지
  • self.object 가 Model 클래스인지
  • self.object 가 없지만, 클래스에 model 속성이 설정되었는지

아무것도 설정되지 않았다면 get_template_names() 는 에러를 반환합니다.


DetailView

class DetailView(SingleObjectTemplateResponseMixin, BaseDetailView):
    """
    Render a "detail" view of an object.

    By default this is a model instance looked up from `self.queryset`, but the
    view will support display of *any* object by overriding `self.get_object()`.
    """

앞서 정리했던 mixin 과 view 를 상속하여 하나의 객체에 대한 정보와 template 을 제공하는 detail view 로 완성합니다.

DetailView
├── SingleObjectTemplateResponseMixin
│   └── TemplateResponseMixin
│
├── BaseDetailView
    ├── SingleObjectMixin
    │   └── ContextMixin
    └── View

DetailView 가 상속하는 mixin 과 view 를 정리한 계층도입니다.

2가지의 클래스를 상속하는 구조에서 Django 의 MVT 패턴이 드러납니다.

  • BaseDetailVIew 를 통해 구현하는 기능
    • 하나의 데이터 개체를 조회
    • 데이터를 template 에 전달하기 위한 context 생성 및 전달
    • http get 에 대응하는 메소드 제공
  • SingleObjectTemplateResponseMixin 을 통해 구현하는 기능
    • 데이터 개체에 적절한 template 조회
    • 설정된 template 제공

DetailView 의 사용법

view 에서 model 에 접근하고, template 을 제어하는 모습으로 보입니다.

DetailView 가 상속하는 클래스들의 클래스 변수들을 알고 있어야 합니다.

필수로 설정해야 하는 변수

클래스변수설명
SingleObjectMixinmodel조회할 모델 클래스 지정
SingleObjectMixinqueryset조회할 QuerySet 지정 (선택적으로 model 대신 사용 가능)

선택적으로 설정하는 변수

클래스변수기본값설명
TemplateResponseMixintemplate_nameNone사용할 템플릿 경로를 직접 지정
SingleObjectTemplateResponseMixintemplate_name_fieldNone객체의 특정 필드에서 템플릿 경로를 가져오도록 지정
SingleObjectTemplateResponseMixintemplate_name_suffix"_detail"템플릿 자동 유추 시 뒤에 붙는 접미사
SingleObjectMixinslug_field"slug"슬러그 조회 시 사용할 모델 필드명
SingleObjectMixinslug_url_kwarg"slug"URLConf에서 slug를 꺼낼 키 이름
SingleObjectMixinpk_url_kwarg"pk"URLConf에서 pk를 꺼낼 키 이름
SingleObjectMixinquery_pk_and_slugFalsepk와 slug를 동시에 사용해 조회할지 여부
SingleObjectMixincontext_object_nameNone템플릿에서 사용할 객체 이름 지정
ContextMixinextra_contextNone템플릿에 추가로 전달할 context 딕셔너리
profile
To Infinity, and Beyond!

0개의 댓글