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
black==25.1.0
black==26.1.0
ipdb==0.13.13
pygraphviz==1.14
pygraphviz==1.14

59
requirements/requirements.txt

@ -1,34 +1,35 @@
cron-converter==1.2.1
dnspython==2.7.0
docutils==0.21.2
email_validator==2.2.0
gunicorn==23.0.0
Django==6.0.2
django-admin-autocomplete-filter==0.7.1
django-auth-ldap==5.3.0
django-debug-toolbar==6.2.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
inflection==0.5.1
ipython==9.2.0
ipython==9.10.0
ipython_pygments_lexers==1.1.1
moodlepy==0.24.1
pandas==2.2.3
parsel==1.10.0
pillow==11.3.0
psycopg2-binary==2.9.10
python-docx==1.1.2
pandas==3.0.1
parsel==1.11.0
pillow==12.1.1
psycopg2-binary==2.9.11
python-docx==1.2.0
python-magic==0.4.27
PyYAML==6.0.2
requests==2.32.4
uritemplate==4.1.1
weasyprint==65.1
XlsxWriter==3.2.3
Django==5.2.2
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
PyYAML==6.0.3
requests==2.32.5
uritemplate==4.2.0
weasyprint==68.1
xlsxwriter==3.2.9
djbs-theme @ git+https://github.com/interlegis/djbs-theme.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
from django.contrib.auth.models import User
import sys
from django.utils.translation import gettext as _
from django_extensions.management.jobs import DailyJob
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
class Job(JobReportMixin, DailyJob):
class Job(DailyJob):
help = (
"Sanitiza sintaxe dos e-mails dos órgãos cadastrados, "
"removendo os que não são válidos"
)
def do_job(self):
self.report_data = []
def execute(self):
corrigidos = []
apagados = []
total_corrigido = 0
@ -53,53 +49,51 @@ class Job(JobReportMixin, DailyJob):
else:
tentar = False
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:
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.save()
total_apagado += 1
elif orgao.email != email:
corrigidos.append(
_(
f"{orgao.email} corrigido para {email} do órgão "
f"{orgao.nome} ({orgao.id})"
"{email} corrigido para {novo_email} do órgão "
"{nome} ({id})"
).format(
email=orgao.email,
novo_email=email,
nome=orgao.nome,
id=orgao.id,
)
)
total_corrigido += 1
orgao.email = email
orgao.save()
self.report_data.extend(
[
_("RESUMO"),
"------",
"",
]
)
self.report_data.append(_(f"E-mails verificados: {queryset.count()}"))
self.report_data.append(_(f"E-mails corrigidos.: {total_corrigido}"))
self.report_data.append(_(f"E-mails apagados...: {total_apagado}"))
self.report_data.extend(
[
"",
_("E-MAILS CORRIGIDOS"),
"------------------",
"",
]
)
self.report_data.extend(corrigidos)
self.report_data.extend(
[
"",
print(_("RESUMO"), "\n------\n\n")
print(_("E-mails verificados: {count}").format(count=queryset.count()))
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")
print("\n".join(corrigidos))
if total_apagado > 0:
print(
"\n\n",
_("E-MAILS APAGADOS"),
"----------------",
"",
]
)
self.report_data.extend(apagados)
"\n----------------\n\n",
file=sys.stderr,
)
print("\n".join(apagados), file=sys.stderr)

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.conf import settings
from django.contrib.admin.models import ADDITION, CHANGE
from django.core.mail import mail_admins
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 sigi.apps.contatos.models import (
UnidadeFederativa,
@ -12,50 +13,21 @@ from sigi.apps.contatos.models import (
Municipio,
)
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 = _(
"Atualiza Unidades Federativas, mesorregiões, microrregiões e "
"municípios com dados do IBGE"
)
report_template = "contatos/emails/report_atualiza_ibge.rst"
uf_novas = []
uf_atualizadas = []
municipios_novos = []
municipios_atualizados = []
meso_novas = []
meso_atualizadas = []
micro_novas = []
micro_atualizadas = []
sigi_user = None
def do_job(self):
def execute(self):
self.atualiza_ufs()
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):
regioes_map = {
"N": "NO",
"NE": "NE",
"SE": "SE",
"S": "SL",
"CO": "CO",
}
regioes_map = {"N": "NO", "NE": "NE", "SE": "SE", "S": "SL", "CO": "CO"}
for ibge_uf in Estados().json():
regiao = regioes_map[ibge_uf["regiao"]["sigla"]]
try:
@ -71,69 +43,66 @@ class Job(JobReportMixin, MonthlyJob):
populacao=0,
)
sigi_uf.save()
self.uf_novas.append(sigi_uf)
self.admin_log_addition(sigi_uf, "Nova UF encontrada no IBGE")
self.admin_log_addition(
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 (
sigi_uf.nome != ibge_uf["nome"]
or sigi_uf.sigla != ibge_uf["sigla"]
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.sigla = ibge_uf["sigla"]
sigi_uf.regiao = regiao
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):
for ibge_mun in Municipios().json():
uf_id = ibge_mun["microrregiao"]["mesorregiao"]["UF"]["id"]
cod_meso = ibge_mun["microrregiao"]["mesorregiao"]["id"]
cod_micro = int(
str(cod_meso) + str(ibge_mun["microrregiao"]["id"])[-3:]
)
# Atualiza ou cria mesorregião #
if ibge_mun["microrregiao"]:
uf_id = ibge_mun["microrregiao"]["mesorregiao"]["UF"]["id"]
uf_nome = ibge_mun["microrregiao"]["mesorregiao"]["UF"]["nome"]
else:
uf_id = ibge_mun["regiao-imediata"]["regiao-intermediaria"][
"UF"
]["id"]
uf_nome = ibge_mun["regiao-imediata"]["regiao-intermediaria"][
"UF"
]["nome"]
try:
meso = Mesorregiao.objects.get(codigo_ibge=cod_meso)
except Mesorregiao.DoesNotExist:
meso = Mesorregiao(
codigo_ibge=cod_meso,
uf_id=uf_id,
nome=ibge_mun["microrregiao"]["mesorregiao"]["nome"],
)
meso.save()
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"
uf = UnidadeFederativa.objects.get(codigo_ibge=uf_id)
except UnidadeFederativa.DoesNotExist:
print(
_(
"* ERRO: UF {uf_id} - {uf_nome} não encontrada no SIGI"
" ao processar o município do IBGE {mun_id} {mun_nome}"
).format(
uf_id=uf_id, uf_nome=uf_nome, mun_id=ibge_mun["id"]
)
)
if (
micro.nome != ibge_mun["microrregiao"]["nome"]
or micro.mesorregiao != 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")
continue
meso = self.atualiza_meso(ibge_mun, uf)
micro = self.atualiza_micro(ibge_mun, meso)
# Atualiza ou cria o município #
try:
sigi_mun = Municipio.objects.get(codigo_ibge=ibge_mun["id"])
@ -142,12 +111,16 @@ class Job(JobReportMixin, MonthlyJob):
codigo_ibge=ibge_mun["id"],
microrregiao=micro,
nome=ibge_mun["nome"],
uf_id=uf_id,
uf=uf,
populacao=0,
idh=0.0,
)
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(
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.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.uf_id = uf_id
sigi_mun.microrregiao = micro
sigi_mun.save()
self.municipios_atualizados.append(sigi_mun)
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 docutils.core
from django.core.mail import mail_admins
import sys
from django.utils.translation import gettext as _
from django_extensions.management.jobs import HourlyJob
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."
def do_job(self):
def execute(self):
gescon = Gescon.load()
self.send_report_mail = gescon.importa_contratos()
self.report_data = gescon.ultima_importacao.splitlines()
errors = gescon.importa_contratos()
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 datetime
from django.conf import settings
@ -6,7 +7,6 @@ from django.utils import timezone
from django.utils.formats import localize
from django.utils.translation import gettext as _
from django_extensions.management.jobs import HourlyJob
from sigi.apps.utils.management.jobs import JobReportMixin
from sigi.apps.espacos.models import (
Espaco,
Recurso,
@ -22,12 +22,8 @@ DEPARA_SITUACAO = {
}
class Job(JobReportMixin, HourlyJob):
class Job(HourlyJob):
help = "Sincroniza dados do sistema de reserva de salas"
report_data = []
resumo = []
infos = []
erros = []
@property
def auth_data(self):
@ -36,11 +32,7 @@ class Job(JobReportMixin, HourlyJob):
settings.RESERVA_SALA_API_PASSWORD,
)
def do_job(self):
self.resumo = []
self.infos = []
self.erros = []
def execute(self):
if (
settings.RESERVA_SALA_BASE_URL is None
or settings.RESERVA_SALA_API_USER is None
@ -54,11 +46,7 @@ class Job(JobReportMixin, HourlyJob):
self.carrega_reservas()
def carrega_salas(self):
tit = ["", "\t*Carga de salas*", ""]
self.infos.extend(tit)
self.erros.extend(tit)
self.resumo.extend(tit)
print("\n", _("Carga de salas"), "\n==============\n")
tot_novas = 0
tot_erros = 0
tot_atualizadas = 0
@ -66,11 +54,13 @@ class Job(JobReportMixin, HourlyJob):
settings.RESERVA_SALA_BASE_URL + "salas", auth=self.auth_data
)
if not req.ok:
self.erros.append(
print(
"\t",
_(
"\t* Erro de autenticação na API do sistema de reserva "
f"de salas, com a mensagem *{req.reason}*"
)
"* Erro de autenticação na API do sistema de reserva "
"de salas, com a mensagem *{reason}*"
).format(reason=req.reason),
file=sys.stderr,
)
return
for sala in req.json():
@ -87,21 +77,28 @@ class Job(JobReportMixin, HourlyJob):
id_sala=sala["id"],
)
espaco.save()
self.infos.append(
print(
"\t",
_(
f"\t* Criado espaço *{espaco.id}* para a "
f"sala *{sala['id']} - {sala['nome']}*"
)
"* Criado espaço *{espaco_id}* para a "
"sala *{sala_id} - {sala_nome}*"
).format(
espaco_id=espaco.id,
sala_id=sala["id"],
sala_nome=sala["nome"],
),
)
tot_novas += 1
continue
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. "
f"id_sala={sala['id']}"
)
"id_sala={sala_id}"
).format(sala_id=sala["id"]),
file=sys.stderr,
)
tot_erros += 1
continue
@ -119,25 +116,21 @@ class Job(JobReportMixin, HourlyJob):
espaco.local = sala["local"]
espaco.capacidade = sala["capacidade"]
espaco.save()
self.infos.append(
print(
"\t",
_(
f"\t* Espaço *{espaco.id}* atualizado com novos dados "
f"da sala *{sala['id']}*"
)
"* Espaço *{espaco_id}* atualizado com novos dados "
"da sala *{sala_id}*"
).format(espaco_id=espaco.id, sala_id=sala["id"]),
)
tot_atualizadas += 1
self.resumo.append(
_(f"\t* Total de salas processadas: {len(req.json())}")
)
self.resumo.append(_(f"\t* Novos espaços criados: {tot_novas}"))
self.resumo.append(_(f"\t* Espaços atualizados: {tot_atualizadas}"))
self.resumo.append(_(f"\t* Erros encontrados nas salas: {tot_erros}"))
print("\t", _("* Total de salas processadas: %s") % len(req.json()))
print("\t", _("* Novos espaços criados: %s") % tot_novas)
print("\t", _("* Espaços atualizados: %s") % tot_atualizadas)
print("\t", _("* Erros encontrados nas salas: %s") % tot_erros)
def carrega_recursos(self):
tit = ["", "\t*Carga de recursos*", ""]
self.infos.extend(tit)
self.erros.extend(tit)
self.resumo.extend(tit)
print("\n", _("Carga de recursos"), "\n=================\n")
tot_novas = 0
tot_erros = 0
@ -148,11 +141,14 @@ class Job(JobReportMixin, HourlyJob):
auth=self.auth_data,
)
if not req.ok:
self.erros.append(
print(
"\t",
_(
"\t* Erro na API do sistema de reserva ao ler "
f"equipamentos, com a mensagem *{req.reason}*"
"* Erro na API do sistema de reserva ao ler "
"equipamentos, com a mensagem *%s*"
)
% req.reason,
file=sys.stderr,
)
return
@ -170,53 +166,58 @@ class Job(JobReportMixin, HourlyJob):
id_equipamento=equipamento["id"],
)
recurso.save()
self.infos.append(
f"\t* Recurso *{recurso}* criado a partir do equipamento *{equipamento['id']} - {equipamento['nome']}*"
print(
"\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
continue
except Recurso.MultipleObjectsReturned:
lista = ", ".join(
[
str(r)
for r in Recurso.objects.filter(
id_equipamento=equipamento["id"]
)
]
)
self.erros.append(
f"\t* O equipamento *{equipamento['id']} - "
f"{equipamento['nome']}* possui os seguintes recursos "
f"com mesmo ID no SIGI: *{lista}*"
print(
"\t",
_(
"* O equipamento *{id} - {nome}* possui os seguintes "
"recursos com mesmo ID no SIGI:"
).format(id=equipamento["id"], nome=equipamento["nome"]),
file=sys.stderr,
)
for r in Recurso.objects.filter(
id_equipamento=equipamento["id"]
):
print("\t\t -", str(r), file=sys.stderr)
tot_erros += 1
continue
if equipamento["nome"] != recurso.nome:
recurso.nome = equipamento["nome"]
recurso.save()
self.infos.append(
f"\t* Recurso *{str(recurso)}* atualizado com as alterações "
f"do equipamento *{equipamento['id']}*"
print(
"\t",
_(
"* Recurso *{recurso}* atualizado com as alterações "
"do equipamento *{id}*"
).format(recurso=str(recurso), id=equipamento["id"]),
)
tot_atualizadas += 1
self.resumo.append(
_(f"\t* Total de equipamentos processados: {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", _("* Total de equipamentos processados: %s") % len(req.json())
)
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(
self,
ontem=(timezone.localdate() - timezone.timedelta(days=1)).isoformat(),
):
tit = ["", "\t*Carga de reservas*", ""]
self.infos.extend(tit)
self.erros.extend(tit)
self.resumo.extend(tit)
print("\n", _("Carga de reservas"), "\n=================\n")
tot_processadas = 0
tot_novas = 0
@ -231,13 +232,16 @@ class Job(JobReportMixin, HourlyJob):
auth=self.auth_data,
)
if not req.ok:
self.erros.append(
print(
"\t",
_(
"\t* Erro na API do sistema de reserva ao ler "
f"reservas da sala *{espaco.id_sala}*, com data de "
f"início maior que *{ontem}*, "
f"com a mensagem *{req.reason}*"
)
"* Erro na API do sistema de reserva ao ler reservas "
"da sala *{id_sala}*, com data de início maior que "
"*{ontem}*, com a mensagem *{msg}*"
).format(
id_sala=espaco.id_sala, ontem=ontem, msg=req.reason
),
file=sys.stderr,
)
continue
tot_processadas += len(req.json())
@ -258,12 +262,8 @@ class Job(JobReportMixin, HourlyJob):
reserva["coordenador"] = reserva["coordenador"][:100]
reserva["ramal"] = reserva["ramal"][:100]
data_inicio = datetime.date.fromisoformat(
reserva["dataInicio"]
)
hora_inicio = datetime.time.fromisoformat(
reserva["horaInicio"]
)
data_inicio = datetime.date.fromisoformat(reserva["dataInicio"])
hora_inicio = datetime.time.fromisoformat(reserva["horaInicio"])
data_termino = datetime.date.fromisoformat(reserva["dataFim"])
hora_termino = datetime.time.fromisoformat(reserva["horaFim"])
status = DEPARA_SITUACAO[reserva["situacao"]]
@ -277,9 +277,7 @@ class Job(JobReportMixin, HourlyJob):
continue
# Tratar os demais casos
try:
reserva_sigi = Reserva.objects.get(
id_reserva=reserva["id"]
)
reserva_sigi = Reserva.objects.get(id_reserva=reserva["id"])
except Reserva.DoesNotExist:
conflitos = self.verifica_conflito(
espaco,
@ -320,23 +318,36 @@ class Job(JobReportMixin, HourlyJob):
)
if reserva_sigi.status == Reserva.STATUS_CONFLITO:
# Reportar como erro se a reserva é conflitante
lista = ", ".join([str(c) for c in conflitos])
self.erros.append(
f"\t* A reserva *{reserva['id']} - "
f"{reserva['evento']}"
"* do sistema de reservas conflita com "
"a(s) seguinte(s) reserva(s) do SIGI: "
f"*{lista}*. e foi copiada para o SIGI "
"como conflitante."
print(
"\t",
_(
"* A reserva *{id} - {evento}* "
"do sistema de reservas conflita com "
"a(s) seguinte(s) reserva(s) do SIGI: "
"*{lista}*. e foi copiada para o SIGI "
"como conflitante."
).format(
id=reserva["id"],
evento=reserva["evento"],
lista=", ".join(
[str(c) for c in conflitos]
),
),
file=sys.stderr,
)
tot_erros += 1
else:
# Reportar como nova se o status for cancelada
self.infos.append(
f"\t* Reserva *{str(reserva_sigi)}* "
"criada no SIGI a partir da reserva "
f"*{reserva['descricao']}* "
"do sistema de reservas"
print(
"\t",
_(
"* 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
continue
@ -350,22 +361,28 @@ class Job(JobReportMixin, HourlyJob):
hora_inicio,
hora_termino,
)
self.infos.append(
f"\t* Reserva *{str(reserva_sigi)}* criada no SIGI"
f" a partir da reserva *{reserva['descricao']}* "
"do sistema de reservas"
print(
"\t",
_(
"* 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
continue
except Reserva.MultipleObjectsReturned:
# 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. "
"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
continue
@ -392,31 +409,49 @@ class Job(JobReportMixin, HourlyJob):
reserva_sigi.data_termino = data_termino
reserva_sigi.hora_inicio = hora_inicio
reserva_sigi.hora_termino = hora_termino
self.infos.append(
f"\t* *{str(reserva_sigi)}* mudou para o período "
f"de *{localize(data_inicio)} "
f"{localize(hora_inicio)}* a "
f"*{localize(data_termino)} "
f"{localize(hora_termino)}*"
print(
"\t",
_(
"* *{res}* mudou para o período de "
"*{dt_inicio} {hr_inicio} a *{dt_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
else:
lista = ", ".join([str(c) for c in conflitos])
self.erros.append(
f"\t* A reserva *{reserva['evento']}* no sistema "
"de reservas mudou de data, mas esta mudança não "
"pode ser aplicada no SIGI pois gera conflito "
"com a(s) seguinte(s) outra(s) reserva(s): "
f"*{lista}*"
print(
"\t",
_(
"* A reserva *{res}* no sistema de reservas "
"mudou de data, mas esta mudança não pode ser "
"aplicada no SIGI pois gera conflito com a(s) "
"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
continue
# Verificar outras atualizações
if reserva_sigi.status != status:
reserva_sigi.status = status
self.infos.append(
f"\t* A reserva SIGI *{str(reserva_sigi)}* mudou de "
f"status para *{reserva_sigi.get_status_display()}*"
print(
"\t",
_(
"* A reserva SIGI *{res}* mudou de status para "
"*{status}*"
).format(
res=str(reserva_sigi),
status=reserva_sigi.get_status_display(),
),
)
atualizou = True
rr = (
@ -450,31 +485,32 @@ class Job(JobReportMixin, HourlyJob):
reserva_sigi.contato = reserva["coordenador"]
reserva_sigi.telefone_contato = reserva["ramal"]
reserva_sigi.save()
self.infos.append(
f"\t* A reserva SIGI *{str(reserva_sigi)}* foi "
"atualizada com as alterações da "
f"reserva *{reserva['id']}*"
print(
"\t",
_(
"* A reserva SIGI *{res}* foi atualizada com as "
"alterações da reserva *{id}*"
).format(res=str(reserva_sigi), id=reserva["id"]),
)
atualizou = True
if self.recursos_solicitados(
reserva_sigi, reserva["equipamentos"]
):
self.infos.append(
"\t* Os recursos solicitados da reserva SIGI "
f"*{str(reserva_sigi)}* foram atualizados"
print(
"\t",
_(
"* Os recursos solicitados da reserva SIGI "
"*{res}* foram atualizados"
).format(res=str(reserva_sigi)),
)
atualizou = True
if atualizou:
tot_atualizadas += 1
self.resumo.append(
_(f"\t* Total de reservas processadas: {tot_processadas}")
)
self.resumo.append(_(f"\t* Novas reservas criadas: {tot_novas}"))
self.resumo.append(_(f"\t* Reservas atualizados: {tot_atualizadas}"))
self.resumo.append(_(f"\t* Reservas excluídas: {tot_excluidas}"))
self.resumo.append(
_(f"\t* Erros encontrados nas reservas: {tot_erros}")
)
print("\t", _("* Total de reservas processadas: %s") % tot_processadas)
print("\t", _("* Novas reservas criadas: %s") % tot_novas)
print("\t", _("* Reservas atualizados: %s") % tot_atualizadas)
print("\t", _("* Reservas excluídas: %s") % tot_excluidas)
print("\t", _("* Erros encontrados nas reservas: %s") % tot_erros)
def verifica_conflito(
self,
@ -570,37 +606,3 @@ class Job(JobReportMixin, HourlyJob):
)
atualizou = True
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.db.models import Q
from django.conf import settings
from django.utils import timezone
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.eventos.models import Evento
INSCRICOES_ENCERRADAS = _("INSCRIÇÕES ENCERRADAS")
class Job(JobReportMixin, DailyJob):
class Job(DailyJob):
help = _(
"Encerra inscrições e despublica eventos do Portal se já ocorreram"
)
report_data = []
def do_job(self):
def execute(self):
dias_a_retroagir = int(Config.get_param("ENCERRA_INSCRICAO")[0])
self.report_data = []
hoje = timezone.localtime().replace(hour=23, minute=59, second=59)
retroagir = hoje - timezone.timedelta(days=dias_a_retroagir)
total_encerrar = 0
total_despublicar = 0
encerrar_inscricao = (
Evento.objects.exclude(publicar=False)
@ -28,54 +27,46 @@ class Job(JobReportMixin, DailyJob):
.exclude(chave_inscricao=INSCRICOES_ENCERRADAS)
)
self.report_data.extend(
[
"",
"",
_("Inscrições encerradas"),
"---------------------",
"",
]
)
self.report_data.extend(
[f"{e.nome} ({e.id})" for e in encerrar_inscricao]
)
if encerrar_inscricao.exists():
print(_("Inscrições encerradas"))
print("---------------------")
print("")
print("\n".join([f"{e.nome} ({e.id})" for e in encerrar_inscricao]))
print("")
total_encerrar = encerrar_inscricao.update(
chave_inscricao=INSCRICOES_ENCERRADAS
)
total_encerrar = encerrar_inscricao.update(
chave_inscricao=INSCRICOES_ENCERRADAS
)
despublicar = Evento.objects.exclude(publicar=False).filter(
data_termino__lte=retroagir
)
self.report_data.extend(
[
"",
"",
_("Despublicados"),
"-------------",
"",
]
)
self.report_data.extend([f"{e.nome} ({e.id})" for e in despublicar])
if despublicar.exists():
print("")
print(_("Despublicados"))
print("-------------")
print("")
print("\n".join([f"{e.nome} ({e.id})" for e in despublicar]))
print("")
total_despublicar = despublicar.update(publicar=False)
total_despublicar = despublicar.update(publicar=False)
self.report_data.extend(
[
"",
"",
_("RESUMO"),
"------",
"",
_(
"* Total de eventos alterados para inscrições encerradas: "
f"{total_encerrar}"
),
_(
"* Total de eventos despublicados do portal: "
f"{total_despublicar}"
),
]
)
if total_encerrar > 0 or total_despublicar > 0:
print("")
print(_("RESUMO"))
print("------")
print("")
if total_encerrar > 0:
print(
_(
"* Total de eventos alterados para inscrições "
"encerradas: {count}"
).format(count=total_encerrar)
)
if total_despublicar > 0:
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.db.models import Q
from django.conf import settings
from django.forms.models import model_to_dict
from django.utils import timezone
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.saberes import SaberesSyncException
class Job(JobReportMixin, DailyJob):
class Job(DailyJob):
help = _("Sincroniza número de inscritos e aprovados com o Saberes.")
report_data = []
def do_job(self):
self.report_data = []
infos = []
errors = []
def execute(self):
total_sinc = 0
total_ok = 0
total_erros = 0
@ -37,39 +32,36 @@ class Job(JobReportMixin, DailyJob):
try:
evento.sincroniza_saberes()
if model_to_dict(evento) != initial:
infos.append(
f"Evento {evento.nome} ({evento.id}) atualizado"
print(
_("Evento {nome} ({id}) atualizado").format(
nome=evento.nome, id=evento.id
)
)
total_sinc += 1
else:
total_ok += 1
except SaberesSyncException as err:
errors.append(
print(
_(
f"Erro ao sincronizar evento {evento.nome} "
f"({evento.id}), com a mensagem '{err.message}'"
)
"Erro ao sincronizar evento {nome} ({id}), "
"com a mensagem '{message}'"
).format(
nome=evento.nome, id=evento.id, message=err.message
),
file=sys.stderr,
)
total_erros += 1
self.report_data.append(_("ATUALIZAÇÕES"))
self.report_data.append("------------")
self.report_data.append("")
self.report_data.extend(infos)
self.report_data.append("")
self.report_data.append(_("ERROS"))
self.report_data.append("-----")
self.report_data.append("")
self.report_data.extend(errors)
self.report_data.append("")
self.report_data.append(_("RESUMO"))
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("")
if eventos.count() > 0:
print(_("RESUMO"))
print("------")
print("")
print(
_("* Eventos a sincronizar: {count}").format(
count=eventos.count()
)
)
print(_("* Eventos atualizados: {count}").format(count=total_sinc))
print(_("* Já estavam corretos: {count}").format(count=total_ok))
print(_("* Erros: {count}").format(count=total_erros))
print("")

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

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

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

@ -1,7 +1,9 @@
import json
import shutil
import sys
from django.conf import settings
from django.db.models import Q
from django.template.loader import render_to_string
from django.utils import timezone
from django.utils.translation import gettext as _
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.casas.models import Orgao
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")
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")
report_template = "servicos/emails/report_sincroniza_dns.rst"
nomes_gerados = None
report_data = {}
def do_job(self):
def execute(self):
self.report_data[LOG_GERAL] = get_log_entry()
if (
@ -80,7 +81,15 @@ class Job(JobReportMixin, DailyJob):
try:
shutil.rmtree(settings.REGISTRO_PATH)
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):
dominio = dns_rec["name"][:-1]

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

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

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

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

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

@ -1,9 +1,10 @@
import sys
import ldap
from django.conf import settings
from django.utils.translation import gettext as _
from django_auth_ldap.config import _DeepStringCoder
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.utils import (
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")
report_data = []
def do_job(self):
def execute(self):
coder = _DeepStringCoder("utf8")
connect = ldap.initialize(settings.AUTH_LDAP_SERVER_URI)
@ -60,21 +61,15 @@ class Job(JobReportMixin, DailyJob):
for dn, ldap_user in decoded_data:
total_ldap += 1
resp, servidor = servidor_create_or_update(
ldap_attrs=ldap_user
)
resp, servidor = servidor_create_or_update(ldap_attrs=ldap_user)
if servidor.user:
user_staff_and_group(servidor.user, ldap_user)
if resp == servidor_create_or_update.UPDATED:
total_update += 1
self.report_data.append(
_(f"{servidor.nome_completo} atualizado")
)
print(f"{servidor.nome_completo} atualizado")
elif resp == servidor_create_or_update.CREATED:
total_create += 1
self.report_data.append(
_(f"{servidor.nome_completo} criado")
)
print(f"{servidor.nome_completo} criado")
if dn in servidores:
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
self.report_data.append("")
self.report_data.append(
_(
nao_encontrados = Servidor.objects.filter(
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 "
"como Externos"
"como Externos",
file=sys.stderr,
)
)
self.report_data.append("=" * 72)
self.report_data.append("")
for s in Servidor.objects.filter(
ldap_dn="", externo=False, sigi=False
).order_by("nome_completo"):
self.report_data.append(f"- {s.nome_completo}")
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("")
for s in nao_encontrados:
print(f"- {s.nome_completo}", file=sys.stderr)
print(f"* {total_ldap} usuários lidos do LDAP")
print(f"* {total_create} novos servidores criados")
print(f"* {total_update} servidores atualizados")
print(f"* {total_deactive} usuários desativados")
print(f"* {nao_encontrados.count()} servidores não encontrados no LDAP")

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 tinymce.models import HTMLField
from tinymce.widgets import AdminTinyMCE
from sigi.apps.utils.models import SigiAlert, Cronjob, JobSchedule, 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 ""
from sigi.apps.utils.models import SigiAlert, Config
@admin.register(SigiAlert)
@ -56,179 +21,6 @@ class SigiAlertAdmin(admin.ModelAdmin):
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)
class ConfigAdmin(admin.ModelAdmin):
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"
class JobReportMixin:
error_report_template = "emails/report_error.rst"
report_template = "emails/base_report.rst"
report_data = None
sys_user = None
send_report_mail = True
class AdminJobMixin:
_sys_user = None
def execute(self):
start_time = datetime.datetime.now()
def get_sys_user(self):
if self._sys_user is None:
try:
from sigi.apps.servidores.models import Servidor
try:
from sigi.apps.servidores.models import Servidor
self.sys_user = Servidor.objects.get(sigi=True).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")
self._sys_user = Servidor.objects.get(sigi=True).user
except Exception:
pass
return self._sys_user
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
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,
object_id=object.id,
object_id=object.pk,
object_repr=str(object),
action_flag=action_flag,
change_message=message,
@ -63,78 +49,3 @@ class JobReportMixin:
def admin_log_change(self, object, 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
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):
PARAMETRO_CHOICES = (
("ENCERRA_INSCRICAO", _("Encerra inscrições de oficinas no Portal")),

69
sigi/menu_conf.yaml

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

13
sigi/settings.py

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

Loading…
Cancel
Save