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<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
valuede cada botão radio está associado ao ID da opção. Onamede 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
actiondo formulário para{% url 'polls:vote' question.id %}, e definimosmethod=post. Usandomethod=post(em vez de domethod=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.counterindica 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.pypath('<int:question_id>/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.pyfrom django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls 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 derequest.POSTsão sempre strings.Note que Django também fornece
request.GETpara 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çãoKeyErrorcaso umachoicenão seja fornecida via dados POST. O código acima checa porKeyErrore re-exibe o formulário da enquete com as mensagens de erro se umachoicenã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
HttpResponseRedirectdepois 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 daHttpResponseRedirectneste exemplo. Essa função nos ajuda a evitar de colocar a URL dentro da view de maneira literal. A ele é dado então o nome da “view” que queremos que ele passe o controle e a parte variável do padrão de formato da URL que aponta para a “view”. Neste caso, usando o URLconf nós definimos em Tutorial 3, esta chamada dereverse()irá retornar uma string como'/polls/3/results/'onde o
3é o valor paraquestion.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.pyfrom 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<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:
Converta o URLconf.
Delete algumas das views antigas, desnecessárias.
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.pyfrom django.urls import path
from . import views
app_name = 'polls'
urlpatterns = [
path('', views.IndexView.as_view(), name='index'),
path('<int:pk>/', views.DetailView.as_view(), name='detail'),
path('<int:pk>/results/', views.ResultsView.as_view(), name='results'),
path('<int:question_id>/vote/', views.vote, name='vote'),
]
Note that the name of the matched pattern in the path strings of the second and
third patterns has changed from <question_id> to <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.pyfrom django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls 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
DetailViewespera o valor de chave primaria capturado da URL chamada"pk", então mudamosquestion_idparapkpara 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.