Browse Source

Troca controlador de jobs local pelo pacote dx_job_controller

pull/5/merge
Sesóstris Vieira 4 days ago
parent
commit
2c5a81eadc
  1. 4
      requirements/dev-requirements.txt
  2. 59
      requirements/requirements.txt
  3. 80
      sigi/apps/casas/jobs/daily/sanitiza_email_orgao.py
  4. 75
      sigi/apps/casas/jobs/daily/usuario_contato.py
  5. 253
      sigi/apps/contatos/jobs/monthly/atualiza_ibge.py
  6. 16
      sigi/apps/convenios/jobs/hourly/importa_gescon.py
  7. 352
      sigi/apps/espacos/jobs/hourly/sincroniza_reservas.py
  8. 87
      sigi/apps/eventos/jobs/daily/encerra_inscricao.py
  9. 62
      sigi/apps/eventos/jobs/daily/sincroniza_saberes.py
  10. 20
      sigi/apps/parlamentares/jobs/minutely/importa_parlamentar.py
  11. 19
      sigi/apps/servicos/jobs/daily/sincroniza_dns.py
  12. 95
      sigi/apps/servicos/jobs/daily/sincroniza_rancher.py
  13. 24
      sigi/apps/servicos/jobs/daily/verifica_dominios.py
  14. 57
      sigi/apps/servidores/jobs/daily/sync_ldap.py
  15. 210
      sigi/apps/utils/admin.py
  16. 0
      sigi/apps/utils/jobs/__init__.py
  17. 0
      sigi/apps/utils/jobs/daily/__init__.py
  18. 0
      sigi/apps/utils/jobs/hourly/__init__.py
  19. 105
      sigi/apps/utils/jobs/job_controller.py
  20. 0
      sigi/apps/utils/jobs/monthly/__init__.py
  21. 0
      sigi/apps/utils/jobs/weekly/__init__.py
  22. 0
      sigi/apps/utils/jobs/yearly/__init__.py
  23. 117
      sigi/apps/utils/management/jobs.py
  24. 23
      sigi/apps/utils/migrations/0007_remove_jobschedule_job_delete_cronjob_and_more.py
  25. 277
      sigi/apps/utils/models.py
  26. 69
      sigi/menu_conf.yaml
  27. 13
      sigi/settings.py

4
requirements/dev-requirements.txt

@ -1,4 +1,4 @@
-r requirements.txt -r requirements.txt
black==25.1.0 black==26.1.0
ipdb==0.13.13 ipdb==0.13.13
pygraphviz==1.14 pygraphviz==1.14

59
requirements/requirements.txt

@ -1,34 +1,35 @@
cron-converter==1.2.1 Django==6.0.2
dnspython==2.7.0 django-admin-autocomplete-filter==0.7.1
docutils==0.21.2 django-auth-ldap==5.3.0
email_validator==2.2.0 django-debug-toolbar==6.2.0
gunicorn==23.0.0 django-environ==0.13.0
django-extensions==4.1
django-filter==25.2
django-import-export==4.4.0
django-localflavor==5.0
django-tinymce==5.0.0
django-weasyprint==2.4.0
djangorestframework==3.16.1
dnspython==2.8.0
docutils==0.22.4
email-validator==2.3.0
gunicorn==25.1.0
ibge==0.0.5 ibge==0.0.5
inflection==0.5.1 inflection==0.5.1
ipython==9.2.0 ipython==9.10.0
ipython_pygments_lexers==1.1.1
moodlepy==0.24.1 moodlepy==0.24.1
pandas==2.2.3 pandas==3.0.1
parsel==1.10.0 parsel==1.11.0
pillow==11.3.0 pillow==12.1.1
psycopg2-binary==2.9.10 psycopg2-binary==2.9.11
python-docx==1.1.2 python-docx==1.2.0
python-magic==0.4.27 python-magic==0.4.27
PyYAML==6.0.2 PyYAML==6.0.3
requests==2.32.4 requests==2.32.5
uritemplate==4.1.1 uritemplate==4.2.0
weasyprint==65.1 weasyprint==68.1
XlsxWriter==3.2.3 xlsxwriter==3.2.9
Django==5.2.2 djbs-theme @ git+https://github.com/interlegis/djbs-theme.git
django-admin-autocomplete-filter==0.7.1
django-auth-ldap==5.2.0
django-debug-toolbar==5.2.0
django-environ==0.12.0
django-extensions==4.1
django-filter==25.1
django-import-export==4.3.7
django-localflavor==4.0
django-tinymce==4.1.0
django-weasyprint==2.4.0
djangorestframework==3.16.0
djbs-theme @ git+https://github.com/interlegis/djbs-theme.git@1.0.3
django-dashboard @ git+https://github.com/interlegis/django-dashboard.git django-dashboard @ git+https://github.com/interlegis/django-dashboard.git
dx-job-controller @ git+https://github.com/interlegis/dx-job-controller.git

80
sigi/apps/casas/jobs/daily/sanitiza_email_orgao.py

@ -1,21 +1,17 @@
from django.db.models import Q import sys
from django.contrib.auth.models import User
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django_extensions.management.jobs import DailyJob from django_extensions.management.jobs import DailyJob
from sigi.apps.casas.models import Orgao from sigi.apps.casas.models import Orgao
from sigi.apps.servidores.models import Servidor
from sigi.apps.utils.management.jobs import JobReportMixin
from email_validator import validate_email, EmailNotValidError from email_validator import validate_email, EmailNotValidError
class Job(JobReportMixin, DailyJob): class Job(DailyJob):
help = ( help = (
"Sanitiza sintaxe dos e-mails dos órgãos cadastrados, " "Sanitiza sintaxe dos e-mails dos órgãos cadastrados, "
"removendo os que não são válidos" "removendo os que não são válidos"
) )
def do_job(self): def execute(self):
self.report_data = []
corrigidos = [] corrigidos = []
apagados = [] apagados = []
total_corrigido = 0 total_corrigido = 0
@ -53,53 +49,51 @@ class Job(JobReportMixin, DailyJob):
else: else:
tentar = False tentar = False
limpar = True limpar = True
apagados.append(
_(
f"{orgao.email} do órgão {orgao.nome} "
f"({orgao.id}) foi excluído porque não "
f"pode ser corrigido: {str(e)}"
)
)
if limpar: if limpar:
apagados.append(
_(
"{email} do órgão {nome} ({id}) foi excluído porque "
"não pode ser corrigido: {msg}"
).format(
email=orgao.email,
nome=orgao.nome,
id=orgao.id,
msg=msg,
)
)
orgao.email = "" orgao.email = ""
orgao.save() orgao.save()
total_apagado += 1 total_apagado += 1
elif orgao.email != email: elif orgao.email != email:
corrigidos.append( corrigidos.append(
_( _(
f"{orgao.email} corrigido para {email} do órgão " "{email} corrigido para {novo_email} do órgão "
f"{orgao.nome} ({orgao.id})" "{nome} ({id})"
).format(
email=orgao.email,
novo_email=email,
nome=orgao.nome,
id=orgao.id,
) )
) )
total_corrigido += 1 total_corrigido += 1
orgao.email = email orgao.email = email
orgao.save() orgao.save()
self.report_data.extend( print(_("RESUMO"), "\n------\n\n")
[ print(_("E-mails verificados: {count}").format(count=queryset.count()))
_("RESUMO"), print(_("E-mails corrigidos.: {count}").format(count=total_corrigido))
"------", print(_("E-mails apagados...: {count}").format(count=total_apagado))
"",
] if total_corrigido > 0:
) print("\n\n", _("E-MAILS CORRIGIDOS"), "\n------------------\n\n")
self.report_data.append(_(f"E-mails verificados: {queryset.count()}")) print("\n".join(corrigidos))
self.report_data.append(_(f"E-mails corrigidos.: {total_corrigido}"))
self.report_data.append(_(f"E-mails apagados...: {total_apagado}")) if total_apagado > 0:
self.report_data.extend( print(
[ "\n\n",
"",
_("E-MAILS CORRIGIDOS"),
"------------------",
"",
]
)
self.report_data.extend(corrigidos)
self.report_data.extend(
[
"",
_("E-MAILS APAGADOS"), _("E-MAILS APAGADOS"),
"----------------", "\n----------------\n\n",
"", file=sys.stderr,
] )
) print("\n".join(apagados), file=sys.stderr)
self.report_data.extend(apagados)

75
sigi/apps/casas/jobs/daily/usuario_contato.py

@ -1,75 +0,0 @@
from django.db.models import Q
from django.contrib.auth.models import User
from django.utils.translation import gettext as _
from django_extensions.management.jobs import DailyJob
from sigi.apps.casas.models import Funcionario
from sigi.apps.servidores.models import Servidor
from sigi.apps.utils.management.jobs import JobReportMixin
class Job(JobReportMixin, DailyJob):
help = "Ativa / desativa usuários para os Contatos Interlegis"
def do_job(self):
self.report_data = []
tot_news = 0
tot_updates = 0
tot_deactivated = 0
# Seleciona contatos interlegis com mínimo de informações
# (nome, cpf, email). Elegíveis para fazer login no sistema
contatos = Funcionario.objects.filter(
setor="contato_interlegis"
).exclude(Q(nome="") | Q(cpf="") | Q(email=""))
# Ativa / atualiza usuários para os contatos interlegis elegíveis
for contato in contatos:
email = contato.email
first, *__, last = f"{contato.nome} ".split(" ")
user, created = User.objects.update_or_create(
defaults={
"email": email,
"first_name": first,
"last_name": last,
"is_active": True,
"is_staff": False,
"is_superuser": False,
},
username=email,
)
if created:
tot_news += 1
self.admin_log_addition(
user, "Novo contato técnico habilitado"
)
else:
tot_updates += 1
self.admin_log_change(user, "Contato técnico atualizado")
self.report_data.append(
_(
f"Usuário '{user.username}' "
f"{['atualizado', 'criado'][created]} "
f"para o contato {contato.id}"
)
)
# Desativa usuários de contatos que não estão na lista de elegíveis
for user in User.objects.filter(
username__contains="@", is_active=True
).exclude(username__in=contatos.values_list("email", flat=True)):
user.is_active = False
user.save()
self.admin_log_change(
user,
_("Desativado pelo sistema - Não é mais contato técnico"),
)
tot_deactivated += 1
self.report_data.append("")
self.report_data.append(_("RESUMO"))
self.report_data.append("------")
self.report_data.append("")
self.report_data.append(_(f"{tot_news} novos usuários"))
self.report_data.append(_(f"{tot_updates} usuários atualizados"))
self.report_data.append(_(f"{tot_deactivated} usuários desativados"))

253
sigi/apps/contatos/jobs/monthly/atualiza_ibge.py

@ -1,9 +1,10 @@
import sys
from django_extensions.management.jobs import MonthlyJob from django_extensions.management.jobs import MonthlyJob
from django.conf import settings from django.conf import settings
from django.contrib.admin.models import ADDITION, CHANGE from django.contrib.admin.models import ADDITION, CHANGE
from django.core.mail import mail_admins from django.core.mail import mail_admins
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _, ngettext
from ibge.localidades import Estados, Municipios from ibge.localidades import Estados, Municipios
from sigi.apps.contatos.models import ( from sigi.apps.contatos.models import (
UnidadeFederativa, UnidadeFederativa,
@ -12,50 +13,21 @@ from sigi.apps.contatos.models import (
Municipio, Municipio,
) )
from sigi.apps.servidores.models import Servidor from sigi.apps.servidores.models import Servidor
from sigi.apps.utils.management.jobs import JobReportMixin from sigi.apps.utils.management.jobs import AdminJobMixin
class Job(JobReportMixin, MonthlyJob): class Job(AdminJobMixin, MonthlyJob):
help = _( help = _(
"Atualiza Unidades Federativas, mesorregiões, microrregiões e " "Atualiza Unidades Federativas, mesorregiões, microrregiões e "
"municípios com dados do IBGE" "municípios com dados do IBGE"
) )
report_template = "contatos/emails/report_atualiza_ibge.rst"
uf_novas = [] def execute(self):
uf_atualizadas = []
municipios_novos = []
municipios_atualizados = []
meso_novas = []
meso_atualizadas = []
micro_novas = []
micro_atualizadas = []
sigi_user = None
def do_job(self):
self.atualiza_ufs() self.atualiza_ufs()
self.atualiza_municipios() self.atualiza_municipios()
self.report_data = {
"uf_novas": self.uf_novas,
"uf_atualizadas": self.uf_atualizadas,
"municipios_novos": self.municipios_novos,
"municipios_atualizados": self.municipios_atualizados,
"meso_novas": self.meso_novas,
"meso_atualizadas": self.meso_atualizadas,
"micro_novas": self.micro_novas,
"micro_atualizadas": self.micro_atualizadas,
}
def atualiza_ufs(self): def atualiza_ufs(self):
regioes_map = { regioes_map = {"N": "NO", "NE": "NE", "SE": "SE", "S": "SL", "CO": "CO"}
"N": "NO",
"NE": "NE",
"SE": "SE",
"S": "SL",
"CO": "CO",
}
for ibge_uf in Estados().json(): for ibge_uf in Estados().json():
regiao = regioes_map[ibge_uf["regiao"]["sigla"]] regiao = regioes_map[ibge_uf["regiao"]["sigla"]]
try: try:
@ -71,69 +43,66 @@ class Job(JobReportMixin, MonthlyJob):
populacao=0, populacao=0,
) )
sigi_uf.save() sigi_uf.save()
self.uf_novas.append(sigi_uf) self.admin_log_addition(
self.admin_log_addition(sigi_uf, "Nova UF encontrada no IBGE") sigi_uf, _("Nova UF encontrada no IBGE")
)
print(
_("Nova UF encontrada no IBGE: {sigla}, {nome}").format(
sigla=sigi_uf.sigla, nome=sigi_uf.nome
)
)
if ( if (
sigi_uf.nome != ibge_uf["nome"] sigi_uf.nome != ibge_uf["nome"]
or sigi_uf.sigla != ibge_uf["sigla"] or sigi_uf.sigla != ibge_uf["sigla"]
or sigi_uf.regiao != regiao or sigi_uf.regiao != regiao
): ):
print(
_(
"UF {codigo_ibge} atualizada pelo IBGE: "
"{regiao} => {nova_regiao}, {sigla} => {nova_sigla}, "
"{nome} => {novo_nome}"
).format(
codigo_ibge=sigi_uf.codigo_ibge,
regiao=sigi_uf.regiao,
nova_regiao=regiao,
sigla=sigi_uf.sigla,
nova_sigla=ibge_uf["sigla"],
nome=sigi_uf.nome,
novo_nome=ibge_uf["nome"],
)
)
sigi_uf.nome = ibge_uf["nome"] sigi_uf.nome = ibge_uf["nome"]
sigi_uf.sigla = ibge_uf["sigla"] sigi_uf.sigla = ibge_uf["sigla"]
sigi_uf.regiao = regiao sigi_uf.regiao = regiao
sigi_uf.save() sigi_uf.save()
self.uf_atualizadas.append(sigi_uf) self.admin_log_change(sigi_uf, _("Atualizada pelo IBGE"))
self.admin_log_change(sigi_uf, "Atualizada pelo IBGE")
def atualiza_municipios(self): def atualiza_municipios(self):
for ibge_mun in Municipios().json(): for ibge_mun in Municipios().json():
uf_id = ibge_mun["microrregiao"]["mesorregiao"]["UF"]["id"] if ibge_mun["microrregiao"]:
cod_meso = ibge_mun["microrregiao"]["mesorregiao"]["id"] uf_id = ibge_mun["microrregiao"]["mesorregiao"]["UF"]["id"]
cod_micro = int( uf_nome = ibge_mun["microrregiao"]["mesorregiao"]["UF"]["nome"]
str(cod_meso) + str(ibge_mun["microrregiao"]["id"])[-3:] else:
) uf_id = ibge_mun["regiao-imediata"]["regiao-intermediaria"][
# Atualiza ou cria mesorregião # "UF"
]["id"]
uf_nome = ibge_mun["regiao-imediata"]["regiao-intermediaria"][
"UF"
]["nome"]
try: try:
meso = Mesorregiao.objects.get(codigo_ibge=cod_meso) uf = UnidadeFederativa.objects.get(codigo_ibge=uf_id)
except Mesorregiao.DoesNotExist: except UnidadeFederativa.DoesNotExist:
meso = Mesorregiao( print(
codigo_ibge=cod_meso, _(
uf_id=uf_id, "* ERRO: UF {uf_id} - {uf_nome} não encontrada no SIGI"
nome=ibge_mun["microrregiao"]["mesorregiao"]["nome"], " ao processar o município do IBGE {mun_id} {mun_nome}"
) ).format(
meso.save() uf_id=uf_id, uf_nome=uf_nome, mun_id=ibge_mun["id"]
self.meso_novas.append(meso) )
self.admin_log_addition(
meso, "Nova mesorregião encontrada no IBGE"
)
if meso.nome != ibge_mun["microrregiao"]["mesorregiao"]["nome"]:
meso.nome = ibge_mun["microrregiao"]["mesorregiao"]["nome"]
meso.save()
self.meso_atualizadas.append(meso)
self.admin_log_change(meso, "Atualizada pelo IBGE")
# Atualiza ou cria a microrregião #
try:
micro = Microrregiao.objects.get(codigo_ibge=cod_micro)
except Microrregiao.DoesNotExist:
micro = Microrregiao(
codigo_ibge=cod_micro,
mesorregiao=meso,
nome=ibge_mun["microrregiao"]["nome"],
)
micro.save()
self.micro_novas.append(micro)
self.admin_log_addition(
micro, "Nova microrregião encontrada no IBGE"
) )
if ( continue
micro.nome != ibge_mun["microrregiao"]["nome"] meso = self.atualiza_meso(ibge_mun, uf)
or micro.mesorregiao != meso micro = self.atualiza_micro(ibge_mun, meso)
):
micro.nome = ibge_mun["microrregiao"]["nome"]
micro.mesorregiao = meso
micro.save()
self.micro_atualizadas.append(micro)
self.admin_log_change(micro, "Atualizada pelo IBGE")
# Atualiza ou cria o município # # Atualiza ou cria o município #
try: try:
sigi_mun = Municipio.objects.get(codigo_ibge=ibge_mun["id"]) sigi_mun = Municipio.objects.get(codigo_ibge=ibge_mun["id"])
@ -142,12 +111,16 @@ class Job(JobReportMixin, MonthlyJob):
codigo_ibge=ibge_mun["id"], codigo_ibge=ibge_mun["id"],
microrregiao=micro, microrregiao=micro,
nome=ibge_mun["nome"], nome=ibge_mun["nome"],
uf_id=uf_id, uf=uf,
populacao=0, populacao=0,
idh=0.0, idh=0.0,
) )
sigi_mun.save() sigi_mun.save()
self.municipios_novos.append(sigi_mun) print(
_(
"Novo município {nome} da UF {uf_nome} criado pelo IBGE"
).format(nome=sigi_mun.nome, uf_nome=sigi_mun.uf.nome)
)
self.admin_log_addition( self.admin_log_addition(
sigi_mun, "Novo município encontrado no IBGE" sigi_mun, "Novo município encontrado no IBGE"
) )
@ -156,9 +129,117 @@ class Job(JobReportMixin, MonthlyJob):
or sigi_mun.uf_id != uf_id or sigi_mun.uf_id != uf_id
or sigi_mun.microrregiao != micro or sigi_mun.microrregiao != micro
): ):
print(
_(
"Município {codigo_ibge} alterado no IBGE. "
"{old_name} => {new_name}, {old_uf} => {new_uf}, "
"{old_micro} => {new_micro}"
).format(
codigo_ibge=sigi_mun.codigo_ibge,
old_name=sigi_mun.nome,
new_name=ibge_mun["nome"],
old_uf=sigi_mun.uf.nome,
new_uf=uf_nome,
old_micro=sigi_mun.microrregiao.nome,
new_micro=micro.nome,
)
)
sigi_mun.nome = ibge_mun["nome"] sigi_mun.nome = ibge_mun["nome"]
sigi_mun.uf_id = uf_id sigi_mun.uf_id = uf_id
sigi_mun.microrregiao = micro sigi_mun.microrregiao = micro
sigi_mun.save() sigi_mun.save()
self.municipios_atualizados.append(sigi_mun)
self.admin_log_change(sigi_mun, "Atualizada pelo IBGE") self.admin_log_change(sigi_mun, "Atualizada pelo IBGE")
def atualiza_meso(self, ibge_mun, uf):
# Atualiza ou cria mesorregião #
if ibge_mun["microrregiao"]:
cod_meso = ibge_mun["microrregiao"]["mesorregiao"]["id"]
nome_meso = ibge_mun["microrregiao"]["mesorregiao"]["nome"]
else:
cod_meso = ibge_mun["regiao-imediata"]["regiao-intermediaria"]["id"]
nome_meso = ibge_mun["regiao-imediata"]["regiao-intermediaria"][
"nome"
]
try:
meso = Mesorregiao.objects.get(codigo_ibge=cod_meso)
except Mesorregiao.DoesNotExist:
meso = Mesorregiao(
codigo_ibge=cod_meso,
uf=uf,
nome=nome_meso,
)
meso.save()
print(
_("Incluída nova mesorregião {nome} na UF {uf}").format(
nome=meso.nome, uf=uf.nome
)
)
self.admin_log_addition(
meso, _("Nova mesorregião encontrada no IBGE")
)
if meso.nome != nome_meso:
print(
_(
"Nome da mesorregião {codigo_ibge} mudou de {old_name} "
"para {new_name}"
).format(
codigo_ibge=meso.codigo_ibge,
old_name=meso.nome,
new_name=nome_meso,
)
)
meso.nome = nome_meso
meso.save()
self.admin_log_change(meso, _("Atualizada pelo IBGE"))
return meso
def atualiza_micro(self, ibge_mun, meso):
# Atualiza ou cria a microrregião #
if ibge_mun["microrregiao"]:
cod_micro = int(
str(ibge_mun["microrregiao"]["mesorregiao"]["id"])
+ str(ibge_mun["microrregiao"]["id"])[-3:]
)
nome_micro = ibge_mun["microrregiao"]["nome"]
else:
cod_micro = int(
str(ibge_mun["regiao-imediata"]["regiao-intermediaria"]["id"])
+ str(ibge_mun["regiao-imediata"]["id"])[-3:]
)
nome_micro = ibge_mun["regiao-imediata"]["nome"]
try:
micro = Microrregiao.objects.get(codigo_ibge=cod_micro)
except Microrregiao.DoesNotExist:
micro = Microrregiao(
codigo_ibge=cod_micro,
mesorregiao=meso,
nome=nome_micro,
)
micro.save()
print(
_("Incluída nova microrregião {nome} na UF {uf}").format(
nome=micro.nome, uf=meso.uf.nome
)
)
self.admin_log_addition(
micro, _("Nova microrregião encontrada no IBGE")
)
if micro.nome != nome_micro or micro.mesorregiao != meso:
print(
_(
"Microrregião {codigo_ibge} atualizada pelo IBGE: "
"{old_name} => {new_name}, {old_meso} => {new_meso}"
).format(
codigo_ibge=micro.codigo_ibge,
old_name=micro.nome,
new_name=nome_micro,
old_meso=micro.mesorregiao.nome,
new_meso=meso.nome,
)
)
micro.nome = nome_micro
micro.mesorregiao = meso
micro.save()
self.admin_log_change(micro, _("Atualizada pelo IBGE"))
return micro

16
sigi/apps/convenios/jobs/hourly/importa_gescon.py

@ -1,16 +1,16 @@
import datetime import sys
import docutils.core
from django.core.mail import mail_admins
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django_extensions.management.jobs import HourlyJob from django_extensions.management.jobs import HourlyJob
from sigi.apps.convenios.models import Gescon from sigi.apps.convenios.models import Gescon
from sigi.apps.utils.management.jobs import JobReportMixin
class Job(JobReportMixin, HourlyJob): class Job(HourlyJob):
help = "Carga de dados do Gescon." help = "Carga de dados do Gescon."
def do_job(self): def execute(self):
gescon = Gescon.load() gescon = Gescon.load()
self.send_report_mail = gescon.importa_contratos() errors = gescon.importa_contratos()
self.report_data = gescon.ultima_importacao.splitlines() if errors:
print(gescon.ultima_importacao, file=sys.stderr)
else:
print(gescon.ultima_importacao)

352
sigi/apps/espacos/jobs/hourly/sincroniza_reservas.py

@ -1,3 +1,4 @@
import sys
import requests import requests
import datetime import datetime
from django.conf import settings from django.conf import settings
@ -6,7 +7,6 @@ from django.utils import timezone
from django.utils.formats import localize from django.utils.formats import localize
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django_extensions.management.jobs import HourlyJob from django_extensions.management.jobs import HourlyJob
from sigi.apps.utils.management.jobs import JobReportMixin
from sigi.apps.espacos.models import ( from sigi.apps.espacos.models import (
Espaco, Espaco,
Recurso, Recurso,
@ -22,12 +22,8 @@ DEPARA_SITUACAO = {
} }
class Job(JobReportMixin, HourlyJob): class Job(HourlyJob):
help = "Sincroniza dados do sistema de reserva de salas" help = "Sincroniza dados do sistema de reserva de salas"
report_data = []
resumo = []
infos = []
erros = []
@property @property
def auth_data(self): def auth_data(self):
@ -36,11 +32,7 @@ class Job(JobReportMixin, HourlyJob):
settings.RESERVA_SALA_API_PASSWORD, settings.RESERVA_SALA_API_PASSWORD,
) )
def do_job(self): def execute(self):
self.resumo = []
self.infos = []
self.erros = []
if ( if (
settings.RESERVA_SALA_BASE_URL is None settings.RESERVA_SALA_BASE_URL is None
or settings.RESERVA_SALA_API_USER is None or settings.RESERVA_SALA_API_USER is None
@ -54,11 +46,7 @@ class Job(JobReportMixin, HourlyJob):
self.carrega_reservas() self.carrega_reservas()
def carrega_salas(self): def carrega_salas(self):
tit = ["", "\t*Carga de salas*", ""] print("\n", _("Carga de salas"), "\n==============\n")
self.infos.extend(tit)
self.erros.extend(tit)
self.resumo.extend(tit)
tot_novas = 0 tot_novas = 0
tot_erros = 0 tot_erros = 0
tot_atualizadas = 0 tot_atualizadas = 0
@ -66,11 +54,13 @@ class Job(JobReportMixin, HourlyJob):
settings.RESERVA_SALA_BASE_URL + "salas", auth=self.auth_data settings.RESERVA_SALA_BASE_URL + "salas", auth=self.auth_data
) )
if not req.ok: if not req.ok:
self.erros.append( print(
"\t",
_( _(
"\t* Erro de autenticação na API do sistema de reserva " "* Erro de autenticação na API do sistema de reserva "
f"de salas, com a mensagem *{req.reason}*" "de salas, com a mensagem *{reason}*"
) ).format(reason=req.reason),
file=sys.stderr,
) )
return return
for sala in req.json(): for sala in req.json():
@ -87,21 +77,28 @@ class Job(JobReportMixin, HourlyJob):
id_sala=sala["id"], id_sala=sala["id"],
) )
espaco.save() espaco.save()
self.infos.append( print(
"\t",
_( _(
f"\t* Criado espaço *{espaco.id}* para a " "* Criado espaço *{espaco_id}* para a "
f"sala *{sala['id']} - {sala['nome']}*" "sala *{sala_id} - {sala_nome}*"
) ).format(
espaco_id=espaco.id,
sala_id=sala["id"],
sala_nome=sala["nome"],
),
) )
tot_novas += 1 tot_novas += 1
continue continue
except Espaco.MultipleObjectsReturned: except Espaco.MultipleObjectsReturned:
self.erros.append( print(
"\t",
_( _(
"\t* Existe mais de um espaço com o mesmo ID de sala. " "* Existe mais de um espaço com o mesmo ID de sala. "
"Isso deve ser corrigido manualmente no SIGI. " "Isso deve ser corrigido manualmente no SIGI. "
f"id_sala={sala['id']}" "id_sala={sala_id}"
) ).format(sala_id=sala["id"]),
file=sys.stderr,
) )
tot_erros += 1 tot_erros += 1
continue continue
@ -119,25 +116,21 @@ class Job(JobReportMixin, HourlyJob):
espaco.local = sala["local"] espaco.local = sala["local"]
espaco.capacidade = sala["capacidade"] espaco.capacidade = sala["capacidade"]
espaco.save() espaco.save()
self.infos.append( print(
"\t",
_( _(
f"\t* Espaço *{espaco.id}* atualizado com novos dados " "* Espaço *{espaco_id}* atualizado com novos dados "
f"da sala *{sala['id']}*" "da sala *{sala_id}*"
) ).format(espaco_id=espaco.id, sala_id=sala["id"]),
) )
tot_atualizadas += 1 tot_atualizadas += 1
self.resumo.append( print("\t", _("* Total de salas processadas: %s") % len(req.json()))
_(f"\t* Total de salas processadas: {len(req.json())}") print("\t", _("* Novos espaços criados: %s") % tot_novas)
) print("\t", _("* Espaços atualizados: %s") % tot_atualizadas)
self.resumo.append(_(f"\t* Novos espaços criados: {tot_novas}")) print("\t", _("* Erros encontrados nas salas: %s") % tot_erros)
self.resumo.append(_(f"\t* Espaços atualizados: {tot_atualizadas}"))
self.resumo.append(_(f"\t* Erros encontrados nas salas: {tot_erros}"))
def carrega_recursos(self): def carrega_recursos(self):
tit = ["", "\t*Carga de recursos*", ""] print("\n", _("Carga de recursos"), "\n=================\n")
self.infos.extend(tit)
self.erros.extend(tit)
self.resumo.extend(tit)
tot_novas = 0 tot_novas = 0
tot_erros = 0 tot_erros = 0
@ -148,11 +141,14 @@ class Job(JobReportMixin, HourlyJob):
auth=self.auth_data, auth=self.auth_data,
) )
if not req.ok: if not req.ok:
self.erros.append( print(
"\t",
_( _(
"\t* Erro na API do sistema de reserva ao ler " "* Erro na API do sistema de reserva ao ler "
f"equipamentos, com a mensagem *{req.reason}*" "equipamentos, com a mensagem *%s*"
) )
% req.reason,
file=sys.stderr,
) )
return return
@ -170,53 +166,58 @@ class Job(JobReportMixin, HourlyJob):
id_equipamento=equipamento["id"], id_equipamento=equipamento["id"],
) )
recurso.save() recurso.save()
self.infos.append( print(
f"\t* Recurso *{recurso}* criado a partir do equipamento *{equipamento['id']} - {equipamento['nome']}*" "\t",
_(
"* Recurso *{recurso}* criado a partir do equipamento "
"*{equipamento_id} - {equipamento_nome}*"
).format(
recurso=str(recurso),
equipamento_id=equipamento["id"],
equipamento_nome=equipamento["nome"],
),
) )
tot_novas += 1 tot_novas += 1
continue continue
except Recurso.MultipleObjectsReturned: except Recurso.MultipleObjectsReturned:
lista = ", ".join( print(
[ "\t",
str(r) _(
for r in Recurso.objects.filter( "* O equipamento *{id} - {nome}* possui os seguintes "
id_equipamento=equipamento["id"] "recursos com mesmo ID no SIGI:"
) ).format(id=equipamento["id"], nome=equipamento["nome"]),
] file=sys.stderr,
)
self.erros.append(
f"\t* O equipamento *{equipamento['id']} - "
f"{equipamento['nome']}* possui os seguintes recursos "
f"com mesmo ID no SIGI: *{lista}*"
) )
for r in Recurso.objects.filter(
id_equipamento=equipamento["id"]
):
print("\t\t -", str(r), file=sys.stderr)
tot_erros += 1 tot_erros += 1
continue continue
if equipamento["nome"] != recurso.nome: if equipamento["nome"] != recurso.nome:
recurso.nome = equipamento["nome"] recurso.nome = equipamento["nome"]
recurso.save() recurso.save()
self.infos.append( print(
f"\t* Recurso *{str(recurso)}* atualizado com as alterações " "\t",
f"do equipamento *{equipamento['id']}*" _(
"* Recurso *{recurso}* atualizado com as alterações "
"do equipamento *{id}*"
).format(recurso=str(recurso), id=equipamento["id"]),
) )
tot_atualizadas += 1 tot_atualizadas += 1
self.resumo.append( print(
_(f"\t* Total de equipamentos processados: {len(req.json())}") "\t", _("* Total de equipamentos processados: %s") % len(req.json())
)
self.resumo.append(_(f"\t* Novos recursos criados: {tot_novas}"))
self.resumo.append(_(f"\t* Recursos atualizados: {tot_atualizadas}"))
self.resumo.append(
_(f"\t* Erros encontrados nos equipamentos: {tot_erros}")
) )
print("\t", _("* Novos recursos criados: %s") % tot_novas)
print("\t", _("* Recursos atualizados: %s") % tot_atualizadas)
print("\t", _("* Erros encontrados nos equipamentos: %s") % tot_erros)
def carrega_reservas( def carrega_reservas(
self, self,
ontem=(timezone.localdate() - timezone.timedelta(days=1)).isoformat(), ontem=(timezone.localdate() - timezone.timedelta(days=1)).isoformat(),
): ):
tit = ["", "\t*Carga de reservas*", ""] print("\n", _("Carga de reservas"), "\n=================\n")
self.infos.extend(tit)
self.erros.extend(tit)
self.resumo.extend(tit)
tot_processadas = 0 tot_processadas = 0
tot_novas = 0 tot_novas = 0
@ -231,13 +232,16 @@ class Job(JobReportMixin, HourlyJob):
auth=self.auth_data, auth=self.auth_data,
) )
if not req.ok: if not req.ok:
self.erros.append( print(
"\t",
_( _(
"\t* Erro na API do sistema de reserva ao ler " "* Erro na API do sistema de reserva ao ler reservas "
f"reservas da sala *{espaco.id_sala}*, com data de " "da sala *{id_sala}*, com data de início maior que "
f"início maior que *{ontem}*, " "*{ontem}*, com a mensagem *{msg}*"
f"com a mensagem *{req.reason}*" ).format(
) id_sala=espaco.id_sala, ontem=ontem, msg=req.reason
),
file=sys.stderr,
) )
continue continue
tot_processadas += len(req.json()) tot_processadas += len(req.json())
@ -258,12 +262,8 @@ class Job(JobReportMixin, HourlyJob):
reserva["coordenador"] = reserva["coordenador"][:100] reserva["coordenador"] = reserva["coordenador"][:100]
reserva["ramal"] = reserva["ramal"][:100] reserva["ramal"] = reserva["ramal"][:100]
data_inicio = datetime.date.fromisoformat( data_inicio = datetime.date.fromisoformat(reserva["dataInicio"])
reserva["dataInicio"] hora_inicio = datetime.time.fromisoformat(reserva["horaInicio"])
)
hora_inicio = datetime.time.fromisoformat(
reserva["horaInicio"]
)
data_termino = datetime.date.fromisoformat(reserva["dataFim"]) data_termino = datetime.date.fromisoformat(reserva["dataFim"])
hora_termino = datetime.time.fromisoformat(reserva["horaFim"]) hora_termino = datetime.time.fromisoformat(reserva["horaFim"])
status = DEPARA_SITUACAO[reserva["situacao"]] status = DEPARA_SITUACAO[reserva["situacao"]]
@ -277,9 +277,7 @@ class Job(JobReportMixin, HourlyJob):
continue continue
# Tratar os demais casos # Tratar os demais casos
try: try:
reserva_sigi = Reserva.objects.get( reserva_sigi = Reserva.objects.get(id_reserva=reserva["id"])
id_reserva=reserva["id"]
)
except Reserva.DoesNotExist: except Reserva.DoesNotExist:
conflitos = self.verifica_conflito( conflitos = self.verifica_conflito(
espaco, espaco,
@ -320,23 +318,36 @@ class Job(JobReportMixin, HourlyJob):
) )
if reserva_sigi.status == Reserva.STATUS_CONFLITO: if reserva_sigi.status == Reserva.STATUS_CONFLITO:
# Reportar como erro se a reserva é conflitante # Reportar como erro se a reserva é conflitante
lista = ", ".join([str(c) for c in conflitos]) print(
self.erros.append( "\t",
f"\t* A reserva *{reserva['id']} - " _(
f"{reserva['evento']}" "* A reserva *{id} - {evento}* "
"* do sistema de reservas conflita com " "do sistema de reservas conflita com "
"a(s) seguinte(s) reserva(s) do SIGI: " "a(s) seguinte(s) reserva(s) do SIGI: "
f"*{lista}*. e foi copiada para o SIGI " "*{lista}*. e foi copiada para o SIGI "
"como conflitante." "como conflitante."
).format(
id=reserva["id"],
evento=reserva["evento"],
lista=", ".join(
[str(c) for c in conflitos]
),
),
file=sys.stderr,
) )
tot_erros += 1 tot_erros += 1
else: else:
# Reportar como nova se o status for cancelada # Reportar como nova se o status for cancelada
self.infos.append( print(
f"\t* Reserva *{str(reserva_sigi)}* " "\t",
"criada no SIGI a partir da reserva " _(
f"*{reserva['descricao']}* " "* Reserva *{res}* criada no SIGI a "
"do sistema de reservas" "partir da reserva *{desc}* do sistema "
"de reservas"
).format(
res=str(reserva_sigi),
desc=reserva["descricao"],
),
) )
tot_novas += 1 tot_novas += 1
continue continue
@ -350,22 +361,28 @@ class Job(JobReportMixin, HourlyJob):
hora_inicio, hora_inicio,
hora_termino, hora_termino,
) )
self.infos.append( print(
f"\t* Reserva *{str(reserva_sigi)}* criada no SIGI" "\t",
f" a partir da reserva *{reserva['descricao']}* " _(
"do sistema de reservas" "* Reserva *{res}* criada no SIGI a partir da "
"reserva *{desc}* do sistema de reservas"
).format(
res=str(reserva_sigi), desc=reserva["descricao"]
),
) )
tot_novas += 1 tot_novas += 1
continue continue
except Reserva.MultipleObjectsReturned: except Reserva.MultipleObjectsReturned:
# Esse erro nunca poderia acontecer, mas ... # Esse erro nunca poderia acontecer, mas ...
self.erros.append( print(
"\t",
_( _(
"\t* Existe mais de uma reserva no SIGI com o " "* Existe mais de uma reserva no SIGI com o "
"mesmo ID de reserva do sistema de reservas. " "mesmo ID de reserva do sistema de reservas. "
"Isso deve ser corrigido manualmente no SIGI. " "Isso deve ser corrigido manualmente no SIGI. "
f"id_reserva=*{reserva['id']}*" f"id_reserva=*{id}*"
) ).format(id=reserva["id"]),
file=sys.stderr,
) )
tot_erros += 1 tot_erros += 1
continue continue
@ -392,31 +409,49 @@ class Job(JobReportMixin, HourlyJob):
reserva_sigi.data_termino = data_termino reserva_sigi.data_termino = data_termino
reserva_sigi.hora_inicio = hora_inicio reserva_sigi.hora_inicio = hora_inicio
reserva_sigi.hora_termino = hora_termino reserva_sigi.hora_termino = hora_termino
self.infos.append( print(
f"\t* *{str(reserva_sigi)}* mudou para o período " "\t",
f"de *{localize(data_inicio)} " _(
f"{localize(hora_inicio)}* a " "* *{res}* mudou para o período de "
f"*{localize(data_termino)} " "*{dt_inicio} {hr_inicio} a *{dt_termino} "
f"{localize(hora_termino)}*" "{hr_termino}"
).format(
res=str(reserva_sigi),
dt_inicio=localize(data_inicio),
hr_inicio=localize(hora_inicio),
dt_termino=localize(data_termino),
hr_termino=localize(hora_termino),
),
) )
atualizou = True atualizou = True
else: else:
lista = ", ".join([str(c) for c in conflitos]) print(
self.erros.append( "\t",
f"\t* A reserva *{reserva['evento']}* no sistema " _(
"de reservas mudou de data, mas esta mudança não " "* A reserva *{res}* no sistema de reservas "
"pode ser aplicada no SIGI pois gera conflito " "mudou de data, mas esta mudança não pode ser "
"com a(s) seguinte(s) outra(s) reserva(s): " "aplicada no SIGI pois gera conflito com a(s) "
f"*{lista}*" "seguinte(s) outra(s) reserva(s): *{lista}*"
).format(
res=reserva["evento"],
lista=", ".join([str(c) for c in conflitos]),
),
file=sys.stderr,
) )
tot_erros += 1 tot_erros += 1
continue continue
# Verificar outras atualizações # Verificar outras atualizações
if reserva_sigi.status != status: if reserva_sigi.status != status:
reserva_sigi.status = status reserva_sigi.status = status
self.infos.append( print(
f"\t* A reserva SIGI *{str(reserva_sigi)}* mudou de " "\t",
f"status para *{reserva_sigi.get_status_display()}*" _(
"* A reserva SIGI *{res}* mudou de status para "
"*{status}*"
).format(
res=str(reserva_sigi),
status=reserva_sigi.get_status_display(),
),
) )
atualizou = True atualizou = True
rr = ( rr = (
@ -450,31 +485,32 @@ class Job(JobReportMixin, HourlyJob):
reserva_sigi.contato = reserva["coordenador"] reserva_sigi.contato = reserva["coordenador"]
reserva_sigi.telefone_contato = reserva["ramal"] reserva_sigi.telefone_contato = reserva["ramal"]
reserva_sigi.save() reserva_sigi.save()
self.infos.append( print(
f"\t* A reserva SIGI *{str(reserva_sigi)}* foi " "\t",
"atualizada com as alterações da " _(
f"reserva *{reserva['id']}*" "* A reserva SIGI *{res}* foi atualizada com as "
"alterações da reserva *{id}*"
).format(res=str(reserva_sigi), id=reserva["id"]),
) )
atualizou = True atualizou = True
if self.recursos_solicitados( if self.recursos_solicitados(
reserva_sigi, reserva["equipamentos"] reserva_sigi, reserva["equipamentos"]
): ):
self.infos.append( print(
"\t* Os recursos solicitados da reserva SIGI " "\t",
f"*{str(reserva_sigi)}* foram atualizados" _(
"* Os recursos solicitados da reserva SIGI "
"*{res}* foram atualizados"
).format(res=str(reserva_sigi)),
) )
atualizou = True atualizou = True
if atualizou: if atualizou:
tot_atualizadas += 1 tot_atualizadas += 1
self.resumo.append( print("\t", _("* Total de reservas processadas: %s") % tot_processadas)
_(f"\t* Total de reservas processadas: {tot_processadas}") print("\t", _("* Novas reservas criadas: %s") % tot_novas)
) print("\t", _("* Reservas atualizados: %s") % tot_atualizadas)
self.resumo.append(_(f"\t* Novas reservas criadas: {tot_novas}")) print("\t", _("* Reservas excluídas: %s") % tot_excluidas)
self.resumo.append(_(f"\t* Reservas atualizados: {tot_atualizadas}")) print("\t", _("* Erros encontrados nas reservas: %s") % tot_erros)
self.resumo.append(_(f"\t* Reservas excluídas: {tot_excluidas}"))
self.resumo.append(
_(f"\t* Erros encontrados nas reservas: {tot_erros}")
)
def verifica_conflito( def verifica_conflito(
self, self,
@ -570,37 +606,3 @@ class Job(JobReportMixin, HourlyJob):
) )
atualizou = True atualizou = True
return atualizou return atualizou
def report(self, start_time, end_time):
self.report_data = [
"",
"",
"RESUMO",
"------",
"",
"",
]
self.report_data.extend(self.resumo)
self.report_data.extend(
[
"",
"",
"ERROS ENCONTRADOS",
"-----------------",
"",
"",
]
)
self.report_data.extend(self.erros)
self.report_data.extend(
[
"",
"",
"MAIS INFORMAÇÕES",
"----------------",
"",
"",
]
)
self.report_data.extend(self.infos)
super().report(start_time, end_time)

87
sigi/apps/eventos/jobs/daily/encerra_inscricao.py

@ -1,26 +1,25 @@
from django_extensions.management.jobs import DailyJob from django_extensions.management.jobs import DailyJob
from django.db.models import Q
from django.conf import settings
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from sigi.apps.utils.management.jobs import JobReportMixin
from sigi.apps.utils.models import Config from sigi.apps.utils.models import Config
from sigi.apps.eventos.models import Evento from sigi.apps.eventos.models import Evento
INSCRICOES_ENCERRADAS = _("INSCRIÇÕES ENCERRADAS") INSCRICOES_ENCERRADAS = _("INSCRIÇÕES ENCERRADAS")
class Job(JobReportMixin, DailyJob): class Job(DailyJob):
help = _( help = _(
"Encerra inscrições e despublica eventos do Portal se já ocorreram" "Encerra inscrições e despublica eventos do Portal se já ocorreram"
) )
report_data = [] report_data = []
def do_job(self): def execute(self):
dias_a_retroagir = int(Config.get_param("ENCERRA_INSCRICAO")[0]) dias_a_retroagir = int(Config.get_param("ENCERRA_INSCRICAO")[0])
self.report_data = [] self.report_data = []
hoje = timezone.localtime().replace(hour=23, minute=59, second=59) hoje = timezone.localtime().replace(hour=23, minute=59, second=59)
retroagir = hoje - timezone.timedelta(days=dias_a_retroagir) retroagir = hoje - timezone.timedelta(days=dias_a_retroagir)
total_encerrar = 0
total_despublicar = 0
encerrar_inscricao = ( encerrar_inscricao = (
Evento.objects.exclude(publicar=False) Evento.objects.exclude(publicar=False)
@ -28,54 +27,46 @@ class Job(JobReportMixin, DailyJob):
.exclude(chave_inscricao=INSCRICOES_ENCERRADAS) .exclude(chave_inscricao=INSCRICOES_ENCERRADAS)
) )
self.report_data.extend( if encerrar_inscricao.exists():
[ print(_("Inscrições encerradas"))
"", print("---------------------")
"", print("")
_("Inscrições encerradas"), print("\n".join([f"{e.nome} ({e.id})" for e in encerrar_inscricao]))
"---------------------", print("")
"",
]
)
self.report_data.extend(
[f"{e.nome} ({e.id})" for e in encerrar_inscricao]
)
total_encerrar = encerrar_inscricao.update( total_encerrar = encerrar_inscricao.update(
chave_inscricao=INSCRICOES_ENCERRADAS chave_inscricao=INSCRICOES_ENCERRADAS
) )
despublicar = Evento.objects.exclude(publicar=False).filter( despublicar = Evento.objects.exclude(publicar=False).filter(
data_termino__lte=retroagir data_termino__lte=retroagir
) )
self.report_data.extend( if despublicar.exists():
[ print("")
"", print(_("Despublicados"))
"", print("-------------")
_("Despublicados"), print("")
"-------------", print("\n".join([f"{e.nome} ({e.id})" for e in despublicar]))
"", print("")
]
)
self.report_data.extend([f"{e.nome} ({e.id})" for e in despublicar])
total_despublicar = despublicar.update(publicar=False) total_despublicar = despublicar.update(publicar=False)
self.report_data.extend( if total_encerrar > 0 or total_despublicar > 0:
[ print("")
"", print(_("RESUMO"))
"", print("------")
_("RESUMO"), print("")
"------", if total_encerrar > 0:
"", print(
_( _(
"* Total de eventos alterados para inscrições encerradas: " "* Total de eventos alterados para inscrições "
f"{total_encerrar}" "encerradas: {count}"
), ).format(count=total_encerrar)
_( )
"* Total de eventos despublicados do portal: " if total_despublicar > 0:
f"{total_despublicar}" print(
), _(
] "* Total de eventos despublicados do portal: {count}"
) ).format(count=total_despublicar)
)

62
sigi/apps/eventos/jobs/daily/sincroniza_saberes.py

@ -1,22 +1,17 @@
import sys
from django_extensions.management.jobs import DailyJob from django_extensions.management.jobs import DailyJob
from django.db.models import Q from django.db.models import Q
from django.conf import settings
from django.forms.models import model_to_dict from django.forms.models import model_to_dict
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from sigi.apps.utils.management.jobs import JobReportMixin
from sigi.apps.eventos.models import Evento from sigi.apps.eventos.models import Evento
from sigi.apps.eventos.saberes import SaberesSyncException from sigi.apps.eventos.saberes import SaberesSyncException
class Job(JobReportMixin, DailyJob): class Job(DailyJob):
help = _("Sincroniza número de inscritos e aprovados com o Saberes.") help = _("Sincroniza número de inscritos e aprovados com o Saberes.")
report_data = []
def do_job(self): def execute(self):
self.report_data = []
infos = []
errors = []
total_sinc = 0 total_sinc = 0
total_ok = 0 total_ok = 0
total_erros = 0 total_erros = 0
@ -37,39 +32,36 @@ class Job(JobReportMixin, DailyJob):
try: try:
evento.sincroniza_saberes() evento.sincroniza_saberes()
if model_to_dict(evento) != initial: if model_to_dict(evento) != initial:
infos.append( print(
f"Evento {evento.nome} ({evento.id}) atualizado" _("Evento {nome} ({id}) atualizado").format(
nome=evento.nome, id=evento.id
)
) )
total_sinc += 1 total_sinc += 1
else: else:
total_ok += 1 total_ok += 1
except SaberesSyncException as err: except SaberesSyncException as err:
errors.append( print(
_( _(
f"Erro ao sincronizar evento {evento.nome} " "Erro ao sincronizar evento {nome} ({id}), "
f"({evento.id}), com a mensagem '{err.message}'" "com a mensagem '{message}'"
) ).format(
nome=evento.nome, id=evento.id, message=err.message
),
file=sys.stderr,
) )
total_erros += 1 total_erros += 1
self.report_data.append(_("ATUALIZAÇÕES")) if eventos.count() > 0:
self.report_data.append("------------") print(_("RESUMO"))
self.report_data.append("") print("------")
self.report_data.extend(infos) print("")
self.report_data.append("") print(
_("* Eventos a sincronizar: {count}").format(
self.report_data.append(_("ERROS")) count=eventos.count()
self.report_data.append("-----") )
self.report_data.append("") )
self.report_data.extend(errors) print(_("* Eventos atualizados: {count}").format(count=total_sinc))
self.report_data.append("") print(_("* Já estavam corretos: {count}").format(count=total_ok))
print(_("* Erros: {count}").format(count=total_erros))
self.report_data.append(_("RESUMO")) print("")
self.report_data.append("------")
self.report_data.append("")
self.report_data.append(f"* Eventos a sincronizar: {eventos.count()}")
self.report_data.append(f"* Eventos atualizados: {total_sinc}")
self.report_data.append(f"* Já estavam corretos: {total_ok}")
self.report_data.append(f"* Erros: {total_erros}")
self.report_data.append("")

20
sigi/apps/parlamentares/jobs/minutely/importa_parlamentar.py

@ -2,10 +2,8 @@ import csv
import zipfile import zipfile
from datetime import datetime from datetime import datetime
import json import json
import logging
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.conf import settings from django.conf import settings
from django.db import transaction
from django.core.mail import send_mail from django.core.mail import send_mail
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
@ -24,9 +22,11 @@ class Job(MinutelyJob):
return return
json_data["inicio_processamento"] = str(datetime.now()) json_data["inicio_processamento"] = str(datetime.now())
print( print(
f"Start importing parlamentares at {json_data['inicio_processamento']}: Details: {json_data}" _(
"Start importing parlamentares at "
"{start}: Details: {details}"
).format(start=json_data["inicio_processamento"], details=json_data)
) )
result_final = []
# Importa parlamentares # # Importa parlamentares #
if "resultados" in json_data: if "resultados" in json_data:
result = self.importa_parlamentares( result = self.importa_parlamentares(
@ -36,8 +36,8 @@ class Job(MinutelyJob):
self.remove_files(json_data) self.remove_files(json_data)
self.send_mail(result["erros"], json_data) self.send_mail(result["erros"], json_data)
return return
result_final.append(_("* IMPORTAÇÃO DOS PARLAMENTARES *")) print(_("* IMPORTAÇÃO DOS PARLAMENTARES *"))
result_final.extend(result["infos"]) print(result["infos"])
if "redes_sociais" in json_data: if "redes_sociais" in json_data:
result = self.importa_redes( result = self.importa_redes(
import_path / json_data["redes_sociais"], import_path / json_data["redes_sociais"],
@ -234,9 +234,11 @@ class Job(MinutelyJob):
nome_parlamentar=row["nm_urna_candidato"], nome_parlamentar=row["nm_urna_candidato"],
partido=partido, partido=partido,
casa_legislativa=casa, casa_legislativa=casa,
status_mandato="S" status_mandato=(
if row["ds_sit_totalizacao"] == "Suplente" "S"
else "E", if row["ds_sit_totalizacao"] == "Suplente"
else "E"
),
) )
imported += 1 imported += 1
if result["erros"]: if result["erros"]:

19
sigi/apps/servicos/jobs/daily/sincroniza_dns.py

@ -1,7 +1,9 @@
import json import json
import shutil import shutil
import sys
from django.conf import settings from django.conf import settings
from django.db.models import Q from django.db.models import Q
from django.template.loader import render_to_string
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django_extensions.management.jobs import DailyJob from django_extensions.management.jobs import DailyJob
@ -9,7 +11,7 @@ from sigi.apps.servicos import generate_instance_name, nomeia_instancias
from sigi.apps.servicos.models import Servico, TipoServico from sigi.apps.servicos.models import Servico, TipoServico
from sigi.apps.casas.models import Orgao from sigi.apps.casas.models import Orgao
from sigi.apps.contatos.models import UnidadeFederativa from sigi.apps.contatos.models import UnidadeFederativa
from sigi.apps.utils.management.jobs import JobReportMixin from sigi.apps.utils.management.jobs import AdminJobMixin
LOG_GERAL = _("Mensagens gerais") LOG_GERAL = _("Mensagens gerais")
IGNORES = ["_psl", "k8s", "www.", "sapl.", "addr.arpa"] IGNORES = ["_psl", "k8s", "www.", "sapl.", "addr.arpa"]
@ -33,13 +35,12 @@ def get_log_entry():
} }
class Job(JobReportMixin, DailyJob): class Job(AdminJobMixin, DailyJob):
help = _("Sincronização dos registros de DNS da infraestrutura") help = _("Sincronização dos registros de DNS da infraestrutura")
report_template = "servicos/emails/report_sincroniza_dns.rst"
nomes_gerados = None nomes_gerados = None
report_data = {} report_data = {}
def do_job(self): def execute(self):
self.report_data[LOG_GERAL] = get_log_entry() self.report_data[LOG_GERAL] = get_log_entry()
if ( if (
@ -80,7 +81,15 @@ class Job(JobReportMixin, DailyJob):
try: try:
shutil.rmtree(settings.REGISTRO_PATH) shutil.rmtree(settings.REGISTRO_PATH)
except Exception as e: except Exception as e:
self.info(_(f"Erro ao excluir diretório {settings.REGISTRO_PATH}")) self.error(_(f"Erro ao excluir diretório {settings.REGISTRO_PATH}"))
print(
render_to_string(
"servicos/emails/report_sincroniza_dns.rst",
context={"report_data": self.report_data},
)
)
if any([len(d.erros) > 0 for uf, d in self.report_data.items()]):
print("* EXISTEM ERROS A SEREM ANALISADOS *", file=sys.stderr)
def processa_rec(self, dns_rec, log_entry=LOG_GERAL): def processa_rec(self, dns_rec, log_entry=LOG_GERAL):
dominio = dns_rec["name"][:-1] dominio = dns_rec["name"][:-1]

95
sigi/apps/servicos/jobs/daily/sincroniza_rancher.py

@ -1,5 +1,6 @@
import json import json
import shutil import shutil
import sys
from django.conf import settings from django.conf import settings
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
@ -7,17 +8,15 @@ from django_extensions.management.jobs import DailyJob
from sigi.apps.servicos import generate_instance_name, nomeia_instancias from sigi.apps.servicos import generate_instance_name, nomeia_instancias
from sigi.apps.servicos.models import Servico, TipoServico from sigi.apps.servicos.models import Servico, TipoServico
from sigi.apps.casas.models import Orgao from sigi.apps.casas.models import Orgao
from sigi.apps.utils.management.jobs import JobReportMixin from sigi.apps.utils.management.jobs import AdminJobMixin
class Job(JobReportMixin, DailyJob): class Job(AdminJobMixin, DailyJob):
help = _("Sincronização dos Serviços SEIT na infraestrutura") help = _("Sincronização dos Serviços SEIT na infraestrutura")
report_template = "servicos/emails/report_sincroniza_rancher.rst" report_template = "servicos/emails/report_sincroniza_rancher.rst"
nomes_gerados = None nomes_gerados = None
errors = {}
infos = {}
def do_job(self): def execute(self):
self.nomes_gerados = { self.nomes_gerados = {
generate_instance_name(o): o generate_instance_name(o): o
for o in Orgao.objects.filter(tipo__legislativo=True) for o in Orgao.objects.filter(tipo__legislativo=True)
@ -33,11 +32,6 @@ class Job(JobReportMixin, DailyJob):
except Exception as e: except Exception as e:
pass pass
self.report_data = {
"erros": self.errors,
"infos": self.infos,
}
def process(self, tipo): def process(self, tipo):
nomeia_instancias( nomeia_instancias(
servicos=Servico.objects.filter( servicos=Servico.objects.filter(
@ -46,12 +40,12 @@ class Job(JobReportMixin, DailyJob):
user=self.sys_user, user=self.sys_user,
) )
NAO_CONSTA = "*não-consta-no-rancher*" NAO_CONSTA = "*não-consta-no-rancher*"
self.errors[tipo] = []
self.infos[tipo] = []
file_path = settings.HOSPEDAGEM_PATH / tipo.arquivo_rancher file_path = settings.HOSPEDAGEM_PATH / tipo.arquivo_rancher
if not file_path.exists() or not file_path.is_file(): if not file_path.exists() or not file_path.is_file():
self.errors[tipo].append(_(f"Arquivo {file_path} não encontado.")) print(
f"{tipo}: Arquivo {file_path} não encontado.", file=sys.stderr
)
return return
json_data = json.loads(file_path.read_text()) json_data = json.loads(file_path.read_text())
@ -72,9 +66,7 @@ class Job(JobReportMixin, DailyJob):
novos = 0 novos = 0
desativados = 0 desativados = 0
self.infos[tipo].append( print(f"{len(portais)} {tipo.nome} encontrados no Rancher")
_(f"{len(portais)} {tipo.nome} encontrados no Rancher")
)
# Atualiza portais existentes e cria novos # # Atualiza portais existentes e cria novos #
for p in portais: for p in portais:
@ -89,11 +81,10 @@ class Job(JobReportMixin, DailyJob):
hostname = p["spec"]["values"][tipo.spec_rancher]["domain"] hostname = p["spec"]["values"][tipo.spec_rancher]["domain"]
else: else:
hostname = NAO_CONSTA hostname = NAO_CONSTA
self.errors[tipo].append( print(
_( f"Instância {namespace} de {tipo.nome} sem URL no "
f"Instância {namespace} de {tipo.nome} sem URL no " "rancher",
"rancher" file=sys.stderr,
)
) )
if "hostprefix" in p["spec"]["values"][tipo.spec_rancher]: if "hostprefix" in p["spec"]["values"][tipo.spec_rancher]:
@ -105,10 +96,9 @@ class Job(JobReportMixin, DailyJob):
hostname = f"{tipo.prefixo_padrao}.{hostname}" hostname = f"{tipo.prefixo_padrao}.{hostname}"
else: else:
hostname = NAO_CONSTA hostname = NAO_CONSTA
self.errors[tipo].append( print(
_( f"Instância {namespace} de {tipo.nome} sem URL no rancher",
f"Instância {namespace} de {tipo.nome} sem URL no rancher" file=sys.stderr,
)
) )
nova_versao = ( nova_versao = (
@ -137,20 +127,17 @@ class Job(JobReportMixin, DailyJob):
) )
encontrados += 1 encontrados += 1
except Servico.MultipleObjectsReturned: except Servico.MultipleObjectsReturned:
self.errors[tipo].append( print(
_( f"Existe mais de um registro ativo da instância "
f"Existe mais de um registro ativo da instância " f"{namespace} de {tipo}.",
f"{namespace} de {tipo}." file=sys.stderr,
)
) )
continue continue
except Servico.DoesNotExist: except Servico.DoesNotExist:
# Se a instância está suspensa, não precisa criar o registro # Se a instância está suspensa, não precisa criar o registro
# no SIGI. # no SIGI.
if suspenso: if suspenso:
continue continue
if ( if (
namespace in self.nomes_gerados namespace in self.nomes_gerados
or name in self.nomes_gerados or name in self.nomes_gerados
@ -172,30 +159,33 @@ class Job(JobReportMixin, DailyJob):
portal.save() portal.save()
self.admin_log_addition(portal, "Criado no Rancher") self.admin_log_addition(portal, "Criado no Rancher")
novos += 1 novos += 1
self.infos[tipo].append( print(
_( f"Criada instância {namespace} de {tipo.nome} para "
f"Criada instância {namespace} de {tipo.nome} para " f"{orgao.nome} ({orgao.municipio.uf.sigla})"
f"{orgao.nome} ({orgao.municipio.uf.sigla})"
)
) )
else: else:
self.errors[tipo].append( print(
_( f"{namespace} ({hostname}) não parece pertencer a "
f"{namespace} ({hostname}) não parece pertencer a " "nenhum órgão.",
"nenhum órgão." file=sys.stderr,
)
) )
continue continue
# se tem registro de suspensão do namespace # se tem registro de suspensão do namespace
if suspenso: if suspenso:
# Desativar o portal no SIGI # Desativar o portal no SIGI
apontamentos = ", ".join([f'"{s}"' for s in suspenso])
portal.data_desativacao = timezone.localdate() portal.data_desativacao = timezone.localdate()
portal.motivo_desativacao = ( portal.motivo_desativacao = (
"Suspenso no Rancher com os seguintes apontamentos:" "Suspenso no Rancher com os seguintes apontamentos:"
+ ", ".join([f'"{s}"' for s in suspenso]) + apontamentos
) )
portal.save() portal.save()
self.admin_log_change(portal, portal.motivo_desativacao) self.admin_log_change(portal, portal.motivo_desativacao)
print(
f"{portal.tipo_servico} em {portal.url} de "
f"{portal.casa_legislativa} suspenso no Rancher com os"
f"seguintes apontamentos: {apontamentos}"
)
# atualiza o serviço no SIGI # atualiza o serviço no SIGI
if ( if (
nova_versao != portal.versao nova_versao != portal.versao
@ -225,6 +215,11 @@ class Job(JobReportMixin, DailyJob):
portal.hospedagem_interlegis = True portal.hospedagem_interlegis = True
portal.save() portal.save()
self.admin_log_change(portal, message) self.admin_log_change(portal, message)
print(
f"{portal.tipo_servico} em {portal.url} de "
f"{portal.casa_legislativa} atualizado no Rancher: "
+ message
)
# Desativa portais registrados no SIGI que não estão no Rancher # # Desativa portais registrados no SIGI que não estão no Rancher #
nomes_instancias = [p["metadata"]["name"] for p in portais] nomes_instancias = [p["metadata"]["name"] for p in portais]
@ -241,19 +236,13 @@ class Job(JobReportMixin, DailyJob):
portal.motivo_desativacao = _("Não encontrado no Rancher") portal.motivo_desativacao = _("Não encontrado no Rancher")
portal.save() portal.save()
self.admin_log_change(portal, "Desativado no Rancher") self.admin_log_change(portal, "Desativado no Rancher")
self.infos[tipo].append( print(
f"{portal.instancia} ({portal.url}) de " f"{portal.instancia} ({portal.url}) de "
f"{portal.casa_legislativa.nome} desativado pois não " f"{portal.casa_legislativa.nome} desativado pois não "
"foi encontrado no Rancher." "foi encontrado no Rancher."
) )
desativados += 1 desativados += 1
self.infos[tipo].append( print(f"{encontrados} {tipo.nome} do Rancher encontrados no SIGI")
_(f"{encontrados} {tipo.nome} do Rancher encontrados no SIGI") print(f"{novos} novos {tipo.nome} criados no SIGI")
) print(f"{desativados} {tipo.nome} desativados no SIGI")
self.infos[tipo].append(
_(f"{novos} novos {tipo.nome} criados no SIGI")
)
self.infos[tipo].append(
_(f"{desativados} {tipo.nome} desativados no SIGI")
)

24
sigi/apps/servicos/jobs/daily/verifica_dominios.py

@ -1,16 +1,15 @@
import sys
import dns.resolver import dns.resolver
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django_extensions.management.jobs import DailyJob from django_extensions.management.jobs import DailyJob
from sigi.apps.servicos.models import Servico from sigi.apps.servicos.models import Servico
from sigi.apps.utils.management.jobs import JobReportMixin
class Job(JobReportMixin, DailyJob): class Job(DailyJob):
help = "Verifica domínios registrados no Interlegis" help = "Verifica domínios registrados no Interlegis"
report_data = []
def do_job(self): def execute(self):
servicos = Servico.objects.filter( servicos = Servico.objects.filter(
tipo_servico__modo="R", data_desativacao=None tipo_servico__modo="R", data_desativacao=None
).exclude(url="") ).exclude(url="")
@ -26,19 +25,12 @@ class Job(JobReportMixin, DailyJob):
erros += 1 erros += 1
s.resultado_verificacao = "O" s.resultado_verificacao = "O"
s.erro_atualizacao = str(e) s.erro_atualizacao = str(e)
self.report_data.append( print(
f" * {s.url} {s.get_resultado_verificacao_display()}: " f" * {s.url} {s.get_resultado_verificacao_display()}: "
f"{s.erro_atualizacao}" f"{s.erro_atualizacao}",
file=sys.stderr,
) )
s.save() s.save()
self.report_data = [ print(f" * Total de registros verificados: {total}")
"", print(f" * Registros com erros: {erros}")
"RESUMO",
"======",
"",
f" * Total de registros verificados: {total}",
f" * Registros com erros: {erros}",
"",
"",
] + self.report_data

57
sigi/apps/servidores/jobs/daily/sync_ldap.py

@ -1,9 +1,10 @@
import sys
import ldap import ldap
from django.conf import settings from django.conf import settings
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django_auth_ldap.config import _DeepStringCoder from django_auth_ldap.config import _DeepStringCoder
from django_extensions.management.jobs import DailyJob from django_extensions.management.jobs import DailyJob
from sigi.apps.utils.management.jobs import JobReportMixin from sigi.apps.utils.management.jobs import AdminJobMixin
from sigi.apps.servidores.models import Servico, Servidor from sigi.apps.servidores.models import Servico, Servidor
from sigi.apps.servidores.utils import ( from sigi.apps.servidores.utils import (
servidor_update_from_ldap, servidor_update_from_ldap,
@ -12,11 +13,11 @@ from sigi.apps.servidores.utils import (
) )
class Job(JobReportMixin, DailyJob): class Job(AdminJobMixin, DailyJob):
help = _("Sincroniza servidores com o ldap") help = _("Sincroniza servidores com o ldap")
report_data = [] report_data = []
def do_job(self): def execute(self):
coder = _DeepStringCoder("utf8") coder = _DeepStringCoder("utf8")
connect = ldap.initialize(settings.AUTH_LDAP_SERVER_URI) connect = ldap.initialize(settings.AUTH_LDAP_SERVER_URI)
@ -60,21 +61,15 @@ class Job(JobReportMixin, DailyJob):
for dn, ldap_user in decoded_data: for dn, ldap_user in decoded_data:
total_ldap += 1 total_ldap += 1
resp, servidor = servidor_create_or_update( resp, servidor = servidor_create_or_update(ldap_attrs=ldap_user)
ldap_attrs=ldap_user
)
if servidor.user: if servidor.user:
user_staff_and_group(servidor.user, ldap_user) user_staff_and_group(servidor.user, ldap_user)
if resp == servidor_create_or_update.UPDATED: if resp == servidor_create_or_update.UPDATED:
total_update += 1 total_update += 1
self.report_data.append( print(f"{servidor.nome_completo} atualizado")
_(f"{servidor.nome_completo} atualizado")
)
elif resp == servidor_create_or_update.CREATED: elif resp == servidor_create_or_update.CREATED:
total_create += 1 total_create += 1
self.report_data.append( print(f"{servidor.nome_completo} criado")
_(f"{servidor.nome_completo} criado")
)
if dn in servidores: if dn in servidores:
del servidores[dn] del servidores[dn]
@ -106,27 +101,21 @@ class Job(JobReportMixin, DailyJob):
# Reporta servidores que não estão no LDAP e também não são externos # Reporta servidores que não estão no LDAP e também não são externos
self.report_data.append("") nao_encontrados = Servidor.objects.filter(
self.report_data.append( ldap_dn="", externo=False, sigi=False
_( ).order_by("nome_completo")
if nao_encontrados.exists():
print(
"Servidores que não estão no LDAP e também não estão marcados " "Servidores que não estão no LDAP e também não estão marcados "
"como Externos" "como Externos",
file=sys.stderr,
) )
) for s in nao_encontrados:
self.report_data.append("=" * 72) print(f"- {s.nome_completo}", file=sys.stderr)
self.report_data.append("")
print(f"* {total_ldap} usuários lidos do LDAP")
for s in Servidor.objects.filter( print(f"* {total_create} novos servidores criados")
ldap_dn="", externo=False, sigi=False print(f"* {total_update} servidores atualizados")
).order_by("nome_completo"): print(f"* {total_deactive} usuários desativados")
self.report_data.append(f"- {s.nome_completo}") print(f"* {nao_encontrados.count()} servidores não encontrados no LDAP")
self.report_data.append("")
self.report_data.append("RESUMO")
self.report_data.append("=" * 6)
self.report_data.append("")
self.report_data.append(f"* {total_ldap} usuários lidos do LDAP")
self.report_data.append(f"* {total_create} novos servidores criados")
self.report_data.append(f"* {total_update} servidores atualizados")
self.report_data.append(f"* {total_deactive} usuários desativados")
self.report_data.append("")

210
sigi/apps/utils/admin.py

@ -10,42 +10,7 @@ from django.utils.translation import gettext as _
from django_extensions.management.jobs import get_job, get_jobs from django_extensions.management.jobs import get_job, get_jobs
from tinymce.models import HTMLField from tinymce.models import HTMLField
from tinymce.widgets import AdminTinyMCE from tinymce.widgets import AdminTinyMCE
from sigi.apps.utils.models import SigiAlert, Cronjob, JobSchedule, Config from sigi.apps.utils.models import SigiAlert, Config
class JobScheduleInline(admin.TabularInline):
model = JobSchedule
fields = ["status", "iniciar", "iniciado", "tempo_gasto", "get_runner"]
readonly_fields = [
"status",
"iniciar",
"iniciado",
"tempo_gasto",
"get_runner",
]
can_delete = False
can_add = False
extra = 0
def has_add_permission(self, request, obj):
return False
@mark_safe
@admin.display(description=_("Ver/executar"))
def get_runner(self, sched):
if sched.status == JobSchedule.STATUS_AGENDADO:
url = reverse("admin:utils_jobschedule_runjob", args=[sched.id])
return (
f"<a href='{url}' title='{_('Executar')}'>"
"<i class='material-icons'>play_arrow</i></a>"
)
elif sched.status == JobSchedule.STATUS_CONCLUIDO:
url = reverse("admin:utils_jobschedule_change", args=[sched.id])
return (
f"<a href='{url}' title='{_('Ver resultado')}'>"
"<i class='material-icons'>description</i></a>"
)
return ""
@admin.register(SigiAlert) @admin.register(SigiAlert)
@ -56,179 +21,6 @@ class SigiAlertAdmin(admin.ModelAdmin):
list_filter = ("destinatarios",) list_filter = ("destinatarios",)
@admin.register(Cronjob)
class CronjobAdmin(admin.ModelAdmin):
list_display = (
"job_name",
"app_name",
"get_help",
"expressao_cron",
"get_schedule",
"get_runner",
"destinatario_email",
"digest",
"last_digest",
)
fields = [
"job_name",
"app_name",
"get_help",
"expressao_cron",
"manter_logs",
"destinatario_email",
"digest",
"last_digest",
]
readonly_fields = ("job_name", "app_name", "get_help", "last_digest")
inlines = [JobScheduleInline]
def get_urls(self):
urls = super().get_urls()
model_info = (self.model._meta.app_label, self.model._meta.model_name)
my_urls = [
path(
"<path:object_id>/runjob/",
self.admin_site.admin_view(self.run_job),
name="%s_%s_runjob" % model_info,
),
]
return my_urls + urls
@admin.display(description=_("descrição"))
def get_help(self, job):
try:
JobClass = get_job(job.app_name, job.job_name)
except KeyError:
return _(
f"A rotina de JOB {job.app_name}.{job.job_name} "
"não foi encontrada."
)
job_obj = JobClass()
return job_obj.help
@admin.display(description=_("agenda"))
def get_schedule(self, job):
sched = job.jobschedule_set.first()
if sched is None:
return _("Nenhum agendamento para este job")
if sched.status == JobSchedule.STATUS_AGENDADO:
return _(
"início agendado para "
f"{localize(timezone.localtime(sched.iniciar))}."
)
if sched.status == JobSchedule.STATUS_EXECUTANDO:
return _(
"em execução desde "
f"{localize(timezone.localtime(sched.iniciado))}"
)
return _(
f"executado em {localize(timezone.localtime(sched.iniciado))}, "
f"levando {sched.tempo_gasto} minutos para concluir"
)
@mark_safe
@admin.display(description=_("executar"))
def get_runner(self, job):
url = reverse("admin:utils_cronjob_runjob", args=[job.id])
return (
f"<a href='{url}'>" "<i class='material-icons'>play_arrow</i></a>"
)
def run_job(self, request, object_id):
cronjob = get_object_or_404(Cronjob, id=object_id)
sched = cronjob.next_schedule()
if sched.status != JobSchedule.STATUS_AGENDADO:
raise PermissionDenied(
_(
"Este agendamento não pode ser executado pois "
f"está com status {sched.get_status_display()}"
)
)
sched.run_job()
self.message_user(
request,
_("JOB executado!"),
messages.SUCCESS,
)
return redirect("admin:utils_jobschedule_change", object_id=sched.id)
@admin.register(JobSchedule)
class JobScheduleAdmin(admin.ModelAdmin):
list_display = [
"job",
"status",
"iniciar",
"iniciado",
"tempo_gasto",
"get_runner",
]
fields = [
"job",
"status",
"iniciar",
"iniciado",
"tempo_gasto",
]
readonly_fields = fields
list_filter = ("status", "job")
date_hierarchy = "iniciar"
def get_urls(self):
urls = super().get_urls()
model_info = (self.model._meta.app_label, self.model._meta.model_name)
my_urls = [
path(
"<path:object_id>/runjob/",
self.admin_site.admin_view(self.run_job),
name="%s_%s_runjob" % model_info,
),
]
return my_urls + urls
def has_add_permission(self, request):
return False
def has_delete_permission(self, request, obj=None):
if obj is None or obj.status == JobSchedule.STATUS_CONCLUIDO:
return super().has_delete_permission(request, obj)
else:
return False
def has_change_permission(self, request, obj=None):
return False
@mark_safe
@admin.display(description=_("executar"))
def get_runner(self, sched):
if sched.status == JobSchedule.STATUS_AGENDADO:
url = reverse("admin:utils_jobschedule_runjob", args=[sched.id])
return (
f"<a href='{url}'>"
"<i class='material-icons'>play_arrow</i></a>"
)
return ""
def run_job(self, request, object_id):
sched = get_object_or_404(JobSchedule, id=object_id)
if sched.status != JobSchedule.STATUS_AGENDADO:
raise PermissionDenied(
_(
"Este agendamento não pode ser executado pois "
f"está com status {sched.get_status_display()}"
)
)
sched.run_job()
self.message_user(
request,
_("JOB executado!"),
messages.SUCCESS,
)
return redirect("admin:utils_jobschedule_change", object_id=object_id)
@admin.register(Config) @admin.register(Config)
class ConfigAdmin(admin.ModelAdmin): class ConfigAdmin(admin.ModelAdmin):
list_display = ["parametro", "valor"] list_display = ["parametro", "valor"]

0
sigi/apps/utils/jobs/__init__.py

0
sigi/apps/utils/jobs/daily/__init__.py

0
sigi/apps/utils/jobs/hourly/__init__.py

105
sigi/apps/utils/jobs/job_controller.py

@ -1,105 +0,0 @@
from django_extensions.management.jobs import BaseJob
from django_extensions.management.jobs import get_jobs
from django.utils import timezone
from django.utils.formats import localize
from django.utils.translation import gettext as _
from sigi.apps.utils.models import Cronjob, JobSchedule
WHEN_SETS = {
"daily": "0 0 * * *",
"hourly": "0 * * * *",
"monthly": "0 0 1 * *",
"weekly": "0 0 * * 0",
"yearly": "0 0 1 1 *",
"minutely": "* * * * *",
}
class Job(BaseJob):
help = "Controlador de cronjobs do SIGI."
def execute(self):
print("Rodando controlador de jobs...")
self.remove_old_jobs()
self.sync_new_jobs()
self.run_scheduled()
self.schedule_jobs()
self.remove_old_logs()
def remove_old_jobs(self):
"""Remover das tabelas os jobs que foram removidos do código"""
print("\tRemover das tabelas os jobs que foram removidos do código...")
all_jobs = get_jobs()
excludes = Cronjob.objects.all()
for app_name, job_name in all_jobs.keys():
excludes = excludes.exclude(app_name=app_name, job_name=job_name)
print("\t\t", excludes.delete())
def sync_new_jobs(self):
"""
Atualizar a tabela de JOBS com os novos JOBS que tenham sido criados
"""
print(
"\tAtualizar a tabela de JOBS com os novos JOBS que tenham "
"sido criados..."
)
all_jobs = get_jobs()
for (app_name, job_name), JobClass in all_jobs.items():
if app_name == "sigi.apps.utils" and job_name == "job_controller":
# Ignorar job_controller
continue
try:
job = Cronjob.objects.get(app_name=app_name, job_name=job_name)
except Cronjob.DoesNotExist:
# Inserir o JOB na tabela de JOBS #
job_obj = JobClass()
if job_obj.when in WHEN_SETS:
expressao_cron = WHEN_SETS[job_obj.when]
else:
expressao_cron = WHEN_SETS["daily"] # Default
job = Cronjob(
app_name=app_name,
job_name=job_name,
expressao_cron=expressao_cron,
)
job.save()
print(f"\t\tNovo job encontrado: {job_name}: {job_obj.help}")
def run_scheduled(self):
"""Executa os jobs que estão agendados"""
print("\tExecutar os jobs que estão agendados...")
for sched in JobSchedule.objects.filter(
status=JobSchedule.STATUS_AGENDADO
):
agora = timezone.localtime()
if sched.iniciar <= agora:
sched.run_job()
def schedule_jobs(self):
"""Criar agenda para próxima execução"""
print("\tCriar agenda para próxima execução...")
for job in Cronjob.objects.exclude(
jobschedule__status__in=[
JobSchedule.STATUS_AGENDADO,
JobSchedule.STATUS_EXECUTANDO,
]
):
sched = job.next_schedule()
print(
f"\t\tAgendado job {sched.job.job_name} "
f"para {localize(sched.iniciar)}"
)
def remove_old_logs(self):
print("\tExcluir logs antigos...")
for job in Cronjob.objects.exclude(manter_logs=0):
limite = timezone.localtime() - timezone.timedelta(
days=job.manter_logs
)
result = JobSchedule.objects.filter(
job=job,
status=JobSchedule.STATUS_CONCLUIDO,
iniciado__lt=limite,
).delete()
if result[0] > 0:
print(f"\t\t{result[0]} logs excluídos do job '{job}'")

0
sigi/apps/utils/jobs/monthly/__init__.py

0
sigi/apps/utils/jobs/weekly/__init__.py

0
sigi/apps/utils/jobs/yearly/__init__.py

117
sigi/apps/utils/management/jobs.py

@ -18,41 +18,27 @@ class QuarterDailyJob(BaseJob):
when = "quarter_daily" when = "quarter_daily"
class JobReportMixin: class AdminJobMixin:
error_report_template = "emails/report_error.rst" _sys_user = None
report_template = "emails/base_report.rst"
report_data = None
sys_user = None
send_report_mail = True
def execute(self): def get_sys_user(self):
start_time = datetime.datetime.now() if self._sys_user is None:
try:
from sigi.apps.servidores.models import Servidor
try: self._sys_user = Servidor.objects.get(sigi=True).user
from sigi.apps.servidores.models import Servidor except Exception:
pass
self.sys_user = Servidor.objects.get(sigi=True).user return self._sys_user
except Exception:
pass
try:
self.do_job()
except Exception as e:
self.report_error(e)
return
end_time = datetime.datetime.now()
self.report(start_time, end_time)
def do_job(self):
raise NotImplementedError("Job needs to implement the 'do_job' method")
def _admin_log(self, object, action_flag, message=""): def _admin_log(self, object, action_flag, message=""):
if self.sys_user is None: sys_user = self.get_sys_user()
if sys_user is None:
return # No admin log return # No admin log
LogEntry.objects.log_action( LogEntry.objects.log_action(
user_id=self.sys_user.id, user_id=sys_user.id,
content_type_id=ContentType.objects.get_for_model(type(object)).pk, content_type_id=ContentType.objects.get_for_model(type(object)).pk,
object_id=object.id, object_id=object.pk,
object_repr=str(object), object_repr=str(object),
action_flag=action_flag, action_flag=action_flag,
change_message=message, change_message=message,
@ -63,78 +49,3 @@ class JobReportMixin:
def admin_log_change(self, object, message=""): def admin_log_change(self, object, message=""):
self._admin_log(object, CHANGE, message) self._admin_log(object, CHANGE, message)
def report_error(self, error):
rst = render_to_string(
self.error_report_template,
{
"title": self.help,
"traceback": traceback.format_exception(error),
},
)
html = docutils.core.publish_string(
rst,
writer_name="html5",
settings_overrides={
"input_encoding": "unicode",
"output_encoding": "unicode",
},
)
# send_mail(
# subject=f"JOB: {self.help}",
# message=rst,
# from_email=settings.SERVER_EMAIL,
# recipient_list=Config.get_param("EMAIL_JOBS"),
# fail_silently=True,
# html_message=html,
# )
print(rst)
def prepare_report(self, start_time, end_time):
"""Prepara RST e HTML do relatório do JOB
Args:
start_time (datetime): Timestamp do início da execução
end_time (datetime): Timestamp do término da execução
Returns:
tupla(rst: str, html:str): Retorna o relatório do job formatado em
RST e HTML.
"""
rst = render_to_string(
self.report_template,
{
"title": self.help,
"start_time": start_time,
"end_time": end_time,
"report_data": self.report_data,
},
)
html = docutils.core.publish_string(
rst,
writer_name="html5",
settings_overrides={
"input_encoding": "unicode",
"output_encoding": "unicode",
},
)
return (rst, html)
def report(self, start_time, end_time):
if self.report_data is None:
raise MisconfiguredError(
"Job needs to define 'report_data' property"
)
rst, html = self.prepare_report(start_time, end_time)
# if self.send_report_mail:
# send_mail(
# subject=f"JOB: {self.help}",
# message=rst,
# from_email=settings.SERVER_EMAIL,
# recipient_list=Config.get_param("EMAIL_JOBS"),
# fail_silently=True,
# html_message=html,
# )
print(rst)

23
sigi/apps/utils/migrations/0007_remove_jobschedule_job_delete_cronjob_and_more.py

@ -0,0 +1,23 @@
# Generated by Django 5.2.1 on 2025-11-05 13:45
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("utils", "0006_remove_jobschedule_enviado_cronjob_last_digest"),
]
operations = [
migrations.RemoveField(
model_name="jobschedule",
name="job",
),
migrations.DeleteModel(
name="Cronjob",
),
migrations.DeleteModel(
name="JobSchedule",
),
]

277
sigi/apps/utils/models.py

@ -39,283 +39,6 @@ class SigiAlert(models.Model):
return self.titulo return self.titulo
class Cronjob(models.Model):
DIGEST_CHOICES = [
("N", _("Enviar sem digest")),
("D", _("Enviar com digest diário")),
("S", _("Enviar com digest semanal")),
]
digest = models.CharField(
_("digest"),
max_length=1,
choices=DIGEST_CHOICES,
default="N",
)
app_name = models.CharField(_("app"), max_length=100, editable=False)
job_name = models.CharField(_("job"), max_length=100, editable=False)
expressao_cron = models.CharField(
_("expressão CRON"),
max_length=100,
default="* * * * *",
help_text=_(
"Usar expressoões no formato padrão de CRON: "
"'minute hour day month day-of-week'. "
"Mais detalhes: "
"<a href='https://help.ubuntu.com/community/CronHowto'>"
"CronHowTo"
"</a>"
),
)
manter_logs = models.PositiveIntegerField(
_("dias para manter log"),
help_text=_(
"Número de dias que os logs de execução serão mantidos "
"na base de dados. Zero significa que o log jamais será apagado."
),
default=30,
)
destinatario_email = models.TextField(
_("destinatário(s) de e-mail"),
help_text=_("Insira um endereço de e-mail por linha."),
blank=True,
)
last_digest = models.DateTimeField(
_("último envio de digest"), blank=True, null=True
)
def get_emails_list(self):
return [
email.strip()
for email in self.destinatario_email.splitlines()
if email.strip()
]
class Meta:
ordering = ("app_name", "job_name")
verbose_name = _("Cron job")
verbose_name_plural = _("Cron jobs")
def __str__(self):
return self.job_name
def run(self):
try:
JobClass = get_job(self.app_name, self.job_name)
except KeyError:
return (
f"A rotina de JOB {self.job.job_name} do app "
f"{self.job.app_name} não foi encontrada."
)
try:
job_obj = JobClass()
with io.StringIO() as so_buf, io.StringIO() as se_buf, redirect_stdout(
so_buf
), redirect_stderr(
se_buf
):
job_obj.execute()
messages = so_buf.getvalue()
errors = se_buf.getvalue()
report_data = ["", "MENSAGENS", "---------", ""]
if messages:
report_data.extend(messages.splitlines())
else:
report_data.extend(["Nenhuma mensagem gerada", ""])
report_data.extend(["", "ERROS", "-----", ""])
if errors:
report_data.extend(errors.splitlines())
else:
report_data.extend(["Nenhum erro gerado", ""])
return "\n".join(report_data)
except Exception as e:
# Qualquer erro deve ser reportado
return _(f"JOB abortado com erro: {str(e)}")
def next_schedule(self):
"""Recupera a agenda da próxima execução. Se não existe, cria."""
try:
sch = self.jobschedule_set.get(
status__in=[
JobSchedule.STATUS_AGENDADO,
JobSchedule.STATUS_EXECUTANDO,
]
)
except JobSchedule.DoesNotExist:
iniciar = self.get_next_schedule_time()
sch = JobSchedule(job=self, iniciar=iniciar)
sch.save()
return sch
def get_next_schedule_time(self):
cron_instance = Cron(self.expressao_cron)
scheduller = cron_instance.schedule(timezone.localtime())
return scheduller.next()
class JobSchedule(models.Model):
STATUS_AGENDADO = "A"
STATUS_EXECUTANDO = "E"
STATUS_CONCLUIDO = "C"
STATUS_CHOICES = (
(STATUS_AGENDADO, _("Agendado")),
(STATUS_EXECUTANDO, _("Executando")),
(STATUS_CONCLUIDO, _("Concluído")),
)
job = models.ForeignKey(
Cronjob, verbose_name=_("Cron job"), on_delete=models.CASCADE
)
iniciar = models.DateTimeField(_("Iniciar em"))
iniciado = models.DateTimeField(_("Iniciado em"), blank=True, null=True)
status = models.CharField(
_("estado"),
max_length=1,
choices=STATUS_CHOICES,
default=STATUS_AGENDADO,
)
tempo_gasto = models.DurationField(
_("tempo gasto"), blank=True, null=True, editable=False
)
resultado = models.TextField(
_("resultado da execução"), blank=True, editable=False
)
class Meta:
ordering = ("-iniciar",)
verbose_name = _("Agenda de execução")
verbose_name_plural = _("Agenda de execuções")
class DoesNotExecute(Exception):
"""This scheduled job cannot be executed because not in AGENDADO state"""
pass
def __str__(self):
if self.status == JobSchedule.STATUS_AGENDADO:
return _(
f"{self.job.job_name}: início agendado para "
f"{localize(timezone.localtime(self.iniciar))}."
)
elif self.status == JobSchedule.STATUS_EXECUTANDO:
return _(
f"{self.job.job_name}: em execução desde "
f"{localize(timezone.localtime(self.iniciado))}"
)
return _(
f"{self.job.job_name}: executado em "
f"{localize(timezone.localtime(self.iniciado))}, "
f"levando {self.tempo_gasto} para concluir"
)
def run_job(self):
"""Executa o job agendado. Esta rotina não verifica se a agenda está
na hora certa, apenas executa o job associado."""
if self.status != JobSchedule.STATUS_AGENDADO:
raise JobSchedule.DoesNotExecute()
self.iniciado = timezone.localtime()
self.status = JobSchedule.STATUS_EXECUTANDO
self.save()
self.resultado = self.job.run()
self.status = JobSchedule.STATUS_CONCLUIDO
self.tempo_gasto = timezone.localtime() - self.iniciado
self.save()
if self.job.destinatario_email == "":
return
now = timezone.localtime()
# Converte o resultado para HTML usando docutils
try:
html_result = publish_string(self.resultado, writer_name="html")
except Exception as e:
html_result = f"<p>Erro ao converter o log para HTML: {str(e)}</p>"
if self.job.digest == "N":
# Envia imediatamente sem acumular
send_mail(
subject=f"JOB: {self.job.job_name}",
message=self.resultado,
from_email=settings.SERVER_EMAIL,
recipient_list=self.job.get_emails_list(),
fail_silently=True,
html_message=html_result.decode("utf-8"),
)
self.job.last_digest = now
self.job.save()
else:
# Determina o período de digest
if self.job.digest == "D":
period = timedelta(days=1)
elif self.job.digest == "S":
period = timedelta(weeks=1)
else:
raise ValueError(
f"Valor inválido para digest: {self.job.digest}"
)
# Se o período foi atingido desde o último digest, envia
if (
not self.job.last_digest
or now >= self.job.last_digest + period
):
self.send_digest_email(frequency=self.job.digest)
self.job.last_digest = now
self.job.save()
def send_digest_email(self, frequency):
"""Envia email de digest acumulando jobs desde o último digest."""
now = timezone.localtime()
# Determina o período de acumulação baseado no último digest
if self.job.last_digest:
period_start = self.job.last_digest
else:
period_start = (
now - timedelta(days=1)
if frequency == "D"
else now - timedelta(weeks=1)
)
job_schedules = JobSchedule.objects.filter(
job=self.job,
status=JobSchedule.STATUS_CONCLUIDO,
iniciado__gte=period_start,
)
if job_schedules.exists():
message_lines = []
html_message_lines = []
for js in job_schedules:
message_lines.append(
f"{localize(js.iniciado)}: {js.resultado}"
)
try:
html_message_lines.append(
publish_string(
js.resultado, writer_name="html"
).decode("utf-8")
)
except Exception as e:
html_message_lines.append(
f"<p>Erro ao converter o log para HTML: {str(e)}</p>"
)
message = "\n\n".join(message_lines)
html_message = "<br><br>".join(html_message_lines)
send_mail(
subject=f"Digest JOB: {self.job.job_name} ({frequency})",
message=message,
from_email=settings.SERVER_EMAIL,
recipient_list=self.job.get_emails_list(),
fail_silently=True,
html_message=html_message,
)
class Config(models.Model): class Config(models.Model):
PARAMETRO_CHOICES = ( PARAMETRO_CHOICES = (
("ENCERRA_INSCRICAO", _("Encerra inscrições de oficinas no Portal")), ("ENCERRA_INSCRICAO", _("Encerra inscrições de oficinas no Portal")),

69
sigi/menu_conf.yaml

@ -1,6 +1,6 @@
admin_menu: admin_menu:
- title: Superusuários - title: Superusuários
icon: bi bi-gear icon: config
children: children:
- title: Usuários - title: Usuários
view_name: admin:auth_user_changelist view_name: admin:auth_user_changelist
@ -19,15 +19,15 @@ admin_menu:
view_name: utils_runjob view_name: utils_runjob
view_param: encerra_inscricao view_param: encerra_inscricao
- title: Jobs de cron - title: Jobs de cron
view_name: admin:utils_cronjob_changelist view_name: admin:job_controller_cronjob_changelist
- title: Jobs agendados - title: Jobs agendados
view_name: admin:utils_jobschedule_changelist view_name: admin:job_controller_jobschedule_changelist
querystr: status__exact=A querystr: status__exact=A
- title: Configurações - title: Configurações
view_name: admin:utils_config_changelist view_name: admin:utils_config_changelist
main_menu: main_menu:
- title: Relatórios - title: Relatórios
icon: bi bi-filetype-pdf icon: pdf
children: children:
- title: VALIDAÇÃO - Erros importação Gescon - title: VALIDAÇÃO - Erros importação Gescon
view_name: convenios-report_erros_gescon view_name: convenios-report_erros_gescon
@ -35,7 +35,7 @@ main_menu:
view_name: casas_cnpj_duplicado view_name: casas_cnpj_duplicado
- title: VALIDAÇÃO - Órgãos com CNPJ errado - title: VALIDAÇÃO - Órgãos com CNPJ errado
view_name: casas_cnpj_errado view_name: casas_cnpj_errado
separator: [after,] separator: [after]
- title: Eventos por UF - title: Eventos por UF
view_name: eventos_eventosporuf view_name: eventos_eventosporuf
- title: Alunos por UF - title: Alunos por UF
@ -50,18 +50,18 @@ main_menu:
view_name: eventos_calendario view_name: eventos_calendario
- title: Alocação de equipe eventos - title: Alocação de equipe eventos
view_name: eventos_alocacaoequipe view_name: eventos_alocacaoequipe
separator: [after,] separator: [after]
- title: Reservas de espaços - title: Reservas de espaços
view_name: espacos_agenda view_name: espacos_agenda
- title: Uso dos espaços - title: Uso dos espaços
view_name: espacos_usoespaco view_name: espacos_usoespaco
- title: Resumo das reservas de espaços - title: Resumo das reservas de espaços
view_name: espacos_resumoreservas view_name: espacos_resumoreservas
separator: [after,] separator: [after]
- title: Lista de gerentes - title: Lista de gerentes
view_name: casas_gerentes view_name: casas_gerentes
- title: Municípios - title: Municípios
icon: bi bi-globe-americas icon: map
children: children:
- title: Municípios - title: Municípios
view_name: admin:contatos_municipio_changelist view_name: admin:contatos_municipio_changelist
@ -70,7 +70,7 @@ main_menu:
- title: Mesorregiões - title: Mesorregiões
view_name: admin:contatos_mesorregiao_changelist view_name: admin:contatos_mesorregiao_changelist
- title: Casas e órgãos - title: Casas e órgãos
icon: bi bi-bank icon: building
children: children:
- title: Todo legislativo - title: Todo legislativo
view_name: admin:casas_orgao_changelist view_name: admin:casas_orgao_changelist
@ -99,14 +99,14 @@ main_menu:
view_name: admin:casas_orgao_changelist view_name: admin:casas_orgao_changelist
querystr: tipo__sigla__in=AL,EL querystr: tipo__sigla__in=AL,EL
- title: Gerência Interlegis - title: Gerência Interlegis
icon: bi bi-people icon: people
children: children:
- title: Carteira de relacionamentos - title: Carteira de relacionamentos
view_name: view_name:
- title: Organizar relacionamentos - title: Organizar relacionamentos
view_name: view_name:
- title: Convênios - title: Convênios
icon: bi bi-file-earmark-check icon: file-check
children: children:
- title: Convênios - title: Convênios
view_name: admin:convenios_convenio_changelist view_name: admin:convenios_convenio_changelist
@ -114,10 +114,10 @@ main_menu:
view_name: ocorrencias_painel view_name: ocorrencias_painel
querystr: tipo_categoria=C&status=1&status=2 querystr: tipo_categoria=C&status=1&status=2
- title: Serviços SEIT - title: Serviços SEIT
icon: bi bi-cloud-fog2 icon: cloud
view_name: admin:servicos_servico_changelist view_name: admin:servicos_servico_changelist
- title: Ocorrências - title: Ocorrências
icon: bi bi-telephone icon: telephone
children: children:
- title: Registro de ocorrências - title: Registro de ocorrências
view_name: admin:ocorrencias_ocorrencia_changelist view_name: admin:ocorrencias_ocorrencia_changelist
@ -126,11 +126,11 @@ main_menu:
view_name: ocorrencias_painel view_name: ocorrencias_painel
querystr: status=1&status=2 querystr: status=1&status=2
- title: Reserva de espaços - title: Reserva de espaços
icon: bi bi-book icon: schedule
view_name: admin:espacos_reserva_changelist view_name: admin:espacos_reserva_changelist
querystr: status__exact=A querystr: status__exact=A
- title: Eventos - title: Eventos
icon: bi bi-calendar-week icon: calendar
children: children:
- title: Solicitações - title: Solicitações
view_name: admin:eventos_solicitacao_changelist view_name: admin:eventos_solicitacao_changelist
@ -153,14 +153,14 @@ main_menu:
view_name: admin:eventos_evento_changelist view_name: admin:eventos_evento_changelist
querystr: tipo_evento__categoria__exact=V querystr: tipo_evento__categoria__exact=V
- title: Servidores - title: Servidores
icon: bi bi-person-badge icon: badge
children: children:
- title: Serviços (unidades do ILB) - title: Serviços (unidades do ILB)
view_name: admin:servidores_servico_changelist view_name: admin:servidores_servico_changelist
- title: Servidores e colaboradores - title: Servidores e colaboradores
view_name: admin:servidores_servidor_changelist view_name: admin:servidores_servidor_changelist
- title: Tabelas auxiliares - title: Tabelas auxiliares
icon: bi bi-database icon: database
children: children:
- title: Tipos de órgãos - title: Tipos de órgãos
view_name: admin:casas_tipoorgao_changelist view_name: admin:casas_tipoorgao_changelist
@ -186,21 +186,20 @@ main_menu:
view_name: admin:espacos_espaco_changelist view_name: admin:espacos_espaco_changelist
- title: Recursos para eventos - title: Recursos para eventos
view_name: admin:espacos_recurso_changelist view_name: admin:espacos_recurso_changelist
# contato_menu:
contato_menu: # - title: Casa legislativa
- title: Casa legislativa # icon: account_balance
icon: account_balance # children:
children: # - title: Dados cadastrais
- title: Dados cadastrais # view_name: casas:orgao_update
view_name: casas:orgao_update # - title: Parlamentares
- title: Parlamentares # view_name: parlamentares:parlamentar_listview
view_name: parlamentares:parlamentar_listview # - title: Contatos
- title: Contatos # view_name: casas:funcionario_listview
view_name: casas:funcionario_listview # - title: Informações Interlegis
- title: Informações Interlegis # icon: cloud_done
icon: cloud_done # children:
children: # - title: Solicitar treinamentos
- title: Solicitar treinamentos # view_name: ocorrencias:solicita_oficina_create
view_name: ocorrencias:solicita_oficina_create # - title: Ver ocorrências
- title: Ver ocorrências # view_name: ocorrencias:ocorrencia_listview
view_name: ocorrencias:ocorrencia_listview

13
sigi/settings.py

@ -53,6 +53,7 @@ INSTALLED_APPS = [
"django.contrib.staticfiles", "django.contrib.staticfiles",
"django.contrib.sites", "django.contrib.sites",
"django_extensions", "django_extensions",
"job_controller",
"django_filters", "django_filters",
"localflavor", "localflavor",
"import_export", "import_export",
@ -87,12 +88,8 @@ MIDDLEWARE = [
SITE_ID = 1 SITE_ID = 1
if DEBUG: if DEBUG:
INSTALLED_APPS = [ INSTALLED_APPS += ["debug_toolbar"]
"debug_toolbar", MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"]
] + INSTALLED_APPS
MIDDLEWARE = [
"debug_toolbar.middleware.DebugToolbarMiddleware",
] + MIDDLEWARE
EMAIL_PORT = env("EMAIL_PORT", int, default=25) EMAIL_PORT = env("EMAIL_PORT", int, default=25)
EMAIL_HOST = env("EMAIL_HOST", default="") EMAIL_HOST = env("EMAIL_HOST", default="")
@ -103,9 +100,7 @@ EMAIL_USE_LOCALTIME = env("EMAIL_USE_LOCALTIME", bool, default=False)
EMAIL_USE_TLS = env("EMAIL_USE_TLS", bool, default=False) EMAIL_USE_TLS = env("EMAIL_USE_TLS", bool, default=False)
EMAIL_USE_SSL = env("EMAIL_USE_SSL", bool, default=False) EMAIL_USE_SSL = env("EMAIL_USE_SSL", bool, default=False)
EMAIL_TIMEOUT = env("EMAIL_TIMEOUT", int, default=None) EMAIL_TIMEOUT = env("EMAIL_TIMEOUT", int, default=None)
DEFAULT_FROM_EMAIL = env( DEFAULT_FROM_EMAIL = env("DEFAULT_FROM_EMAIL", default="sigi@interlegis.leg.br")
"DEFAULT_FROM_EMAIL", default="sigi@interlegis.leg.br"
)
SERVER_EMAIL = DEFAULT_FROM_EMAIL SERVER_EMAIL = DEFAULT_FROM_EMAIL
# Database # Database

Loading…
Cancel
Save