Przegląd nowych "Generic Views" Django opartych o klasy

W Django dostępne były "generyczne" widoki upraszczające tworzenie widoków o często powtarzającej się funkcjonalności. W Django 1.3 wprowadzono zupełnie nowy typ ogólnych, "generycznych" widoków opartych o klasy. Stary system oparty o funkcje został oznaczony jako przestarzały (czyli zostanie usunięty w przyszłych wydaniach frameworka). Opis jak i referencje nowych widoków znajdziemy w dokumentacji. Poniżej przedstawię je na kilku przykładach.

Na potrzeby tego artykułu stworzyłem prosty projekt Django a w nim aplikację "book". Do wyświetlenia statycznej strony (szablonu) można użyć widoku TemplateView tworząc w views.py taki oto kod:

from django.views.generic import TemplateView

class AboutView(TemplateView):
    template_name = "about.html"

Gdzie atrybut template_name przechowuje nazwę (ze ścieżką) szablonu jaki ma zostać użyty. W katalogu "templates" stworzyłem prosty szablon o takiej nazwie (zawartość nieistotna)

W urls.py dodaję import:
from book.views import AboutView
A do reguł mapujących URLe dodaję:
(r'^about/$', AboutView.as_view()),

Gdy teraz otworzymy adres "/about/" naszej aplikacji to powinniśmy zobaczyć nasz testowy szablon. Tak wygląda użycie "generycznego" widoku na szybko.

Klasa TemplateView służy do wyświetlania statycznych widoków. Wystarczy podać atrybut template_name - co wylistowane jest w referencji widoku.

Z klas tych można korzystać także bezpośrednio w urls.py przy mapowaniu linków na widoki. Wystarczy że zaimportujemy TemplateView i użyjemy takiego mapowania:
(r'^about2/$', TemplateView.as_view(template_name="about.html")),

Nazwę szablonu przekazujemy jako argument i gotowe. Dla tego rozwiązania nie jest potrzebny żaden widok z views.py.

Kolejny prosty widok to RedirectView, który przekierowuje na podany adres URL. Używa takich atrybutów jak url - adres przekierowania, permanent (True - użyje kodu 301, False - użyje kodu 302; domyślnie True), czy query_string na opcjonalne parametry GET. Użycie tego widoku jest proste:
(r'^bad_url/$', RedirectView.as_view(url="/about/")),
Jeżeli zmienialiśmy strukturę odnośników i np. artykuł jest pod nieco innym odnośnikiem to możemy wykorzystać takie przekierowanie z kodem 301 (przeniesienie trwałe). Jeżeli np. jakaś akcja wymaga uzupełnienia danych w innym widoku (a obecny widok jest pod użytym adresem URL) to możemy użyć przekierowania tymczasowego 302 (ma to znaczenie dla wyszukiwarek).

Widoki obiektów

Zazwyczaj do czynienia mamy z widokami pracującymi na obiektach - na danych pochodzących z bazy danych. W nowym systemie "generycznych" widoków są też i takie, które operują na modelach. Widok ListView służy do listowania wpisów podanego modelu.

Dla przykładu stworzyłem dwa modele:
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 __unicode__(self):
        return self.name

class Book(models.Model):
    title = models.CharField(max_length=100)
    publisher = models.ForeignKey(Publisher)
    publication_date = models.DateField()
Można dodać kilka wpisów do nich. By wylistować wszystkich wydawców książek (Publisher) wystarczy taki oto widok:
from django.views.generic import ListView

from book.models import *

class ListPublishers(ListView):
    model = Publisher
Do tego potrzebny jest szablon. Jeżeli nie podany własnego to widok założy szablon wg wzoru:
NazwaAplikacji/NazwaModelu_list
Czyli w naszym przypadku book/publisher_list.html. W szablonie tym list obiektów typu Publisher będzie dostępna w zmiennej object_list. Jeżeli "object_list" nam się nie podoba to można podać nazwę zrozumiałą dla człowieka za pomocą atrybutu context_object_name. Możemy także ograniczyć zbiór obiektów listowanych przez widok (domyślnie wszystkie) podając dla atrybutu queryset własne zapytanie-QuerySet dla modelu. Co więcej do każdego widoku możemy dodać coś do kontekstu:
class ListPublishers(ListView):
    model = Publisher
    
    context_object_name="publisher_list"
    queryset = Publisher.objects.all().order_by('name')
    
    def get_context_data(self, **kwargs):
        # Call the base implementation first to get a context
        context = super(ListPublishers, self).get_context_data(**kwargs)
        # Add something
        context['foo'] = 'bar'
        return context
Dla tego widoku lista wydawców będzie dostępna pod zmienną "publisher_list". Dodatkowo do kontekstu szablonów dodałem zmienną "foo". W normalnych aplikacjach elementy listy podlinkowane byłyby do widoku szczegółowego. To samo zrobiłem w "testowym" szablonie:
<h1>Test</h1>
<h2>{{ foo }}</h2>
<ul>
{% for i in publisher_list %}
<li><a href="{% url show_publisher i.id %}">{{ i.name }}</a></li>
{% endfor %}
</ul>
Za wyjątkiem użycia taga "url" nic nadzwyczajnego. Sam tag będzie generował odnośniki do widoku o nazwie "show_publisher". Stosując klasyczny widok jako funkcję podalibyśmy tam book.views.nazwa_widoku. W przypadku tych "generycznych" można podać własną nazwę w urls.py przy definiowaniu powiązania URLa z widokiem:
url(r'book/publisher/(?P<pk>[0-9]+)/$', ShowPublisher.as_view(), name='show_publisher'),
Czyli mamy widok "ShowPublisher", który zaraz napiszemy i nazwę tego powiązania stosowanę m.ni. przez tag "url". Wspomniany widok "ShowPublisher" będzie używał klasy DetailView - służącej jak sama nazwa wskazuje do wyświetlania konkretnego obiektu (wpisu) danego modelu:
class ShowPublisher(DetailView):
    context_object_name = "publisher"
    model = Publisher
Szablony dla DetailView mają końcówkę "detail", czyli potrzebujemy book/publisher_detail.html. W tymże widoku obiekt będzie domyślnie dostępny pod zmienną "object", choć też możemy nadać bardziej zrozumiałą nazwę - w tym przypadku "publisher" (podany w context_object_name). Zawartość szablonu dla demonstracji że kod działa poprawnie może wyglądać tak:
<h1>Test</h1>
<h2>{{ publisher }}</h2>
Znajdziemy też widoki z formularzami modelu pozwalające tworzyć, edytować jak i kasować wpisy - CreateView, UpdateView, DeleteView. Przykładowy widok z formularzem dodawania nowego wydawcy wyglądałby tak:
class AddPublisher(CreateView):
    model = Publisher
    success_url = '/book/'
A gdybyśmy chcieli nałożyć jakiś dekorator, np. wymuszenie zalogowania:
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator

class AddPublisher(CreateView):
    model = Publisher
    success_url = '/book/'
    
    @method_decorator(login_required)
    def dispatch(self, *args, **kwargs):
        return super(AddPublisher, self).dispatch(*args, **kwargs)
Szablon, domyślnie book/publisher_form.html w najprostszej postaci wyglądałby tak:
<h1>Test</h1>

<form method="post" action="./">
    {% csrf_token %}    
    <table>
    {{ form }}
    </table>
    <input type="submit" value="Add" />
</form>
RkBlog

Django, 2 April 2012

Comment article
Comment article RkBlog main page Search RSS Contact