/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