Gabriel Rocha
Projects

Mercado Cesar

A web shopping application for a cash-and-carry store with products, inventory, orders and home delivery.

2025RepositoryLive demo

Mercado Cesar is a web shopping application for a cash-and-carry store, built as a team. Customers browse products, fill the cart, choose between in-store pickup or home delivery, and repeat past orders; the team manages products, warehouses and inventory. It is a classic Django app with server-rendered templates (no REST API), SQLite in development and PostgreSQL in production, images on Cloudinary, and deployment on Render.

Architecture and stack

The Django app is a single one (mercadocesar) and views.py concentrates almost all the logic, along with the administrative CRUDs.

Models

The main models:

Business rules

Two rules are concentrated in validators.py and in the delivery flow:

Inventory decrement at checkout prioritizes the warehouses with the most units:

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 and permissions

Authentication uses Django’s standard flows (UserCreationForm on signup and the framework’s login). Administrative screens use user_passes_test:

Administrative CRUDs are implemented as a single view per resource that dispatches on the query string ?action=list|add|edit|delete, and the old routes (produtos/adicionar/, produtos/editar/<id>/) only redirect to that 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')
    ...

Cart, checkout and orders

The cart is stored in the database (one active cart per user). Checkout keeps the delivery data in the session (request.session['pedido_temp']), shows the review with the saved cards, and only in finalizar_pedido creates the Pedido. In that finalization, besides validating card ownership and sufficient inventory, the code copies the cart items into ItemPedido with the current price, decrements inventory and deactivates the cart.

Repeating a past order is a JSON POST on checkout itself (the “quick purchase”): it looks up the order only for the logged-in user, deactivates the current cart, recreates the items in a new cart and responds via JsonResponse.

Users see their own orders in pedidos_recentes (with filters) and managers see all of them in visualizar_pedidos (with filters by type and date).

Configuration and deploy

settings.py uses python-decouple: SECRET_KEY, DEBUG, ALLOWED_HOSTS and the Cloudinary credentials come from environment variables. The database switches on 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'}}

On Render, build.sh runs collectstatic, migrate and, if it exists, loads the admin_user.json fixture. Because it is on the free plan, the application hibernates and takes a while to “wake up” on the first access.

Particularities and points I would revisit

  1. Development junk under version control. The repository has db.sqlite3 (400 KB) and __pycache__ folders with committed .pyc files. This should be in .gitignore.

  2. Strange contract on Produto. descricao has unique=True and default="", but there is a constraint that forbids an empty description. In practice two products cannot have the same description (even if they are different items with distinct names/codes), and default="" conflicts with the non-empty constraint.

  3. Order finalization without an atomic transaction. finalizar_pedido performs several writes (creates the Pedido, copies the items, decrements inventory from one or more warehouses, deactivates the cart) without @transaction.atomic. A failure midway leaves the state partial, and since the decrement uses quantidade -= without a lock (F()), two simultaneous checkouts can oversell the inventory.

  4. Monster views and redundant routes. views.py is around 45 KB, with entire CRUDs dispatched by ?action= and “wrapper” routes that only do redirect('gerenciar_produtos?action=...'). It works, but the file became hard to navigate and there is duplication between the old routes and the unified view.

  5. Divergent permission criteria. Products, warehouses and inventory require is_staff, while stores and the general order listing require is_superuser. It is worth standardizing the level across administrative screens.

  6. Risky fallbacks in settings. SECRET_KEY has a hardcoded fallback value, DEBUG is True by default and there is a stray print in settings.py that runs on every startup. In production the env vars are set, but deploying without them leaves debug on and a known key.

  7. Admin created by fixture on deploy. build.sh loads mercadocesar/fixtures/admin_user.json, which creates an admin user with a password embedded in the repository; if nobody changes the password after deploy, it is public.

Points for improvement