Gabriel Rocha
Projetos

Mercado Cesar

Aplicativo web de compras para atacarejo com produtos, estoque, pedidos e entrega em domicílio.

2025RepositórioDemo

O Mercado Cesar é um aplicativo web de compras para uma loja de atacarejo, desenvolvido em equipe. O cliente navega pelos produtos, monta o carrinho, escolhe entre retirada na loja ou entrega em domicílio e repete pedidos antigos; o time administra produtos, armazéns e estoque. É um Django clássico com templates server-rendered (sem API REST), banco SQLite no desenvolvimento e PostgreSQL na produção, imagens no Cloudinary e deploy no Render.

Arquitetura e stack

O app Django é único (mercadocesar) e o views.py concentra quase toda a lógica, com os CRUDs administrativos.

Modelos

Os modelos principais:

Regras de negócio

Duas regras estão concentradas em validators.py e no fluxo de entrega:

A baixa de estoque na finalização prioriza os armazéns com mais unidades:

estoques = Estoque.objects.filter(
    produto=item_carrinho.produto,
    quantidade__gt=0
).order_by('-quantidade')

for estoque in estoques:
    if quantidade_restante <= 0:
        break
    if estoque.quantidade >= quantidade_restante:
        estoque.quantidade -= quantidade_restante
        quantidade_restante = 0
    else:
        quantidade_restante -= estoque.quantidade
        estoque.quantidade = 0
    estoque.save()

Views e permissões

A autenticação usa os fluxos padrão do Django (UserCreationForm no registro e o login do framework). As telas administrativas usam user_passes_test:

Os CRUDs administrativos são implementados como uma view única por recurso que despacha pela query string ?action=list|add|edit|delete, e as rotas antigas (produtos/adicionar/, produtos/editar/<id>/) só redirecionam para essa view:

@user_passes_test(lambda u: u.is_staff)
def gerenciar_produtos(request):
    action = request.GET.get('action', 'list')
    produto_id = request.GET.get('id')
    if action == 'delete' and produto_id:
        ...
    if action == 'edit' and produto_id:
        ...
    if action == 'add':
        ...
    produtos = Produto.objects.all().order_by('codigo')
    ...

Carrinho, checkout e pedidos

O carrinho é salvo no banco (um ativo por usuário). O checkout guarda os dados de entrega na sessão (request.session['pedido_temp']), mostra a revisão com os cartões salvos e só no finalizar_pedido cria o Pedido. Nessa finalização, além de validar posse do cartão e estoque suficiente, o código copia os itens do carrinho para o ItemPedido com o preço atual, dá baixa no estoque e desativa o carrinho.

A repetição de pedido antigo é um POST JSON no próprio checkout (o “compra rápida”): ele busca o pedido somente do usuário logado, desativa o carrinho atual, recria os itens num carrinho novo e responde via JsonResponse.

O usuário vê os próprios pedidos em pedidos_recentes (com filtros) e os gestores veem todos em visualizar_pedidos (com filtros por tipo e data).

Configuração e deploy

O settings.py usa python-decouple: SECRET_KEY, DEBUG, ALLOWED_HOSTS e credenciais do Cloudinary vêm de variáveis de ambiente. O banco alterna pelo DATABASE_URL:

if DATABASE_URL:
    db_config = dj_database_url.parse(DATABASE_URL)
    if 'postgres' in DATABASE_URL:
        db_config['ENGINE'] = 'django.db.backends.postgresql'
    DATABASES = {'default': db_config}
else:
    DATABASES = {'default': {'ENGINE': 'django.db.backends.sqlite3',
                             'NAME': BASE_DIR / 'db.sqlite3'}}

No Render o build.sh roda collectstatic, migrate e, se existir, carrega a fixture admin_user.json. Por ser plano gratuito, a aplicação hiberna e demora para “acordar” no primeiro acesso.

Particularidades e pontos que revisaria

  1. Lixo de desenvolvimento versionado. O repositório tem o db.sqlite3 (400 KB) e as pastas __pycache__ com arquivos .pyc commitados. Isso deveria estar no .gitignore.

  2. Contrato estranho no Produto. descricao tem unique=True e default="", mas existe uma constraint que proíbe descrição vazia. Na prática não dá para dois produtos terem a mesma descrição (mesmo que sejam itens diferentes com nomes/códigos distintos), e o default="" conflita com a constraint de não-vazio.

  3. Finalização de pedido sem transação atômica. O finalizar_pedido faz várias escritas (cria o Pedido, copia os itens, baixa estoque de um ou mais armazéns, desativa o carrinho) sem @transaction.atomic. Uma falha no meio deixa o estado parcial, e como a baixa usa quantidade -= sem lock (F()), dois checkouts simultâneos podem vender além do estoque.

  4. Views-monstro e rotas redundantes. views.py tem cerca de 45 KB, com CRUDs inteiros despachados por ?action= e rotas “wrapper” que só fazem redirect('gerenciar_produtos?action=...'). Funciona, mas o arquivo ficou difícil de navegar e há duplicação entre as rotas antigas e a view unificada.

  5. Critérios de permissão divergentes. Produtos, armazéns e estoque exigem is_staff, enquanto lojas e a listagem geral de pedidos exigem is_superuser. Vale padronizar o nível entre as telas administrativas.

  6. Fallbacks arriscados no settings. SECRET_KEY tem um valor fixo de fallback, DEBUG é True por padrão e existe um print solto no settings.py que roda a cada inicialização. Em produção as env são setadas, mas subir sem elas deixa debug ligado e chave conhecida.

  7. Admin criado por fixture no deploy. O build.sh carrega mercadocesar/fixtures/admin_user.json, que cria um usuário admin com senha embutida no repositório; se ninguém trocar a senha depois do deploy, ela é pública.

Pontos de melhoria