Implement generating/sending bulletin

This commit is contained in:
Yingtong Li 2019-02-08 21:21:24 +11:00
parent cd8900070a
commit 9ce8af1200
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
4 changed files with 212 additions and 32 deletions

View File

@ -0,0 +1,23 @@
{% for event in events %}{{ event.date.strftime('%a %d %b') }}: {{ event.title }}{% if event.link %} <{{ event.link }}>{% endif %}
{% endfor %}
{% for group in groups %}{{ group.name }}
==============================
{% for item in group.bulletin_items %}{{ item.title }}
--------------------------
{{ item.content }}
{% if item.link %}Read more <{{ item.link }}>
{% endif %}{% endfor %}{% endfor %}{% if more %}More from MUMUS
==============================
{% for item in more %}{{ item.title }}
--------------------------
{{ item.content }}
{% if item.link %}Read more <{{ item.link }}>
{% endif %}{% endfor %}{% endif %}© Copyright {{ import('datetime').datetime.now().strftime('%Y') }} MUMUS Inc. All Rights Reserved.

View File

@ -0,0 +1,108 @@
# Society Self-Service
# Copyright © 2018-2019 Yingtong Li (RunasSudo)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import boto3
from botocore.exceptions import ClientError
import premailer
from django.core.management.base import BaseCommand, CommandError
from django.conf import settings
from django.template import loader
from django.urls import reverse
from django.utils import timezone
import ssmembership.models
import sspromotions.utils
import logging
import time
import urllib.parse
def send_aws_email(client, email, subject, content_html, content_txt):
def send_mail(**kwargs):
for i in range(0, 10):
try:
client.send_email(**kwargs)
return
except ClientError as e:
if e.response['Error']['Code'] == 'Throttling' and e.response['Error']['Message'] == 'Maximum sending rate exceeded.':
wait_time = max(10*(2**i), 5000)
self.stdout.write(self.style.NOTICE('Reached maximum sending rate, waiting {} ms'.format(wait_time)))
time.sleep(wait_time/1000)
else:
raise e
raise Exception('Reached maximum number of retries')
send_mail(
Destination={
'ToAddresses': [email],
},
Message={
'Body': {
'Html': {
'Charset': 'utf-8',
'Data': content_html,
},
'Text': {
'Charset': 'utf-8',
'Data': content_txt,
},
},
'Subject': {
'Charset': 'utf-8',
'Data': subject,
},
},
Source='{} <{}>'.format(settings.ORG_NAME, settings.AWS_SENDER_EMAIL),
)
class Command(BaseCommand):
help = 'Send bulletin emails'
def add_arguments(self, parser):
parser.add_argument('ids', nargs='*', type=int, help='Members with ID numbers equal to these values will be emailed (default all)')
parser.add_argument('--render', action='store_true', help='Render to stdout instead of sending emails')
def handle(self, *args, **options):
template_html = loader.get_template('sspromotions/email/bulletin.html')
template_txt = loader.get_template('sspromotions/email/bulletin.txt')
members = ssmembership.models.Member.objects.all()
if len(options['ids']) > 0:
members = [member for member in members if member.id in options['ids']]
else:
raise Exception('Must provide IDs')
client = boto3.client('ses', aws_access_key_id=settings.AWS_KEY_ID, aws_secret_access_key=settings.AWS_SECRET, region_name=settings.AWS_REGION)
title = '{} News: {}'.format(settings.ORG_NAME, timezone.now().strftime('%d %m %Y'))
calbegin, calend, bulbegin, bulend = sspromotions.utils.bulletin_dates(timezone.now())
events = list(sspromotions.utils.get_calendar_events(calbegin, calend))
for member in members:
template_args = sspromotions.utils.bulletin_args(member, events, bulbegin, bulend)
content_html = premailer.Premailer(template_html.render(template_args), cssutils_logging_level=logging.ERROR, strip_important=False).transform()
content_txt = template_txt.render(template_args)
if options['render']:
self.stdout.write('Content-Type: multipart/alternative; boundary=boundary\n\n--boundary\nContent-Type: text/html; charset=utf-8\n\n' + content_html + '\n--boundary\nContent-Type: text/plain; charset=utf-8\n\n' + content_txt + '\n--boundary')
else:
self.stdout.write('Emailing {} at {}'.format(member.id, member.email))
send_aws_email(client, member.email, title, content_html, content_txt)

78
sspromotions/utils.py Normal file
View File

@ -0,0 +1,78 @@
# Society Self-Service
# Copyright © 2018-2019 Yingtong Li (RunasSudo)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from googleapiclient.discovery import build
from django.conf import settings
from django.utils import timezone
from . import models
import datetime
def get_calendar_events(calbegin, calend):
service = build('calendar', 'v3', developerKey=settings.GOOGLE_API_KEY)
events = service.events().list(
calendarId=settings.GOOGLE_CALENDAR_ID,
orderBy='startTime',
singleEvents=True,
timeMin=datetime.datetime.combine(calbegin, datetime.time(tzinfo=timezone.get_current_timezone())).isoformat(),
timeMax=datetime.datetime.combine(calend, datetime.time(tzinfo=timezone.get_current_timezone())).isoformat()
).execute()
for event in events['items']:
yield {
'date': datetime.datetime.strptime(event['start']['date'], '%Y-%m-%d') if 'date' in event['start'] else datetime.datetime.strptime(event['start']['dateTime'][:-3] + event['start']['dateTime'][-2:], '%Y-%m-%dT%H:%M:%S%z'),
'title': event['summary'],
'link': event['htmlLink']
}
def bulletin_dates(dt):
calbegin = dt.date() + datetime.timedelta(days=1) # Start tomorrow for calendar
calend = calbegin + datetime.timedelta(days=14)
bulbegin = dt.date()
bulend = bulbegin + datetime.timedelta(days=7)
return calbegin, calend, bulbegin, bulend
def bulletin_args(member, events, bulbegin, bulend):
if member is None:
groups = models.Group.objects.all()
else:
groups = models.Group.get_member_groups(member)
groups_data = []
for group in groups:
items = []
for item in group.bulletinitem_set.filter(date__gte=bulbegin).filter(date__lt=bulend).all():
# Check also_limit
if member is None or len(item.also_limit) == 0 or any(models.Group.objects.get(id=x).contains_member(member) for x in item.also_limit if models.Group.objects.filter(id=x).count() > 0):
items.append(item)
if len(items) > 0:
groups_data.append({
'group': group,
'name': group.name,
'bulletin_items': items
})
return {
'events': events,
'groups': groups_data,
'more': [], # TODO
'bulbegin': bulbegin,
'bulend': bulend
}

View File

@ -14,8 +14,6 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from googleapiclient.discovery import build
from django.contrib.auth.decorators import login_required
from django.conf import settings
@ -25,30 +23,14 @@ from django.urls import reverse
from django.utils import timezone
from . import models
from . import utils
import datetime
import json
@login_required
def index(request):
return render(request, 'sspromotions/index.html')
def get_calendar_events(calbegin, calend):
service = build('calendar', 'v3', developerKey=settings.GOOGLE_API_KEY)
events = service.events().list(
calendarId=settings.GOOGLE_CALENDAR_ID,
orderBy='startTime',
singleEvents=True,
timeMin=datetime.datetime.combine(calbegin, datetime.time(tzinfo=timezone.get_current_timezone())).isoformat(),
timeMax=datetime.datetime.combine(calend, datetime.time(tzinfo=timezone.get_current_timezone())).isoformat()
).execute()
for event in events['items']:
yield {
'date': datetime.datetime.strptime(event['start']['date'], '%Y-%m-%d') if 'date' in event['start'] else datetime.datetime.strptime(event['start']['dateTime'][:-3] + event['start']['dateTime'][-2:], '%Y-%m-%dT%H:%M:%S%z'),
'title': event['summary'],
'link': event['htmlLink']
}
@login_required
def bulletin_list(request):
items_past = []
@ -77,19 +59,8 @@ def bulletin_list(request):
@login_required
def bulletin_preview(request):
calbegin = timezone.now().date() + datetime.timedelta(days=1) # Start tomorrow for calendar
calend = calbegin + datetime.timedelta(days=14)
bulbegin = timezone.now().date()
bulend = bulbegin + datetime.timedelta(days=7)
return render(request, 'sspromotions/email/bulletin.html', {
'events': get_calendar_events(calbegin, calend),
'groups': models.Group.objects.all(),
'more': [],
'bulbegin': bulbegin,
'bulend': bulend
})
calbegin, calend, bulbegin, bulend = utils.bulletin_dates(timezone.now())
return render(request, 'sspromotions/email/bulletin.html', utils.bulletin_args(None, utils.get_calendar_events(calbegin, calend), bulbegin, bulend))
@login_required
def bulletin_new(request):