Add a send_email function that takes a template_prefix and context.

This commit replaces all uses of django.core.mail.send_mail with send_email,
other than in the password reset flow, since that code looks like it is just
a patch to Django's password reset code.

The send_email function is in a new file, since putting it in
zerver.lib.notifications would create an import loop with confirmation.models.

send_future_email will soon be moved into email.py as well.
This commit is contained in:
Rishi Gupta
2017-05-01 16:15:58 -07:00
committed by Tim Abbott
parent 55a7aa4f9d
commit 925ee8c0f1
6 changed files with 45 additions and 41 deletions

View File

@@ -8,15 +8,14 @@ import re
from django.db import models from django.db import models
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.core.mail import send_mail
from django.conf import settings from django.conf import settings
from django.template import loader, Context, TemplateDoesNotExist
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.fields import GenericForeignKey
from django.utils.timezone import now as timezone_now from django.utils.timezone import now as timezone_now
from confirmation.util import get_status_field from confirmation.util import get_status_field
from zerver.lib.send_email import send_email
from zerver.lib.utils import generate_random_token from zerver.lib.utils import generate_random_token
from zerver.models import PreregistrationUser, EmailChangeStatus from zerver.models import PreregistrationUser, EmailChangeStatus
from typing import Any, Dict, Optional, Text, Union from typing import Any, Dict, Optional, Text, Union
@@ -91,31 +90,24 @@ class ConfirmationManager(models.Manager):
# type: () -> int # type: () -> int
return getattr(settings, 'EMAIL_CONFIRMATION_DAYS', 10) return getattr(settings, 'EMAIL_CONFIRMATION_DAYS', 10)
def send_confirmation(self, obj, template_prefix, email_address, additional_context=None, def send_confirmation(self, obj, template_prefix, to_email, additional_context=None,
host=None, custom_body=None): host=None, custom_body=None):
# type: (ContentType, str, Text, Optional[Dict[str, Any]], Optional[str], Optional[str]) -> Confirmation # type: (ContentType, str, Text, Optional[Dict[str, Any]], Optional[str], Optional[str]) -> Confirmation
confirmation_key = generate_key() confirmation_key = generate_key()
current_site = Site.objects.get_current() current_site = Site.objects.get_current()
activate_url = self.get_activation_url(confirmation_key, host=host) activate_url = self.get_activation_url(confirmation_key, host=host)
context = Context({ context = {
'activate_url': activate_url, 'activate_url': activate_url,
'current_site': current_site, 'current_site': current_site,
'confirmation_key': confirmation_key, 'confirmation_key': confirmation_key,
'target': obj, 'target': obj,
'days': getattr(settings, 'EMAIL_CONFIRMATION_DAYS', 10), 'days': getattr(settings, 'EMAIL_CONFIRMATION_DAYS', 10),
'custom_body': custom_body, 'custom_body': custom_body,
}) }
if additional_context is not None: if additional_context is not None:
context.update(additional_context) context.update(additional_context)
subject = loader.render_to_string(template_prefix + '.subject', context).strip() send_email(template_prefix, to_email, from_email=settings.DEFAULT_FROM_EMAIL, context=context)
body = loader.render_to_string(template_prefix + '.txt', context)
try:
html_content = loader.render_to_string(template_prefix + '.html', context)
except TemplateDoesNotExist:
html_content = None
send_mail(subject, body, settings.DEFAULT_FROM_EMAIL, [email_address], html_message=html_content)
return self.create(content_object=obj, date_sent=timezone_now(), confirmation_key=confirmation_key) return self.create(content_object=obj, date_sent=timezone_now(), confirmation_key=confirmation_key)
class EmailChangeConfirmationManager(ConfirmationManager): class EmailChangeConfirmationManager(ConfirmationManager):

29
zerver/lib/send_email.py Normal file
View File

@@ -0,0 +1,29 @@
from django.conf import settings
from django.core.mail import send_mail
from django.template import loader, TemplateDoesNotExist
from zerver.models import UserProfile
from typing import Dict, List, Optional, Text
def display_email(user):
# type: (UserProfile) -> Text
# Change to '%s <%s>' % (user.full_name, user.email) once
# https://github.com/zulip/zulip/issues/4676 is resolved
return user.email
def send_email(template_prefix, to_email, from_email=None, context={}):
# type: (str, Text, Optional[Text], Dict[str, Any]) -> bool
subject = loader.render_to_string(template_prefix + '.subject', context).strip()
message = loader.render_to_string(template_prefix + '.txt', context)
# Remove try/expect once https://github.com/zulip/zulip/issues/4691 is resolved.
try:
html_message = loader.render_to_string(template_prefix + '.html', context)
except TemplateDoesNotExist:
html_message = None
if from_email is None:
from_email = settings.NOREPLY_EMAIL_ADDRESS
return send_mail(subject, message, from_email, [to_email], html_message=html_message) > 0
def send_email_to_user(template_prefix, user, from_email=None, context={}):
# type: (str, UserProfile, Optional[Text], Dict[str, Text]) -> bool
return send_email(template_prefix, display_email(user), from_email=from_email, context=context)

View File

@@ -65,6 +65,9 @@ class Command(BaseCommand):
} }
logging.warning("Sending %s email to %s" % (email_template_name, user_profile.email,)) logging.warning("Sending %s email to %s" % (email_template_name, user_profile.email,))
# Our password reset flow is basically a patch of Django's password
# reset flow, so this is aligned with Django code rather than using
# zerver.lib.send_email.send_email.
self.send_email(subject_template_name, email_template_name, self.send_email(subject_template_name, email_template_name,
context, from_email, user_profile.email, context, from_email, user_profile.email,
html_email_template_name=html_email_template_name) html_email_template_name=html_email_template_name)

View File

@@ -2,12 +2,12 @@ from __future__ import absolute_import
from django.dispatch import receiver from django.dispatch import receiver
from django.contrib.auth.signals import user_logged_in from django.contrib.auth.signals import user_logged_in
from django.core.mail import send_mail
from django.conf import settings from django.conf import settings
from django.template import loader from django.template import loader
from django.utils.timezone import get_current_timezone_name as timezone_get_current_timezone_name from django.utils.timezone import get_current_timezone_name as timezone_get_current_timezone_name
from django.utils.timezone import now as timezone_now from django.utils.timezone import now as timezone_now
from typing import Any, Dict, Optional from typing import Any, Dict, Optional
from zerver.lib.send_email import send_email_to_user
from zerver.models import UserProfile from zerver.models import UserProfile
def get_device_browser(user_agent): def get_device_browser(user_agent):
@@ -85,10 +85,4 @@ def email_on_new_login(sender, user, request, **kwargs):
context['zulip_support'] = settings.ZULIP_ADMINISTRATOR context['zulip_support'] = settings.ZULIP_ADMINISTRATOR
context['user'] = user context['user'] = user
text_content = loader.render_to_string('zerver/emails/notify_new_login.txt', context) send_email_to_user('zerver/emails/notify_new_login', user, context=context)
html_content = loader.render_to_string('zerver/emails/notify_new_login.html', context)
sender = settings.NOREPLY_EMAIL_ADDRESS
recipients = [user.email]
subject = loader.render_to_string('zerver/emails/notify_new_login.subject').strip()
send_mail(subject, text_content, sender, recipients, html_message=html_content)

View File

@@ -12,11 +12,11 @@ from django.template import RequestContext, loader
from django.utils.timezone import now from django.utils.timezone import now
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core import validators from django.core import validators
from django.core.mail import send_mail
from zerver.models import UserProfile, Realm, PreregistrationUser, \ from zerver.models import UserProfile, Realm, PreregistrationUser, \
name_changes_disabled, email_to_username, \ name_changes_disabled, email_to_username, \
completely_open, get_unique_open_realm, email_allowed_for_realm, \ completely_open, get_unique_open_realm, email_allowed_for_realm, \
get_realm, get_realm_by_email_domain get_realm, get_realm_by_email_domain
from zerver.lib.send_email import send_email_to_user
from zerver.lib.events import do_events_register from zerver.lib.events import do_events_register
from zerver.lib.actions import do_change_password, do_change_full_name, do_change_is_admin, \ from zerver.lib.actions import do_change_password, do_change_full_name, do_change_is_admin, \
do_activate_user, do_create_user, do_create_realm, set_default_streams, \ do_activate_user, do_create_user, do_create_realm, set_default_streams, \
@@ -390,17 +390,6 @@ def generate_204(request):
# type: (HttpRequest) -> HttpResponse # type: (HttpRequest) -> HttpResponse
return HttpResponse(content=None, status=204) return HttpResponse(content=None, status=204)
def send_find_my_team_emails(user_profile):
# type: (UserProfile) -> None
context = {'user_profile': user_profile}
text_content = loader.render_to_string('zerver/emails/find_team.txt', context)
html_content = loader.render_to_string('zerver/emails/find_team.html', context)
sender = settings.NOREPLY_EMAIL_ADDRESS
recipients = [user_profile.email]
subject = loader.render_to_string('zerver/emails/find_team.subject').strip()
send_mail(subject, text_content, sender, recipients, html_message=html_content)
def find_my_team(request): def find_my_team(request):
# type: (HttpRequest) -> HttpResponse # type: (HttpRequest) -> HttpResponse
url = reverse('zerver.views.registration.find_my_team') url = reverse('zerver.views.registration.find_my_team')
@@ -411,7 +400,8 @@ def find_my_team(request):
if form.is_valid(): if form.is_valid():
emails = form.cleaned_data['emails'] emails = form.cleaned_data['emails']
for user_profile in UserProfile.objects.filter(email__in=emails): for user_profile in UserProfile.objects.filter(email__in=emails):
send_find_my_team_emails(user_profile) send_email_to_user('zerver/emails/find_team', user_profile,
context={'user_profile': user_profile})
# Note: Show all the emails in the result otherwise this # Note: Show all the emails in the result otherwise this
# feature can be used to ascertain which email addresses # feature can be used to ascertain which email addresses

View File

@@ -5,10 +5,8 @@ from typing import Dict, Text
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.conf import settings from django.conf import settings
from django.contrib.auth import authenticate, update_session_auth_hash from django.contrib.auth import authenticate, update_session_auth_hash
from django.core.mail import send_mail
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from django.shortcuts import redirect, render from django.shortcuts import redirect, render
from django.template.loader import render_to_string
from django.urls import reverse from django.urls import reverse
from zerver.decorator import authenticated_json_post_view, has_request_variables, \ from zerver.decorator import authenticated_json_post_view, has_request_variables, \
@@ -24,6 +22,7 @@ from zerver.lib.actions import do_change_password, \
do_change_pm_content_in_desktop_notifications, validate_email, \ do_change_pm_content_in_desktop_notifications, validate_email, \
do_change_user_email, do_start_email_change_process do_change_user_email, do_start_email_change_process
from zerver.lib.avatar import avatar_url from zerver.lib.avatar import avatar_url
from zerver.lib.send_email import send_email, display_email
from zerver.lib.i18n import get_available_language_codes from zerver.lib.i18n import get_available_language_codes
from zerver.lib.response import json_success, json_error from zerver.lib.response import json_success, json_error
from zerver.lib.upload import upload_avatar_image from zerver.lib.upload import upload_avatar_image
@@ -59,11 +58,8 @@ def confirm_email_change(request, confirmation_key):
'realm': obj.realm, 'realm': obj.realm,
'new_email': new_email, 'new_email': new_email,
} }
subject = render_to_string( send_email('zerver/emails/notify_change_in_email', old_email,
'zerver/emails/notify_change_in_email.subject', context) from_email=settings.DEFAULT_FROM_EMAIL, context=context)
body = render_to_string(
'zerver/emails/notify_change_in_email.txt', context)
send_mail(subject, body, settings.DEFAULT_FROM_EMAIL, [old_email])
ctx = { ctx = {
'confirmed': confirmed, 'confirmed': confirmed,