mirror of https://github.com/interlegis/sapl.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
200 lines
7.8 KiB
200 lines
7.8 KiB
import json
|
|
import json
|
|
import logging
|
|
import time
|
|
|
|
from channels.db import database_sync_to_async
|
|
from channels.generic.websocket import AsyncWebsocketConsumer, AsyncJsonWebsocketConsumer
|
|
|
|
from sapl.base.models import CasaLegislativa, AppConfig
|
|
from sapl.sessao.models import SessaoPlenaria, SessaoPresencaView, \
|
|
SessaoOradorView, SessaoMateriaVotacaoView
|
|
|
|
|
|
def get_materia_votacao(votacao):
|
|
return {
|
|
# TOO UGLY! FIX THIS if-else
|
|
"materia_id": votacao.materia.id if votacao and votacao.materia else "",
|
|
"texto": str(votacao.materia) if votacao and votacao.materia else "",
|
|
"ementa": votacao.materia.ementa if votacao and votacao.materia and votacao.materia.ementa else "",
|
|
"resultado": {
|
|
"resultado_votacao": votacao.resultado_votacao if votacao and votacao.resultado_votacao else "",
|
|
"resultado": votacao.resultado if votacao and votacao.resultado else "",
|
|
"numero_votos": votacao.numero_votos if votacao and votacao.numero_votos else {},
|
|
"votos_parlamentares": votacao.votos_parlamentares if votacao and votacao.votos_parlamentares else [],
|
|
},
|
|
}
|
|
|
|
|
|
def get_dados_painel(sessao_plenaria_id: int) -> dict:
|
|
app_config = AppConfig.objects.first()
|
|
sessao = SessaoPlenaria.objects.get(id=sessao_plenaria_id)
|
|
casa = CasaLegislativa.objects.first()
|
|
brasao = casa.logotipo.url \
|
|
if app_config.mostrar_brasao_painel else None
|
|
|
|
# Painel
|
|
# TODO: recuperar outra matéria quando não existir nenhuma materia_votacao aberta!
|
|
materia_votacao = SessaoMateriaVotacaoView.objects. \
|
|
filter(sessao_plenaria_id=sessao_plenaria_id, votacao_aberta=True).first()
|
|
|
|
if not materia_votacao:
|
|
return {
|
|
"type": "data",
|
|
"sessao_aberta": sessao.iniciada and not sessao.finalizada,
|
|
"painel_aberto": sessao.painel_aberto,
|
|
"mostrar_voto": app_config.mostrar_voto,
|
|
"message": "PAINEL ENCONTRA-SE FECHADO" if not sessao.painel_aberto else "",
|
|
"sessao": {
|
|
"sessao_plenaria_id": sessao.id,
|
|
"brasao": brasao,
|
|
"sessao_plenaria": str(sessao),
|
|
"sessao_plenaria_data": sessao.data_inicio.strftime("%d/%m/%Y"),
|
|
"sessao_plenaria_hora_inicio": sessao.hora_inicio,
|
|
"sessao_solene": sessao.tipo.nome == "Solene",
|
|
"sessao_finalizada": sessao.finalizada,
|
|
"tema_solene": sessao.tema_solene,
|
|
},
|
|
"message": "Nenhuma matéria aberta para votação!",
|
|
}
|
|
|
|
presentes = SessaoPresencaView.objects.filter(sessao_plenaria_id=sessao_plenaria_id,
|
|
etapa_sessao=materia_votacao.etapa_sessao).values_list(
|
|
'parlamentar_id',
|
|
'nome_parlamentar',
|
|
'filiacao', )
|
|
parlamentares = [dict(zip(['parlamentar_id', 'nome_parlamentar', 'filiacao'], p)) for p in presentes]
|
|
if materia_votacao and materia_votacao.numero_votos:
|
|
materia_votacao.numero_votos.update({"num_presentes": len(parlamentares)})
|
|
|
|
oradores = SessaoOradorView.objects.filter(sessao_plenaria_id=sessao_plenaria_id,
|
|
etapa_sessao=materia_votacao.etapa_sessao).values_list(
|
|
'ordem_pronunciamento',
|
|
'nome_parlamentar',
|
|
)
|
|
oradores = [dict(zip(['ordem_pronunciamento', 'nome_parlamentar'], o)) for o in oradores]
|
|
|
|
# TODO: recover stopwatch state from DB/Cache
|
|
stopwatch = {
|
|
"type": "stopwatch.state",
|
|
"id": "sw:main",
|
|
"status": "running", # "running" | "paused" | "stopped"
|
|
"started_at_ms": 1699990000123, # epoch ms when (re)started
|
|
"elapsed_ms": 5320
|
|
}
|
|
|
|
dados_sessao = {
|
|
"type": "data",
|
|
"sessao_aberta": sessao.iniciada and not sessao.finalizada,
|
|
"painel_aberto": sessao.painel_aberto,
|
|
"mostrar_voto": app_config.mostrar_voto,
|
|
"message": "PAINEL ENCONTRA-SE FECHADO" if not sessao.painel_aberto else "",
|
|
"sessao": {
|
|
"sessao_plenaria_id": sessao.id,
|
|
"brasao": brasao,
|
|
"sessao_plenaria": str(sessao),
|
|
"sessao_plenaria_data": sessao.data_inicio.strftime("%d/%m/%Y"),
|
|
"sessao_plenaria_hora_inicio": sessao.hora_inicio,
|
|
"sessao_solene": sessao.tipo.nome == "Solene",
|
|
"sessao_finalizada": sessao.finalizada,
|
|
"tema_solene": sessao.tema_solene,
|
|
},
|
|
"parlamentares": parlamentares,
|
|
"oradores": oradores,
|
|
"materia": get_materia_votacao(materia_votacao),
|
|
"stopwatch": [stopwatch], # TODO: array of stopwatches
|
|
}
|
|
|
|
print(json.dumps(dados_sessao, indent=4))
|
|
return dados_sessao
|
|
|
|
|
|
class PainelConsumer(AsyncJsonWebsocketConsumer):
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# def __init__(self):
|
|
# self.group = set()
|
|
# self.controller_id = None
|
|
|
|
async def connect(self):
|
|
# TODO: transformar prints em log messages
|
|
user = self.scope.get("user")
|
|
controller_id = self.scope["url_route"]["kwargs"]["controller_id"]
|
|
print(f"user: {user}, controller_id: {controller_id}")
|
|
|
|
if not (user and user.is_authenticated):
|
|
self.logger.info(f"{user} is not authenticated user!")
|
|
await self.close(code=4401) # explicit, graceful close
|
|
return
|
|
|
|
if not await self.user_can_view(user.id, controller_id):
|
|
await self.close(code=4403)
|
|
return
|
|
|
|
self.controller_id = controller_id
|
|
self.group = f"controller_{controller_id}"
|
|
print(self.group)
|
|
await self.channel_layer.group_add(self.group, self.channel_name)
|
|
|
|
await self.accept()
|
|
# await self.send_json({"type": "data",
|
|
# "text": "Connection established!"})
|
|
|
|
print("SENDING bootstrap DATA DO CONSUMER ")
|
|
print(get_dados_painel(controller_id))
|
|
await self.send_json(get_dados_painel(controller_id))
|
|
|
|
async def disconnect(self, code):
|
|
if hasattr(self, "group"):
|
|
await self.channel_layer.group_discard(self.group, self.channel_name)
|
|
|
|
# Called by server via channel_layer.group_send
|
|
# async def notify(self, event):
|
|
# # event: {"type": "notify", "data": {...}}
|
|
# await self.send_json(event)
|
|
|
|
async def receive(self, text_data=None, bytes_data=None):
|
|
data = json.loads(text_data or "{}")
|
|
print("Received from client:", data) # TODO: turn into log messages
|
|
msg_type = data.get("type")
|
|
if msg_type == "ping":
|
|
print("PING")
|
|
await self.send_json({"type": "ping", "ts": time.time()})
|
|
return
|
|
elif msg_type == "echo":
|
|
await self.send_json(data)
|
|
return
|
|
elif msg_type == "stopwatch.state":
|
|
print(data)
|
|
return
|
|
await self.send_json({"type": "error", "message": "Misformed message"})
|
|
return
|
|
|
|
async def stopwatch_update(self, event):
|
|
await self.send_json(event)
|
|
return
|
|
|
|
@database_sync_to_async
|
|
def user_can_view(self, user_id, controller_id) -> bool:
|
|
# Replace with your ACL check (ORM must be in a sync wrapper)
|
|
# return Controller.objects.filter(id=controller_id, owners__id=user_id).exists()
|
|
return True
|
|
|
|
|
|
class HealthConsumer(AsyncWebsocketConsumer):
|
|
"""
|
|
WebSockets consumer that doesn"t require authentication to
|
|
debug with wscat (wscat -c ws://127.0.0.1:8000/ws/painel/)
|
|
"""
|
|
logger = logging.getLogger(__name__)
|
|
|
|
async def connect(self):
|
|
try:
|
|
await self.accept()
|
|
await self.send(json.dumps({"ok": True}))
|
|
except Exception as e:
|
|
self.logger.exception("connect failed: %s", e)
|
|
# Let Channels close with 1011 if we got here
|
|
|
|
async def receive(self, text_data=None, bytes_data=None):
|
|
await self.send(text_data or "")
|
|
|