Construindo um CRUD com frameworks Python, Parte II: Django

dez 16 2009

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:

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.

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):

DATABASE_ENGINE = 'sqlite3'
DATABASE_NAME = 'portal_abc.db'
DATABASE_USER = ''
DATABASE_PASSWORD = ''
DATABASE_HOST = ''
DATABASE_PORT = ''

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:

# 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'

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

TIME_ZONE = 'America/Sao_Paulo'

LANGUAGE_CODE = 'pt-br'

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:

class Usuario(models.Model):
nome = models.CharField(max_length=100)
idade = models.IntegerField()

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:

INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'usuarios',
)

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:

def listar_usuarios(request):
usuarios = Usuario.objects.all()
return render_to_response('usuarios/listar.html', locals(), context_instance=RequestContext(request))

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:

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'),
)

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:

import os
RAIZ_DO_PROJETO = os.path.dirname(os.path.abspath(__file__))

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

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')
)

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:

{% block "conteudo" %}
{% endblock "conteudo" %}

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:

{% extends "base.html" %}

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

{% block "conteudo" %}
<ul> {% for usuario in usuarios %}&nbsp;
    <li><a href="{% url usuarios.views.ver_usuario usuario.id %}">{{ usuario.nome }}</a></li>
{% endfor %}</ul>
<a href="{% url usuarios.views.novo %}">Cadastrar novo usuário</a>

{% endblock "conteudo" %}

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:

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'),
)

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:

class UsuarioForm(ModelForm):
class Meta:
model = Usuario

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:

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))

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:

def ver_usuario(request, id):
usuario = Usuario.objects.get(pk=id)
return render_to_response('usuarios/ver.html', locals(), context_instance=RequestContext(request))

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:

def apagar(request, id):
usuario = Usuario.objects.get(pk=id)
usuario.delete()
return HttpResponseRedirect(reverse('usuarios.views.listar_usuarios'))

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

23 responses so far

  1. [...] This post was mentioned on Twitter by Loiane, Francisco Souza. Francisco Souza said: Construindo um CRUD com frameworks Python, Parte II: Django http://is.gd/5pOHP [...]

  2. Eu acho que você deveria fazer um versão utilizando as generic views[1], acredito que isso mostre mais o poder do framework do que simplesmente fazer uma versão tendo que escrever todo o código.

    [1]. http://docs.djangoproject.com/en/dev/ref/generic-views/#create-update-delete-generic-views

  3. Salve Furlan,
    quando resolvi escrever o tutorial, pensei em usar as generic views, mas por fim decidi fazer tudo na mão mesmo. Não tenho muitas justificativas pra isso, deve ter sido a cafeína, sei lá =)

    De qualquer forma, obrigado pelo feedback o/

  4. Legal, vou aguardar para ver então :)

  5. Franscico,
    Apesar de hoje eu estar totalmente voltado para o Web2Py achei excelente o teu tutorial, simples, direto e objetivo. Meus sinceros parabéns e sucesso no blog.

  6. Concordo com o Arthur.

    Acho que essa parte Parte II merece uma Parte II (rs) trantando sobre o uso das Generic Views. ;)

  7. Salve Jader, muito obrigado pelo feedback! :)

    Seguindo a indicação do Furlan, eu devo fazer um “remake” dessa parte, mostrando as generic views =)

    E, Leandro, a ideia é abordar o web2py também nessa “sequência de CRUDs”.

    Abraços o/

  8. Muito bom. Pena que usa Django.

  9. I want to quote your post in my blog. It can?
    And you et an account on Twitter?

  10. Yes, you can quote all my posts :)

  11. [...] Python, Parte IV: Django Remake Postado em 23 de dezembro de 2009 por Francisco Souza Na segunda parte desta sequência de tutoriais sobre frameworks Python, eu apresentei a criação do CRUD em Django, fazendo “tudo na mão”. Fui intimado [...]

  12. cara vc esqueceu da view editar não?

  13. Olá Leandro, criei a view editar no projeto do Bitbucket, mas não descrevi a criação dela no artigo.

    Veja o código: http://bitbucket.org/franciscosouza/tutorial-django-crud/src/tip/usuarios/views.py

    Abraços,
    Francisco

  14. [...] segunda parte desta sequência de tutoriais sobre frameworks Python, eu apresentei a criação do CRUD em Django, fazendo “tudo na mão”. Fui intimado [...]

  15. Cara sou um novato no python e django…
    estou fazendo um tcc nessa linguagem…

    e to tendo problemas na adaptação….

    tipo aqui não sei em qual template??? Criar um html novo ??? com qual nome???
    Obrigado por enquanto!!! Abraços!!!

    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:

  16. Salve Killiam,
    de acordo com o código da view, o template se chama ver.html e fica dentro do subdiretório usuarios (dentro da pasta de templates).

    Logo, é um novo arquivo html, chamado ver.html, que deve ficar dentro da pasta templates/usuarios.

    Abraços

  17. Caro amigo francisco…..

    To com uma dúvida meio incomun…

    Cara foi acompanhando pelo seu blog e adaptando ao meu projeto… o CRUD…

    Ocorre um erro no caminho… pois tenho uma view no meu projeto q é do blog ( q funciona) … e outra no diretório teste q seria essa linha de baixo… porém não sei como proceder pois dah erro no caminho???

    Efetuar Testes

    Desculpa o incomodo denovo!!!! Mas no início o bagulho é complicado!!!

    Abraços!!!

    KILLIAM KIPPER

  18. “”" Efetuar Testes “”"

  19. Olá, você criou uma django app chamada “teste”?

    Neste caso, seu link deve apontar para {% url “testes.views.testes” %}

    Killiam, eu recomendaria como material de estudo de Django livro Aprendendo Django no Planeta Terra, que apresenta o framework de forma simples e descomplicada.

  20. Amigo tentei seguir seu manual na risca mas me ocorre o seguinte erro!!!

    Using the URLconf defined in dj_portal_abc.urls, Django tried these URL patterns, in this order:

    1. ^usuarios/$
    2. ^usuarios/novo/$
    3. ^usuarios/(?P\d+)/$
    4. ^usuarios/(?P\d+)/apagar/$
    5. ^usuarios/(?P\d+)/editar/$

    The current URL, , didn’t match any of these.

    Obrigado…. ahhhh obrigado pela dica ….
    Aprendendo Django no Planeta Terra…… muito legal!!!!

    Ahhhh … no encontramos em curitiba… PYTHON BRASIL 6.

  21. Killiam,
    você obteve esse erro tentando acessar qual URL?

    Abraços ;)

  22. Qnd acesso http://localhost:8000/
    ocorre o erro q lhe enviei acima!

    Não entendi direito se erra isso q vc me pediu….!!!!

    Abraços!

  23. Então Killiam, com o mapeamento criado, a URL http://localhost:8000 realmente não funciona.

    As URLs que funcionam são:

    http://localhost:8000/usuarios/
    http://localhost:8000/usuarios/novo
    http://localhost:8000/usuarios/
    http://localhost:8000/usuarios//apagar
    http://localhost:8000/usuarios//editar

    Abraços

Leave a Reply

Spam protection by WP Captcha-Free