Escrevendo sua primeira aplicação Django, parte 4Link para este cabeçalho

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

Crie um formulário simplesLink para este cabeçalho

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

polls/templates/polls/detail.html
Django template
<h1>{{ question.question_text }}</h1>

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

<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %}
{% for choice in question.choice_set.all %}
    <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" />
    <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</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 o dado``choice=#`` por POST onde # é o ID da escolha selecionada. Este é o conceito básico sobre formulários HTML.

  • Nós definimos o parâmetro action do formulário para {% url 'polls:vote' question.id %}, e definimos method=post. Usando method=post (em vez de do 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` passou pelo laço.

  • Desde que nós estamos criando um formulário POST (que pode ter o efeito de modificação de dados), nós temos que se preocupar com Cross Site Request Forgeries. Felizmente, você não precisa se preocupar muito, porque o Django vem com um sistema muito fácil de usar para se proteger contra isto. Em suma, todas as formas de POST que são direcionados a URLs internas devem usar uma tag {% csrf_token%} de template.

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:

polls/urls.py
Code
url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),

Nós tambem criamos uma implementação falsa da função vote(). Vamos criar a versão real. Adicione o seguinte em polls/views.py:

polls/views.py
Code
from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect, HttpResponse
from django.core.urlresolvers import reverse

from .models import Choice, Question
# ...
def vote(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    try:
        selected_choice = question.choice_set.get(pk=request.POST['choice'])
    except (KeyError, Choice.DoesNotExist):
        # Redisplay the question voting form.
        return render(request, 'polls/detail.html', {
            'question': question,
            '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('polls:results', args=(question.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 pelos seus nomes chaves. Neste caso, request.POST['choice'] retorna o ID da opção selecionada, 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 attr:request.POST <django.http.HttpRequest.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 dados 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 class:~django.http.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, neste exemplo, a função reverse() no construtor da classe HttpResponseRedirect. Essa função ajuda a evitar escrevermos a URL na função da view. O nome da view a qual queremos passar o controle e a porção variável do pattern da URL que aponta para essa view são informados. Neste caso, usando o URLConf, criado no Tutorial 3, esta chamada a reverse() irá retornar uma string como:

    Code
    '/polls/3/results/'
    

    onde o 3 é o valor para question.id. Esta URL redirecionada irá então chamar a view 'results' para exibir a página final.

Como mencionado no Tutorial 3, request é um objeto HttpRequest object. Para mais informações sobre objetos HttpRequest, veja request and response documentation.

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

polls/views.py
Code
from django.shortcuts import get_object_or_404, render


def results(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, 'polls/results.html', {'question': question})

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 o template polls/results.html:

polls/templates/polls/results.html
Django template
<h1>{{ question.question_text }}</h1>

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

<a href="{% url 'polls:detail' question.id %}">Vote again?</a>

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 é melhorLink para este cabeçalho

As views detail() (de Tutorial 3) e results() são muito simples – e, como mencionado acima, redundantes. A view index(), que mostra a lista de votações, é similar.

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. Converta o URLconf.

  2. Delete algumas das views antigas, desnecessárias.

  3. Introduz novas views baseadas em views genéricas Django’s.

Leia para obter mais detalhes.

Corrija URLconfLink para este cabeçalho

Em primeiro lugar, abra a URLconf polls/urls.py e modifique para ficar assim:

polls/urls.py
Code
from django.conf.urls import url

from . import views

app_name = 'polls'
urlpatterns = [
    url(r'^$', views.IndexView.as_view(), name='index'),
    url(r'^(?P<pk>[0-9]+)/$', views.DetailView.as_view(), name='detail'),
    url(r'^(?P<pk>[0-9]+)/results/$', views.ResultsView.as_view(), name='results'),
    url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
]

Note que o nome do padrão correspondente na regexes do segundo e terceiro padrão mudou de <question_id> para <pk>.

Views alteradasLink para este cabeçalho

Em seguida, vamos remover a nossas velhas views index, `` detail``, e results e usar views genéricas do Django em seu lugar. Para fazer isso, abra o arquivo polls/views.py e alterar para:

polls/views.py
Code
from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.views import generic

from .models import Choice, Question


class IndexView(generic.ListView):
    template_name = 'polls/index.html'
    context_object_name = 'latest_question_list'

    def get_queryset(self):
        """Return the last five published questions."""
        return Question.objects.order_by('-pub_date')[:5]


class DetailView(generic.DetailView):
    model = Question
    template_name = 'polls/detail.html'


class ResultsView(generic.DetailView):
    model = Question
    template_name = 'polls/results.html'


def vote(request, question_id):
    ... # same as above, no changes needed.

Nós estamos usando duas views genéricas aqui: ListView e DetailView. 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 é o modelo que ela vai agir. Isto é fornecido usando o atributo model.

  • A view genérica DetailView espera o valor de chave primaria capturado da URL chamada "pk", então mudamos question_id para pk para as views genérica.

Por padrão, a view genérica DetailView utiliza um template chamado <app name>/<model name>_detail.html`. Em nosso caso, ela vai utilizar o template `"polls/question_detail.html". O atributo template_name é usado para indicar ao Django para usar um nome de template em vez de auto gerar uma nome de template. Nós também especificamos template_name para a view de listagem de results – isto garante que a view de detalhe tem uma aparencia quando rederizada, mesmo que sejam tanto um class:~django.views.generic.detail.DetailView por trás das cenas.

Semelhantemente, a view genérica ListView utiliza um template chamado <app name>/<model name>_list.html; Nós usamos template_name para informar ListView para usar nossos templates "polls/index.html".

Nas partes anteriores deste tutorial, os templates tem sido fornecidos com um contexto que contém as variáveis question e latest_question_list. Para a DetailView a variavel question é fornecida automaticamente – já que estamos usando um model Django (Question), Django is able to determine an appropriate name for the context variable. However, for ListView, the automatically generated context variable is question_list. To override this we provide the context_object_name attribute, specifying that we want to use latest_question_list instead. As an alternative approach, you could change your templates to match the new default context variables – but it’s a lot easier to just tell Django to use the variable you want.

Execute o servidor, e use sua nova aplicação de enquete baseada em generic views.

Para detalhes completos sobre views genéricas, veja a documentação de generic views documentation.

Quando você estiver confortável com formulários e generic views, leia a parte 5 deste tutorial para aprender sobre como testar nossa aplicação de enquete.