Django Class Based views (5)
CBV를 좀 더 자세하게 알아보자
제네릭 뷰 중 Date View에 대해 공부 !
Generic Date View
- 날짜 기반 객체의 연/월/일 페이지로 구분해 보여주는 뷰
- ArchiveIndexVIew, YearArchiveView, MonthArchiveView, DayArchiveView, TodayArchiveView, DateDetailView 로 구성
ArchiveIndexView
- 날짜별로 최신 object를 보여주는 최상위 인덱스 페이지
- allow_future를 True로 설정하지 않는 한 미래의 날짜를 가진 object는 포함되지 않는다
1
2
3
class ArchiveIndexView(MultipleObjectTemplateResponseMixin, BaseArchiveIndexView):
"""Top-level archive of date-based items."""
template_name_suffix = '_archive'
-
MultipleObjectTemplateResponseMixin, BaseArchiveIndexView를 상속받는다
- BaseArchiveIndexView
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
class BaseArchiveIndexView(BaseDateListView): """ Base class for archives of date-based items. Requires a response mixin. """ context_object_name = 'latest' def get_dated_items(self): """Return (date_list, items, extra_context) for this request.""" qs = self.get_dated_queryset() date_list = self.get_date_list(qs, ordering='DESC') if not date_list: qs = qs.none() return (date_list, qs, {})
-
날짜 기반 아카이브에 대한 기본 클래스이다
-
BaseDateListView를 상속받는다
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
class BaseDateListView(MultipleObjectMixin, DateMixin, View): """Abstract base class for date-based views displaying a list of objects.""" allow_empty = False date_list_period = 'year' def get(self, request, *args, **kwargs): self.date_list, self.object_list, extra_context = self.get_dated_items() context = self.get_context_data( object_list=self.object_list, date_list=self.date_list, **extra_context ) return self.render_to_response(context) def get_dated_items(self): """Obtain the list of dates and items.""" raise NotImplementedError('A DateView must provide an implementation of get_dated_items()') def get_ordering(self): """ Return the field or fields to use for ordering the queryset; use the date field by default. """ return '-%s' % self.get_date_field() if self.ordering is None else self.ordering def get_dated_queryset(self, **lookup): """ Get a queryset properly filtered according to `allow_future` and any extra lookup kwargs. """ qs = self.get_queryset().filter(**lookup) date_field = self.get_date_field() allow_future = self.get_allow_future() allow_empty = self.get_allow_empty() paginate_by = self.get_paginate_by(qs) if not allow_future: now = timezone.now() if self.uses_datetime_field else timezone_today() qs = qs.filter(**{'%s__lte' % date_field: now}) if not allow_empty: # When pagination is enabled, it's better to do a cheap query # than to load the unpaginated queryset in memory. is_empty = not qs if paginate_by is None else not qs.exists() if is_empty: raise Http404(_("No %(verbose_name_plural)s available") % { 'verbose_name_plural': qs.model._meta.verbose_name_plural, }) return qs def get_date_list_period(self): """ Get the aggregation period for the list of dates: 'year', 'month', or 'day'. """ return self.date_list_period def get_date_list(self, queryset, date_type=None, ordering='ASC'): """ Get a date list by calling `queryset.dates/datetimes()`, checking along the way for empty lists that aren't allowed. """ date_field = self.get_date_field() allow_empty = self.get_allow_empty() if date_type is None: date_type = self.get_date_list_period() if self.uses_datetime_field: date_list = queryset.datetimes(date_field, date_type, ordering) else: date_list = queryset.dates(date_field, date_type, ordering) if date_list is not None and not date_list and not allow_empty: raise Http404( _("No %(verbose_name_plural)s available") % { 'verbose_name_plural': queryset.model._meta.verbose_name_plural, } ) return date_list
- 모든 날짜 기반 View에 대해 일반적인 동작을 제공하는 기본 클래스이다
- 클래스 변수
- allow_empty
- 사용 가능한 object가 없는 경우 페이지를 표시할지 여부를 지정하는 변수
- True이고, 사용할 수 있는 object가 없는경우 404페이지 대신 빈 페이지가 표시된다
- default는 False
- date_list_period
- date_list의 집계 기간을 정의하는 문자열로 year(default), month, day 중 하나여야 한다
- allow_empty
- get_dated_items
- date_list, object_list, extra_conext가 포함된 튜플을 return한다
- date_list는 가능한 날짜의 목록들을, object_list는 ojbects의 목록, extra_context는 MultipleObjectMixin이 제공하는 context data에 추가되는 context dictionary이다
- get_dated_queryset
- allow_future, allow_empty에 따라 필터링된 QuerySet을 return
- get_date_list
- QuerySet에 date_type의 유무에 따라서 date_list를 return한다
-
DateMixin
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
class DateMixin: """Mixin class for views manipulating date-based data.""" date_field = None allow_future = False def get_date_field(self): """Get the name of the date field to be used to filter by.""" if self.date_field is None: raise ImproperlyConfigured("%s.date_field is required." % self.__class__.__name__) return self.date_field def get_allow_future(self): """ Return `True` if the view should be allowed to display objects from the future. """ return self.allow_future # Note: the following three methods only work in subclasses that also # inherit SingleObjectMixin or MultipleObjectMixin. @cached_property def uses_datetime_field(self): """ Return `True` if the date field is a `DateTimeField` and `False` if it's a `DateField`. """ model = self.get_queryset().model if self.model is None else self.model field = model._meta.get_field(self.get_date_field()) return isinstance(field, models.DateTimeField) def _make_date_lookup_arg(self, value): """ Convert a date into a datetime when the date field is a DateTimeField. When time zone support is enabled, `date` is assumed to be in the current time zone, so that displayed items are consistent with the URL. """ if self.uses_datetime_field: value = datetime.datetime.combine(value, datetime.time.min) if settings.USE_TZ: value = timezone.make_aware(value) return value def _make_single_date_lookup(self, date): """ Get the lookup kwargs for filtering on a single date. If the date field is a DateTimeField, we can't just filter on date_field=date because that doesn't take the time into account. """ date_field = self.get_date_field() if self.uses_datetime_field: since = self._make_date_lookup_arg(date) until = self._make_date_lookup_arg(date + datetime.timedelta(days=1)) return { '%s__gte' % date_field: since, '%s__lt' % date_field: until, } else: # Skip self._make_date_lookup_arg, it's a no-op in this branch. return {date_field: date}
- 모든 날짜 기반 View에 대해 공통적인 동작을 제공하는 Mixin
- 클래스 변수
- date_field
- 페이지에 표시할 object 목록을 결정하는데 사용해야 하는 QuerySet 모델의 DataField나 DateTimeField의 이름
- Timezone을 사용하고, date_field가 DateTimeField인 경우 날짜는 현재 시간대인 것으로 가정한다. 그렇지 않으면 QuerySet은 최종 사용자의 Timezone에 전날이나 다음날이 포함될 수 있다
- 사용자별 Timezone 선택을 구현한 경우, 최종 사용자의 Timezone에 따라 동일한 URL에 다른 object가 표시될 수 있다. 이를 방지하려면 DateField를 date_field 속성으로 사용해야 한다
- date_field
-
MultipleObjectTemplateResponseMixin
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
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
- DisplayView에서 짚고 넘어가지 않았기 때문에 여기서 다뤄보도록 하자
django.views.generic.list.MultipleObjectTemplateResponseMixin
- ListView에 대해 template기반 response rendering을 수행하는 mixin class이다
- self.object_list는 QuerySet일 수 있지만 반드시 필요한 것은 아니다
YearArchiveView
-
연도에 대해서만 알아보자..
-
주어진 연도에 해당하는 객체 출력
1
2
3
class YearArchiveView(MultipleObjectTemplateResponseMixin, BaseYearArchiveView):
"""List of objects published in a given year."""
template_name_suffix = '_archive_year'
-
MultipleObjectTemplateResponseMixin, BaseYearArchiveView를 상속
- BaseYearArchiveView
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
class BaseYearArchiveView(YearMixin, BaseDateListView): """List of objects published in a given year.""" date_list_period = 'month' make_object_list = False def get_dated_items(self): """Return (date_list, items, extra_context) for this request.""" year = self.get_year() date_field = self.get_date_field() date = _date_from_string(year, self.get_year_format()) since = self._make_date_lookup_arg(date) until = self._make_date_lookup_arg(self._get_next_year(date)) lookup_kwargs = { '%s__gte' % date_field: since, '%s__lt' % date_field: until, } qs = self.get_dated_queryset(**lookup_kwargs) date_list = self.get_date_list(qs) if not self.get_make_object_list(): # We need this to be a queryset since parent classes introspect it # to find information about the model. qs = qs.none() return (date_list, qs, { 'year': date, 'next_year': self.get_next_year(date), 'previous_year': self.get_previous_year(date), }) def get_make_object_list(self): """ Return `True` if this view should contain the full list of objects in the given year. """ return self.make_object_list
-
클래스 변수
- make_object_list
- 해당 연도의 전체 object 목록을 검색하고 template으로 전달할지 여부를 지정하는 변수
- True이면 context에서 object 목록을 사용할 수 있게 된다
- False이면 None QuerySet이 object 목록으로 사용된다
- make_object_list
-
get_make_object_list
- context의 일부로 object 목록이 반환되는지 여부를 결정
- 해당 연도의 전체 object 목록을 포함해야 하는 경우에는 True를 return한다
-
YearMixin
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
class YearMixin: """Mixin for views manipulating year-based data.""" year_format = '%Y' year = None def get_year_format(self): """ Get a year format string in strptime syntax to be used to parse the year from url variables. """ return self.year_format def get_year(self): """Return the year for which this view should display data.""" year = self.year if year is None: try: year = self.kwargs['year'] except KeyError: try: year = self.request.GET['year'] except KeyError: raise Http404(_("No year specified")) return year def get_next_year(self, date): """Get the next valid year.""" return _get_next_prev(self, date, is_previous=False, period='year') def get_previous_year(self, date): """Get the previous valid year.""" return _get_next_prev(self, date, is_previous=True, period='year') def _get_next_year(self, date): """ Return the start date of the next interval. The interval is defined by start date <= item date < next start date. """ try: return date.replace(year=date.year + 1, month=1, day=1) except ValueError: raise Http404(_("Date out of range")) def _get_current_year(self, date): """Return the start date of the current interval.""" return date.replace(month=1, day=1)
-
1년 구성 요소에 대한 구문 분석 정보를 검색하고 제공하는데 사용
-
클래스 변수
- year_format
- 연도를 파싱할 때 사용할
strftime()
형식 - default로
%Y
이다
- 연도를 파싱할 때 사용할
strftime() 이란?
주어진 포맷에 따라 객체를 문자열로 변환
%Y는 세기가 있는 해(year)fmf 10진수로 변환한다
strptime()은 주어진 포맷에 따라 문자열을 datetime 객체로 파싱한다
- year
- 문자열로 해당 연도의 값. default로 None, 이는 연도가 다른 방법을 사용하여 결정된다는 것을 의미한다
- year_format
-
get_year_format()
- 연도를 파싱할 때 사용할 strftime() 형식을 return
코드 주석에는 strptime()을 사용한다고 하지만 django 공식문서에는 strftime()이라고 나와있다..
- default로 year_format을 return
-
참고사이트