def generate_meeting_reminder(meeting): if Reminder.objects.filter(study_group_meeting=meeting).exists(): reminder = Reminder.objects.get(study_group_meeting=meeting) reminder.edited_by_facilitator = False else: reminder = Reminder() reminder.study_group = meeting.study_group reminder.study_group_meeting = meeting context = { 'facilitator': meeting.study_group.facilitator, 'study_group': meeting.study_group, 'next_meeting': meeting, 'reminder': reminder, } timezone.activate(pytz.timezone(meeting.study_group.timezone)) with use_language(meeting.study_group.language): reminder.email_subject = render_to_string_ctx( 'studygroups/email/meeting_reminder-subject.txt', context).strip('\n') reminder.email_body = render_to_string_ctx( 'studygroups/email/meeting_reminder.html', context) reminder.sms_body = render_to_string_ctx( 'studygroups/email/meeting_reminder-sms.txt', context) # TODO - handle SMS reminders that are too long if len(reminder.sms_body) > 160: logger.error('SMS body too long: ' + reminder.sms_body) reminder.sms_body = reminder.sms_body[:160] reminder.save() return reminder
def send_optout_message(self): email = self.cleaned_data.get('email') mobile = self.cleaned_data.get('mobile') if email: for application in Application.objects.active().filter(email__iexact=email): # send opt-out email context = { 'application': application } subject = render_to_string_ctx('studygroups/email/optout_confirm-subject.txt', context).strip('\n') html_body = render_html_with_css('studygroups/email/optout_confirm.html', context) text_body = render_to_string_ctx('studygroups/email/optout_confirm.txt', context) notification = EmailMultiAlternatives(subject, text_body, settings.DEFAULT_FROM_EMAIL, [application.email]) notification.attach_alternative(html_body, 'text/html') notification.send() # Find all signups with mobile with email and delete if mobile: applications = Application.objects.active().filter(mobile=mobile) if email: # don't send text to applications with a valid email in opt out form applications = applications.exclude(email__iexact=email) for application in applications: # This remains for old signups without email address context = { 'application': application } message = render_to_string_ctx('studygroups/email/optout_confirm_text.txt', context) try: send_message(application.mobile, message) except twilio.TwilioRestException as e: logger.exception("Could not send text message to %s", to, exc_info=e) application.delete()
def send_email_confirm_email(user): context = { "user": user, "profile": user.profile, "uid": urlsafe_base64_encode(force_bytes(user.pk)), "token": generate_user_token(user), } subject_template = 'custom_registration/email_confirm-subject.txt' email_template = 'custom_registration/email_confirm.txt' html_email_template = 'custom_registration/email_confirm.html' subject = render_to_string_ctx(subject_template, context).strip(' \n') text_body = render_to_string_ctx(email_template, context) html_body = render_html_with_css(html_email_template, context) to = [user.email] email = EmailMultiAlternatives( subject, text_body, settings.DEFAULT_FROM_EMAIL, to, ) email.attach_alternative(html_body, 'text/html') email.send()
def send_team_invitation_email(team, email, organizer): """ Send email to new or existing facilitators """ """ organizer should be a User object """ user_qs = User.objects.filter(email__iexact=email) context = { "team": team, "organizer": organizer, } if user_qs.count() == 0: # invite user to join subject = render_to_string_ctx( 'studygroups/email/new_facilitator_invite-subject.txt', context).strip('\n') html_body = render_html_with_css( 'studygroups/email/new_facilitator_invite.html', context) text_body = render_to_string_ctx( 'studygroups/email/new_facilitator_invite.txt', context) else: context['user'] = user_qs.get() subject = render_to_string_ctx( 'studygroups/email/team_invite-subject.txt', context).strip('\n') html_body = render_html_with_css('studygroups/email/team_invite.html', context) text_body = render_to_string_ctx('studygroups/email/team_invite.txt', context) to = [email] from_ = organizer.email msg = EmailMultiAlternatives(subject, text_body, from_, to) msg.attach_alternative(html_body, 'text/html') msg.send()
def render_email_templates(template_base, context): """ use template_base to render subject, text and html for email """ # TODO make text template optional - use html to text when not specified subject = render_to_string_ctx('{}-subject.txt'.format(template_base), context).strip() txt = render_to_string_ctx('{}.txt'.format(template_base), context) html = render_html_with_css('{}.html'.format(template_base), context) return subject, txt, html
def generate_reminder(study_group): now = timezone.now() next_meeting = study_group.next_meeting() # TODO reminder generation code should be moved to models, only sending facilitator notification should be here if next_meeting and next_meeting.meeting_datetime( ) - now < datetime.timedelta(days=4): # check if a notifcation already exists for this meeting if not Reminder.objects.filter( study_group=study_group, study_group_meeting=next_meeting).exists(): reminder = Reminder() reminder.study_group = study_group reminder.study_group_meeting = next_meeting context = { 'facilitator': study_group.facilitator, 'study_group': study_group, 'next_meeting': next_meeting, 'reminder': reminder, } previous_meeting = study_group.meeting_set.filter( meeting_date__lt=next_meeting.meeting_date).order_by( 'meeting_date').last() if previous_meeting and previous_meeting.feedback_set.first(): context['feedback'] = previous_meeting.feedback_set.first() timezone.activate(pytz.timezone(study_group.timezone)) with use_language(study_group.language): reminder.email_subject = render_to_string_ctx( 'studygroups/email/reminder-subject.txt', context).strip('\n') reminder.email_body = render_to_string_ctx( 'studygroups/email/reminder.txt', context) reminder.sms_body = render_to_string_ctx( 'studygroups/email/sms.txt', context) # TODO - handle SMS reminders that are too long if len(reminder.sms_body) > 160: logger.error('SMS body too long: ' + reminder.sms_body) reminder.sms_body = reminder.sms_body[:160] reminder.save() with use_language(reminder.study_group.language): facilitator_notification_subject = _( 'A reminder for %(studygroup_name)s was generated' % {"studygroup_name": study_group.name}) facilitator_notification_html = render_html_with_css( 'studygroups/email/reminder_notification.html', context) facilitator_notification_txt = render_to_string_ctx( 'studygroups/email/reminder_notification.txt', context) timezone.deactivate() to = [study_group.facilitator.email] notification = EmailMultiAlternatives( facilitator_notification_subject, facilitator_notification_txt, settings.DEFAULT_FROM_EMAIL, to) notification.attach_alternative(facilitator_notification_html, 'text/html') notification.send()
def render_html_with_css(template, context): html = render_to_string_ctx(template, context) base_url = f"{settings.PROTOCOL}://{settings.DOMAIN}" html_with_inlined_css = Premailer(html, base_url=base_url, disable_validation=True).transform() return html_with_inlined_css
def send_community_digest(start_date, end_date): context = community_digest_data(start_date, end_date) chart_data = { "meetings_chart": charts.LearningCircleMeetingsChart( end_date.date()).generate(output="png"), "countries_chart": charts.LearningCircleCountriesChart( start_date.date(), end_date.date()).generate(output="png"), "top_topics_chart": charts.TopTopicsChart( end_date.date(), context['studygroups_that_met']).generate(output="png"), } context.update(chart_data) subject = render_to_string_ctx( 'studygroups/email/community_digest-subject.txt', context) html_body = render_html_with_css('studygroups/email/community_digest.html', context) text_body = html_body_to_text(html_body) to = [settings.COMMUNITY_DIGEST_EMAIL] msg = EmailMultiAlternatives(subject, text_body, settings.DEFAULT_FROM_EMAIL, to) msg.attach_alternative(html_body, 'text/html') msg.send()
def handle_new_study_group_creation(sender, instance, created, **kwargs): if not created: return study_group = instance context = { 'study_group': study_group, } subject = render_to_string_ctx( 'studygroups/email/learning_circle_created-subject.txt', context).strip(' \n') html_body = render_html_with_css( 'studygroups/email/learning_circle_created.html', context) text_body = html_body_to_text(html_body) # on all learning circles, CC p2pu cc = [settings.TEAM_EMAIL] # if the user is part of a team, send to the organizer(s) cc += [o.email for o in get_study_group_organizers(study_group)] # if there is a question, send to the welcoming comitte if study_group.facilitator_concerns: cc += [settings.COMMUNITY_MANAGER] notification = EmailMultiAlternatives( subject, text_body, settings.DEFAULT_FROM_EMAIL, [study_group.facilitator.email], cc=cc, reply_to=[study_group.facilitator.email] + cc) notification.attach_alternative(html_body, 'text/html') notification.send()
def send_announcement(sender, subject, body_text, body_html): """ Send message to all users that opted-in for the community email list """ # Check that account settings link is present in message account_settings_url = settings.PROTOCOL + '://' + settings.DOMAIN + reverse( 'account_settings') # check if account settings URL is in HTML body if not re.search(account_settings_url, body_html): settings_link = render_to_string_ctx( 'announce/account_settings_email_link.html') # if there is a body tag, add link before the closing body tag if re.search('</body>', body_html): settings_link = settings_link + '</body>' body_html = re.sub(r'</body>', settings_link, body_html) else: settings_link = settings_link body_html = body_html + settings_link # check if account settings URL is in text body if not re.search(account_settings_url, body_text): settings_link = _( 'If you would no longer like to receive announcements from P2PU, you can update your account preferences at {url}' ).format(url=account_settings_url) body_text = '\n'.join([body_text, settings_link]) # Get list of users who opted-in to communications users = User.objects.filter(is_active=True, profile__communication_opt_in=True) batch_size = 500 # send in batches of batch_size url = 'https://api.mailgun.net/v3/{}/messages'.format( settings.MAILGUN_DOMAIN) auth = HTTPBasicAuth('api', settings.MAILGUN_API_KEY) for index in range(0, len(users), batch_size): to = users[index:index + 500] post_data = [ ('from', sender), ('subject', subject), ('text', body_text), ('html', body_html), ('o:tracking', 'yes'), ('o:tracking-clicks', 'htmlonly'), ('o:tracking-opens', 'yes'), ('o:tag', 'announce'), ] post_data += [('to', u.email) for u in to] # Add recipient variables to ensure mailgun sends messages individually and # not with everyone in the to field. post_data += [('recipient-variables', json.dumps({u.email: {} for u in to}))] resp = requests.post(url, auth=auth, data=post_data) if resp.status_code != 200: logger.error('Could not send mailgun batch email')
def send_meeting_change_notification(old_meeting, new_meeting): study_group = new_meeting.study_group to = [ su.email for su in study_group.application_set.active().filter( accepted_at__isnull=False).exclude(email='') ] context = { 'old_meeting': old_meeting, 'new_meeting': new_meeting, 'learning_circle': study_group, } with use_language(study_group.language), timezone.override( pytz.timezone(study_group.timezone)): subject = render_to_string_ctx( 'studygroups/email/meeting_changed-subject.txt', context).strip('\n') html_body = render_to_string_ctx( 'studygroups/email/meeting_changed.html', context) text_body = html_body_to_text(html_body) sms_body = render_to_string_ctx( 'studygroups/email/meeting_changed-sms.txt', context).strip('\n') notification = EmailMultiAlternatives(subject, text_body, settings.DEFAULT_FROM_EMAIL, bcc=to) notification.attach_alternative(html_body, 'text/html') try: notification.send() except Exception as e: logger.exception('Could not send meeting change notification', exc_info=e) applications = study_group.application_set.active().filter( accepted_at__isnull=False).exclude(mobile='') applications = applications.filter(mobile_opt_out_at__isnull=True) tos = [su.mobile for su in applications] for to in tos: try: send_message(to, sms_body) except TwilioRestException as e: logger.exception("Could not send text message to %s", to, exc_info=e)
def form_valid(self, form): # send notification to organizers about feedback to = [] #TODO should we send this to someone if the facilitators is not part of a team? - for now, don't worry, this notification is likely to be removed. meeting = get_object_or_404(Meeting, pk=self.kwargs.get('study_group_meeting_id')) organizers = get_study_group_organizers(meeting.study_group) if organizers: to = [o.email for o in organizers] context = { 'feedback': form.save(commit=False), 'study_group_meeting': self.get_initial()['study_group_meeting'] } subject = render_to_string_ctx('studygroups/email/feedback-submitted-subject.txt', context).strip('\n') html_body = render_to_string_ctx('studygroups/email/feedback-submitted.html', context) text_body = render_to_string_ctx('studygroups/email/feedback-submitted.txt', context) notification = EmailMultiAlternatives(subject, text_body, settings.DEFAULT_FROM_EMAIL, to) notification.attach_alternative(html_body, 'text/html') notification.send() return super(FeedbackCreate, self).form_valid(form)
def send_organization_guide(self): email = self.cleaned_data['email'] first_name = self.cleaned_data['first_name'] last_name = self.cleaned_data['last_name'] location = self.cleaned_data['location'] context = { 'first_name': first_name, 'last_name': last_name, 'location': location, } to = [email, settings.TEAM_EMAIL] subject = render_to_string_ctx('studygroups/email/organizer_guide-subject.txt', context).strip('\n') html_body = render_to_string_ctx('studygroups/email/organizer_guide.html', context) text_body = render_to_string_ctx('studygroups/email/organizer_guide.txt', context) email = EmailMultiAlternatives(subject, text_body, settings.DEFAULT_FROM_EMAIL, to) email.attach_alternative(html_body, 'text/html') email.attach_file("static/files/organizer_guide.pdf") email.send()
def send_new_event_notification(event): to = User.objects.filter(is_staff=True).values_list('email', flat=True) context = {"event": event} subject = render_to_string_ctx('community_calendar/new_event-subject.txt', context).strip('\n') html_body = render_to_string_ctx('community_calendar/new_event.html', context) text_body = html_body_to_text(html_body) notification = EmailMultiAlternatives(subject, text_body, settings.DEFAULT_FROM_EMAIL, bcc=to) notification.attach_alternative(html_body, 'text/html') try: notification.send() except Exception as e: logger.exception('Could not sent event notification', exc_info=e)
def handle_new_application(sender, instance, created, **kwargs): """ Send welcome message to learner introducing them to their facilitator """ if not created: return application = instance # get a random piece of advice # TODO remove unused advice logic advice = None if application.study_group.language == 'en': advice = Advice.objects.order_by('?').first() # activate language and timezone for message reminder with use_language(application.study_group.language), timezone.override(pytz.timezone(application.study_group.timezone)): # Send welcome message to learner learner_signup_subject = render_to_string_ctx( 'studygroups/email/learner_signup-subject.txt', { 'application': application, 'advice': advice, } ).strip('\n') learner_signup_html = render_html_with_css( 'studygroups/email/learner_signup.html', { 'application': application, 'advice': advice, } ) learner_signup_body = html_body_to_text(learner_signup_html) to = [application.email] # CC facilitator and put in reply-to welcome_message = EmailMultiAlternatives( learner_signup_subject, learner_signup_body, settings.DEFAULT_FROM_EMAIL, to, cc=[application.study_group.facilitator.email], reply_to=[application.study_group.facilitator.email] ) welcome_message.attach_alternative(learner_signup_html, 'text/html') welcome_message.send()
def send_new_user_email(user): context = { "user": user, } subject_template = 'custom_registration/new_user_confirmed-subject.txt' html_email_template = 'custom_registration/new_user_confirmed.html' subject = render_to_string_ctx(subject_template, context).strip(' \n') html_body = render_html_with_css(html_email_template, context) text_body = html_body_to_text(html_body) to = [user.email] email = EmailMultiAlternatives( subject, text_body, settings.DEFAULT_FROM_EMAIL, to, ) email.attach_alternative(html_body, 'text/html') email.send()
def _send_learning_circle_insights(study_group): timezone.deactivate() goals_met_chart = charts.GoalsMetChart(study_group) report_path = reverse('studygroups_final_report', kwargs={'study_group_id': study_group.id}) recipients = study_group.application_set.active().values_list('email', flat=True) organizers = get_study_group_organizers(study_group) organizers_emails = [organizer.email for organizer in organizers] to = list(recipients) + organizers_emails to.append(study_group.facilitator.email) context = { 'study_group': study_group, 'report_path': report_path, 'facilitator_name': study_group.facilitator.first_name, 'registrations': study_group.application_set.active().count(), 'survey_responses': study_group.learnersurveyresponse_set.count(), 'goals_met_chart': goals_met_chart.generate(output="png"), 'learner_goals_chart': charts.goals_chart(study_group), } subject = render_to_string_ctx( 'studygroups/email/learning_circle_final_report-subject.txt', context).strip('\n') html = render_html_with_css( 'studygroups/email/learning_circle_final_report.html', context) txt = html_body_to_text(html) notification = EmailMultiAlternatives( subject, txt, settings.DEFAULT_FROM_EMAIL, bcc=to, reply_to=[study_group.facilitator.email]) notification.attach_alternative(html, 'text/html') notification.send()
def send_contact_form_inquiry(email, name, content, source, organization=None): context = { "email": email, "name": name, "content": content, "source": source, "organization": organization, } subject_template = 'contact/contact_email-subject.txt' html_email_template = 'contact/contact_email.html' subject = render_to_string_ctx(subject_template, context).strip(' \n') html_body = render_html_with_css(html_email_template, context) text_body = html_body_to_text(html_body) to = [settings.TEAM_EMAIL] email = EmailMultiAlternatives(subject, text_body, settings.DEFAULT_FROM_EMAIL, to, cc=[settings.SUPPORT_EMAIL], reply_to=[email, settings.SUPPORT_EMAIL]) email.attach_alternative(html_body, 'text/html') email.send()
def receive_sms(request): # TODO - secure this callback sender = request.POST.get('From') message = request.POST.get('Body') STOP_LIST = ['STOP', 'STOPALL', 'UNSUBSCRIBE', 'CANCEL', 'END', 'QUIT'] command = message.strip(string.punctuation + string.whitespace).upper() opt_out = command in STOP_LIST if opt_out: application_mobile_opt_out(sender) return http.HttpResponse(status=200) START_LIST = ['START', 'YES', 'UNSTOP'] opt_in = command in START_LIST if opt_in: application_mobile_opt_out_revert(sender) return http.HttpResponse(status=200) to = [] bcc = None subject = 'New SMS reply from {0}'.format(sender) context = { 'message': message, 'sender': sender, } # Only forward message to facilitator if there is a meeting in the future-ish today = datetime.datetime.now().date() yesterday = today - datetime.timedelta(days=1) meetings = Meeting.objects.active().filter(meeting_date__gte=yesterday) signups = Application.objects.active().filter( Q(mobile=sender) & Q(mobile_opt_out_at__isnull=True) & Q(study_group__in=meetings.values('study_group'))) # TODO handle user signed up to 2 learning circles if signups.count() == 1: signup = signups.first() context['signup'] = signup # TODO i18n subject = 'New SMS reply from {0} <{1}>'.format(signup.name, sender) to += [signup.study_group.facilitator.email] next_meeting = signups.first().study_group.next_meeting() # TODO - replace this check with a check to see if the meeting reminder has been sent if next_meeting and next_meeting.meeting_datetime() - timezone.now( ) < datetime.timedelta(days=2): context['next_meeting'] = next_meeting context['rsvp_yes'] = next_meeting.rsvp_yes_link(sender) context['rsvp_no'] = next_meeting.rsvp_no_link(sender) text_body = render_to_string_ctx('studygroups/email/incoming_sms.txt', context) html_body = render_to_string_ctx('studygroups/email/incoming_sms.html', context) if len(to) == 0: to = [a[1] for a in settings.ADMINS] else: bcc = [a[1] for a in settings.ADMINS] notification = EmailMultiAlternatives(subject, text_body, settings.DEFAULT_FROM_EMAIL, to, bcc) notification.attach_alternative(html_body, 'text/html') notification.send() return http.HttpResponse(status=200)