sábado, 24 de janeiro de 2009

Parte 5 - Integrando o cadastro aos usuários do Django


Neste tópico, mostrarei um dos muitos recursos do Django em detalhes: a Integração do cadastro do projeto ao usuários Django.

Vamos primeiro entender do que se trata. Cadastro é uma das muitas aplicações que geralmente temos em todos os projetos, e muita das vezes, os usuários que se cadastrarem tem acesso privilegiado à algumas seções. Para restringir o acesso não autorizado ao conteúdo controlado, era necessário controlar em todas as páginas se o usuário estava logado, se tinha permissão de fazer àquela requisição, etc.
No Django há uma forma fácil de fazer isso. Vamos ao código. Crie sua aplicação cadastro:

python manage.py startapp cadastro


E deixe o model da seguinte forma:






O relacionamento da classe cadastro recém criada e a de usuários do Django ocorre na linha 11, onde temos uma FK para a classe User, importada na linha 2.

Outra coisa interessante, é a linha 3, onde importamos a lista de estados brasileiros provida pelo próprio Django. Utilizo esta lista na linha 25 para prover uma escolha no campo estado da classe cadastro.

Abaixo da classe meta que vimos nos posts anteriores, temos um método muito importante. A def save é a responsável por fazer a persistencia dos dados no banco. O que estou fazendo nesta classe é simplemente sobrescrevendo ela, de forma a funcionar como preciso.

Quando um objeto é adicionado ao banco, o método save é chamado e instancia um objeto de uma classe, setando os valores passados para cada respectivo atributo.

Caso for sobrescrever a def save, é necessário prever as duas situações: quando um objeto for inserido ou quando for alterado. Em ambos os casos, o o save é chamado.

Para testar qual caso estaremos lidando basta fazer o teste da linha 36:

if not self.id:

Se não existir conteudo dendo do atributo id do objeto atual, o mesmo estará sendo inserido ao banco de dados, caso contrário, alterado.


Neste exemplo utilizaremos o email como usuário. Como o usuário do Django tem que ser único, farei alguns tratar isso. Na linha 37, é feito uma busca no banco de cadastro para verificar se o email já existe no sistema.

c = Cadastro.objects.filter(email=self.email).count()

Caso o email existir (linha 38), levantaremos uma exceção EmailExistente impedir o cadastro.

Veremos como utilizar as excessões mais a frente.

Nas linhas 41 à 47, é feito uma verificação para saber se o usuário já existe. Caso existir, o mesmo é atribuído ao objeto u, caso contrário, é criado um novo usuário com base no email e senha fornecidos e atribuido ao objeto u. Em ambos os casos, o objeto u é salvo e a FK para a classe User é setada com o mesmo.


Para alteração (linhas 48 à 52), basta setar os atributos da classe user com os novos dados forncecidos.


Após este tratamento, será chamado o método save original para fazer a persistência, passando os seguintes parâmetros: a classe Cadastro e nosso objeto em questão (self), da seguinte forma:

super(Cadastro, self).save()


As classes CadastroPF e CadastroPJ, são classes normais iguais as que já foram feitas, com apenas uma diferença: não herdam da models.Model, mas sim da classe Cadastro, onde temos os atributos comuns às duas. Crie o restante das classes conforme a figura abaixo:






Crie um admin.py da seguinte forma:




Note que apesar de criar uma administração pra classe Cadastro, esta não está registrada na administração. Desta forma, apenas será inserido um cadastro de pessoa física ou jurídica, e veremos a herança funcionado.



Instale sua aplicação no settings, sincronize o banco, inicie o servidor e faça os testes necessários.


svn: Revision 8
hasta!


domingo, 18 de janeiro de 2009

Parte 4 - Criando views

Para concluir essa primeira fase do tutorial, faremos agora exemplo com duas views para a aplicação CD.

Abra o arquivo views.py e o deixe da seguinte forma:

# -*- coding: utf-8 -*-
# Create your views here.

from django.shortcuts import render_to_response
from app.cd.models import Interprete, Album

def listaInterpretes(request):

interpretes = Interprete.objects.order_by('nome')
return render_to_response('cds/interpretes.html', {'interpretes':interpretes,})


def listaCds(request,slug=None):

cds = Album.objects.filter(interprete__slug = slug)
return render_to_response('cds/cds.html', {'cds':cds,})

A função render_to_response é a responsável de passar todos os objetos para o template especificado.

Para listar os intérpretes, utilizaremos def listaInterpretes. Todas as defs criadas no views.py para utilização no front precisam do parâmetro obrigátorio resquest. Através dele, será possível pegar conteúdos dos POSTs, GETs e SESSIONs.

A primeira linha da def listaInterpretes, é a responsavél por pegar todos os registros contidos no banco para esta classe e através do método order_by, os registros já virão ordenados pelo campo solicitado, neste caso, o nome.

Ao clicar no nome do intérprete, queremos mostrar quais cds estão relacionados a ele. Faremos isso utilizando a listaCds. Da mesma forma que fizemos com a primeira def, será necessário pegar todos os cds, porém agora temos uma restrição: mostraremos apenas os cds vinculados ao intérprete escolhido.

Vamos enteder como isso será feito:

cds = Album.objects

Neste ponto, temos todos os cds contidos no banco de dados. Para filtrar apenas os relacionados ao intérprete escolhido utilizaremos o método filter.

Nossa chave estrangeira interprete da classe Album é uma conexão direta aos objetos da classe Interprete, sendo possivel desta forma acessar quaisquer conteudos lá contidos.

Lembra do campo slug do model? Aqui ele será utilzado. Cada intérprete possui um slug, e como ele estará contido na url, por que não utilizá-lo para o filtro? É extamente isso que iremos fazer.

No comando filter, vamos filtrar pelo campo slug da classe Interprete da seguinte forma:

filter(interprete__slug = slug)

O parâmetro slug desta def será passado pela url. Com as defs prontas veremos agora como criar tais urls para deixá-las elegantes e funcionais.

O URLS.PY

Adicone ao urls.py da raiz do seu projeto a seguinte linha abaixo da url do admin:

(r'^cds/', include('app.cd.urls')),

Esta linha importará todas as urls contidas no urls.py da sua aplicação cd. É possivel colocá-las no arquivo urls.py da raiz, mas por questão de organização, criaremos um outro urls.py dentro da aplicação, referenciando as mesmas través do include acima, mapeadas pela url: /cds/.

Agora crie dentro da pasta cd, um arquivo chamado urls.py e deixe-o da seguinte forma:



from django.conf.urls.defaults import *

urlpatterns = patterns('app.cd.views',

(r'^$', 'listaInterpretes'),
(r'^interpretes/$', 'listaInterpretes'),
(r'^(?P<slug>[-\w]+)/$', listaCds),

)


Com as importações necessárias, definiremos os padrões de urls desta aplicação.
Neste exemplo estão sendo criadas três urls:

  • /cds/
    Irá funcionar de maneira similar ao index.html em uma pasta qualquer. Essa urls chamará a def listaInterpretes contidas no views.py da aplicação cd.

  • /cds/interpretes/
    Esta url terá a mesma funcionalidade da anterior.

  • /cds/
    Aqui, o slug do intérprete será passado através da expressão regular para texto : [-\w]+ , que chamará a def listaCds, passando o segundo parâmetro necessária para a listagem de cds: o slug do intérprete.

OS TEMPLATES

Criarei os dois templates dos exemplos acima de forma bem simples, apenas para servir de exemplo.

Dentro da pasta templates na raiz do projeto crie a pasta cds. Dentro dela crie os dois arquivos abaixo:

interpretes.html



Na linha 14 da imagem a acima, temos um for que irá passar por todos os registros vindos o objeto interepretes que foi passado na view. Para cada registro, será criado um link para redirecionar até SLUG/, onde o mesmo será o valor contido no atributo slug do objeto i corrente.
Conforme vimos anteriormente, o parâmetro slug, será passado através da expressão regular contida no urls.py que foi criado na aplicação.

Vejamos o outro template a ser criado:


cds.html


Neste template, apenas temos um for, que irá correr todos os registros do objeto cds filtrados pelo slug do intérprete, conforme vimos na view.


OBSERVAÇÕES:

  • Note que as variáveis apenas são ACESSADAS no template, não é possivel definí-las no mesmo.

  • Para os comandos, deve-se utilizar o bloco:
    {% comando %} com seu respectivo bloco de fechamento {% endcomando %}.

  • Para acessar o conteúdo dos objetos deve-se utilizar: {{ obj.atritbuto }}, conforme nos exemplos acima.

Agora basta iniciar o servidor e acessar http://localhost:8000/cds/, se estiver rodando local.

Repositório atulizado até este post: Revisao 7.

hasta!

sexta-feira, 16 de janeiro de 2009

Parte 3 - Criação da primeira APP

Dentro da pasta do projeto iremos criar a primeira aplicação deste projeto. Começarei por CD. Navegue até a pasta do projetop via prompt e execute o comando:

python manage.py startapp cd

Este comando irá criar uma pasta com 3 arquivos:

  • __init__.py
  • models.py
    Neste arquivo será colocado todos os modelos da aplicaçã
  • views.py
    Aqui ficam as views que utilizaremos no front. Voltaremos a falar delas mais a frente.

Por hora abra o models.py.

Acima da importação do pacote models, coloque a codificação a ser utilizada:

# -*- coding: utf-8 -*-
from django.db import models


Deixando tudo em UTF-8, problemas de acentuação tanto no banco quanto nos templates serão evitados.
Mantenha essa string sempre na primeira linha de todos os models.py e views.py.


CRIANDO O PRIMEIRO MODELO


Nesta aplicação, defini que os cds serão divididos por categoria, intérprete e conterão algumas informações espefíficas do album tais como: foto, ano e faixas. Vamos ao código:




A classe categoria:

class Categoria(models.Model):

nome = models.CharField(max_length=100)
slug = models.SlugField(max_length=100)

class Meta:

ordering = ['nome']

def __unicode__(self):

return self.nome



A classe categoria herda as propriedades da classe Model, por isso o import nas primeiras linhas.

Os atributos:

nome = models.CharField(max_length=100)
slug = models.SlugField(max_length=100)


Ambos serão renderizados como um INPUT TEXT no admin, porém o campo slug gera uma melhor indexação no banco, além de ser o grande responsável pelas urls elegantes que criaremos para as views.
Em ambos os casos o parâmetro max_length é obrigatório.

O campo slug será utlizado de forma a identificar um registro nos banco de forma única no banco, e será preenchido de uma forma interessante quando chegarmos na administração desse modelo.

A classe Meta, que está definida dentro de da classe recém criada possui muitos recursos interessantes. Para categoria, estou utilizando apenas a ordenação padrão quando os objetos da mesma forem listados:

ordering = ['nome']

Com o ordering posso colocar a ordenação por quaisquer atributos pertencentes a minha classe, e utilizar o sinal "-" (menos) para ordenação decrescente, como por exemplo:

ordering = ['-id']


O método __unicode__ é o responsável por retornar uma string contendo o conteudo de qualquer campo da instância do objeto em questão. Caso ele não esteja defindo, ocorre a seguinte diferença para um objeto c pertencente a esta classe:

c - retornaria: OBJECT
c.nome - retornaria o valor do atributo nome

Já com o __unicode__, ambos os casos retornariam diretamente o valor, o que poupa trabalho.





A classe Interprete:

class Interprete(models.Model):

nome = models.CharField(max_length=100)
slug = models.SlugField(max_length=100)

def __unicode__(self):

return self.nome



class Meta:

ordering = ['-id']
verbose_name = u'Intérprete'
verbose_name_plural = u'Intérpretes'



Aqui temos dois novos recursos da classe meta. É possivel setar qual será o nome no singular e no plural através do verbose_name, de forma a deixar correto (com acento) no admin.




A classe Album:

class Album(models.Model):


categoria = models.ForeignKey(Categoria)
interprete = models.ForeignKey(Interprete)
titulo = models.CharField(max_length=255,verbose_name=u'Título')
cover = models.ImageField(upload_to='albuns/%Y/', null=True, blank=True)
ano = models.IntegerField()
slug = models.SlugField(max_length=255)

def __unicode__(self):

return self.titulo


class Meta:

ordering = ['-id']
verbose_name = u'Album'
verbose_name_plural = u'Albuns'




Aqui temos algumas novidades em relação as anteriores:

- É possivel utilizar o verbose_name para atributos de forma a corrigir o nome mostrado no admin, conforme visto no título;

- Para nomes com acentos, se faz necessário colocar a letra "u" antes da string com o mesmo, para ficar correto com a codificação;

- um campo do tipo Imagem para fazer o upload da capa do álbum. O parâmetro upload_to é obrigatório e deve ser a string do caminho onde ficarão as imagens. Também é possivel organizar os uploads por datas. Neste exemplo, será gerada a pasta albuns dentro do media, e dentro dela, uma pasta com o ano que fez o upload defindo pelo caracter "%Y". Observação: o ano do álbum não está relacionado com o caminho do upload;

- Para o campo cover, temos dois parâmetros opcionais que permitem ao usuário criar um álbum sem a necessidade de preencher este campo. Colocando ambos campos em um atributo, o django exclui o mesmo da validação automática.

- Temos duas chaves estrangeiras para a classe album: categoria e interprete. Previmente definidos;


- E por fim, o campo do tipo inteiro ano, que também será renderizado como um INPUT TEXT no admin.




A classe Faixa:

class Faixa(models.Model):

album = models.ForeignKey(Album)
nro = models.IntegerField(verbose_name = u'Número')
nome = models.CharField(max_length=200)
tempo = models.CharField(max_length=10, help_text='Formato MM:SS',blank=True,null=True)

def __unicode__(self):

return self.nome




A novidade na classe faixa é uma dica dada ao usuário sobre o correto preenchimento para um campo na administração. Utilizando o atributo opcional help_text, a mensagem desejada aparece ao lado do campo escolhido.


CRIANDO O ADMIN PARA O MODELO

Crie dentro da pasta da sua aplicação um arquivo com o nome de admin.py. Dentro dele precisaremos de dois imports:

from django.contrib import admin
from app.cd.models import *

O primeiro para as funcionalidades do admin em si, e o segundo para as informações do modelo criado.

Logo abaixo, será criado as classes do administrativo:





O admin de Categoria:

class CategoriaAdmin(admin.ModelAdmin):

search_fields = ('nome',)
list_display = ('nome',)
prepopulated_fields = {'slug': ('nome',)}
save_on_top = True



search_fields - recebe uma tupla com os campos em que será realizada uma busca por quaisquer ocorrências do que for digitado.

list_display - recebe uma tupla com os campos que aparecerão na listagem

prepopulated_fields - aqui um recurso interssante do django para o campo slug. O preenchimento automático do campo slug com o texto do quer for inserido no campo nome, com excessão de algumas palavras reservadas, espaços e acentuação.

save_on_top - útil para formulários grandes. Esta opção replica a barra de controle para salvar/apagar registros acima do form.




O admin de Interprete:


class InterpreteAdmin(admin.ModelAdmin):

search_fields = ('nome',)
list_display = ('nome',)
prepopulated_fields = {'slug': ('nome',)}
save_on_top = True


Vamos utilizar um recurso muito interessante para as duas classes que faltam. Imagine se tivesse que inserir primeiro álbum com todas as informações, depois ir até o administrativo de faixas para inserir uma a uma tendo que escolher o álbum. Trabalhoso demais. O Django possui uma solução para isso.

É possível utilizar um recurso chamado INLINE, para adicionar uma ou várias faixas durante a inserção do álbum. Vejamos como:




O admin inline de Faixa:

class FaixasInline(admin.TabularInline):

model = Faixa
extra = 15


Esta classe herda de uma classe diferente das outras extamente por possuir esse recurso. Apenas precisamos definir a qual modelo ela se refere:

model = Faixa


E a quantidade de itens que serão exibidas além dos preenchidos:

extra = 15




O admin de Album:

class AlbumAdmin(admin.ModelAdmin):

inlines = [

FaixasInline,

]
raw_id_fields = ('categoria','interprete',)
search_fields = ('titulo',)
list_display = ('titulo','interprete','categoria','ano')
list_filter = ['categoria']
prepopulated_fields = {'slug': ('titulo',)}
save_on_top = True


Novidades desta classe:

- Nesta classe, é definido que será utilizado a classe FaixasInline criada anteriormente

- Como provavelmente teremos muitos artistas e categorias, procurá-los em um campo do tipo SELECT (tipo renderizado pelos campos ForeignKey) seria um tanto trabalhoso. Com a opção raw_id_fields, é aberto uma nova janela com a listagem definida pela referência a chave estrangeira, de forma a utilizar todas as buscas, filtros e ordenação providas pelo admin da mesma.

- list_filter - gera um filtro para facilitar a busca de um registro. O filtro só é exibido quando esta classe possuir registros pertencentes a mais de uma classe do filtro.




O admin de Faixa:

class FaixaAdmin(admin.ModelAdmin):

search_fields = ('nome',)
list_display = ('nome','album','nro','tempo')
save_on_top = True




Para concluir o admin.py, registre os admins criados ao final do arquivo:


admin.site.register(Categoria, CategoriaAdmin)
admin.site.register(Interprete, InterpreteAdmin)
admin.site.register(Album, AlbumAdmin)
admin.site.register(Faixa, FaixaAdmin)



TESTES


Adicone dentro do INSTALLED_APPS no settings.py a aplicação criada.


INSTALLED_APPS = (

'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.admin',
'app.cd',


)



Pare o servidor local de testes caso esteja rodando e execute um syncdb para gerar as tabelas.
Inicie novamente o servidor de testes com o runserver e abra o admin do seu projeto no link:

http://localhost:8000/admin/

Faça o login e veja o foi criado.