Alatazan esticou suas pernas para relaxar um pouco na cama. Naquele dia não haveria aula e ele sabia o que isso significava.
- Hoje eu dou uma cara para a minha aplicação web 2.0!
Nas várias vezes que acordou durante à noite ele pensou automaticamente em como faria as Contas Pessoais de seu site uma forma de ganhar dinheiro e em todas as vezes adormeceu enquanto hesitava entre anotar aquelas coisas e pensar mais um pouco.
Levantou-se, estalou as costas num movimento de fúria que faria qualquer cachorrinho tremer. Mas era só pra relaxar, nada mais...
Uma curta corrida e um pulinho, e as pontas dos pés se tocaram, quando ele percebeu que esse movimento era um tanto constrangedor no planeta onde estava vivendo.
Voltou ao chão, se refez e pegou a conta telefônica. Quantas páginas só pra dizer que ele devia pagar aquele valor no dia 15!
E ele se lembrou de que as contas eram mais, e algumas delas estavam um tanto desfolheadas, enfiadas como se quisessem saltar para fora de uma caixa de sapatos.
Muito do que vamos ver hoje é apenas a confirmação do que já vimos em capítulos anteriores, mas nós vamos um pouco além.
A primeira coisa a fazer é executar o seu projeto. Clique duas vezes sobre o arquivo "executar.bat" da pasta do projeto e pronto. A primeira coisa está feita.
Dã... mas tá, isso não pode ser considerada uma coisa a fazer, pode?
Não importa, vamos começar abrindo o arquivo "urls.py" para edição, para incluir essa nova URL:
(r'^contas/', include('contas.urls')),
Agora o arquivo inteiro ficou assim:
from django.conf.urls.defaults import *
from django.conf import settings
# Uncomment the next two lines to enable the admin:
from django.contrib import admin
admin.autodiscover()
from blog.models import Artigo
from blog.feeds import UltimosArtigos
urlpatterns = patterns('',
(r'^$', 'django.views.generic.date_based.archive_index',
{'queryset': Artigo.objects.all(), 'date_field': 'publicacao'}),
(r'^admin/(.*)', admin.site.root),
(r'^rss/(?P<url>.*)/$', 'django.contrib.syndication.views.feed',
{'feed_dict': {'ultimos': UltimosArtigos}}),
(r'^artigo/(?P<slug>[\w_-]+)/$', 'blog.views.artigo'),
(r'^contato/$', 'views.contato'),
(r'^comments/', include('django.contrib.comments.urls')),
(r'^galeria/', include('galeria.urls')),
(r'^tags/', include('tags.urls')),
(r'^i18n/', include('django.conf.urls.i18n')),
(r'^contas/', include('contas.urls')),
)
if settings.LOCAL:
urlpatterns += patterns('',
(r'^media/(.*)$', 'django.views.static.serve',
{'document_root': settings.MEDIA_ROOT}),
)
Salve o arquivo. Feche o arquivo. Vá agora até a pasta da aplicação "contas" e crie o arquivo "urls.py", com o seguinte código dentro:
from django.conf.urls.defaults import *
urlpatterns = patterns('contas.views',
url('^$', 'contas', name='contas'),
)
Salve o arquivo. Feche o arquivo. Agora crie mais um novo arquivo nesta pasta, chamado "views.py", com o seguinte código dentro:
from django.shortcuts import render_to_response
from django.template import RequestContext
from models import ContaPagar, ContaReceber, CONTA_STATUS_APAGAR
def contas(request):
contas_a_pagar = ContaPagar.objects.filter(
status=CONTA_STATUS_APAGAR,
)
contas_a_receber = ContaReceber.objects.filter(
status=CONTA_STATUS_APAGAR,
)
return render_to_response(
'contas/contas.html',
locals(),
context_instance=RequestContext(request),
)
Bom, o que temos que novo aqui?
Nesta linha de código, nós importamos as duas classes de contas: "ContaPagar" e "ContaReceber". E também importamos a variável que representa o status "A Pagar" das contas.
from models import ContaPagar, ContaReceber, CONTA_STATUS_APAGAR
Já este outro trecho de código carrega os objetos de contas a pagar e a receber, filtrados pelo campo "status", que deve ter o valor contido na variável "CONTA_STATUS_APAGAR". Isso significa que somente as contas que seu campo "status" contiver o valor informado serão retornadas para as variáveis "contas_a_pagar" e "contas_a_receber":
contas_a_pagar = ContaPagar.objects.filter(
status=CONTA_STATUS_APAGAR,
)
contas_a_receber = ContaReceber.objects.filter(
status=CONTA_STATUS_APAGAR,
)
No mais, são as mesmas já conhecidas linhas de código.
Salve o arquivo. Feche o arquivo.
Agora precisamos criar o template "contas/contas.html" para esta view, certo? Pois ainda na pasta da aplicação "contas", abra a pasta "templates" e crie uma nova pasta, chamada "contas", e dentro dela um arquivo chamado "contas.html", com o seguinte código dentro:
{% extends "base.html" %}
{% load i18n %}
{% block titulo %}{% trans "Contas Pessoais" %} - {{ block.super }}{% endblock %}
{% block h1 %}{% trans "Contas Pessoais" %}{% endblock %}
{% block conteudo %}
<h2>{% trans "Contas a Pagar" %}</h2>
<ul>
{% for conta in contas_a_pagar %}
<li><a href="{{ conta.get_absolute_url }}">{{ conta }}</a></li>
{% endfor %}
</ul>
<h2>{% trans "Contas a Receber" %}</h2>
<ul>
{% for conta in contas_a_receber %}
<li><a href="{{ conta.get_absolute_url }}">{{ conta }}</a></li>
{% endfor %}
</ul>
{% endblock conteudo %}
Observe bem para notar que não há novidades ali. Temos duas listagens: uma de Contas a Pagar e outra de Contas a Receber, usando as variáveis que declaramos na view.
Salve o arquivo. Feche o arquivo.
Agora, vá ao navegador e carregue a seguinte URL:
http://localhost:8000/contas/
Veja como ela é carregada:
Como você pode ver, fizemos progresso, mas temos que melhorar muito. Agora vá até a pasta da aplicação "contas" e abra o arquivo "models.py" para edição. Localize a seguinte linha:
from django.utils.translation import ugettext_lazy as _
Abaixo dela, acrescente esta:
from django.core.urlresolvers import reverse
Agora localize este linha:
descricao = models.TextField(blank=True)
Acrescente esta abaixo dela:
def __unicode__(self):
data_vencto = self.data_vencimento.strftime('%d/%m/%Y')
valor = '%0.02f'%self.valor
return '%s - %s (%s)'%(valor, self.pessoa.nome, data_vencto)
Nós acrescentamso o método "__unicode__()" aqui porque ele será idêntico tanto para Contas a Pagar quanto para Contas a Receber, e a classe "Conta" concentra tudo o que há de idêntico entre as classes "ContaPagar" e "ContaReceber".
Na linha seguinte, nós formatamos a data de vencimento em um formato amigável ( "dia/mês/ano" ), usando o método "strftime()":
data_vencto = self.data_vencimento.strftime('%d/%m/%Y')
E a seguir, formatamos novamente, mas dessa vez, ao invés de ser uma data, formatamos um valor monetário, garantindo duas casas decimais, veja:
valor = '%0.02f'%self.valor
E por fim, juntamos tudo em outra formatação, para que a representação textual desse objeto fique no formato "valor - pessoa (data de vencimento)":
return '%s - %s (%s)'%(valor, self.pessoa.nome, data_vencto)
Quanta formatação hein?
Agora localize a classe "ContaPagar":
class ContaPagar(Conta):
def save(self, *args, **kwargs):
self.operacao = CONTA_OPERACAO_DEBITO
super(ContaPagar, self).save(*args, **kwargs)
E acrescente esse bloco de código ao final dela:
def get_absolute_url(self):
return reverse('conta_a_pagar', kwargs={'conta_id': self.id})
O que fizemos aí você já conhece: usamos a função "reverse()" para indicar a URL de exibição desta Conta a Pagar, que tem o nome de "conta_a_pagar". E agora vamos fazer o mesmo com a Conta a Receber:
Localize a classe "ContaReceber":
class ContaReceber(Conta):
def save(self, *args, **kwargs):
self.operacao = CONTA_OPERACAO_CREDITO
super(ContaReceber, self).save(*args, **kwargs)
E acrescente esse bloco de código ao final dela:
def get_absolute_url(self):
return reverse('conta_a_receber', kwargs={'conta_id': self.id})
O código é muito semelhante ao da Conta a Pagar, mas desta vez indicamos a URL com o nome "conta_a_receber".
Salve o arquivo. Feche o arquivo.
E como indicamos as URLs "conta_a_pagar" e "conta_a_receber", agora precisamos que elas existam, certo? Pois então abra argora o arquivo "urls.py" da aplicação "contas" para edição e acrescente as seguintes URLs:
url('^pagar/(?P<conta_id>\d+)/$', 'conta', {'classe': ContaPagar},
name='conta_a_pagar'),
url('^receber/(?P<conta_id>\d+)/$', 'conta', {'classe': ContaReceber},
name='conta_a_receber'),
Você pode notar que ambas as URLs fazem uso do mesmo argumento para levar o código da conta: "conta_id". As duas também apontam para a mesma view: "conta", mas daí em diante as coisas mudam, pois cada uma indica o parâmetro "classe" para sua respectiva classe de modelo e cada uma também possui um nome diferente.
Indicar parâmetros a uma URL é uma medida prática para evitar repetição de código, pois ambas as classes possuem um comportamento muito semelhante uma da outra e não há necessidade de duplicar coisas que podem ser generalizadas entre elas.
Mas falta uma coisa: importar as classes "ContaPagar" e "ContaReceber". Para isso localize a primeira linha do arquivo:
from django.conf.urls.defaults import *
E acrescente esta abaixo dela:
from models import ContaPagar, ContaReceber
Salve o arquivo. Feche o arquivo. Agora vamos criar a view "conta".
Para isso, ainda na pasta da aplicação "contas", abra o arquivo "views.py" e acrescente o seguinte trecho de código ao seu final:
def conta(request, conta_id, classe):
conta = get_object_or_404(classe, id=conta_id)
return render_to_response(
'contas/conta.html',
locals(),
context_instance=RequestContext(request),
)
Agora para entender melhor como essa view funciona, observe as duas primeiras linhas:
def conta(request, conta_id, classe):
conta = get_object_or_404(classe, id=conta_id)
Veja que aqui temos o argumento "classe", que recebe ora a classe "ContaPagar", ora "ContaReceber", e quem atribui a classe a esse argumento está no código que escrevemos lá atrás, veja novamente:
url('^pagar/(?P<conta_id>\d+)/$', 'conta', {'classe': ContaPagar},
name='conta_a_pagar'),
url('^receber/(?P<conta_id>\d+)/$', 'conta', {'classe': ContaReceber},
name='conta_a_receber'),
Observe que além do argumento "conta_id", temos também o dicionário com um parâmetro dentro: "classe", atribuindo a classe de modelo que esta URL indica.
Está claro? Observe com atenção, pois esta é uma prática que muitas vezes ajuda no seu dia-a-dia...
Agora localize a primeira linha do arquivo:
from django.shortcuts import render_to_response
E modifique-a, acrescentando a função "get_object_or_404" ao seu final, assim:
from django.shortcuts import render_to_response, get_object_or_404
Salve o arquivo. Feche o arquivo. Já quer testar? Espere... ainda temos uma coisa a fazer.
Na pasta da aplicação "contas", abra a pasta "templates/contas" e crie um novo arquivo, chamado "conta.html" com o seguinte código dentro:
{% extends "base.html" %}
{% load i18n %}
{% block titulo %}{{ conta }} - {{ block.super }}{% endblock %}
{% block h1 %}{{ conta }}{% endblock %}
{% block conteudo %}
<ul>
<li>Valor: {{ conta.valor }}</li>
<li>Vencimento: {{ conta.data_vencimento }}</li>
<li>Pessoa: {{ conta.pessoa }}</li>
<li>Histórico: {{ conta.historico }}</li>
<li>Status: {{ conta.status }}</li>
</ul>
{% endblock conteudo %}
Salve o arquivo (lembre-se de salvar no formato "UTF-8" caso esteja usando Bloco de Notas no Windows). Feche o arquivo.
Volte ao navegador e atualize a página com a tecla F5. Veja como ficou agora:
Agora clique sobre a Conta a Receber e veja como ela é carregada:
Estamos caminhando bem, não estamos? Vamos agora dar uma melhorada nessa página?
Faça assim: abra para edição novamente o arquivo "conta.html" da pasta "contas/templates/contas", partindo da pasta do projeto e localize este bloco de código:
<li>Valor: {{ conta.valor }}</li>
<li>Vencimento: {{ conta.data_vencimento }}</li>
<li>Pessoa: {{ conta.pessoa }}</li>
<li>Histórico: {{ conta.historico }}</li>
<li>Status: {{ conta.status }}</li>
Modifique para ficar assim:
<li>{% trans "Valor" %}: {{ conta.valor|floatformat:2 }}</li>
<li>{% trans "Vencimento" %}: {{ conta.data_vencimento|date:"d/m/Y" }}</li>
<li>{% trans "Pessoa" %}: <a href="{{ conta.pessoa.get_absolute_url }}">{{ conta.pessoa }}</a></li>
<li>{% trans "Histórico" %}: <a href="{{ conta.historico.get_absolute_url }}">{{ conta.historico }}</a></li>
<li>{% trans "Status" %}: {{ conta.get_status_display }}</li>
Vamos passar parte por parte:
Na primeira linha, nós formatamos o valor da conta para sempre ser exibido com duas casas decimais. Elas serão arredondadas caso sejam mais que duas:
<li>{% trans "Valor" %}: {{ conta.valor|floatformat:2 }}</li>
Em seguida, nós formatamos a data de vencimento no formato "dia/mês/ano":
<li>{% trans "Vencimento" %}: {{ conta.data_vencimento|date:"d/m/Y" }}</li>
Nas duas linhas seguintes, nós definimos um link para os campos "Pessoa" e "Histórico":
<li>{% trans "Pessoa" %}: <a href="{{ conta.pessoa.get_absolute_url }}">{{ conta.pessoa }}</a></li>
<li>{% trans "Histórico" %}: <a href="{{ conta.historico.get_absolute_url }}">{{ conta.historico }}</a></li>
E em seguida... você notou que no campo "Status" foi mostrada uma letra "a"? Pois é, isso não é nada agradável... vamos mostrar seu rótulo correto! Para isso, usamos o método "get_status_display", que trata-se de um método calculado, construído em memória para o campo "status".
<li>{% trans "Status" %}: {{ conta.get_status_display }}</li>
Para cada um dos campos que possuem o argumento "choices" é oferecido um método com o nome assim: "get_NOMEDOCAMPO_display()", que pode ser usado tanto em templates (removendo os parênteses) como no código em Python normalmente.
Agora vamos fazer mais. Abaixo do bloco que modificamos, acrescente mais este:
{% ifequal conta.status "p" %}
<li>{% trans "Pagamento" %}: {{ conta.data_pagamento|date:"d/m/Y" }}</li>
{% endifequal %}
Este código faz uma condição para ser exibido: é necessário que o status da conta seja "Pago", representado pela condição da template tag "{% ifequal conta.status "p" %}".
Agora abaixo desta linha:
</ul>
Acrescente mais estas linhas código:
{{ conta.descricao|linebreaks }}
{% if conta.pagamentos.count %}
<h2>{% trans "Pagamentos" %}</h2>
<table>
<tr>
<th>{% trans "Data" %}</th>
<th>{% trans "Valor" %}</th>
</tr>
{% for pagamento in conta.pagamentos %}
<tr>
<td>{{ pagamento.data_pagamento|date:"d/m/Y" }}</td>
<td>{{ pagamento.valor|floatformat:2 }}</td>
</tr>
{% endfor %}
</table>
{% endif %}
Puxa, mas quanta coisa!
Veja, nesta linha nós exibimos a Descrição da conta:
{{ conta.descricao|linebreaks }}
E aqui, iniciamos um bloco que será exibido somente se houverem sido feitos pagamentos na conta. A condição é: se a quantidade de pagamentos for diferente de zero (valor verdadeiro), o bloco é exibido.
{% if conta.pagamentos.count %}
E todo o restante do código que escrevemos é uma tabela com sua linha de cabeçalho e um laço através da template tag {% for pagamento in conta.pagamentos %} que vai exibir uma linha na tabela para cada um dos pagamentos.
Salve o arquivo. Feche o arquivo. Volte ao navegador e atualize a página. Veja o resultado:
Mas que bacana, tudo formatadinho... mas acontece que o atributo "conta.pagamentos" não existe, então, devemos criá-lo agora!
Abra o arquivo "models.py" da pasta da aplicação "contas" para edição e localize a seguinte linha. Ela faz parte da classe "ContaPagar":
return reverse('conta_a_pagar', kwargs={'conta_id': self.id})
Abaixo dela, acrescente o seguinte código:
def pagamentos(self):
return self.pagamentopago_set.all()
Isso vai fazê-la retornar a lista de pagamentos da Conta a Pagar.
O mesmo deve ser feito para a classe "ContaReceber". Localize esta outra linha:
return reverse('conta_a_receber', kwargs={'conta_id': self.id})
E acrescente estas abaixo dela:
def pagamentos(self):
return self.pagamentorecebido_set.all()
Salve o arquivo. Feche o arquivo. Agora volte ao navegador e atualize com F5:
Muito legal! Estamos caminhando a passos largos!
Agora precisamos de um formulário para fazer esses pagamentos!
Então vamos lá! Volte a editar o arquivo "conta.html" da pasta "contas/templates/contas" partindo da pasta do projeto, e localize esta linha aqui:
{% endblock conteudo %}
Acima dela, acrescente estas linhas de código:
{% ifequal conta.status "a" %}
<hr/>
<h2>{% trans "Novo pagamento" %}</h2>
<form action="{{ conta.get_absolute_url }}pagar/" method="post">
<table>
{{ form_pagamento }}
</table>
<input type="submit" value="{% trans "Salvar pagamento" %}"/>
</form>
{% endifequal %}
Na primeira linha nós estabelecemos uma condição: só exibimos o formulário de pagamento se a conta ainda tiver seu Status como "A Pagar" (valor "a"):
{% ifequal conta.status "a" %}
Traçamos uma linha para separar visualmente o formulário do restante da página:
<hr/>
Declaramos uma tag HTML <form> que direciona o formulário para a URL da conta somada de "pagar/". Observe também que ele utiliza o método "post":
<form action="{{ conta.get_absolute_url }}pagar/" method="post">
E a outra coisa a observar no que fizemos é o uso do formulário dinâmico da variável "form_pagamento":
{{ form_pagamento }}
Salve o arquivo. Feche o arquivo. O próximo passo agora é fazer a variável "form_pagamento" existir!
Na pasta da aplicação "contas", abra o arquivo "views.py" para edição e localize a seguinte linha de código:
conta = get_object_or_404(classe, id=conta_id)
Acrescente esta linha abaixo dela:
form_pagamento = FormPagamento()
Agora localize esta outra linha:
from models import ContaPagar, ContaReceber, CONTA_STATUS_APAGAR
Acrescente abaixo dela:
from forms import FormPagamento
Agora salve o arquivo. Feche o arquivo. Vamos criar a classe "FormPagamento"?
Na pasta da aplicação "contas", crie um novo arquivo chamado "forms.py" com o seguinte código dentro:
from datetime import date
from django import forms
class FormPagamento(forms.Form):
valor = forms.DecimalField(max_digits=15, decimal_places=2)
Humm... eu acho que não há novidades aí...
Fizemos a importação dos elementos que vamos precisar no arquivo: o objeto "date" para nos retornar a data atual e o pacote "forms" para criarmos o formulário dinâmico:
from datetime import date
from django import forms
Criamos a classe do formulário dinâmico e declaramos seu único campo: "valor", seguindo o mesmo tipo de campo que seguimos na classe de modelo "Pagamento":
class FormPagamento(forms.Form):
valor = forms.DecimalField(max_digits=15, decimal_places=2)
Salve o arquivo. Feche o arquivo. Agora volte ao navegador e atualize a página da conta com F5. Veja:
Show! Nosso formulário já está aí... mas ainda sem vida hein? Pois então vamos dar vida a ele agora!
Abra o arquivo "urls.py" da pasta da aplicação "contas" e acrescente estas duas novas URLs:
url('^pagar/(?P<conta_id>\d+)/pagar/$', 'conta_pagamento',
{'classe': ContaPagar},
name='conta_a_pagar_pagamento'),
url('^receber/(?P<conta_id>\d+)/pagar/$', 'conta_pagamento',
{'classe': ContaReceber},
name='conta_a_receber_pagamento'),
Repare que seguimos o mesmo raciocínio das URLs da Conta a Pagar e da Conta a Receber, mas desta vez acrescentamos a palavra "pagar/" à expressão regular e a palavra "_pagamento" ao nome da view e ao nome da URL. Não há segredo na sopa de letrinhas!
Salve o arquivo. Feche o arquivo.
Agora vamos criar essa nova view! Para isso abra o arquivo "views.py" da mesma pasta e acrescente estas linhas de código ao final:
def conta_pagamento(request, conta_id, classe):
conta = get_object_or_404(classe, id=conta_id)
if request.method == 'POST':
form_pagamento = FormPagamento(request.POST)
if form_pagamento.is_valid():
form_pagamento.salvar_pagamento(conta)
return HttpResponseRedirect(request.META['HTTP_REFERER'])
Bom, veja só o que nós fizemos:
Aqui na declaração da função usamos o mesmo raciocínio da view "conta()". Carregamos o objeto "conta" com base na classe informada na definição da URL:
def conta_pagamento(request, conta_id, classe):
conta = get_object_or_404(classe, id=conta_id)
O próximo passo usou o mesmo tipo de código que usamos no formulário dinâmico de Contato, lembra-se do capítulo 10? Só aceitamos envio de dados usando método HTTP "post", que aliás, usamos quando declaramos a tag HTML <form> neste mesmo capítulo.
if request.method == 'POST':
form_pagamento = FormPagamento(request.POST)
Em seguida, fazemos a validação do formulário dinâmico, usando o método "is_valid()", isso não só valida os dados informados pelo usuário como também habilita o atributo "cleaned_data" do formulário. Depois disso o método "salvar_pagamento()" é chamado para o objeto "conta":
if form_pagamento.is_valid():
form_pagamento.salvar_pagamento(conta)
E por fim, a view retorna um redirecionamento, usando "HttpResponseRedirect" e indicando a URL anterior como destino:
return HttpResponseRedirect(request.META['HTTP_REFERER'])
O atributo "META" da request vem recheado de informações do protocolo HTTP. Isso inclui dados sobre o navegador que originou a requisição, sistema operacional, IP e outras informações. Entre elas está o item "HTTP_REFERER", que carrega a URL anterior, a que o usuário estava quando clicou no botão "Salvar pagamento".
E agora, para dizer ao Python quem é o tal "HttpResponseRedirect", localize esta linha:
from django.template import RequestContext
E acrescente esta abaixo dela:
from django.http import HttpResponseRedirect
Salve o arquivo. Feche o arquivo. O próximo passo agora é fazer existir o método "salvar_pagamento()" do formulário "FormPagamento".
Para isso, abra o arquivo "forms.py" da pasta da aplicação "contas" para edição e localize a classe "FormPagamento":
class FormPagamento(forms.Form):
valor = forms.DecimalField(max_digits=15, decimal_places=2)
Ao final dela, acrescente o método do qual precisamos:
def salvar_pagamento(self, conta):
return conta.lancar_pagamento(
data_pagamento=date.today(),
valor=self.cleaned_data['valor'],
)
Veja que o método "salvar_pagamento()" recebe o argumento "conta" e chama outro método, agora do objeto "conta" para gravar o pagamento. O novo método se chama "lancar_pagamento" e recebe os argumentos "data_pagamento" e "valor" com a data atual e o valor informado pelo usuário.
Porquê fazemos isso? Porque assim podemos mantêr esse formulário útil tanto para Contas a Pagar quanto para Contas a Receber, mantemos também as devidas responsabilidades separadas e nos concentramos apenas em repassar as informações das quais o formulário dinâmico é responsável, respeitando as camadas.
Salve o arquivo. Feche o arquivo.
Agora na mesma pasta abra o arquivo "models.py" para edição e localize este trecho de código:
def pagamentos(self):
return self.pagamentorecebido_set.all()
Abaixo disso, acrescente o método que acabamos de citar:
def lancar_pagamento(self, data_pagamento, valor):
return PagamentoRecebido.objects.create(
conta=self,
data_pagamento=data_pagamento,
valor=valor,
)
Veja que o método "lancar_pagamento()" recebe os argumentos "data_pagamento" e "valor" e cria o novo objeto de "PagamentoRecebido" com o valor dos argumentos e a referência à conta - que se trata do próprio objeto do método: self.
Faça o mesmo com a classe "ContaPagar". Para isso localize o trecho de código:
def pagamentos(self):
return self.pagamentopago_set.all()
Acrescente este trecho abaixo dele:
def lancar_pagamento(self, data_pagamento, valor):
return PagamentoPago.objects.create(
conta=self,
data_pagamento=data_pagamento,
valor=valor,
)
A única diferença aqui é que criamos um novo objeto da classe "PagamentoPago" ao invés da classe "PagamentoRecebido".
Salve o arquivo. Feche o arquivo.
Agora volte ao navegador, informe um valor no campo "valor", clique sobre o botão "Salvar pagamento" e veja o que acontece:
Está lá o arquivo que você acrescentou!
Vamos agora criar uma página para exibir uma listagem com todas as Contas, organizadas em páginas.
Na pasta "contas/templates/contas" partindo da pasta do projeto, abra o arquivo "contas.html" para edição e localize esta linha:
<h2>{% trans "Contas a Receber" %}</h2>
Acima dela, acrescente esta linha:
<a href="{% url contas_a_pagar %}">{% trans "Ver todas" %}</a>
O link acima vai permitir que o usuário clique sobre ele para ver todas as contas a pagar". Para fazer o mesmo com as contas a receber, localize esta outra linha de código:
{% endblock conteudo %}
E acima dela acrescente esta:
<a href="{% url contas_a_receber %}">{% trans "Ver todas" %}</a>
Salve o arquivo. Feche o arquivo. Precisamos agora criar as URLs com os nomes "contas_a_pagar" e "contas_a_receber" na aplicação "contas". Para isso, vá até a pasta da aplicação "contas" e abra o arquivo "urls.py" para edição. Acrescente as novas URLs:
url('^pagar/$', 'contas_por_classe',
{'classe': ContaPagar, 'titulo': 'Contas a Pagar'},
name='contas_a_pagar'),
url('^receber/$', 'contas_por_classe',
{'classe': ContaReceber, 'titulo': 'Contas a Receber'},
name='contas_a_receber'),
Veja que indicamos ambas para a view "contas_por_classe", mas cada uma possui sua classe de modelo e seu nome respectivo.
A novidade está no parâmetro "titulo", que leva o título da página. Logo vamos saber porquê.
Salve o arquivo. Feche o arquivo.
Agora abra o arquivo "views.py" da mesma pasta para edição e acrescente ao final o bloco de código abaixo:
def contas_por_classe(request, classe, titulo):
contas = classe.objects.order_by('status','data_vencimento')
titulo = _(titulo)
return render_to_response(
'contas/contas_por_classe.html',
locals(),
context_instance=RequestContext(request),
)
Como você pode ver, a view carrega todas as contas para a variável "contas" usando a QuerySet de "classe.objects.order_by('status','data_vencimento')", e por fim utiliza o template "contas/contas_por_classe.html" para retornar.
contas = classe.objects.order_by('status','data_vencimento')
O método "order_by('status','data_vencimento')" define que a QuerySet deve retornar os objetos ordenados pelos campos "status" e "data_vencimento", ambos em ordem ascendente.
E o argumento "titulo" é atribuído a uma variável com o mesmo nome, mas fazendo uso da função de internacionalização. Isso é porque o título da página deve variar dependendo da classe de modelo, veja:
titulo = _(titulo)
Ainda precisamos importar a função de internacionalização, não é? Então encontre esta linha:
from django.http import HttpResponseRedirect
E acrescente esta abaixo dela:
from django.utils.translation import ugettext as _
Salve o arquivo. Feche o arquivo.
Vamos agora criar o novo template! Volte à pasta "contas/templates/contas" e crie um arquivo chamado "contas_por_classe.html" com o seguinte código dentro:
{% extends "base.html" %}
{% load i18n %}
{% block titulo %}{{ titulo }} - {{ block.super }}{% endblock %}
{% block h1 %}{{ titulo }}{% endblock %}
{% block conteudo %}{{ block.super }}
{% for conta in contas %}
<div>
<a href="{{ conta.get_absolute_url }}">{{ conta }}</a>
</div>
{% endfor %}
{% endblock conteudo %}
Você pode ver que usamos a variável "titulo" nos blocks "titulo" e "h1". No mais, fazemos um laço com a template tag {% for conta in contas %}.
Salve o arquivo. Feche o arquivo.
Volte ao navegador e carregue a seguinte URL:
http://localhost:8000/contas/
Veja como a página ficou agora:
Bacana! Agora clique sobre um dos links "Ver todas", e veja:
Ótimo! Mas que tal agrupar as contas pelo campo de Status?
Então volte a editar o arquivo "contas_por_classe.html" da pasta "contas/templates/contas" localize este trecho de código:
{% for conta in contas %}
<div>
<a href="{{ conta.get_absolute_url }}">{{ conta }}</a>
</div>
{% endfor %}
Acrescente este acima dele:
{% regroup contas by get_status_display as contas %}
Salve e volte ao navegador, atualize com F5 e veja:
Humm... esquisito né? Porquê isso?
Veja, a template tag {% regroup %} agrupa uma lista de objetos por um de seus campos, atributos e métodos. Os objetos de contas possuem um método chamado "get_status_display" que retorna o rótulo do campo "status", certo? Pois então a linha que criamos reagrupa a variável "contas" pelos valores do método "get_status_display" para um nome de variável: "contas".
Veja que a variável que usamos como base tem o mesmo nome que a variável que damos saída: "contas". Isso significa que a antiga será substituída pela nova, com o agrupamento.
A partir daí a variável "contas" passa a conter uma lista de agrupamentos. Cada grupo é um dicionário com dois itens:
- "grouper" - que carrega o valor agrupador - que neste caso é o rótulo do status da conta;
- "list" - que carrega a lista de objetos que se enquadraram dentro daquele grupo.
Agora volte ao arquivo que estamos editando e modifique este bloco de código:
{% for conta in contas %}
<div>
<a href="{{ conta.get_absolute_url }}">{{ conta }}</a>
</div>
{% endfor %}
Para ficar assim:
{% for grupo in contas %}
<h2>{{ grupo.grouper }}</h2>
{% for conta in grupo.list %}
<div>
<a href="{{ conta.get_absolute_url }}">{{ conta }}</a>
</div>
{% endfor %}
{% endfor %}
Veja que ali fazemos dois laços:
O primeiro passa a ser o laço para os grupos:
{% for grupo in contas %}
<h2>{{ grupo.grouper }}</h2>
A variável "grupo.grouper" é exibida como título do grupo.
Em seguida, esta linha muda para ser um laço em "grupo.list", que contém a lista de contas a partir de agora:
{% for conta in grupo.list %}
No mais, as coisas permanecem como estão, apenas acrescentando o fechamento dos laços.
Salve o arquivo. Feche o arquivo. Atualize o navegador com F5 e veja o resultado:
Viu? Agora que tal criar mais uma porção de contas no Admin, mudar seus status e voltar aqui para ver como fica? Veja um exemplo:
E a listagem equivalente na página que acabamos de construir:
É isso aí!
Agora que tal organizar essas contas por páginas de 5 em 5? Então vamos lá!
O Django possui uma classe chamada Paginator que organiza listas de objetos de forma que possam ser divididas por páginas.
Na pasta da aplicação "contas", abra o arquivo "views.py" para edição e localize a linha de código abaixo:
contas = classe.objects.order_by('status','data_vencimento')
Acrescente estas linhas de código abaixo dela:
paginacao = Paginator(contas, 5)
pagina = paginacao.page(request.GET.get('pagina', 1))
O que fizemos aí foi instanciar o controlador "paginacao" da lista "contas" com páginas de 5 objetos e na segunda linha carregamos a página atual com base no número da página contido no parâmetro "pagina".
Agora precisamos de importar a classe "Paginator". Para isso, localize esta linha de código:
from django.utils.translation import ugettext as _
Acrescente a linha abaixo para importar a classe "Paginator":
from django.core.paginator import Paginator
Salve o arquivo. Feche o arquivo.
Agora vamos ao template para habilitar a nossa paginação. Abra para edição o arquivo "contas_por_classe.html" da pasta "contas/templates/contas", partindo da pasta do projeto. Localize esta linha:
{% regroup contas by get_status_display as contas %}
Modifique a linha para ficar assim:
{% regroup pagina.object_list by get_status_display as contas %}
Observe que trocamos o elemento "contas" pelo "pagina.object_list" e o resto permaneceu inalterado. A variável "pagina.object_list" traz consigo a lista de contas para a página atual.
Agora localize esta outra linha:
{% endblock conteudo %}
Acima dela, acrescente este trecho de código:
<hr/>
<div class="paginacao">
Paginas:
{% for pagina_numero in paginacao.page_range %}
<a href="{{ request.path }}?pagina={{ pagina_numero }}">{{ pagina_numero }}</a>
{% endfor %}
</div>
O trecho de código acima faz um laço em "paginacao.page_range" - a variável que carrega consigo a lista de páginas disponíveis para paginação. Dentro do laço o trabalho é exibir o número da página com seu respectivo link.
O link é composto pela URL atual em "request.path", mais o parametro "?pagina=", somado ao número da página, em "pagina_numero".
Mas aí há uma questão: de onde saiu a variável "request" que citamos em {{ request.path }}? É preciso fazer uma pequena modificação para que ela ganhe vida no template de forma segura!
Salve o arquivo. Feche o arquivo. Vá até a pasta do projeto e abra o arquivo "settings.py" para edição. Localize a seguinte linha:
TEMPLATE_DIRS = (
Acima dela, acrescente o seguinte trecho de código:
TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.auth',
'django.core.context_processors.debug',
'django.core.context_processors.i18n',
'django.core.context_processors.media',
'django.core.context_processors.request',
)
O valor padrão da setting "TEMPLATE_CONTEXT_PROCESSORS" é o mesmo que definimos, exceto pelo último item. Então o que fizemos na verdade foi acrescentar este item:
'django.core.context_processors.request',
É ele quem vai fazer a variável "request" ser visível em qualquer view.
Template Context Processors são funções que retornam dicionários de variáveis para o contexto padrão dos templates, o que significa que são variáveis que estarão presentes em todos os templates mesmo que não sejam declarados nas views.
Salve o arquivo. Feche o arquivo. Agora vá ao navegador, atualize com F5 e veja o resultado:
E aí, fantástico, não?
Depois desse ritmo alucinante, Alatazan estava cansado...
... em parte porque todas as vezes que ele imaginava um "ahh, agora te peguei!" para o Django, o Django respondia com um: "hummm, vê se pegou direito o que mostrei pra você hoje!".
Mas em outra parte também porque sua aplicação caminhava firme para ser organizada por usuários e publicada em breve e ele não aguentava mais de ansiedade.
Mas o que Alatazan precisava rapidamente fazer era organizar as idéias sobre o aprendizado de hoje, e elas foram:
- Algumas funcionalidades da aplicação podem ser compartilhadas entre outras em comum, de forma a evitar a repetição de código;
- URLs podem passar parâmetros para views para que elas sejam extensíveis;
- Todos os campos que possuem a propriedade "choices" podem ser usados pelo método "get_NOMEDOCAMPO_display()" para retornar seus rótulos;
- Formulários dinâmicos podem ser usados para fazer lançamentos em classes de modelo de infinitas formas diferentes;
- A template tag "{% regroup %}" organiza uma lista em grupos;
- Com duas linhas cria-se a paginação para uma lista, usando a classe "Paginator";
- Com mais algumas linhas se resolve a paginação no template;
- Template Context Processors adicionam variáveis ao contexto das templates e isso pode ser muito útil;
Alatazan parou por aqui, querendo cama e um bom chá com pão e manteiga.
O próximo passo agora é a edição, criação e exclusão de Contas, Pessoas e Históricos. Por fim, também organizar isso tudo para separar para usuários diferentes.