일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- javascript
- 알고리즘 문제
- 파이썬
- 알고리즘
- Git
- PYTHON
- form
- DRF
- 백준
- Baekjoon
- MAC
- django ORM
- Algorithm
- AWS
- API
- 파이썬 알고리즘
- CSS
- react
- java
- 알고리즘 풀이
- es6
- django rest framework
- web
- js
- 장고
- 알고리즘 연습
- Django
- django widget
- HTML
- c++
- Today
- Total
수학과의 좌충우돌 프로그래밍
[Django] CBV (2) Generic display views 본문
Generic display views
에는 DetailView
와 ListView
가 있습니다.
DetailView 는 각 상세 페이지를 나타내는데 있어서 ListView 는 전체적인 페이지를 나타내는데 있어서 굉장히 효과적입니다. 두 클래스를 알아보도록 합시다.
DetailView
-
실습
전체적인 코드를 살펴보기 전에 어떻게 사용되고 있는지 예시를 먼저 보도록 하겠습니다.
DetailView
의 역할은 각 상세페이지를 보여주는 역할을 합니다. 간단히Post
모델을 정의하고 이 상세페이지를 보여주도록 하겠습니다.# models.py from django.db import models class Post(models.Model): title = models.CharField(max_length=20) content = models.TextField() created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True)
# urls.py from django.urls import path from . import views urlpatterns = [ path('<int:pk>', views.post_detail, name="post_detail"), ]
<!-- core/post_detail.html --> <h1>글의 상세 페이지</h1> <h2>{{ post.title }}</h2> <div>{{post.content}}</div>
# views.py from django.views.generic import DetailView from .models import Post post_detail = DetailView.as_view(model=Post)
-
DetailView 살펴보기
전체적은 코드를 살펴보며 위의 구현을 이해해보도록 하겠습니다.
- 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()`. """
DetailView
는 아무 내용도 없이SingleObjectTemplateResponseMixin
,BaseDetailView
두 개의 클래스를 상속받고 있습니다.- 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)
get 요청에 대해서 db 로 부터 하나의 object를 가져와서 이를 context 에 담아 rendering 해줍니다.
BaseDetailView
는 다시SingleObjectMixin
의 상속을 받습니다.- 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 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)
가장 위에 여러 클래스 변수들이 지정이 되어있습니다.
-
pk_url_kwarg , slug_url_kwarg
두 변수에 대해서 이미 기본값으로
pk
,slug
로 지정이 되어있습니다. 이는 urls 에서 넘겨주는 인자의 이름을 뜻합니다. 즉 아래와 같이 id 로 넘겨주려면 해당 값을 id 로 바꿔야 합니다.# urls.py urlpatterns = [ path('<int:id>', views.post_detail, name="post_detail"), ] # views.py post_detail = DetailView.as_view(model=Post, pk_url_kwarg='id')
-
get_object
모델로 부터 object 를 얻어오는 메소드입니다. 위에서 지정한 클래스 변수들에 대해서 queryset, pk, slug 을 설정하여 원하는 queryset 을 만들어줍니다. 이를 통해 모델에서 객체를 가져오고 실패할 경우 에러를 표시합니다.
-
get_queryset
기본적으로는 모델의 모든 객체들을 전부 반환하지만 위에서 qureyset 이 지정이 되어 있다면 그 queryset 에 맞게 객체를 return 합니다.
-
get_context_data
위의 예시의 경우에는
Post
모델에 대해서 context 를 따로 넘겨주지 않았지만 templates 에서post
를 사용하였습니다.이렇게 모델의 이름으로 context 를 넘겨주는 역할을 합니다. 모델의 이름 외에도object
라는 이름으로도 사용이 가능합니다.
-
SingleObjectTemplateResponseMixin
전체적으로 중요하지 않은 코드가 많아서 따로 코드를 첨부하지는 않겠습니다.
template_name
값을 따로 설정한다면 이에 맞는 templates 를 반환해주지만 따로 설정을 하지 않는다면 default 값이 있습니다.앱이름/모델이름(소문자)_detail.html
와 같은 꼴을 가집니다. 위 예시에서 앱은core
,Post
모델 이므로core/post_detail.html
이 됩니다.
http://127.0.0.1:8000/home/?page=2
ListView
ListView
는 1개의 모델에 대해서 list 템플릿 처리를 해줍니다. SNS의 경우에 main 화면에서 여러 Post 들을 띄우기 위해서 사용한다고 생각하면 됩니다. 또한 ListView 의 장점은 자동으로 페이징 처리를 지원해줌으로서 편리하게 사용할 수 있습니다. 또한 모델이름(소문자)_list
의 queryset 을 templates 에 전달합니다. 여기서는 post_list
가 됩니다.
-
실습
# urls.py from django.urls import path from . import views urlpatterns = [ path('home/', views.post_list, name="post_list"), ]
<!-- core/post_list.html --> <h1>list 페이지</h1> {% for post in post_list %} <div>{{post.title}}</div> <div>{{post.content}}</div> {% endfor %}
# views.py from django.views.generic import ListView from .models import Post post_list = ListView.as_view( model=Post, paginate_by=2, )
다음과 같이
paginate_by
로 페이지네이션을 쉽게 구현 할 수 있습니다. 다음과 같이 값을 2로 준다면 한 페이지의 두 개의 객체만 표시하게 됩니다. 각 페이지는 url 뒤에 querystring으로?page=페이지수
로 접근할 수 있습니다.
-
ListView 살펴보기
ListView
는 어떻게 구성되어 있는지 전체적인 코드를 살펴보도록 하겠습니다.- ListView
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. """
DetailView
에서 처럼MultipleObjectTemplateResponseMixin
와BaseListView
를 상속받습니다.- 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)
get 요청을 받아 queryset 으로 object_list 를 만들어줍니다.
- MultipleObjectMixin
class MultipleObjectMixin(ContextMixin): """A mixin for views manipulating multiple objects.""" allow_empty = True queryset = None model = None paginate_by = None paginate_orphans = 0 context_object_name = None paginator_class = Paginator page_kwarg = 'page' ordering = None def get_queryset(self): """ Return the list of items for this view. The return value must be an iterable and may be an instance of `QuerySet` in which case `QuerySet` specific behavior will be enabled. """ if self.queryset is not None: queryset = self.queryset if isinstance(queryset, QuerySet): queryset = queryset.all() elif self.model is not None: queryset = 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__ } ) ordering = self.get_ordering() if ordering: if isinstance(ordering, str): ordering = (ordering,) queryset = queryset.order_by(*ordering) return queryset def get_ordering(self): """Return the field or fields to use for ordering the queryset.""" return self.ordering def paginate_queryset(self, queryset, page_size): """Paginate the queryset, if needed.""" paginator = self.get_paginator( queryset, page_size, orphans=self.get_paginate_orphans(), allow_empty_first_page=self.get_allow_empty()) page_kwarg = self.page_kwarg page = self.kwargs.get(page_kwarg) or self.request.GET.get(page_kwarg) or 1 try: page_number = int(page) except ValueError: if page == 'last': page_number = paginator.num_pages else: raise Http404(_("Page is not 'last', nor can it be converted to an int.")) try: page = paginator.page(page_number) return (paginator, page, page.object_list, page.has_other_pages()) except InvalidPage as e: raise Http404(_('Invalid page (%(page_number)s): %(message)s') % { 'page_number': page_number, 'message': str(e) }) def get_paginate_by(self, queryset): """ Get the number of items to paginate by, or ``None`` for no pagination. """ return self.paginate_by def get_paginator(self, queryset, per_page, orphans=0, allow_empty_first_page=True, **kwargs): """Return an instance of the paginator for this view.""" return self.paginator_class( queryset, per_page, orphans=orphans, allow_empty_first_page=allow_empty_first_page, **kwargs) def get_paginate_orphans(self): """ Return the maximum number of orphans extend the last page by when paginating. """ return self.paginate_orphans def get_allow_empty(self): """ Return ``True`` if the view should display empty lists and ``False`` if a 404 should be raised instead. """ return self.allow_empty def get_context_object_name(self, object_list): """Get the name of the item to be used in the context.""" if self.context_object_name: return self.context_object_name elif hasattr(object_list, 'model'): return '%s_list' % object_list.model._meta.model_name else: return None def get_context_data(self, *, object_list=None, **kwargs): """Get the context for this view.""" queryset = object_list if object_list is not None else self.object_list page_size = self.get_paginate_by(queryset) context_object_name = self.get_context_object_name(queryset) if page_size: paginator, page, queryset, is_paginated = self.paginate_queryset(queryset, page_size) context = { 'paginator': paginator, 'page_obj': page, 'is_paginated': is_paginated, 'object_list': queryset } else: context = { 'paginator': None, 'page_obj': None, 'is_paginated': False, 'object_list': queryset } if context_object_name is not None: context[context_object_name] = queryset context.update(kwargs) return super().get_context_data(**context)
내부적으로 pagination 이 구현이 되어있기 떄문에 이를 위한 함수를 덕에 전체적인 코드길이가 길어졌습니다.
-
get_context_data
context 를 얻는 과정입니다. pagination 을 할 경우에는 queryset 도 부분적으로 나눠서 가져와야 할 것이고, 여러가지로 바뀌어야 하는 부분이 많습니다. 이 부분이 if 문을 통한 분기로 구현이 되어있습니다.
아래 링크를 통해 pagination 에 대해 더 알아보실 수 있습니다.
'웹프로그래밍 > Django' 카테고리의 다른 글
[Django] HttpRequest, HttpResponse (0) | 2019.08.08 |
---|---|
[Django] FBV 와 CBV 의 decorators 사용법 (0) | 2019.08.08 |
[Django] CBV (1) CBV 와 Base Views (2) | 2019.08.06 |
[Django] media 파일 업로드하기 (7) | 2019.07.30 |
[Django] static 파일 다루기 (0) | 2019.07.30 |