class Meta:
model = SponsorMail
- exclude = ('conference', )
+ exclude = ('conference', 'sent', )
widgets = {
'message': EmailTextWidget(),
}
--- /dev/null
+# Send queued sponsor emails.
+#
+
+from django.core.management.base import BaseCommand
+from django.utils import timezone
+from django.db import transaction
+from django.db.models import Q
+from django.conf import settings
+
+from datetime import timedelta
+
+from postgresqleu.confsponsor.models import Sponsor, SponsorMail
+from postgresqleu.confsponsor.util import send_conference_sponsor_notification, send_sponsor_manager_email
+from postgresqleu.confreg.util import send_conference_mail
+
+
+class Command(BaseCommand):
+ help = 'Send sponsor emails'
+
+ class ScheduledJob:
+ scheduled_interval = timedelta(minutes=5)
+ internal = True
+
+ @classmethod
+ def should_run(self):
+ return SponsorMail.objects.filter(sentat__lte=timezone.now(), sent=False).exists()
+
+ @transaction.atomic
+ def handle(self, *args, **options):
+ for msg in SponsorMail.objects.filter(sentat__lte=timezone.now(), sent=False):
+ if msg.levels.exists():
+ sponsors = list(Sponsor.objects.select_related('conference').filter(level__sponsormail=msg, confirmed=True))
+ deststr = "sponsorship levels {}".format(", ".join(level.levelname for level in msg.levels.all()))
+ else:
+ sponsors = list(Sponsor.objects.select_related('conference').filter(sponsormail=msg)) # We include unconfirmed sponsors here intentionally!
+ deststr = "sponsors {}".format(", ".join(s.name for s in sponsors))
+
+ conference = None
+ for sponsor in sponsors:
+ conference = sponsor.conference
+ send_sponsor_manager_email(
+ sponsor,
+ msg.subject,
+ 'confsponsor/mail/sponsor_mail.txt',
+ {
+ 'body': msg.message,
+ 'sponsor': sponsor,
+ },
+ )
+
+ # And possibly send it out to the extra address for the sponsor
+ if sponsor.extra_cc:
+ send_conference_mail(conference,
+ sponsor.extra_cc,
+ msg.subject,
+ 'confsponsor/mail/sponsor_mail.txt',
+ {
+ 'body': msg.message,
+ 'sponsor': sponsor,
+ },
+ sender=conference.sponsoraddr,
+ )
+ msg.sent = True
+ msg.save(update_fields=['sent'])
+
+ if conference:
+ send_conference_sponsor_notification(
+ conference,
+ "Email sent to sponsors",
+ """An email was sent to sponsors of {0}
+ with subject '{1}'.
+
+ It was sent to {2}.
+
+ ------
+ {3}
+ ------
+
+ To view it on the site, go to {4}/events/sponsor/admin/{5}/viewmail/{6}/""".format(
+ conference,
+ msg.subject,
+ deststr,
+ msg.message,
+ settings.SITEBASE,
+ conference.urlname,
+ msg.id,
+ ),
+ )
from django.db import migrations, models
import postgresqleu.util.validators
from django.conf import settings
+from django.utils import timezone
import postgresqleu.util.storage
name='SponsorMail',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
- ('sentat', models.DateTimeField(auto_now_add=True)),
+ ('sentat', models.DateTimeField(default=timezone.now, verbose_name="Send at")),
('subject', models.CharField(max_length=100)),
('message', models.TextField(max_length=8000)),
('conference', models.ForeignKey(to='confreg.Conference', on_delete=models.CASCADE)),
--- /dev/null
+# Generated by Django 4.2.11 on 2024-05-14 13:54
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('confsponsor', '0030_benefit_overview'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='sponsormail',
+ name='sent',
+ field=models.BooleanField(default=True),
+ ),
+ migrations.AlterField(
+ model_name='sponsormail',
+ name='sent',
+ field=models.BooleanField(default=False),
+ ),
+ migrations.AddIndex(
+ model_name='sponsormail',
+ index=models.Index(condition=models.Q(('sent', False)), fields=['sentat'], name='confsponsor_sponsormail_unsent'),
+ ),
+ ]
from django.db import models
+from django.db.models import Q
from django.utils.functional import cached_property
from django.core.serializers.json import DjangoJSONEncoder
from django.core.validators import MinValueValidator
conference = models.ForeignKey(Conference, null=False, blank=False, on_delete=models.CASCADE)
levels = models.ManyToManyField(SponsorshipLevel, blank=True)
sponsors = models.ManyToManyField(Sponsor, blank=True)
- sentat = models.DateTimeField(null=False, blank=False, auto_now_add=True)
+ sentat = models.DateTimeField(null=False, blank=False, default=timezone.now, verbose_name="Send at")
+ sent = models.BooleanField(null=False, blank=False, default=False)
subject = models.CharField(max_length=100, null=False, blank=False)
message = models.TextField(max_length=8000, null=False, blank=False)
class Meta:
ordering = ('-sentat',)
+ indexes = [
+ models.Index(name="confsponsor_sponsormail_unsent", fields=['sentat'], condition=Q(sent=False)),
+ ]
+
+ @property
+ def future(self):
+ return self.sentat > timezone.now()
class SponsorScanner(models.Model):
from django.db.models import Q
from django.shortcuts import get_object_or_404
from django.conf import settings
+from django.utils import timezone
from postgresqleu.util.db import exec_to_list
from postgresqleu.util.currency import format_currency
attachments=attachments,
sender=sponsor.conference.sponsoraddr,
sendername=sponsor.conference.conferencename,
- receivername='{0} {1}'.format(manager.first_name, manager.last_name)
+ receivername='{0} {1}'.format(manager.first_name, manager.last_name),
+ sendat=None,
)
-def get_mails_for_sponsor(sponsor):
+def get_mails_for_sponsor(sponsor, future=False):
return SponsorMail.objects.filter(
Q(conference=sponsor.conference),
- Q(levels=sponsor.level) | Q(sponsors=sponsor)
+ Q(levels=sponsor.level) | Q(sponsors=sponsor),
+ sent=not future,
)
from postgresqleu.invoices.util import InvoiceWrapper, InvoiceManager
from postgresqleu.digisign.pdfutil import fill_pdf_fields, pdf_watermark_preview
from postgresqleu.digisign.models import DigisignDocument, DigisignLog
+from postgresqleu.scheduler.util import trigger_immediate_job_run
from .models import Sponsor, SponsorshipLevel, SponsorshipBenefit
from .models import SponsorClaimedBenefit, SponsorMail, SponsorshipContract, SponsorAdditionalContract
# Create a message record
msg = SponsorMail(conference=conference,
subject=form.data['subject'],
- message=form.data['message'])
+ message=form.data['message'],
+ sentat=max(form.cleaned_data['sentat'], timezone.now()), # If time is set in the past, adjust to now
+ )
msg.save()
if sendto == 'level':
for level in form.data.getlist('levels'):
msg.levels.add(level)
- sponsors = Sponsor.objects.filter(conference=conference, level__in=form.data.getlist('levels'), confirmed=True)
deststr = "sponsorship levels {0}".format(", ".join([level.levelname for level in msg.levels.all()]))
else:
for s in form.data.getlist('sponsors'):
msg.sponsors.add(s)
- sponsors = Sponsor.objects.filter(conference=conference, pk__in=form.data.getlist('sponsors'))
deststr = "sponsors {0}".format(", ".join([s.name for s in msg.sponsors.all()]))
msg.save()
- # Now also send the email out to the *current* subscribers
- for sponsor in sponsors:
- send_sponsor_manager_email(
- sponsor,
- msg.subject,
- 'confsponsor/mail/sponsor_mail.txt',
- {
- 'body': msg.message,
- 'sponsor': sponsor,
- },
- )
-
- # And possibly send it out to the extra address for the sponsor
- if sponsor.extra_cc:
- send_conference_mail(conference,
- sponsor.extra_cc,
- msg.subject,
- 'confsponsor/mail/sponsor_mail.txt',
- {
- 'body': msg.message,
- 'sponsor': sponsor,
- },
- sender=conference.sponsoraddr,
- )
-
- send_conference_sponsor_notification(
- conference,
- "Email sent to sponsors",
- """An email was sent to sponsors of {0}
-with subject '{1}'.
-
-It was sent to {2}.
-
-------
-{3}
-------
-
-To view it on the site, go to {4}/events/sponsor/admin/{5}/viewmail/{6}/""".format(
- conference,
- msg.subject,
- deststr,
- msg.message,
- settings.SITEBASE,
- conference.urlname,
- msg.id,
- ),
- )
+ if msg.sentat > timezone.now():
+ messages.info(request, "Email scheduled for later sending to sponsors")
+ else:
+ trigger_immediate_job_run('sponsor_send_emails')
+ messages.info(request, "Email sent to sponsors, and added to their sponsor pages")
- messages.info(request, "Email sent to %s sponsors, and added to their sponsor pages" % len(sponsors))
return HttpResponseRedirect("../")
else:
if sendto == 'sponsor' and request.GET.get('preselectsponsors', ''):
</tr>
{%for m in mails%}
<tr>
- <td>{{m.sentat|date:"Y-m-d H:i"}}</td>
+ <td>{{m.sentat|date:"Y-m-d H:i"}}{%if m.future%}<span title="E-mail has not been sent yet, but is in the queue to be automatically flushed at the send date"> (Not sent yet!)</span>{%endif%}</td>
<td><a href="viewmail/{{m.id}}/">{{m.subject}}</a></td>
<td>{{m.levels.all|join:", "}}</td>
<td>{{m.sponsors.all|join:", "}}</td>
</tr>
<tr>
<th>Date:</th>
- <td>{{mail.sentat|date:"Y-m-d H:i:s"}}</td>
+ <td>{{mail.sentat|date:"Y-m-d H:i"}}{%if mail.future%}<span title="E-mail has not been sent yet, but is in the queue to be automatically flushed at the send date"> (Not sent yet!)</span>{%endif%}</td>
</tr>
<tr>
<th>Subject:</th>