Hello guys, i want to ask you a question and would like to hear your suggestions. I’m building Ticket system, something like a Trac project, and I’m wondering, what should be the best approach to do email notification. I already implemented send email to person, which is assigned to ticket through form_valid, but I would like to send email when someone comments to ticket to all people connected to ticket like creator, assigned. I don’t know if i made it clear. Should I go for new model Notficiations, use signals, use this in class views or anything else? Version of django is latest.
There is a ticket and comment model:
class Ticket(models.Model):
title = models.CharField(max_length=200)
created = models.DateTimeField(auto_now_add=True)
description = models.TextField()
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='created_by')
assigned = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, blank=True, null=True, related_name='assigned')
modified = models.DateTimeField(auto_now=True)
class Meta:
ordering = ["-created"]
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('detail', kwargs={'pk': self.pk})
class Comment(models.Model):
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
ticket = models.ForeignKey(Ticket, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
description = models.TextField()
class Meta:
ordering = ["-created"]
def __str__(self):
return 'Comment by {} on {}'.format(self.author.get_full_name(), self.ticket)
We have a need similar to that (sending emails to a group of people upon creation of an object). We do it in the view where that particular object is created and not in the model. (We have a business policy for this system that an admin can create, edit, or delete those objects without email notifications being sent. If that doesn’t apply to you, you might want to do this work in the model.)
We definitely don’t use signals for this. (Actually, I think we don’t use signals for anything.)
However, we do use a celery process to actually send the emails - what happens in the view is that a celery task to send emails is called for each recipient. Sending an email through smtp can take a couple seconds if your smtp server is non-local, and if you’re sending 10+ emails, it can really slow down response for a submission.
Oh thanks for the answer, I thought more about doing it in view. For now I will stick to it, to make it working and about celery do you by any chance have a good reference to tutorial or should I stick with their documentation?
FWIW, I have a similar thing implemented in a third party mail sending library (django-post_office), like so…
There’s some extra stuff in here (unsubscribe link and checking if a user has unsubscribed before adding them to the list of recipients) and a manual building of context per-iteration so that I have access to the user object for template purposes (individualization, like {{ first_name }} {{ last_name }} for example). And of course there’s a field in the model for each outgoing mail task to say whether or not the mail is addressed to a group, and if so which group it is (I have that selectable as a list of groups in the admin).
Should be just a matter of modifying the query to get the list of users you want. If you want the unsubscribe view it’s in my other recent post about my weird uwsgi bug with tokens that I was asking for help with.
def send_queued_mail_until_done(lockfile=default_lockfile, processes=1, log_level=None):
"""
Send mail in queue batch by batch, until all emails have been processed.
"""
try:
with FileLock(lockfile):
logger.info('Acquired lock for sending queued emails at %s.lock', lockfile)
while True:
try:
group_emails = Email.objects.filter(status=STATUS.queued, group_id__isnull=False) \
.select_related('template') \
.filter(Q(scheduled_time__lte=now()) | Q(scheduled_time=None))
if group_emails:
kwargs_list = []
for email in group_emails:
group_emails_users = User.objects.filter(Q(groups__id=email.group_id, is_subscribed=True))
for user in group_emails_users:
token = unsubscribe_token.make_token(user)
uid = urlsafe_base64_encode(force_bytes(user.email))
unsub_link = settings.BASE_URL + '/unsubscribe/' + uid + '/' + token + '/'
user_context = model_to_dict(user, exclude=['groups'])
unsub_context = { 'unsubscribe': unsub_link }
context = {**user_context, **unsub_context}
user.id = {
'sender': settings.DEFAULT_FROM_EMAIL,
'recipients': user.email,
'template': email.template,
'render_on_delivery': True,
'context': context,
}
kwargs_list.append(user.id)
send_many(kwargs_list)
send_queued(processes, log_level)
else:
send_queued(processes, log_level)
except Exception as e:
logger.error(e, exc_info=sys.exc_info(), extra={'status_code': 500})
raise