Escrevendo sua primeira aplicação Django, parte 4

Este tutorial inicia-se onde o Tutorial 3 terminou. Estamos continuando a aplicação web de enquete e concentraremos em uma forma simples de processamento de formulário e a redução de nosso código.

Escreva um simples formulário

Vamos atualizar nosso template de detalhamento da enquete (“polls/detail.html”) do último tutorial, para que ele contenha um elemento HTML <form>:

<h1>{{ poll.question }}</h1>

{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}

<form action="/polls/{{ poll.id }}/vote/" method="post">
{% for choice in poll.choice_set.all %}
    <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" />
    <label for="choice{{ forloop.counter }}">{{ choice.choice }}</label><br />
{% endfor %}
<input type="submit" value="Vote" />
</form>

Uma rápida explicação:

  • O template acima exibe um botão radio para cada opção da enquete. O value de cada botão radio está associado ao ID da opção. O name de cada botão radio é a escolha (choice). Isso significa que, quando alguém escolhe um dos botões de radio e submete a formulário, ele vai enviar choice=3 por POST. Este são os formulários HTML 101.
  • Nós definimos o parâmetro action do formulário para /polls/{{ poll.id }}/vote/, e definimos method=post. Usando method=post (em vez de method=get) é muito importante, porque o ato de enviar este formulário irá alterar dados do lado servidor. Sempre que criar um formulário que modifique os dados do lado do servidor, utilize method="post". Esta dica não é específica do Django; mais sim uma boa prática para o desenvolvimento é Web.
  • forloop.counter indica quantas vezes a tag :ttag`for` atravessou o seu ciclo.

Agora, vamos criar uma view Django que manipula os dados submetidos e faz algo com eles. Lembre-se, no Tutorial 3, criamos uma URLconf para a aplicação de enquete que inclui esta linha:

(r'^(?P<poll_id>\d+)/vote/$', 'vote'),

Então, vamos criar um função vote() em mysite/polls/views.py:

from django.shortcuts import get_object_or_404, render_to_response
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from mysite.polls.models import Choice, Poll
# ...
def vote(request, poll_id):
    p = get_object_or_404(Poll, pk=poll_id)
    try:
        selected_choice = p.choice_set.get(pk=request.POST['choice'])
    except (KeyError, Choice.DoesNotExist):
        # Redisplay the poll voting form.
        return render_to_response('polls/detail.html', {
            'poll': p,
            'error_message': "You didn't select a choice.",
        })
    else:
        selected_choice.votes += 1
        selected_choice.save()
        # Always return an HttpResponseRedirect after successfully dealing
        # with POST data. This prevents data from being posted twice if a
        # user hits the Back button.
        return HttpResponseRedirect(reverse('mysite.polls.views.results', args=(p.id,)))

Este código inclui algumas coisas que ainda não foram cobertas neste tutorial:

  • request.POST é um objeto como dicionários que lhe permite acessar os dados submetidos pelas suas chaves. Neste caso, request.POST['choice'] retorna o ID da opção selecionada, tal como uma string. Os valores de request.POST são sempre strings.

    Note que Django também fornece request.GET para acesar dados GET da mesma forma – mas nós estamos usando request.POST explicitamente no nosso código, para garantir que os dados só podem ser alterados por meio de uma chamada POST.

  • request.POST['choice'] irá levantar a exceção KeyError caso uma choice não seja fornecida via POST. O código acima checa por KeyError e re-exibe o formulário da enquete com as mensagens de erro se uma choice não for fornecida.

  • Após incrementar uma opção, o código retorna um HttpResponseRedirect em vez de um normal HttpResponse. HttpResponseRedirect` recebe um único argumento: a URL para o qual o usuário será redirecionado (veja o ponto seguinte para saber como construímos a URL, neste caso).

    Como o comentário Python acima salienta, você deve sempre retornar uma HttpResponseRedirect depois de lidar com sucesso com dados POST. Esta dica não é específica do Django; mais sim uma boa prática para o desenvolvimento Web.

  • Estamos usando a função reverse() no construtor do HttpResponseRedirect neste exemplo. Essa função ajuda a evitar ter que escrever na mão a URL na função de view. É dado o nome da view de que queremos passar o controle e uma porção da variável do padrão de URL que aponta para essa view. Neste caso, usando o URLConf criado no Tutorial 3, esta chamada a reverse() irá retornar uma string como:

    '/polls/3/results/'
    

    ... onde o 3 é o valor de p.id. Esta URL redirecionada irá então chamar a view 'results' afim de exibir a página final. Note que você precisará usar o nome completo da view aqui (incluindo o prefixo).

Como mencionado no Tutorial 3, request é um objeto HttpRequest. Para mais informações sobre o objeto HttpRequest, veja a documentação do request e response.

Depois que alguém votar em uma enquete, a view vote() redireciona para a página de resultados da enquete. Vamos escrever essa view:

def results(request, poll_id):
    p = get_object_or_404(Poll, pk=poll_id)
    return render_to_response('polls/results.html', {'poll': p})

Isto é quase exatamente o mesmo que a view detail() do Tutorial 3. A única diferença é o nome do template. Iremos corrigir esta redundância depois.

Agora, crie um template results.html:

<h1>{{ poll.question }}</h1>

<ul>
{% for choice in poll.choice_set.all %}
    <li>{{ choice.choice }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
{% endfor %}
</ul>

Agora, vá para /polls/1/ no seu navegador e vote em uma enquete. Você deverá ver uma página de resultados que será atualizado cada vez que você votar. Se você enviar o formulário sem ter escolhido uma opção, você deverá ver a mensagem de erro.

Use views genéricas: Menos código é melhor

As views detail() (do Tutorial 3) e results() são estupidamente simples – e, como já mencionado acima, são redundantes. A view, index() (também do Tutorial 3), que exibe uma lista de enquetes, é semelhante.

Estas views representam um caso comum do desenvolvimento Web básico: obter dados do banco de dados de acordo com um parâmetro passado na URL, carregar um template e devolvê-lo renderizado. Por isto ser muito comum, o Django fornece um atalho, chamado sistema de “views genéricas”.

Views genéricas abstraem padrões comuns para um ponto onde você nem precisa escrever código Python para escrever uma aplicação.

Vamos converter a nossa aplicação de enquete para utilizar o sistema de views genéricas, por isso podemos excluir um monte do nosso próprio código. Iremos apenas ter que executar alguns passos para fazer a conversão. Nós iremos:

  1. Converter a URLconf.
  2. Renomear alguns templates.
  3. Remover algumas views antigas e desnecessárias.
  4. Corrigir a manipulação de URL para as novas views.

Leia a respeito para obter mais detalhes.

Por que o código se arrastou?

Geralmente, quando estiver escrevendo uma aplicação Django, você vai avaliar se views genéricas são uma escolha adequada para o seu problema e você irá utilizá-las desde o início em vez de refatorar seu código no meio do caminho. Mas este tutorial intencionalmente tem focado em escrever views “do jeito mais difícil” até agora, para concentrarmos nos conceitos fundamentais.

Você deve saber matemática básica antes de você começar a usar uma calculadora.

Em primeiro lugar, abra a URLconf polls/urls.py. Ela esta assim, de acordo com o tutorial até o momento:

from django.conf.urls.defaults import *

urlpatterns = patterns('mysite.polls.views',
    (r'^$', 'index'),
    (r'^(?P<poll_id>\d+)/$', 'detail'),
    (r'^(?P<poll_id>\d+)/results/$', 'results'),
    (r'^(?P<poll_id>\d+)/vote/$', 'vote'),
)

Modifique para ficar assim:

from django.conf.urls.defaults import *
from mysite.polls.models import Poll

info_dict = {
    'queryset': Poll.objects.all(),
}

urlpatterns = patterns('',
    (r'^$', 'django.views.generic.list_detail.object_list', info_dict),
    (r'^(?P<object_id>\d+)/$', 'django.views.generic.list_detail.object_detail', info_dict),
    url(r'^(?P<object_id>\d+)/results/$', 'django.views.generic.list_detail.object_detail', dict(info_dict, template_name='polls/results.html'), 'poll_results'),
    (r'^(?P<poll_id>\d+)/vote/$', 'mysite.polls.views.vote'),
)

Nós estamos usando duas views genéricas aqui: object_list() e object_list(). Respectivamente, essas duas views abstraem o conceito de exibir uma lista de objetos e exibir uma página de detalhe para um tipo particular de objeto.

  • Cada view genérica precisa saber qual tipo de dado ela vai agir em cima. Esses dados são fornecidos em um dicionário. A chave queryset neste dicionário aponta para a lista de objetos a serem manipulados pela view genérica.
  • A view genérica object_detail() espera o valor ID capturado da URL em "object_id", de modo que alteramos poll_id para object_id para as views genérica.
  • Nós adicionamos um nome, poll_results, para os resultados da view assim nós temos uma maneira de se referir à sua URL depois (veja a documentação sobre naming URL patterns para informações). Também estamos usando a função url() de django.conf.urls.defaults aqui. É um bom hábito usar url() quando você estiver fornecendo um padrão de nome como este.

Por padrão, a view genérica object_detail() utiliza um template chamado <app name>/<model name>_detail.html. Em nosso caso, ela vai utilizar o template "polls/poll_detail.html". Assim, renomeie o seu template polls/detail.html para polls/poll_detail.html e altere a linha render_to_response em ``vote()`().

Semelhantemente, a view genérica object_list() utiliza um template chamado <app name>/<model name>_list.html. Assim, renomeie polls/index.html para polls/poll_list.html.

Por termos mais de uma entrada na URLconf que usa object_detail() para a aplicação de enquete, nós especificamos manualmente um nome para a view de resultados: template_name='polls/results.html'. Caso contrário, as duas views utilizariam o mesmo template. Note que nós usamos dict() para retornar um dicionário alterado no lugar.

Note

django.db.models.QuerySet.all() é preguiçosa

Poderá parecer um pouco assustador ver Poll.objects.all() ser utilizado na view de detalhe que necessita apenas de um objeto Poll mas não se preocupe; Poll.objects.all() é na verdade um objeto especial chamado de QuerySet, que é “preguiçosa” e não toca no seu banco de dados a menos que seja absolutamente necessário. No momento em que a consulta ao banco de dados acontecer, a view genérica object_detail() terá limitado o seu escopo para um único objeto, de modo que a eventual consulta só irá selecionar uma linha da base de dados.

Se você quiser saber mais sobre seu funcionamento, a documentação de banco de dados do Django explica a natureza preguiçosa dos abjetos QuerySet.

Nas partes anteriores deste tutorial, os templates tem sido fornecidos com um context que contém as variáveis de contexto poll` e latest_poll_list. No entanto, as views genérica fornecem as variáveis object e object_list como contexto. Portanto, você precisa mudar seus templates para combinar com as novas variáveis de contexto. Vá através de seus templates, e altere qualquer referência a latest_poll_list para object_list e altere qualquer referência de poll para object.

Agora você pode deletar as views, index(), detail() e results() do arquivo polls/views.py. Não precisamos delas mais – elas foram substituídas por views genéricas.

A view vote() ainda é necessária. No entanto, ela deve ser modificada para corresponder ao novo contexto de variáveis. Na chamada render_to_response(), renomeie a variável de contexto de poll para object.

A última coisa a fazer é corrigir o manipulador da URL para levar em conta a utilização de views genéricas. Na view vote() acima, usamos a função reverse() para evitar escrever na mão nossas URLs. Agora que mudamos para view genérica, nós vamos precisar alterar a chamada reverse() para apontar de volta para a nossa nova view genérica. Nós não podemos simplesmente utilizar a função view mais – views genéricas podem ser (e são) utilizada várias vezes - mas podemos usar o nome que foi dado:

return HttpResponseRedirect(reverse('poll_results', args=(p.id,)))

Execute o servidor e use sua nova aplicação de enquete baseada em views genéricas.

Para maiores detalhes sobre views genéricas, consulte a documentação sobre views genéricas.

Brevemente

O tutorial acaba aqui por enquanto. Futuras partes deste tutorial cobrirão:

  • Processamento avançado de formulário
  • Usando o framework de RSS
  • Usando o framework de cache
  • Usando o framework de comentários
  • Características avançadas da interface de administração: Permissões
  • Características avançadas da interface de administração: JavaScript personalizado

Por enquanto, você pode querer verificar alguns pontos em para onde ir a partir daqui