Francisco Souza

Open source, Python, Django, Java, agile e outras coisas mais

Construindo Um CRUD Com Frameworks Python, Parte II: Django

O segundo framework a ser abordado na sequência de posts sobre CRUD’s usando frameworks Python é o Django, o mais badalado entre os frameworks Python, com comunidade mais ativa. Assim como foi com o Pylons, vamos ver como Django trabalha com URL’s, MVC (ou MTV… - veja mais a frente), abstração do banco de dados, etc. Vamos ao mesmo caso: no portal da empresa ABC Informática, desenvolveremos o cadastro de usuários (que, por incrível que pareça, não tem login, senha, essas coisas, apenas nome e idade hehe)…

A instalação do Django também é bem simples e se da pelo easy_install, assim como no Pylons. Então, basta acessar o terminal e digitar o comando:

# easy_install -U django

O Django fornece a ferramenta de linha de comando django-admin para criação de projetos e aplicações. Projetos Django se dividem em aplicações e é importante entender o conceito de Django Apps, para evitar o uso inadequado do framework. Vamos chamar nosso projeto de dj_portal_abc, então, para criar o projeto, basta executar o comando:

$ django-admin.py startproject dj_portal_abc

Será criado o diretório dj_portal_abc, com estrutura semelhante à seguinte:

Estrutura de arquivos de um projeto novo Django

Sim, a estrutura é muito mais simples que a estrutura do Pylons, mas a criança ainda vai crescer :) Agora que criamos o projeto, vamos configurá-lo. Faremos apenas alguns ajustes básicos: o banco de dados utilizado; o idioma padrão e também a timezone padrão. Abra o arquivo settings.py e encontre o seguinte trecho de configuração de banco de dados:

[cc lang=”python” wraplines=”false”]DATABASE_ENGINE = ” # ‘postgresql_psycopg2’, ‘postgresql’, ‘mysql’, ‘sqlite3’ or ‘oracle’. DATABASE_NAME = ” # Or path to database file if using sqlite3. DATABASE_USER = ” # Not used with sqlite3. DATABASE_PASSWORD = ” # Not used with sqlite3. DATABASE_HOST = ” # Set to empty string for localhost. Not used with sqlite3. DATABASE_PORT = ” # Set to empty string for default. Not used with sqlite3.[/cc]

Altere a variável DATABASE_ENGINE para sqlite3 e a variável DATABASE_NAME para portal_abc.db, ficando assim (note as linhas em destaque):

[cc lang=”python” wraplines=”false” highlights=”1, 2”]DATABASE_ENGINE = ‘sqlite3’ DATABASE_NAME = ‘portal_abc.db’ DATABASE_USER = ” DATABASE_PASSWORD = ” DATABASE_HOST = ” DATABASE_PORT = ”[/cc]

Agora vamos alterar a TIME_ZONE para São Paulo e o idioma padrão para português brasileiro. Logo abaixo da configuração do banco de dados, você encontra as seguintes linhas no settings.py:

[cc lang=”python” wraplines=”false”]# Local time zone for this installation. Choices can be found here:

http://en.wikipedia.org/wiki/List_of_tz_zones_by_name

although not all choices may be available on all operating systems.

If running in a Windows environment this must be set to the same as your

system time zone.

TIME_ZONE = ‘America/Chicago’

Language code for this installation. All choices can be found here:

http://www.i18nguy.com/unicode/language-identifiers.html

LANGUAGE_CODE = ‘en-us’[/cc]

Configure a variável TIME_ZONE como America/Sao_Paulo e a variável LANGUAGE_CODE como pt-br, ficando assim:

[cc lang=”python” wraplines=”false”]TIME_ZONE = ‘America/Sao_Paulo’

LANGUAGE_CODE = ‘pt-br’[/cc]

Agora que terminamos de configurar o projeto, vamos criar nossa aplicação. Vamos novamente utilizar o django-admin.py para gerar uma aplicação. Como no caso de iniciar um projeto, usamos startproject, para iniciar uma aplicação, usamos startapp. Intuitivo, não?

$ django-admin-py startapp usuarios

Após executar este comando, temos um novo diretório e a estrutura mudou um pouco. O Django criou o diretório com o nome da aplicação, e colocou lá alguns arquivos: models.py, onde definimos nossos modelos; tests.py, onde definimos nossos testes unitários (e também doctests); e views.py, nossa controladora que tem nome estranho =P Vamos começar definindo nosso model. Diferente do SQL Alchemy, usado no Pylons, o Django implementa o modelo de ORM chamado ActiveRecord. Reinout van Rees considera o pattern ActiveRecord excelente para pequenas queries, mas problemático para estruturas mais complexas. A disputa DataMapper x ActiveRecord, e a preferência por DataMapper, foi fator fundamental na decisão do SourceForge adotar Turbo Gears (outro framework Python, que também utiliza SQL Alchemy, muito influenciado pelo Pylons) ao invés Django, Ruby on Rails ou Cake PHP.

“Sem querer querendo”, eu vou apresentar um pouco uma comparação entre ActiveRecord e DataMapper em Python, estabelecida nas ferramentas Django ORM vs. SQL Alchemy. No meu exemplo mega simples, ActiveRecord será a melhor resposta sem sombra de dúvidas, mas não deixe de ler sobre o assunto para formar uma opinião.

Definindo o Model

No Django, os models são definidos dentro da aplicação, no módulo models.py. Cada model é uma classe que herda de django.db.models.Model. Abaixo segue nosso model Usuario:

[cc lang=”python” wraplines=”false”]class Usuario(models.Model): nome = models.CharField(max_length=100) idade = models.IntegerField()[/cc]

Note que a classe ficou bem simples. Agora, vamos dizer ao Django que vamos utilizar a aplicação usuarios em nosso projeto dj_portal_abc e depois criar o banco de dados. Para isso, basta abrir o arquivo settings.py novamente e buscar a tupla INSTALLED_APPS. Adicione ao final da tupla o nome da nossa aplicação,  ficando desta forma a tupla:

[cc lang=”python” wraplines=”false”]INSTALLED_APPS = ( ‘django.contrib.auth’, ‘django.contrib.contenttypes’, ‘django.contrib.sessions’, ‘django.contrib.sites’, ‘usuarios’, )[/cc]

Agora basta utilizar o manage.py, que permite gerenciar o banco de dados e também a execução da aplicação. Para criar as tabelas no banco de dados, rodamos o comando:

$ python manage.py syncdb

Como a aplicação django.contrib.auth está instalada, ele vai perguntar se desejamos criar um usuário. Já que este recurso não vai ser utilizado, podemos simplesmente responder “no”.

“Controllers” e URL’s no Django

Como visto no começo, controllers são chamados de views no Django. Assim, definimos nossas funções de controle no módulo views.py da nossa aplicação. O Django não fornece um padrão de acesso às URL’s, sendo necessário configurar todas as URLs para uso. A ideia de usar REST com Django fica como assunto de um próximo post. Neste CRUD, vamos seguir o padrão torto de trabalhar de maneira “não-RESTful”.

Vamos trabalhar na mesma ordem, criando primeiramente a view responsável por listar os usuários. Vamos chamá-la de listar_usuarios. Dentro do nosso arquivo views.py, da aplicação usuarios, adicionamos o seguinte código:

[cc lang=”python” wraplines=”false”]def listar_usuarios(request): usuarios = Usuario.objects.all() return render_to_response(‘usuarios/listar.html’, locals(), context_instance=RequestContext(request))[/cc]

Após criar a view, precisamos mapeá-la na configuração de urls. Abra o módulo urls.py e adicione ao objeto urlpatterns o item respectivo à view criada. Ficará desta forma:

[cc lang=”python” wraplines=”false” highlight=”11”]urlpatterns = patterns(”,

Example:

(r’dj_portal_abc/’, include(‘dj_portal_abc.foo.urls’)),

Uncomment the admin/doc line below and add ‘django.contrib.admindocs’

to INSTALLED_APPS to enable admin documentation:

(r’admin/doc/’, include(‘django.contrib.admindocs.urls’)),

Uncomment the next line to enable the admin:

(r’admin/’, include(admin.site.urls)),

(r’usuarios/$’, ‘usuarios.views.listar_usuarios’), )[/cc]

Vemos na linha 11 (em destaque) a configuração: dizemos que o endereço /usuarios/ será tratado pela view listar_usuarios. Por mais chato que isso possa parecer, tem que ser feito assim, infelizmente. Se executarmos o servidor agora, através do comando

$ python manage.py runserver

obteremos um erro: TemplateDoesNotExist at /usuarios/. Dentro da nossa view, utilizamos o atalho render_to_response, para renderizar nosso template ‘usuarios/listar.html’, passando para ele todas as variáveis locais (no caso, apenas a variável usuarios). Mas em que momento definimos este template? Em momento algum!

Vamos, antes de mais nada, configurar no settings.py qual será nosso diretório com os templates. Assim, criamos o diretório templates na raiz do nosso projeto (junto aos arquivos manage.py e settings.py), e abrimos o settings.py para indicar que aquele será nosso diretório de templates, na tupla TEMPLATE_DIRS. Para obter o caminho absoluto, fazemos uso do módulo os, da biblioteca padrão do Python. Assim, adiciona as duas linhas ao começo do arquivo settings.py:

[cc lang=”python” wraplines=”false”]import os RAIZ_DO_PROJETO = os.path.dirname(os.path.abspath(file))[/cc]

Em seguida, altere a tupla TEMPLATE_DIRS, para que fique desta forma:

[cc lang=”python” wraplines=”false”]TEMPLATE_DIRS = (

Put strings here, like “/home/html/django_templates” or “C:/www/django/templates”.

Always use forward slashes, even on Windows.

Don’t forget to use absolute paths, not relative paths.

os.path.join(RAIZ_DO_PROJETO, ‘templates’) )[/cc]

Após isso, criamos o arquivo base.html, que é nosso template que servirá de base. Este arquivo deve ficar dentro do diretório templates, e tem simplesmente o seguinte código:

[cc lang=”html” wraplines=”false”]

{% block “conteudo” %} {% endblock “conteudo” %}

[/cc]

Após esta definição, crie o template da listagem de usuários, dentro do diretório templates/usuarios, com o nome listar.html.  Este template fica com o seguinte código:

[cc lang=”html” wraplines=”false”]{% extends “base.html” %}

{% block “titulo” %}Listagem de usuários - {{ block.super }}{% endblock “titulo” %}

{% block “conteudo” %}

Cadastrar novo usuário

{% endblock “conteudo” %}[/cc]

Novamente, não vou postar o código de todos os templates, para evitar que o post cresça muito. Daqui pra frente, colocarei todos os templates no pastebin e todo o código estará disponível no Bitbucket.

Para não ficar abrindo e mapeando toda hora as URLs, vamos acessar o arquivo e mapear todas elas:

  • /usuarios: Uma lista com todos os usuários;
  • /usuarios/novo: Cria um novo usuário (exibe o formulário e também processa o formulário);
  • /usuarios/{id}: Exibe os dados de um usuário (visualização);
  • /usuarios/{id}/apagar: Apaga um usuário do banco de dados;
  • /usuarios/{id}/editar: Edita os dados de um usuário.

Assim, nosso arquivo urls.py fica assim:

[cc lang=”python” wraplines=”false”]from django.conf.urls.defaults import *

urlpatterns = patterns(”,

Example:

(r’dj_portal_abc/’, include(‘dj_portal_abc.foo.urls’)),

Uncomment the admin/doc line below and add ‘django.contrib.admindocs’

to INSTALLED_APPS to enable admin documentation:

(r’admin/doc/’, include(‘django.contrib.admindocs.urls’)),

Uncomment the next line to enable the admin:

(r’admin/’, include(admin.site.urls)),

(r’usuarios/$’, ‘usuarios.views.listar_usuarios’), (r’usuarios/novo/$’, ‘usuarios.views.novo’), (r’usuarios/(?P\d+)/$’, ‘usuarios.views.ver_usuario’), (r’usuarios/(?P\d+)/apagar/$’, ‘usuarios.views.apagar’), (r’usuarios/(?P\d+)/editar/$’, ‘usuarios.views.editar’), )[/cc]

Vamos agora criar todas essas views mapeadas. Vamos fazer na ordem: construir o formulário de cadastro, antes de tudo. Antes de criar a nossa view novo, definiremos o formulário, utilizando o recurso de formulário do Django (Django Forms). Para isto, crie um arquivo chamado forms.py dentro do diretório da aplicação usuarios. Este arquivo conterá nosso form, que é uma classe que herda da classe django.forms.Form. Além da classe django.forms.Form, o Django Forms fornece a classe django.forms.ModelForm, que se integra com os models e faz quase tudo de forma transparente. Veja como ficou a classe UsuarioForm:

[cc lang=”python” wraplines=”false”]class UsuarioForm(ModelForm): class Meta: model = Usuario[/cc]

Note que o Django faz tudo “por baixo dos panos”, relacionando a classe de modelo com o formulário. Também seria possível definir manualmente todos os campos. Para mais detalhes sobre Django Forms, consulte a documentação oficial.

Com a classe de formulário definida, podemos partir agora para nossa view, que fica com o seguinte código:

[cc lang=”python” wraplines=”false” highlight=”4,5”]def novo(request): if request.method == ‘POST’: form = UsuarioForm(request.POST) if form.is_valid(): form.save() return HttpResponseRedirect(reverse(‘usuarios.views.listar_usuarios’)) else: form = UsuarioForm() return render_to_response(‘usuarios/novo.html’, locals(), context_instance=RequestContext(request))[/cc]

Note que o Django faz algumas “mágicas” nas linhas destacadas: validação automática e salva os dados no banco de dados. Tudo por causa da uma forte e acoplada integração com os models. Eu sei que isso de perguntar se o método do request é POST lembra um pouco PHP, mas é a forma como geralmente se trabalha nas views :)

Agora é só definir o template, que recebe o formulário pela variável forms. O código do template está aqui: http://pastebin.com/f1479663c. Nossa view e nosso template estão prontos, vamos seguir a ordem e criar agora a view de visualização dos dados, chamada ver_usuario. Seu código fica bem simples:

[cc lang=”python” wraplines=”false”]def ver_usuario(request, id): usuario = Usuario.objects.get(pk=id) return render_to_response(‘usuarios/ver.html’, locals(), context_instance=RequestContext(request))[/cc]

Depois de definir a view, basta construir o template. O código do template usuarios/ver.html é o seguinte: http://pastebin.com/f47a43b9a. Novamente, vou deixar a view editar por conta, e vou terminar na view apagar, que é a mais simples de toda:

[cc lang=”python” wraplines=”false”]def apagar(request, id): usuario = Usuario.objects.get(pk=id) usuario.delete() return HttpResponseRedirect(reverse(‘usuarios.views.listar_usuarios’))[/cc]

E pronto! =) Aí está, podemos rodar e testar nosso projeto. O código completo do projeto pode ser obtido no Bitbucket.