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".
Filosofia
Uma view é um "tipo" de página em sua aplicação Django que em geral serve uma função específica e tem uma template específica. Por exemplo, em uma aplicação de blog, existem as seguintes views:
Em nossa aplicação de enquetes, nós temos as seguintes views:
No Django, cada view é representada por uma função simples em Python.
O primeiro passo para escrever views é montar sua estrutura de URLs. Você faz isso criando um módulo em Python, chamado de URLconf. URLconfs são como o Django associa uma URL desejada a um código em Python.
Quando um usuário requisita uma página construída em Django, o sistema verifica a setting ROOT_URLCONF, que contém uma string do caminho de um módulo Python. O Django carrega esse módulo e verifica se o mesmo possui uma variável chamada urlpatterns, que é uma sequência de tuplas no seguinte formato:
(expressão regular, função em Python para resposta [, dicionário opcional])
O Django começa pela primeira expressão regular e percorre até o final da lista, comparando a URL requisitada com cada expressão regular até encontrar uma que combine.
Quando isso ocorre, o Django chama a função de resposta em Python, com um objeto HTTPRequest como seu primeiro argumento, alguns valores "capturados" da expressão regular como argumentos-chave e, opcionalmente, os elementos do dicionário informado na URL (um terceiro item opcional da tupla).
Para saber mais sobre objetos HttpRequest, veja a documentação sobre requisição e resposta (em inglês). Para maiores detalhes sobre a URLconf, veja a documentação sobre URLconf (em inglês).
Quando você executou python manage.py startproject mysite no início do Tutorial 1, ele criou uma URLconf default em mysite/urls.py. E também automaticamente configurou a setting ROOT_URLCONF (em settings.py) para este arquivo:
ROOT_URLCONF = 'mysite.urls'
Pausa para um exemplo. Edite mysite/urls.py e deixe como abaixo:
from django.conf.urls.defaults import *
urlpatterns = patterns('',
(r'^polls/$', 'mysite.polls.views.index'),
(r'^polls/(?P<poll_id>\d+)/$', 'mysite.polls.views.detail'),
(r'^polls/(?P<poll_id>\d+)/results/$', 'mysite.polls.views.results'),
(r'^polls/(?P<poll_id>\d+)/vote/$', 'mysite.polls.views.vote'),
)
Aqui vale uma revisão. Quando alguém solicita uma página do seu web site -- diz, "/polls/23/", o Django carregará este módulo em Python, porque ele foi apontado pela setting ROOT_URLCONF. Ele procura a variável urlpatterns e percorre as expressões regulares na ordem de declaração. Quando ele encontra uma expressão regular que combine -- r'^polls/(?P<poll_id>\d+)/$' -- ele carrega o módulo/pacote associado: mysite.polls.views.detail. O que corresponde à função detail() em mysite/polls/views.py. E finalmente, ele chama a função detail() como a seguir:
detail(request=<HttpRequest object>, poll_id='23')
A parte poll_id='23'` vem de (?P<poll_id>\d+). Usando parênteses em torno de um pattern "captura" o texto casado por ele e envia-o como um argumento da função de view; o ?P<poll_id> define o nome que será usado para identificar o pattern casado; e \d+ é a expressão regular para casar uma sequência de algarismos (ou seja, 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 a extensão na URL como .php - a menos que você tenha um sinistro senso de humor, neste caso você faria algo como isto:
(r'^polls/latest.php$', 'mysite.polls.views.index'),
Mas não o faça, é idiota.
Note que aquelas expressões regulares não procuram por parâmetros de GET e POST, ou o domínio. Por exemplo, em uma requisição para http://www.example.com/myapp/, a URLconf irá procurar por /myapp/. Numa requisição para http://www.example.com/myapp/?page\\0753, a URLconf irá procurar por /myapp/.
Se você precisar de ajuda com expressões regulares, veja nesta entrada da Wikipédia e na documentação do Python. Outra opção é o livro da O\47Reilly "Mastering Regular Expressions", de Jeffrey Friedl, que é fantástico.
Finalmente, uma nota de performance: essas expressões regulares são compiladas na primeira vez que o módulo URLconf é carregado. Elas são super rápidas.
Bom, nós não criamos nenhuma view ainda -- nós só temos a URLconf. Mas vamos ter certeza de que o Django está seguindo a URLconf corretamente.
Rode o servidor web de desenvolvimento do Django:
python manage.py runserver
Agora vá para http://localhost:8000/polls/ no seu domínio em seu navegador web. Você deverá ver uma amigavelmente-colorida página de erro com a seguinte mensagem:
ViewDoesNotExist at /polls/ Tried index in module mysite.polls.views. Error was: 'module' object has no attribute 'index'
Este erro ocorreu porque você não escreveu uma função index() no módulo mysite/polls/views.py.
Tente /polls/23/, /polls/23/results/ e /polls/23/vote/. As mensagens de erro dirão que uma view do Django foi executada (e não conseguiu encontrar, porque você não a escreveu ainda).
Hora de criar a primeira view. Abra o arquivo mysite/polls/views.py e cole o seguinte código em Python dentro:
from django.http import HttpResponse
def index(request):
return HttpResponse("Alô mundo. Você está na página index.")
Esta é a view mais simples possível. Vá para /polls/ em seu navegador, e você irá ver seu texto.
Agora adicione a seguinte view. Esta é ligeiramente diferente, porque ela recebe um argumento (que, lembre-se, é passado com o que foi capturado pela expressão regular na URLconf):
def detail(request, poll_id):
return HttpResponse("Você esta olhando para a enquete %s." % poll_id)
Dê uma olhada no seu navegador, em /polls/34/. Ele vai mostrar o ID que você informou na URL.
Cada view é responsável por fazer uma de 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 opde 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 escrito por terceiros em Python - ou não. Ele pode gerar um arquivo PDF, dar saída em um XML, criar um arquivo ZIP ao vivo, qualquer coisa que você quiser, usando quais bibliotecas em Python você quiser.
Tudo que o Django espera é que a view retorne um HttpResponse. Ou uma exceção.
Por conveniência, vamos usar a própria API de banco de dados do Django, que nós vimos no Tutorial 1. Aqui está um trecho para a view index(), que mostra as últimas 5 enquetes no sistema, separadas por vírgulas, de acordo com a data de publicação:
from mysite.polls.models import Poll
from django.http import HttpResponse
def index(request):
latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
output = ', '.join([p.question for p in latest_poll_list])
return HttpResponse(output)
Há um problema aqui, veja: o design da página dessa view está em linguagem de computador. Se você quiser mudar a apresentação de sua página, você terá de editar este código diretamente em Python. Vamos usar o sistema de templates do Django para separar o design do Python:
from django.template import Context, loader
from mysite.polls.models import Poll
from django.http import HttpResponse
def index(request):
latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
t = loader.get_template('polls/index.html')
c = Context({
'latest_poll_list': latest_poll_list,
})
return HttpResponse(t.render(c))
Este código carrega o template chamado polls/index.html e lhe passa um contexto. O contexto é um dicionário que mapeia variáveis do template para o nome dos objetos em Python.
Recarregue a página. Agora você verá um erro:
TemplateDoesNotExist at /polls/ polls/index.html
Ah. É porque não há template ainda. Primeiro, crie um diretório, em algum lugar de seu sistema de arquivos, que o Django tenha acesso. (O Django roda o que um usuário de seu servidor roda.) Portanto não o crie dentro de sua raiz de documentos. Você provavelmente o tornaria público, por questões de segurança.
Agora edite a setting TEMPLATE_DIRS em seu settings.py para dizer ao Django onde ele pode encontrar templates - exatamente como você fez na seção "Personalize o visual da administração" do Tutorial 2.
Quando você terminar, crie um diretório polls em seu diretório de templates. Dentro, crie um arquivo chamado index.html. Note que nosso loader.get_template('polls/index.html') acima direciona para [diretório de templates]/polls/index.html no sistema de arquivos.
Digite o seguinte código no arquivo do template:
{% if latest_poll_list %}
<ul>
{% for poll in latest_poll_list %}
<li>{{ poll.question }}</li>
{% endfor %}
</ul>
{% else %}
<p>No polls are available.</p>
{% endif %}
Carregue a página em seu navegador, e você verá uma lista marcada contendo a enquete "What?s up" do Tutorial 1.
É 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 vai a view index() completa, reescrita:
from django.shortcuts import render_to_response
from mysite.polls.models import Poll
def index(request):
latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
return render_to_response('polls/index.html', {'latest_poll_list': latest_poll_list})
Note que uma vez que você terminar isto em todas as views, nós não vamos precisar importar loader, Context e HttpResponse.
A função render_to_response() recebe o nome da template como primeiro argumento e um dicionário como segundo (e opcional) argumento. Ele retorna um objeto HttpResponse do template informado renderizado com o contexto passado.
Agora, vamos enfrentar a view de detalhes da enquete - a página que mostra a questão para uma enquete lançada. Aí vai:
from django.http import Http404
# ...
def detail(request, poll_id):
try:
p = Poll.objects.get(pk=poll_id)
except Poll.DoesNotExist:
raise Http404
return render_to_response('polls/detail.html', {'poll': p})
Um novo conceito aqui: a view levanta a exceção django.http.Http404 se a enquete com ID requisitado não existir.
É um dialeto muito comum usar o get() e levantar uma exceção Http404 se o objeto não existir. O Django fornece um atalho para isso. Aqui vai a view detail(), reescrita:
from django.shortcuts import render_to_response, get_object_or_404
# ...
def detail(request, poll_id):
p = get_object_or_404(Poll, pk=poll_id)
return render_to_response('polls/detail.html', {'poll': p})
A função get_object_or_404() recebe um modulo de modelo do Django como primeiro argumento e um determinado número de argumentos, que ele passa para a função get() do módulo. E levanta uma exceção 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 DoesNotExist em alto-nível, ou fazer a API de modelo levantar Http404 ao invés de DoesNotExist?
Porque isso seria acoplar a camada de modelo com a camada de visão. Um das principais metas de design do Django é evitar acoplamento.
Há 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.
Quando você levanta Http404 de dentro de uma view, o Django carregará uma view especial específica para manipulação de erros 404. Ela é encontrada através da variável handler404, que é uma string em Python com o caminho da view separado por pontos - o mesmo formato usado para informar a função de chamada das URLconf. Uma view 404 por sí só não tem nada de especial: é apenas uma view normal.
Você normalmente não terá de se incomodar em escrever views de 404. Por padrão, as URLconfs têm a seguinte linha no começo do arquivo:
from django.conf.urls.defaults import *
Que tem o cuidado de definir handler404 no módulo corrente. Como você pode ver em django/conf/urls/defaults.py, handler404 é definido para 'django.views.defaults.page_not_found' por padrão.
Mais três coisas a se notar sobre views 404:
- A view 404 também é chamada se o Django não conseguir casar todas as expressões regulares da URLconf;
- Se você não definir sua própria view 404 e simplismente usar a padrão, que é recomendadavel - você terá uma obrigação: criar um template 404.html na raiz de seu diretório de templates. A view padrão de 404 irá usar este template para todos os erros 404;
- Se DEBUG estiver definida como True (em seu módulo settings) então sua view 404 nunca será usada e a página de depuração será exibida em seu lugar.
Similarmente, URLconfs pode definir uma handler500, que aponta para uma view a ser chamada em caso de erro no servidor. Erros de servidor acontecem quando você tem erros de tempo de execução código da view.
De volta para view detail(), para nossa aplicação de enquentes. Dada a variável de contexto poll, aqui está como polls/detail.htm deve ser:
<h1>{{ poll.question }}</h1>
<ul>
{% for choice in poll.choice_set.all %}
<li>{{ choice.choice }}</li>
{% endfor %}
</ul>
O sistema de templates usa uma sintaxe separada por pontos para acessar os atributos da variável. No exemplo de {{ poll.question }}, primeiro o Django procura por uma chave de dicionário no objeto``poll``. Se não encontrar, ele tenta procurar por atributo com esse nome - que funciona neste caso. Se a localização do atributo também falhar, ele irá tentar chamar o método question() no objeto poll.
A chamada do método acontece no laço {% for %}: poll.choice_set.all é interpretado em Python como poll.choice_set.all(), que retorna objetos Choice iteraveis que são suportado para ser usado na tag {% for %}.
Veja o guia de templates para maiores detalhes de como os templates fucionam.
Vamos tomar um tempo para brincar com as views e o sistema de templates. Se você editar a URLconf, você poderá notar uma leve redundância nisto:
urlpatterns = patterns('',
(r'^polls/$', 'mysite.polls.views.index'),
(r'^polls/(?P<poll_id>\d+)/$', 'mysite.polls.views.detail'),
(r'^polls/(?P<poll_id>\d+)/results/$', 'mysite.polls.views.results'),
(r'^polls/(?P<poll_id>\d+)/vote/$', 'mysite.polls.views.vote'),
)
Isto é, mysite.polls.views está em todas as chamadas.
Como este é um caso comum, o framework de URLconf provê um atalho para prefixos comuns. Você pode fatorár os prefixos comuns e adicioná-los no primeiro argumento de patterns(), como abaixo:
urlpatterns = patterns('mysite.polls.views',
(r'^polls/$', 'index'),
(r'^polls/(?P<poll_id>\d+)/$', 'detail'),
(r'^polls/(?P<poll_id>\d+)/results/$', 'results'),
(r'^polls/(?P<poll_id>\d+)/vote/$', 'vote'),
)
Assim irá funcionar de forma indêntica à formatação anterior. Com uma pequena melhoria.
Já que nós estamos aqui, nós vamos tomar um tempo para desacoplar as URLs da aplicação poll da configuração de nosso projeto em Django. As aplicações no Django devem ser escritas para serem plugáveis - que é, cada aplicação em particular deveria ser transferível para qualquer outra instalação em Django com o mínimo de esforço.
Nossa aplicação poll é agradavelmente desacoplada neste ponto, graças à estrita estrutura de diretórios que o comando python manage.py startapp criou, mas uma parte dela está acoplada às settings em Django: a URLconf.
Nós editamos as URLs em mysite/urls.py, mas o modelo das URLs desta aplicação são específicas para ela, não para a instalação do Django - vamos então mover as URLs para dentro do diretório da aplicação.
Copie o arquivo mysite/urls.py to mysite/polls/urls.py. Então, altere o mysite/urls.py removendo as URLs específicas da aplicação poll e insira um include():
(r'^polls/', include('mysite.polls.urls')),
O include() simplismente referencia outra URLconf. Observe que a expressão regular não tem um $ (caracter que casa o fim da string) mas possui uma barra. Em algum momento o Django encontra O include(), e pega qualquer URL que tiver uma parte casada e aponta e envia a string restante para a URLconf incluída para o processamento posterior.
Veja o que acontece se um usuário vai para "/polls/34/" neste sistema:
- O Django irá tentar casar '^polls/';
- Ele irá cortar o texto casado ("polls/") e enviar o restante - "34/" - para a URLconf 'mysite.polls.urls' para processamento posterior.
Agora que nós desacoplamos isto, nós precisamos desacoplar a URLconf 'mysite.polls.urls' removendo o trecho "polls/" de cada linha:
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'),
)
A idéia por trás de include() e o desacoplamento da URLconf é criar facilmente URLs plug-and-play. Agora que as polls possuem sua própria URLconf, elas podem ser mudadas abaixo de "/polls/", ou abaixo de "/fun_polls/", ou abaixo de "/content_polls/", ou qualquer outra raiz de URL, que a aplicação irá funcionar.
Toda a aplicação poll está sobre URLs relativas, e não sobre URLs absolutas.
Quando você estiver confortável em escrever views, lêia a parte 4 deste tutorial para aprender sobre processamento de formulários simples e sobre as generic vies (views genéricas).
Hospedado por PyTown.com. Django Brasil é a comunidade brasileira de usuários do framework web Django. Django é uma marca registrada de Lawrence Journal-World.