Skip to content

Latest commit

 

History

History
987 lines (581 loc) · 39.9 KB

25-a-aplicacao-de-contas-pessoais-sai-do-backstage.md

File metadata and controls

987 lines (581 loc) · 39.9 KB

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.

Dando uma cara à aplicação de contas pessoais

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!

Criando um formulário dinâmico para 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!

Trabalhando com agrupamentos

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í!

Trabalhando com paginação

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?

Melhorando um pouco mais e separando as contas para usuários

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.