[Django] Base views / Generic display views / Generic date views

57649 단어 djangodjango

🎈Base Views

코드 보기

View

  • 최상위 부모 제네릭 뷰 클래스

  • 직접 쓸 일은 거의 없지만 다른 클래스들을 사용함으로서 간접적으로 항상 사용되고 있음

  • http method별로 지정 이름의 멤버 함수를 호출하도록 구현

  • __init__: as_view에 인자를 넘겨주게 되면 인자가 **kwargs로 넘어와 dict를 순회하며 해당 설정을 해주게 됨

def __init__(self, **kwargs):
        """
        Constructor. Called in the URLconf; can contain helpful extra
        keyword arguments, and other things.
        """
        # Go through keyword arguments, and either save their values to our
        # instance, or raise an error.
        for key, value in kwargs.items():
            setattr(self, key, value)
  • as_view: view를 하나 만들어서 return

  • dispatch: as_view 메소드 내부에서 호출
    request method를 확인하고 http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'] 안에 있는 정상적인 method라면 request에서 이를 가져옴
    정상적인 method가 아니라면 http_method_not_allowed 호출

def dispatch(self, request, *args, **kwargs):
        # Try to dispatch to the right method; if a method doesn't exist,
        # defer to the error handler. Also defer to the error handler if the
        # request method isn't on the approved list.
        if request.method.lower() in self.http_method_names:
            handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
        else:
            handler = self.http_method_not_allowed
        return handler(request, *args, **kwargs)

TemplateView

class TemplateView(TemplateResponseMixin, ContextMixin, View):
    """
    Render a template. Pass keyword arguments from the URLconf to the context.
    """
    def get(self, request, *args, **kwargs):
        context = self.get_context_data(**kwargs)
        return self.render_to_response(context)

  • TemplateResponseMixin
    template의 기본적인 설정들, template 이름, rendering하는 과정 등 담당
class TemplateResponseMixin:
    """A mixin that can be used to render a template."""
    template_name = None
    template_engine = None
    response_class = TemplateResponse
    content_type = None

    def render_to_response(self, context, **response_kwargs):
        """
        Return a response, using the `response_class` for this view, with a
        template rendered with the given context.
        Pass response_kwargs to the constructor of the response class.
        """
        response_kwargs.setdefault('content_type', self.content_type)
        return self.response_class(
            request=self.request,
            template=self.get_template_names(),
            context=context,
            using=self.template_engine,
            **response_kwargs
        )

    def get_template_names(self):
        """
        Return a list of template names to be used for the request. Must return
        a list. May not be called if render_to_response() is overridden.
        """
        if self.template_name is None:
            raise ImproperlyConfigured(
                "TemplateResponseMixin requires either a definition of "
                "'template_name' or an implementation of 'get_template_names()'")
        else:
            return [self.template_name]

  • ContextMixin
    정적인 context에 대해서는 extra_context 변수 사용, 동적으로 사용해야하는 경우 get_context_data 사용
class ContextMixin:
    """
    A default context mixin that passes the keyword arguments received by
    get_context_data() as the template context.
    """
    extra_context = None

    def get_context_data(self, **kwargs):
        kwargs.setdefault('view', self)
        if self.extra_context is not None:
            kwargs.update(self.extra_context)
        return kwargs

예시

#방법 1
urlpatterns = [
	path('', TemplateView.as_view(template_name='root.html'), name='root'),
]

#방법 2

class RootView(TemplateView):
  template_name = 'root.html'

urlpatterns = [
  path('', RootView.as_view(), name='root'),
]

RedirectView

class RedirectView(View):
    """Provide a redirect on any GET request."""
    permanent = False
    url = None
    pattern_name = None
    query_string = False

    def get_redirect_url(self, *args, **kwargs):
        """
        Return the URL redirect to. Keyword arguments from the URL pattern
        match generating the redirect request are provided as kwargs to this
        method.
        """
        if self.url:
            url = self.url % kwargs
        elif self.pattern_name:
            url = reverse(self.pattern_name, args=args, kwargs=kwargs)
        else:
            return None

        args = self.request.META.get('QUERY_STRING', '')
        if args and self.query_string:
            url = "%s?%s" % (url, args)
        return url

    def get(self, request, *args, **kwargs):
        url = self.get_redirect_url(*args, **kwargs)
        if url:
            if self.permanent:
                return HttpResponsePermanentRedirect(url)
            else:
                return HttpResponseRedirect(url)
        else:
            logger.warning(
                'Gone: %s', request.path,
                extra={'status_code': 410, 'request': request}
            )
            return HttpResponseGone()

    def head(self, request, *args, **kwargs):
        return self.get(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        return self.get(request, *args, **kwargs)

    def options(self, request, *args, **kwargs):
        return self.get(request, *args, **kwargs)

    def delete(self, request, *args, **kwargs):
        return self.get(request, *args, **kwargs)

    def put(self, request, *args, **kwargs):
        return self.get(request, *args, **kwargs)

    def patch(self, request, *args, **kwargs):
        return self.get(request, *args, **kwargs)
  • parmanet

    • True: 301 응답(영구적 이동)
    • False: 302 응답(임시 이동) - default
  • url

  • pattern_name: URL Reverse를 수행할 문자열

  • query_string: QueryString을 그대로 넘길 것인지?

  • get: get_redirect_url을 호출하여 url을 얻음
    url을 얻지 못하면 error 출력, 제대로 얻었다면 parmanent에 의해 분기 처리

  • get_redirect_url: 클래스 변수 url을 통해 직접 url을 받아오거나 pattern_name을 통해 url의 name 값을 받아왔으면 이를 return

예시

  • myproject/urls.py
urlpatterns = [
  path('', RedirectView.as_view(pattern_name='instagram:post_list', name='root'),
]
  • instagram/urls.py
app_name = 'instagram'

urlpatterns = [
  path('', views.post_list, name='post_list'),
]

🎈 Generic display views

DetailView

코드 보기
1개 모델의 1개 Object에 대한 템플릿 처리
각 상세페이지 보여주는 역할

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()`.
    """
  • SingleObjectTemplateResponseMixin
    template_name 값을 설정하면 이에 맞는 templates 반환
    따로 설정해주지 않으면 앱이름/모델이름_detail.html

  • BaseDetailView
    get 요청에 대해 db로부터 하나의 object를 가져와 이를 context에 담아 rendering

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

    • pk_url_kwarg, slug_url_kwargs: 기본값으로 pk, slug로 지정되어 있음 (urls에서 넘겨주는 인자 이름이 다르다면 해당 값 변경해주기)

    • get_object: 모델로부터 object 얻어옴

    • get_queryset: 기본적으로 모델의 모든 객체 반환, 위에 queryset 지정되어있으면 queryset에 맞게 객체 return

    • get_context_data: 모델의 이름으로 context를 넘겨주는 역할

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

    def get_object(self, queryset=None):
        """
        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.
        """
        # 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

    def get_queryset(self):
        """
        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.
        """
        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()

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

    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

    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)

예시

  • instagram/views.py
class PostDetailView(DetailView):
  model = Post
  
  def get_queryset(self):
    qs = super().get_queryset()
    if not self.request.user.is_authoricated:
      qs = qs.filter(is_public=True)
    return qs


post_detail = PostDetailView.as_view()

ListView

코드 보기
1개 모델에 대한 List 템플릿 처리

class ListView(MultipleObjectTemplateResponseMixin, BaseListView):
    """
    Render some list of objects, set by `self.model` or `self.queryset`.
    `self.queryset` can actually be any iterable of items, not just a queryset.
    """
  • MultipleObjectTemplateResponseMixin
    내부적으로 pagination이 구현되어있음
class MultipleObjectTemplateResponseMixin(TemplateResponseMixin):
    """Mixin for responding with a template and list of objects."""
    template_name_suffix = '_list'

    def get_template_names(self):
        """
        Return a list of template names to be used for the request. Must return
        a list. May not be called if render_to_response is overridden.
        """
        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 the list is a queryset, we'll invent a template name based on the
        # app and model name. This name gets put at the end of the template
        # name list so that user-supplied names override the automatically-
        # generated ones.
        if hasattr(self.object_list, 'model'):
            opts = self.object_list.model._meta
            names.append("%s/%s%s.html" % (opts.app_label, opts.model_name, self.template_name_suffix))
        elif not names:
            raise ImproperlyConfigured(
                "%(cls)s requires either a 'template_name' attribute "
                "or a get_queryset() method that returns a QuerySet." % {
                    'cls': self.__class__.__name__,
                }
            )
        return names

  • BaseListView
class BaseListView(MultipleObjectMixin, View):
    """A base view for displaying a list of objects."""
    def get(self, request, *args, **kwargs):
        self.object_list = self.get_queryset()
        allow_empty = self.get_allow_empty()

        if not allow_empty:
            # When pagination is enabled and object_list is a queryset,
            # it's better to do a cheap query than to load the unpaginated
            # queryset in memory.
            if self.get_paginate_by(self.object_list) is not None and hasattr(self.object_list, 'exists'):
                is_empty = not self.object_list.exists()
            else:
                is_empty = not self.object_list
            if is_empty:
                raise Http404(_("Empty list and '%(class_name)s.allow_empty' is False.") % {
                    'class_name': self.__class__.__name__,
                })
        context = self.get_context_data()
        return self.render_to_response(context)

예시
django-bootstrap-v5를 사용하여 pagination

  • instagram/views.py
post_list = ListView.as_view(model=Post, paginate_by=2)
  • templates/instagram/post_list.html
  {% if is_paginated%}
    {% bootstrap_pagination page_obj size="small" justify_content="center"%}
  {% endif %}


🎈Generic date views

ArchiveIndexView

날짜별로 최신 개체를 표시하는 최상위 인덱스 페이지

옵션

  • model
  • date_field: 정렬 기준 필드
  • date_list_period (디폴트: year)

context

  • latest: Queryset
  • date_list: 등록된 Record의 년도 목록

디폴트 template_name_suffix: _archive

예시

  • views.py
from django.views.generic import ArchiveIndexView

post_archive = ArchiveIndexView.as_view(model=Post, date_field='post_detail')
  • urls.py
urlpatterns = [
  path('archive/', views.post_archive, name='post_archive'),
]
  • post_archive.html
{{latest}}

{% for date in date_list %}
  {{date.year}}
{% endfor %}

YearArchiveView

주어진 연도의 목록

옵션

  • model
  • date_field
  • date_list_period (디폴트: mohth)
  • make_object_list

context

  • year
  • previos_year
  • next_year
  • date_list
  • object_list

디폴트 template_name_suffix: _archive_year


MonthArchiveView

옵션

  • month_format

context

  • month
  • previous_month
  • next_month
  • date_list
  • object_list

디폴트 template_name_suffix: archive_month

참고
https://docs.djangoproject.com/en/3.2/ref/class-based-views/generic-date-based/#django.views.generic.dates.BaseArchiveIndexView
https://ssungkang.tistory.com/entry/Django-CBV-1-CBV-%EC%99%80-Base-Views?category=320582

좋은 웹페이지 즐겨찾기