From 36241668b0a2b45322ffb96382f3f7074187fb06 Mon Sep 17 00:00:00 2001 From: lucasmndc Date: Tue, 30 Jul 2024 13:16:18 -0300 Subject: [PATCH 1/4] =?UTF-8?q?adi=C3=A7=C3=A3o=20de=20digest=20email?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sigi/apps/utils/admin.py | 6 ++ sigi/apps/utils/management/jobs.py | 34 ++++---- ...tinatario_email_cronjob_digest_and_more.py | 41 +++++++++ sigi/apps/utils/models.py | 85 +++++++++++++++++++ 4 files changed, 149 insertions(+), 17 deletions(-) create mode 100644 sigi/apps/utils/migrations/0005_cronjob_destinatario_email_cronjob_digest_and_more.py diff --git a/sigi/apps/utils/admin.py b/sigi/apps/utils/admin.py index 268c1c0..0696222 100644 --- a/sigi/apps/utils/admin.py +++ b/sigi/apps/utils/admin.py @@ -65,6 +65,8 @@ class CronjobAdmin(admin.ModelAdmin): "expressao_cron", "get_schedule", "get_runner", + "destinatario_email", + "digest", ) fields = [ "job_name", @@ -72,6 +74,8 @@ class CronjobAdmin(admin.ModelAdmin): "get_help", "expressao_cron", "manter_logs", + "destinatario_email", + "digest", ] readonly_fields = ("job_name", "app_name", "get_help") inlines = [JobScheduleInline] @@ -157,6 +161,7 @@ class JobScheduleAdmin(admin.ModelAdmin): "iniciado", "tempo_gasto", "get_runner", + "enviado", ] fields = [ "job", @@ -164,6 +169,7 @@ class JobScheduleAdmin(admin.ModelAdmin): "iniciar", "iniciado", "tempo_gasto", + "enviado", ] readonly_fields = fields list_filter = ("status", "job") diff --git a/sigi/apps/utils/management/jobs.py b/sigi/apps/utils/management/jobs.py index 8638bef..60fa857 100644 --- a/sigi/apps/utils/management/jobs.py +++ b/sigi/apps/utils/management/jobs.py @@ -80,14 +80,14 @@ class JobReportMixin: "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, - ) + # 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): @@ -128,13 +128,13 @@ class JobReportMixin: 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, - ) + # 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/0005_cronjob_destinatario_email_cronjob_digest_and_more.py b/sigi/apps/utils/migrations/0005_cronjob_destinatario_email_cronjob_digest_and_more.py new file mode 100644 index 0000000..960008a --- /dev/null +++ b/sigi/apps/utils/migrations/0005_cronjob_destinatario_email_cronjob_digest_and_more.py @@ -0,0 +1,41 @@ +# Generated by Django 5.0.6 on 2024-07-30 15:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("utils", "0004_alter_jobschedule_options_cronjob_manter_logs"), + ] + + operations = [ + migrations.AddField( + model_name="cronjob", + name="destinatario_email", + field=models.TextField( + blank=True, + help_text="Insira um endereço de e-mail por linha.", + verbose_name="destinatário(s) de e-mail", + ), + ), + migrations.AddField( + model_name="cronjob", + name="digest", + field=models.CharField( + choices=[ + ("N", "Enviar sem digest"), + ("D", "Enviar com digest diário"), + ("S", "Enviar com digest semanal"), + ], + default="N", + max_length=1, + verbose_name="digest", + ), + ), + migrations.AddField( + model_name="jobschedule", + name="enviado", + field=models.BooleanField(default=False, verbose_name="enviado"), + ), + ] diff --git a/sigi/apps/utils/models.py b/sigi/apps/utils/models.py index 0642b11..c7f6a4c 100644 --- a/sigi/apps/utils/models.py +++ b/sigi/apps/utils/models.py @@ -9,6 +9,9 @@ from django.utils.formats import localize from django.utils.translation import gettext as _ from django_extensions.management.jobs import get_job, get_jobs from tinymce.models import HTMLField +from django.core.mail import send_mail +from django.conf import settings +from datetime import timedelta class SigiAlert(models.Model): @@ -59,6 +62,34 @@ class Cronjob(models.Model): 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, + ) + + def get_emails_list(self): + return [ + email.strip() + for email in self.destinatario_email.splitlines() + if email.strip() + ] + + def __str__(self): + return f"Destinatários: {', '.join(self.get_emails_list())}" + + 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", + ) + class Meta: ordering = ("app_name", "job_name") verbose_name = _("Cron job") @@ -147,6 +178,7 @@ class JobSchedule(models.Model): resultado = models.TextField( _("resultado da execução"), blank=True, editable=False ) + enviado = models.BooleanField(_("enviado"), default=False) class Meta: ordering = ("-iniciar",) @@ -190,6 +222,59 @@ class JobSchedule(models.Model): self.tempo_gasto = timezone.localtime() - self.iniciado self.save() + if self.job.destinatario_email == "": + return + + if self.job.digest == "N": + 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=self.resultado, + ) + self.enviado = True + self.save() + + elif self.job.digest == "D": + self.send_digest_email(frequency="daily") + + elif self.job.digest == "S": + self.send_digest_email(frequency="weekly") + + def send_digest_email(self, frequency): + """Envia email de digest diário ou semanal.""" + now = timezone.localtime() + if frequency == "daily": + start_time = now - timedelta(days=1) + elif frequency == "weekly": + start_time = now - timedelta(weeks=1) + else: + raise ValueError("Invalid frequency for digest email.") + + job_schedules = JobSchedule.objects.filter( + job=self.job, + status=JobSchedule.STATUS_CONCLUIDO, + iniciado__gte=start_time, + enviado=False, + ) + + if job_schedules.exists(): + message = "\n\n".join( + [f"{js.iniciado}: {js.resultado}" for js in job_schedules] + ) + 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=message, + ) + + job_schedules.update(enviado=True) + class Config(models.Model): PARAMETRO_CHOICES = ( From 9dde6f019a71a93721cf29515fce4f0e71f48600 Mon Sep 17 00:00:00 2001 From: lucasmndc Date: Wed, 7 Aug 2024 11:55:41 -0300 Subject: [PATCH 2/4] =?UTF-8?q?Adi=C3=A7=C3=A3o=20de=20hor=C3=A1rio=20est?= =?UTF-8?q?=C3=A1tico?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sigi/apps/utils/models.py | 56 ++++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/sigi/apps/utils/models.py b/sigi/apps/utils/models.py index c7f6a4c..9fe630a 100644 --- a/sigi/apps/utils/models.py +++ b/sigi/apps/utils/models.py @@ -38,6 +38,17 @@ class SigiAlert(models.Model): 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( @@ -78,18 +89,6 @@ class Cronjob(models.Model): def __str__(self): return f"Destinatários: {', '.join(self.get_emails_list())}" - 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", - ) - class Meta: ordering = ("app_name", "job_name") verbose_name = _("Cron job") @@ -246,24 +245,45 @@ class JobSchedule(models.Model): def send_digest_email(self, frequency): """Envia email de digest diário ou semanal.""" now = timezone.localtime() + + # Definir horário estático + send_time = timezone.datetime.min.time() # Meia-noite + today = now.date() + if frequency == "daily": - start_time = now - timedelta(days=1) + # Verifica se é meia-noite + if now.time() != send_time: + return + + # Definir início do período como o dia anterior + period_start = today - timedelta(days=1) + elif frequency == "weekly": - start_time = now - timedelta(weeks=1) + # Verifica se é segunda-feira e se é meia-noite + if today.weekday() != 0 or now.time() != send_time: + return + + # Define o início do período como segunda-feira da semana passada + period_start = today - timedelta(days=7) + else: raise ValueError("Invalid frequency for digest email.") job_schedules = JobSchedule.objects.filter( job=self.job, status=JobSchedule.STATUS_CONCLUIDO, - iniciado__gte=start_time, + iniciado__gte=period_start, enviado=False, ) if job_schedules.exists(): - message = "\n\n".join( - [f"{js.iniciado}: {js.resultado}" for js in job_schedules] - ) + message_lines = [] + for js in job_schedules: + message_lines.append( + f"{localize(js.iniciado)}: {js.resultado}" + ) + message = "\n\n".join(message_lines) + send_mail( subject=f"Digest JOB: {self.job.job_name} ({frequency})", message=message, From 4fb6acb01c5e32a4e3c1d470af5505a735258b09 Mon Sep 17 00:00:00 2001 From: lucasmndc Date: Mon, 12 Aug 2024 12:19:43 -0300 Subject: [PATCH 3/4] =?UTF-8?q?Atualiza=C3=A7=C3=A3o=20do=20digest?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sigi/apps/utils/admin.py | 4 +-- sigi/apps/utils/models.py | 72 +++++++++++++++++++-------------------- 2 files changed, 37 insertions(+), 39 deletions(-) diff --git a/sigi/apps/utils/admin.py b/sigi/apps/utils/admin.py index 0696222..a444852 100644 --- a/sigi/apps/utils/admin.py +++ b/sigi/apps/utils/admin.py @@ -67,6 +67,7 @@ class CronjobAdmin(admin.ModelAdmin): "get_runner", "destinatario_email", "digest", + "last_digest", ) fields = [ "job_name", @@ -76,6 +77,7 @@ class CronjobAdmin(admin.ModelAdmin): "manter_logs", "destinatario_email", "digest", + "last_digest", ] readonly_fields = ("job_name", "app_name", "get_help") inlines = [JobScheduleInline] @@ -161,7 +163,6 @@ class JobScheduleAdmin(admin.ModelAdmin): "iniciado", "tempo_gasto", "get_runner", - "enviado", ] fields = [ "job", @@ -169,7 +170,6 @@ class JobScheduleAdmin(admin.ModelAdmin): "iniciar", "iniciado", "tempo_gasto", - "enviado", ] readonly_fields = fields list_filter = ("status", "job") diff --git a/sigi/apps/utils/models.py b/sigi/apps/utils/models.py index 9fe630a..feb8c79 100644 --- a/sigi/apps/utils/models.py +++ b/sigi/apps/utils/models.py @@ -1,7 +1,6 @@ import io from contextlib import redirect_stderr, redirect_stdout from cron_converter import Cron -from pyexpat import model from django.db import models from django.contrib.auth.models import Group from django.utils import timezone @@ -72,12 +71,14 @@ class Cronjob(models.Model): ), 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 [ @@ -86,9 +87,6 @@ class Cronjob(models.Model): if email.strip() ] - def __str__(self): - return f"Destinatários: {', '.join(self.get_emails_list())}" - class Meta: ordering = ("app_name", "job_name") verbose_name = _("Cron job") @@ -177,7 +175,6 @@ class JobSchedule(models.Model): resultado = models.TextField( _("resultado da execução"), blank=True, editable=False ) - enviado = models.BooleanField(_("enviado"), default=False) class Meta: ordering = ("-iniciar",) @@ -224,7 +221,10 @@ class JobSchedule(models.Model): if self.job.destinatario_email == "": return + now = timezone.localtime() + if self.job.digest == "N": + # Envia imediatamente sem acumular send_mail( subject=f"JOB: {self.job.job_name}", message=self.resultado, @@ -233,47 +233,47 @@ class JobSchedule(models.Model): fail_silently=True, html_message=self.resultado, ) - self.enviado = True - self.save() + self.job.last_digest = now + self.job.save() - elif self.job.digest == "D": - self.send_digest_email(frequency="daily") + 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}" + ) - elif self.job.digest == "S": - self.send_digest_email(frequency="weekly") + # 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 diário ou semanal.""" + """Envia email de digest acumulando jobs desde o último digest.""" now = timezone.localtime() - # Definir horário estático - send_time = timezone.datetime.min.time() # Meia-noite - today = now.date() - - if frequency == "daily": - # Verifica se é meia-noite - if now.time() != send_time: - return - - # Definir início do período como o dia anterior - period_start = today - timedelta(days=1) - - elif frequency == "weekly": - # Verifica se é segunda-feira e se é meia-noite - if today.weekday() != 0 or now.time() != send_time: - return - - # Define o início do período como segunda-feira da semana passada - period_start = today - timedelta(days=7) - + # Determina o período de acumulação baseado no último digest + if self.job.last_digest: + period_start = self.job.last_digest else: - raise ValueError("Invalid frequency for digest email.") + 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, - enviado=False, ) if job_schedules.exists(): @@ -293,8 +293,6 @@ class JobSchedule(models.Model): html_message=message, ) - job_schedules.update(enviado=True) - class Config(models.Model): PARAMETRO_CHOICES = ( From 8e3df0ec460b7f29795ac1e612542b206be54433 Mon Sep 17 00:00:00 2001 From: lucasmndc Date: Tue, 13 Aug 2024 10:40:46 -0300 Subject: [PATCH 4/4] =?UTF-8?q?Migra=C3=A7=C3=B5es=20e=20admin=20ajustados?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sigi/apps/utils/admin.py | 2 +- ...jobschedule_enviado_cronjob_last_digest.py | 24 +++++++++++++++++++ sigi/apps/utils/models.py | 1 + 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 sigi/apps/utils/migrations/0006_remove_jobschedule_enviado_cronjob_last_digest.py diff --git a/sigi/apps/utils/admin.py b/sigi/apps/utils/admin.py index a444852..598da0c 100644 --- a/sigi/apps/utils/admin.py +++ b/sigi/apps/utils/admin.py @@ -79,7 +79,7 @@ class CronjobAdmin(admin.ModelAdmin): "digest", "last_digest", ] - readonly_fields = ("job_name", "app_name", "get_help") + readonly_fields = ("job_name", "app_name", "get_help", "last_digest") inlines = [JobScheduleInline] def get_urls(self): diff --git a/sigi/apps/utils/migrations/0006_remove_jobschedule_enviado_cronjob_last_digest.py b/sigi/apps/utils/migrations/0006_remove_jobschedule_enviado_cronjob_last_digest.py new file mode 100644 index 0000000..fa4e15c --- /dev/null +++ b/sigi/apps/utils/migrations/0006_remove_jobschedule_enviado_cronjob_last_digest.py @@ -0,0 +1,24 @@ +# Generated by Django 5.0.6 on 2024-08-13 13:35 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("utils", "0005_cronjob_destinatario_email_cronjob_digest_and_more"), + ] + + operations = [ + migrations.RemoveField( + model_name="jobschedule", + name="enviado", + ), + migrations.AddField( + model_name="cronjob", + name="last_digest", + field=models.DateTimeField( + blank=True, null=True, verbose_name="último envio de digest" + ), + ), + ] diff --git a/sigi/apps/utils/models.py b/sigi/apps/utils/models.py index feb8c79..59d720e 100644 --- a/sigi/apps/utils/models.py +++ b/sigi/apps/utils/models.py @@ -1,6 +1,7 @@ import io from contextlib import redirect_stderr, redirect_stdout from cron_converter import Cron +from pyexpat import model from django.db import models from django.contrib.auth.models import Group from django.utils import timezone