No capítulo anterior foi explicado como montar um projeto Django e executar o servidor de desenvolvimento do Django. Neste capítulo serão vistos os conceitos básicos de como criar páginas dinâmicas com o Django.
Como primeiro objetivo iremos criar uma página que tem como saída a famosa mensagem de exemplo: "Hello world."
Se estivesse publicando uma simples página Web sem um framework Web, bastaria digitar
"Hello World" em um arquivo texto, chamá-lo de hello.html
e fazer upload do mesmo
para um diretório em um servidor Web em algum lugar. Note que nesse processo foram
especificadas duas peças chave de informação sobre a página: o conteúdo (a string
"Hello world"
) e sua URL (http:://www.exemplo.com/hello.html
, ou talvez
http:://www.exemplo.com/files/hello.html
caso tenha posto em um subdiretório)
Com Django, as mesmas coisas são especificadas, porém de modos diferentes. Os conteúdos da página são produzidos por uma função view (visão), e a URL é especificada em um URLconf. Primeiro será escrita a função view "Hello World".
Dentro do diretório meusite
que o django-admin.py startproject
criou no
último capítulo, crie um arquivo vazio chamado views.py
. Esse módulo Python conterá
as views vistas neste capítulo. Note que não há nada especial sobre o nome views.py
-- o Django não se preocupa como o arquivo é chamado, como será visto mais para
frente -- mas é uma boa ideia chamá-lo de views.py
por ser uma convenção, para o
benefício de outros desenvolvedores que irão ler seu código.
A view "Hello world" é simples. Aqui está toda a função, incluindo os imports, que
deverá ser digitada no arquivo views.py
:
from django.http import HttpResponse def hello(request): return HttpResponse("Hello world")
Vamos analisar esse código linha-por-linha:
Primeiro, é importada a classe
HttpResponse
, que está localizada no módulodjango.http
. É necessário importá-la pois é usada no código.Depois é definida uma função chamada
hello
-- a função view.Cada função view tem pelo menos um parametro chamado
request
, por convenção. Este é um objeto que contém informação sobre a requisição Web atual que ativou a view, e é uma instância da classedjango.http.HttpRequest
. Nesse exemplo não é feito nada comrequest
, mas de qualquer modo deve ser o primeiro parametro da view.Note que o nome da função view não importa, não é necessário nomeá-la de uma maneira específica para que o django a reconheça. É chamanda de
hello
, pois esse nome indica de forma clara a essência da view, mas nada impede de que fosse chamada dehello_wonderful_beautiful_world
, ou algo do tipo. Na próxima seção, "Primeiro URLconf", será explicado como o Django encontra essa função.A função só contém uma linha: simplesmente retorna um objeto
HttpResponse
que foi instanciado com o textoHello World
.
A lição principal aqui é a seguinte: a view é somente uma função Python que
recebe um HttpRequest
como primeiro argumento e retorna uma instância de
HttpRequest
. Para que uma função Python seja uma view Django é necessário
que faça essas duas coisas. (Existem exceções, mas chegaremos nelas depois.)
Se, neste ponto, for executado python manage.py runserver
de novo, ainda será
exibida a mensagem "Welcome to Django", sem nenhum traço da view "Hello World".
Isso acontece, pois o projeto meusite
ainda não sabe da view hello
; é
preciso mostrar explicitamente ao Django que essa view está sendo ativada em uma
URL particular. (Continuando a analogia anterior de publicar arquivos HTML estáticos,
neste ponto é criado o arquivo HTML, mas ainda não foi feito upload dele no servidor
web) Para ligar uma função view para uma URL com o Django, é utilizado um URLconf.
Um URLconf é como um índice remissivo para sua página Django. Basicamente é um
mapeamento entre URLs e funções view que devem ser chamadas para essas URLs. É como
dizer ao Django: "Para esta URL, chame este código, e para aquela URL, chame aquele
código". Por exemplo: "Quando alguém visita a URL /foo/
, a função view foo_view()
é chamada, que está no arquivo Python views.py
."
Quando django-admin startproject
foi executado no capítulo anterior, um URLconf
foi criado automaticamente: o arquivo urls.py
. Por padrão ele parece com
algo assim:
from django.conf.urls import patterns, include, url # Uncomment the next two lines to enable the admin: # from django.contrib import admin # admin.autodiscover() urlpatterns = patterns('', # Examples: # url(r'^$', 'mysite.views.home', name='home'), # url(r'^mysite/', include('mysite.foo.urls')), # Uncomment the admin/doc line below to enable admin documentation: # url(r'^admin/doc/', include('django.contrib.admindocs.urls')), # Uncomment the next line to enable the admin: # url(r'^admin/', include(admin.site.urls)), )
Este URLconf padrão inclui algumas funcionalidades comumente usadas no Django dentro de comentários, para que ativá-las seja tão fácil quanto descomentar as linhas certas. Se os códigos comentados forem ignorados, aqui está a essência de um URLconf:
from django.conf.urls.defaults import patterns, include, url urlpatterns = patterns('', )
Vamos analisar linha-por-linha deste código:
- A primeira linha importa três funções do módulo
django.conf.urls.defaults
, que são a infraestrutura do URLconf do Django:patterns
,include
eurls
. - A segunda linha chama a função
patterns
e salva o resultado em uma variável chamadaurlpatterns
. A funçãopatterns
recebe um único argumento: uma string vazia. (A string pode ser usada para prover um prefixo comum para funções view, que será visto no :doc:`chapter08`.)
O que deve-se notar aqui é a váriavel urlpatterns
, que o Django espera encontrar
em seu módulo URLconf. Essa variável define o mapeamento entre URLs e o código que
manipula essas URLs. Por padrão, como pode-se ver, o URLconf está vazio -- sua
aplicação Django está vazia. (Como uma nota, é desse jeito que o Django mostra
a página "Welcome to Django" vista no último capítulo. Se um URLconf está vazio,
o Django assume que um novo projeto foi iniciado e, portanto, mostra essa mensagem.)
Para adicionar uma URL e view ao URLconf, só é necessário adicionar um mapeamento
entre um URLpattern e a função view. Aqui mostra-se como fazer a ligação na nossa
view hello
:
from django.conf.urls.defaults import patterns, include, url from mysite.views import hello urlpatterns = patterns('', url(r'^hello/$', hello), )
(Note que os códigos comentados foram removidos para poupar espaço. É possível deixar as linhas se quiser.)
Foram feitas duas mudanças aqui:
- Primeiro, a view
hello
foi importada de seu módulomysite/views.py
, que é acessado comomysite.views
na sintaxe de importação do Python. (Isso assume quemysite/views.py
está no seu diretório Python; veja a barra lateral para detalhes.) - Depois é adicionada a linha
url(r'^ĥello/$'),
à variávelurlpatterns
. Essa linha é denominada URLpattern. A funçãourl()
avisa ao Django como lidar com a url que está sendo configurado. O primeiro argumento é uma string de correspondência de padrão (pattern-matching, uma expressão regular; será explicado melhor mais a frente) e o segundo argumento é a função view para aquele padrão.url()
pode levar outros argumentos, que serão vistos mais profundamente no :doc:`chapter08`.
.. nota:: Um detalhe importante que foi introduzido é o caracter ``r`` no começo da string de expressão regular. Ele avisa o Python que a string é uma "raw string" -- seu conteúdo não deve interpretar contrabarras -- como na string ``'\n'``, que é uma string com um único caracter contendo uma nova linha. Quando ``r`` é adicionado, o Python não faz a substituição, então ``r'\n'`` é uma string composta de dois caracteres contendo uma contrabarra e um "n". Há uma colisão natural entre o uso de contrabarras pelo Python e as contrabarras utilizadas em expressões regulares, então é fortemente recomendado utilizar raw strings sempre que for definir expressões regulares em Python. Todos os URLpatterns neste livro serão raw strings.
Em poucas palavras, foi instruído ao Django que qualquer solicitação feita a URL
/hello/
deve ser manuseada pela função view hello
.
Diretório Python
O diretório Python é a lista de diretório do sistema que o Python
procura quando a instrução import
é utilizada.
Por exemplo, supondo que o diretório Python está em ['', '
'/usr/lib/python2.7/site-packages', '/home/username/djcode']
. Caso a instrução
from foo import bar
seja executada, o Python procurará um módulo chamado
foo.py
no diretório atual. (A primeira ocorrência no diretório Python, uma
string vazia, significa "o diretório atual.") Se o arquivo não existir, o Python
procurará pelo arquivo /usr/lib/python2.7/site-packages/foo.py
. Se esse
arquivo também não existir, o Python tentará procurar pelo arquivo
/home/username/djcode/foo.py
. Por fim, se aquele arquivo não existir, será
levantado um ImportError
.
Para descobrir seu diretório Python, inicie o interpretador interativo Python e digite isso:
>>> import sys >>> print sys.path
Geralmente não é necessário se preocupar com o diretório Python -- o Python e
Django tomam conta disso automaticamente nos bastidores. (Configurar o diretório
Python é uma das tarefas que o script manage.py
faz.)
Vale a pena discutir a sintaxe desse URLpattern, pois pode não parecer óbvio a
primeira vista. Apesar de querer encontrar a URL /hello/
, o padrão é um pouco
diferente disso. Aqui está o por quê:
O Django remove a barra do começo de cada URL antes de checar os URLpatterns. Isto significa que o URLpattern visto no capítulo não possui uma barra no começo em
/hello/
. (A princípio isso pode parecer nada intuitivo, mas isso simplifica as coisas, como a inclusão de URLconfs dentro de outros URLconfs, que será visto no Capítulo 8.)O padrão inclui um acento circunflexo (
^
) e um sifrão ($
). Esses são caracteres que possuem signficado especial em expressões regulares: o acento circunflexo signfica "o padrão deve casar com o começo da string" e o sifrão significa "o padrão deve casar com o fim da string."Esse conceito é melhor explicado através de um exemplo. Se ao invés de usar o padrão
'^hello/'
(sem o sifrão no final), então qualquer URL começada por/hello/
seria casa, como/hello/foo
e/hello/bar
, e não somente/hello/
. Semelhantemente, se o acento circunflexo fosse retirado (ou seja,'hello/$'
), o Django casaria qualquer URL terminada emhello/
, como/foo/bar/hello/
. Se fosse usadohello/
, sem acento circunflexo ou sifrão, então qualquer URL que contessehello/
seria casada, como/foo/hello/bar
. Desse modo usamos tanto o acento circunflexo quanto o sifão para garantir que somente a URL/hello/
seja casada -- nada mais, nada menos.A maioria dos URLpatterns começarão com acentos circunflexos e terminarão com sifrão, mas é interessante ter a flexibilidade de poder casar de formas mais sofisticadas.
Pode estar se perguntando o que acontece se a URL
/hello
(isto é, sem a barra no final) for requisitada. Essa URL não casaria, pois o URLpattern exige uma barra no fim. Entretanto, por padrão, qualquer requisição feita a uma URL que não casa com a URLpattern e não termina com uma barra é redirecionada para a mesma URL com uma barra no final. (Isso é controlado pela configuraçãoAPPEND_SLASH
do Django, que é explicada no Apêndice D.)Se optar por utilizar URLs terminadas por barra, tudo o que precisa fazer é adicionar barras em cada URLpattern e atribuir
True
aoAPPEND_SLASH
. Caso opte por não deixar barras no fim das URLs, ou quiser escolher dependendo da URL, deixe oAPEEND_SLASH
comoFalse
e colocar barras no fim de cada URLpattern que desejar.
Outro ponto importante sobre esse URLconf é que passamos a função view
hello
como um objeto sem chamar a função. Essa é uma das características
chaves do Python (e outras linguagens dinâmicas): funções são objetos de
primeira classe, o que quer dizer que é possível utilizá-las como quaisquer
outras variáveis. Maneiro, não?
Para testar as mudanças feitas no URLconf, inicie o servidor de desenvolvimento
do Django, como visto Capítulo 2, executando o comando python manage.py runserver
.
(Se já estiver rodando, não tem problema. O servidor detecta mudanças feitas no
código automaticamente e as recarrega, para não ser preciso reiniciar o servidor quando
houver mudança.) O servidor está rodando no endereço http://127.0.0.1:8000/
, então
abra o navegador e vá para http://127.0.0.1:8000/hello/
. Deve aparecer o texto
"Hello world" -- a saída da sua view Django.
Eba! Você fez sua primeira página Django.
Expressões regulares
Expressões regulares (ou regexes) são uma forma compacta de especificar padrões em um texto. Enquanto URLconfs do Django possibilitam o uso de qualquer regex para casas URLs, só serão utilizados alguns símbolos de regex na prática. Aqui estão alguns símbolos mais utilizados:
Símbolo | Casa com |
---|---|
. (dot) |
Qualquer (um) caracter |
\d |
Qualquer (um) digito |
[A-Z] |
Qualquer caracter entre A e Z (caixa alta) |
[a-z] |
Qualquer caracter entre a e z (caixa baixa) |
[A-Za-z] |
Qualquer caracter entre a e z (caixa baixa e alta) |
+ |
Um ou mais da expressão anterior (por ex., \d+ casa um
ou mais digitos. |
[^/]+ |
Um ou mais caracteres até (e não incluindo) uma barra |
? |
Zero ou um da expressão anterior (por ex., \d? casa
zero ou mais digitos |
* |
Zero or mais da expressão anterior (por ex., \d* casa
zero, um ou mais que um dígito |
{1,3} |
Entre um e três (incluso) da expressão anterior (por ex/,
\d{1,3} casa um, dois ou três digitos) |
Para mais sobre expressões regulares, veja http://turing.com.br/material/regex/python_re.html
Neste ponto, o URLconf define somente um URLpattern, o que lida com as
requisições da URL /hello/
. O que acontece quando é feito uma requisição
para outra URL?
Para descobrir, rode o servidor de desenvolvimento do Django e visite alguma
página como http://127.0.0.1:8000/goodbye/
ou
http://127.0.0.1:8000/hello/subdirectory/
, ou até http://127.0.0.1:8000/
(a "raiz" do site). Deverá aparecer a mensagem "Page not found" (ver
Figura 3-1). O Django exibe essa mensagem, pois você requisitou uma URL que
não está especificada em seu URLconf.
A função dessa página vai além de exibir a mensagem de erro 404. Ela também mostra qual URLconf exatamente o Django usou para cada padrão nesse URLconf. Com essa informação é possível saber por quê a URL requisitada lançou um erro 404.
Naturalmente, essa informação sensível é só para você, o desenvolvedor Web. Se esse fosse um site em produção disponível na Internet, não seria bom exibir essa informação para o público. Por esse motivo, essa página "Page not found" é exibida somente se seu projeto Django está no modo debug. Mais para frente será explicado como desativar esse modo. Por enquanto, saiba que cada projeto Django está no modo debug quando é criado pela primeira vez. Se o projeto não esá no modo debug, o Django mostra outra mensagem 404.
Como explicado na última seção, você verá uma mensagem de erro ao acessar a
raiz do site -- http://127.0.0.1:8000
. O Django não adiciona nada magicamente
à raiz do site; essa URL não é tratada de modo especial. O desenvolvedor deve
atribuir um URLpatternt a ela, como todo outro acesso em seu URLconf.
O URLpattern que casa com a raiz do site é um pouco não intuitivo, então será
explicado aqui. Quando for implementar a view da raiz do site, use a URLpattern
'^$'
, que casa uma string vazia. Por exemplo:
from mysite.views import hello, my_homepage_view urlpatterns = patterns('', url(r'^$', my_homepage_view), # ... )
Antes de continuar para nossa segunda função view, vamos parar e aprender um
pouco sobre como o Django funciona. Especificamente, quando é exibida a mensagem
"Hello world" ao visitar http://127.0.0.1:8000/hello/
no seu navegador, o que
o Django faz por trás?
Tudo começa com o arquivo de configurações. Quando é executado python
manage.py runser
, o script procura por um arquivo chamado settings.py
,
dentro do site mysite
. Esse arquivo contem todo o tipo de configuração
para esse projeto Django, como (tudo em caixa alta): TEMPLATE_DIRS
,
DATABASES
e etc. A configuração mais importante é chamada de
ROOT_URLCONF
. Essa configuração avisa ao Django qual módulo deve ser usado
como URLconf para esse site.
Lembra quando django-admin.py startproject
criou os arquivos
settings.py
e urls.py
? O settings.py
gerado automaticamente contém um
configuração ROOT_URLCONF
que aponto para o urls.py
gerado automaticamente.
Abra o arquivo settings.py
e veja por si mesmo; deve parecer com algo assim:
ROOT_URLCONF = 'mysite.urls'
Isso corresponde ao arquivo mysite/urls.py
.
Quando uma requisição é feita para uma URL em particular, por exemplo uma
requisição para /hello/
, o Django carrega o URLconf apontado pelo
ROOT_URLCONF
. Então checa cada URLpattern naquele URLconf, a fim de comparar
a URL requisitada com os padrões, um por um, até encontrar um que case.
Quando um padrão é casado, é chamada a função view associada com esse padrão,
passando-a como um objeto HttpRequest
como primeiro parametro. (O objeto
HttpRequest
será explicado melhor mais para a frente.)
Como visto no primeiro exemplo, uma função view deve retornar um
HttpResponse
. Uma vez que isso é feito o Django lida com o resto, convertendo
o objeto Python para uma requisição web com os cabeçalhos HTTP e corpo (ou seja,
conteúdo da página) apropriados.
Resumindo:
- Uma requisição chega a
/hello/
. - Django determina o URLconf raiz analisando a configuração
ROOT_URLCONF
. - Django examina todas as URLpatterns no URLconf até encontrar a primeira
que case com
/hello/
. - Se for casado, é chamado a função view associada.
- A função view retorna um
HttpResponse
. - Django converte o
HttpResponse
para uma resposta HTTP, que resulta em uma página Web.
Agora você sabe o básico de como fazer páginas em Django. É realmente simples, só é necessário escrever funções view e mapeá-las a URLs através de URLconfs.
Nossa view "Hello world" serviu para demonstrar o básico de como o Django
funciona, mas não foi um exemplo de uma página dinâmica, pois o conteúdo
da página é sempre o mesmo. Cada vez que /hello
é exibida, será mostrada
a mesma coisa; na verdade, poderia ser um arquivo HTML estático.
Para nossa segunda view, mas criar algo mais dinâmico -- uma página que exibe a data e hora atuais. Esse exemplo é um bom e, simples, próximo passo, pois não envolve banco de dados ou entrada de usuário, somente a saída do relógio interno do servidor. Só é um pouco mais interessante que o "Hello world", mas demonstrará alguns conceitos.
Esta view precisa fazer duas coisas: calcular a hora e data atuais, e retornar
um HttpResponse
contendo esse valor. Se possui experiência com Python,
deve saber que há um módulo Python chamado datetime
para calcular datas.
Aqui mostra como usá-lo:
>>> import datetime >>> now = datetime.datetime.now() >>> now datetime.datetime(2008, 12, 13, 14, 9, 39, 2731) >>> print now 2008-12-13 14:09:39.002731
Isso é simples o bastante e não tem nada a ver com o Django. É só código Python. (Queremos enfatizar quais códigos são "só Python" vs código específico do Django. Conforme for aprendendo Django, queremos ensiná-lo Python, para que possa usar o mesmo para outros projetos que não envolvem Django).
Para o Django fazer com que a view mostre a data e hora atuais, só é necessário
colocar a instrução datetime.datetime.now()
em uma view e retornar um
HttpResponse
. Fica assim:
from django.http import HttpResponse import datetime def current_datetime(request): now = datetime.datetime.now() html = "<html><body>It's now %s.</body></html>" % now return HttpResponse(html)
Como na função view hello
, esse código deve estar em views.py
. Note que
não mostramos a função hello
neste exemplo por brevidade, mas por completude,
aqui está como todo o views.py
fica:
from django.http import HttpResponse import datetime def hello(request): return HttpResponse("Hello world") def current_datetime(request): now = datetime.datetime.now() html = "<html><body>It's now %s.</body></html>" % now return HttpResponse(html)
(De agora em diante não mostraremos código anterior em exemplos, exceto quando necessário. Você deve saber diferenciar a partir do contexto quais partes do exemplo são novas das antigas).
Vamos passar pelas mudanças que fizemos ao views.py
para acomodar a view
current_datetime
.
Adicionamos um
import datetime
na parte superior do módulo, para calcular datas.O nova função
current_datetime
calcula a data e hora atuais, com um objetodatetime.datetime
, e a armazena como a variável localnow
.A segunda linha de código dentro da view constrói uma resosta HTML usando funcionalidade do Python de formata strings. O
%s
na string é um placeholder, e o símbolo de porcentagem após a string significa "Substitua o%s
da string com o valor da variávelnow
". A variávelnow
é, tecnicamente, um objetodatetime.datetime
, não uma string, mas o caracter de formatação%s
faz a conversão para sua representação em string, que é algo do tipo"2008-12-13 14:09:39.002731"
. Isso resultará em uma string HTML como"<html><body>It is now 2008-12-13 14:09:39.002731.</body></html>"
.(Sim, esse código HTML é inválido, pois tentamos manter os exemplos simples e curtos)
Por fim, a view retorna um objeto
HttpResponse
que contém a resposta, assim como fizemos na viewhello
.
Após essa adição ao views.py
, adicione o URLpatter ao urls.py
para
avisar o Django qual URL deve acessar essa view. Algo como /time/
é um
bom nome:
from django.conf.urls.defaults import patterns, include, url from mysite.views import hello, current_datetime urlpatterns = patterns('', url(r'^hello/$', hello), url(r'^time/$', current_datetime), )
Foram feitas duas mudanças. Primeiro importamos a função current_datetime
no começo do código. Depois, e mais importante, adicionamos a URLpattern que
mapeia a URL /time/
àquela view. Está começando a entender como funciona?
Com a view escrita e o URLconf atualizado, execute o runserver
e visite
http://127.0.0.1:8000/time/
no seu navegador. Deverá ser exibidas a data
e hora atuais.
Fuso horário do Django
Dependendo do seu computador, a data e hora atuais pode estar errado.
Isso acontece pois o Django usa o fuso horário America/Chicago
como
padrão. (Algum tem de ser o padrão e esse é o fuso horário que os
desenvolvedores originais do Django vivem) Se você vive em outro lugar,
você pode mudá-lo no arquivo settings.py
. Leia os comentários nesse
arquivo para um link atualizado contendo a lista de todas os fuso horário
do globo.
Agora é uma boa hora para ressaltar a filosofia por trás dos URLconfs e do Django em geral: o princípio de acoplamento fraco. De for simples, acoplamento fraco é uma abordagem em desenvolvimento de software que valoriza a importância de fazer partes intercambiáveis. Se duas partes de código são fracamente acopladas, então mudanças feitas em uma das partes terá pouco ou nenhum efeito a outra.
URLconfs do Django são um bom exemplo desse princípio na prática. Em uma aplicação web em Django, as definições de URLs e funções views são fracamente acopladas, ou seja, a decisão de qual URL chamará qual função e a implementação em si da função, ficam em lugares separados. Isso permite mudar uma parte sem afetar a outra.
Por exemplo, considere a função current_datetime
feita recentemente. Para mudar
a URL da aplicação de /time/
para current-time
, é necessário somente mudar
o URLconf, sem ter de se preocupar com a view em si. Do mesmo modo, para mudar
a função view, alterando sua lógico de algum jeito, poderíamos fazer isso sem
afetar a URL com que a função está ligada.
Além disso, para mostrar a funcionalidade de mostrar data e hora locais para
várias URLs, tudo o que levaria seria editar o URLconf, sem ter que alterar
o código da view. Neste exemplo, nosso current_datetime
está disponível para
duas URLs. É um exemplo não muito real, mas essa técnica pode ser útil:
urlpatterns = patterns('', url(r'^hello/$', hello), url(r'^time/$', current_datetime), url(r'^outra-pagina-de-hora/$', current_datetime), )
URLconfs e view são fracamente acopladas na práticas. Mais exemplos dessa importante filosofia serão dados ao longo deste livro.
Na view current_datetime
o conteúdo da página, a data e hora atuais,
eram dinâmicos, mas a URL (/time
) era estática. Entretanto, na maioria das
aplicações Web dinâmicas, há parâmetros na URL que influenciam na saída da
página. Por exemplo, uma loja de livros online pode dar a cada livro uma URL,
como /livros/243/
e /livros/81196/
.
Criaremos uma terceia view que exibe a data e hora atuais deslocados por um
certo número de horas. O objeto é fazer um site em que a página /hora/mais/1
mostre a data/hora de uma hora no futuro, a página /hora/mais/2
mostre a
data/hora de duas horas no futuro, a página /hora/mais/3
mostre a data/hora
de três horas no futuro, e assim por diante.
Um novato pode pensar em criar uma função view para cada deslocamento de hora, que resultaria em um URLconf parecido com este:
urlpatterns = patterns('', url(r'^hora/$', current_datetime), url(r'^hora/mais/1/$', uma_hora_a_mais), url(r'^hora/mais/2/$', duas_horas_a_mais), url(r'^hora/mais/3/$', tres_horas_a_mais), url(r'^hora/mais/4/$', quatro_horas_a_mais), )
Claramente essa linha de pensamento é falha. Não somente isso resultaria em funções view redundantes, mas a aplicação seria limitada por um número pré-definido de horas -- um, dois, três ou quatro horas. Para criar uma página de cinco horas no futuro, seria necessário criar uma view separada e adicionar uma linha no URLconf, aumentando a duplicação. É necessário abstrair um pouco.
Uma palavra sobre URLs bonitas
Se você possui experiência em outra plataforma de desenvolvimento web,
como PHP ou Java, você deve estar pensando: "Ei, vamos utilizar um query
string parameter!", algo como /hora/mais?horas=3
, em que as horas seriam
atribuídos a partir do parametro horas
na query string da URL (a parte
após o ?
).
É possível fazer isso com Django (é explicado como no Capitulo 7), mas
uma das filosofias do Django é que a URL deve ser linda. A URL
/time/plus/3/
é muito mais limpa, simples, legível, fácil de dizer alto
para alguém e... simplesmente mais bonita que a query string equivalente.
URLs bonitas são uma carecterística de aplicações Web de qualidade.
O sistema URLconf do Django encoraja o uso de URLs bonitas ao fazer mais simples usar URLbonitas do que não usar.
Então como projetamos nossa aplicação para lidar com deslocamentos de horas
arbitrários? A chave é utilizar URLpatterns curingas. Como citado
anteriormente, uma URLpatterns é uma expressão regular; por isso, podemos
utilizar o padrão de expressões regulares \d+
para casar um ou mais digitos:
urlpatterns = patterns('', # ... url(r'^hora/mais/\d+/$', horas_adiante), # ... )
(Estamos utilizando o # ...
para sugerir que podem haver outros URLpatterns
que foram retirados desde exemplo).
Esse novo URLpattern casa qualquer URL parecida com /hora/mais/2
,
/hora/mais/25
, ou até /time/mais/100000000000
. Pensando nisso, vamos
limitar o deslocamento máximo para 99 horas. Isso significa que permitiremos
números com um ou dois digitos, na sintaxe de expressões regulares isso se
traduz em \d{1,2}
:
url(r'^hora/mais/\d{1,2}/$', horas_adiante),
.. nota:: Ao construir aplicações Web, é sempre importante considerar as entradas mais estranhas e decidir se a aplicação deve suportar essa entrada. As entradas estranhas foram removidas ao limitar o deslocamento para 99 horas.
Agora que designamos um curinga para a URL, precisamos passar o dado desse
curinga para qualquer deslocamento de hora. Isso é feito colocando o parênteses
em volta do dado no URLpattern que queremos salvar. No caso do nosso exemplo,
queremos salvar qualquer número digitado na URL, então colocamos os parênteses
em volta do \d{1,2}
, assim:
url(r'^hora/mais/(\d{1,2})/$', horas_adiante),
Se você conhece expressões regulares, deve estar se sentindo em casa. Estamos usando parênteses para capturar dados do texto casado.
A URLconf final, incluindo as duas views anteriores, fica assim:
from django.conf.urls.defaults import * from mysite.views import hello, current_datetime, hours_ahead urlpatterns = patterns('', url(r'^hello/$', hello), url(r'^time/$', current_datetime), url(r'^hora/mais/(\d{1,2})/$', hours_ahead), )
Resolvido isso, vamos escrever a view hours_ahead
.
hours_ahead
é bastante parecido com a view current_datetime
escrita
anteriormente, com uma diferença chave: recebe mais um argumento, o número de
horas do deslocamento. Aqui está o código da view:
from django.http import Http404, HttpResponse import datetime def hours_ahead(request, offset): try: offset = int(offset) except ValueError: raise Http404() dt = datetime.datetime.now() + datetime.timedelta(hours=offset) html = "<html><body>Em %s hora(s), será %s.</body></html>" % (offset, dt) return HttpResponse(html)
Vamos analisar o código passo-a-passo:
A função view
hours_ahead
possui dois parametros:request
eoffset
.request
é um objeto do tipoHttpRequest
, assim como emhello
ecurrent_datetime
. Repetindo: cada view sempre recebe umHttpRequest
como primeiro parâmetro.offset
é a string capturada pelo parênteses no URLpattern. Por exemplo, se a URL requisitada for/hora/mais/3/
, então a stringoffset
seria a string'3'
. Se a URL fosse/time/plus/21/
,offset
seria a string'21'
. Note que todos os valores capturados sempre serão strings e não inteiros, mesmo se a string for composta somente de dígitos, como'21'
.(Tecnicamente, valores capturados sempre serão objetos Unicode, não strings puras do Python, mas não se preocupe com essa diferenciação neste momento.)
Decidimos chamar a variável
offset
, mas pode ser nomeada como prefirir, contanto que seja um identificador Python válido. O nome da variável não importa; o que importa é que seja o segundo argumento, apósrequest
. (Também é possível utilizar argumento por palavra-chave, ao invés de posicional, em um URLconf. Isso é explicado no Capítulo 8).
A primeira coisa que fazemos dentro da função é chamar
int()
na variáveloffset
. Isso converte o valor da string para um inteiro.Note que o Python levantará uma exceção
ValueError
se a funçãoint()
for chama em um valor que não possa ser converido para inteiro, como a stringfoo
. Neste exemplo, se for encontrado umValueError
, nós levantamos a exceçãodjango.http.Http404
, que, como deve estar imaginando, resulta em um erro 404 "Página não encontrada".Leitores astutos se perguntarão: como chegaríamos ao caso
ValueError
, já que a expressão regular no nosso URLpattern,(\d{1,2})
, captura somente digitos e, portanto,offset
só será uma string composta de digitos? A resposta é, não chegaríamos, pois o URLpattern dá um modesto, porém poderoso, nível de validação de entrada, mas ainda checamos peloValueError
, caso a função view seja chamada de outro modo. É uma boa prática implementar funções view que não façam suposições sobre seus parametros. Acoplamento fraco, lembra-se?Na próxima linha da função calculmaos a data e hora atuais e o número apropiado de horas. Já haviamos visto
datetime.datetime.now()
, usado na viewcurrent_datetime
. O novo conceito aqui é o uso de operações aritméticas com data/hora, usando o objetodatetime.timedelta
e somando ao objetodatetime.datetime
. O resultado é armazenado na variáveldt
.Essa linha também mostra o por quê de chamarmos
int()
nooffset
, a funçãodatetime.timedelta
requer que o parametrohours
seja um inteiro.Depois construimos a saída HTML dessa função view, como feito em
current_datetime
. A pequena diferença desta linha com a anterior é que e utilizado a funcionalidade do Python de formatar strings com dois valores, não somente um. Por isso, há dois símbolos%s
na string e uma tupla de valores a serem inseridos:(offset, dt)
.Por fim retornamos um
HttpResponse
do código HTML. Por agora esse já é um velho conhecido.
Após escrever esse URLconf e função view, inicie o servidor de desenvolvimneto
do Django (caso ainda não esteja sendo executado) e visite
http://127.0.0.1:8000/time/plus/3/
para verificar que funcione. Então tente
http://127.0.0.1:8000/time/plus/5
. Então http://127.0.0.1:8000/time/plus/24
.
Por fim, visite http://127.0.0.1:8000/time/plus/100/
para verificar que o
padrão em seu URLconf só aceita números com um ou dois digitos; Django deverá
mostrar um erro "Página não encontrada" nesse caso, assim como visto anteriormente
na seção "Uma nota rápida sobre erros 404". A URL
http://127.0.0.1:8000/time/plus/
(sem deslocamento de hora) também deverá
lançar um erro 404.
Ordem do código
Neste exemplo escrevemos primeiro o URLpattern e depois a view, mas nos exemplos anteriores escrevemos primeiro a view e então o URLpattern. Qual técnica é melhor?
Bem, cada desenvolvedor é diferente.
Se você é uma pessoa com uma boa visão de todo o problema, pode fazer sentido escrever todos os URLpatterns de uma só vez, no começo do projeto para então codificar as views. Isso tem a vantagem de fornecer uma lista de afazeres, e essencialmente define os requerimentos de parâmetros para as funções views a serem escritas.
Se você é o tipo de desenvolvedor que começa as coisas por baixo, talvez prefira escrever as views primeiro e depois ligá-las a URLs. Essa forma também está certa.
No fim a técnica utilizada é aquela seu cérebro melhor se adapta. Ambos métodos são válidos.
Leve um tempo para admirar as boas aplicações Web que criamos até agora...
Agora vamos quebrá-las! Vamos intencionalmente colocar um erro Python em
nosso arquivo views.py
comentando as linhas offset = int(offset)
na view hours_ahead
:
def hours_ahead(request, offset): # try: # offset = int(offset) # except ValueError: # raise Http404() dt = datetime.datetime.now() + datetime.timedelta(hours=offset) html = "<html><body>In %s hour(s), it will be %s.</body></html>" % (offset, dt) return HttpResponse(html)
Carregue o servidor de desenvolvimento e navegue para /time/plus/3/
. Você
verá uma página de erro com bastante informações, encluindo uma mensagem
TypeError
no topo da pagina: "unsupported type for timedelta hours
compontent: unicode"
.
O que aconteceu? Bem, a função datetime.timedelta
expera que o parametro
horas
seja um inteiro, e comentamos a parte de código que converte
offset
para inteiro. Isso fez com que datetime.timedelta
levantasse o
TypeError
. É o típico tipo de bug pequeno que todo programador acaba
passando por em alguma hora.
A motivação desse exmplo era demonstrar as páginas de erro do Django. Leve algum tempo explorando a página de erro e preste atenção nas informações que a página fornece.
Aqui estão alguns pontos para se saber:
No topo da página, há informações importantes sobre a exceção: o tipo, quaisquer parâmetros para a exceção (a mensagem
"unsupported type"
, neste caso), o arquivo em que a exceção foi levantada, e o número da linha em que ocorreu.Abaixo disso, a página exibe o traceback Python completo para a exceção. O traceback é parecido com o padrão, que é exibido no interpetrador Python em linha de comando, exceto que é mais interativo. Para cada nível ("frame") na pilha, o Django mostra o nome do arquivo, o nome da função ou método, o número da linha e o código-fonte da linha.
Ao clicar na linha do código-fonte (em cinza escuro) você verá várias linhas antes e depois da linha com erro, para dar contexto.
Clique "Local vars" em qualquer "frame" na pilha para ver uma tabela de todas variáveis locais e seus valores, nesse "frame", na exata hora em que a exceção foi levantada. Essa informação de debug pode ser de grande ajuda.
Observe o texto "Switch to copy-and-paste-view" embaixo do cabeçalho "Traceback". Clique nessa palavras e o traceback mudará para uma versão alternativa que pode ser facilmente copiada e colada. Use essa opção quando quiser compartilhar o traceback da exceção com outras pessoas para receber apoio técnico -- do pessoal da sala de chat do Django no IRC ou na lista de emails de usuários do Django.
Embaixo, o botão "Share this traceback on a public Web site" fará esse trabalho em somente um clique. Clique para postar o traceback no http://www.dpaste.com/, aonde será gerada uma URL única que pode ser compartilhada com outras pessoas.
Após isso, a seção "Request information" inclui uma gama de informação sbore a requisição Web que gerou o erro: informação GET e POST, valor dos cookies, e meta informação, como cabeçalhos CGI. O Apêndice G possui uma referência completa sobre toda a informação que um objeto de requisição contém.
Abaixo da seção "Request information", a seção "Settings" lista todas as configurações dessa instalação particular do Django. (Já mencionamos o
ROOT_URLCONF
, e mostraremos várias configurações do Django no decorrer do livro. Todas as configurações são cobertas em detalhes no Apêndice D).
A página de erro do Django é capaz de exibir mais informação em alguns casos
especiais, como no caso de ocorrer um erro com a sintaxe de template. Esses
serão explicados mais tarde, ao explicar o sistema de templates do Django.
Por agora, retire os comentários da linha offset = inf(offset)
para que
a view volte a funcionar corretamente.
Você é do tipo de programar que gosta de fazer debug com a ajuda de instruções
print
bem localizadas? Você pode utilizar a página do Django para fazer isso,
mas sem as instruções print
. A qualquer ponto na sua view, insira
temporariamente um assert False
para ativar a página de erro. Então será
possível visualizar as variáveis locais e o estado do programa. Aqui está um
exemplo usando a view hours_ahead
:
def hours_ahead(request, offset): try: offset = int(offset) except ValueError: raise Http404() dt = datetime.datetime.now() + datetime.timedelta(hours=offset) assert False html = "<html><body>In %s hour(s), it will be %s.</body></html>" % (offset, dt) return HttpResponse(html)
Por fim, é óbvio que muitas dessas informações são sensíveis, pois mostram o conteúdo do código Python e configuração do Django, e seria burrice mostrar essa informação a qualquer um na Internet. Uma pessoa maliciosa poderia usar para fazer engenharia reversa da sua aplicação Web e fazer coisas más. Por esse motivo a página de erro do Django só é exibida quando seu projeto está em modo debug. Como desativar o modo debug é explicado no Capítulo 12. Por enquanto, fique atento que todo projeto django está em modo debug quando iniciado. (Soa familiar? Os erros "Página não encontrada", descritos no começo do capítulo, funcionam da mesma maneira).
Até agora estivemos escrevendo nossas funções view com HTML codificado diretamento no código Python. Fizemos isso para manter tudo simples para demonstrar os conceitos mais importantes, mas no mundo real essa é, quase sempre, uma má ideia.
Django vem com uma simples, porém poderosa engine de template que permite separar o design da página do código por debaixo. A engine de templates do Django será explicada no :doc:`next chapter <chapter04>`.