def test_recipients_to_addresses_with_groups_with_members(self): """Testing generating addresses from recipients that are groups with no mailing list addresses """ group1 = Group.objects.create(name='group1') group2 = Group.objects.create(name='group2') user1 = User.objects.create_user(username='******', first_name='User', last_name='One', email='*****@*****.**') user2 = User.objects.create_user(username='******', first_name='User', last_name='Two', email='*****@*****.**') group1.users = [user1] group2.users = [user2] addresses = recipients_to_addresses([group1, group2]) expected_addresses = set([ build_email_address_for_user(user1), build_email_address_for_user(user2), ]) self.assertEqual(addresses, expected_addresses)
def get_email_addresses_for_group(group, review_request_id=None): """Build a list of e-mail addresses for the group. Args: group (reviewboard.reviews.models.Group): The review group to build the e-mail addresses for. Returns: list: A list of properly formatted e-mail addresses for all users in the review group. """ addresses = [] if group.mailing_list: if ',' not in group.mailing_list: # The mailing list field has only one e-mail address in it, # so we can just use that and the group's display name. addresses = [ build_email_address(full_name=group.display_name, email=group.mailing_list) ] else: # The mailing list field has multiple e-mail addresses in it. # We don't know which one should have the group's display name # attached to it, so just return their custom list as-is. addresses = group.mailing_list.split(',') if not (group.mailing_list and group.email_list_only): users_q = Q(is_active=True) local_site = group.local_site if local_site: users_q = users_q & (Q(local_site=local_site) | Q(local_site_admins=local_site)) users = group.users.filter(users_q).select_related('profile') if review_request_id: users = users.extra( select={ 'visibility': """ SELECT accounts_reviewrequestvisit.visibility FROM accounts_reviewrequestvisit WHERE accounts_reviewrequestvisit.review_request_id = %s AND accounts_reviewrequestvisit.user_id = reviews_group_users.user_id """ % review_request_id }) addresses.extend([ build_email_address_for_user(u) for u in users if (u.should_send_email() and ( not review_request_id or u.visibility != ReviewRequestVisit.MUTED)) ]) return addresses
def test_review_request_published_email_hook(self): """Testing ReviewRequestPublishedEmailHook""" class DummyHook(ReviewRequestPublishedEmailHook): def get_to_field(self, to_field, review_request, user): return set([user]) def get_cc_field(self, cc_field, review_request, user): return set([user]) hook = DummyHook(self.extension) self.spy_on(hook.get_to_field) self.spy_on(hook.get_cc_field) review_request = self.create_review_request() admin = User.objects.get(username='******') call_kwargs = { 'user': admin, 'review_request': review_request, } with self.siteconfig_settings({'mail_send_review_mail': True}): review_request.publish(admin) self.assertEqual(len(mail.outbox), 1) self.assertEqual(mail.outbox[0].to, [build_email_address_for_user(admin)]) self.assertTrue(hook.get_to_field.called) self.assertTrue(hook.get_to_field.called_with(**call_kwargs)) self.assertTrue(hook.get_cc_field.called) self.assertTrue(hook.get_cc_field.called_with(**call_kwargs))
def test_recipients_to_addresses_groups_local_site_inactive_members(self): """Testing generating addresses from recipients that are groups in local sites that have inactive members """ local_site1 = LocalSite.objects.create(name='local-site1') local_site2 = LocalSite.objects.create(name='local-site2') group1 = self.create_review_group('group1', local_site=local_site1) group2 = self.create_review_group('group2', local_site=local_site2) user1 = User.objects.create_user(username='******', first_name='User', last_name='One', email='*****@*****.**') user2 = User.objects.create(username='******', first_name='User', last_name='Two', is_active=False, email='*****@*****.**') local_site1.users = [user1] local_site2.users = [user2] group1.users = [user1] group2.users = [user2] addresses = recipients_to_addresses([group1, group2]) self.assertEqual(len(addresses), 1) self.assertEqual(addresses, set([build_email_address_for_user(user1)]))
def test_recipients_to_addresses_with_groups_and_users(self): """Testing generating addresses from recipients that are users and groups with mailing list addresses """ groups = [ Group(name='group1', display_name='Group One', mailing_list='*****@*****.**'), Group(name='group2', display_name='Group Two', mailing_list='*****@*****.**'), ] users = list(User.objects.filter(username__in=['doc', 'grumpy']).all()) addresses = recipients_to_addresses(groups + users) self.assertEqual(len(addresses), 4) user_addresses = [ build_email_address_for_user(u) for u in users ] group_addresses = sum( ( get_email_addresses_for_group(group) for group in groups ), []) self.assertEqual(addresses, set(user_addresses + group_addresses))
def test_build_email_address_for_user(self): """Testing build_email_address_for_user""" user = User.objects.create(username='******', first_name='Test', last_name='User', email='*****@*****.**') self.assertEqual(build_email_address_for_user(user), 'Test User <*****@*****.**>')
def get_email_addresses_for_group(group, review_request_id=None): """Build a list of e-mail addresses for the group. Args: group (reviewboard.reviews.models.Group): The review group to build the e-mail addresses for. Returns: list: A list of properly formatted e-mail addresses for all users in the review group. """ addresses = [] if group.mailing_list: if ',' not in group.mailing_list: # The mailing list field has only one e-mail address in it, # so we can just use that and the group's display name. addresses = [build_email_address(full_name=group.display_name, email=group.mailing_list)] else: # The mailing list field has multiple e-mail addresses in it. # We don't know which one should have the group's display name # attached to it, so just return their custom list as-is. addresses = group.mailing_list.split(',') if not (group.mailing_list and group.email_list_only): users_q = Q(is_active=True) local_site = group.local_site if local_site: users_q = users_q & (Q(local_site=local_site) | Q(local_site_admins=local_site)) users = group.users.filter(users_q).select_related('profile') if review_request_id: users = users.extra(select={ 'visibility': """ SELECT accounts_reviewrequestvisit.visibility FROM accounts_reviewrequestvisit WHERE accounts_reviewrequestvisit.review_request_id = %s AND accounts_reviewrequestvisit.user_id = reviews_group_users.user_id """ % review_request_id }) addresses.extend([ build_email_address_for_user(u) for u in users if (u.should_send_email() and (not review_request_id or u.visibility != ReviewRequestVisit.MUTED)) ]) return addresses
def test_recipients_to_addresses_with_users(self): """Testing generating addresses from recipients with user recipients """ users = list(User.objects.filter(username__in=['doc', 'grumpy'])) addresses = recipients_to_addresses(users) self.assertEqual(len(addresses), 2) expected_addresses = set( build_email_address_for_user(u) for u in users) self.assertEqual(addresses, expected_addresses)
def test_recipients_to_addresses_with_users(self): """Testing generating addresses from recipients with user recipients """ users = list(User.objects.filter(username__in=['doc', 'grumpy'])) addresses = recipients_to_addresses(users) self.assertEqual(len(addresses), 2) expected_addresses = set( build_email_address_for_user(u) for u in users ) self.assertEqual(addresses, expected_addresses)
def mail_new_user(user): """Send an e-mail to administrators for newly registered users. Args: user (django.contrib.auth.models.User): The user to send an e-mail about. """ current_site = Site.objects.get_current() siteconfig = SiteConfiguration.objects.get_current() domain_method = siteconfig.get("site_domain_method") subject = "New Review Board user registration for %s" % user.username from_email = build_email_address_for_user(user) context = { 'domain': current_site.domain, 'domain_method': domain_method, 'user': user, 'user_url': reverse('admin:auth_user_change', args=(user.id, )) } text_message = render_to_string('notifications/new_user_email.txt', context) html_message = render_to_string('notifications/new_user_email.html', context) message = EmailMessage(subject=subject.strip(), text_body=text_message, html_body=html_message, from_email=settings.SERVER_EMAIL, sender=settings.SERVER_EMAIL, to=[ build_email_address(full_name=admin[0], email=admin[1]) for admin in settings.ADMINS ]) try: message.send() except Exception as e: logging.error( "Error sending e-mail notification with subject '%s' on " "behalf of '%s' to admin: %s", subject.strip(), from_email, e, exc_info=1)
def mail_new_user(user): """Send an e-mail to administrators for newly registered users. Args: user (django.contrib.auth.models.User): The user to send an e-mail about. """ current_site = Site.objects.get_current() siteconfig = SiteConfiguration.objects.get_current() domain_method = siteconfig.get("site_domain_method") subject = "New Review Board user registration for %s" % user.username from_email = build_email_address_for_user(user) context = { 'domain': current_site.domain, 'domain_method': domain_method, 'user': user, 'user_url': reverse('admin:auth_user_change', args=(user.id,)) } text_message = render_to_string('notifications/new_user_email.txt', context) html_message = render_to_string('notifications/new_user_email.html', context) message = EmailMessage( subject=subject.strip(), text_body=text_message, html_body=html_message, from_email=settings.SERVER_EMAIL, sender=settings.SERVER_EMAIL, to=[ build_email_address(full_name=admin[0], email=admin[1]) for admin in settings.ADMINS ]) try: message.send() except Exception as e: logging.error("Error sending e-mail notification with subject '%s' on " "behalf of '%s' to admin: %s", subject.strip(), from_email, e, exc_info=1)
def test_recipients_to_addresses_with_groups_inactive_members(self): """Testing generating addresses form recipients that are groups with inactive members """ group1 = self.create_review_group('group1') group2 = self.create_review_group('group2') user1 = User.objects.create_user(username='******', first_name='User', last_name='One', email='*****@*****.**') user2 = User.objects.create(username='******', first_name='User', last_name='Two', is_active=False, email='*****@*****.**') group1.users = [user1] group2.users = [user2] addresses = recipients_to_addresses([group1, group2]) self.assertEqual(len(addresses), 1) self.assertEqual(addresses, set([build_email_address_for_user(user1)]))
def recipients_to_addresses(recipients, review_request_id=None): """Return the set of e-mail addresses for the recipients. Args: recipients (list): A list of :py:class:`Users <django.contrib.auth.models.User>` and :py:class:`Groups <reviewboard.reviews.models.Group>`. Returns: set: The e-mail addresses for all recipients. """ addresses = set() for recipient in recipients: assert isinstance(recipient, User) or isinstance(recipient, Group) if isinstance(recipient, User): addresses.add(build_email_address_for_user(recipient)) else: addresses.update(get_email_addresses_for_group(recipient, review_request_id)) return addresses
def recipients_to_addresses(recipients, review_request_id=None): """Return the set of e-mail addresses for the recipients. Args: recipients (list): A list of :py:class:`Users <django.contrib.auth.models.User>` and :py:class:`Groups <reviewboard.reviews.models.Group>`. Returns: set: The e-mail addresses for all recipients. """ addresses = set() for recipient in recipients: assert isinstance(recipient, User) or isinstance(recipient, Group) if isinstance(recipient, User): addresses.add(build_email_address_for_user(recipient)) else: addresses.update( get_email_addresses_for_group(recipient, review_request_id)) return addresses
def mail_password_changed(user): """Send an e-mail when a user's password changes. Args: user (django.contrib.auth.model.User): The user whose password changed. """ api_token_url = ('%s#api-tokens' % build_server_url(reverse('user-preferences'))) server_url = get_server_url() context = { 'api_token_url': api_token_url, 'has_api_tokens': user.webapi_tokens.exists(), 'server_url': server_url, 'user': user, } user_email = build_email_address_for_user(user) text_body = render_to_string('notifications/password_changed.txt', context) html_body = render_to_string('notifications/password_changed.html', context) message = EmailMessage( subject='Password changed for user "%s" on %s' % server_url, text_body=text_body, html_body=html_body, from_email=settings.SERVER_EMAIL, sender=settings.SERVER_EMAIL, to=user_email, ) try: message.send() except Exception as e: logging.exception('Failed to send password changed email to %s: %s', user.username, e)
def test_recipients_to_addresses_with_groups_and_users(self): """Testing generating addresses from recipients that are users and groups with mailing list addresses """ groups = [ Group(name='group1', display_name='Group One', mailing_list='*****@*****.**'), Group(name='group2', display_name='Group Two', mailing_list='*****@*****.**'), ] users = list(User.objects.filter(username__in=['doc', 'grumpy']).all()) addresses = recipients_to_addresses(groups + users) self.assertEqual(len(addresses), 4) user_addresses = [build_email_address_for_user(u) for u in users] group_addresses = sum( (get_email_addresses_for_group(group) for group in groups), []) self.assertEqual(addresses, set(user_addresses + group_addresses))
def mail_webapi_token(webapi_token, op): """Send an e-mail about an API token update. This will inform the user about a newly-created, updated, or deleted token. Args: webapi_token (reviewboard.webapi.models.WebAPIToken): The API token the e-mail is about. op (unicode): The operation the email is about. This is one of ``created``, ``updated``, or ``deleted``. Raises: ValueError: The provided ``op`` argument was invalid. """ if op == 'created': subject = 'New Review Board API token created' template_name = 'notifications/api_token_created' elif op == 'updated': subject = 'Review Board API token updated' template_name = 'notifications/api_token_updated' elif op == 'deleted': subject = 'Review Board API token deleted' template_name = 'notifications/api_token_deleted' else: raise ValueError('Unexpected op "%s" passed to mail_webapi_token.' % op) current_site = Site.objects.get_current() siteconfig = SiteConfiguration.objects.get_current() domain_method = siteconfig.get('site_domain_method') user = webapi_token.user user_email = build_email_address_for_user(user) context = { 'api_token': webapi_token, 'domain': current_site.domain, 'domain_method': domain_method, 'partial_token': '%s...' % webapi_token.token[:10], 'user': user, } text_message = render_to_string('%s.txt' % template_name, context) html_message = render_to_string('%s.html' % template_name, context) message = EmailMessage(subject=subject, text_body=text_message, html_body=html_message, from_email=settings.SERVER_EMAIL, sender=settings.SERVER_EMAIL, to=[user_email]) try: message.send() except Exception as e: logging.exception( "Error sending API Token e-mail with subject '%s' " "from '%s' to '%s': %s", subject, settings.SERVER_EMAIL, user_email, e)
def send_review_mail(user, review_request, subject, in_reply_to, to_field, cc_field, text_template_name, html_template_name, context=None, extra_headers=None): """Format and send an e-mail out. Args: user (django.contrib.auth.models.User): The user who is sending the e-mail. review_request (reviewboard.reviews.models.ReviewRequest): The review request that the e-mail is about. subject (unicode): The subject of the e-mail address. in_reply_to (unicode): The e-mail message ID for threading. to_field (list): The recipients to send the e-mail to. This should be a list of :py:class:`Users <django.contrib.auth.models.User>` and :py:class:`Groups <reviewboard.reviews.models.Group>`. cc_field (list): The addresses to be CC'ed on the e-mail. This should be a list of :py:class:`Users <django.contrib.auth.models.User>` and :py:class:`Groups <reviewboard.reviews.models.Group>`. text_template_name (unicode): The name for the text e-mail template. html_template_name (unicode): The name for the HTML e-mail template. context (dict): Optional extra context to provide to the template. extra_headers (dict): Either a dict or :py:class:`~django.utils.datastructures.MultiValueDict` providing additional headers to send with the e-mail. Returns: unicode: The resulting e-mail message ID. """ current_site = Site.objects.get_current() local_site = review_request.local_site from_email = build_email_address_for_user(user) to_field = recipients_to_addresses(to_field, review_request.id) cc_field = recipients_to_addresses(cc_field, review_request.id) - to_field if not user.should_send_own_updates(): user_email = build_email_address_for_user(user) to_field.discard(user_email) cc_field.discard(user_email) if not to_field and not cc_field: # Nothing to send. return siteconfig = current_site.config.get() domain_method = siteconfig.get("site_domain_method") if not context: context = {} context['user'] = user context['domain'] = current_site.domain context['domain_method'] = domain_method context['review_request'] = review_request if review_request.local_site: context['local_site_name'] = review_request.local_site.name text_body = render_to_string(text_template_name, context) html_body = render_to_string(html_template_name, context) base_url = get_server_url(local_site=local_site) headers = MultiValueDict({ 'X-ReviewBoard-URL': [base_url], 'X-ReviewRequest-URL': [urljoin(base_url, review_request.get_absolute_url())], 'X-ReviewGroup': [', '.join(group.name for group in review_request.target_groups.all())], }) if extra_headers: if not isinstance(extra_headers, MultiValueDict): extra_headers = MultiValueDict( (key, [value]) for (key, value) in six.iteritems(extra_headers) ) headers.update(extra_headers) if review_request.repository: headers['X-ReviewRequest-Repository'] = review_request.repository.name latest_diffset = review_request.get_latest_diffset() if latest_diffset: modified_files = set() for filediff in latest_diffset.files.all(): if filediff.deleted or filediff.copied or filediff.moved: modified_files.add(filediff.source_file) if filediff.is_new or filediff.copied or filediff.moved: modified_files.add(filediff.dest_file) for filename in modified_files: headers.appendlist('X-ReviewBoard-Diff-For', filename) subject = subject.strip() to_field = list(to_field) cc_field = list(cc_field) if settings.DEFAULT_FROM_EMAIL: sender = build_email_address(full_name=user.get_full_name(), email=settings.DEFAULT_FROM_EMAIL) else: sender = None message = EmailMessage(subject=subject, text_body=text_body.encode('utf-8'), html_body=html_body.encode('utf-8'), from_email=from_email, sender=sender, to=to_field, cc=cc_field, in_reply_to=in_reply_to, headers=headers) try: message.send() except Exception: logging.exception("Error sending e-mail notification with subject " "'%s' on behalf of '%s' to '%s'", subject, from_email, ','.join(to_field + cc_field)) return message.message_id
def send_review_mail(user, review_request, subject, in_reply_to, to_field, cc_field, text_template_name, html_template_name, context=None, extra_headers=None): """Format and send an e-mail out. Args: user (django.contrib.auth.models.User): The user who is sending the e-mail. review_request (reviewboard.reviews.models.ReviewRequest): The review request that the e-mail is about. subject (unicode): The subject of the e-mail address. in_reply_to (unicode): The e-mail message ID for threading. to_field (list): The recipients to send the e-mail to. This should be a list of :py:class:`Users <django.contrib.auth.models.User>` and :py:class:`Groups <reviewboard.reviews.models.Group>`. cc_field (list): The addresses to be CC'ed on the e-mail. This should be a list of :py:class:`Users <django.contrib.auth.models.User>` and :py:class:`Groups <reviewboard.reviews.models.Group>`. text_template_name (unicode): The name for the text e-mail template. html_template_name (unicode): The name for the HTML e-mail template. context (dict): Optional extra context to provide to the template. extra_headers (dict): Either a dict or :py:class:`~django.utils.datastructures.MultiValueDict` providing additional headers to send with the e-mail. Returns: unicode: The resulting e-mail message ID. """ current_site = Site.objects.get_current() local_site = review_request.local_site from_email = build_email_address_for_user(user) to_field = recipients_to_addresses(to_field, review_request.id) cc_field = recipients_to_addresses(cc_field, review_request.id) - to_field if not user.should_send_own_updates(): user_email = build_email_address_for_user(user) to_field.discard(user_email) cc_field.discard(user_email) if not to_field and not cc_field: # Nothing to send. return siteconfig = current_site.config.get() domain_method = siteconfig.get("site_domain_method") if not context: context = {} context['user'] = user context['domain'] = current_site.domain context['domain_method'] = domain_method context['review_request'] = review_request if review_request.local_site: context['local_site_name'] = review_request.local_site.name text_body = render_to_string(text_template_name, context) html_body = render_to_string(html_template_name, context) base_url = get_server_url(local_site=local_site) headers = MultiValueDict({ 'X-ReviewBoard-URL': [base_url], 'X-ReviewRequest-URL': [urljoin(base_url, review_request.get_absolute_url())], 'X-ReviewGroup': [ ', '.join(group.name for group in review_request.target_groups.all()) ], }) if extra_headers: if not isinstance(extra_headers, MultiValueDict): extra_headers = MultiValueDict( (key, [value]) for (key, value) in six.iteritems(extra_headers)) headers.update(extra_headers) if review_request.repository: headers['X-ReviewRequest-Repository'] = review_request.repository.name latest_diffset = review_request.get_latest_diffset() if latest_diffset: modified_files = set() for filediff in latest_diffset.files.all(): if filediff.deleted or filediff.copied or filediff.moved: modified_files.add(filediff.source_file) if filediff.is_new or filediff.copied or filediff.moved: modified_files.add(filediff.dest_file) for filename in modified_files: headers.appendlist('X-ReviewBoard-Diff-For', filename) subject = subject.strip() to_field = list(to_field) cc_field = list(cc_field) if settings.DEFAULT_FROM_EMAIL: sender = build_email_address(full_name=user.get_full_name(), email=settings.DEFAULT_FROM_EMAIL) else: sender = None message = EmailMessage(subject=subject, text_body=text_body.encode('utf-8'), html_body=html_body.encode('utf-8'), from_email=from_email, sender=sender, to=to_field, cc=cc_field, in_reply_to=in_reply_to, headers=headers) try: message.send() except Exception: logging.exception( "Error sending e-mail notification with subject " "'%s' on behalf of '%s' to '%s'", subject, from_email, ','.join(to_field + cc_field)) return message.message_id
def mail_webapi_token(webapi_token, op): """Send an e-mail about an API token update. This will inform the user about a newly-created, updated, or deleted token. Args: webapi_token (reviewboard.webapi.models.WebAPIToken): The API token the e-mail is about. op (unicode): The operation the email is about. This is one of ``created``, ``updated``, or ``deleted``. Raises: ValueError: The provided ``op`` argument was invalid. """ if op == 'created': subject = 'New Review Board API token created' template_name = 'notifications/api_token_created' elif op == 'updated': subject = 'Review Board API token updated' template_name = 'notifications/api_token_updated' elif op == 'deleted': subject = 'Review Board API token deleted' template_name = 'notifications/api_token_deleted' else: raise ValueError('Unexpected op "%s" passed to mail_webapi_token.' % op) current_site = Site.objects.get_current() siteconfig = SiteConfiguration.objects.get_current() domain_method = siteconfig.get('site_domain_method') user = webapi_token.user user_email = build_email_address_for_user(user) context = { 'api_token': webapi_token, 'domain': current_site.domain, 'domain_method': domain_method, 'partial_token': '%s...' % webapi_token.token[:10], 'user': user, } text_message = render_to_string('%s.txt' % template_name, context) html_message = render_to_string('%s.html' % template_name, context) message = EmailMessage( subject=subject, text_body=text_message, html_body=html_message, from_email=settings.SERVER_EMAIL, sender=settings.SERVER_EMAIL, to=[user_email]) try: message.send() except Exception as e: logging.exception("Error sending API Token e-mail with subject '%s' " "from '%s' to '%s': %s", subject, settings.SERVER_EMAIL, user_email, e)