Estrutura Escalável de Projetos Django: Organização Profissional para Produção, Times e Crescimento

Published on: 2026-01-09
Post image
pt estrutura-django organizacao-de-projeto-django django-em-producao settings-por-ambiente-django django-settings-modular boas-praticas-django arquitetura-django projeto-django-escalavel django-startproject-estrutura django-apps-folder django-core

Uma aplicação Django costuma começar pequena, com poucos módulos e configurações simples. Com o tempo, surgem ambientes diferentes (desenvolvimento, testes e produção), mais pessoas no time e novas integrações, e a estrutura padrão criada por startproject deixa de organizar bem as responsabilidades.

Uma estrutura escalável prioriza separação de configurações, isolamento de segredos, organização consistente de apps e um lugar claro para testes, scripts e documentação interna. Esse tipo de arranjo reduz erros de deploy, evita dependências circulares e torna mais previsível evoluir o projeto sem “remendar” caminhos e importações.

Por que a estrutura padrão do Django costuma falhar em produção

A estrutura padrão concentra quase tudo em um único settings.py, o que incentiva condicionais como DEBUG espalhadas e decisões misturadas. Isso aumenta a chance de configurar algo de forma correta localmente e quebrar no staging ou em produção. Também não existe uma divisão clara entre código de aplicação e código de configuração, e arquivos “utilitários” acabam em lugares arbitrários. Em projetos maiores, essa falta de convenção abre espaço para importações circulares e acoplamento desnecessário entre apps.

Outro problema recorrente é manter segredos como SECRET_KEY e credenciais de banco dentro do repositório, mesmo sem intenção. Basta um commit indevido para comprometer chaves e ambientes, e trocar esses segredos depois pode ser trabalhoso. A estrutura padrão também não define locais explícitos para testes mais organizados, dados de teste (fixtures) e rotinas de automação. O resultado é um projeto difícil de navegar e de padronizar quando o time cresce.

Estrutura de projeto pronta para crescer

Uma estrutura escalável define pastas com propósito, reduzindo decisões repetidas e mantendo a raiz do projeto limpa. A ideia central é separar configuração (settings, URLs, ASGI/WSGI) do código de domínio (apps), e criar pontos claros para dependências, arquivos estáticos e automação. A separação por ambiente evita “gambiarras” e torna cada deploy mais previsível. A seguir está um exemplo de estrutura completa e comum em produção.

O diretório config concentra o projeto Django (settings e URLs), enquanto apps concentra os módulos funcionais. Pastas como static, media e templates ficam na raiz para serem encontradas facilmente por ferramentas de build e deploy. Arquivos como Dockerfile, docker-compose.yml, Makefile e requirements ajudam na padronização do ciclo de desenvolvimento. Essa organização reduz fricção e melhora a manutenção.

O que essa estrutura representa pode ser resumido na árvore abaixo.

myproject/
├── .env.example
├── .gitignore
├── docker-compose.yml
├── Dockerfile
├── Makefile
├── README.md
├── requirements/
│   ├── base.txt
│   ├── development.txt
│   ├── production.txt
│   └── testing.txt
├── config/
│   ├── __init__.py
│   ├── settings/
│   │   ├── __init__.py
│   │   ├── base.py
│   │   ├── development.py
│   │   ├── production.py
│   │   └── testing.py
│   ├── urls.py
│   ├── wsgi.py
│   └── asgi.py
├── apps/
│   ├── __init__.py
│   ├── users/
│   │   ├── __init__.py
│   │   ├── admin.py
│   │   ├── apps.py
│   │   ├── models.py
│   │   ├── views.py
│   │   ├── urls.py
│   │   ├── serializers.py
│   │   ├── services.py
│   │   ├── selectors.py
│   │   └── tests/
│   │       ├── __init__.py
│   │       ├── test_models.py
│   │       ├── test_views.py
│   │       └── test_services.py
│   └── core/
│       ├── __init__.py
│       ├── models.py
│       ├── utils.py
│       └── exceptions.py
├── static/
├── media/
├── templates/
├── scripts/
├── docs/
└── manage.py

Configurações separadas por ambiente (settings em pacote)

Separar configurações por ambiente significa trocar um único arquivo grande por um pacote settings com módulos específicos. O arquivo base.py contém somente configurações compartilhadas, enquanto development.py, production.py e testing.py concentram decisões específicas de cada cenário. Isso elimina condicionais repetidas e reduz o risco de publicar DEBUG=True em produção. Também simplifica a auditoria, porque o que muda por ambiente fica explícito.

Uma boa prática é não definir no base.py itens que quase sempre variam, como banco de dados e DEBUG. Em vez disso, o base.py configura apps, middlewares, templates e caminhos comuns. Outra convenção útil é dividir INSTALLED_APPS em blocos: apps do Django, apps de terceiros e apps locais. Essa separação reduz conflitos e melhora a legibilidade em revisões de código.

O conteúdo abaixo exemplifica um config/settings/base.py com boas separações e leitura de variáveis de ambiente usando python-decouple.

import os
from pathlib import Path

from decouple import config, Csv

# Caminhos do projeto
BASE_DIR = Path(__file__).resolve().parent.parent.parent

# Segurança: segredos nunca ficam hardcoded
SECRET_KEY = config("SECRET_KEY")

# Apps do Django
DJANGO_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
]

# Apps de terceiros
THIRD_PARTY_APPS = [
    "rest_framework",
    "corsheaders",
    "django_extensions",
]

# Apps do próprio projeto
LOCAL_APPS = [
    "apps.core",
    "apps.users",
]

INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS

# Autenticação: exemplo com modelo de usuário customizado
AUTH_USER_MODEL = "users.User"

# Middlewares
MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "corsheaders.middleware.CorsMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
]

ROOT_URLCONF = "config.urls"

# Templates
TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [BASE_DIR / "templates"],
        "APP_DIRS": True,
        "OPTIONS": {
            "context_processors": [
                "django.template.context_processors.debug",
                "django.template.context_processors.request",
                "django.contrib.auth.context_processors.auth",
                "django.contrib.messages.context_processors.messages",
            ],
        },
    }
]

WSGI_APPLICATION = "config.wsgi.application"

# Validadores de senha
AUTH_PASSWORD_VALIDATORS = [
    {"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"},
    {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"},
    {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"},
    {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
]

# Internacionalização
LANGUAGE_CODE = "en-us"
TIME_ZONE = "UTC"
USE_I18N = True
USE_TZ = True

# Arquivos estáticos
STATIC_URL = "/static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
STATICFILES_DIRS = [BASE_DIR / "static"]

# Arquivos de mídia
MEDIA_URL = "/media/"
MEDIA_ROOT = BASE_DIR / "media"

DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"

Settings de desenvolvimento: produtividade e simplicidade

O ambiente de desenvolvimento tende a priorizar rapidez, logs verbosos e ferramentas de depuração. Um exemplo clássico é usar SQLite para reduzir dependências locais, além de liberar CORS para facilitar integrações com front-ends locais. Também é comum adicionar apps como debug_toolbar para inspecionar queries e caches. Mesmo assim, o arquivo de desenvolvimento deve permanecer pequeno e focado no que realmente difere do base.

Ao importar from .base import *, as configurações comuns são herdadas e apenas as diferenças são declaradas. Isso reduz duplicação e minimiza divergências acidentais entre ambientes. Itens como ALLOWED_HOSTS podem ser mais permissivos, e e-mails podem ir para o console em vez de serem enviados. Abaixo está um exemplo funcional.

O trecho a seguir mostra um config/settings/development.py típico.

from .base import *

DEBUG = True

ALLOWED_HOSTS = ["localhost", "127.0.0.1", "0.0.0.0"]

# Banco simples no desenvolvimento
DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.sqlite3",
        "NAME": BASE_DIR / "db.sqlite3",
    }
}

# Ferramentas de desenvolvimento
INSTALLED_APPS += [
    "debug_toolbar",
]

MIDDLEWARE += [
    "debug_toolbar.middleware.DebugToolbarMiddleware",
]

INTERNAL_IPS = ["127.0.0.1"]

# E-mail no console
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"

# CORS aberto somente no desenvolvimento
CORS_ALLOW_ALL_ORIGINS = True

Settings de produção: segurança, banco e logging

O ambiente de produção exige segurança mais rígida, banco adequado e logs estruturados. Configurações como SECURE_SSL_REDIRECT e cookies seguros ajudam a reduzir exposição a ataques comuns. Em produção, é usual usar PostgreSQL e ativar parâmetros como CONN_MAX_AGE para reaproveitar conexões. Também é importante que ALLOWED_HOSTS e CORS_ALLOWED_ORIGINS sejam controlados por variáveis de ambiente.

Além disso, a configuração de LOGGING define formato e nível de logs, o que facilita observabilidade. Em times maiores, logs consistentes ajudam a correlacionar erros e eventos de deploy. O arquivo de produção deve evitar valores “mágicos” e preferir leitura via python-decouple. A seguir está um exemplo que cobre esses pontos.

O trecho a seguir mostra um config/settings/production.py com medidas comuns de segurança e logging.

from .base import *
from decouple import config, Csv

DEBUG = False

ALLOWED_HOSTS = config("ALLOWED_HOSTS", cast=Csv())

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": config("DB_NAME"),
        "USER": config("DB_USER"),
        "PASSWORD": config("DB_PASSWORD"),
        "HOST": config("DB_HOST"),
        "PORT": config("DB_PORT", default="5432"),
        "CONN_MAX_AGE": 60,
        "OPTIONS": {
            "connect_timeout": 10,
        },
    }
}

# Segurança
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = "DENY"
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True

# HSTS
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True

# CORS restrito
CORS_ALLOWED_ORIGINS = config("CORS_ALLOWED_ORIGINS", cast=Csv())

# Logging
LOGGING = {
    "version": 1,
    "disable_existing_loggers": False,
    "formatters": {
        "verbose": {
            "format": "{levelname} {asctime} {module} {process:d} {thread:d} {message}",
            "style": "{",
        },
    },
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "formatter": "verbose",
        },
    },
    "root": {
        "handlers": ["console"],
        "level": "INFO",
    },
    "loggers": {
        "django": {
            "handlers": ["console"],
            "level": config("DJANGO_LOG_LEVEL", default="INFO"),
            "propagate": False,
        },
    },
}

Como alternar ambientes com DJANGO_SETTINGS_MODULE

O Django escolhe o arquivo de configuração a partir da variável DJANGO_SETTINGS_MODULE. Ao usar um pacote de settings, essa variável aponta para o módulo correto do ambiente. Isso reduz divergências, porque o mesmo código pode rodar com configurações diferentes sem alterações no repositório. Em pipelines de CI/CD, esse mecanismo também facilita executar testes com um settings próprio.

A configuração pode ser definida no sistema operacional, em containers ou em um arquivo .env. O ponto central é que cada ambiente declara explicitamente seu módulo de settings, sem depender de condicionais. Essa abordagem funciona bem com múltiplos deploys e com staging. Os exemplos abaixo mostram formas comuns de definir essa variável.

A lista a seguir mostra valores típicos para a variável DJANGO_SETTINGS_MODULE em cada ambiente.

  • Desenvolvimento: config.settings.development

  • Produção: config.settings.production

  • Testes: config.settings.testing

Variáveis de ambiente e segredos: .env, .env.example e .gitignore

Variáveis de ambiente guardam informações sensíveis fora do código, como chaves, senhas e hosts permitidos. O arquivo .env é uma conveniência local para carregar essas variáveis sem configurá-las manualmente em cada execução. Em produção, o mesmo conteúdo costuma ser injetado pelo provedor de deploy, e o arquivo .env nem precisa existir. O mais importante é que o .env nunca seja versionado.

O arquivo .env.example é a versão “modelo” sem segredos, mostrando quais variáveis são necessárias. Ele padroniza onboarding e reduz falhas por falta de configuração. Já o .gitignore impede que arquivos sensíveis, caches e saídas de build sejam enviados ao repositório. A combinação desses três itens reduz vazamentos de segredos e inconsistências de ambiente.

O exemplo abaixo mostra um .env típico com variáveis comuns em projetos Django.

SECRET_KEY=uma-chave-super-secreta
DEBUG=True
DB_NAME=myproject
DB_USER=postgres
DB_PASSWORD=senha-secreta
DB_HOST=localhost
DB_PORT=5432
ALLOWED_HOSTS=localhost,127.0.0.1
CORS_ALLOWED_ORIGINS=http://localhost:3000
DJANGO_SETTINGS_MODULE=config.settings.development

O exemplo abaixo mostra um .env.example versionável, sem valores sensíveis.

SECRET_KEY=
DEBUG=
DB_NAME=
DB_USER=
DB_PASSWORD=
DB_HOST=
DB_PORT=
ALLOWED_HOSTS=
CORS_ALLOWED_ORIGINS=
DJANGO_SETTINGS_MODULE=

O conteúdo abaixo exemplifica entradas essenciais para um .gitignore focado em Django.

.env
*.pyc
__pycache__/
db.sqlite3
staticfiles/
media/
.venv/

Diretório apps/: organização consistente do código de aplicação

Centralizar apps em um diretório apps ajuda a separar claramente o domínio do projeto da camada de configuração. Isso evita que a raiz do repositório fique cheia e também torna as importações mais consistentes. Em vez de importar módulos por caminhos ambíguos, as importações passam a seguir um padrão como apps.users e apps.core. Essa previsibilidade melhora manutenibilidade e reduz colisões de nomes entre apps.

Ao criar um app dentro de apps, o arquivo apps.py deve declarar o atributo name com o caminho completo do pacote. Esse detalhe evita problemas com autodiscovery de apps, especialmente quando existem apps com nomes comuns. Também facilita reorganizações futuras, porque o caminho é explícito. Abaixo está um exemplo de configuração de app com o prefixo apps..

O trecho a seguir mostra um apps/products/apps.py com o caminho correto do app.

from django.apps import AppConfig

class ProductsConfig(AppConfig):
    default_auto_field = "django.db.models.BigAutoField"
    name = "apps.products"

App core: base para modelos, utilitários e exceções

O app core funciona como base compartilhada para o restante do projeto. Ele concentra elementos reutilizáveis, como modelos abstratos, exceções de domínio e utilitários. Um modelo abstrato é um modelo do Django que não cria tabela própria, servindo apenas para herança e reutilização de campos. Isso reduz duplicação e mantém consistência em auditoria, timestamps e comportamentos comuns.

Também é comum centralizar exceções que expressam erros de regra de negócio. Em APIs com Django REST Framework, uma exceção pode herdar de APIException para padronizar status HTTP e mensagem. Utilitários devem ser pequenos e específicos, evitando virar um “depósito” genérico sem responsabilidade. Abaixo estão exemplos de itens típicos em apps.core.

O código a seguir mostra um modelo abstrato com timestamps em apps/core/models.py.

from django.db import models

class TimeStampedModel(models.Model):
    """
    Modelo abstrato com campos automáticos de criação e atualização.
    """
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        abstract = True

O código a seguir mostra exceções de API em apps/core/exceptions.py.

from rest_framework.exceptions import APIException

class ServiceUnavailable(APIException):
    status_code = 503
    default_detail = "Serviço temporariamente indisponível."
    default_code = "service_unavailable"

class BusinessLogicError(APIException):
    status_code = 400
    default_detail = "Ocorreu um erro de regra de negócio."
    default_code = "business_logic_error"

O código a seguir mostra um utilitário em apps/core/utils.py para gerar slug único.

def generate_unique_slug(model_class, value, slug_field="slug"):
    """
    Gera um slug único para um modelo, adicionando sufixo numérico se necessário.
    """
    from django.utils.text import slugify

    slug = slugify(value)
    unique_slug = slug
    num = 1

    while model_class.objects.filter(**{slug_field: unique_slug}).exists():
        unique_slug = f"{slug}-{num}"
        num += 1

    return unique_slug

Services e selectors: regras de negócio e consultas fora das views

Em projetos maiores, views muito “gordas” acumulam validação, regras de negócio e acesso ao banco, tornando mudanças arriscadas. O padrão de services organiza operações de negócio em funções ou classes focadas, como criar usuário, ativar/desativar conta e gerar registros relacionados. Já selectors centralizam consultas complexas, reduzindo repetição e facilitando otimizações. Essa separação melhora testes, porque cada camada pode ser testada isoladamente.

Outro benefício é que regras críticas podem ser executadas dentro de uma transação usando transaction.atomic, garantindo consistência se algo falhar no meio do processo. Exceções de regra de negócio podem ser lançadas no service e tratadas de forma padronizada na API. Com isso, a view fica responsável por orquestrar a entrada e saída, enquanto o service executa a lógica. A seguir estão exemplos completos e alinhados a essa abordagem.

O código abaixo exemplifica um service de usuários em apps/users/services.py.

from django.db import transaction

from apps.core.exceptions import BusinessLogicError
from .models import User

class UserService:
    @staticmethod
    @transaction.atomic
    def create_user(email: str, password: str, **kwargs) -> User:
        """
        Cria um usuário com validação e transação.
        """
        if User.objects.filter(email=email).exists():
            raise BusinessLogicError("Já existe um usuário com este e-mail.")

        user = User.objects.create_user(
            email=email,
            password=password,
            **kwargs,
        )
        return user

    @staticmethod
    def deactivate_user(user: User) -> User:
        """
        Desativa um usuário mantendo auditoria simples.
        """
        user.is_active = False
        user.save(update_fields=["is_active"])
        return user

O exemplo abaixo mostra como a view fica mais enxuta em apps/users/views.py ao delegar lógica para services e selectors.

from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView

from .serializers import UserSerializer
from .services import UserService
from .selectors import UserSelector

class UserCreateView(APIView):
    def post(self, request):
        serializer = UserSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        user = UserService.create_user(**serializer.validated_data)

        return Response(UserSerializer(user).data, status=status.HTTP_201_CREATED)

class ActiveUsersView(APIView):
    def get(self, request):
        users = UserSelector.get_active_users()
        return Response(UserSerializer(users, many=True).data)

Dependências por ambiente com requirements/

Separar dependências por ambiente evita instalar ferramentas de desenvolvimento em produção e mantém o runtime mais enxuto. O arquivo base.txt contém o mínimo necessário para rodar a aplicação. O arquivo development.txt adiciona ferramentas como depuradores e shells aprimorados. Já production.txt inclui servidores WSGI, coleta de estáticos e observabilidade, e testing.txt inclui bibliotecas de teste e cobertura.

Essa organização também reduz conflitos de versão, porque cada contexto tem seu conjunto explícito. Em pipelines, isso simplifica cache e acelera builds ao instalar apenas o necessário. O padrão -r base.txt reutiliza o conjunto comum e diminui duplicação. A seguir estão exemplos de arquivos alinhados a esse modelo.

Os trechos a seguir mostram uma divisão típica de dependências por ambiente.

# requirements/base.txt
Django>=5.2,<5.3
djangorestframework>=3.15.0
django-cors-headers>=4.3.0
python-decouple>=3.8
psycopg2-binary>=2.9.9
# requirements/development.txt
-r base.txt
django-debug-toolbar>=4.3.0
django-extensions>=3.2.3
ipython>=8.0.0
# requirements/production.txt
-r base.txt
gunicorn>=21.2.0
whitenoise>=6.6.0
sentry-sdk>=1.40.0
# requirements/testing.txt
-r base.txt
pytest>=8.0.0
pytest-django>=4.8.0
pytest-cov>=4.1.0
factory-boy>=3.3.0

Makefile: padronização de comandos do dia a dia

Um Makefile agrupa comandos frequentes em atalhos consistentes, reduzindo erro humano e melhorando a produtividade do time. Em vez de memorizar várias flags, comandos como instalar dependências, rodar servidor, migrar banco e rodar testes ficam padronizados. Isso ajuda especialmente em projetos com mais ferramentas, como linters e formatadores. Também é comum adicionar comandos de limpeza para remover caches e arquivos temporários.

Mesmo em ambientes onde make não é padrão, esse arquivo serve como documentação executável do fluxo do projeto. A ideia é ter alvos simples e previsíveis, com nomes curtos e claros. Também é útil manter o lint e formatação juntos para manter consistência do código. A seguir está um exemplo funcional com alvos comuns.

O código abaixo mostra um Makefile típico para projetos Django.

.PHONY: install run migrate test lint clean shell docker-up docker-down

install:
	pip install -r requirements/development.txt

run:
	python manage.py runserver

migrate:
	python manage.py makemigrations
	python manage.py migrate

test:
	pytest --cov=apps --cov-report=html

lint:
	ruff check apps/
	ruff format apps/

shell:
	python manage.py shell_plus

clean:
	find . -type f -name '*.pyc' -delete
	find . -type d -name '__pycache__' -delete

docker-up:
	docker-compose up -d

docker-down:
	docker-compose down

Estrutura de testes: testes próximos do código e fixtures compartilhadas

Uma estrutura de testes escalável mantém testes próximos do app que eles validam. Isso facilita encontrar rapidamente o que cobre modelos, views, services e selectors. Separar arquivos por tipo reduz arquivos enormes e torna falhas mais fáceis de localizar. Além disso, bibliotecas como pytest usam fixtures, que são funções reutilizáveis para criar dependências de teste, como clientes HTTP e usuários autenticados.

Um diretório tests/ dentro de cada app permite que o app seja evoluído com suas garantias. Um conftest.py pode existir tanto no app quanto na raiz do projeto, criando fixtures compartilhadas. Isso reduz duplicação de setup e deixa os testes mais curtos. A seguir está um exemplo de organização e um conftest.py simples para APIs.

O exemplo abaixo mostra uma organização comum de testes por app.

apps/users/tests/
├── __init__.py
├── conftest.py
├── factories.py
├── test_models.py
├── test_views.py
├── test_services.py
└── test_selectors.py

O código abaixo mostra um conftest.py na raiz do projeto para fixtures compartilhadas.

import pytest
from rest_framework.test import APIClient

@pytest.fixture
def api_client():
    return APIClient()

@pytest.fixture
def authenticated_client(api_client, user):
    api_client.force_authenticate(user=user)
    return api_client

Pontos relevantes do Django 5.2 em projetos novos

O Django 5.2 exige Python 3.10+, o que impacta ambientes legados e imagens de container antigas. Em projetos novos, isso normalmente simplifica, porque versões recentes do Python trazem melhorias de performance e typing. Outra novidade citada com frequência é a evolução do suporte a cenários mais avançados, incluindo discussões e recursos relacionados a chaves primárias compostas. Esses pontos não mudam a estrutura de pastas diretamente, mas influenciam decisões de compatibilidade e dependências.

Também há mudanças de ergonomia em ambiente interativo e shell, que reduzem atrito no desenvolvimento. A organização de settings por ambiente continua sendo útil independentemente da versão, pois resolve problemas de deploy e consistência. Em especial, projetos em 5.2 tendem a adotar padrões mais explícitos de configuração e observabilidade. Assim, a estrutura escalável se encaixa bem na expectativa de longevidade típica de versões estáveis.

Script de bootstrap: criando a estrutura do zero

Um script de bootstrap automatiza a criação de diretórios e arquivos base, reduzindo inconsistência entre projetos. Esse tipo de script não substitui decisões do produto, mas garante um ponto de partida repetível. A automação também evita esquecer arquivos como __init__.py e a estrutura de requirements. O resultado é um repositório pronto para evoluir sem precisar reorganizar tudo mais tarde.

O exemplo abaixo usa bash e cobre criação de pastas, inicialização do git, virtualenv, instalação inicial e criação do projeto Django com config como pacote principal. Ajustes podem ser necessários conforme o sistema operacional, mas a ideia central é reproduzir a estrutura de forma previsível. A seguir está um script completo alinhado à estrutura apresentada. Ele cria os diretórios essenciais e prepara os arquivos iniciais.

O trecho a seguir mostra um script bash para iniciar um projeto com essa estrutura.

#!/bin/bash

# Estrutura de diretórios
mkdir -p myproject/{config/settings,apps/core,apps/users/tests,requirements,static,media,templates,scripts,docs}

cd myproject || exit 1

# Git
git init

# Ambiente virtual
python -m venv .venv
source .venv/bin/activate

# Dependências iniciais
pip install django python-decouple

# Cria projeto Django em config
django-admin startproject config .

# Move settings padrão para o pacote de settings
mkdir -p config/settings
mv config/settings.py config/settings/base.py
touch config/settings/__init__.py
touch config/settings/development.py
touch config/settings/production.py
touch config/settings/testing.py

# Inicializa pacotes em apps
touch apps/__init__.py
touch apps/core/__init__.py
touch apps/users/__init__.py
touch apps/users/tests/__init__.py

# Arquivos de requirements
touch requirements/base.txt
touch requirements/development.txt
touch requirements/production.txt
touch requirements/testing.txt

# Arquivos auxiliares
touch .env .env.example .gitignore Makefile README.md

echo "Estrutura do projeto criada!"

Conclusão: uma base previsível para equipes, ambientes e crescimento

Uma estrutura de Django que escala separa claramente configuração de código de aplicação e evita decisões implícitas. O pacote de settings por ambiente reduz risco de erros de deploy, e o uso de variáveis de ambiente impede vazamento de segredos no repositório. O diretório apps organiza o domínio e mantém importações consistentes, enquanto o app core concentra componentes reutilizáveis com responsabilidade clara. A combinação de services, selectors, requirements por ambiente, Makefile e testes bem organizados cria um projeto mais legível, testável e sustentável.