mirror of https://github.com/interlegis/sapl.git
Browse Source
Correcao de semantica (decisao do arquiteto, 17/08/2026): o que o vereador anexa no app e evidencia da demanda — foto da rua, video da indicacao — nao o documento legislativo. O codigo gravava o anexo como texto_original, o que faria um video virar "texto" da proposicao. - AnexoProposicao (app isolado, custo zero de rebase): arquivo + nome + mime + tamanho + sha256 DOS BYTES RECEBIDOS — o SAPL declara o que guardou, fechando a cadeia app -> hub -> legislativo. So gravacao por ora; consulta vem depois. - limite de 1 arquivo removido (premissa errada); foto e video convivem - texto_original volta a ser exclusivo do fluxo proprio do SAPL (tipo_texto '') De quebra, conserta flakiness pre-existente dos testes: o baker criava usuario com username de 150 chars e o auditing do SAPL grava em varchar(100) — o INSERT do AuditLog estourava DENTRO do atomic e envenenava a transacao inteira. Username explicito nos fixtures. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>pull/3858/head
9 changed files with 438 additions and 45 deletions
@ -1,40 +1,221 @@ |
|||||
# Subir o SAPL apontando para o banco remoto (localhost) |
# Subir o SAPL em localhost (banco remoto ou cópia local) |
||||
|
|
||||
Passo a passo para subir o ambiente local usando o Postgres já disponível em `sgvp.com.br:5432`. |
Passo a passo para subir o ambiente local usando o Postgres já disponível em |
||||
|
`demo.legisinc.com.br:5432` (base `demo`) — ou uma cópia local dele, quando a tarefa |
||||
|
exigir migration. |
||||
|
|
||||
|
> Validado em 13/08/2026 em Ubuntu 26.04. O host `sgvp.com.br:5432`, citado em |
||||
|
> versões anteriores deste documento, não responde mais — use `demo.legisinc.com.br`. |
||||
|
|
||||
|
**Antes de tudo, responda uma pergunta: esta tarefa precisa de migration?** |
||||
|
Se precisar, o banco remoto não serve — o usuário de acesso não tem permissão de DDL — |
||||
|
e você vai precisar da cópia local descrita na [seção 4](#4-cópia-local-do-banco-quando-há-migration-pendente). |
||||
|
A [seção 3](#3-decidir-remoto-direto-ou-cópia-local) mostra como descobrir isso em dois comandos. |
||||
|
|
||||
## 1) Preparar o ambiente Python |
## 1) Preparar o ambiente Python |
||||
|
|
||||
|
O projeto roda em **Python 3.12 + Django 2.2**. Django 2.2 não funciona em Python 3.13+, |
||||
|
então escolha o caminho conforme o seu sistema. |
||||
|
|
||||
|
### Opção A — Docker (recomendado; obrigatório em Ubuntu 24.04+) |
||||
|
|
||||
|
Distribuições recentes (Ubuntu 26.04, por exemplo) já não oferecem Python 3.12 no apt. |
||||
|
Nesse caso use a imagem `python:3.12-slim-bookworm`, com as mesmas dependências de |
||||
|
sistema do `docker/Dockerfile.dev`: |
||||
|
|
||||
|
```bash |
||||
|
cd ~/dev/sapl |
||||
|
docker build -f docker/Dockerfile.dev -t sapl:dev . |
||||
|
``` |
||||
|
|
||||
|
> **Atenção**: o `Dockerfile.dev` instala `requirements/dev-requirements.txt`, que hoje |
||||
|
> tem um conflito de dependências — `django-debug-toolbar` está pinado em `2.2.1` no |
||||
|
> `requirements.txt` e em `3.2.4` no `dev-requirements.txt`, e o pip não resolve. |
||||
|
> Até isso ser corrigido, copie o `Dockerfile.dev` trocando a última linha do |
||||
|
> `pip install` para `requirements/requirements.txt` e construa a partir da cópia: |
||||
|
|
||||
```bash |
```bash |
||||
cd /root/dev/sapl |
sed 's|dev-requirements.txt|requirements.txt|' docker/Dockerfile.dev > /tmp/Dockerfile.localhost |
||||
python3 -m venv .venv |
docker build -f /tmp/Dockerfile.localhost -t sapl:localhost . |
||||
|
``` |
||||
|
|
||||
|
### Opção B — virtualenv (se o sistema tiver Python 3.12) |
||||
|
|
||||
|
```bash |
||||
|
cd ~/dev/sapl |
||||
|
python3.12 -m venv .venv |
||||
source .venv/bin/activate |
source .venv/bin/activate |
||||
pip install -r requirements/requirements.txt |
pip install -r requirements/requirements.txt |
||||
``` |
``` |
||||
|
|
||||
## 2) Configurar variáveis de ambiente |
## 2) Configurar variáveis de ambiente |
||||
|
|
||||
Edite `sapl/.env` com os valores do banco remoto: |
Crie `sapl/.env` (o arquivo é gitignored) com os valores do banco remoto: |
||||
|
|
||||
```env |
```env |
||||
DATABASE_URL=postgresql://kemuel:kasepulvida@sgvp.com.br:5432/sapl |
DATABASE_URL=postgresql://<usuario>:<senha-url-encoded>@demo.legisinc.com.br:5432/demo |
||||
SECRET_KEY=<sua-chave-secreta> |
SECRET_KEY=<sua-chave-secreta> |
||||
DEBUG=True |
DEBUG=True |
||||
|
DJANGO_DEBUG=True |
||||
EMAIL_USE_TLS=True |
EMAIL_USE_TLS=True |
||||
EMAIL_PORT=587 |
EMAIL_PORT=587 |
||||
``` |
``` |
||||
|
|
||||
> Observação: não rode `migrate` contra esse banco se ele for de produção. |
> **`DJANGO_DEBUG`, não `DEBUG`**: o `settings.py` lê o modo debug do Django de |
||||
|
> `DJANGO_DEBUG` (linha 38). A variável `DEBUG` só controla o log de conexão do banco. |
||||
|
> Defina as duas. |
||||
|
> |
||||
|
> **Senha com caractere especial** precisa ser URL-encoded no `DATABASE_URL`: |
||||
|
> `@` → `%40`, `:` → `%3A`, `/` → `%2F`. Ex.: `S3nh@2026` vira `S3nh%402026`. |
||||
|
> Sem isso o parser da URL quebra no lugar errado e a autenticação falha. |
||||
|
|
||||
|
Peça as credenciais atuais ao responsável pelo ambiente — as que estavam neste |
||||
|
documento e no `CONFIGURACAO_BD_REMOTO.md` foram rejeitadas pelo servidor em 12/08/2026. |
||||
|
|
||||
|
## 3) Decidir: remoto direto ou cópia local? |
||||
|
|
||||
|
Esta é a decisão que define o resto do setup. O critério é **se há migration pendente**: |
||||
|
|
||||
|
```bash |
||||
|
docker run --rm -v "$PWD":/sapl-dev -w /sapl-dev sapl:localhost \ |
||||
|
python manage.py showmigrations | grep '\[ \]' |
||||
|
``` |
||||
|
|
||||
|
(o `settings.py` lê o `sapl/.env` do próprio volume montado, não precisa de `--env-file`; |
||||
|
se a `DATABASE_URL` já apontar para o banco local da seção 4, acrescente `--network sapl-net`) |
||||
|
|
||||
|
- **Não imprimiu nada** (todas `[X]`) → siga usando o banco remoto direto. Pule para a seção 5. |
||||
|
A aplicação grava dados normalmente; o que falta ao usuário é só permissão de DDL. |
||||
|
- **Imprimiu alguma migration** → você precisa de DDL, e o remoto vai recusar. **Faça a cópia |
||||
|
local (seção 4).** |
||||
|
|
||||
|
Para confirmar a permissão de DDL antes de tentar (opcional): |
||||
|
|
||||
|
```bash |
||||
|
DB=$(grep '^DATABASE_URL=' sapl/.env | cut -d= -f2-) |
||||
|
docker run --rm -e PGURL="$DB" postgres:17-alpine \ |
||||
|
sh -c 'psql "$PGURL" -Atc "select has_schema_privilege(current_user, '"'"'public'"'"', '"'"'CREATE'"'"');"' |
||||
|
``` |
||||
|
|
||||
|
`f` significa sem permissão de criar tabela. Foi o resultado do usuário `kasepulvida` em |
||||
|
`demo.legisinc.com.br` em 13/08/2026 — o `migrate` falha com: |
||||
|
|
||||
|
``` |
||||
|
django.db.utils.ProgrammingError: permission denied for schema public |
||||
|
``` |
||||
|
|
||||
|
A migration roda em transação, então uma tentativa recusada não deixa resíduo no banco. |
||||
|
|
||||
|
> **Nunca rode `migrate` num banco remoto compartilhado ou de produção** sem autorização |
||||
|
> do dono do banco. A alternativa "peça um `GRANT CREATE ON SCHEMA public`" existe, mas |
||||
|
> aplica o schema novo num banco que outras pessoas usam — prefira a cópia local. |
||||
|
|
||||
|
## 4) Cópia local do banco (quando há migration pendente) |
||||
|
|
||||
|
Copia o banco que você está apontando hoje — `demo` ou qualquer outro — para um Postgres |
||||
|
local, onde você tem liberdade total de `migrate`, reset e escrita, sem tocar no remoto. |
||||
|
São ~1 min para 61 MB. Os comandos leem a origem do próprio `sapl/.env`, então valem para |
||||
|
qualquer banco de origem. |
||||
|
|
||||
|
**4.1 — Dump da origem** (só precisa de `SELECT`; o usuário remoto tem): |
||||
|
|
||||
|
```bash |
||||
|
DB=$(grep '^DATABASE_URL=' sapl/.env | cut -d= -f2-) |
||||
|
docker run --rm -e PGURL="$DB" -v /tmp:/dump postgres:17-alpine \ |
||||
|
sh -c 'pg_dump "$PGURL" --no-owner --no-privileges --format=custom --file=/dump/origem.dump' |
||||
|
``` |
||||
|
|
||||
|
`--no-owner --no-privileges` descarta os donos e ACLs do servidor de origem, que não |
||||
|
existem na sua máquina. |
||||
|
|
||||
## 3) Testar conexão (opcional) |
**4.2 — Postgres local numa rede Docker própria:** |
||||
|
|
||||
```bash |
```bash |
||||
python manage.py showmigrations --plan |
docker network create sapl-net |
||||
|
docker run -d --name sapl-postgres --network sapl-net -p 5433:5432 \ |
||||
|
-e POSTGRES_USER=sapl -e POSTGRES_PASSWORD=sapl -e POSTGRES_DB=demo \ |
||||
|
postgres:17-alpine |
||||
|
``` |
||||
|
|
||||
|
A rede é o que permite o container do SAPL enxergar o banco pelo hostname `sapl-postgres`. |
||||
|
A porta `5433` no host é só para você abrir um cliente SQL (DBeaver, psql) de fora. |
||||
|
|
||||
|
**4.3 — Restaurar:** |
||||
|
|
||||
|
```bash |
||||
|
docker cp /tmp/origem.dump sapl-postgres:/tmp/origem.dump |
||||
|
docker exec sapl-postgres pg_restore -U sapl -d demo \ |
||||
|
--no-owner --no-privileges /tmp/origem.dump |
||||
|
docker exec sapl-postgres psql -U sapl -d demo -Atc \ |
||||
|
"select count(*) from information_schema.tables where table_schema='public';" |
||||
|
``` |
||||
|
|
||||
|
A contagem deve bater com a da origem (163 tabelas no `demo` em 13/08/2026). |
||||
|
|
||||
|
**4.4 — Apontar o `.env` para o banco local**, mantendo a URL remota comentada para voltar |
||||
|
depois: |
||||
|
|
||||
|
```env |
||||
|
# DATABASE_URL_REMOTO=postgresql://<usuario>:<senha>@demo.legisinc.com.br:5432/demo |
||||
|
DATABASE_URL=postgresql://sapl:sapl@sapl-postgres:5432/demo |
||||
``` |
``` |
||||
|
|
||||
## 4) Subir o servidor Django |
> Se preferir guardar um backup do `.env`, **não deixe o arquivo dentro do repositório**: |
||||
|
> o `.gitignore` cobre `sapl/.env`, mas não variações como `.env.bak`, e a senha do remoto |
||||
|
> vazaria no commit. Guarde fora da árvore do projeto. |
||||
|
|
||||
|
**4.5 — Conectar o container do SAPL à rede e aplicar a migration:** |
||||
|
|
||||
|
```bash |
||||
|
docker network connect sapl-net sapl-localhost |
||||
|
docker restart sapl-localhost |
||||
|
docker exec sapl-localhost python manage.py migrate |
||||
|
docker exec sapl-localhost python manage.py showmigrations | grep '\[ \]' # sem saída |
||||
|
``` |
||||
|
|
||||
|
Se o container ainda não existe, crie-o já com `--network sapl-net` (seção 5). |
||||
|
|
||||
|
**Para voltar ao banco remoto**: reponha a `DATABASE_URL` remota no `.env` e reinicie o |
||||
|
container. Os containers locais podem ficar parados (`docker stop sapl-postgres`) e serem |
||||
|
reaproveitados depois; para refazer a cópia do zero, `docker rm -f sapl-postgres` e repita |
||||
|
os passos 4.2 e 4.3. |
||||
|
|
||||
|
## 5) Subir o servidor Django |
||||
|
|
||||
|
### Docker |
||||
|
|
||||
|
```bash |
||||
|
docker run -d --name sapl-localhost -p 8001:8001 \ |
||||
|
-v "$PWD":/sapl-dev -w /sapl-dev sapl:localhost \ |
||||
|
python manage.py runserver 0.0.0.0:8001 |
||||
|
``` |
||||
|
|
||||
|
Se estiver usando a cópia local do banco (seção 4), acrescente `--network sapl-net` para o |
||||
|
container enxergar o `sapl-postgres`. |
||||
|
|
||||
|
O repositório é montado como volume, então o autoreload pega as edições no código. |
||||
|
|
||||
|
```bash |
||||
|
docker logs -f sapl-localhost # acompanhar o log |
||||
|
docker restart sapl-localhost # reiniciar |
||||
|
docker rm -f sapl-localhost # derrubar |
||||
|
``` |
||||
|
|
||||
|
### virtualenv |
||||
|
|
||||
```bash |
```bash |
||||
python manage.py runserver 0.0.0.0:8001 |
python manage.py runserver 0.0.0.0:8001 |
||||
``` |
``` |
||||
|
|
||||
Se preferir sem autoreload: `python manage.py runserver 0.0.0.0:8001 --noreload`. |
Se preferir sem autoreload: `python manage.py runserver 0.0.0.0:8001 --noreload`. |
||||
|
|
||||
|
## 6) Verificar |
||||
|
|
||||
|
```bash |
||||
|
curl -s -o /dev/null -w '%{http_code}\n' http://localhost:8001/ # 200 |
||||
|
curl -s http://localhost:8001/ | grep -o '<title>[^<]*</title>' |
||||
|
``` |
||||
|
|
||||
|
Acesse <http://localhost:8001> — o título deve ser |
||||
|
`SGVP - Câmara Municipal de Franco da Rocha`. Use os usuários já cadastrados no banco |
||||
|
(os mesmos do remoto, se você fez a cópia da seção 4) para logar. |
||||
|
|||||
@ -1 +1 @@ |
|||||
Subproject commit bce4eae59d594182a8b5451b509b141c9c0df416 |
Subproject commit 0290845521c9419992038a2499b3e8f6302b6e56 |
||||
@ -0,0 +1,33 @@ |
|||||
|
# Generated by Django 2.2.28 on 2026-08-18 14:13 |
||||
|
|
||||
|
from django.db import migrations, models |
||||
|
import django.db.models.deletion |
||||
|
import sapl.integracao_hub.models |
||||
|
|
||||
|
|
||||
|
class Migration(migrations.Migration): |
||||
|
|
||||
|
dependencies = [ |
||||
|
('materia', '0094_add_anexoproposicao'), |
||||
|
('integracao_hub', '0001_initial'), |
||||
|
] |
||||
|
|
||||
|
operations = [ |
||||
|
migrations.CreateModel( |
||||
|
name='AnexoProposicao', |
||||
|
fields=[ |
||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
||||
|
('arquivo', models.FileField(upload_to=sapl.integracao_hub.models._caminho_anexo, verbose_name='Arquivo')), |
||||
|
('nome_original', models.CharField(max_length=255, verbose_name='Nome original')), |
||||
|
('mime', models.CharField(blank=True, max_length=100, verbose_name='Tipo MIME')), |
||||
|
('tamanho_bytes', models.BigIntegerField(verbose_name='Tamanho (bytes)')), |
||||
|
('hash_sha256', models.CharField(max_length=64, verbose_name='SHA-256 dos bytes recebidos')), |
||||
|
('recebido_em', models.DateTimeField(auto_now_add=True, verbose_name='Recebido em')), |
||||
|
('proposicao', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='anexos_do_app', to='materia.Proposicao', verbose_name='Proposição')), |
||||
|
], |
||||
|
options={ |
||||
|
'verbose_name': 'Anexo de Proposição (app)', |
||||
|
'verbose_name_plural': 'Anexos de Proposição (app)', |
||||
|
}, |
||||
|
), |
||||
|
] |
||||
Loading…
Reference in new issue