def test_unsubscribe_url(self): text, html = render_digest(self.user, self.digest, "dummy", "dummy") expected_url = "{lms_url_base}/notification_prefs/unsubscribe/{token}/".format( lms_url_base=settings.LMS_URL_BASE, token=self.user["preferences"][DIGEST_NOTIFICATION_PREFERENCE_KEY] ) self.assertIn(expected_url, text) self.assertIn(expected_url, html)
def generate_and_send_digests(users, from_dt, to_dt): """ This task generates and sends forum digest emails to multiple users in a single background operation. `users` is an iterable of dictionaries, as returned by the edx user_api (required keys are "id", "name", "username", and "email"). `from_dt` and `to_dt` are datetime objects representing the start and end of the time window for which to generate a digest. """ users_by_id = dict((str(u['id']), u) for u in users) with closing(get_connection()) as cx: msgs = [] for user_id, digest in generate_digest_content(users_by_id.keys(), from_dt, to_dt): user = users_by_id[user_id] # format the digest text, html = render_digest( user, digest, settings.FORUM_DIGEST_EMAIL_TITLE, settings.FORUM_DIGEST_EMAIL_DESCRIPTION) # send the message through our mailer msg = EmailMultiAlternatives( settings.FORUM_DIGEST_EMAIL_SUBJECT, text, '@'.join( [settings.FORUM_DIGEST_EMAIL_SENDER, settings.EMAIL_DOMAIN]), [user['email']] ) msg.attach_alternative(html, "text/html") msgs.append(msg) if not msgs: return try: cx.send_messages(msgs) except SESMaxSendingRateExceededError as e: # we've tripped the per-second send rate limit. we generally # rely on the django_ses auto throttle to prevent this, # but in case we creep over, we can re-queue and re-try this task # - if and only if none of the messages in our batch were # sent yet. # this implementation is also non-ideal in that the data will be # fetched from the comments service again in the event of a retry. if not any((getattr(msg, 'extra_headers', {}).get('status') == 200 for msg in msgs)): raise generate_and_send_digests.retry(exc=e) else: # raise right away, since we don't support partial retry raise
def generate_and_send_digests(users, from_dt, to_dt): """ This task generates and sends forum digest emails to multiple users in a single background operation. `users` is an iterable of dictionaries, as returned by the edx user_api (required keys are "id", "name", "username", and "email"). `from_dt` and `to_dt` are datetime objects representing the start and end of the time window for which to generate a digest. """ users_by_id = dict((str(u['id']), u) for u in users) with closing(get_connection()) as cx: msgs = [] for user_id, digest in generate_digest_content(users_by_id.keys(), from_dt, to_dt): user = users_by_id[user_id] # format the digest text, html = render_digest(user, digest, settings.FORUM_DIGEST_EMAIL_TITLE, settings.FORUM_DIGEST_EMAIL_DESCRIPTION) # send the message through our mailer msg = EmailMultiAlternatives(settings.FORUM_DIGEST_EMAIL_SUBJECT, text, settings.FORUM_DIGEST_EMAIL_SENDER, [user['email']]) msg.attach_alternative(html, "text/html") msgs.append(msg) if not msgs: return try: cx.send_messages(msgs) except SESMaxSendingRateExceededError as e: # we've tripped the per-second send rate limit. we generally # rely on the django_ses auto throttle to prevent this, # but in case we creep over, we can re-queue and re-try this task # - if and only if none of the messages in our batch were # sent yet. # this implementation is also non-ideal in that the data will be # fetched from the comments service again in the event of a retry. if not any((getattr(msg, 'extra_headers', {}).get('status') == 200 for msg in msgs)): raise generate_and_send_digests.retry(exc=e) else: # raise right away, since we don't support partial retry raise
def generate_and_send_digests(users, from_dt, to_dt): """ This task generates and sends forum digest emails to multiple users in a single background operation. `users` is an iterable of dictionaries, as returned by the edx user_api (required keys are "id", "name", "email", "preferences", and "course_info"). `from_dt` and `to_dt` are datetime objects representing the start and end of the time window for which to generate a digest. """ logger.info("DIGEST TASK UPLOAD") users_by_id = dict((str(u['id']), u) for u in users) msgs = [] try: with closing(get_connection()) as cx: for user_id, digest in generate_digest_content( users_by_id, from_dt, to_dt): user = users_by_id[user_id] # format the digest text, html = render_digest( user, digest, settings.FORUM_DIGEST_EMAIL_TITLE, settings.FORUM_DIGEST_EMAIL_DESCRIPTION) # send the message through our mailer msg = EmailMultiAlternatives( settings.FORUM_DIGEST_EMAIL_SUBJECT, text, settings.FORUM_DIGEST_EMAIL_SENDER, [user['email']]) msg.attach_alternative(html, "text/html") msgs.append(msg) if msgs: cx.send_messages(msgs) if settings.DEAD_MANS_SNITCH_URL: requests.post(settings.DEAD_MANS_SNITCH_URL) except (CommentsServiceException, SESMaxSendingRateExceededError) as e: # only retry if no messages were successfully sent yet. if not any((getattr(msg, 'extra_headers', {}).get('status') == 200 for msg in msgs)): raise generate_and_send_digests.retry(exc=e) else: # raise right away, since we don't support partial retry raise
def _test_unicode_data(self, input_text, expected_text, expected_html=None): user = { "id": "0", "username": "******", } digest = Digest([ DigestCourse( TEST_COURSE_ID, [DigestThread( "0", TEST_COURSE_ID, TEST_COMMENTABLE, input_text, [DigestItem("test content", None, None)] )] ) ]) (rendered_text, rendered_html) = render_digest(user, digest, "Test Title", "Test Description") self.assertIn(expected_text, rendered_text) self.assertIn(expected_html if expected_html else expected_text, rendered_html)
def show_rendered(self, fmt, users, from_dt, to_dt): users_by_id = dict((str(u['id']), u) for u in users) def _fail(msg): logger.warning('could not show rendered %s: %s', fmt, msg) try: user_id, digest = generate_digest_content(users_by_id, from_dt, to_dt).next() except StopIteration: _fail('no digests found') return text, html = render_digest(users_by_id[user_id], digest, settings.FORUM_DIGEST_EMAIL_TITLE, settings.FORUM_DIGEST_EMAIL_DESCRIPTION) if fmt == 'text': print >> self.stdout, text elif fmt == 'html': print >> self.stdout, html
def _test_unicode_data(self, input_text, expected_text, expected_html=None): user = { "id": "0", "username": "******", } digest = Digest([ DigestCourse(TEST_COURSE_ID, [ DigestThread("0", TEST_COURSE_ID, TEST_COMMENTABLE, input_text, [DigestItem("test content", None, None)]) ]) ]) (rendered_text, rendered_html) = render_digest(user, digest, "Test Title", "Test Description") self.assertIn(expected_text, rendered_text) self.assertIn(expected_html if expected_html else expected_text, rendered_html)
def generate_and_send_digests(users, from_dt, to_dt, language=None): """ This task generates and sends forum digest emails to multiple users in a single background operation. `users` is an iterable of dictionaries, as returned by the edx user_api (required keys are "id", "name", "email", "preferences", and "course_info"). `from_dt` and `to_dt` are datetime objects representing the start and end of the time window for which to generate a digest. """ settings.LANGUAGE_CODE = language or settings.LANGUAGE_CODE or DEFAULT_LANGUAGE users_by_id = dict((str(u['id']), u) for u in users) msgs = [] try: with closing(get_connection()) as cx: for user_id, digest in generate_digest_content(users_by_id, from_dt, to_dt): user = users_by_id[user_id] # format the digest text, html = render_digest( user, digest, settings.FORUM_DIGEST_EMAIL_TITLE, settings.FORUM_DIGEST_EMAIL_DESCRIPTION) # send the message through our mailer msg = EmailMultiAlternatives( settings.FORUM_DIGEST_EMAIL_SUBJECT, text, settings.FORUM_DIGEST_EMAIL_SENDER, [user['email']] ) msg.attach_alternative(html, "text/html") msgs.append(msg) if msgs: cx.send_messages(msgs) if settings.DEAD_MANS_SNITCH_URL: requests.post(settings.DEAD_MANS_SNITCH_URL) except (CommentsServiceException, SESMaxSendingRateExceededError) as e: # only retry if no messages were successfully sent yet. if not any((getattr(msg, 'extra_headers', {}).get('status') == 200 for msg in msgs)): raise generate_and_send_digests.retry(exc=e) else: # raise right away, since we don't support partial retry raise
def show_rendered(self, fmt, users, from_dt, to_dt): users_by_id = dict((str(u['id']), u) for u in users) def _fail(msg): logger.warning('could not show rendered %s: %s', fmt, msg) try: user_id, digest = next(generate_digest_content(users_by_id, from_dt, to_dt)) except StopIteration: _fail('no digests found') return text, html = render_digest( users_by_id[user_id], digest, settings.FORUM_DIGEST_EMAIL_TITLE, settings.FORUM_DIGEST_EMAIL_DESCRIPTION ) if fmt == 'text': print >> self.stdout, text elif fmt == 'html': print >> self.stdout, html
def _test_unicode_data(self, input_text, expected_text, expected_html=None): self.set_digest(input_text) (rendered_text, rendered_html) = render_digest(self.user, self.digest, "Test Title", "Test Description") self.assertIn(expected_text, rendered_text) self.assertIn(expected_html if expected_html else expected_text, rendered_html)
def test_user_lang_pref_absent(self, mock_activate): if LANGUAGE_PREFERENCE_KEY in self.user["preferences"]: del self.user["preferences"][LANGUAGE_PREFERENCE_KEY] render_digest(self.user, self.digest, "dummy", "dummy") mock_activate.assert_not_called()
def test_user_lang_pref_unsupported(self, mock_activate): user_lang = "x-unsupported-lang" self.user["preferences"][LANGUAGE_PREFERENCE_KEY] = user_lang render_digest(self.user, self.digest, "dummy", "dummy") mock_activate.assert_not_called()
def test_user_lang_pref_supported(self, mock_activate, mock_deactivate): user_lang = "fr" self.user["preferences"][LANGUAGE_PREFERENCE_KEY] = user_lang render_digest(self.user, self.digest, "dummy", "dummy") mock_activate.assert_called_with(user_lang) mock_deactivate.assert_called()
class Command(BaseCommand): """ """ option_list = BaseCommand.option_list + ( make_option('--to_datetime', action='store', dest='to_datetime', default=None, help='datetime as of which to generate digest content, in ISO-8601 format (UTC). Defaults to today at midnight (UTC).'), make_option('--minutes', action='store', dest='minutes', type='int', default=1440, help='number of minutes up to TO_DATETIME for which to generate digest content. Defaults to 1440 (one day).'), make_option('--users', action='store', dest='users_str', default=None, help='send digests for the specified users only (regardless of opt-out settings!)'), make_option('--show-content', action='store_true', dest='show_content', default=None, help='output the retrieved content only (don\'t send anything)'), make_option('--show-users', action='store_true', dest='show_users', default=None, help='output the retrieved users only (don\'t fetch content or send anything)'), make_option('--show-text', action='store_true', dest='show_text', default=None, help='output the rendered text body of the first user-digest generated, and exit (don\'t send anything)'), make_option('--show-html', action='store_true', dest='show_html', default=None, help='output the rendered html body of the first user-digest generated, and exit (don\'t send anything)'), ) def get_specific_users(self, user_ids): # this makes an individual HTTP request for each user - # it is only intended for use with small numbers of users # (e.g. for diagnostic purposes). users = [] for user_id in user_ids: user = get_user(user_id) if user: users.append(user) return users def show_users(self, users): json.dump(list(users), self.stdout) def show_content(self, users, from_dt, to_dt): all_content = generate_digest_content( (u['id'] for u in users), from_dt, to_dt) # use django's encoder; builtin one doesn't handle datetime objects json.dump(list(all_content), self.stdout, cls=DigestJSONEncoder) def show_rendered(self, fmt, users, from_dt, to_dt): def _fail(msg): logger.warning('could not show rendered %s: %s', fmt, msg) try: user = list(users)[0] except IndexError, e: _fail('no users found') return try: user_id, digest = generate_digest_content( [user['id']], from_dt, to_dt).next() except StopIteration: _fail('no digests found') return text, html = render_digest( user, digest, settings.FORUM_DIGEST_EMAIL_TITLE, settings.FORUM_DIGEST_EMAIL_DESCRIPTION) if fmt == 'text': print >> self.stdout, text elif fmt == 'html': print >> self.stdout, html