Julia Elman e Mark Lavin
Novatec
© [2015] Novatec Editora Ltda Authorized Portuguese translation of the English edition of Lightweight Django, ISBN 9781491945940 © 2015 Julia Elman and Mark Lavin. This translation is published and sold by permission of O’Reilly Media, Inc., which owns or controls all rights to publish and sell the same. Tradução em português autorizada da edição em inglês da obra Lightweight Django, ISBN 9781491945940 © 2015 Julia Elman e Mark Lavin. Esta tradução é publicada e vendida com a permissão da O'Reilly Media, Inc., que detém ou controla todos os direitos para publicação e venda desta obra. © Novatec Editora Ltda. 2015. Todos os direitos reservados e protegidos pela Lei 9.610 de 19/02/1998. É proibida a reprodução desta obra, mesmo parcial, por qualquer processo, sem prévia autorização, por escrito, do autor e da Editora. Editor: Rubens Prates Tradução: Lúcia Kinoshita Revisão gramatical: Viviane Oshima Assistente editorial: Priscila A. Yoshimatsu Editoração eletrônica: Carolina Kuwabata ISBN: 978-85-7522-424-3 OG20150318 Histórico de impressões: Março/2015
Primeira edição
Novatec Editora Ltda. Rua Luís Antônio dos Santos 110 02460-000 – São Paulo, SP – Brasil Tel.: +55 11 2959-6529 Email: novatec@novatec.com.br Site: www.novatec.com.br Twitter: twitter.com/novateceditora Facebook: facebook.com/novatec LinkedIn: linkedin.com/in/novatec
capítulo 1
O menor projeto Django do mundo
Quantas de nossas jornadas no uso do Django não começaram com o tutorial oficial de criação de uma aplicação para enquetes? Para muitos, parece ser um ritual de passagem, porém, como introdução ao Django, é uma tarefa bastante difícil. Com vários comandos a serem executados e arquivos a serem gerados, é até mais difícil dizer a diferença entre um projeto e uma aplicação. Para usuários novos que queiram começar a criar aplicações, o Django passa a impressão inicial de ser uma opção “pesada” demais como framework web. Quais são algumas das maneiras de atenuar os temores desses novos usuários para que eles possam ter um início claro e simples? Vamos reservar um tempo para considerar as tarefas recomendadas para iniciar um projeto Django. A criação de um novo projeto geralmente começa com o comando startproject. Não há nenhuma mágica no que esse comando faz; ele simplesmente cria alguns arquivos e diretórios. Embora seja uma ferramenta útil, o comando startproject não é obrigatório para iniciar um projeto Django. Você é livre para organizar o seu projeto da maneira que quiser, de acordo com o que você quiser fazer. Em projetos maiores, os desenvolvedores se beneficiam da organização de código proporcionada pelo comando startproject. Entretanto, a conveniência desse comando não deve impedir você de entender o que ele faz e de saber por que ele é útil. Neste capítulo, apresentaremos um exemplo de como criar um projeto simples usando os blocos básicos de construção do Django. Por meio desse projeto “Hello World” descomplicado, criaremos uma aplicação Django simples usando uma abordagem de arquivo único. 21
22
Django Essencial
Hello Django Criar um exemplo do tipo “Hello World” em uma nova linguagem ou framework é um projeto inicial comum. Vimos esse exemplo de projeto inicial simples ser disponibilizado pela comunidade Flask para mostrar quão leve ele é como microframework. Neste capítulo, começaremos usando um único arquivo chamado hello.py. Esse arquivo conterá todo o código necessário para executar o nosso projeto Django. Para ter um projeto funcional completo, devemos criar uma view para servir o URL raiz e ter as configurações necessárias ao ambiente Django.
Criando a view O Django é chamado de framework MTV (Model-Template-View, ou Modelo-Template-Visão). A parte referente à view (visão) normalmente inspeciona a solicitação HTTP de entrada e faz queries ou compõe os dados necessários a serem enviados à camada de apresentação. Em nosso arquivo hello.py de exemplo, vamos implementar uma maneira simples de gerar uma resposta “Hello World”. from django.http import HttpResponse def index(request): return HttpResponse('Hello World')
Em um projeto maior, esse código normalmente estaria em um arquivo views.py em uma de suas aplicações. No entanto, não há nenhum requisito que obrigue as views a estar nas aplicações. Também não há nenhum requisito que as obrigue a estar em um arquivo chamado views.py. Isso é puramente uma questão de convenção, e não um requisito em que a estrutura de nosso projeto deverá estar baseada.
Capítulo 1 ■ O menor projeto Django do mundo
23
Os padrões de URL Para conectar nossa view à estrutura do site, devemos associá-la a um padrão de URL. Nesse exemplo, a própria raiz do servidor pode servir a view. O Django associa views a seus URL ao combiná-las com uma expressão regular que corresponda ao URL e qualquer argumento que possa ser chamado com a view. O exemplo a seguir está em hello.py e mostra como fazemos essa associação. from django.conf.urls import url from django.http import HttpResponse def index(request): return HttpResponse('Hello World') urlpatterns = ( url(r'^$', index), )
Esse arquivo combina tanto um arquivo views.py comum quanto o arquivo urls.py da raiz. Novamente, vale a pena observar que não há nenhum requisito que obrigue os padrões de URL a serem incluídos em um arquivo urls.py. Eles podem estar em qualquer módulo Python que possa ser importado. Vamos passar para as configurações do Django e as linhas simples necessárias para tornar o nosso projeto executável.
As configurações As configurações do Django detalham de tudo, desde conexões com bancos de dados e cache até funcionalidades associadas à internacionalização, além de recursos estáticos e carregados. Para muitos desenvolvedores que acabaram de começar, as configurações do Django representam um ponto significativo de confusão. Embora as versões mais recentes tenham trabalhado para reduzir o tamanho do arquivo default de configuração, ele ainda pode ser exagerado.
24
Django Essencial
O exemplo a seguir executará o Django em modo debugging. Fora isso, o Django simplesmente deverá ser configurado para saber em que local poderão ser encontrados os URLs da raiz e usará o valor definido na variável urlpatterns nesse módulo. No exemplo a seguir em hello.py, os URLs da raiz estão no módulo corrente e o urlpatterns definido na seção anterior será usado. from django.conf import settings settings.configure( DEBUG=True, SECRET_KEY='thisisthesecretkey', ROOT_URLCONF=__name__, MIDDLEWARE_CLASSES=( 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ), ) ...
Esse exemplo inclui uma configuração SECRET_KEY não aleatória, que não deverá ser usada em ambiente de produção. Uma chave secreta deverá ser gerada para a sessão default e para proteção contra CSRF (Cross-Site Request Forgery). É importante que qualquer site de produção tenha uma SECRET_KEY aleatória que permaneça privada. Para saber mais sobre esse assunto, acesse a documentação em https://docs.djangoproject.com/en/1.7/topics/signing/.
Precisamos definir as configurações antes de fazer qualquer importação adicional do Django, pois algumas partes do framework esperam que as configurações estejam definidas antes de poderem ser importadas. Normalmente, isso não será um problema, pois essas configurações estarão incluídas no arquivo settings.py. O arquivo gerado pelo comando startproject default também incluirá configurações para itens que não serão usados nesse exemplo, por exemplo internacionalização e recursos estáticos.
Capítulo 1 ■ O menor projeto Django do mundo
25
Executando o exemplo Vamos dar uma olhada em nosso exemplo durante o runserver. Um projeto Django típico contém um arquivo manage.py, usado para executar diversos comandos, como criar tabelas de banco de dados e executar o servidor de desenvolvimento. Esse arquivo tem um total de dez linhas de código. Adicionaremos as partes relevantes desse arquivo em nosso arquivo hello.py para proporcionar as mesmas funcionalidades oferecidas por manage.py: import sys from django.conf import settings settings.configure( DEBUG=True, SECRET_KEY='thisisthesecretkey', ROOT_URLCONF=__name__, MIDDLEWARE_CLASSES=( 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ), ) from django.conf.urls import url from django.http import HttpResponse def index(request): return HttpResponse('Hello World') urlpatterns = ( url(r'^$', index), ) if __name__ == "__main__": from django.core.management import execute_from_command_line execute_from_command_line(sys.argv)
26
Django Essencial
Agora você pode iniciar o exemplo na linha de comando: hostname $ python hello.py runserver Performing system checks... System check identified no issues (0 silenced). August 06, 2014 - 19:15:36 Django version 1.7c2, using settings None Starting development server at http://7.0.0.1:8000/ Quit the server with CONTROL-C.
e acessar http://localhost:8000/ em seu navegador favorito para ver “Hello World”, conforme exibido na figura 1.1.
Figura 1.1 – Hello World.
Agora que temos uma estrutura de arquivos bem básica definida, vamos prosseguir e adicionar mais elementos para servir nossos arquivos.
Capítulo 1 ■ O menor projeto Django do mundo
27
Melhorias Esse exemplo mostra algumas das partes fundamentais do framework Django: criar views, definir configurações e executar comandos de administração. Em sua essência, o Django é um framework Python que recebe solicitações HTTP e retorna respostas HTTP. O que acontece entre essas operações é de sua responsabilidade. O Django também disponibiliza utilitários adicionais para tarefas comuns envolvidas no tratamento de solicitações HTTP como renderizar HTML, fazer parse de dados de formulário e efetuar persistência de estado de sessão. Embora não seja obrigatório, é importante entender como esses recursos podem ser usados em sua aplicação de maneira descomplicada. Ao fazer isso, você entenderá melhor o framework Django como um todo e conhecerá seus verdadeiros recursos.
Aplicação WSGI No momento, nosso projeto “Hello World” está sendo executado por meio do comando runserver. Esse é um servidor simples, baseado no servidor de sockets da biblioteca-padrão. Ele tem funcionalidades úteis para efetuar desenvolvimento local, como carregamento automático de código. Apesar de ser conveniente para um desenvolvimento local, o runserver não é apropriado para implantação em ambiente de produção por questões de segurança. O WSGI (Web Server Gateway Interface) corresponde à especificação de como os servidores web se comunicam com os frameworks de aplicação como o Django, e foi definido pela PEP 333 e melhorado na PEP 3333. Há inúmeras opções para servidores web que se comuniquem por meio de WSGI, incluindo o Apache via mod_wsgi, o Gunicorn, o uWSGI, o CherryPy, o Tornado e o Chaussette. Cada um desses servidores precisa de uma aplicação WSGI adequadamente definida para ser usada. O Django oferece uma interface simples para criar essa aplicação por meio de get_wsgi_application.
28
Django Essencial ... from django.conf.urls import url from django.core.wsgi import get_wsgi_application from django.http import HttpResponse ... application = get_wsgi_application() if __name__ == "__main__": from django.core.management import execute_from_command_line execute_from_command_line(sys.argv)
Esse código normalmente estaria contido no arquivo wsgi.py criado pelo comando startproject. O nome application é simplesmente uma convenção usada pela maioria dos servidores WSGI; cada um oferece opções de configuração para que um nome diferente possa ser usado caso seja necessário. Agora o nosso projeto Django simples está pronto para o servidor WSGI. O Gunicorn é uma opção popular para um servidor de aplicações WSGI puramente Python; ele tem um histórico de desempenho sólido, é fácil de ser instalado e executa também com Python 3. O Gunicorn pode ser instalado por meio do Python Package Index (pip). hostname $ pip install gunicorn
Depois que o Gunicorn estiver instalado, ele poderá ser executado de maneira bem simples com o comando gunicorn. hostname $ gunicorn hello --log-file=[2014-08-06 19:17:26 -0400] [37043] [INFO] Starting gunicorn 19.1.1 [2014-08-06 19:17:26 -0400] [37043] [INFO] Listening at: http://127.0.0.1:8000 (37043) [2014-08-06 19:17:26 -0400] [37043] [INFO] Using worker: sync [2014-08-06 19:17:26 -0400] [37046] [INFO] Booting worker with pid: 37046
Como vimos na saída, esse exemplo está sendo executado com a versão 19.1.1 do Gunicorn. Os timestamps mostrados contêm o offset de seu fuso horário, que poderá ser diferente de acordo com a sua localidade. Os IDs de processo para arbiter e worker também serão diferentes.
Capítulo 1 ■ O menor projeto Django do mundo
29
Desde a versão R19, o Gunicorn não faz mais logging no console por padrão. A adição da opção --log-file=- garante que a saída será registrada no console. Você pode ler mais sobre as configurações do Gunicorn em http://docs.gunicorn.org/en/19.1/.
Como ocorre com o runserver no Django, o servidor ficará ouvindo em http://127.0.0.1:8000/. Isso funciona muito bem e permite ter uma configuração mais simples com a qual possamos trabalhar.
Configurações adicionais Embora o Gunicorn seja um servidor web pronto para produção, a aplicação em si ainda não está preparada para produção, pois DEBUG jamais deveria estar habilitado nesse ambiente. Como observamos anteriormente, SECRET_KEY também não é aleatória e deverá sê-lo para proporcionar mais segurança. Para obter mais informações sobre as implicações quanto à segurança associadas às configurações DEBUG e SECRET_KEY, consulte a documentação oficial do Django (https://docs. djangoproject.com/en/1.7/ref/settings/ ).
Essa situação leva a uma pergunta comum na comunidade Django: como o projeto deve administrar configurações diferentes para ambientes de desenvolvimento, staging (testes) e produção? A wiki do Django (https://code. djangoproject.com/wiki/SplitSettings) contém uma lista longa de abordagens, e há várias aplicações reutilizáveis cujo objetivo é lidar com esse problema. Uma comparação entre essas aplicações pode ser encontrada na página Django Packages (https://www.djangopackages.com/grids/g/configuration/). Embora muitas dessas opções possam ser ideais em alguns casos, por exemplo, converter settings.py em um pacote e criar módulos para cada ambiente, elas não se alinham muito bem com a configuração atual de arquivo único de nosso exemplo. A Twelve Factor App (http://12factor.net/) é uma metodologia para criação e implantação de aplicações de serviços HTTP. A metodologia recomenda separar a configuração e o código, bem como armazenar configurações em variáveis de ambiente. Isso faz a configuração ser fácil de ser alterada na implantação e a torna independente do sistema operacional.
30
Django Essencial
Vamos aplicar essa metodologia em nosso exemplo com hello.py. Há apenas duas configurações que provavelmente mudarão entre os ambientes: DEBUG e SECRET_KEY. import os import sys from django.conf import settings DEBUG = os.environ.get('DEBUG', 'on') == 'on' SECRET_KEY = os.environ.get('SECRET_KEY', os.urandom(32)) settings.configure( DEBUG=DEBUG, SECRET_KEY=SECRET_KEY, ROOT_URLCONF=__name__, MIDDLEWARE_CLASSES=( 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ), )
Como você pode ter notado, o default para DEBUG é True, e SECRET_KEY será gerada aleatoriamente sempre que a aplicação for carregada, caso não esteja definida. Isso funcionará nesse exemplo usado somente para brincar, porém, se a aplicação estivesse usando uma parte do Django que exigisse que SECRET_KEY permanecesse estável, por exemplo, se cookies assinados estivessem sendo utilizados, isso faria as sessões serem frequentemente invalidadas. Vamos analisar como isso se reflete na inicialização da aplicação. Para desabilitar a configuração DEBUG, devemos definir a variável de ambiente DEBUG com um valor diferente de on. Em um sistema derivado do Unix, como Linux, OS X ou FreeBSD, as variáveis de ambiente são definidas na linha de comando com o comando export. No Windows, utilize set. hostname $ export DEBUG=off hostname $ python hello.py runserver CommandError: You must set settings.ALLOWED_HOSTS if DEBUG is False.
Capítulo 1 ■ O menor projeto Django do mundo
31
Como podemos ver a partir do erro, ALLOWED_HOSTS não foi configurado pela nossa aplicação. ALLOWED_HOSTS é usado para validar valores do cabeçalho HTTP HOST de entrada e deve ser configurado com uma lista dos valores aceitáveis para HOST. Se a aplicação foi criada para servir example.com, então ALLOWED_HOSTS deverá permitir somente clientes que estejam solicitando example.com. Se a variável de ambiente ALLOWED_HOSTS não estiver definida, somente solicitações para localhost serão permitidas. O trecho de código de hello.py a seguir mostra isso: import os import sys from django.conf import settings DEBUG = os.environ.get('DEBUG', 'on') == 'on' SECRET_KEY = os.environ.get('SECRET_KEY', os.urandom(32)) ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', 'localhost').split(',') settings.configure( DEBUG=DEBUG, SECRET_KEY=SECRET_KEY, ALLOWED_HOSTS=ALLOWED_HOSTS, ROOT_URLCONF=__name__, MIDDLEWARE_CLASSES=( 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ), )
Com a nossa variável ALLOWED_HOSTS definida, agora temos uma validação para os valores do cabeçalho HTTP HOST de entrada. Para obter uma referência completa sobre a configuração de ALLOWED_HOSTS , consulte a documentação oficial do Django (https://docs.djangoproject.com/en/1.7/ref/settings/#allowed-hosts).
Fora do ambiente de desenvolvimento, a aplicação poderá servir vários hosts como localhost e example.com, portanto a configuração nos permite especificar vários nomes de host separados por vírgula.
32
Django Essencial hostname $ export DEBUG=off hostname $ export ALLOWED_HOSTS=localhost,example.com hostname $ python hello.py runserver ... [06/Aug/2014 19:45:53] "GET / HTTP/1.1" 200 11
Isso nos permite ter meios flexíveis de configuração entre os ambientes. Apesar de ser um pouco mais difícil alterar configurações mais complexas como INSTALLED_APPS ou MIDDLEWARE_CLASSES, essa opção está alinhada à metodologia em geral, que incentiva a existência de diferenças mínimas entre os ambientes. Se quiser fazer alterações complexas entre os ambientes, reserve tempo para considerar os impactos causados na testabilidade e na implantação da aplicação.
Podemos reiniciar DEBUG com o valor default ao remover a variável de ambiente do shell ou iniciar um novo shell. hostname $ unset DEBUG
Template reutilizável Até agora, esse exemplo focou em repensar o layout criado pelo comando startproject do Django. Entretanto, esse comando também permite usar um template para definir o layout. Não é difícil transformar esse arquivo em um template reutilizável para iniciar futuros projetos usando o mesmo layout básico. Um template para startproject é um diretório ou um arquivo zip considerados como um template Django quando o comando for executado. Por padrão, todos os arquivos-fontes Python serão considerados como um template. Esse processo receberá project_name, project_directory, secret_key e docs_version como contexto. Os nomes dos arquivos também serão resolvidos de acordo com esse contexto. Para transformar hello.py em um template de projeto (project_name/project_name.py), as partes relevantes do arquivo devem ser substituídas por essas variáveis.
Capítulo 1 ■ O menor projeto Django do mundo import os import sys from django.conf import settings DEBUG = os.environ.get('DEBUG', 'on') == 'on' SECRET_KEY = os.environ.get('SECRET_KEY', '{{ secret_key }}') ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', 'localhost').split(',') settings.configure( DEBUG=DEBUG, SECRET_KEY=SECRET_KEY, ALLOWED_HOSTS=ALLOWED_HOSTS, ROOT_URLCONF=__name__, MIDDLEWARE_CLASSES=( 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ), ) from django.conf.urls import url from django.core.wsgi import get_wsgi_application from django.http import HttpResponse def index(request): return HttpResponse('Hello World') urlpatterns = ( url(r'^$', index), ) application = get_wsgi_application() if __name__ == "__main__": from django.core.management import execute_from_command_line execute_from_command_line(sys.argv)
33
34
Django Essencial
Agora vamos salvar esse arquivo como project_name.py em um diretório chamado project_name. Além disso, em vez de usar os.urandom como default para SECRET_KEY, esse código gerará um segredo aleatório usado como default sempre que um novo projeto for criado. Isso torna a SECRET_KEY default estável no nível de projeto, enquanto a deixa suficientemente aleatória entre os projetos. Para usar o template com startproject, o argumento --template pode ser usado: hostname $ django-admin.py startproject foo --template=project_name
Esse comando deverá criar um arquivo foo.py em um diretório foo, que agora estará pronto para ser executado, como o hello.py original. Como mostrado nesse exemplo, certamente é possível escrever e executar um projeto Django sem a necessidade de utilizar o comando startproject. As configurações e o layout default utilizados pelo Django não são apropriados a todos os projetos. A opção --template de startproject pode ser usada para expandir esses defaults ou para reduzi-los, como vimos neste capítulo. Como ocorre com qualquer projeto Python, haverá um momento em que organizar o código em vários módulos será uma parte importante do processo. Para um site suficientemente focado, com apenas alguns URLs, nosso exemplo com “Hello World” poderá ser uma abordagem razoável. Um aspecto também interessante dessa abordagem está no fato de não ser imediatamente óbvio que o Django tenha um sistema de templating ou um ORM (Object-Relational Mapper, ou Mapeador objeto-relacional) incluídos. A abordagem deixa claro que você é livre para escolher qualquer biblioteca Python que ache que resolverá o seu problema de modo mais adequado. Usar o Django ORM não é mais obrigatório, como o tutorial oficial talvez implique que seja. Em vez disso, você usará o ORM se quiser. O projeto do próximo capítulo expandirá esse exemplo com um arquivo único para disponibilizar um serviço HTTP simples, além de usar outros utilitários que acompanham o Django.