Escrevendo sua primeira app Django, parte 3¶
Este tutorial inicia-se onde o Tutorial 2 terminou. Vamos continuar a aplicação web de enquete e focaremos na criação da interface pública – “views”.
Visão Geral¶
Uma view é um “tipo” de página Web em sua aplicação Django que em geral serve a uma função específica e tem um template específico. Por exemplo, em uma aplicação de blog, você deve ter as seguintes views:
- Página inicial do blog - exibe os artigos mais recentes.
- Página de “detalhes” - página de vínculo estático permanente para um único artigo.
- Página de arquivo por ano - exibe todos os meses com artigos para um determinado ano.
- Página de arquivo por mês - exibe todos os dias com artigos para um determinado mês.
- Página de arquivo por dia - exibe todos os artigos de um determinado dia.
- Ação de comentários - controla o envio de comentários para um artigo.
Em nossa aplicação de enquetes, nós teremos as seguintes views:
- Página de “índice” de enquetes - exibe as enquetes mais recente.
- Question “detail” page – displays a question text, with no results but with a form to vote.
- Página de “resultados” de perguntas - exibe os resultados de uma pergunta em particular.
- Ação de voto - gerencia a votação para uma escolha particular em uma enquete em particular.
No Django, páginas web e outros conteúdos são entregues por views. Cada view é representada por uma simples função Python(ou metodos, no caso das class-based views). O django irá escolher uma view examinando a URL que foi requisitada (para ser preciso, a parte da URL depois do nome de dominio).
Agora em seu tempo pela internet você deve ter visto coisas lindas como “ME2/Sites/dirmod.asp?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B”. Você ficará satisfeito em saber que o Django permite padrões de URL muito mais elegantes do que isso.
Um padrão de URL é simplesmente a forma geral de uma URL - por exemplo: /newsarchive/<year>/<month>/
.
Para ir de uma URL para uma view, O Django usa oque é conhecido como ‘URLconfs’. Uma URLconf mapeia padrões de URL (descritas com expressões regulares) para views.
Este tutorial fornece instruções básicas na utilização de URLconfs, e você pode recorrer a django.urls
para mais informações.
Escrevendo mais views¶
Agora vamos adicionar mais algumas views em polls/views.py
. Estas views são um pouco diferentes, porque elas recebem um argumento:
def detail(request, question_id):
return HttpResponse("You're looking at question %s." % question_id)
def results(request, question_id):
response = "You're looking at the results of question %s."
return HttpResponse(response % question_id)
def vote(request, question_id):
return HttpResponse("You're voting on question %s." % question_id)
Ligue estas novas views dentro do módulo polls.urls
adicionando as seguintes chamadas url()
:
from django.conf.urls import url
from . import views
urlpatterns = [
# ex: /polls/
url(r'^$', views.index, name='index'),
# ex: /polls/5/
url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
# ex: /polls/5/results/
url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
# ex: /polls/5/vote/
url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
]
Dê uma olhada no seu navegador, em /polls/34/
. Ele vai executar o método detail()
e mostrar o ID que você informou na URL. Tente “/polls/34/results/” e “/polls/34/vote/” também – Eles vão mostrar a pagina resultadoss e a pagina de votação.
Quando alguém requisita uma página do seu site –vamos dizer, “/polls/34”, o Django irá carregar o módulo Python mysite.urls
porque este está sendo apontado pela definição ROOT_URLCONF
. Ele encontra a variável denominada urlpatterns
e atravessa as expressões regulares seguindo a ordem. Depois de encontrar uma combinação no '^polls/'
, ele corta o texto que combina ("polls/"
) e envia a parte remanescente – "34/"
– para o URLconf do ‘polls.urls’ para processamento posterior. Lá ele encontra a r'^(?P<question_id>[0-9]+)/$'
, resultando em uma chamada para a “view” detail()
como aqui:
detail(request=<HttpRequest object>, question_id='34')
A parte question_id='34'
vem de (?P<question_id>[0-9]+)
. Usando parênteses em torno de um padrão “captura” o texto casado por este padrão e envia ele como um argumento da função; ?P<question_id>
define o nome que será usado para identificar o padrão casado; e [0-9]+
é a expressão regular para casar uma sequência de dígitos (ex., um número).
Como os padrões de URL são expressões regulares, realmente não há limites para o que você possa fazer com elas. E também não é necessário adicionar extensão na URL como .html
- a menos que você queira, neste caso você pode fazer algo como:
url(r'^polls/latest\.html$', views.index),
Mas, não o faça isso, Isto é idiota.
Escreva views que façam algo¶
Cada view é responsável por fazer uma das duas coisas: retornar um objeto HttpResponse
contendo o conteúdo para a página requisitada, ou levantar uma exceção como Http404
. O resto é com você.
Sua view pode ler registros do banco de dados, ou não. Ela pode usar um sistema de templates como o do Django - ou outro sistema de templates Python de terceiros - ou não. Ele pode gerar um arquivo PDF, saída em um XML, criar um arquivo ZIP sob demanda, qualquer coisa que você quiser,usando qualquer biblioteca Python você quiser.
Tudo que o Django espera é que a view retorne um HttpResponse
. Ou uma exceção.
Porque é conveniente, vamos usar a própria API de banco de dados do Django, a qual cobrimos em Tutorial 2. Aqui uma nova tentativa de view index()
, a qual mostra as últimas 5 “poll questions” do sistema, separada por vírgulas, de acordo com sua data de publicação:
from django.http import HttpResponse
from .models import Question
def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5]
output = ', '.join([q.question_text for q in latest_question_list])
return HttpResponse(output)
# Leave the rest of the views (detail, results, vote) unchanged
Há um problema aqui, no entanto: o design da página esta codificado na view. Se você quiser mudar a forma de apresentação de sua página, você terá de editar este código diretamente em Python. Então vamos usar o sistema de templates do Django para separar o design do código Python:
Primeiro, crie um diretório chamado `` templates`` em seu diretório polls
. O Django irá procurar templates lá.
A sua configuração de projeto TEMPLATES
descreve como o Django vai carregar e renderizar templates. O arquivo de configuração padrão usa o backend DjangoTemplates
do qual a opção APP_DIRS
é configurada como True
. Por convenção DjangoTemplates` procura por um subdiretório “templates” em cada uma das INSTALLED_APPS
.
Dentro do diretório templates
que acabou de criar, crie outro diretório polls
, e dentro crie um arquivo chamado index.html
. Em outras palavras, seu template deve estar em polls/templates/polls/index.html
. Devido a forma como o carregador de templates app_directories
funciona como descrito acima, você pode referenciar este template dentro do Django simplesmente como polls/index.html
.
Namespacing de template
Agora nós podemos ser capazes de avnçar com a colocação dos nossos modelos diretamente em polls/templates
(em vez de criar outro subdiretório poll
), mas na verdade seria uma má ideia. Django irá escolher o primeiro template que encontra cujo nome corresponde, e se você tivesse um template com o mesmo nome em uma aplicação diferente, O Django e incapaz de distinguir entre eles. Precisamos ser capazes de apontar Django no caminho certo, e a maneira mais fácil de garantir isso é usar namespacing. Ou seja, colocando esses templates dentro de outro diretório nomeado para a aplicação em si.
Ponha o seguinte código neste template:
{% if latest_question_list %}
<ul>
{% for question in latest_question_list %}
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No polls are available.</p>
{% endif %}
Agora vamos atualizar nossa view index
em polls/views.py
para usar o template:
from django.http import HttpResponse
from django.template import loader
from .models import Question
def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5]
template = loader.get_template('polls/index.html')
context = {
'latest_question_list': latest_question_list,
}
return HttpResponse(template.render(context, request))
Esse código carrega o template chamado polls/index.html
e passa um contexto para ele. O contexto é um dicionário mapeando nomes de variáveis para objetos Python.
Carregue a página apontando seu navegador para “/polls/”, e você deve ver uma lista contendo a questão “What’s up” do Tutorial 2. O link aponta para página de detalhes das perguntas.
Um atalho: render()
¶
É um estilo muito comum carregar um template, preenchê-lo com um contexto e retornar um objeto HttpResponse
com o resultado do template renderizado. O Django fornece este atalho. Aqui esta toda a view index()
reescrita:
from django.shortcuts import render
from .models import Question
def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5]
context = {'latest_question_list': latest_question_list}
return render(request, 'polls/index.html', context)
Note que uma vez que você tiver feito isto em todas as views, nós não vamos mais precisar importar loader
e HttpResponse
(você vai querer manter HttpResponse
se você ainda tiver os métodos criados para detail
, results
, e vote
).
A função render()
recebe o nome do template como primeiro argumento e um dicionário opcional como segundo argumento. Ele retorna um objeto HttpResponse
do template informado renderizado com o contexto determinado.
Levantando um erro 404¶
Agora, vamos abordar a view de detalhe de pergunta – a página que mostra as questões para uma enquete lançada. Aqui esta a view:
from django.http import Http404
from django.shortcuts import render
from .models import Question
# ...
def detail(request, question_id):
try:
question = Question.objects.get(pk=question_id)
except Question.DoesNotExist:
raise Http404("Question does not exist")
return render(request, 'polls/detail.html', {'question': question})
Um novo conceito aqui: A view levanta uma exceção Http404
se a enquete com ID requisitado não existir.
Nós iremos discutir o que você poderia colocar no template polls/detail.html
mais tarde, mas se você gostaria de ter o exemplo acima funcionando rapidamente, um arquivo contendo:
{{ question }}
irá ajudar a começar por enquanto.
Um atalho: get_object_or_404()
¶
É um estilo muito comum usar get()
e levantar uma exceção Http404
se o objeto não existir. O Django fornece um atalho para isso. Aqui esta a view detail()
, reescrita:
from django.shortcuts import get_object_or_404, render
from .models import Question
# ...
def detail(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/detail.html', {'question': question})
A função get_object_or_404()
recebe um modelo do Django como primeiro argumento e um número arbitrário de argumentos chave, que ele passa para a função do módulo get()
. E levanta uma exceção Http404
se o objeto não existir.
Filosofia
Porquê usamos uma função auxiliar get_object_or_404()
ao invés de automaticamente capturar as exceções ObjectDoesNotExist
em alto nível ou fazer a API do modelo levantar Http404
ao invés de ObjectDoesNotExist
?
Porque isso seria acoplar a camada de modelo com a camada de visão. Um dos principais objetivo do design do Django é manter o baixo acoplamento. Alguns acoplamento controlado é introduzido no módulo django.shortcuts
.
Existe também a função get_list_or_404()
, que trabalhada da mesma forma que get_object_or_404()
– com a diferença de que ela usa filter()
ao invés de get()
. Ela levanta Http404
se a lista estiver vazia.
Use o sistema de template¶
De volta para a view detail()
da nossa aplicação de enquentes. Dada a variável de contexto question
, aqui está como o template polls/detail.htm
deve ser:
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
<li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>
O sistema de templates usa uma sintaxe separada por pontos para acessar os atributos da variável. No exemplo de {{ question.question_text }}
, primeiro o Django procura por dicionário no objeto question
. Se isto falhar, ele tenta procurar por um atributo – que funciona, neste caso. Se a procura do atributo também falhar, ele irá tentar uma chamada do tipo list-index.
A chamada do método acontece no laço {% for %}
: poll.choice_set.all
é interpretado como código Python poll.choice_set.all()
, que retorna objetos Choice
iteráveis que são suportado para ser usado na tag {% for %}
.
Veja o guia de templates para maiores detalhes sobre templates.
Removendo URLs codificados nos templates¶
Lembre-se, quando escrevemos o link para uma pergunta no template polls/templates/index.html
, o link foi parcialmente codificado como este:
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
O problema com esta abordagem, muita acoplada é que ele se torna um desafio para mudar URLs em projetos com um monte de templates. No entanto, uma vez que você definiu o argumento nome na url()
no módulo polls.urls
, você pode remover uma dependência de caminhos de URL específicos definidos em suas configurações de URL usando a tag de template ``{% url%}`:
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
A maneira como isso funciona é, observando as definições de URL como especificado no módulo polls.urls
. Você pode ver exatamente onde o nome de URL ‘detalhe’ é definido a seguir
...
# the 'name' value as called by the {% url %} template tag
url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
...
Se você quiser alterar a URL das views de detalhe da enquete para outra coisa, talvez para algo como polls/specifics/12/
em vez de fazê-lo no template (ou templates) você mudaria em polls/urls.py
:
...
# added the word 'specifics'
url(r'^specifics/(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
...
Namespacing nomes de URL¶
Este projeto tutorial tem apenas um aplicação, polls
. Em projetos reais Django, pode haver cinco, dez, vinte ou mais aplicações. Como o Django diferencia os nomes de URL entre eles? Por exemplo, a aplicação polls
tem uma view detail
, e assim pode um aplicativo no mesmo projeto que é para um blog. Como é que faz para que o Django saiba qual view da aplicação será criada para a url ao usar a tag de template`` {% url%} ``?
A resposta é adicionar namespaces a seu URLconf. No arquivo polls/urls.py
, continue e adicione um app_name
para configurar o namespace da aplicação.
from django.conf.urls import url
from . import views
app_name = 'polls'
urlpatterns = [
url(r'^$', views.index, name='index'),
url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
]
Agora mudo seu template``polls/index.html`` de:
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
para apontar para a view de detalhes d namespace:
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
Quando você estiver confortável em escrever views, leia a parte 4 deste tutorial para aprender sobre processamento de formulários simples e sobre as views genéricas.