def get_id_token(user): """ Return a JWT for `user`, suitable for use with the course discovery service. Arguments: user (User): User for whom to generate the JWT. Returns: str: The JWT. """ try: # Service users may not have user profiles. full_name = UserProfile.objects.get(user=user).name except UserProfile.DoesNotExist: full_name = None now = datetime.datetime.utcnow() expires_in = getattr(settings, 'OAUTH_ID_TOKEN_EXPIRATION', 30) payload = { 'preferred_username': user.username, 'name': full_name, 'email': user.email, 'administrator': user.is_staff, 'iss': helpers.get_value('OAUTH_OIDC_ISSUER', settings.OAUTH_OIDC_ISSUER), 'exp': now + datetime.timedelta(seconds=expires_in), 'iat': now, 'aud': helpers.get_value('JWT_AUTH', settings.JWT_AUTH)['JWT_AUDIENCE'], 'sub': anonymous_id_for_user(user, None), } secret_key = helpers.get_value('JWT_AUTH', settings.JWT_AUTH)['JWT_SECRET_KEY'] return jwt.encode(payload, secret_key)
def is_commerce_service_configured(): """ Return a Boolean indicating whether or not configuration is present to use the external commerce service. """ ecommerce_api_url = helpers.get_value("ECOMMERCE_API_URL", settings.ECOMMERCE_API_URL) ecommerce_api_signing_key = helpers.get_value("ECOMMERCE_API_SIGNING_KEY", settings.ECOMMERCE_API_SIGNING_KEY) return bool(ecommerce_api_url and ecommerce_api_signing_key)
def is_commerce_service_configured(): """ Return a Boolean indicating whether or not configuration is present to use the external commerce service. """ ecommerce_api_url = helpers.get_value("ECOMMERCE_API_URL", settings.ECOMMERCE_API_URL) ecommerce_api_signing_key = helpers.get_value( "ECOMMERCE_API_SIGNING_KEY", settings.ECOMMERCE_API_SIGNING_KEY) return bool(ecommerce_api_url and ecommerce_api_signing_key)
def ecommerce_api_client(user): """ Returns an E-Commerce API client setup with authentication for the specified user. """ return EdxRestApiClient( helpers.get_value("ECOMMERCE_API_URL", settings.ECOMMERCE_API_URL), helpers.get_value("ECOMMERCE_API_SIGNING_KEY", settings.ECOMMERCE_API_SIGNING_KEY), user.username, user.profile.name if hasattr(user, 'profile') else None, user.email, tracking_context=create_tracking_context(user), issuer=settings.JWT_ISSUER, expires_in=settings.JWT_EXPIRATION )
def ecommerce_api_client(user): """ Returns an E-Commerce API client setup with authentication for the specified user. """ jwt_auth = helpers.get_value("JWT_AUTH", settings.JWT_AUTH) return EdxRestApiClient( helpers.get_value("ECOMMERCE_API_URL", settings.ECOMMERCE_API_URL), helpers.get_value("ECOMMERCE_API_SIGNING_KEY", settings.ECOMMERCE_API_SIGNING_KEY), user.username, user.profile.name if hasattr(user, 'profile') else None, user.email, tracking_context=create_tracking_context(user), issuer=jwt_auth['JWT_ISSUER'], expires_in=jwt_auth['JWT_EXPIRATION'])
def request_password_change(email, orig_host, is_secure): """Email a single-use link for performing a password reset. Users must confirm the password change before we update their information. Args: email (str): An email address orig_host (str): An originating host, extracted from a request with get_host is_secure (bool): Whether the request was made with HTTPS Returns: None Raises: UserNotFound AccountRequestError UserAPIInternalError: the operation failed due to an unexpected error. """ # Binding data to a form requires that the data be passed as a dictionary # to the Form class constructor. form = PasswordResetFormNoActive({'email': email}) # Validate that a user exists with the given email address. if form.is_valid(): # Generate a single-use link for performing a password reset # and email it to the user. form.save(from_email=theming_helpers.get_value( 'default_from_email', settings.DEFAULT_FROM_EMAIL), domain_override=orig_host, use_https=is_secure) else: # No user with the provided email address exists. raise UserNotFound
def test_reset_password_email(self, send_email): """Tests contents of reset password email, and that user is not active""" good_req = self.request_factory.post('/password_reset/', {'email': self.user.email}) good_req.user = self.user good_resp = password_reset(good_req) self.assertEquals(good_resp.status_code, 200) obj = json.loads(good_resp.content) self.assertEquals(obj, { 'success': True, 'value': "('registration/password_reset_done.html', [])", }) (subject, msg, from_addr, to_addrs) = send_email.call_args[0] self.assertIn("Password reset", subject) self.assertIn("You're receiving this e-mail because you requested a password reset", msg) self.assertEquals(from_addr, theming_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL)) self.assertEquals(len(to_addrs), 1) self.assertIn(self.user.email, to_addrs) self.assert_event_emitted( SETTING_CHANGE_INITIATED, user_id=self.user.id, setting=u'password', old=None, new=None, ) #test that the user is not active self.user = User.objects.get(pk=self.user.pk) self.assertFalse(self.user.is_active) re.search(r'password_reset_confirm/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/', msg).groupdict()
def test_reset_password_email(self, send_email): """Tests contents of reset password email, and that user is not active""" good_req = self.request_factory.post('/password_reset/', {'email': self.user.email}) good_req.user = self.user good_resp = password_reset(good_req) self.assertEquals(good_resp.status_code, 200) obj = json.loads(good_resp.content) self.assertEquals(obj, { 'success': True, 'value': "('registration/password_reset_done.html', [])", }) (subject, msg, from_addr, to_addrs) = send_email.call_args[0] self.assertIn("Password reset", subject) self.assertIn("You're receiving this e-mail because you requested a password reset", msg) self.assertEquals(from_addr, theming_helpers.get_value('default_from_email', settings.DEFAULT_FROM_EMAIL)) self.assertEquals(len(to_addrs), 1) self.assertIn(self.user.email, to_addrs) self.assert_event_emitted( SETTING_CHANGE_INITIATED, user_id=self.user.id, setting=u'password', old=None, new=None, ) #test that the user is not active self.user = User.objects.get(pk=self.user.pk) self.assertFalse(self.user.is_active) re.search(r'password_reset_confirm/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/', msg).groupdict()
def _generate_jwt(self, user, scopes, expires_in): """ Returns a JWT access token. """ now = int(time()) jwt_auth = helpers.get_value("JWT_AUTH", settings.JWT_AUTH) payload = { 'iss': jwt_auth['JWT_ISSUER'], 'aud': jwt_auth['JWT_AUDIENCE'], 'exp': now + expires_in, 'iat': now, 'preferred_username': user.username, 'scopes': scopes, } for scope in scopes: handler = self.claim_handlers.get(scope) if handler: handler(payload, user) secret = jwt_auth['JWT_SECRET_KEY'] token = jwt.encode(payload, secret, algorithm=jwt_auth['JWT_ALGORITHM']) return token
def request_password_change(email, orig_host, is_secure): """Email a single-use link for performing a password reset. Users must confirm the password change before we update their information. Args: email (str): An email address orig_host (str): An originating host, extracted from a request with get_host is_secure (bool): Whether the request was made with HTTPS Returns: None Raises: UserNotFound AccountRequestError UserAPIInternalError: the operation failed due to an unexpected error. """ # Binding data to a form requires that the data be passed as a dictionary # to the Form class constructor. form = PasswordResetFormNoActive({'email': email}) # Validate that a user exists with the given email address. if form.is_valid(): # Generate a single-use link for performing a password reset # and email it to the user. form.save( from_email=theming_helpers.get_value('default_from_email', settings.DEFAULT_FROM_EMAIL), domain_override=orig_host, use_https=is_secure ) else: # No user with the provided email address exists. raise UserNotFound
def _get_course_email_context(course): """ Returns context arguments to apply to all emails, independent of recipient. """ course_id = course.id.to_deprecated_string() course_title = course.display_name course_end_date = get_default_time_display(course.end) course_url = 'https://{}{}'.format( settings.SITE_NAME, reverse('course_root', kwargs={'course_id': course_id})) image_url = u'https://{}{}'.format(settings.SITE_NAME, course_image_url(course)) email_context = { 'course_title': course_title, 'course_url': course_url, 'course_image_url': image_url, 'course_end_date': course_end_date, 'account_settings_url': 'https://{}{}'.format(settings.SITE_NAME, reverse('account_settings')), 'email_settings_url': 'https://{}{}'.format(settings.SITE_NAME, reverse('dashboard')), 'platform_name': theming_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME), } return email_context
def course_discovery_api_client(user): """ Returns a Course Discovery API client setup with authentication for the specified user. """ course_discovery_client = Client.objects.get(name=CLIENT_NAME) secret_key = helpers.get_value('JWT_AUTH', settings.JWT_AUTH)['JWT_SECRET_KEY'] return EdxRestApiClient( course_discovery_client.url, jwt=get_id_token(user, CLIENT_NAME, secret_key=secret_key) )
def payment_page_url(self): """ Return the URL for the checkout page. Example: http://localhost:8002/basket/single_item/ """ ecommerce_url_root = helpers.get_value('ECOMMERCE_PUBLIC_URL_ROOT', settings.ECOMMERCE_PUBLIC_URL_ROOT) return urljoin(ecommerce_url_root, self.config.single_course_checkout_page)
def course_discovery_api_client(user): """ Returns a Course Discovery API client setup with authentication for the specified user. """ course_discovery_client = Client.objects.get(name=CLIENT_NAME) secret_key = helpers.get_value('JWT_AUTH', settings.JWT_AUTH)['JWT_SECRET_KEY'] return EdxRestApiClient(course_discovery_client.url, jwt=get_id_token(user, CLIENT_NAME, secret_key=secret_key))
def test_get_value_returns_override(self): """ Tests to make sure the get_value() operation returns a combined dictionary consisting of the base container with overridden keys from the microsite configuration """ with patch('microsite_configuration.microsite.get_value') as mock_get_value: override_key = 'JWT_ISSUER' override_value = 'testing' mock_get_value.return_value = {override_key: override_value} jwt_auth = helpers.get_value('JWT_AUTH') self.assertEqual(jwt_auth[override_key], override_value)
def generate_refund_notification_body(student, refund_ids): # pylint: disable=invalid-name """ Returns a refund notification message body. """ msg = _( "A refund request has been initiated for {username} ({email}). " "To process this request, please visit the link(s) below." ).format(username=student.username, email=student.email) ecommerce_url_root = get_value('ECOMMERCE_PUBLIC_URL_ROOT', settings.ECOMMERCE_PUBLIC_URL_ROOT) refund_urls = [urljoin(ecommerce_url_root, '/dashboard/refunds/{}/'.format(refund_id)) for refund_id in refund_ids] return '{msg}\n\n{urls}'.format(msg=msg, urls='\n'.join(refund_urls))
def test_get_value_returns_override(self): """ Tests to make sure the get_value() operation returns a combined dictionary consisting of the base container with overridden keys from the microsite configuration """ with patch('microsite_configuration.microsite.get_value' ) as mock_get_value: override_key = 'JWT_ISSUER' override_value = 'testing' mock_get_value.return_value = {override_key: override_value} jwt_auth = helpers.get_value('JWT_AUTH') self.assertEqual(jwt_auth[override_key], override_value)
def generate_refund_notification_body(student, refund_ids): # pylint: disable=invalid-name """ Returns a refund notification message body. """ msg = _("A refund request has been initiated for {username} ({email}). " "To process this request, please visit the link(s) below.").format( username=student.username, email=student.email) ecommerce_url_root = get_value('ECOMMERCE_PUBLIC_URL_ROOT', settings.ECOMMERCE_PUBLIC_URL_ROOT) refund_urls = [ urljoin(ecommerce_url_root, '/dashboard/refunds/{}/'.format(refund_id)) for refund_id in refund_ids ] return '{msg}\n\n{urls}'.format(msg=msg, urls='\n'.join(refund_urls))
def format_address(course_title_no_quotes): """ Partial function for formatting the from_addr. Since `course_title_no_quotes` may be truncated to make sure the returned string has fewer than 320 characters, we define this function to make it easy to determine quickly what the max length is for `course_title_no_quotes`. """ return from_addr_format.format( course_title=course_title_no_quotes, course_name=course_name, from_email=theming_helpers.get_value( 'bulk_email_default_from_email', settings.BULK_EMAIL_DEFAULT_FROM_EMAIL))
def assertEmailUser(self, email_user, subject_template, subject_context, body_template, body_context): """Assert that `email_user` was used to send and email with the supplied subject and body `email_user`: The mock `django.contrib.auth.models.User.email_user` function to verify `subject_template`: The template to have been used for the subject `subject_context`: The context to have been used for the subject `body_template`: The template to have been used for the body `body_context`: The context to have been used for the body """ email_user.assert_called_with( mock_render_to_string(subject_template, subject_context), mock_render_to_string(body_template, body_context), theming_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL) )
def assertEmailUser(self, email_user, subject_template, subject_context, body_template, body_context): """Assert that `email_user` was used to send and email with the supplied subject and body `email_user`: The mock `django.contrib.auth.models.User.email_user` function to verify `subject_template`: The template to have been used for the subject `subject_context`: The context to have been used for the subject `body_template`: The template to have been used for the body `body_context`: The context to have been used for the body """ email_user.assert_called_with( mock_render_to_string(subject_template, subject_context), mock_render_to_string(body_template, body_context), theming_helpers.get_value('default_from_email', settings.DEFAULT_FROM_EMAIL) )
def format_address(course_title_no_quotes): """ Partial function for formatting the from_addr. Since `course_title_no_quotes` may be truncated to make sure the returned string has fewer than 320 characters, we define this function to make it easy to determine quickly what the max length is for `course_title_no_quotes`. """ return from_addr_format.format( course_title=course_title_no_quotes, course_name=course_name, from_email=theming_helpers.get_value( 'bulk_email_default_from_email', settings.BULK_EMAIL_DEFAULT_FROM_EMAIL ) )
def save(self, domain_override=None, subject_template_name='registration/password_reset_subject.txt', email_template_name='registration/password_reset_email.html', use_https=False, token_generator=default_token_generator, from_email=theming_helpers.get_value('default_from_email', settings.DEFAULT_FROM_EMAIL), request=None): """ Generates a one-use only link for resetting password and sends to the user. """ # This import is here because we are copying and modifying the .save from Django 1.4.5's # django.contrib.auth.forms.PasswordResetForm directly, which has this import in this place. from django.core.mail import send_mail for user in self.users_cache: if not domain_override: site_name = microsite.get_value('SITE_NAME', settings.SITE_NAME) else: site_name = domain_override context = { 'email': user.email, 'site_name': site_name, 'uid': int_to_base36(user.id), 'user': user, 'token': token_generator.make_token(user), 'protocol': 'https' if use_https else 'http', 'platform_name': microsite.get_value('platform_name', settings.PLATFORM_NAME) } subject = loader.render_to_string(subject_template_name, context) # Email subject *must not* contain newlines subject = subject.replace('\n', '') email = loader.render_to_string(email_template_name, context) send_mail(subject, email, from_email, [user.email])
def test_email_success(self, send_mail): """ Test email was sent if no errors encountered. """ old_email = self.user.email new_email = "*****@*****.**" registration_key = "test registration key" self.assertIsNone(self.do_email_change(self.user, new_email, registration_key)) context = { 'key': registration_key, 'old_email': old_email, 'new_email': new_email } send_mail.assert_called_with( mock_render_to_string('emails/email_change_subject.txt', context), mock_render_to_string('emails/email_change.txt', context), theming_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL), [new_email] ) self.assert_event_emitted( SETTING_CHANGE_INITIATED, user_id=self.user.id, setting=u'email', old=old_email, new=new_email )
def test_email_success(self, send_mail): """ Test email was sent if no errors encountered. """ old_email = self.user.email new_email = "*****@*****.**" registration_key = "test registration key" self.assertIsNone(self.do_email_change(self.user, new_email, registration_key)) context = { 'key': registration_key, 'old_email': old_email, 'new_email': new_email } send_mail.assert_called_with( mock_render_to_string('emails/email_change_subject.txt', context), mock_render_to_string('emails/email_change.txt', context), theming_helpers.get_value('default_from_email', settings.DEFAULT_FROM_EMAIL), [new_email] ) self.assert_event_emitted( SETTING_CHANGE_INITIATED, user_id=self.user.id, setting=u'email', old=old_email, new=new_email )
def _generate_jwt(self, user, scopes, expires_in): """ Returns a JWT access token. """ now = int(time()) jwt_auth = helpers.get_value("JWT_AUTH", settings.JWT_AUTH) payload = { 'iss': jwt_auth['JWT_ISSUER'], 'aud': jwt_auth['JWT_AUDIENCE'], 'exp': now + expires_in, 'iat': now, 'preferred_username': user.username, } for scope in scopes: handler = self.claim_handlers.get(scope) if handler: handler(payload, user) secret = jwt_auth['JWT_SECRET_KEY'] token = jwt.encode(payload, secret, algorithm=jwt_auth['JWT_ALGORITHM']) return token
def save( self, domain_override=None, subject_template_name='registration/password_reset_subject.txt', email_template_name='registration/password_reset_email.html', use_https=False, token_generator=default_token_generator, from_email=theming_helpers.get_value('default_from_email', settings.DEFAULT_FROM_EMAIL), request=None ): """ Generates a one-use only link for resetting password and sends to the user. """ # This import is here because we are copying and modifying the .save from Django 1.4.5's # django.contrib.auth.forms.PasswordResetForm directly, which has this import in this place. from django.core.mail import send_mail for user in self.users_cache: if not domain_override: site_name = microsite.get_value( 'SITE_NAME', settings.SITE_NAME ) else: site_name = domain_override context = { 'email': user.email, 'site_name': site_name, 'uid': int_to_base36(user.id), 'user': user, 'token': token_generator.make_token(user), 'protocol': 'https' if use_https else 'http', 'platform_name': microsite.get_value('platform_name', settings.PLATFORM_NAME) } subject = loader.render_to_string(subject_template_name, context) # Email subject *must not* contain newlines subject = subject.replace('\n', '') email = loader.render_to_string(email_template_name, context) send_mail(subject, email, from_email, [user.email])
def _get_course_email_context(course): """ Returns context arguments to apply to all emails, independent of recipient. """ course_id = course.id.to_deprecated_string() course_title = course.display_name course_end_date = get_default_time_display(course.end) course_url = 'https://{}{}'.format( settings.SITE_NAME, reverse('course_root', kwargs={'course_id': course_id}) ) image_url = u'https://{}{}'.format(settings.SITE_NAME, course_image_url(course)) email_context = { 'course_title': course_title, 'course_url': course_url, 'course_image_url': image_url, 'course_end_date': course_end_date, 'account_settings_url': 'https://{}{}'.format(settings.SITE_NAME, reverse('account_settings')), 'email_settings_url': 'https://{}{}'.format(settings.SITE_NAME, reverse('dashboard')), 'platform_name': theming_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME), } return email_context
def save( self, domain_override=None, subject_template_name="registration/password_reset_subject.txt", email_template_name="registration/password_reset_email.html", use_https=False, token_generator=default_token_generator, from_email=theming_helpers.get_value("email_from_address", settings.DEFAULT_FROM_EMAIL), request=None, ): """ Generates a one-use only link for resetting password and sends to the user. """ # This import is here because we are copying and modifying the .save from Django 1.4.5's # django.contrib.auth.forms.PasswordResetForm directly, which has this import in this place. from django.core.mail import send_mail for user in self.users_cache: if not domain_override: site_name = microsite.get_value("SITE_NAME", settings.SITE_NAME) else: site_name = domain_override context = { "email": user.email, "site_name": site_name, "uid": int_to_base36(user.id), "user": user, "token": token_generator.make_token(user), "protocol": "https" if use_https else "http", "platform_name": microsite.get_value("platform_name", settings.PLATFORM_NAME), } subject = loader.render_to_string(subject_template_name, context) # Email subject *must not* contain newlines subject = subject.replace("\n", "") email = loader.render_to_string(email_template_name, context) send_mail(subject, email, from_email, [user.email])
def __init__(self, user, asymmetric=False, secret=None): self.user = user self.asymmetric = asymmetric self.secret = secret self.jwt_auth = helpers.get_value('JWT_AUTH', settings.JWT_AUTH)
def send_credit_notifications(username, course_key): """Sends email notification to user on different phases during credit course e.g., credit eligibility, credit payment etc. """ try: user = User.objects.get(username=username) except User.DoesNotExist: log.error("No user with %s exist", username) return course = modulestore().get_course(course_key, depth=0) course_display_name = course.display_name tracking_context = tracker.get_tracker().resolve_context() tracking_id = str(tracking_context.get("user_id")) client_id = str(tracking_context.get("client_id")) events = "&t=event&ec=email&ea=open" tracking_pixel = "https://www.google-analytics.com/collect?v=1&tid" + tracking_id + "&cid" + client_id + events dashboard_link = _email_url_parser("dashboard") credit_course_link = _email_url_parser("courses", "?type=credit") # get attached branded logo logo_image = cache.get("credit.email.attached-logo") if logo_image is None: branded_logo = {"title": "Logo", "path": settings.NOTIFICATION_EMAIL_EDX_LOGO, "cid": str(uuid.uuid4())} logo_image_id = branded_logo["cid"] logo_image = attach_image(branded_logo, "Header Logo") if logo_image: cache.set("credit.email.attached-logo", logo_image, settings.CREDIT_NOTIFICATION_CACHE_TIMEOUT) else: # strip enclosing angle brackets from 'logo_image' cache 'Content-ID' logo_image_id = logo_image.get("Content-ID", "")[1:-1] providers_names = get_credit_provider_display_names(course_key) providers_string = make_providers_strings(providers_names) context = { "full_name": user.get_full_name(), "platform_name": theming_helpers.get_value("PLATFORM_NAME", settings.PLATFORM_NAME), "course_name": course_display_name, "branded_logo": logo_image_id, "dashboard_link": dashboard_link, "credit_course_link": credit_course_link, "tracking_pixel": tracking_pixel, "providers": providers_string, } # create the root email message notification_msg = MIMEMultipart("related") # add 'alternative' part to root email message to encapsulate the plain and # HTML versions, so message agents can decide which they want to display. msg_alternative = MIMEMultipart("alternative") notification_msg.attach(msg_alternative) # render the credit notification templates subject = _(u"Course Credit Eligibility") if providers_string: subject = _(u"You are eligible for credit from {providers_string}").format(providers_string=providers_string) # add alternative plain text message email_body_plain = render_to_string("credit_notifications/credit_eligibility_email.txt", context) msg_alternative.attach(SafeMIMEText(email_body_plain, _subtype="plain", _charset="utf-8")) # add alternative html message email_body_content = cache.get("credit.email.css-email-body") if email_body_content is None: html_file_path = file_path_finder("templates/credit_notifications/credit_eligibility_email.html") if html_file_path: with open(html_file_path, "r") as cur_file: cur_text = cur_file.read() # use html parser to unescape html characters which are changed # by the 'pynliner' while adding inline css to html content html_parser = HTMLParser.HTMLParser() email_body_content = html_parser.unescape(with_inline_css(cur_text)) # cache the email body content before rendering it since the # email context will change for each user e.g., 'full_name' cache.set("credit.email.css-email-body", email_body_content, settings.CREDIT_NOTIFICATION_CACHE_TIMEOUT) else: email_body_content = "" email_body = Template(email_body_content).render([context]) msg_alternative.attach(SafeMIMEText(email_body, _subtype="html", _charset="utf-8")) # attach logo image if logo_image: notification_msg.attach(logo_image) # add email addresses of sender and receiver from_address = theming_helpers.get_value("email_from_address", settings.DEFAULT_FROM_EMAIL) to_address = user.email # send the root email message msg = EmailMessage(subject, None, from_address, [to_address]) msg.attach(notification_msg) msg.send()
def send_credit_notifications(username, course_key): """Sends email notification to user on different phases during credit course e.g., credit eligibility, credit payment etc. """ try: user = User.objects.get(username=username) except User.DoesNotExist: log.error('No user with %s exist', username) return course = modulestore().get_course(course_key, depth=0) course_display_name = course.display_name tracking_context = tracker.get_tracker().resolve_context() tracking_id = str(tracking_context.get('user_id')) client_id = str(tracking_context.get('client_id')) events = '&t=event&ec=email&ea=open' tracking_pixel = 'https://www.google-analytics.com/collect?v=1&tid' + tracking_id + '&cid' + client_id + events dashboard_link = _email_url_parser('dashboard') credit_course_link = _email_url_parser('courses', '?type=credit') # get attached branded logo logo_image = cache.get('credit.email.attached-logo') if logo_image is None: branded_logo = { 'title': 'Logo', 'path': settings.NOTIFICATION_EMAIL_EDX_LOGO, 'cid': str(uuid.uuid4()) } logo_image_id = branded_logo['cid'] logo_image = attach_image(branded_logo, 'Header Logo') if logo_image: cache.set('credit.email.attached-logo', logo_image, settings.CREDIT_NOTIFICATION_CACHE_TIMEOUT) else: # strip enclosing angle brackets from 'logo_image' cache 'Content-ID' logo_image_id = logo_image.get('Content-ID', '')[1:-1] providers_names = get_credit_provider_display_names(course_key) providers_string = make_providers_strings(providers_names) context = { 'full_name': user.get_full_name(), 'platform_name': settings.PLATFORM_NAME, 'course_name': course_display_name, 'branded_logo': logo_image_id, 'dashboard_link': dashboard_link, 'credit_course_link': credit_course_link, 'tracking_pixel': tracking_pixel, 'providers': providers_string, } # create the root email message notification_msg = MIMEMultipart('related') # add 'alternative' part to root email message to encapsulate the plain and # HTML versions, so message agents can decide which they want to display. msg_alternative = MIMEMultipart('alternative') notification_msg.attach(msg_alternative) # render the credit notification templates subject = _(u'Course Credit Eligibility') if providers_string: subject = _(u'You are eligible for credit from {providers_string}').format( providers_string=providers_string ) # add alternative plain text message email_body_plain = render_to_string('credit_notifications/credit_eligibility_email.txt', context) msg_alternative.attach(SafeMIMEText(email_body_plain, _subtype='plain', _charset='utf-8')) # add alternative html message email_body_content = cache.get('credit.email.css-email-body') if email_body_content is None: html_file_path = file_path_finder('templates/credit_notifications/credit_eligibility_email.html') if html_file_path: with open(html_file_path, 'r') as cur_file: cur_text = cur_file.read() # use html parser to unescape html characters which are changed # by the 'pynliner' while adding inline css to html content html_parser = HTMLParser.HTMLParser() email_body_content = html_parser.unescape(with_inline_css(cur_text)) # cache the email body content before rendering it since the # email context will change for each user e.g., 'full_name' cache.set('credit.email.css-email-body', email_body_content, settings.CREDIT_NOTIFICATION_CACHE_TIMEOUT) else: email_body_content = '' email_body = Template(email_body_content).render([context]) msg_alternative.attach(SafeMIMEText(email_body, _subtype='html', _charset='utf-8')) # attach logo image if logo_image: notification_msg.attach(logo_image) # add email addresses of sender and receiver from_address = theming_helpers.get_value('default_from_email', settings.DEFAULT_FROM_EMAIL) to_address = user.email # send the root email message msg = EmailMessage(subject, None, from_address, [to_address]) msg.attach(notification_msg) msg.send()
def send_credit_notifications(username, course_key): """Sends email notification to user on different phases during credit course e.g., credit eligibility, credit payment etc. """ try: user = User.objects.get(username=username) except User.DoesNotExist: log.error('No user with %s exist', username) return course = modulestore().get_course(course_key, depth=0) course_display_name = course.display_name tracking_context = tracker.get_tracker().resolve_context() tracking_id = str(tracking_context.get('user_id')) client_id = str(tracking_context.get('client_id')) events = '&t=event&ec=email&ea=open' tracking_pixel = 'https://www.google-analytics.com/collect?v=1&tid' + tracking_id + '&cid' + client_id + events dashboard_link = _email_url_parser('dashboard') credit_course_link = _email_url_parser('courses', '?type=credit') # get attached branded logo logo_image = cache.get('credit.email.attached-logo') if logo_image is None: branded_logo = { 'title': 'Logo', 'path': settings.NOTIFICATION_EMAIL_EDX_LOGO, 'cid': str(uuid.uuid4()) } logo_image_id = branded_logo['cid'] logo_image = attach_image(branded_logo, 'Header Logo') if logo_image: cache.set('credit.email.attached-logo', logo_image, settings.CREDIT_NOTIFICATION_CACHE_TIMEOUT) else: # strip enclosing angle brackets from 'logo_image' cache 'Content-ID' logo_image_id = logo_image.get('Content-ID', '')[1:-1] providers_names = get_credit_provider_display_names(course_key) providers_string = make_providers_strings(providers_names) context = { 'full_name': user.get_full_name(), 'platform_name': settings.PLATFORM_NAME, 'course_name': course_display_name, 'branded_logo': logo_image_id, 'dashboard_link': dashboard_link, 'credit_course_link': credit_course_link, 'tracking_pixel': tracking_pixel, 'providers': providers_string, } # create the root email message notification_msg = MIMEMultipart('related') # add 'alternative' part to root email message to encapsulate the plain and # HTML versions, so message agents can decide which they want to display. msg_alternative = MIMEMultipart('alternative') notification_msg.attach(msg_alternative) # render the credit notification templates subject = _(u'Course Credit Eligibility') if providers_string: subject = _(u'You are eligible for credit from {providers_string}' ).format(providers_string=providers_string) # add alternative plain text message email_body_plain = render_to_string( 'credit_notifications/credit_eligibility_email.txt', context) msg_alternative.attach( SafeMIMEText(email_body_plain, _subtype='plain', _charset='utf-8')) # add alternative html message email_body_content = cache.get('credit.email.css-email-body') if email_body_content is None: html_file_path = file_path_finder( 'templates/credit_notifications/credit_eligibility_email.html') if html_file_path: with open(html_file_path, 'r') as cur_file: cur_text = cur_file.read() # use html parser to unescape html characters which are changed # by the 'pynliner' while adding inline css to html content html_parser = HTMLParser.HTMLParser() email_body_content = html_parser.unescape( with_inline_css(cur_text)) # cache the email body content before rendering it since the # email context will change for each user e.g., 'full_name' cache.set('credit.email.css-email-body', email_body_content, settings.CREDIT_NOTIFICATION_CACHE_TIMEOUT) else: email_body_content = '' email_body = Template(email_body_content).render([context]) msg_alternative.attach( SafeMIMEText(email_body, _subtype='html', _charset='utf-8')) # attach logo image if logo_image: notification_msg.attach(logo_image) # add email addresses of sender and receiver from_address = theming_helpers.get_value('default_from_email', settings.DEFAULT_FROM_EMAIL) to_address = user.email # send the root email message msg = EmailMessage(subject, None, from_address, [to_address]) msg.attach(notification_msg) msg.send()
def send_mail_to_student(student, param_dict, language=None): """ Construct the email using templates and then send it. `student` is the student's email address (a `str`), `param_dict` is a `dict` with keys [ `site_name`: name given to edX instance (a `str`) `registration_url`: url for registration (a `str`) `display_name` : display name of a course (a `str`) `course_id`: id of course (a `str`) `auto_enroll`: user input option (a `str`) `course_url`: url of course (a `str`) `email_address`: email of student (a `str`) `full_name`: student full name (a `str`) `message`: type of email to send and template to use (a `str`) `is_shib_course`: (a `boolean`) ] `language` is the language used to render the email. If None the language of the currently-logged in user (that is, the user sending the email) will be used. Returns a boolean indicating whether the email was sent successfully. """ # add some helpers and microconfig subsitutions if 'display_name' in param_dict: param_dict['course_name'] = param_dict['display_name'] param_dict['site_name'] = microsite.get_value( 'SITE_NAME', param_dict['site_name'] ) subject = None message = None # see if we are running in a microsite and that there is an # activation email template definition available as configuration, if so, then render that message_type = param_dict['message'] email_template_dict = { 'allowed_enroll': ( 'emails/enroll_email_allowedsubject.txt', 'emails/enroll_email_allowedmessage.txt' ), 'enrolled_enroll': ( 'emails/enroll_email_enrolledsubject.txt', 'emails/enroll_email_enrolledmessage.txt' ), 'allowed_unenroll': ( 'emails/unenroll_email_subject.txt', 'emails/unenroll_email_allowedmessage.txt' ), 'enrolled_unenroll': ( 'emails/unenroll_email_subject.txt', 'emails/unenroll_email_enrolledmessage.txt' ), 'add_beta_tester': ( 'emails/add_beta_tester_email_subject.txt', 'emails/add_beta_tester_email_message.txt' ), 'remove_beta_tester': ( 'emails/remove_beta_tester_email_subject.txt', 'emails/remove_beta_tester_email_message.txt' ), 'account_creation_and_enrollment': ( 'emails/enroll_email_enrolledsubject.txt', 'emails/account_creation_and_enroll_emailMessage.txt' ), } subject_template, message_template = email_template_dict.get(message_type, (None, None)) if subject_template is not None and message_template is not None: subject, message = render_message_to_string( subject_template, message_template, param_dict, language=language ) if subject and message: # Remove leading and trailing whitespace from body message = message.strip() # Email subject *must not* contain newlines subject = ''.join(subject.splitlines()) from_address = theming_helpers.get_value( 'email_from_address', settings.DEFAULT_FROM_EMAIL ) send_mail(subject, message, from_address, [student], fail_silently=False)
def send_mail_to_student(student, param_dict, language=None): """ Construct the email using templates and then send it. `student` is the student's email address (a `str`), `param_dict` is a `dict` with keys [ `site_name`: name given to edX instance (a `str`) `registration_url`: url for registration (a `str`) `display_name` : display name of a course (a `str`) `course_id`: id of course (a `str`) `auto_enroll`: user input option (a `str`) `course_url`: url of course (a `str`) `email_address`: email of student (a `str`) `full_name`: student full name (a `str`) `message`: type of email to send and template to use (a `str`) `is_shib_course`: (a `boolean`) ] `language` is the language used to render the email. If None the language of the currently-logged in user (that is, the user sending the email) will be used. Returns a boolean indicating whether the email was sent successfully. """ # add some helpers and microconfig subsitutions if 'display_name' in param_dict: param_dict['course_name'] = param_dict['display_name'] param_dict['site_name'] = microsite.get_value('SITE_NAME', param_dict['site_name']) subject = None message = None # see if we are running in a microsite and that there is an # activation email template definition available as configuration, if so, then render that message_type = param_dict['message'] email_template_dict = { 'allowed_enroll': ('emails/enroll_email_allowedsubject.txt', 'emails/enroll_email_allowedmessage.txt'), 'enrolled_enroll': ('emails/enroll_email_enrolledsubject.txt', 'emails/enroll_email_enrolledmessage.txt'), 'allowed_unenroll': ('emails/unenroll_email_subject.txt', 'emails/unenroll_email_allowedmessage.txt'), 'enrolled_unenroll': ('emails/unenroll_email_subject.txt', 'emails/unenroll_email_enrolledmessage.txt'), 'add_beta_tester': ('emails/add_beta_tester_email_subject.txt', 'emails/add_beta_tester_email_message.txt'), 'remove_beta_tester': ('emails/remove_beta_tester_email_subject.txt', 'emails/remove_beta_tester_email_message.txt'), 'account_creation_and_enrollment': ('emails/enroll_email_enrolledsubject.txt', 'emails/account_creation_and_enroll_emailMessage.txt'), } subject_template, message_template = email_template_dict.get( message_type, (None, None)) if subject_template is not None and message_template is not None: subject, message = render_message_to_string(subject_template, message_template, param_dict, language=language) if subject and message: # Remove leading and trailing whitespace from body message = message.strip() # Email subject *must not* contain newlines subject = ''.join(subject.splitlines()) from_address = theming_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL) send_mail(subject, message, from_address, [student], fail_silently=False)