ListView - Django class-based generic views - I

Aprenda diminuir repetição de código em suas views de listagem com ListView

Introdução

Nosso arquivo views.py pode ficar mais elegante e legível e escrever views pode se tornar uma tarefa trivial para implementações simples. #AskMeHow

Class-based generic views

Antes havia as generic views que eram baseadas em funções, mas a partir do Django 1.3 está disponível as class-based generic views e assim podemos definir atributos, comportamentos e características especiais em nossas views de modo mais elegante, reaproveitável e inteligível.

Então farei uma série de artigos aqui em meu blog, cobrindo as principais formas de uso desse ótimo recurso do Django. Para isto criei um repositório em meu github com um projeto simples, com uma app para criar as views para ela e assim ter o conteúdo prático do que estarei explicando nos artigos.

Vamos começar a série de artigos com um dos tipos de views que mais utilizo no meu dia-a-dia como desenvolvedor, é a ListView. Levando em consideração que meu arquivo models.py é este em meu repositório.

ListView - listagem de objetos de um model

Vamos listar as listas de reprodução que estão cadastradas minha base de dados. Para isto só precisamos importar, dentro de views.py, o meu model e a classe ListView. Como no exemplo abaixo:

views.py

from django.views.generic import ListView
from app_exemplo.models import ListaDeReproducao

class ListasDeReproducao(ListView):
    model = ListaDeReproducao

Simples não? E meu template, por padrão, ficaria assim:

listadereproducao_list.html

<ul>
{% for lista_de_reproducao in object_list %}
    <li>
        <p><a href="{{ lista_de_reproducao.get_absolute_url }}">{{ lista_de_reproducao.titulo }}</a></p>
        <p>{{ lista_de_reproducao.descricao }}</p>
    </li>
{% endfor %}
</ul>

Personalizando a ListView

Podemos fazer muito mais com ListView utilizando e personalizando outros atributos que têm valores padrão mas que podemos alterar e manipulá-los. Vamos ver os principais:

template_name

Você pode usar o atributo template_name para informar a localização do template personalizado que você quer utilizar.

Caso não queira informar, que é o que eu fiz aqui, por padrão o Django tentará encontrar um template com um nome neste padrão: NomeDoModel_TipoDeView.html. Como o nosso model é o ListaDeReproducao e nossa view é uma ListView ele buscará por: listadereproducao_list.html. Você pode conferir em: app_exemplo/templates/app_exemplo/

Para informar a localização do template com o nome personalizado basta fazer como no exemplo abaixo:

# ...
class ListasDeReproducao(ListView):
    # ...
    template_name = 'app_exemplo/lista_de_reproducao.html'

queryset

Um outro atributo que pode substituir o model é o queryset. Com ele é possível passar a lita de objetos a serem listados. A vantagem é que você já pode personalizar a recuperação desses dados, informando por exemplo um .filter(), .order(), entre outros métodos disponíveis no model. Dessa forma:

# ...
class ListasDeReproducao(ListView):
    # ...
    queryset = ListaDeReproducao.objects.all()

context_object_name

O context_object_name é o nome da variável de contexto que estará disponível para você manipular em seu template, em nosso caso no listadereproducao_list.html.

Se você não informa um nome personalizado, que foi o que fizemos, a variável vai se chamar object_list que será uma lista de objetos. Ela também poderá ser chamada por um nome composto da seguinte maneira: NOME_DO_MODEL_list, em nosso caso listadereproducao_list.

Para representar visualmente, em nosso exemplo será algo como isto:

{
    # OUTROS OBJETOS DE CONTEXTO,
    'object_list':
             [<ListaDeReproducao: Animiais>,
             <ListaDeReproducao: Clicks>,
             <ListaDeReproducao: Armas disparando>,
             <ListaDeReproducao: Sons de armas (GERAL)>],
     'listadereproducao_list':
         [<ListaDeReproducao: Animiais>,
         <ListaDeReproducao: Clicks>,
         <ListaDeReproducao: Armas disparando>,
         <ListaDeReproducao: Sons de armas (GERAL)>]
}

Podemos manipular, por exemplo, assim:

<ul>
{% for lista_de_reproducao in object_list %}
    <li>
        <p>{{ lista_de_reproducao.titulo }}</p>
        <p>{{ lista_de_reproducao.descricao }}</p>
    </li>
{% endfor %}
</ul>

Então se quisermos que object_list vire, por exemplo, listas_de_reproducao faça como no exemplo abaixo:

# ...
class ListasDeReproducao(ListView):
    # ...
    context_object_name = 'listas_de_reproducao'

NOTE: Sobre objetos de contexto

Se você quiser obter o context_object_name ou o queryset, você poderia simplesmente usar dos métodos: get_context_object_name, get_queryset que podem ser encontrados, para melhor visualização, em django/views/generic/list.py.

Caso queira fazer mais do que apenas obter, se quizer, em algum momento modificar, tratar casos específicos ou mesmo deixar mais explícito os objetos de contexto que estarão disponíveis em seu template, você pode utilizar o get_context_data, como no exemplo:

# ...
class ListasDeReproducao(ListView):
    # ...
    def get_context_data(self, **kwargs):
        context = super(ListasDeReproducao, self).get_context_data(**kwargs)
        # Aqui você fará inclusão, alteração ou exclusão de contextos como desejar
        context['ultima_lista_de_reproducao'] = ListaDeReproducao.objects.latest()
        return context

Se quiser saber em tempo de execução quais os objetos de contexto que estão sendo passados para seu template, você pode definir este método e mandar imprimir em console para que você possa ver assim que acessar a url.

# ...
class ListasDeReproducao(ListView):
    # ...
    def get_context_data(self, **kwargs):
        context = super(ListasDeReproducao, self).get_context_data(**kwargs)
        # imprimindo no console o que há no dicionário de contextos
        print context

Você verá algo desse tipo:

{
     'paginator': None,
     'page_obj': None,
     'is_paginated': False,
     'object_list':
         [<ListaDeReproducao: Animiais>,
         <ListaDeReproducao: Clicks>,
         <ListaDeReproducao: Armas disparando>,
         <ListaDeReproducao: Sons de armas (GERAL)>],
     'listadereproducao_list':
         [<ListaDeReproducao: Animiais>,
         <ListaDeReproducao: Clicks>,
         <ListaDeReproducao: Armas disparando>,
         <ListaDeReproducao: Sons de armas (GERAL)>]
}

paginate_by - Paginação

Toda lista de objetos tende a aumentar, para isto precisamos controlar a exibição desses objetos em tela com a paginação. Para isto basta fornecer ao atributo paginate_by a quantidade de objetos que você quer que seja listada por página. Por exemplo, paginate_by = 4,  listará 15 objetos por página. Se quiser ver isto em funcionamento, faça algo assim:

# ...
class ListasDeReproducao(ListView):
    # ...
    paginate_by = 2

No template, em um exemplo bem simples de páginação você poderia fazer, como fiz no exemplo, algo como:

{% if is_paginated %}
<div class="paginacao">
    <span class="paginacao-links">
        {% if page_obj.has_previous %}
        <a href="?page={{ page_obj.previous_page_number }}">anterior</a>
        {% endif %}
        <span class="page-current">
            P&aacute;gina {{ page_obj.number }} de {{ page_obj.paginator.num_pages }}.
        </span>
        {% if page_obj.has_next %}
        <a href="?page={{ page_obj.next_page_number }}">pr&oacute;xima</a>
        {% endif %}
    </span>
</div>
{% endif %}

Que terá como resultado algo como: "Página 1 de 2. próxima"

NOTE: Sobre paginação

A criatividade é bastante aplicada neste ponto, então você além de explorar as opções de personalização do template pode alterar conforme quiser o limite desta paginação inclusive em tempo de execução com o "paginate_queryset". No exemplo abaixo exemplifico isto, tentando obter da url o parâmetro limite para ser usado como paginação, caso não exista eu uso a paginação padrão.

# ...
class ListasDeReproducao(ListView):
    # ...
    def paginate_queryset(self, queryset, page_size):
        page_size = self.request.GET.get('limite', page_size)
        paginate_queryset = super(ListasDeReproducao, self).paginate_queryset(queryset, page_size)
        return paginate_queryset

Conclusão

Então é isto, quaisquer dúvidas, críticas e sugestões façam nos comentários e/ou issues no repositório do projeto no github. Que a propósito você pode acompanhar o repositório "django-class-based-generic-views" e pode visualizar o exemplo que abordei aqui em projeto / app_exemplo / views / list_and_detail.py.

O próximo artigo será sobre DetailView.

UPDATE: Já há o artigo sobre DetailView, confiram: DetailView - Django class-based generic views - II

UPDATE2 (28/05/2012): Em um novo artigo eu estendi esta ListView e criei um JsonView, confira em: Json View - Django class-based generic views - III

blog comments powered by Disqus