장고 마스터하기 - 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메소드는 객체를 검색하는 메서드이다.

김땡땡's blog

김땡땡's blog

김땡땡