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. Oname
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 enviarchoice=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 definimosmethod=post
. Usandomethod=post
(em vez demethod=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, utilizemethod="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 derequest.POST
são sempre strings.Note que Django também fornece
request.GET
para acesar dados GET da mesma forma – mas nós estamos usandorequest.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çãoKeyError
caso umachoice
não seja fornecida via POST. O código acima checa porKeyError
e re-exibe o formulário da enquete com as mensagens de erro se umachoice
não for fornecida.Após incrementar uma opção, o código retorna um
HttpResponseRedirect
em vez de um normalHttpResponse
.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 doHttpResponseRedirect
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 areverse()
irá retornar uma string como:'/polls/3/results/'
... onde o
3
é o valor dep.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 daview
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:
- Converter a URLconf.
- Renomear alguns templates.
- Remover algumas views antigas e desnecessárias.
- 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 alteramospoll_id
paraobject_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çãourl()
dedjango.conf.urls.defaults
aqui. É um bom hábito usarurl()
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