Criando uma primeira aplicação com Django e mysql
Criando um formulário no django
https://docs.djangoproject.com/pt-br/1.11/intro/tutorial04/
Vamos atualizar nosso template de detalhamento da enquete (“polls/detail.html”), para que ele contenha um elemento HTML <form>
Vamos editar o arquivo:
polls/templates/polls/detail.html
<!-- Pega o texto da question e coloca no h1 -->
<h1>{{ question.question_text }}</h1>
<!-- Caso um erro seja lançado, imprime o texto do erro em negrito, tag <strong> -->
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
<!-- Início do formulário -->
<form action="{% url 'polls:vote' question.id %}" method="post">
<!-- Usa a tag de template {% csrf_token%} para prevenir ataques Cross Site Request Forgeries -->
{% csrf_token %}
<!-- Para cada choice associada a essa question -->
{% for choice in question.choice_set.all %}
<!-- exibe um botão radio para cada opção da enquete associado ao ID da choice-->
<input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" />
<!-- forloop.counter indica quantas vezes a tag for passou pelo laço -->
<label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br />
{% endfor %}
<!-- Envia por POST o dado choice=#, onde o # é o ID da choice selecionada pra votar -->
<input type="submit" value="Vote" />
</form>
Uma rápida explicação:
- O template detail.html 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 é associado”choice“. Isso significa que, quando alguém escolhe um dos botões de radio e submete o formulário, ele vai enviar por POST o dado choice=#, onde o # é o ID da choice selecionada.
- Foi definido o parâmetro action do formulário para {% url ‘polls:vote’ question.id %}, como method=post.
- Usar o 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.
- GET passa variáveis por URL, tem um limite de caracteres. É útil para você compartilhar uma página de produto, fazer paginação, etc.
- POST é mais seguro, visto que a URL não exibe as informações que estão sendo enviadas, ideal para formulários de login, cadastro, envio de arquivos. Não possui limite de caracteres, sendo necessário uma conexão paralela para envio das informações. neste método, as informações enviadas ficam no corpo da requisição, e assim como toda requisição HTTP, existem os cabeçalhos que também são enviados.
- forloop.counter indica quantas vezes a tag for passou pelo laço.
- Como estamos criando um formulário POST (que pode modificar dados no servidor), é preciso garantir ao máximo a proteção contra Cross Site Request Forgeries.
- CSRF é um tipo de ataque malicioso a um website no qual comandos não autorizados são transmitidos através de um utilizador em quem o website confia.
- Felizmente, você não precisa se preocupar muito, o Django vem com um sistema muito fácil de usar para proteção contra isto.
- Em suma, todas as formas de POST que são direcionados a URLs internas devem usar uma tag de template {% csrf_token%}.
Agora, vamos criar uma view Django que manipula os dados submetidos e faz algo com eles.
Nós já criamos uma URLconf para a aplicação de enquete que inclui esta linha:
polls/urls.py
path('/vote/', views.vote, name='vote'),
Nós já criamos também uma implementação falsa da função vote().
Vamos criar a versão real.
Adicione o seguinte em polls/views.py:
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):
# Mostra novamente o formulário de votação das perguntas.
return render(request, 'polls/detail.html', {
'question': question,
'error_message': "You didn't select a choice.",
})
else:
selected_choice.votes += 1
selected_choice.save()
# Sempre retorna um HttpResponseRedirect depois de lidar com sucesso
# com dados via POST. Isso impede que os dados sejam postados duas vezes se
# o usuário clicar no botão voltar.
return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))
Este código inclui algumas coisas que ainda não vimos:
- 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 choice selecionada, como uma string. Os valores de request.POST são sempre strings.
- O Django também fornece a 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 anterior 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 choice, o código retorna um django.http.HttpResponseRedirect ao invés de um HttpResponse normal.
- HttpResponseRedirect recebe um único argumento: a URL para o qual o usuário será redirecionado.
- Como o comentário Python do código salienta, você deve sempre retornar uma HttpResponseRedirect depois de lidar com sucesso com dados POST.
- Esta dica não é específica do Django, mas sim, uma boa prática para o desenvolvimento Web.
- Estamos usando a função reverse() no construtor da HttpResponseRedirect neste 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, esta chamada de reverse() irá retornar uma string como:’/polls/3/results/’
- O 3 é o valor para question.id. Esta URL redirecionada irá então chamar a view ‘results‘ para exibir a página final.
- Conforme mencionado anteriormente, request é um objeto HttpRequest
- 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
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() que já fizemos. A única diferença é o nome do template. Iremos corrigir esta redundância depois.
- Agora, crie o template 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.
NOTA
O código da nossa view vote() tem um pequeno problema. Ele primeiro pega o objeto de selected_choice do banco de dados, calcula o novo valor de votes e os salva novamente.
Se dois usuários do seu site tentarem votar ao mesmo tempo: o mesmo valor, digamos 42, será obtido para votes. Então, para ambos usuários, o novo valor 43 é calculado e salvo, quando o valor esperado seria 44.
Isto é chamado de condição de concorrência (race condition).
- As views detail() e results() são muito simples e redundantes. A view index(), que mostra a lista de votações, a mesma coisa.
- Estas views representam um caso comum no 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.
- 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 causa disso, podemos excluir um monte de código. Iremos apenas ter que executar alguns passos para fazer a conversão.
Nós iremos:
- Converter o URLconf.
- Deletar algumas das views antigas, desnecessárias.
- Introduzir novas views baseadas em views genéricas Django’s.
- Geralmente, quando estiver escrevendo uma aplicação Django, você vai avaliar se as views genéricas são uma escolha adequada para o seu problema e você irá utilizá-las desde o início ao invés 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.
Corrijindo a URLconf
Em primeiro lugar, abra a URLconf polls/urls.py e modifique para ficar assim:
from django.urls import path
from django.contrib import admin
from polls import views
app_name = 'polls'
urlpatterns = [
path('', views.IndexView.as_view(), name='index'),
path('admin/', admin.site.urls),
path('/', views.DetailView, name='detail'),
path('/results/', views.ResultsView, name='results'),
path('/vote/', views.vote, name='vote'),
]
Observe que o nome do padrão correspondente nas sequências do path do segundo e terceiro padrão, foi alterado de <question_id> para <pk>.
VIEWS ALTERADAS
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 altere para:
from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect
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 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, isso garante que a exibição de resultados e a exibição de detalhes tenham uma aparência diferente quando renderizada, mesmo que ambas sejam uma DetailView nos bastidores.
Da mesma forma, 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 nosso template “polls/index.html“.
Nas partes anteriores desse 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).
O Django é capaz de determinar um nome apropriado para a variável de contexto. No entanto, para ListView, a variável de contexto gerada automaticamente é question_list.
Para substituir isso, fornecemos o atributo context_object_name, especificando que queremos usar o last_question_list ao invés question_list.
Como uma abordagem alternativa, você pode alterar seus modelos para combinar as novas variáveis de contexto padrão, mas, é muito mais fácil simplesmente dizer ao Django que use a variável que deseja.
Execute o servidor e use sua nova aplicação de enquete baseada em generic views.