Este documento descreve a estrutura dos models Django e os relacionamentos entre as entidades do Finanpy.
User (Django Auth)
↓ OneToOne
Profile
User
↓ OneToMany
Account
↓ OneToMany
Transaction
↓ ManyToOne
Category
↓ ManyToOne
User
Utiliza o model padrão do Django (django.contrib.auth.models.User).
Campos principais:
email: Email do usuário (usado como username)password: Senha hasheadais_active: Status da contadate_joined: Data de cadastrolast_login: Último acesso
Relacionamentos:
- OneToOne com
Profile - OneToMany com
Account - OneToMany com
Category
Informações complementares do usuário.
App: profiles
Campos:
| Campo | Tipo | Descrição | Obrigatório |
|---|---|---|---|
| user | OneToOneField | Relação com User | Sim |
| full_name | CharField | Nome completo do usuário | Sim |
| phone | CharField | Telefone de contato | Não |
| created_at | DateTimeField | Data de criação | Auto |
| updated_at | DateTimeField | Data de última atualização | Auto |
Constraints:
user: unique, on_delete=CASCADE
Comportamento:
- Criado automaticamente ao criar usuário (signal)
- Deletado automaticamente ao deletar usuário (CASCADE)
Exemplo:
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
full_name = models.CharField(max_length=200)
phone = models.CharField(max_length=20, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.full_nameContas bancárias do usuário.
App: accounts
Campos:
| Campo | Tipo | Descrição | Obrigatório |
|---|---|---|---|
| user | ForeignKey | Relação com User | Sim |
| name | CharField | Nome/apelido da conta | Sim |
| bank_name | CharField | Nome do banco | Sim |
| account_type | CharField | Tipo de conta (corrente, poupança) | Sim |
| balance | DecimalField | Saldo atual | Sim |
| is_active | BooleanField | Status da conta | Sim |
| created_at | DateTimeField | Data de criação | Auto |
| updated_at | DateTimeField | Data de última atualização | Auto |
Constraints:
user: on_delete=CASCADEbalance: max_digits=10, decimal_places=2, default=0is_active: default=True
Choices para account_type:
ACCOUNT_TYPES = [
('checking', 'Conta Corrente'),
('savings', 'Conta Poupança'),
('investment', 'Conta Investimento'),
]Comportamento:
- Sempre filtrado por usuário logado
- Saldo calculado pela soma das transações
- Deletado automaticamente ao deletar usuário (CASCADE)
Exemplo:
class Account(models.Model):
ACCOUNT_TYPES = [
('checking', 'Conta Corrente'),
('savings', 'Conta Poupança'),
('investment', 'Conta Investimento'),
]
user = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
bank_name = models.CharField(max_length=100)
account_type = models.CharField(max_length=50, choices=ACCOUNT_TYPES)
balance = models.DecimalField(max_digits=10, decimal_places=2, default=0)
is_active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return f'{self.name} - {self.bank_name}'Categorias para organização de transações.
App: categories
Campos:
| Campo | Tipo | Descrição | Obrigatório |
|---|---|---|---|
| user | ForeignKey | Relação com User | Sim |
| name | CharField | Nome da categoria | Sim |
| category_type | CharField | Tipo (entrada ou saída) | Sim |
| color | CharField | Cor hexadecimal para UI | Não |
| created_at | DateTimeField | Data de criação | Auto |
| updated_at | DateTimeField | Data de última atualização | Auto |
Constraints:
user: on_delete=CASCADEcategory_type: choices entre 'income' e 'expense'
Choices para category_type:
CATEGORY_TYPES = [
('income', 'Entrada'),
('expense', 'Saída'),
]Comportamento:
- Sempre filtrada por usuário logado
- Cor padrão se não informada
- Deletado automaticamente ao deletar usuário (CASCADE)
Exemplo:
class Category(models.Model):
CATEGORY_TYPES = [
('income', 'Entrada'),
('expense', 'Saída'),
]
user = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
category_type = models.CharField(max_length=10, choices=CATEGORY_TYPES)
color = models.CharField(max_length=7, blank=True, default='#667eea')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name_plural = 'Categories'
def __str__(self):
return self.nameTransações financeiras (entradas e saídas).
App: transactions
Campos:
| Campo | Tipo | Descrição | Obrigatório |
|---|---|---|---|
| account | ForeignKey | Conta associada | Sim |
| category | ForeignKey | Categoria da transação | Sim |
| transaction_type | CharField | Tipo (entrada ou saída) | Sim |
| amount | DecimalField | Valor da transação | Sim |
| transaction_date | DateField | Data da transação | Sim |
| description | TextField | Descrição/observação | Não |
| created_at | DateTimeField | Data de criação | Auto |
| updated_at | DateTimeField | Data de última atualização | Auto |
Constraints:
account: on_delete=CASCADEcategory: on_delete=PROTECTamount: max_digits=10, decimal_places=2transaction_type: choices entre 'income' e 'expense'
Choices para transaction_type:
TRANSACTION_TYPES = [
('income', 'Entrada'),
('expense', 'Saída'),
]Comportamento:
- Sempre acessível apenas pelo dono da conta
- Atualiza saldo da conta automaticamente (via signal)
- Validação: tipo deve corresponder ao tipo da categoria
- Ordenação padrão: mais recentes primeiro
Exemplo:
class Transaction(models.Model):
TRANSACTION_TYPES = [
('income', 'Entrada'),
('expense', 'Saída'),
]
account = models.ForeignKey(Account, on_delete=models.CASCADE)
category = models.ForeignKey(Category, on_delete=models.PROTECT)
transaction_type = models.CharField(max_length=10, choices=TRANSACTION_TYPES)
amount = models.DecimalField(max_digits=10, decimal_places=2)
transaction_date = models.DateField()
description = models.TextField(blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['-transaction_date', '-created_at']
def __str__(self):
return f'{self.transaction_type} - R$ {self.amount} - {self.transaction_date}'- Um usuário tem exatamente um perfil
- Perfil é criado automaticamente via signal
- Deletar usuário deleta o perfil (CASCADE)
- Um usuário pode ter várias contas
- Uma conta pertence a um único usuário
- Deletar usuário deleta todas as contas (CASCADE)
- Um usuário pode ter várias categorias
- Uma categoria pertence a um único usuário
- Deletar usuário deleta todas as categorias (CASCADE)
- Uma conta pode ter várias transações
- Uma transação pertence a uma única conta
- Deletar conta deleta todas as transações (CASCADE)
- Uma categoria pode ter várias transações
- Uma transação pertence a uma única categoria
- Deletar categoria é PROTEGIDO (PROTECT) - impede se houver transações
Todos os dados são isolados por usuário:
# Correto
accounts = Account.objects.filter(user=request.user)
# Incorreto - expõe dados de outros usuários
accounts = Account.objects.all()balancedeve ser decimal com 2 casasnamenão pode ser vaziobank_namenão pode ser vazio- Usuário não pode ver/editar contas de outros usuários
namenão pode ser vaziocategory_typedeve ser 'income' ou 'expense'- Cor deve ser hexadecimal válido (se fornecida)
- Usuário não pode ver/editar categorias de outros usuários
amountdeve ser positivotransaction_typedeve corresponder aocategory.category_typeaccountdeve pertencer ao usuário logadocategorydeve pertencer ao usuário logadotransaction_datenão pode ser futura (recomendado)
def calculate_balance(account):
incomes = account.transaction_set.filter(
transaction_type='income'
).aggregate(total=Sum('amount'))['total'] or 0
expenses = account.transaction_set.filter(
transaction_type='expense'
).aggregate(total=Sum('amount'))['total'] or 0
return incomes - expensesdef total_balance(user):
accounts = Account.objects.filter(user=user, is_active=True)
return sum(account.balance for account in accounts)Todos os models devem ter:
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)Motivos:
- Auditoria
- Ordenação cronológica
- Debugging
- Análises temporais
class Meta:
verbose_name = 'Nome Singular'
verbose_name_plural = 'Nome Plural'
ordering = ['-created_at'] # Ou outro campo relevante
indexes = [
models.Index(fields=['user', '-created_at']),
]# Evita N+1 queries
transactions = Transaction.objects.select_related(
'account', 'category'
).filter(account__user=request.user)# Carrega transações com contas
accounts = Account.objects.prefetch_related(
'transaction_set'
).filter(user=request.user)from django.db.models import Sum, Count
# Total de entradas do mês
incomes = Transaction.objects.filter(
account__user=request.user,
transaction_type='income',
transaction_date__month=current_month
).aggregate(total=Sum('amount'))from django.db.models.signals import post_save
from django.dispatch import receiver
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)@receiver(post_save, sender=Transaction)
def update_account_balance(sender, instance, **kwargs):
account = instance.account
account.balance = calculate_balance(account)
account.save()python manage.py makemigrationspython manage.py migratepython manage.py showmigrations- Sempre use
get_object_or_404para buscar objetos únicos - Sempre filtre por usuário para garantir segurança
- Use
select_relatedeprefetch_relatedpara otimizar queries - Valide tipos de transação com categorias
- Use transactions do Django para operações críticas
- Mantenha
created_ateupdated_atem todos os models - Use
__str__descritivos para facilitar debug - Defina
Meta.orderingpara ordenação consistente - Use
choicespara campos com valores fixos - Proteja deleções com
on_delete=PROTECTquando necessário