장고 마스터하기 - 10장 - generic view
객체의 제네릭 뷰
장고의 제네릭 뷰는 데이터베이스 컨텐츠의 뷰를 보여줄 때 장점이 많다.
객체 목록이나 개별 객체를 보여주는 예를 보자. 먼저 모델은 아래의 것을 사용한다.
# models.py
from django.db import models
class Publisher(models.Model):
name = models.CharField(max_length=30)
address = models.CharField(max_length=50)
city = models.CharField(max_length=60)
state_province = models.CharField(max_length=30)
country = models.CharField(max_length=50)
website = models.URLField()
class Meta:
ordering = ["-name"]
def __str__(self):
return self.name
class Author(models.Model):
salutation = models.CharField(max_length=10)
name = models.CharField(max_length=200)
email = models.EmailField()
headshot = models.ImageField(upload_to='author_headshots')
def __str__(self):
return self.name
class Book(models.Model):
title = models.CharField(max_length=100)
authors = models.ManyToManyField('Author')
publisher = models.ForeignKey(Publisher)
publication_date = models.DateField()
다음은 뷰를 정의한다.
# views.py
from django.views.generic import ListView
from books.models import Publisher
class PublisherList(ListView):
model = Publisher
그리고 urls.py에 뷰를 연결한다.
# urls.py
from django.conf.urls import url
from books.views import PublisherList
urlpatterns = [
url(r'^publishers/$', PublisherList.as_view()),
]
이제 이 뷰에서 보여줄 템플릿을 작성해야한다.
- /books/publisher_list.html
{% extends "base.html" %}
{% block content %}
<h2>Publishers</h2>
<ul>
{% for publisher in object_list %}
<li>{{ publisher.name }}</li>
{% endfor %}
</ul>
{% endblock %}
이것만으로 해당 모델의 객체 목록을 보여주는 뷰를 작성할 수 있다. template_name
속성을 추가해 어떤 템플릿을 사용할 건지 알려줄 수 있지만, 속성이 없다면 장고가 객체 이름에서 유추해 찾아낸다. 장고는 객체이름_list.html
에 해당하는 템플릿을 찾아내 보여준다. 그리고 object_list
변수에 객체를 넣어 템플릿으로 보내준다.
템플릿 context 만들기
기본적으로 장고는 object_list
변수를 만들어 그 안에 해당 객체를 넣어 보내준다. 하지만 템플릿 작성자는 이 사실을 알고 있어야만 쓸 수 있다. 이 변수를 바꿀 수 있는 속성이 있다.
# views.py
from django.views.generic import ListView
from books.models import Publisher
class PublisherList(ListView):
model = Publisher
context_object_name = 'my_favorite_publishers'
context_object_name
속성을 사용하면 다른 변수를 설정할 수 있다.
추가 context 추가
하나만 아니라 다른 객체들도 context에 넣어 보내고 싶다면 get_context_data
메서드를 구현하는 것으로 해결할 수 있다.
from django.views.generic import DetailView
from books.models import Publisher, Book
class PublisherDetail(DetailView):
model = Publisher
def get_context_data(self, **kwargs):
# 기본 구현을 호출해 context를 가져온다.
context = super(PublisherDetail, self).get_context_data(**kwargs)
# 모든 책을 쿼리한 집합을 context 객체에 추가한다.
context['book_list'] = Book.objects.all()
return context
객체의 하위 집합 보기
model
속성으로 객체를 지정하는 것은 모든 generic view에서 사용할 수 있다. 하지만 queryset 인수를 사용해 객체 목록을 지정할 수도 있다.
from django.views.generic import DetailView
from books.models import Publisher
class PublisherDetail(DetailView):
context_object_name = 'publisher'
queryset = Publisher.objects.all()
model=Publisher
로 설정하는 것은 queryset=Publisher.objects.all()
을 축약한 것이다 다름없다. 그래서 이 속성을 이용하면 더 객체에 대해 구체화 할 수 있다.
from django.views.generic import ListView
from books.models import Book
class BookList(ListView):
queryset = Book.objects.order_by('-publication_date')
context_object_name = 'book_list'
이렇게 하면 Book 객체를 출판날 기준으로 재정렬한 후 템플릿에 보내질 것이다.
from django.views.generic import ListView
from books.models import Book
class AcmeBookList(ListView):
context_object_name = 'book_list'
queryset = Book.objects.filter(publisher__name='Acme Publishing')
template_name = 'books/acme_list.html'
filter
메소드를 사용하면 출판사의 이름에 해당하는 Book 객체만 보여줄 수 있다. 하지만 다른 출판사의 책들을 보고 싶으면 다른 페이지를 만들어야한다. 그럼 불필요하게 많은 반복적인 행이 생겨버린다.
동적 필터링
동적으로 받아온 url 파라미터로 출판사를 필터링한다면 위에 처럼 하드코딩하지 않아도 된다. 이것을 위해 get_queryset()
메소드를 재정의할 수 있다. 클래스 뷰를 호출하면 self
에 유용한 것들이 많이 저장된다. self.request
뿐만 아니라 url 설정에 따라 캡쳐된 위치(self.args)와 이름 기반(self.kwargs) 인수가 포함된다.
- 예)
# urls.py
from django.conf.urls import url
from books.views import PublisherBookList
urlpatterns = [
url(r'^books/([\w-]+)/$', PublisherBookList.as_view()),
]
# views.py
from django.shortcuts import get_object_or_404
from django.views.generic import ListView
from books.models import Book, Publisher
class PublisherBookList(ListView):
template_name = 'books/books_by_publisher.html'
def get_queryset(self):
self.publisher = get_object_or_404(Publisher, name=self.args[0])
return Book.objects.filter(publisher=self.publisher)
필요하다면 self.request.user를 사용해 현재 사용자나 복잡한 논리를 구현할 수 있다.
또 콘텍스트에 추가할 수도 있어 템플릿에 이용할 수도 있다.
def get_context_data(self, **kwargs):
# 기본 구현을 먼저 호출해 콘텍스트를 얻는다.
context = super(PublisherBookList, self).get_context_data(**kwargs)
# Add in the publisher
context['publisher'] = self.publisher
return context ## Performing Extra Work
추가 작업 수행
제네릭 뷰를 호출하기 전이나 후에 몇 가지 추가 작업을 수행할 수 있다.
마지막으로 봤던 시간을 추적하기 위해 Author 모델에 last_accessed
필드가 있다고 한다면 다음과 같이 구현할 수 있다.
# models.py
from django.db import models
class Author(models.Model):
salutation = models.CharField(max_length=10)
name = models.CharField(max_length=200)
email = models.EmailField()
headshot = models.ImageField(upload_to='author_headshots')
last_accessed = models.DateTimeField()
# urls.py
from django.conf.urls import url
from books.views import AuthorDetailView
urlpatterns = [
#...
url(r'^authors/(?P<pk>[0-9]+)/$', AuthorDetailView.as_view(), name='author-detail\
'),
]
# views.py
from django.views.generic import DetailView
from django.utils import timezone
from books.models import Author
class AuthorDetailView(DetailView):
queryset = Author.objects.all()
def get_object(self):
# Call the superclass
object = super(AuthorDetailView, self).get_object()
# Record the last accessed date
object.last_accessed = timezone.now()
object.save()
# Return the object
return object
get_object
메소드는 객체를 검색하는 메서드이다.