mirror of https://github.com/interlegis/sigi.git
21 changed files with 827 additions and 169 deletions
@ -1,21 +1,30 @@ |
|||
from django.core.management.base import BaseCommand |
|||
from sigi.apps.eventos.models import Evento |
|||
from django.utils import timezone |
|||
from sigi.apps.eventos.models import Evento |
|||
from sigi.apps.eventos.saberes import SaberesSyncException |
|||
|
|||
|
|||
class Command(BaseCommand): |
|||
help = "Carrega dados de participantes de eventos do Moodle para o SIGI" |
|||
|
|||
def handle(self, *args, **options): |
|||
for evento in Evento.objects.exclude(moodle_courseid=None).filter( |
|||
eventos = Evento.objects.exclude(moodle_courseid=None).filter( |
|||
data_termino__lt=timezone.localtime() |
|||
): |
|||
) |
|||
self.stdout.write(f"Processando {eventos.count()} eventos:") |
|||
counter = 0 |
|||
for evento in eventos: |
|||
counter += 1 |
|||
try: |
|||
evento.sincroniza_saberes() |
|||
self.stdout.write( |
|||
self.style.SUCCESS(f"✔ {evento.nome} sincronizado.") |
|||
self.style.SUCCESS( |
|||
f"✔ {counter}: {evento.nome} sincronizado." |
|||
) |
|||
) |
|||
except Evento.SaberesSyncException as err: |
|||
except SaberesSyncException as err: |
|||
self.stdout.write( |
|||
self.style.ERROR(f"✖ {evento.nome}: {err.message}") |
|||
self.style.ERROR( |
|||
f"✖ {counter}: {evento.nome}: {err.message}" |
|||
) |
|||
) |
|||
|
@ -0,0 +1,80 @@ |
|||
# Generated by Django 5.2.1 on 2025-08-29 14:08 |
|||
|
|||
import django.db.models.deletion |
|||
import tinymce.models |
|||
from django.db import migrations, models |
|||
|
|||
|
|||
class Migration(migrations.Migration): |
|||
|
|||
dependencies = [ |
|||
("casas", "0027_alter_orgao_email"), |
|||
("eventos", "0063_participantesevento_and_more"), |
|||
] |
|||
|
|||
operations = [ |
|||
migrations.AlterField( |
|||
model_name="modelodeclaracao", |
|||
name="texto", |
|||
field=tinymce.models.HTMLField( |
|||
help_text="Use as seguintes marcações:<ul><li>{{ casa.nome }} para o nome da Casa Legislativa / órgão</li><li>{{ casa.municipio.uf.sigla }} para a sigla da UF da Casa legislativa</li><li>{{ participante.cpf }} para o CPF do visitante</li><li>{{ participante.nome }} para o nome do visitante</li><li>{{ participante.email }} para o e-mail do visitante</li><li>{{ participante.local_trabalho }} para o cargo / função / local de trabalho do visitante</li><li>{{ data }} para a data de emissão da declaração</li><li>{{ evento.data_inicio }} para a data/hora do início da visita</li><li>{{ evento.data_termino }} para a data/hora do término da visita</li><li>{{ evento.nome }} para o nome do evento</li><li>{{ evento.descricao }} para a descrição do evento</li><li>{% include 'eventos/snippets/comitiva.html' %} para a tabela com toda a comitiva da visita</li></ul>", |
|||
verbose_name="Texto da declaração", |
|||
), |
|||
), |
|||
migrations.CreateModel( |
|||
name="Participante", |
|||
fields=[ |
|||
( |
|||
"id", |
|||
models.BigAutoField( |
|||
auto_created=True, |
|||
primary_key=True, |
|||
serialize=False, |
|||
verbose_name="ID", |
|||
), |
|||
), |
|||
("cpf", models.CharField(max_length=30, verbose_name="CPF")), |
|||
( |
|||
"nome", |
|||
models.CharField( |
|||
max_length=100, verbose_name="nome completo" |
|||
), |
|||
), |
|||
( |
|||
"email", |
|||
models.EmailField( |
|||
blank=True, max_length=254, verbose_name="e-mail" |
|||
), |
|||
), |
|||
( |
|||
"local_trabalho", |
|||
models.TextField( |
|||
blank=True, verbose_name="local de trabalho / cargo" |
|||
), |
|||
), |
|||
( |
|||
"casa_legislativa", |
|||
models.ForeignKey( |
|||
blank=True, |
|||
null=True, |
|||
on_delete=django.db.models.deletion.SET_NULL, |
|||
to="casas.orgao", |
|||
verbose_name="casa legislativa", |
|||
), |
|||
), |
|||
( |
|||
"evento", |
|||
models.ForeignKey( |
|||
on_delete=django.db.models.deletion.CASCADE, |
|||
to="eventos.evento", |
|||
verbose_name="evento", |
|||
), |
|||
), |
|||
], |
|||
options={ |
|||
"verbose_name": "participante", |
|||
"verbose_name_plural": "participantes", |
|||
"ordering": ("casa_legislativa", "nome"), |
|||
}, |
|||
), |
|||
] |
@ -0,0 +1,168 @@ |
|||
# Generated by Django 5.2.1 on 2025-08-28 01:37 |
|||
import re |
|||
from django.db import migrations |
|||
|
|||
|
|||
def forward(apps, schema_editor): |
|||
# Monta regexps # |
|||
cargos = r"|".join( |
|||
[ |
|||
"AUXILIAR LEGISLATIVA", |
|||
"Acessor Parlamentar", |
|||
"Administrador", |
|||
"Agente Administrativo de Eventos", |
|||
"Agente Legislativo", |
|||
"Analista de Revisão Pessoal", |
|||
"Analista de TI", |
|||
"Analista jurídico", |
|||
"Assessora Especial da Secretaria Legislativa e Presidente da Comissão de Regulamentação de Cargos", |
|||
"ASSESSOR JURÍDICO DA PRESIDÊNCIA", |
|||
"Assessor Jurídico", |
|||
"Assessor Legislativo", |
|||
"Assessor Parlamentar", |
|||
"Assessor de Comunicação", |
|||
"Assessora Jurídica", |
|||
"Assessora", |
|||
"Assessor", |
|||
"Auxiliar Administrativa", |
|||
"Chefe de Gabinete da Presidência", |
|||
"Chefe de Gabinete", |
|||
"Controlador Geral da Câmara", |
|||
"Controlador Interno", |
|||
"Controlador", |
|||
"Coord do Departamento Legislativo", |
|||
"Coordenador de Tecnologia da Informação", |
|||
"Coordenador do Departamento Legislativo da Câmara", |
|||
"Coordenadora", |
|||
"Coordenadora da Escola do Parlamento", |
|||
"DIRETOR GERAL", |
|||
"Deputado", |
|||
"Diretor Administrativo", |
|||
"Diretor", |
|||
"Diretor Geral", |
|||
"Diretor Hospital", |
|||
"Diretor Legislativo", |
|||
"Diretor Tesoureiro", |
|||
"Diretor de Compras e Licitações", |
|||
"Diretor de Matérias e Protocolo", |
|||
"Diretor de TI", |
|||
"Diretor de Tecnologia e Informação", |
|||
"Diretor do Departamento Legislativo", |
|||
"Diretor do Legislativo", |
|||
"Diretora Administrativa", |
|||
"Diretora", |
|||
"Diretora Legislativa", |
|||
"Diretora da Escola do Legislativo", |
|||
"Diretora geral da Câmara", |
|||
"Motorista", |
|||
"O Subprocurador Geral", |
|||
"Ouvidor", |
|||
"Ouvidor Geral", |
|||
"PRESIDENTE", |
|||
"Prefeito", |
|||
"Presidente", |
|||
"Presidente Vereador", |
|||
"Presidente Vereadora", |
|||
"Presidente da Escola do Legislativo", |
|||
"Presidente do InGEPE", |
|||
"Primeira Secretária", |
|||
"Procurador Geral", |
|||
"Procurador", |
|||
"Procuradora", |
|||
"Secretária", |
|||
"Secretária Saúde", |
|||
"Secretária da Casa Civil", |
|||
"Secretária-Geral", |
|||
"Secretário Administrativo", |
|||
"Secretário", |
|||
"Secretário de Administração", |
|||
"Secretário de Planejamento", |
|||
"Secretário de Saúde", |
|||
"Secretário-geral da Câmara", |
|||
"Servidor", |
|||
"Servidora", |
|||
"Tesoureiro", |
|||
"Técnica Administrativa", |
|||
"Técnico Legislativo da Secretaria Legislativa e Presidente da Comissão Sistêmica de Sustentabilidade Legislativa", |
|||
"Técnico Legislativo", |
|||
"VEREADOR", |
|||
"Verador", |
|||
"Vereadora", |
|||
"Vice Prefeita", |
|||
"Vice Prefeito", |
|||
"Vice Presidente", |
|||
"Vice-Prefeito", |
|||
"a Vereadora", |
|||
"o Assessor Jurídico", |
|||
"o Presidente", |
|||
"o Servidor", |
|||
"o Vereador", |
|||
] |
|||
) |
|||
patterns = [ |
|||
re.compile(p, re.IGNORECASE) |
|||
for p in [ |
|||
r"(?P<nome>.+?)[,?][ ?]inscrit[o|a] no CPF[ ?](?P<cpf>.+?)[,?][ ?](?P<local_trabalho>.+)", |
|||
r"(?P<nome>.+?)[ ]*-[ ]*(?P<local_trabalho>.+)", |
|||
r"(?P<local_trabalho>.+?)( *):( *)(?P<nome>.+)", |
|||
r"(?P<nome>.+?)( *);( *)[cpf?][ *](?P<cpf>.+)", |
|||
r"(?P<nome>.+?)( *),( *)(?P<local_trabalho>.+)", |
|||
r"(?P<nome>.+?) CPF (?P<cpf>.+)", |
|||
r"(?P<nome>.+?)(?P<cpf>[\d.]*[-]*\d+)", |
|||
r"(?P<nome>.+?)[ ]*\((?P<local_trabalho>.+)\)", |
|||
rf"(?P<local_trabalho>{cargos})[ ]*(?P<nome>.+)", |
|||
rf"(?P<nome>.+?)[ ]*(?P<local_trabalho>{cargos})", |
|||
] |
|||
] |
|||
|
|||
Evento = apps.get_model("eventos", "Evento") |
|||
eventos = ( |
|||
Evento.objects.filter(tipo_evento__categoria="V") |
|||
.exclude(convite=None) |
|||
.exclude(convite__nomes_participantes="") |
|||
).prefetch_related("convite_set") |
|||
|
|||
for evento in eventos: |
|||
evento.participante_set.all().delete() |
|||
for convite in evento.convite_set.all(): |
|||
participantes = convite.nomes_participantes.strip().splitlines() |
|||
for nome in participantes: |
|||
if nome.strip() == "": |
|||
continue |
|||
for pattern in patterns: |
|||
match = pattern.match(nome) |
|||
if match is not None: |
|||
break |
|||
if match is None: |
|||
dados = {"nome": nome} |
|||
else: |
|||
dados = { |
|||
k: v.strip() for k, v in match.groupdict().items() |
|||
} |
|||
if len(dados["nome"]) > 100: |
|||
dados["nome"] = dados["nome"][:100] |
|||
if "cpf" in dados and len(dados["cpf"]) > 30: |
|||
dados["cpf"] = dados["cpf"][:30] |
|||
evento.participante_set.create( |
|||
casa_legislativa_id=convite.casa_id, **dados |
|||
) |
|||
|
|||
|
|||
def backward(apps, schema_editor): |
|||
Evento = apps.get_model("eventos", "Evento") |
|||
Participante = apps.get_model("eventos", "Participante") |
|||
eventos = ( |
|||
Evento.objects.filter(tipo_evento__categoria="V") |
|||
.exclude(convite=None) |
|||
.exclude(convite__nomes_participantes="") |
|||
) |
|||
Participante.objects.filter(evento__in=eventos).delete() |
|||
|
|||
|
|||
class Migration(migrations.Migration): |
|||
|
|||
dependencies = [ |
|||
("eventos", "0064_alter_modelodeclaracao_texto_participante"), |
|||
] |
|||
|
|||
operations = [migrations.RunPython(forward, backward)] |
@ -0,0 +1,27 @@ |
|||
# Generated by Django 5.2.1 on 2025-08-29 13:47 |
|||
|
|||
from django.db import migrations |
|||
|
|||
SQL_STMT = """ |
|||
create view viw_eventos_participante as |
|||
select ep.evento_id as id_evento, |
|||
ep.casa_legislativa_id as id_casa, |
|||
ep.cpf, |
|||
ep.nome, |
|||
ep.email, |
|||
ep.local_trabalho |
|||
from eventos_participante ep; |
|||
grant select on viw_eventos_participante to sigi_qs; |
|||
""" |
|||
SQL_REVERSE_STMT = "DROP VIEW viw_eventos_participante;" |
|||
|
|||
|
|||
class Migration(migrations.Migration): |
|||
|
|||
dependencies = [ |
|||
("eventos", "0065_participantes_visitas"), |
|||
] |
|||
|
|||
operations = [ |
|||
migrations.RunSQL(sql=SQL_STMT, reverse_sql=SQL_REVERSE_STMT) |
|||
] |
@ -0,0 +1,281 @@ |
|||
import lxml |
|||
from difflib import SequenceMatcher |
|||
from moodle import Moodle |
|||
from django.db import models |
|||
from django.conf import settings |
|||
from django.utils.translation import gettext as _ |
|||
from sigi.apps.utils import to_ascii |
|||
from sigi.apps.contatos.models import UnidadeFederativa |
|||
from sigi.apps.casas.models import Orgao |
|||
|
|||
|
|||
CAR_ESP = {x: " " for x in range(33, 65) if x < 48 or x > 58} |
|||
CONECTIVOS = ["a", "e", "o", "da", "de", "do", "na", "no", "em"] |
|||
|
|||
|
|||
def canonize_full(s): |
|||
"""canoniza uma string removendo símbolos, artigos e outros conectivos |
|||
|
|||
Args: |
|||
s (str): A string a ser canonizada |
|||
|
|||
Returns: |
|||
(str, list): a string canonizada e a lista de palavras |
|||
""" |
|||
s = to_ascii(s.lower()).strip().translate(CAR_ESP) |
|||
palavras = [ |
|||
p.strip() |
|||
for p in s.split(" ") |
|||
if p.strip() != "" and p.strip() not in CONECTIVOS |
|||
] |
|||
s = " ".join(palavras) |
|||
return (s, palavras) |
|||
|
|||
|
|||
def canonize(s): |
|||
"""canoniza uma string retornando apenas a string canonizada |
|||
|
|||
Args: |
|||
s (str): A string a ser canonizada |
|||
|
|||
Returns: |
|||
str: a string canonizada |
|||
""" |
|||
return canonize_full(s)[0] |
|||
|
|||
|
|||
class SaberesSyncException(Exception): |
|||
@property |
|||
def message(self): |
|||
return str(self) |
|||
|
|||
|
|||
class EventoSaberes(Moodle): |
|||
_inscritos = None |
|||
_participantes = None |
|||
_aprovados = None |
|||
evento = None |
|||
_ufs = None |
|||
|
|||
def __init__(self, evento): |
|||
url = f"{settings.MOODLE_BASE_URL}/webservice/rest/server.php" |
|||
super().__init__(url, settings.MOODLE_API_TOKEN) |
|||
self.evento = evento |
|||
self._inscritos = None |
|||
self._participantes = None |
|||
self._aprovados = None |
|||
self._ufs = { |
|||
canonize(uf.nome): uf for uf in UnidadeFederativa.objects.all() |
|||
} |
|||
|
|||
def get_inscritos(self): |
|||
if self.evento.moodle_courseid is None: |
|||
raise SaberesSyncException( |
|||
_( |
|||
f"O evento {self.evento} não tem curso associado no Saberes" |
|||
), |
|||
) |
|||
|
|||
if self._inscritos is None: |
|||
try: |
|||
self._inscritos = self.post( |
|||
"core_enrol_get_enrolled_users", |
|||
courseid=self.evento.moodle_courseid, |
|||
) |
|||
except Exception as e: |
|||
raise SaberesSyncException( |
|||
_( |
|||
"Ocorreu um erro ao acessar o curso no Saberes com " |
|||
f"a mensagem {e.message}" |
|||
), |
|||
) |
|||
for i in self._inscritos: |
|||
if "customfields" in i: |
|||
i["dictcustomfields"] = { |
|||
f["shortname"]: canonize( |
|||
lxml.html.fromstring(f["value"]).text_content() |
|||
) |
|||
for f in i["customfields"] |
|||
} |
|||
uf_nome = ( |
|||
i["dictcustomfields"][settings.MOODLE_UF_CUSTOMFIELD] |
|||
if settings.MOODLE_UF_CUSTOMFIELD |
|||
in i["dictcustomfields"] |
|||
else None |
|||
) |
|||
i["uf"] = ( |
|||
self._ufs[uf_nome] if uf_nome in self._ufs else None |
|||
) |
|||
return self._inscritos |
|||
|
|||
def get_participantes(self): |
|||
if self._participantes is None: |
|||
self._participantes = list( |
|||
filter( |
|||
lambda u: any( |
|||
r["roleid"] in settings.MOODLE_STUDENT_ROLES |
|||
for r in u["roles"] |
|||
), |
|||
self.get_inscritos(), |
|||
) |
|||
) |
|||
return self._participantes |
|||
|
|||
def get_aprovados(self): |
|||
if self._aprovados is None: |
|||
for participante in self.get_participantes(): |
|||
try: |
|||
participante["completion_data"] = self.post( |
|||
"core_completion_get_course_completion_status", |
|||
courseid=self.evento.moodle_courseid, |
|||
userid=participante["id"], |
|||
) |
|||
except Exception: |
|||
participante["completed"] = False |
|||
participante["completion_data"] = None |
|||
continue |
|||
participante["completed"] = participante["completion_data"][ |
|||
"completionstatus" |
|||
]["completed"] or any( |
|||
filter( |
|||
lambda c: c["type"] |
|||
== settings.MOODLE_COMPLETE_CRITERIA_TYPE |
|||
and c["complete"], |
|||
participante["completion_data"]["completionstatus"][ |
|||
"completions" |
|||
], |
|||
) |
|||
) |
|||
self._aprovados = list( |
|||
filter(lambda p: p["completed"], self.get_participantes()) |
|||
) |
|||
return self._aprovados |
|||
|
|||
def identifica_orgaos(self): |
|||
obj_list = ( |
|||
Orgao.objects.all() |
|||
.order_by() |
|||
.annotate(uf_sigla=models.F("municipio__uf__sigla")) |
|||
) |
|||
assembleias = obj_list.filter(tipo__sigla="AL") |
|||
orgaos = [(o, canonize(f"{o.nome} {o.uf_sigla}")) for o in obj_list] |
|||
siglados = {canonize(o.sigla): o for o in obj_list if o.sigla != ""} |
|||
siglados.update({canonize(f"ALE{o.uf_sigla}"): o for o in assembleias}) |
|||
siglados.update({canonize(f"AL{o.uf_sigla}"): o for o in assembleias}) |
|||
kcm = ["camara", "municipal", "vereadores"] |
|||
kal = ["assembleia", "legislativa", "estado"] |
|||
ufs = self._ufs |
|||
try: |
|||
senado = Orgao.objects.get(nome__iexact="senado federal") |
|||
except Exception: |
|||
senado = None |
|||
|
|||
def get_names(name, uf, municipio): |
|||
municipio = canonize(municipio) |
|||
uf_sigla = canonize(uf.sigla) if uf else None |
|||
name, palavras = canonize_full(name) |
|||
names = [name] |
|||
# Acrescenta uma versão com a sigla do estado se já não tiver # |
|||
if uf_sigla and uf_sigla not in palavras: |
|||
names.insert(0, f"{name} {uf_sigla}") # Coloca como primeiro |
|||
# Corrige grafia das palavras-chave para Câmara |
|||
matches = { |
|||
s: [ |
|||
p |
|||
for p in palavras |
|||
if SequenceMatcher(a=s, b=p).ratio() > 0.8 |
|||
] |
|||
for s in kcm |
|||
} |
|||
for kw in matches: |
|||
for s in matches[kw]: |
|||
name = name.replace(s, kw) |
|||
# Elimina o termo vereadores |
|||
if "vereadores" in name: |
|||
if "municipal" in name: |
|||
name = name.replace("vereadores", "") # Só elimina |
|||
else: |
|||
name = name.replace( |
|||
"vereadores", "municipal" |
|||
) # troca por municipal |
|||
names.append(canonize(name)) |
|||
if "camara" in name: |
|||
if "municipal" not in name: |
|||
name = name.replace("camara", "camara municipal") |
|||
names.append(canonize(name)) |
|||
# Cria versão canonica com o nome do municipio e a UF |
|||
if uf_sigla: |
|||
names.append( |
|||
canonize(f"camara municipal {municipio} {uf_sigla}") |
|||
) |
|||
# Corrige grafia das palavras-chave para Assembleia |
|||
matches = { |
|||
s: [ |
|||
p |
|||
for p in palavras |
|||
if SequenceMatcher(a=s, b=p).ratio() > 0.8 |
|||
] |
|||
for s in kal |
|||
} |
|||
for kw in matches: |
|||
for s in matches[kw]: |
|||
name = name.replace(s, kw) |
|||
if "assembleia" in name: |
|||
name = name.replace("estado", "") # Elimina o termo "estado" |
|||
# Adiciona "legislativa" se necessário |
|||
if "legislativa" not in name: |
|||
name = name.replace("assembleia", "assembleia legislativa") |
|||
names.append(canonize(name)) |
|||
# Cria versão canonica com o nome e sigla da UF |
|||
if uf_sigla: |
|||
names.append( |
|||
canonize(f"assembleia legislativa {uf} {uf_sigla}") |
|||
) |
|||
# remove duplicados sem mudar a ordem |
|||
names = list(dict.fromkeys(names)) |
|||
return names |
|||
|
|||
for p in self.get_participantes(): |
|||
if ( |
|||
"dictcustomfields" in p |
|||
and settings.MOODLE_ORGAO_CUSTOMFIELD in p["dictcustomfields"] |
|||
and p["dictcustomfields"][ |
|||
settings.MOODLE_ORGAO_CUSTOMFIELD |
|||
].strip() |
|||
!= "" |
|||
): |
|||
nome_orgao = p["dictcustomfields"][ |
|||
settings.MOODLE_ORGAO_CUSTOMFIELD |
|||
] |
|||
municipio = ( |
|||
p["dictcustomfields"][ |
|||
settings.MOODLE_MUNICIPIO_CUSTOMFIELD |
|||
] |
|||
if settings.MOODLE_MUNICIPIO_CUSTOMFIELD |
|||
in p["dictcustomfields"] |
|||
else p["city"] if "city" in p else "" |
|||
) |
|||
nomes_possiveis = get_names(nome_orgao, p["uf"], municipio) |
|||
for nome in nomes_possiveis: |
|||
semelhantes = Orgao.get_semelhantes(nome, orgaos) |
|||
if len(semelhantes) > 0: |
|||
p["orgao"] = semelhantes[-1][0] |
|||
break |
|||
if "orgao" not in p: |
|||
# Buscar por sigla |
|||
nome, palavras = canonize_full(nome_orgao) |
|||
for nome in palavras: |
|||
if nome in siglados: |
|||
p["orgao"] = siglados[nome] |
|||
break |
|||
# Pode ser servidor do Senado - última chance ;D |
|||
if ( |
|||
"orgao" not in p |
|||
and senado is not None |
|||
and settings.MOODLE_SERVSENADO_CUSTOMFIELD |
|||
in p["dictcustomfields"] |
|||
and not p["dictcustomfields"][ |
|||
settings.MOODLE_SERVSENADO_CUSTOMFIELD |
|||
].startswith("nao ") |
|||
): |
|||
p["orgao"] = senado |
@ -0,0 +1,51 @@ |
|||
{% extends "admin/change_form_object_tools.html" %} |
|||
{% load i18n admin_urls djbs_extras %} |
|||
|
|||
{% block object-tools-items %} |
|||
{{ block.super }} |
|||
|
|||
{% if original.equipe_set.exists %} |
|||
{% url opts|admin_urlname:'custos' original.pk|admin_urlquote as custos_url %} |
|||
<a class="addlink nav-link custos" href="{% add_preserved_filters custos_url %}" aria-labelledby="text-tool-custos" title="{% translate 'Relatório de custos' %}"> |
|||
{% icon "money" %} <span id="text-tool-custos" class="d-lg-none">{% translate "Relatório de custos" %}</span> |
|||
</a> |
|||
{% endif %} |
|||
|
|||
{% if original.cronograma_set.exists %} |
|||
{% url opts|admin_urlname:'gantreport' original.pk|admin_urlquote as gant_url %} |
|||
{% url opts|admin_urlname:'checklistreport' original.pk|admin_urlquote as checklist_url %} |
|||
{% url opts|admin_urlname:'comunicacaoreport' original.pk|admin_urlquote as comunicacao_url %} |
|||
<a class="addlink nav-link gant" href="{% add_preserved_filters gant_url %}" aria-labelledby="text-tool-gant" title="{% translate 'Gráfico de gant' %}"> |
|||
{% icon "chart" %} <span id="text-tool-gant" class="d-lg-none">{% translate "Gráfico de gant" %}</span> |
|||
</a> |
|||
<a class="addlink nav-link checklist" href="{% add_preserved_filters checklist_url %}" aria-labelledby="text-tool-checklist" title="{% translate 'Checklist' %}"> |
|||
{% icon "checklist" %} <span id="text-tool-checklist" class="d-lg-none">{% translate "Checklist" %}</span> |
|||
</a> |
|||
<a class="addlink nav-link comunicacao" href="{% add_preserved_filters comunicacao_url %}" aria-labelledby="text-tool-comunicacao" title="{% translate 'Plano de comunicação' %}"> |
|||
{% icon "speak" %} <span id="text-tool-comunicacao" class="d-lg-none">{% translate "Plano de comunicação" %}</span> |
|||
</a> |
|||
{% endif %} |
|||
|
|||
{% if original.moodle_courseid is None %} |
|||
{% if original.tipo_evento.moodle_template_courseid is not None and evento.tipo_evento.moodle_categoryid is not None %} |
|||
{% url opts|admin_urlname:'createcourse' original.pk|admin_urlquote as createcourse_url %} |
|||
<a class="addlink nav-link createcourse" href="{% add_preserved_filters createcourse_url %}" aria-labelledby="text-tool-createcourse" title="{% translate 'Criar curso no Saberes' %}"> |
|||
{% icon "create" %} <span id="text-tool-createcourse" class="d-lg-none">{% translate "Criar curso no Saberes" %}</span> |
|||
</a> |
|||
{% endif %} |
|||
{% endif %} |
|||
|
|||
{% if original.moodle_courseid is not None %} |
|||
{% url opts|admin_urlname:'updateparticipantes' original.pk|admin_urlquote as updateparticipantes_url %} |
|||
<a class="addlink nav-link updateparticipantes" href="{% add_preserved_filters updateparticipantes_url %}" aria-labelledby="text-tool-updateparticipantes" title="{% translate 'Atualizar lista de participantes (Saberes)' %}"> |
|||
{% icon "refresh" %} <span id="text-tool-updateparticipantes" class="d-lg-none">{% translate "Atualizar lista de participantes (Saberes)" %}</span> |
|||
</a> |
|||
{% endif %} |
|||
|
|||
{% if original.tipo_evento.categoria == "V" %} |
|||
{% url opts|admin_urlname:'declaracaoreport' original.pk|admin_urlquote as declaracao_url %} |
|||
<a class="addlink nav-link declaracao" href="{% add_preserved_filters declaracao_url %}" aria-labelledby="text-tool-declaracao" title="{% translate 'Declaração' %}"> |
|||
{% icon "pdf" %} <span id="text-tool-declaracao" class="d-lg-none">{% translate "Declaração" %}</span> |
|||
</a> |
|||
{% endif %} |
|||
{% endblock %} |
@ -0,0 +1,26 @@ |
|||
{% extends "admin/change_form.html" %} |
|||
{% load i18n static admin_urls djbs_extras %} |
|||
|
|||
{% block content %} |
|||
<div id="content-main" class="container"> |
|||
<div class="card"> |
|||
<div class="card-body"> |
|||
<form id="select-form" name="select-form" action="" method="post" novalidate> |
|||
{% csrf_token %} |
|||
{{ form }} |
|||
</form> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="submit-row btn-toolbar hstack justify-content-end mx-2 my-5 gap-2"> |
|||
<button class="default btn btn-outline-success" type="submit" form="select-form" name="submit" value="print"> |
|||
{% icon "print" %} {% trans "Imprimir" %} |
|||
</button> |
|||
{% url opts|admin_urlname:'change' evento_id|admin_urlquote as change_url %} |
|||
<a href="{% add_preserved_filters change_url %}" class="closelink btn btn-outline-success" title="{% translate 'Close' %}"> |
|||
{% icon "dismiss" %} {% translate 'Close' %} |
|||
</a> |
|||
</div> |
|||
</div> |
|||
{% endblock %} |
|||
|
@ -0,0 +1,20 @@ |
|||
{% load i18n %} |
|||
<table> |
|||
<tr> |
|||
<th>{% trans "CPF" %}</th> |
|||
<th>{% trans "Nome" %}</th> |
|||
<th>{% trans "Cargo / função / setor" %}</th> |
|||
</tr> |
|||
{% for membro in evento.participante_set.all %} |
|||
{% ifchanged membro.casa_legislativa %} |
|||
<tr> |
|||
<td colspan="3">{{ membro.casa_legislativa.nome }}</td> |
|||
</tr> |
|||
{% endifchanged %} |
|||
<tr> |
|||
<td>{{ membro.cpf }}</td> |
|||
<td>{{ membro.nome }}</td> |
|||
<td>{{ membro.local_trabalho }}</td> |
|||
</tr> |
|||
{% endfor %} |
|||
</table> |
@ -1,49 +0,0 @@ |
|||
{% extends "admin/base_site.html" %} |
|||
{% load i18n static admin_urls %} |
|||
|
|||
{% block extrastyle %} |
|||
{{ block.super }} |
|||
<link rel="stylesheet" href="{% static 'material/admin/css/submit_line.min.css' %}"> |
|||
{% endblock %} |
|||
|
|||
{% block breadcrumbs %}{% endblock %} |
|||
|
|||
{% block messages %} |
|||
{% if error %} |
|||
<ul class="messagelist"> |
|||
<li class="error">{{ error|capfirst }}</li> |
|||
</ul> |
|||
{% endif %} |
|||
{% endblock messages %} |
|||
|
|||
{% block content %} |
|||
<div class="container"> |
|||
<div class="card"> |
|||
<div class="card-content"> |
|||
<span class="card-title">{% trans 'Emitir declaração de comparecimento' %}</span> |
|||
<form id="select-form" name="select-form" action="" method="post" novalidate> |
|||
{% csrf_token %} |
|||
<div class="form-group"> |
|||
{{ form }} |
|||
</div> |
|||
</form> |
|||
</div> |
|||
<div class="card-action"> |
|||
<div class="submit-row"> |
|||
<div class="open-actions"> |
|||
<button class="default waves-effect waves-light btn" type="submit" form="select-form" name="submit" value="print"> |
|||
<i class="material-icons">picture_as_pdf</i> |
|||
{% trans "Imprimir" %} |
|||
</button> |
|||
{% url opts|admin_urlname:'change' evento_id|admin_urlquote as change_url %} |
|||
<a class="default waves-effect waves-light btn" role="button" href="{% add_preserved_filters change_url %}"> |
|||
<i class="material-icons">undo</i> |
|||
{% trans "Voltar" %} |
|||
</a> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
{% endblock %} |
|||
|
Loading…
Reference in new issue