Django 기본 CBV API (Generic display views)
2022. 1. 10. 16:31ㆍ강의 정리/Django Views
반응형
장고(Django)를 배우기 시작한 입문자이시거나, 또는 배우고 싶은 생각이 있으신 분은 위 출처의 강의를 적극 추천드립니다!!!
- Generic Display View : 객체의 리스트를 보여주거나, 특정 객체의 상세 정보를 보여준다.
- Generic Display View
- DetailView: 객체 하나에 대한 상세한 정보를 보여준다.
- ListView: 조건에 맞는 여러 개의 객체를 보여준다.
DetailView: Model 객체 하나에 대한 상세한 정보를 보여준다.
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)
- get_object
- 모델로부터 object를 얻어오는 메소드이다
- 위에서 지정한 클래스 변수들에 대해서 queryset, pk, slug를 설정해서 원하는 queryset을 만들어주는 역할이다
- 이 메소드를 통해 객체를 가져오고, 실패할 경우 에러를 표시한다
- get_queryset
- 클래스 변수 queryset이 지정되어 있으면 그 queryset에 맞게 객체를 return한다
- 기본적으로는 모델의 모든 객체들을 전부 반환한다
- get_context_data
- 모델의 이름으로 context를 넘겨주는 역할이다
- 모델 이름 외에도 object라는 이름으로도 사용 가능
- 1개 모델의 1개 Object에 대한 템플릿 처리
- 모델명소문자 이름의 Model Instance를 템플릿에 전달
- 지정 pk 혹은 slug에 대응하는 Model Instance
- slug란 URL의 구성요소로 웹사이트의 특정 페이지를 사람이 읽기 쉬운 형식의 식별자로 바꿔주는 것
- 지정 pk 혹은 slug에 대응하는 Model Instance
- template_name 인자가 지정되지 않았다면, 모델명으로 템플릿 경로 유추
#예를 들어서 로그인 한 유저만 특정 권한(글, 게시판 접근 가능)을 부여하고 싶다면 get_queryset이라는 함수를 구현해야한다
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()
class PostDetailView(DetailView):
model = Post
def get_queryset(self):
# 오버라이딩
qs = super().get_queryset()
# 만약 유저가 인증, 로그인 하지않았다면
if not self.request.user.is_authenticated:
# 공개된 것만 확인하도록
qs = qs.filter(is_public=True)
return qs
사용방법
# instagram/views.py
from django.views.generic import DetailView
post_detail = DetailView.as_view(
model = Post,
queryset = Post.objects.all()
)
- model은 템플릿에 넘겨줄 model Instance를 의미한다
- queryset은 넘겨줄 model Instance를 특정 filter등으로 더 정돈되게 템플릿에 넘겨준다
# instagram/templates/instagram/post_detail.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2>Author : {{ post.author }}</h2>
{% if post.photo %}
<div>
<img src="{{ post.photo.url }}" />
</div>
{% endif %}
{{ post.message }}
</body>
</html>
ListView: 조건에 맞는 여러 개의 객체를 보여준다.
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)
- get_context_data
- context 를 얻는 과정이며, pagination 을 할 경우에는 queryset 도 부분적으로 나눠서 가져와야 할 것이고, 여러가지로 바뀌어야 하는 부분이 많다. 이 부분이 if 문을 통한 분기로 구현이 되어있다.
- 1개 모델에 대한 List 템플릿 처리
- 모델명소문자_list 이름의 Qureyset을 템플릿에 전달
- 페이징 처리 지원 # ex) 300개의 게시물을 한 페이지에 다 보여주는 것이 아닌 페이지를 나누어서 보여주는 것
- 인자는 model 이름과 페이징 처리할 페이지 갯수를 받는다
- template_name 인자가 지정되지 않았다면, 모델명으로 템플릿 경로 유추
사용방법
# instagram/views.py
1.
post_list = ListView.as_view(model=Post, paginate_by= 10)
2. 상속을 통한 방법
class PostListView(ListView):
model = Post
paginate_by = 10
post_list = PostListView.as_view()
페이징 UI 간략 코딩
https://django-bootstrap4.readthedocs.io/en/latest/templatetags.html#bootstrap-pagination
# instagram/templates/instagram/post_list.html
{% load bootstrap4 %}
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootswatch@4.5.2/dist/minty/bootstrap.min.css" integrity="sha384-H4X+4tKc7b8s4GoMrylmy2ssQYpDHoqzPa9aKXbDwPoPUA3Ra8PA5dGzijN+ePnH" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.min.js" integrity="sha384-QJHtvGhmr9XOIpI6YVutG+2QOK9T+ZnN4kzFN1RtK3zEFEIsxhlmWl5/YESvpZ13" crossorigin="anonymous"></script>
<title> Instagram / Post List </title>
<style>
@media(max-width: 600px){
body{}
}
</style>
</head>
<body>
<form action="" method="get">
<input type="text" name="q" value="{{ q }}" />
<input type="submit" value="검색" />
</form>
<table class="table table-borderd table-hover">
<tbody>
{% for post in post_list %}
<tr>
<td>
{{ post.pk }}
</td>
<td>
{% if post.photo %}
<img src="{{ post.photo.url }}" style="width: 100px;"/>
{% else %}
No Photo
{% endif %}
</td>
<td>
{{ post.message }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{{ is_paginated }}
{{ page_obj }}
{% if is_paginated %}
{% bootstrap_pagination page_obj size="small" justify_content="center" %}
{% endif %}
</body>
</html>
포스팅 갯수 뻥튀기 하는 방법
>python manage.py shell
KeyboardInterrupt
>>> for i in range(100):
... post = random.choice(post_list)
... post.pk = None
... post.save()
...
>>> exit()
# pk값이 None이면 새롭게 포스팅이 Insert된다. 왜냐하면 모델.saver()는 pk 값이 None이면 Insert하고, None이 아니면 업데이트를 해주기 때문이다.
반응형
'강의 정리 > Django Views' 카테고리의 다른 글
Django 기본 CBV API (Generic date views) (0) | 2022.01.11 |
---|---|
Django 뷰 장식자(Decorators) (0) | 2022.01.10 |
Django 기본 CBV API (Base views) (0) | 2022.01.09 |
Django 클래스 기반 View (0) | 2022.01.08 |
Django URL Dispatcher와 정규 표현식 (0) | 2022.01.07 |