diff --git a/requirements/dev-requirements.txt b/requirements/dev-requirements.txt index 7fdc8a8..fbbddf5 100644 --- a/requirements/dev-requirements.txt +++ b/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 \ No newline at end of file +pygraphviz==1.14 diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 8c03f90..b6b528e 100644 --- a/requirements/requirements.txt +++ b/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 diff --git a/sigi/apps/casas/jobs/daily/sanitiza_email_orgao.py b/sigi/apps/casas/jobs/daily/sanitiza_email_orgao.py index 460b0ef..81aa831 100644 --- a/sigi/apps/casas/jobs/daily/sanitiza_email_orgao.py +++ b/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) diff --git a/sigi/apps/casas/jobs/daily/usuario_contato.py b/sigi/apps/casas/jobs/daily/usuario_contato.py deleted file mode 100644 index 2d13d36..0000000 --- a/sigi/apps/casas/jobs/daily/usuario_contato.py +++ /dev/null @@ -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")) diff --git a/sigi/apps/contatos/jobs/monthly/atualiza_ibge.py b/sigi/apps/contatos/jobs/monthly/atualiza_ibge.py index 1df8e16..159572f 100644 --- a/sigi/apps/contatos/jobs/monthly/atualiza_ibge.py +++ b/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 diff --git a/sigi/apps/convenios/jobs/hourly/importa_gescon.py b/sigi/apps/convenios/jobs/hourly/importa_gescon.py index 518c03c..cfd60fd 100644 --- a/sigi/apps/convenios/jobs/hourly/importa_gescon.py +++ b/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) diff --git a/sigi/apps/espacos/jobs/hourly/sincroniza_reservas.py b/sigi/apps/espacos/jobs/hourly/sincroniza_reservas.py index 1f56d65..1bd0ec5 100644 --- a/sigi/apps/espacos/jobs/hourly/sincroniza_reservas.py +++ b/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) diff --git a/sigi/apps/eventos/jobs/daily/encerra_inscricao.py b/sigi/apps/eventos/jobs/daily/encerra_inscricao.py index 437ba6e..a4c00d9 100644 --- a/sigi/apps/eventos/jobs/daily/encerra_inscricao.py +++ b/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) + ) diff --git a/sigi/apps/eventos/jobs/daily/sincroniza_saberes.py b/sigi/apps/eventos/jobs/daily/sincroniza_saberes.py index d45f47b..f3dd4d4 100644 --- a/sigi/apps/eventos/jobs/daily/sincroniza_saberes.py +++ b/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("") diff --git a/sigi/apps/parlamentares/jobs/minutely/importa_parlamentar.py b/sigi/apps/parlamentares/jobs/minutely/importa_parlamentar.py index 44bd858..ad2d478 100644 --- a/sigi/apps/parlamentares/jobs/minutely/importa_parlamentar.py +++ b/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"]: diff --git a/sigi/apps/servicos/jobs/daily/sincroniza_dns.py b/sigi/apps/servicos/jobs/daily/sincroniza_dns.py index 028d909..2217464 100644 --- a/sigi/apps/servicos/jobs/daily/sincroniza_dns.py +++ b/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] diff --git a/sigi/apps/servicos/jobs/daily/sincroniza_rancher.py b/sigi/apps/servicos/jobs/daily/sincroniza_rancher.py index 9843466..ca4505a 100644 --- a/sigi/apps/servicos/jobs/daily/sincroniza_rancher.py +++ b/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") diff --git a/sigi/apps/servicos/jobs/daily/verifica_dominios.py b/sigi/apps/servicos/jobs/daily/verifica_dominios.py index e2277f1..f4220f9 100644 --- a/sigi/apps/servicos/jobs/daily/verifica_dominios.py +++ b/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}") diff --git a/sigi/apps/servidores/jobs/daily/sync_ldap.py b/sigi/apps/servidores/jobs/daily/sync_ldap.py index 06ae469..04f5963 100644 --- a/sigi/apps/servidores/jobs/daily/sync_ldap.py +++ b/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") diff --git a/sigi/apps/utils/admin.py b/sigi/apps/utils/admin.py index 598da0c..9344e96 100644 --- a/sigi/apps/utils/admin.py +++ b/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"" - "play_arrow" - ) - elif sched.status == JobSchedule.STATUS_CONCLUIDO: - url = reverse("admin:utils_jobschedule_change", args=[sched.id]) - return ( - f"" - "description" - ) - 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( - "/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"" "play_arrow" - ) - - 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( - "/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"" - "play_arrow" - ) - 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"] diff --git a/sigi/apps/utils/jobs/__init__.py b/sigi/apps/utils/jobs/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/sigi/apps/utils/jobs/daily/__init__.py b/sigi/apps/utils/jobs/daily/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/sigi/apps/utils/jobs/hourly/__init__.py b/sigi/apps/utils/jobs/hourly/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/sigi/apps/utils/jobs/job_controller.py b/sigi/apps/utils/jobs/job_controller.py deleted file mode 100644 index 00b39d9..0000000 --- a/sigi/apps/utils/jobs/job_controller.py +++ /dev/null @@ -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}'") diff --git a/sigi/apps/utils/jobs/monthly/__init__.py b/sigi/apps/utils/jobs/monthly/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/sigi/apps/utils/jobs/weekly/__init__.py b/sigi/apps/utils/jobs/weekly/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/sigi/apps/utils/jobs/yearly/__init__.py b/sigi/apps/utils/jobs/yearly/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/sigi/apps/utils/management/jobs.py b/sigi/apps/utils/management/jobs.py index 60fa857..0d64449 100644 --- a/sigi/apps/utils/management/jobs.py +++ b/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) diff --git a/sigi/apps/utils/migrations/0007_remove_jobschedule_job_delete_cronjob_and_more.py b/sigi/apps/utils/migrations/0007_remove_jobschedule_job_delete_cronjob_and_more.py new file mode 100644 index 0000000..0877812 --- /dev/null +++ b/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", + ), + ] diff --git a/sigi/apps/utils/models.py b/sigi/apps/utils/models.py index 519a7ee..b56f7aa 100644 --- a/sigi/apps/utils/models.py +++ b/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: " - "" - "CronHowTo" - "" - ), - ) - 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"

Erro ao converter o log para HTML: {str(e)}

" - - 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"

Erro ao converter o log para HTML: {str(e)}

" - ) - - message = "\n\n".join(message_lines) - html_message = "

".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")), diff --git a/sigi/menu_conf.yaml b/sigi/menu_conf.yaml index b8f36f0..0d36533 100644 --- a/sigi/menu_conf.yaml +++ b/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 diff --git a/sigi/settings.py b/sigi/settings.py index 3c9cf3d..7c3dc87 100644 --- a/sigi/settings.py +++ b/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