mirror of https://github.com/interlegis/sapl.git
Browse Source
Performance-only slice extracted from feat/rate-limiter-2026 (commitfeat/sessao-relatorio-perf2f2a976df), which bundled query-optimization work in with unrelated rate-limiter/caching changes. - customize_link_materia() and the Ordem do Dia / Expediente ListViews now use prefetched querysets instead of per-row queries. - get_etiqueta_protocolos() batch-fetches MateriaLegislativa and DocumentoAdministrativo instead of querying per protocolo in a loop. - New migration redefines the materia_materiaemtramitacao view and adds a concurrent index (tram_materia_id_desc) backing the tramitacao prefetch. - Drops a redundant .distinct() + order_by on a related field in RelatorioMateriasTramitacaoFilterSet.qs. - Removes the unused painel_mensagem/parlamentar/votacao views and templates (also bundled into2f2a976dfby mistake). - Adds CLAUDE.md project documentation. Note: feat/painel-votacao-v2 still imports painel_mensagem_view, painel_parlamentar_view and painel_votacao_view. That branch will need reconciling with this removal when it's rebased onto 3.1.x. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
13 changed files with 709 additions and 597 deletions
@ -0,0 +1,145 @@ |
|||
# CLAUDE.md |
|||
|
|||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
|||
|
|||
## Project Overview |
|||
|
|||
SAPL (Sistema de Apoio ao Processo Legislativo) is a Django-based legislative management system used by Brazilian municipal and state legislative houses. It manages bills, parliamentary sessions, committees, norms, protocols, and related legislative workflows. |
|||
|
|||
## Commands |
|||
|
|||
### Development |
|||
|
|||
```bash |
|||
# Run dev server |
|||
python manage.py runserver |
|||
|
|||
# Docker (dev, without bundled DB) |
|||
docker-compose -f docker/docker-compose-dev.yml up |
|||
|
|||
# Docker (dev, with PostgreSQL container) |
|||
docker-compose -f docker/docker-compose-dev-db.yml up |
|||
``` |
|||
|
|||
### Database Setup (local PostgreSQL) |
|||
|
|||
```bash |
|||
sudo -u postgres psql -c "CREATE ROLE sapl LOGIN ENCRYPTED PASSWORD 'sapl' NOSUPERUSER INHERIT CREATEDB NOCREATEROLE NOREPLICATION;" |
|||
sudo -u postgres psql -c "CREATE DATABASE sapl WITH OWNER=sapl ENCODING='UTF8' LC_COLLATE='pt_BR.UTF-8' LC_CTYPE='pt_BR.UTF-8' CONNECTION LIMIT=-1 TEMPLATE template0;" |
|||
python manage.py migrate |
|||
``` |
|||
|
|||
### Testing |
|||
|
|||
```bash |
|||
# All tests (reuses DB by default for speed) |
|||
pytest |
|||
|
|||
# Single test file or test function |
|||
pytest sapl/materia/tests/test_materia.py |
|||
pytest sapl/materia/tests/test_materia.py::test_function_name |
|||
|
|||
# Force DB recreation |
|||
pytest --create-db |
|||
|
|||
# With coverage |
|||
pytest --cov=sapl |
|||
``` |
|||
|
|||
Tests require `DJANGO_SETTINGS_MODULE=sapl.settings` (set in `pytest.ini`). All tests must be marked with `@pytest.mark.django_db`. The `conftest.py` root fixture provides an `app` fixture (WebTest `DjangoTestApp`). |
|||
|
|||
### Linting / Formatting |
|||
|
|||
```bash |
|||
flake8 . |
|||
isort . |
|||
autopep8 --in-place <file.py> |
|||
``` |
|||
|
|||
### Restore Database from Backup |
|||
|
|||
```bash |
|||
./scripts/restore_db.sh -f /path/to/dump |
|||
./scripts/restore_db.sh -f /path/to/dump -p 5433 # Docker port |
|||
``` |
|||
|
|||
## Architecture |
|||
|
|||
### Django Apps |
|||
|
|||
Apps are under `sapl/` and follow domain boundaries: |
|||
|
|||
| App | Domain | |
|||
|-----|--------| |
|||
| `base` | `CasaLegislativa` (legislative house config), `AppConfig`, `Autor` (authorship) | |
|||
| `parliamentary` | `Parlamentar`, `Legislatura`, `SessaoLegislativa`, `Coligacao` | |
|||
| `materia` | Bills (`MateriaLegislativa`), types, tracking, annexes | |
|||
| `norma` | Laws/norms (`NormaJuridica`) and hierarchies | |
|||
| `sessao` | Plenary sessions, agenda, attendance, voting | |
|||
| `comissoes` | Committees (`Comissao`) and meetings (`Reuniao`) | |
|||
| `protocoloadm` | Administrative protocols and document intake | |
|||
| `compilacao` | Structured/articulated texts (LexML-like tree structure) | |
|||
| `lexml` | LexML XML standard integration | |
|||
| `audiencia` | Public hearings | |
|||
| `painel` | Real-time session display panel | |
|||
| `relatorios` | PDF report generation | |
|||
| `api` | REST API entry point (auto-generated ViewSets) | |
|||
| `crud` | Generic CRUD base views | |
|||
| `rules` | Business rules and permission definitions | |
|||
|
|||
### REST API |
|||
|
|||
The API uses a custom `drfautoapi` package (`drfautoapi/drfautoapi.py`) that auto-generates DRF ViewSets, Serializers, and FilterSets from Django models. Authentication is Token + Session. Permissions use a custom `SaplModelPermissions` class that maps HTTP methods to Django model permissions. |
|||
|
|||
OpenAPI 3.0 docs are generated by drf-spectacular. |
|||
|
|||
### Caching |
|||
|
|||
- **Default:** File-based (`/var/tmp/django_cache`) |
|||
- **Production:** Redis via django-redis; configured at startup by `configure_redis_cache()` in `sapl/settings.py` |
|||
- **Cache key prefix:** `cache:{POD_NAMESPACE}:` (namespace-isolated for multi-tenant k8s) |
|||
- **Rate limiter state** is shared via Redis keys |
|||
|
|||
### Feature Flags |
|||
|
|||
django-waffle is used for feature flags. Switches (global on/off) can be toggled via: |
|||
|
|||
```bash |
|||
python manage.py waffle_switch <switch_name> on|off |
|||
``` |
|||
|
|||
### Key Environment Variables |
|||
|
|||
| Variable | Purpose | |
|||
|----------|---------| |
|||
| `DATABASE_URL` | PostgreSQL connection string | |
|||
| `SECRET_KEY` | Django secret key | |
|||
| `DEBUG` | Debug mode | |
|||
| `REDIS_URL` | Redis host:port | |
|||
| `CACHE_BACKEND` | `file` or `redis` | |
|||
| `POD_NAMESPACE` | K8s namespace (used in cache key prefix) | |
|||
| `USE_SOLR` | Enable Haystack/Solr full-text search | |
|||
| `SOLR_URL` / `SOLR_COLLECTION` | Solr connection | |
|||
|
|||
### Docker Build |
|||
|
|||
The production build requires a MaxMind GeoLite2-ASN license key (for nginx ASN-based bot blocking): |
|||
|
|||
```bash |
|||
docker build --secret id=maxmind_key,src=.env -f docker/Dockerfile -t sapl:local . |
|||
``` |
|||
|
|||
Optional build args: `WITH_NGINX`, `WITH_GRAPHVIZ`, `WITH_POPPLER`, `WITH_PSQL_CLIENT`. |
|||
|
|||
### Key File Locations |
|||
|
|||
| File | Purpose | |
|||
|------|---------| |
|||
| `sapl/settings.py` | All Django settings, including cache/rate-limit setup | |
|||
| `pytest.ini` | Test configuration (DJANGO_SETTINGS_MODULE, addopts) | |
|||
| `conftest.py` | Root pytest fixtures | |
|||
| `drfautoapi/drfautoapi.py` | Auto-API generation logic | |
|||
| `docker/startup_scripts/start.sh` | Container entrypoint (migrations, waffle, gunicorn) | |
|||
| `requirements/requirements.txt` | Production deps | |
|||
| `requirements/test-requirements.txt` | Test deps | |
|||
| `requirements/dev-requirements.txt` | Dev/lint deps | |
|||
@ -0,0 +1,62 @@ |
|||
from django.db import migrations, models |
|||
|
|||
_OLD_VIEW = """ |
|||
create or replace view materia_materiaemtramitacao as |
|||
select m.id as id, |
|||
m.id as materia_id, |
|||
t.id as tramitacao_id, |
|||
t.unidade_tramitacao_destino_id as unidade_tramitacao_atual_id |
|||
from materia_materialegislativa m |
|||
inner join materia_tramitacao t on (m.id = t.materia_id) |
|||
where t.id = (select max(id) from materia_tramitacao where materia_id = m.id) |
|||
order by m.id DESC |
|||
""" |
|||
|
|||
_NEW_VIEW = """ |
|||
create or replace view materia_materiaemtramitacao as |
|||
select distinct on (m.id) |
|||
m.id as id, |
|||
m.id as materia_id, |
|||
t.id as tramitacao_id, |
|||
t.unidade_tramitacao_destino_id as unidade_tramitacao_atual_id |
|||
from materia_materialegislativa m |
|||
inner join materia_tramitacao t on t.materia_id = m.id |
|||
order by m.id desc, t.id desc |
|||
""" |
|||
|
|||
|
|||
class Migration(migrations.Migration): |
|||
# CREATE INDEX CONCURRENTLY cannot run inside a transaction. |
|||
atomic = False |
|||
|
|||
dependencies = [ |
|||
('materia', '0087_update_viewdb_materiaemtramitacao'), |
|||
] |
|||
|
|||
operations = [ |
|||
migrations.RunSQL(sql=_NEW_VIEW, reverse_sql=_OLD_VIEW), |
|||
migrations.SeparateDatabaseAndState( |
|||
database_operations=[ |
|||
migrations.RunSQL( |
|||
sql=""" |
|||
CREATE INDEX CONCURRENTLY IF NOT EXISTS |
|||
tram_materia_id_desc |
|||
ON materia_tramitacao (materia_id, id DESC) |
|||
""", |
|||
reverse_sql=""" |
|||
DROP INDEX CONCURRENTLY IF EXISTS |
|||
tram_materia_id_desc |
|||
""", |
|||
), |
|||
], |
|||
state_operations=[ |
|||
migrations.AddIndex( |
|||
model_name='tramitacao', |
|||
index=models.Index( |
|||
fields=['materia', '-id'], |
|||
name='tram_materia_id_desc', |
|||
), |
|||
), |
|||
], |
|||
), |
|||
] |
|||
@ -1,120 +0,0 @@ |
|||
{% load i18n %} |
|||
{% load common_tags %} |
|||
{% load render_bundle from webpack_loader %} |
|||
{% load webpack_static from webpack_loader %} |
|||
|
|||
<!DOCTYPE HTML> |
|||
<!--[if IE 8]> <html class="no-js lt-ie9" lang="pt-br"> <![endif]--> |
|||
<!--[if gt IE 8]><!--> |
|||
<html lang="pt-br"> |
|||
<!--<![endif]--> |
|||
|
|||
<head> |
|||
<meta charset="UTF-8"> |
|||
<!-- TODO: does it need this head_title here? --> |
|||
<title>{% block head_title %}{% trans 'SAPL - Sistema de Apoio ao Processo Legislativo' %}{% endblock %}</title> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|||
|
|||
{% render_chunk_vendors 'css' %} |
|||
{% render_bundle 'global' 'css' %} |
|||
{% render_bundle 'painel' 'css' %} |
|||
|
|||
|
|||
<STYLE type="text/css"> |
|||
@media screen { |
|||
body {font-size: medium; color: white; line-height: 1em; background: black;} |
|||
} |
|||
</STYLE> |
|||
|
|||
</head> |
|||
<body> |
|||
<h1>{{ context.title }}</h1> |
|||
<input id="json_url" type="hidden" value="{% url 'sapl.painel:dados_painel' %}"> |
|||
<h2>Ajax refresh counter: <span id="counter"></span></h2> |
|||
<h3> |
|||
<span id="sessao_plenaria"></span><br/><br/> |
|||
<span id="sessao_plenaria_data"></span><br/><br/> |
|||
<span id="sessao_plenaria_hora_inicio"></span></br><br/> |
|||
<h2><span id="relogio"></span></h2></br><br/><br/> |
|||
<span id="materia_legislativa_texto"></span><br/> |
|||
<span id="observacao_materia"></span> |
|||
</h3> |
|||
</body> |
|||
|
|||
{% render_chunk_vendors 'js' %} |
|||
{% render_bundle 'global' 'js' %} |
|||
{% render_bundle 'painel' 'js' %} |
|||
|
|||
<script type="text/javascript"> |
|||
$(document).ready(function() { |
|||
|
|||
//TODO: replace by a fancy jQuery clock |
|||
function checkTime(i) { |
|||
if (i<10) {i = "0" + i}; // add zero in front of numbers < 10 |
|||
return i; |
|||
} |
|||
function startTime() { |
|||
var today=new Date(); |
|||
var h=today.getHours(); |
|||
var m=today.getMinutes(); |
|||
var s=today.getSeconds(); |
|||
m = checkTime(m); |
|||
s = checkTime(s); |
|||
$("#relogio").text(h+":"+m+":"+s) |
|||
var t = setTimeout(function(){ |
|||
startTime() |
|||
},500); |
|||
} |
|||
|
|||
startTime(); |
|||
|
|||
var counter = 1; |
|||
(function poll() { |
|||
$.ajax({ |
|||
url: $("#json_url").val(), |
|||
type: "GET", |
|||
success: function(data) { |
|||
|
|||
//TODO: json spitted out is very complex, have to simplify/flat it |
|||
//TODO: probably building it by hand on REST side |
|||
|
|||
console.debug(data) |
|||
|
|||
var presentes = $("#parlamentares"); |
|||
presentes.children().remove(); |
|||
|
|||
presentes_ordem_dia = data.presentes_ordem_dia |
|||
$.each(presentes_ordem_dia, function(index, parlamentar) { |
|||
$('<li />', {text: parlamentar.nome + '/' + parlamentar.partido + ' ' + parlamentar.voto }).appendTo(presentes); |
|||
}); |
|||
|
|||
var votacao = $("#votacao") |
|||
votacao.children().remove() |
|||
votacao.append("<li>Sim: " + data["numero_votos_sim"] + "</li>") |
|||
votacao.append("<li>Não: " + data["numero_votos_nao"] + "</li>") |
|||
votacao.append("<li>Abstenções: " + data["numero_abstencoes"] + "</li>") |
|||
votacao.append("<li>Presentes: " + data["presentes"] + "</li>") |
|||
votacao.append("<li>Total votos: " + data["total_votos"] + "</li>") |
|||
|
|||
$("#sessao_plenaria").text(data["sessao_plenaria"]) |
|||
$("#sessao_plenaria_data").text("Data Início: " + data["sessao_plenaria_data"]) |
|||
$("#sessao_plenaria_hora_inicio").text("Hora Início: " + data["sessao_plenaria_hora_inicio"]) |
|||
|
|||
$("#materia_legislativa_texto").text(data["materia_legislativa_texto"]) |
|||
$("#observacao_materia").text(data["observacao_materia"]) |
|||
$("#resultado_votacao").text(data["tipo_resultado"]) |
|||
|
|||
$("#counter").text(counter); |
|||
counter++; |
|||
}, |
|||
error: function(err) { |
|||
console.error(err); |
|||
}, |
|||
dataType: "json", |
|||
//complete: setTimeout(function() {poll()}, 5000), |
|||
timeout: 20000 // TODO: decrease |
|||
}) |
|||
})(); |
|||
}); |
|||
</script> |
|||
</html> |
|||
@ -1,128 +0,0 @@ |
|||
{% load i18n %} |
|||
{% load common_tags %} |
|||
|
|||
{% load render_bundle from webpack_loader %} |
|||
{% load webpack_static from webpack_loader %} |
|||
|
|||
<!DOCTYPE HTML> |
|||
<!--[if IE 8]> <html class="no-js lt-ie9" lang="pt-br"> <![endif]--> |
|||
<!--[if gt IE 8]><!--> |
|||
<html lang="pt-br"> |
|||
<!--<![endif]--> |
|||
|
|||
<head> |
|||
<meta charset="UTF-8"> |
|||
<!-- TODO: does it need this head_title here? --> |
|||
<title>{% block head_title %}{% trans 'SAPL - Sistema de Apoio ao Processo Legislativo' %}{% endblock %}</title> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|||
|
|||
{% render_chunk_vendors 'css' %} |
|||
{% render_bundle 'global' 'css' %} |
|||
{% render_bundle 'painel' 'css' %} |
|||
|
|||
|
|||
|
|||
<STYLE type="text/css"> |
|||
@media screen { |
|||
body {font-size: medium; color: white; line-height: 1em; background: black;} |
|||
} |
|||
</STYLE> |
|||
|
|||
</head> |
|||
<body> |
|||
<h1>{{ context.title }}</h1> |
|||
<input id="json_url" type="hidden" value="{% url 'sapl.painel:dados_painel' %}"> |
|||
<h3> |
|||
<span id="sessao_plenaria"></span><br/><br/> |
|||
<span id="sessao_plenaria_data"></span><br/><br/> |
|||
<span id="sessao_plenaria_hora_inicio"></span></br><br/> |
|||
<h2><span id="relogio"></span></h2> |
|||
<table> |
|||
<tr> |
|||
<td> |
|||
<ul id="parlamentares"> |
|||
</ul> |
|||
</td> |
|||
</tr> |
|||
</table> |
|||
</h3> |
|||
</body> |
|||
|
|||
{% render_chunk_vendors 'js' %} |
|||
{% render_bundle 'global' 'js' %} |
|||
{% render_bundle 'painel' 'js' %} |
|||
|
|||
<script type="text/javascript"> |
|||
$(document).ready(function() { |
|||
|
|||
//TODO: replace by a fancy jQuery clock |
|||
function checkTime(i) { |
|||
if (i<10) {i = "0" + i}; // add zero in front of numbers < 10 |
|||
return i; |
|||
} |
|||
function startTime() { |
|||
var today=new Date(); |
|||
var h=today.getHours(); |
|||
var m=today.getMinutes(); |
|||
var s=today.getSeconds(); |
|||
m = checkTime(m); |
|||
s = checkTime(s); |
|||
$("#relogio").text(h+":"+m+":"+s) |
|||
var t = setTimeout(function(){ |
|||
startTime() |
|||
},500); |
|||
} |
|||
|
|||
startTime(); |
|||
|
|||
var counter = 1; |
|||
(function poll() { |
|||
$.ajax({ |
|||
url: $("#json_url").val(), |
|||
type: "GET", |
|||
success: function(data) { |
|||
|
|||
//TODO: json spitted out is very complex, have to simplify/flat it |
|||
//TODO: probably building it by hand on REST side |
|||
|
|||
console.debug(data) |
|||
|
|||
var presentes = $("#parlamentares"); |
|||
presentes.children().remove(); |
|||
|
|||
presentes_ordem_dia = data.presentes_ordem_dia |
|||
$.each(presentes_ordem_dia, function(index, parlamentar) { |
|||
$('<li />', {text: parlamentar.nome + '/' + parlamentar.partido }).appendTo(presentes); |
|||
/*$('<li />', {text: parlamentar.nome + '/' + parlamentar.partido + ' ' + parlamentar.voto }).appendTo(presentes);*/ |
|||
}); |
|||
|
|||
var votacao = $("#votacao") |
|||
votacao.children().remove() |
|||
votacao.append("<li>Sim: " + data["numero_votos_sim"] + "</li>") |
|||
votacao.append("<li>Não: " + data["numero_votos_nao"] + "</li>") |
|||
votacao.append("<li>Abstenções: " + data["numero_abstencoes"] + "</li>") |
|||
votacao.append("<li>Presentes: " + data["presentes"] + "</li>") |
|||
votacao.append("<li>Total votos: " + data["total_votos"] + "</li>") |
|||
|
|||
$("#sessao_plenaria").text(data["sessao_plenaria"]) |
|||
$("#sessao_plenaria_data").text("Data Início: " + data["sessao_plenaria_data"]) |
|||
$("#sessao_plenaria_hora_inicio").text("Hora Início: " + data["sessao_plenaria_hora_inicio"]) |
|||
|
|||
$("#materia_legislativa_texto").text(data["materia_legislativa_texto"]) |
|||
$("#observacao_materia").text(data["observacao_materia"]) |
|||
$("#resultado_votacao").text(data["tipo_resultado"]) |
|||
|
|||
$("#counter").text(counter); |
|||
counter++; |
|||
}, |
|||
error: function(err) { |
|||
console.error(err); |
|||
}, |
|||
dataType: "json", |
|||
//complete: setTimeout(function() {poll()}, 5000), |
|||
timeout: 20000 // TODO: decrease |
|||
}) |
|||
})(); |
|||
}); |
|||
</script> |
|||
</html> |
|||
@ -1,123 +0,0 @@ |
|||
{% load i18n %} |
|||
{% load render_bundle from webpack_loader %} |
|||
{% load webpack_static from webpack_loader %} |
|||
|
|||
<!DOCTYPE HTML> |
|||
<!--[if IE 8]> <html class="no-js lt-ie9" lang="pt-br"> <![endif]--> |
|||
<!--[if gt IE 8]><!--> |
|||
<html lang="pt-br"> |
|||
<!--<![endif]--> |
|||
|
|||
<head> |
|||
<meta charset="UTF-8"> |
|||
<!-- TODO: does it need this head_title here? --> |
|||
<title>{% block head_title %}{% trans 'SAPL - Sistema de Apoio ao Processo Legislativo' %}{% endblock %}</title> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|||
|
|||
{% render_chunk_vendors 'css' %} |
|||
{% render_bundle 'global' 'css' %} |
|||
{% render_bundle 'painel' 'css' %} |
|||
|
|||
<STYLE type="text/css"> |
|||
@media screen { |
|||
body {font-size: medium; color: white; line-height: 1em; background: black;} |
|||
} |
|||
</STYLE> |
|||
</head> |
|||
<body> |
|||
<h1>{{ context.title }}</h1> |
|||
<input id="json_url" type="hidden" value="{% url 'sapl.painel:dados_painel' %}"> |
|||
<h3> |
|||
<span id="sessao_plenaria"></span><br/><br/> |
|||
<span id="sessao_plenaria_data"></span><br/><br/> |
|||
<span id="sessao_plenaria_hora_inicio"></span></br><br/> |
|||
<h2><span id="relogio"></span></h2> |
|||
<table> |
|||
<tr> |
|||
<td> |
|||
<ul id="votacao"> |
|||
</ul> |
|||
</td> |
|||
</tr> |
|||
</table> |
|||
<span id="resultado_votacao"></span><br/> |
|||
</h3> |
|||
</body> |
|||
|
|||
{% render_chunk_vendors 'js' %} |
|||
{% render_bundle 'global' 'js' %} |
|||
{% render_bundle 'painel' 'js' %} |
|||
|
|||
<script type="text/javascript"> |
|||
$(document).ready(function() { |
|||
|
|||
//TODO: replace by a fancy jQuery clock |
|||
function checkTime(i) { |
|||
if (i<10) {i = "0" + i}; // add zero in front of numbers < 10 |
|||
return i; |
|||
} |
|||
function startTime() { |
|||
var today=new Date(); |
|||
var h=today.getHours(); |
|||
var m=today.getMinutes(); |
|||
var s=today.getSeconds(); |
|||
m = checkTime(m); |
|||
s = checkTime(s); |
|||
$("#relogio").text(h+":"+m+":"+s) |
|||
var t = setTimeout(function(){ |
|||
startTime() |
|||
},500); |
|||
} |
|||
|
|||
startTime(); |
|||
|
|||
var counter = 1; |
|||
(function poll() { |
|||
$.ajax({ |
|||
url: $("#json_url").val(), |
|||
type: "GET", |
|||
success: function(data) { |
|||
|
|||
//TODO: json spitted out is very complex, have to simplify/flat it |
|||
//TODO: probably building it by hand on REST side |
|||
|
|||
console.debug(data) |
|||
|
|||
var presentes = $("#parlamentares"); |
|||
presentes.children().remove(); |
|||
|
|||
presentes_ordem_dia = data.presentes_ordem_dia |
|||
$.each(presentes_ordem_dia, function(index, parlamentar) { |
|||
$('<li />', {text: parlamentar.nome + '/' + parlamentar.partido + ' ' + parlamentar.voto }).appendTo(presentes); |
|||
}); |
|||
|
|||
var votacao = $("#votacao") |
|||
votacao.children().remove() |
|||
votacao.append("<li>Sim: " + data["numero_votos_sim"] + "</li>") |
|||
votacao.append("<li>Não: " + data["numero_votos_nao"] + "</li>") |
|||
votacao.append("<li>Abstenções: " + data["numero_abstencoes"] + "</li>") |
|||
votacao.append("<li>Presentes: " + data["presentes"] + "</li>") |
|||
votacao.append("<li>Total votos: " + data["total_votos"] + "</li>") |
|||
|
|||
$("#sessao_plenaria").text(data["sessao_plenaria"]) |
|||
$("#sessao_plenaria_data").text("Data Início: " + data["sessao_plenaria_data"]) |
|||
$("#sessao_plenaria_hora_inicio").text("Hora Início: " + data["sessao_plenaria_hora_inicio"]) |
|||
|
|||
$("#materia_legislativa_texto").text(data["materia_legislativa_texto"]) |
|||
$("#observacao_materia").text(data["observacao_materia"]) |
|||
$("#resultado_votacao").text(data["tipo_resultado"]) |
|||
|
|||
$("#counter").text(counter); |
|||
counter++; |
|||
}, |
|||
error: function(err) { |
|||
console.error(err); |
|||
}, |
|||
dataType: "json", |
|||
//complete: setTimeout(function() {poll()}, 5000), |
|||
timeout: 20000 // TODO: decrease |
|||
}) |
|||
})(); |
|||
}); |
|||
</script> |
|||
</html> |
|||
Loading…
Reference in new issue