diff --git a/one80/photos/management/commands/newtagnotifications.py b/one80/photos/management/commands/newtagnotifications.py new file mode 100644 index 0000000..a148953 --- /dev/null +++ b/one80/photos/management/commands/newtagnotifications.py @@ -0,0 +1,76 @@ +import datetime +import logging + +from django.conf import settings +from django.core.mail import send_mail +from django.core.management.base import BaseCommand, CommandError +from django.template import loader, Context +from optparse import make_option + +from postmark.models import EmailMessage +from one80.photos.models import Annotation + +THRESHOLD = getattr(settings, 'ANNOTATION_MAX_QUEUE_SIZE', 5) + +class Command(BaseCommand): + args = None + option_list = BaseCommand.option_list + ( + make_option('--sender', + dest='sender', + default='cron', + help='The sender of the call; could be "cron" or "hook"'), + ) + help = ''' + Sends notifications of new annotations. + This is run 2 ways: crontab and post-save hook. + If called from cron, it will only email every hour at most. + If called from a hook, it will send if n or more new annotations are queued, + and 5 minutes have passed since the last annotation was submitted, not more than every 30 minutes. + ''' + + def handle(self, *args, **options): + verbosity = int(options.get('verbosity', 0)) + if verbosity == 0: + level = logging.WARNING + elif verbosity == 1: + level = logging.INFO + else: + level = logging.DEBUG + + logging.basicConfig(level=level, format="%(message)s") + annot_qset = Annotation.objects.unpublished().order_by('-created_date') + try: + last_email = EmailMessage.objects.filter(subject__startswith='[180] New annotations').order_by('-created_at')[0] + except IndexError: + last_email = lambda: None + last_email.created_at=datetime.datetime(1970,1,1) + waiting = annot_qset.filter(created_date__gt=last_email.created_at) + send_email = False + now = datetime.datetime.now() + if not waiting.count(): + logging.info('No annotations queued.') + return + + if options.get('sender') == 'hook': + if ((waiting.count() >= THRESHOLD) and + ((now - datetime.timedelta(minutes=20) >= last_email.created_at) or + (now - datetime.timedelta(minutes=5) >= waiting[1].created_date))): + send_email = True + else: + if (((waiting.count() >= THRESHOLD) and now - datetime.timedelta(minutes=20) >= last_email.created_at) or + (waiting.count() and (now - datetime.timedelta(hours=1) >= last_email.created_at))): + send_email = True + + if not send_email: + logging.info('Found %d queued annotations, but it\'s not time to send mail yet, skipping.' % waiting.count()) + else: + ctx = { + 'annotations': waiting, + } + subject = '[180] New annotations are waiting for approval' + message = loader.get_template('emails/new_annotations.txt').render(Context(ctx)) + if send_mail(subject, + message, + '%s <%s>' % ('180-mailer', settings.EMAIL_FROM), + [admin[1] for admin in settings.ADMINS]): + logging.info('Email sent.') diff --git a/one80/photos/models.py b/one80/photos/models.py index 28a6690..8db668e 100644 --- a/one80/photos/models.py +++ b/one80/photos/models.py @@ -6,7 +6,7 @@ from django.contrib.auth.models import User from django.core.files.base import ContentFile from django.db import models -from django.db.models.signals import pre_save +from django.db.models.signals import pre_save, post_save from django.template import Context from django.template.loader import get_template from PIL import Image @@ -308,11 +308,17 @@ def _get_thumbnail(self, **kwargs): self.thumbnail.save(self.get_filename(), ContentFile(bffr.getvalue()), save=False) bffr.close() -def save_annotation_handler(sender, instance, *args, **kwargs): +def pre_save_annotation_handler(sender, instance, *args, **kwargs): if instance.is_public and not instance.person: instance.person = Person.objects.get_or_create(first_name=instance.first_name, last_name=instance.last_name, title=instance.title, organization=instance.organization,)[0] -pre_save.connect(save_annotation_handler, sender=Annotation) \ No newline at end of file +def post_save_annotation_handler(sender, instance, *args, **kwargs): + if not instance.is_public: + from django.core.management import call_command + call_command('newtagnotifications', sender='hook') + +pre_save.connect(pre_save_annotation_handler, sender=Annotation) +post_save.connect(post_save_annotation_handler, sender=Annotation) \ No newline at end of file diff --git a/one80/photos/templates/emails/new_annotations.txt b/one80/photos/templates/emails/new_annotations.txt new file mode 100644 index 0000000..a8f775d --- /dev/null +++ b/one80/photos/templates/emails/new_annotations.txt @@ -0,0 +1,15 @@ +Hello there, + +This is just a friendly email to let you know there are some new annotations awaiting your approval: + +{% for annot in annotations %} + - {{ annot.name }}, {{ annot.position }} in {{ annot.hearing.title }} + http://180.sunlightfoundation.com/admin/photos/annotation/{{ annot.pk }}/ + +{% endfor %} + +As a reminder, you can always see the unapproved queue at http://180.sunlightfoundation.com/admin/photos/annotation/?is_public__exact=0 + +Thanks and have a great day! + +- The 180° Project diff --git a/requirements.txt b/requirements.txt index b1cedc2..0c667e1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,4 +19,5 @@ yql -e git+https://github.com/dandrinkard/python-inboxinfluence.git#egg=python_inboxinfluence-dev -e git+https://github.com/dandrinkard/python-crunchbase.git#egg=python_crunchbase-dev -e git+https://github.com/liberation/django-jsonfield.git#egg=django_jsonfield-dev +# important to stick with this fork; relies on a new model field -e git+https://github.com/dandrinkard/django-postmark.git#egg=django_postmark-dev \ No newline at end of file