def send_ace_message(context): context['course_id'] = CourseKey.from_string(context['course_id']) if _should_send_message(context): context['site'] = Site.objects.get(id=context['site_id']) thread_author = User.objects.get(id=context['thread_author_id']) middleware_classes = [ CurrentRequestUserMiddleware, CurrentSiteThemeMiddleware, ] with emulate_http_request(site=context['site'], user=thread_author, middleware_classes=middleware_classes): message_context = _build_message_context(context) message = ResponseNotification().personalize( Recipient(thread_author.username, thread_author.email), _get_course_language(context['course_id']), message_context ) log.info('Sending forum comment email notification with context %s', message_context) ace.send(message)
def send_verification_confirmation_email(context): """Send an email confirming that the user submitted photos for initial verification.""" site = Site.objects.get_current() message_context = get_base_template_context(site) message_context.update(context) user = context['user'] try: with emulate_http_request(site=site, user=user): msg = VerificationSubmitted(context=message_context).personalize( recipient=Recipient(user.username, user.email), language=get_user_preference(user, LANGUAGE_KEY), user_context={'full_name': user.profile.name} ) ace.send(msg) log.info('Verification confirmation email sent to user: %r', user.username) return True except Exception: # pylint: disable=broad-except log.exception('Could not send email for verification confirmation to user %s', user.username) return False
def test_on_behalf_option_with_sailthru(self, message_options, expected_options): """ Tests sailthru send API is called with on_behalf option """ from_address = '*****@*****.**' message = Message( app_label=u'testapp', name=u'testmessage', options=message_options, recipient=Recipient(username=u'Robot', email_address=u'*****@*****.**'), ) rendered_email = render(self.channel, message) with patch( 'edx_ace.channel.sailthru.SailthruClient.send') as mock_send: deliver(self.channel, rendered_email, message) self.assertEqual(mock_send.call_args_list[0][1]['options'], expected_options)
def send(self): schedules = self.get_schedules() for (user, language, context, is_self_paced) in schedules: msg_type = CourseUpdate() if is_self_paced else InstructorLedCourseUpdate() msg = msg_type.personalize( Recipient( user.username, self.override_recipient_email or user.email, ), language, context, ) LOG.info( u'Sending email to user: {} for course-key: {}'.format( user.username, self.course_id ) ) with function_trace('enqueue_send_task'): self.async_send_task.apply_async((self.site.id, str(msg)), retry=False)
def send_proctoring_requirements_email(context): """Send an email with proctoring requirements for a course enrollment""" site = Site.objects.get_current() message_context = get_base_template_context(site) message_context.update(context) user = context['user'] try: msg = ProctoringRequirements(context=message_context).personalize( recipient=Recipient(user.username, user.email), language=settings.LANGUAGE_CODE, user_context={'full_name': user.profile.name}) ace.send(msg) log.info('Proctoring requirements email sent to user: %r', user.username) return True except Exception: # pylint: disable=broad-except log.exception( 'Could not send email for proctoring requirements to user %s', user.username) return False
def send_verification_expiry_email(batch_verifications, dry_run=False): """ Spins a task to send verification expiry email to the learners in the batch using edx_ace If the email is successfully sent change the expiry_email_date to reflect when the email was sent """ if dry_run: logger.info( u"This was a dry run, no email was sent. For the actual run email would have been sent " u"to {} learner(s)".format(len(batch_verifications))) return site = Site.objects.get_current() message_context = get_base_template_context(site) message_context.update({ 'platform_name': settings.PLATFORM_NAME, 'lms_verification_link': '{}{}'.format(settings.LMS_ROOT_URL, reverse("verify_student_reverify")), 'help_center_link': settings.ID_VERIFICATION_SUPPORT_LINK }) expiry_email = VerificationExpiry(context=message_context) users = User.objects.filter( pk__in=[verification.user_id for verification in batch_verifications]) for verification in batch_verifications: user = users.get(pk=verification.user_id) with emulate_http_request(site=site, user=user): msg = expiry_email.personalize( recipient=Recipient(user.username, user.email), language=get_user_preference(user, LANGUAGE_KEY), user_context={ 'full_name': user.profile.name, }) ace.send(msg) verification_qs = SoftwareSecurePhotoVerification.objects.filter( pk=verification.pk) verification_qs.update(expiry_email_date=datetime.now(UTC))
def send_verification_approved_email(context): """ Sends email to a learner when ID verification has been approved. """ site = Site.objects.get_current() message_context = get_base_template_context(site) message_context.update(context) user = context['user'] try: with emulate_http_request(site=site, user=user): msg = VerificationApproved(context=message_context).personalize( recipient=Recipient(user.id, user.email), language=get_user_preference(user, LANGUAGE_KEY), user_context={'full_name': user.profile.name} ) ace.send(msg) log.info('Verification approved email sent to user: %r', user.username) return True except Exception: # pylint: disable=broad-except log.exception('Could not send email for verification approved to user %s', user.username) return False
def recurring_nudge_schedule_hour( site_id, day, target_hour_str, org_list, exclude_orgs=False, override_recipient_email=None, ): target_hour = deserialize(target_hour_str) msg_type = RecurringNudge(day) for (user, language, context) in _recurring_nudge_schedules_for_hour( Site.objects.get(id=site_id), target_hour, org_list, exclude_orgs ): msg = msg_type.personalize( Recipient( user.username, override_recipient_email or user.email, ), language, context, ) _recurring_nudge_schedule_send.apply_async((site_id, str(msg)), retry=False)
def send_verification_expiry_email(self, batch_verifications, email_config): """ Sends verification expiry email to the learners in the batch using edx_ace If the email is successfully sent change the expiry_email_date to reflect when the email was sent :param batch_verifications: verification objects for which email will be sent :param email_config: Contains configuration like dry-run flag value, which determines whether actual email will be sent or not """ if email_config['dry_run']: logger.info( u"This was a dry run, no email was sent. For the actual run email would have been sent " u"to {} learner(s)".format(len(batch_verifications)) ) return site = Site.objects.get_current() message_context = get_base_template_context(site) message_context.update({ 'platform_name': settings.PLATFORM_NAME, 'lms_verification_link': '{}{}'.format(settings.LMS_ROOT_URL, reverse("verify_student_reverify")), 'help_center_link': settings.ID_VERIFICATION_SUPPORT_LINK }) expiry_email = VerificationExpiry(context=message_context) users = User.objects.filter(pk__in=[verification.user_id for verification in batch_verifications]) for verification in batch_verifications: user = users.get(pk=verification.user_id) with emulate_http_request(site=site, user=user): msg = expiry_email.personalize( recipient=Recipient(user.username, user.email), language=get_user_preference(user, LANGUAGE_KEY), user_context={ 'full_name': user.profile.name, } ) ace.send(msg) self._set_email_expiry_date(verification, user, email_config)
def save( self, # pylint: disable=arguments-differ use_https=False, token_generator=default_token_generator, request=None, **_kwargs): """ Generates a one-use only link for resetting password and sends to the user. """ for user in self.users_cache: site = get_current_site() message_context = get_base_template_context(site) message_context.update({ 'request': request, # Used by google_analytics_tracking_pixel # TODO: This overrides `platform_name` from `get_base_template_context` to make the tests passes 'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME), 'reset_link': '{protocol}://{site}{link}'.format( protocol='https' if use_https else 'http', site=configuration_helpers.get_value( 'SITE_NAME', settings.SITE_NAME), link=reverse('password_reset_confirm', kwargs={ 'uidb36': int_to_base36(user.id), 'token': token_generator.make_token(user), }), ) }) msg = PasswordReset().personalize( recipient=Recipient(user.username, user.email), language=get_user_preference(user, LANGUAGE_KEY), user_context=message_context, ) ace.send(msg)
def compose_activation_email(root_url, user, user_registration=None, route_enabled=False, profile_name=''): """ Construct all the required params for the activation email through celery task """ if user_registration is None: user_registration = Registration.objects.get(user=user) message_context = generate_activation_email_context( user, user_registration) message_context.update({ 'confirm_activation_link': '{root_url}/activate/{activation_key}'.format( root_url=root_url, activation_key=message_context['key']), 'route_enabled': route_enabled, 'routed_user': user.username, 'routed_user_email': user.email, 'routed_profile_name': profile_name, }) if route_enabled: dest_addr = settings.FEATURES['REROUTE_ACTIVATION_EMAIL'] else: dest_addr = user.email msg = AccountActivation().personalize( recipient=Recipient(user.username, dest_addr), language=preferences_api.get_user_preference(user, LANGUAGE_KEY), user_context=message_context, ) return msg
def send_password_reset_email(self, user, site): """ Send email to learner with reset password link :param user: :param site: """ message_context = get_base_template_context(site) email = user.email if should_redirect_to_authn_microfrontend(): site_url = settings.AUTHN_MICROFRONTEND_URL else: site_url = configuration_helpers.get_value('SITE_NAME', settings.SITE_NAME) message_context.update({ 'email': email, 'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME), 'reset_link': '{protocol}://{site_url}{link}?track=pwreset'.format( protocol='http', site_url=site_url, link=reverse('password_reset_confirm', kwargs={ 'uidb36': int_to_base36(user.id), 'token': default_token_generator.make_token(user), }), ) }) with emulate_http_request(site, user): msg = PasswordReset().personalize( recipient=Recipient(user.username, email), language=get_user_preference(user, LANGUAGE_KEY), user_context=message_context, ) ace.send(msg)
def send_account_recovery_email_for_user(user, request, email=None): """ Send out a account recovery email for the given user. Arguments: user (User): Django User object request (HttpRequest): Django request object email (str): Send email to this address. """ site = get_current_site() message_context = get_base_template_context(site) site_name = settings.LOGISTRATION_MICROFRONTEND_DOMAIN if should_redirect_to_logistration_mircrofrontend() \ else configuration_helpers.get_value('SITE_NAME', settings.SITE_NAME) message_context.update({ 'request': request, # Used by google_analytics_tracking_pixel 'email': email, 'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME), 'reset_link': '{protocol}://{site}{link}?is_account_recovery=true'.format( protocol='https' if request.is_secure() else 'http', site=site_name, link=reverse('password_reset_confirm', kwargs={ 'uidb36': int_to_base36(user.id), 'token': default_token_generator.make_token(user), }), ) }) msg = AccountRecoveryMessage().personalize( recipient=Recipient(user.username, email), language=get_user_preference(user, LANGUAGE_KEY), user_context=message_context, ) ace.send(msg)
def setUp(self): super().setUp() self.channel = DjangoEmailChannel() self.message = Message( app_label='testapp', name='testmessage', options={ 'from_address': '*****@*****.**', }, recipient=Recipient(lms_user_id=123, email_address='*****@*****.**'), ) self.mock_rendered_message = Mock( subject='\n Hello from \r\nRobot ! \n', body='Just trying to see what is like to talk to a human!', body_html=""" <p>Just trying to see what is like to talk to a human!</p> <hr /> """, )
def upgrade_reminder_schedule_bin( site_id, target_day_str, day_offset, bin_num, org_list, exclude_orgs=False, override_recipient_email=None, ): target_day = deserialize(target_day_str) msg_type = UpgradeReminder() for (user, language, context) in _upgrade_reminder_schedules_for_bin( Site.objects.get(id=site_id), target_day, bin_num, org_list, exclude_orgs ): msg = msg_type.personalize( Recipient( user.username, override_recipient_email or user.email, ), language, context, ) _upgrade_reminder_schedule_send.apply_async((site_id, str(msg)), retry=False)
def send_password_reset_email_for_user(user, request, preferred_email=None): """ Send out a password reset email for the given user. Arguments: user (User): Django User object request (HttpRequest): Django request object preferred_email (str): Send email to this address if present, otherwise fallback to user's email address. """ message_context, user_language_preference = get_user_default_email_params( user) site_name = settings.AUTHN_MICROFRONTEND_DOMAIN if should_redirect_to_authn_microfrontend() \ else configuration_helpers.get_value('SITE_NAME', settings.SITE_NAME) message_context.update({ 'request': request, # Used by google_analytics_tracking_pixel # TODO: This overrides `platform_name` from `get_base_template_context` to make the tests passes 'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME), 'reset_link': '{protocol}://{site}{link}?track=pwreset'.format( protocol='https' if request.is_secure() else 'http', site=site_name, link=reverse('password_reset_confirm', kwargs={ 'uidb36': int_to_base36(user.id), 'token': default_token_generator.make_token(user), }), ) }) msg = PasswordReset().personalize( recipient=Recipient(user.id, preferred_email or user.email), language=user_language_preference, user_context=message_context, ) ace.send(msg)
def send(self): schedules = self.get_schedules() LOG.info( u'Found {} emails to send for course-key: {}'.format( len(schedules), self.course_key ) ) for (user, language, context, is_self_paced) in schedules: msg_type = CourseUpdate() if is_self_paced else InstructorLedCourseUpdate() msg_type.personalize( Recipient( user.username, self.override_recipient_email or user.email, ), language, context, ) LOG.info( u'Sending email to user: {} for course-key: {}'.format( user.username, self.course_key ) )
def send_password_reset_success_email(user, request): """ Send an email to user indicating that password reset was successful. Arguments: user (User): Django User object request (HttpRequest): Django request object """ message_context, user_language_preference = get_user_default_email_params(user) lms_root_url = configuration_helpers.get_value('LMS_ROOT_URL', settings.LMS_ROOT_URL) message_context.update( {'login_link': '{}/login'.format(lms_root_url), 'request': request, } ) msg = PasswordResetSuccess(context=message_context).personalize( recipient=Recipient(user.username, user.email), language=user_language_preference, user_context={"name": user.profile.name}, ) try: ace.send(msg) except Exception: # pylint: disable=broad-except log.exception('PasswordResetSuccess: sending email to user [%s] failed.', user.username)
def test_send_discussion_email_notification(self, user_subscribed): if user_subscribed: non_matching_id = 'not-a-match' # with per_page left with a default value of 1, this ensures # that we test a multiple page result when calling # comment_client.User.subscribed_threads() subscribed_thread_ids = [non_matching_id, self.discussion_id] else: subscribed_thread_ids = [] self.mock_request.side_effect = make_mock_responder( subscribed_thread_ids=subscribed_thread_ids, comment_data=self.comment, thread_data=self.thread, ) user = mock.Mock() comment = cc.Comment.find(id=self.comment['id']).retrieve() site = Site.objects.get_current() with waffle().override(FORUM_RESPONSE_NOTIFICATIONS): with mock.patch( 'lms.djangoapps.discussion.signals.handlers.get_current_site', return_value=site): comment_created.send(sender=None, user=user, post=comment) if user_subscribed: expected_message_context = get_base_template_context(site) expected_message_context.update({ 'comment_author_id': self.comment_author.id, 'comment_body': self.comment['body'], 'comment_created_at': ONE_HOUR_AGO, 'comment_id': self.comment['id'], 'comment_username': self.comment_author.username, 'course_id': self.course.id, 'thread_author_id': self.thread_author.id, 'thread_created_at': TWO_HOURS_AGO, 'thread_id': self.discussion_id, 'thread_title': 'thread-title', 'thread_username': self.thread_author.username, 'thread_commentable_id': self.thread['commentable_id'], 'post_link': urljoin(site.domain, self.mock_permalink.return_value), 'site': site, 'site_id': site.id }) expected_message_context[ 'ga_tracking_pixel_url'] = _generate_ga_pixel_url( expected_message_context) expected_recipient = Recipient(self.thread_author.username, self.thread_author.email) actual_message = self.mock_ace_send.call_args_list[0][0][0] self.assertEqual(expected_message_context, actual_message.context) self.assertEqual(expected_recipient, actual_message.recipient) self.assertEqual(self.course.language, actual_message.language) else: self.assertFalse(self.mock_ace_send.called)
def confirm_email_change(request, key): # pylint: disable=unused-argument """ User requested a new e-mail. This is called when the activation link is clicked. We confirm with the old e-mail, and update """ if waffle().is_enabled(PREVENT_AUTH_USER_WRITES): return render_to_response('email_change_failed.html', {'err_msg': SYSTEM_MAINTENANCE_MSG}) with transaction.atomic(): try: pec = PendingEmailChange.objects.get(activation_key=key) except PendingEmailChange.DoesNotExist: response = render_to_response("invalid_email_key.html", {}) transaction.set_rollback(True) return response user = pec.user address_context = {'old_email': user.email, 'new_email': pec.new_email} if len(User.objects.filter(email=pec.new_email)) != 0: response = render_to_response("email_exists.html", {}) transaction.set_rollback(True) return response use_https = request.is_secure() if settings.FEATURES['ENABLE_MKTG_SITE']: contact_link = marketing_link('CONTACT') else: contact_link = '{protocol}://{site}{link}'.format( protocol='https' if use_https else 'http', site=configuration_helpers.get_value('SITE_NAME', settings.SITE_NAME), link=reverse('contact'), ) site = Site.objects.get_current() message_context = get_base_template_context(site) message_context.update({ 'old_email': user.email, 'new_email': pec.new_email, 'contact_link': contact_link, 'from_address': configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL), }) msg = EmailChangeConfirmation().personalize( recipient=Recipient(user.username, user.email), language=preferences_api.get_user_preference(user, LANGUAGE_KEY), user_context=message_context, ) u_prof = UserProfile.objects.get(user=user) meta = u_prof.get_meta() if 'old_emails' not in meta: meta['old_emails'] = [] meta['old_emails'].append( [user.email, datetime.datetime.now(UTC).isoformat()]) u_prof.set_meta(meta) u_prof.save() # Send it to the old email... try: ace.send(msg) except Exception: # pylint: disable=broad-except log.warning('Unable to send confirmation email to old address', exc_info=True) response = render_to_response("email_change_failed.html", {'email': user.email}) transaction.set_rollback(True) return response user.email = pec.new_email user.save() pec.delete() # And send it to the new email... msg.recipient = Recipient(user.username, pec.new_email) try: ace.send(msg) except Exception: # pylint: disable=broad-except log.warning('Unable to send confirmation email to new address', exc_info=True) response = render_to_response("email_change_failed.html", {'email': pec.new_email}) transaction.set_rollback(True) return response response = render_to_response("email_change_successful.html", address_context) return response
def do_email_change_request(user, new_email, activation_key=None, secondary_email_change_request=False): """ Given a new email for a user, does some basic verification of the new address and sends an activation message to the new address. If any issues are encountered with verification or sending the message, a ValueError will be thrown. """ # if activation_key is not passing as an argument, generate a random key if not activation_key: activation_key = uuid.uuid4().hex confirm_link = reverse('confirm_email_change', kwargs={ 'key': activation_key, }) if secondary_email_change_request: PendingSecondaryEmailChange.objects.update_or_create( user=user, defaults={ 'new_secondary_email': new_email, 'activation_key': activation_key, }) confirm_link = reverse('activate_secondary_email', kwargs={'key': activation_key}) else: PendingEmailChange.objects.update_or_create(user=user, defaults={ 'new_email': new_email, 'activation_key': activation_key, }) use_https = theming_helpers.get_current_request().is_secure() site = Site.objects.get_current() message_context = get_base_template_context(site) message_context.update({ 'old_email': user.email, 'new_email': new_email, 'confirm_link': '{protocol}://{site}{link}'.format( protocol='https' if use_https else 'http', site=configuration_helpers.get_value('SITE_NAME', settings.SITE_NAME), link=confirm_link, ), }) if secondary_email_change_request: msg = RecoveryEmailCreate().personalize( recipient=Recipient(user.username, new_email), language=preferences_api.get_user_preference(user, LANGUAGE_KEY), user_context=message_context, ) else: msg = EmailChange().personalize( recipient=Recipient(user.username, new_email), language=preferences_api.get_user_preference(user, LANGUAGE_KEY), user_context=message_context, ) try: ace.send(msg) except Exception: from_address = configuration_helpers.get_value( 'email_from_address', settings.DEFAULT_FROM_EMAIL) log.error(u'Unable to send email activation link to user from "%s"', from_address, exc_info=True) raise ValueError( _('Unable to send email activation link. Please try again later.')) if not secondary_email_change_request: # When the email address change is complete, a "edx.user.settings.changed" event will be emitted. # But because changing the email address is multi-step, we also emit an event here so that we can # track where the request was initiated. tracker.emit( SETTING_CHANGE_INITIATED, { "setting": "email", "old": message_context['old_email'], "new": message_context['new_email'], "user_id": user.id, })
def password_change_request_handler(request): """Handle password change requests originating from the account page. Uses the Account API to email the user a link to the password reset page. Note: The next step in the password reset process (confirmation) is currently handled by student.views.password_reset_confirm_wrapper, a custom wrapper around Django's password reset confirmation view. Args: request (HttpRequest) Returns: HttpResponse: 200 if the email was sent successfully HttpResponse: 400 if there is no 'email' POST parameter HttpResponse: 403 if the client has been rate limited HttpResponse: 405 if using an unsupported HTTP method Example usage: POST /account/password """ limiter = BadRequestRateLimiter() if limiter.is_rate_limit_exceeded(request): AUDIT_LOG.warning("Password reset rate limit exceeded") return HttpResponseForbidden() user = request.user # Prefer logged-in user's email email = user.email if user.is_authenticated else request.POST.get('email') if email: try: request_password_change(email, request.is_secure()) user = user if user.is_authenticated else User.objects.get( email=email) destroy_oauth_tokens(user) except UserNotFound: AUDIT_LOG.info("Invalid password reset attempt") # Increment the rate limit counter limiter.tick_bad_request_counter(request) # If enabled, send an email saying that a password reset was attempted, but that there is # no user associated with the email if configuration_helpers.get_value( 'ENABLE_PASSWORD_RESET_FAILURE_EMAIL', settings.FEATURES['ENABLE_PASSWORD_RESET_FAILURE_EMAIL']): site = Site.objects.get_current() message_context = get_base_template_context(site) message_context.update({ 'failed': True, 'request': request, # Used by google_analytics_tracking_pixel 'email_address': email, }) msg = PasswordReset().personalize( recipient=Recipient(username='', email_address=email), language=settings.LANGUAGE_CODE, user_context=message_context, ) ace.send(msg) except UserAPIInternalError as err: log.exception( 'Error occured during password change for user {email}: {error}' .format(email=email, error=err)) return HttpResponse(_( "Some error occured during password change. Please try again"), status=500) return HttpResponse(status=200) else: return HttpResponseBadRequest(_("No email address provided."))
def post(self, request): """ POST /api/user/v1/accounts/deactivate_logout/ Marks the user as having no password set for deactivation purposes, and logs the user out. """ user_model = get_user_model() try: # Get the username from the request and check that it exists verify_user_password_response = self._verify_user_password(request) if verify_user_password_response.status_code != status.HTTP_204_NO_CONTENT: return verify_user_password_response with transaction.atomic(): UserRetirementStatus.create_retirement(request.user) # Unlink LMS social auth accounts UserSocialAuth.objects.filter(user_id=request.user.id).delete() # Change LMS password & email user_email = request.user.email request.user.email = get_retired_email_by_email( request.user.email) request.user.save() _set_unusable_password(request.user) # TODO: Unlink social accounts & change password on each IDA. # Remove the activation keys sent by email to the user for account activation. Registration.objects.filter(user=request.user).delete() # Add user to retirement queue. # Delete OAuth tokens associated with the user. retire_dop_oauth2_models(request.user) retire_dot_oauth2_models(request.user) try: # Send notification email to user site = Site.objects.get_current() notification_context = get_base_template_context(site) notification_context.update( {'full_name': request.user.profile.name}) notification = DeletionNotificationMessage().personalize( recipient=Recipient(username='', email_address=user_email), language=request.user.profile.language, user_context=notification_context, ) ace.send(notification) except Exception as exc: log.exception( 'Error sending out deletion notification email') raise # Log the user out. logout(request) return Response(status=status.HTTP_204_NO_CONTENT) except KeyError: return Response(u'Username not specified.', status=status.HTTP_404_NOT_FOUND) except user_model.DoesNotExist: return Response(u'The user "{}" does not exist.'.format( request.user.username), status=status.HTTP_404_NOT_FOUND) except Exception as exc: # pylint: disable=broad-except return Response(text_type(exc), status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def test_send_discussion_email_notification(self, user_subscribed): if user_subscribed: non_matching_id = 'not-a-match' # with per_page left with a default value of 1, this ensures # that we test a multiple page result when calling # comment_client.User.subscribed_threads() subscribed_thread_ids = [non_matching_id, self.discussion_id] else: subscribed_thread_ids = [] self.mock_request.side_effect = make_mock_responder( subscribed_thread_ids=subscribed_thread_ids, comment_data=self.comment, thread_data=self.thread, ) user = mock.Mock() comment = cc.Comment.find(id=self.comment['id']).retrieve() site = Site.objects.get_current() site_config = SiteConfigurationFactory.create(site=site) site_config.site_values[ENABLE_FORUM_NOTIFICATIONS_FOR_SITE_KEY] = True site_config.save() with mock.patch( 'lms.djangoapps.discussion.signals.handlers.get_current_site', return_value=site): comment_created.send(sender=None, user=user, post=comment) if user_subscribed: expected_message_context = get_base_template_context(site) expected_message_context.update({ 'comment_author_id': self.comment_author.id, 'comment_body': self.comment['body'], 'comment_created_at': ONE_HOUR_AGO, 'comment_id': self.comment['id'], 'comment_username': self.comment_author.username, 'course_id': self.course.id, 'thread_author_id': self.thread_author.id, 'thread_created_at': TWO_HOURS_AGO, 'thread_id': self.discussion_id, 'thread_title': 'thread-title', 'thread_username': self.thread_author.username, 'thread_commentable_id': self.thread['commentable_id'], 'post_link': 'https://{}{}'.format(site.domain, self.mock_permalink.return_value), 'site': site, 'site_id': site.id }) expected_recipient = Recipient(self.thread_author.username, self.thread_author.email) actual_message = self.mock_ace_send.call_args_list[0][0][0] assert expected_message_context == actual_message.context assert expected_recipient == actual_message.recipient assert self.course.language == actual_message.language self._assert_rendered_email(actual_message) else: assert not self.mock_ace_send.called
def send_ace_message(goal): """ Send an email reminding users to stay on track for their learning goal in this course Arguments: goal (CourseGoal): Goal object """ user = goal.user try: course = CourseOverview.get_from_id(goal.course_key) except CourseOverview.DoesNotExist: log.error("Goal Reminder course not found.") course_name = course.display_name site = Site.objects.get_current() message_context = get_base_template_context(site) course_home_url = get_learning_mfe_home_url(course_key=goal.course_key, url_fragment='home') goals_unsubscribe_url = f'{settings.LEARNING_MICROFRONTEND_URL}/goal-unsubscribe/{goal.unsubscribe_token}' language = get_user_preference(user, LANGUAGE_KEY) # Code to allow displaying different banner images for different languages # However, we'll likely want to develop a better way to do this within edx-ace image_url = settings.STATIC_URL if image_url: # If the image url is a relative url prepend the LMS ROOT if 'http' not in image_url: image_url = settings.LMS_ROOT_URL + settings.STATIC_URL image_url += 'images/' if language and language in ['es', 'es-419']: image_url += 'spanish-' message_context.update({ 'email': user.email, 'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME), 'course_name': course_name, 'days_per_week': goal.days_per_week, 'course_url': course_home_url, 'goals_unsubscribe_url': goals_unsubscribe_url, 'image_url': image_url, 'unsubscribe_url': None, # We don't want to include the default unsubscribe link 'omit_unsubscribe_link': True, 'courses_url': getattr(settings, 'ACE_EMAIL_COURSES_URL', None), 'programs_url': getattr(settings, 'ACE_EMAIL_PROGRAMS_URL', None), }) msg = Message( name="goalreminder", app_label="course_goals", recipient=Recipient(user.id, user.email), language=language, context=message_context, options={'transactional': True}, ) with emulate_http_request(site, user): ace.send(msg)
def post(self, request, **kwargs): #from student.forms import send_password_reset_email_for_user from openedx.core.djangoapps.ace_common.template_context import get_base_template_context from openedx.core.djangoapps.theming.helpers import get_current_site from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers from student.message_types import PasswordReset from django.contrib.auth.tokens import default_token_generator from django.utils.http import int_to_base36 from edx_ace.recipient import Recipient from openedx.core.djangoapps.user_api.preferences.api import get_user_preference from openedx.core.djangoapps.lang_pref import LANGUAGE_KEY from edx_ace import ace self.data = request.POST.dict() if not ('uservalue' and 'sendotptype' in self.data): return JsonResponse({ "status": 400, "message": "Please enter Valid Mobile Number or Email Address or password", }) if self.data.get('sendotptype') == "mobile": mobile = self.data.get('uservalue') user = User.objects.get(extrafields__phone=mobile) email = user.email else: email = self.data.get('uservalue') if not email: return JsonResponse({ "status": 400, "message": "Email id can not be blank", }) user = User.objects.get(email=email) try: site = get_current_site() message_context = get_base_template_context(site) message_context.update({ 'request': request, # Used by google_analytics_tracking_pixel # TODO: This overrides `platform_name` from `get_base_template_context` to make the tests passes 'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME), 'reset_link': '{protocol}://{site}{link}'.format( protocol='https', site=configuration_helpers.get_value('SITE_NAME', settings.SITE_NAME), link=reverse('password_reset_confirm', kwargs={ 'uidb36': int_to_base36(user.id), 'token': default_token_generator.make_token(user), }), ) }) msg = PasswordReset().personalize( recipient=Recipient(user.username, user.email), language=get_user_preference(user, LANGUAGE_KEY), user_context=message_context, ) ace.send(msg) return JsonResponse({ "status": 200, "message": "We sent mail in you email", }) except Exception as e: return JsonResponse({ "status": 400, "message": "Something error in sending mail", "error": "err2", })
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`) `user_id`: LMS user ID of student (an `int`) - None if unknown `email_address`: email of student (a `str`) `full_name`: student full name (a `str`) `message_type`: 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'] elif 'course' in param_dict: param_dict['course_name'] = Text(param_dict['course'].display_name_with_default) param_dict['site_name'] = configuration_helpers.get_value( 'SITE_NAME', param_dict['site_name'] ) # Extract an LMS user ID for the student, if possible. # ACE needs the user ID to be able to send email via Braze. lms_user_id = 0 if 'user_id' in param_dict and param_dict['user_id'] is not None and param_dict['user_id'] > 0: lms_user_id = param_dict['user_id'] # see if there is an activation email template definition available as configuration, # if so, then render that message_type = param_dict['message_type'] ace_emails_dict = { 'account_creation_and_enrollment': AccountCreationAndEnrollment, 'add_beta_tester': AddBetaTester, 'allowed_enroll': AllowedEnroll, 'allowed_unenroll': AllowedUnenroll, 'enrolled_enroll': EnrollEnrolled, 'enrolled_unenroll': EnrolledUnenroll, 'remove_beta_tester': RemoveBetaTester, } message_class = ace_emails_dict[message_type] message = message_class().personalize( recipient=Recipient(lms_user_id=lms_user_id, email_address=student), language=language, user_context=param_dict, ) ace.send(message)
def test_send_discussion_email_notification(self, user_subscribed): with mock_the_things() as mocked_items: mock_request, mock_ace_send, mock_permalink = mocked_items if user_subscribed: non_matching_id = 'not-a-match' # with per_page left with a default value of 1, this ensures # that we test a multiple page result when calling # comment_client.User.subscribed_threads() mock_request.side_effect = make_mock_responder([non_matching_id, self.discussion_id]) else: mock_request.side_effect = make_mock_responder([]) now = datetime.utcnow() one_hour_ago = now - timedelta(hours=1) thread = mock.Mock( id=self.discussion_id, course_id=self.course.id, created_at=one_hour_ago, title='thread-title', user_id=self.thread_author.id, username=self.thread_author.username, commentable_id='thread-commentable-id' ) comment = mock.Mock( id='comment-id', body='comment-body', created_at=now, thread=thread, user_id=self.comment_author.id, username=self.comment_author.username ) user = mock.Mock() with waffle().override(FORUM_RESPONSE_NOTIFICATIONS): comment_created.send(sender=None, user=user, post=comment) if user_subscribed: expected_message_context = get_base_template_context(Site.objects.get_current()) expected_message_context.update({ 'comment_author_id': self.comment_author.id, 'comment_body': 'comment-body', 'comment_created_at': now, 'comment_id': 'comment-id', 'comment_username': self.comment_author.username, 'course_id': self.course.id, 'thread_author_id': self.thread_author.id, 'thread_created_at': one_hour_ago, 'thread_id': self.discussion_id, 'thread_title': 'thread-title', 'thread_username': self.thread_author.username, 'thread_commentable_id': 'thread-commentable-id', 'post_link': urljoin(Site.objects.get_current().domain, mock_permalink.return_value), 'site': Site.objects.get_current(), 'site_id': Site.objects.get_current().id, }) ga_tracking_pixel_url = _generate_ga_pixel_url(expected_message_context) expected_message_context.update({'ga_tracking_pixel_url': ga_tracking_pixel_url}) expected_recipient = Recipient(self.thread_author.username, self.thread_author.email) actual_message = mock_ace_send.call_args_list[0][0][0] self.assertEqual(expected_message_context, actual_message.context) self.assertEqual(expected_recipient, actual_message.recipient) self.assertEqual(self.course.language, actual_message.language) else: self.assertFalse(mock_ace_send.called)