def send(self, torcpts, ccrcpts, mime_headers={}): body = self._format_body() public_cc = self.config.getbool('notification', 'use_public_cc') headers = { 'X-Mailer': 'Trac %s, by Edgewall Software' % __version__, 'X-Trac-Version': __version__, 'X-Trac-Project': self.env.project_name, 'X-URL': self.env.project_url, 'Precedence': 'bulk', 'Auto-Submitted': 'auto-generated', 'Subject': self.subject, 'From': (self.from_name, self.from_email) if self.from_name else self.from_email, 'Reply-To': self.replyto_email } def build_addresses(rcpts): """Format and remove invalid addresses""" return filter(lambda x: x, [self.get_smtp_address(addr) for addr in rcpts]) def remove_dup(rcpts, all): """Remove duplicates""" tmp = [] for rcpt in rcpts: if not rcpt in all: tmp.append(rcpt) all.append(rcpt) return tmp, all notify_sys = NotificationSystem(self.env) toaddrs = build_addresses(torcpts) ccaddrs = build_addresses(ccrcpts) accaddrs = notify_sys.smtp_always_cc_list bccaddrs = notify_sys.smtp_always_bcc_list recipients = [] toaddrs, recipients = remove_dup(toaddrs, recipients) ccaddrs, recipients = remove_dup(ccaddrs, recipients) accaddrs, recipients = remove_dup(accaddrs, recipients) bccaddrs, recipients = remove_dup(bccaddrs, recipients) # if there is not valid recipient, leave immediately if len(recipients) < 1: self.env.log.info("no recipient for a ticket notification") return pcc = accaddrs if public_cc: pcc += ccaddrs if toaddrs: headers['To'] = ', '.join(toaddrs) if pcc: headers['Cc'] = ', '.join(pcc) headers['Date'] = formatdate() msg = create_mime_text(body, 'plain', self._charset) self.add_headers(msg, headers) self.add_headers(msg, mime_headers) NotificationSystem(self.env).send_email(self.from_email, recipients, msg.as_string())
def _do_send(self, transport, event, message, cc_addrs, bcc_addrs): notify_sys = NotificationSystem(self.env) smtp_from = notify_sys.smtp_from smtp_from_name = notify_sys.smtp_from_name or self.env.project_name smtp_replyto = notify_sys.smtp_replyto if not notify_sys.use_short_addr and notify_sys.smtp_default_domain: if smtp_from and '@' not in smtp_from: smtp_from = '%s@%s' % (smtp_from, notify_sys.smtp_default_domain) if smtp_replyto and '@' not in smtp_replyto: smtp_replyto = '%s@%s' % (smtp_replyto, notify_sys.smtp_default_domain) headers = {} headers['X-Mailer'] = 'Trac %s, by Edgewall Software'\ % self.env.trac_version headers['X-Trac-Version'] = self.env.trac_version headers['X-Trac-Project'] = self.env.project_name headers['X-URL'] = self.env.project_url headers['X-Trac-Realm'] = event.realm headers['Precedence'] = 'bulk' headers['Auto-Submitted'] = 'auto-generated' if isinstance(event.target, (list, tuple)): targetid = ','.join(map(get_target_id, event.target)) else: targetid = get_target_id(event.target) rootid = create_message_id(self.env, targetid, smtp_from, None, more=event.realm) if event.category == 'created': headers['Message-ID'] = rootid else: headers['Message-ID'] = create_message_id(self.env, targetid, smtp_from, event.time, more=event.realm) headers['In-Reply-To'] = rootid headers['References'] = rootid headers['Date'] = formatdate() headers['From'] = (smtp_from_name, smtp_from) \ if smtp_from_name else smtp_from headers['To'] = 'undisclosed-recipients: ;' if cc_addrs: headers['Cc'] = ', '.join(cc_addrs) if bcc_addrs: headers['Bcc'] = ', '.join(bcc_addrs) headers['Reply-To'] = smtp_replyto for k, v in headers.iteritems(): set_header(message, k, v, self._charset) for decorator in self.decorators: decorator.decorate_message(event, message, self._charset) from_name, from_addr = parseaddr(str(message['From'])) to_addrs = set() for name in ('To', 'Cc', 'Bcc'): values = map(str, message.get_all(name, ())) to_addrs.update(addr for name, addr in getaddresses(values) if addr) del message['Bcc'] notify_sys.send_email(from_addr, list(to_addrs), message.as_string())
def _do_delete(self, req, milestone): req.perm(milestone.resource).require('MILESTONE_DELETE') retarget_to = req.args.get('target') or None # Don't translate ticket comment (comment:40:ticket:5658) retargeted_tickets = \ milestone.move_tickets(retarget_to, req.authname, "Ticket retargeted after milestone deleted") milestone.delete() add_notice(req, _('The milestone "%(name)s" has been deleted.', name=milestone.name)) if retargeted_tickets: add_notice(req, _('The tickets associated with milestone ' '"%(name)s" have been retargeted to milestone ' '"%(retarget)s".', name=milestone.name, retarget=retarget_to)) new_values = {'milestone': retarget_to} comment = _("Tickets retargeted after milestone deleted") event = BatchTicketChangeEvent(retargeted_tickets, None, req.authname, comment, new_values, None) try: NotificationSystem(self.env).notify(event) except Exception as e: self.log.error("Failure sending notification on ticket batch " "change: %s", exception_to_unicode(e)) add_warning(req, tag_("The changes have been saved, but an " "error occurred while sending " "notifications: %(message)s", message=to_unicode(e))) req.redirect(req.href.roadmap())
def create_message_id(env, targetid, from_email, time, more=None): """Generate a predictable, but sufficiently unique message ID. In case you want to set the "Message ID" header, this convenience function will generate one by running a hash algorithm over a number of properties. :param env: the `Environment` :param targetid: a string that identifies the target, like `NotificationEvent.target` :param from_email: the email address that the message is sent from :param time: a Python `datetime` :param more: a string that contains additional information that makes this message unique """ items = [env.project_url, targetid, to_utimestamp(time)] if more is not None: items.append(more.encode('ascii', 'ignore')) source = b'.'.join( item if isinstance(item, bytes) else str(item).encode('utf-8') for item in items) hash_type = NotificationSystem(env).message_id_hash try: h = hashlib.new(hash_type) except: raise ConfigurationError( _("Unknown hash type '%(type)s'", type=hash_type)) h.update(source) host = from_email[from_email.find('@') + 1:] return '<%03d.%s@%s>' % (len(source), h.hexdigest(), host)
def setUp(self): self.env = EnvironmentStub( enable=['trac.*', TestEmailSender, TestFormatter, TestSubscriber]) config = self.env.config config.set('notification', 'smtp_from', '*****@*****.**') config.set('notification', 'smtp_enabled', 'enabled') config.set('notification', 'smtp_always_cc', '*****@*****.**') config.set('notification', 'smtp_always_bcc', '*****@*****.**') config.set('notification', 'email_sender', 'TestEmailSender') self.sender = TestEmailSender(self.env) self.notsys = NotificationSystem(self.env) with self.env.db_transaction: self._add_session('foo', email='*****@*****.**') self._add_session('bar', email='*****@*****.**', name=u"Bäŕ's name")
def apply_action_side_effects(self, req, ticket, action): """Add a cross-reference comment to the other ticket""" # TODO: This needs a lot more error checking. id = 'action_%s_xref' % action ticketnum = req.args.get(id).strip('#') actions = self.get_configurable_workflow().actions author = req.authname # Add a comment to the "remote" ticket to indicate this ticket is # related to it. format_string = actions[action].get( 'xref', "Ticket %s is related " "to this ticket") comment = format_string % ('#%s' % ticket.id) # FIXME: we need a cnum to avoid messing up xticket = model.Ticket(self.env, ticketnum) # FIXME: We _assume_ we have sufficient permissions to comment on the # other ticket. now = datetime.now(utc) xticket.save_changes(author, comment, now) # Send notification on the other ticket event = TicketChangeEvent('changed', xticket, now, author) try: NotificationSystem(self.env).notify(event) except Exception, e: self.log.exception( "Failure sending notification on change to " "ticket #%s: %s", ticketnum, e)
def decorate_message(self, event, message, charset): if event.realm != 'ticket': return from_email = self._get_from_email(event) if event.category == 'batchmodify': tickets = sort_tickets_by_priority(self.env, event.target) subject = self._format_subj_batchmodify(tickets) targetid = ','.join(map(str, tickets)) msgid = self._get_message_id(targetid, from_email, event.time) else: subject = self._format_subj(event) ticket = event.target targetid = '%08d' % ticket.id more = ticket['reporter'] or '' msgid = self._get_message_id(targetid, from_email, None, more) url = self.env.abs_href.ticket(ticket.id) if event.category != 'created': set_header(message, 'In-Reply-To', msgid, charset) set_header(message, 'References', msgid, charset) msgid = self._get_message_id(targetid, from_email, event.time, more) cnum = ticket.get_comment_number(event.time) if cnum is not None: url += '#comment:%d' % cnum set_header(message, 'X-Trac-Ticket-ID', ticket.id, charset) set_header(message, 'X-Trac-Ticket-URL', url, charset) # When owner, reporter and updater are listed in the Cc header, # move the address to To header. if NotificationSystem(self.env).use_public_cc: to_addrs = set() matcher = RecipientMatcher(self.env) for rcpt in ticket['owner'], ticket['reporter'], event.author: rcpt = matcher.match_recipient(rcpt) if not rcpt: continue addr = rcpt[2] if addr: to_addrs.add(addr) if to_addrs: cc_addrs = get_message_addresses(message, 'Cc') to_addrs &= set(addr for name, addr in cc_addrs) if to_addrs: cc_header = ', '.join(create_header('Cc', (name, addr), charset) for name, addr in cc_addrs if addr not in to_addrs) if cc_header: set_header(message, 'Cc', cc_header, charset) elif 'Cc' in message: del message['Cc'] to_header = ', '.join(sorted(to_addrs)) set_header(message, 'To', to_header, charset) set_header(message, 'Subject', subject, charset) set_header(message, 'Message-ID', msgid, charset)
def _do_send(self, transport, event, message, cc_addrs, bcc_addrs): config = self.config['notification'] smtp_from = config.get('smtp_from') smtp_from_name = config.get('smtp_from_name') or self.env.project_name smtp_reply_to = config.get('smtp_replyto') headers = dict() headers['X-Mailer'] = 'Trac %s, by Edgewall Software' % __version__ headers['X-Trac-Version'] = __version__ headers['X-Trac-Project'] = self.env.project_name headers['X-URL'] = self.env.project_url headers['X-Trac-Realm'] = event.realm headers['Precedence'] = 'bulk' headers['Auto-Submitted'] = 'auto-generated' targetid = get_target_id(event.target) rootid = create_message_id(self.env, targetid, smtp_from, None, more=event.realm) if event.category == 'created': headers['Message-ID'] = rootid else: headers['Message-ID'] = create_message_id(self.env, targetid, smtp_from, event.time, more=event.realm) headers['In-Reply-To'] = rootid headers['References'] = rootid headers['Date'] = formatdate() headers['From'] = (smtp_from_name, smtp_from) \ if smtp_from_name else smtp_from headers['To'] = 'undisclosed-recipients: ;' if cc_addrs: headers['Cc'] = ', '.join(cc_addrs) if bcc_addrs: headers['Bcc'] = ', '.join(bcc_addrs) headers['Reply-To'] = smtp_reply_to for k, v in headers.iteritems(): set_header(message, k, v, self._charset) for decorator in self.decorators: decorator.decorate_message(event, message, self._charset) from_name, from_addr = parseaddr(str(message['From'])) to_addrs = set() for name in ('To', 'Cc', 'Bcc'): values = map(str, message.get_all(name, ())) to_addrs.update(addr for name, addr in getaddresses(values) if addr) del message['Bcc'] NotificationSystem(self.env).send_email(from_addr, list(to_addrs), message.as_string())
def _notify(self, ticket, date, author, comment): """Send a ticket update notification.""" if not self.notify: return event = TicketChangeEvent('changed', ticket, date, author, comment) try: NotificationSystem(self.env).notify(event) except Exception as e: self.log.error( "Failure sending notification on change to " "ticket #%s: %s", ticket.id, exception_to_unicode(e))
def _save_ticket_changes(self, req, selected_tickets, new_values, comment, action): """Save changes to tickets.""" valid = True for manipulator in self.ticket_manipulators: if hasattr(manipulator, 'validate_comment'): for message in manipulator.validate_comment(req, comment): valid = False add_warning(req, tag_("The ticket %(field)s is invalid: " "%(message)s", field=tag.strong(_('comment')), message=message)) tickets = [] for id_ in selected_tickets: t = Ticket(self.env, id_) values = self._get_updated_ticket_values(req, t, new_values) for ctlr in self._get_action_controllers(req, t, action): values.update(ctlr.get_ticket_changes(req, t, action)) t.populate(values) for manipulator in self.ticket_manipulators: for field, message in manipulator.validate_ticket(req, t): valid = False if field: add_warning(req, tag_("The ticket field %(field)s is " "invalid: %(message)s", field=tag.strong(field), message=message)) else: add_warning(req, message) tickets.append(t) if not valid: return when = datetime_now(utc) with self.env.db_transaction: for t in tickets: t.save_changes(req.authname, comment, when=when) for ctlr in self._get_action_controllers(req, t, action): ctlr.apply_action_side_effects(req, t, action) event = BatchTicketChangeEvent(selected_tickets, when, req.authname, comment, new_values, action) try: NotificationSystem(self.env).notify(event) except Exception as e: self.log.error("Failure sending notification on ticket batch" "change: %s", exception_to_unicode(e)) add_warning(req, tag_("The changes have been saved, but an error " "occurred while sending notifications: " "%(message)s", message=to_unicode(e)))
def setUp(self): self.env = EnvironmentStub(enable=['trac.*', TestEmailSender, TestFormatter, TestSubscriber, TestEmailAddressResolver]) self.config = config = self.env.config config.set('notification', 'smtp_from', '*****@*****.**') config.set('notification', 'smtp_enabled', 'enabled') config.set('notification', 'smtp_always_cc', '*****@*****.**') config.set('notification', 'smtp_always_bcc', '*****@*****.**') config.set('notification', 'email_sender', 'TestEmailSender') config.set('notification', 'email_address_resolvers', 'SessionEmailResolver,TestEmailAddressResolver') self.sender = TestEmailSender(self.env) self.notsys = NotificationSystem(self.env) with self.env.db_transaction: self._add_session('foo', email='*****@*****.**') self._add_session('bar', email='*****@*****.**', name="Bäŕ's name") self._add_session('baz', name='Baz') self._add_session('qux', tz='UTC') self._add_session('corge', email='corge-mail')
def __init__(self, env): super(NotifyEmail, self).__init__(env) self.recipient_matcher = RecipientMatcher(env) self.shortaddr_re = self.recipient_matcher.shortaddr_re self.longaddr_re = self.recipient_matcher.longaddr_re self._ignore_domains = self.recipient_matcher.ignore_domains self.name_map = self.recipient_matcher.name_map self.email_map = self.recipient_matcher.email_map notify_sys = NotificationSystem(self.env) self._charset = create_charset(notify_sys.mime_encoding)
def send_notification(self, ticket, author): if TicketNotifyEmail: tn = TicketNotifyEmail(self.env) tn.notify(ticket, newticket=False, modtime=ticket['changetime']) else: event = TicketChangeEvent('changed', ticket, ticket['changetime'], author) try: NotificationSystem(self.env).notify(event) except Exception as e: self.log.error( "Failure sending notification on change to " "ticket #%s: %s", ticket.id, exception_to_unicode(e))
def __init__(self, env): self.env = env addrfmt = EMAIL_LOOKALIKE_PATTERN self.notify_sys = NotificationSystem(env) admit_domains = self.notify_sys.admit_domains_list if admit_domains: localfmt, domainfmt = addrfmt.split('@') domains = '|'.join(re.escape(x) for x in admit_domains) addrfmt = r'%s@(?:(?:%s)|%s)' % (localfmt, domainfmt, domains) self.shortaddr_re = re.compile(r'\s*(%s)\s*$' % addrfmt) self.longaddr_re = re.compile(r'^\s*(.*)\s+<\s*(%s)\s*>\s*$' % addrfmt) self.ignore_domains = set(x.lower() for x in self.notify_sys.ignore_domains_list) self.users = self.env.get_known_users(as_dict=True)
def _do_send(self, id, ticket, author, origin, description): ticket = Ticket(self.env, ticket) try: # We send reminder only for open tickets if ticket['status'] != 'closed': reminder = self._format_reminder_text(ticket, id, author, origin, description) #tn = TicketReminderNotifyEmail(self.env, reminder) #tn.notify(ticket) event = TicketReminderEvent(ticket, ticket['time'], author, reminder) tn = NotificationSystem(self.env).notify(event) except Exception, e: self.env.log.error("Failure sending reminder notification for ticket #%s: %s", ticket.id, exception_to_unicode(e)) print "Failure sending reminder notification for ticket #%s: %s" % (ticket.id, exception_to_unicode(e))
def _notify_attachment(self, attachment, category, time): resource = attachment.resource.parent if resource.realm != 'ticket': return ticket = Ticket(self.env, resource.id) event = TicketChangeEvent(category, ticket, time, ticket['reporter'], attachment=attachment) try: NotificationSystem(self.env).notify(event) except Exception as e: self.log.error("Failure sending notification when adding " "attachment %s to ticket #%s: %s", attachment.filename, ticket.id, exception_to_unicode(e))
def setUp(self): self.env = EnvironmentStub(enable=['trac.*', TestEmailSender, TestFormatter, TestSubscriber]) config = self.env.config config.set('notification', 'smtp_from', '*****@*****.**') config.set('notification', 'smtp_enabled', 'enabled') config.set('notification', 'smtp_always_cc', '*****@*****.**') config.set('notification', 'smtp_always_bcc', '*****@*****.**') config.set('notification', 'email_sender', 'TestEmailSender') self.sender = TestEmailSender(self.env) self.notsys = NotificationSystem(self.env) with self.env.db_transaction: self._add_session('foo', email='*****@*****.**') self._add_session('bar', email='*****@*****.**', name=u"Bäŕ's name")
def create_message_id(env, targetid, from_email, time, more=None): """Generate a predictable, but sufficiently unique message ID.""" items = [env.project_url.encode('utf-8'), targetid, to_utimestamp(time)] if more is not None: items.append(more.encode('ascii', 'ignore')) source = '.'.join(str(item) for item in items) hash_type = NotificationSystem(env).message_id_hash try: h = hashlib.new(hash_type) except: raise ConfigurationError( _("Unknown hash type '%(type)s'", type=hash_type)) h.update(source) host = from_email[from_email.find('@') + 1:] return '<%03d.%s@%s>' % (len(source), h.hexdigest(), host)
def __init__(self, env): self.env = env addrfmt = EMAIL_LOOKALIKE_PATTERN self.notify_sys = NotificationSystem(env) admit_domains = self.notify_sys.admit_domains_list if admit_domains: localfmt, domainfmt = addrfmt.split('@') domains = [domainfmt] domains.extend(re.escape(x) for x in admit_domains) addrfmt = r'%s@(?:%s)' % (localfmt, '|'.join(domains)) self.shortaddr_re = re.compile(r'<?(%s)>?$' % addrfmt, re.IGNORECASE) self.longaddr_re = re.compile(r'(.*)\s+<\s*(%s)\s*>$' % addrfmt, re.IGNORECASE) self.ignore_domains = set(x.lower() for x in self.notify_sys.ignore_domains_list)
def get_from_author(env, event): """Get the author name and email from a given `event`. The `event` parameter should be of the type `NotificationEvent`. If you only have the username of a Trac user, you should instead use the `RecipientMatcher` to find the user's details. The method returns a tuple that contains the name and email address of the user. For example: `('developer', '*****@*****.**')`. This tuple can be parsed by `set_header()`. """ if event.author and NotificationSystem(env).smtp_from_author: matcher = RecipientMatcher(env) from_ = matcher.match_from_author(event.author) if from_: return from_
def expand_macro(self, formatter, name, content): content = content.strip() if content else '' name_filter = content.strip('*') items = {} for subscriber in NotificationSystem(self.env).subscribers: name = subscriber.__class__.__name__ if not name_filter or name.startswith(name_filter): items[name] = subscriber.description() return tag.div(class_='trac-subscriberlist')(tag.table(class_='wiki')( tag.thead(tag.tr(tag.th(_("Subscriber")), tag.th(_("Description")))), tag.tbody( tag.tr(tag.td(tag.code(name)), tag.td(items[name]), class_='odd' if idx % 2 else 'even') for idx, name in enumerate(sorted(items)))))
def __init__(self, env): super(NotifyEmail, self).__init__(env) self.recipient_matcher = RecipientMatcher(env) self.shortaddr_re = self.recipient_matcher.shortaddr_re self.longaddr_re = self.recipient_matcher.longaddr_re self._ignore_domains = self.recipient_matcher.ignore_domains self.name_map = {} self.email_map = {} for username, name, email in self.env.get_known_users(): if name: self.name_map[username] = name if email: self.email_map[username] = email notify_sys = NotificationSystem(self.env) self._charset = create_charset(notify_sys.mime_encoding)
def _save_ticket_changes(self, req, selected_tickets, new_values, comment, action): """Save all of the changes to tickets.""" when = datetime.now(utc) list_fields = self._get_list_fields() with self.env.db_transaction as db: for id in selected_tickets: t = Ticket(self.env, int(id)) _values = new_values.copy() for field in list_fields: if field in new_values: old = t[field] if field in t else '' new = new_values[field] mode = req.args.get('batchmod_value_' + field + '_mode') new2 = req.args.get( 'batchmod_value_' + field + '_secondary', '') _values[field] = self._change_list( old, new, new2, mode) controllers = list(self._get_action_controllers( req, t, action)) for controller in controllers: _values.update( controller.get_ticket_changes(req, t, action)) t.populate(_values) t.save_changes(req.authname, comment, when=when) for controller in controllers: controller.apply_action_side_effects(req, t, action) event = BatchTicketChangeEvent(selected_tickets, when, req.authname, comment, new_values, action) try: NotificationSystem(self.env).notify(event) except Exception as e: self.log.error( "Failure sending notification on ticket batch" "change: %s", exception_to_unicode(e)) add_warning( req, tag_( "The changes have been saved, but an " "error occurred while sending " "notifications: %(message)s", message=to_unicode(e)))
def __init__(self, env): self.env = env addrfmt = EMAIL_LOOKALIKE_PATTERN notify_sys = NotificationSystem(env) admit_domains = notify_sys.admit_domains_list if admit_domains: localfmt, domainfmt = addrfmt.split('@') domains = '|'.join(re.escape(x) for x in admit_domains) addrfmt = r'%s@(?:(?:%s)|%s)' % (localfmt, domainfmt, domains) self.shortaddr_re = re.compile(r'\s*(%s)\s*$' % addrfmt) self.longaddr_re = re.compile(r'^\s*(.*)\s+<\s*(%s)\s*>\s*$' % addrfmt) self.ignore_domains = [x.lower() for x in notify_sys.ignore_domains_list] # Get the name and email addresses of all known users self.name_map = {} self.email_map = {} for username, name, email in self.env.get_known_users(): if name: self.name_map[username] = name if email: self.email_map[username] = email
def default_subscriptions(self): klass = self.__class__.__name__ return NotificationSystem(self.env).default_subscriptions(klass)
def distribute(self, transport, recipients, event): if transport != 'email': return if not self.config.getbool('notification', 'smtp_enabled'): self.log.debug("%s skipped because smtp_enabled set to false", self.__class__.__name__) return formats = {} for f in self.formatters: for style, realm in f.get_supported_styles(transport): if realm == event.realm: formats[style] = f if not formats: self.log.error("%s No formats found for %s %s", self.__class__.__name__, transport, event.realm) return self.log.debug( "%s has found the following formats capable of " "handling '%s' of '%s': %s", self.__class__.__name__, transport, event.realm, ', '.join(formats)) matcher = RecipientMatcher(self.env) notify_sys = NotificationSystem(self.env) always_cc = set(notify_sys.smtp_always_cc_list) addresses = {} for sid, auth, addr, fmt in recipients: if fmt not in formats: self.log.debug("%s format %s not available for %s %s", self.__class__.__name__, fmt, transport, event.realm) continue if sid and not addr: for resolver in self.resolvers: addr = resolver.get_address_for_session(sid, auth) or None if addr: self.log.debug( "%s found the address '%s' for '%s [%s]' via %s", self.__class__.__name__, addr, sid, auth, resolver.__class__.__name__) break if sid and auth and not addr: addr = sid if notify_sys.smtp_default_domain and \ not notify_sys.use_short_addr and \ addr and matcher.nodomaddr_re.match(addr): addr = '%s@%s' % (addr, notify_sys.smtp_default_domain) if not addr: self.log.debug( "%s was unable to find an address for " "'%s [%s]'", self.__class__.__name__, sid, auth) elif matcher.is_email(addr) or \ notify_sys.use_short_addr and \ matcher.nodomaddr_re.match(addr): addresses.setdefault(fmt, set()).add(addr) if sid and auth and sid in always_cc: always_cc.discard(sid) always_cc.add(addr) elif notify_sys.use_public_cc: always_cc.add(addr) else: self.log.debug( "%s was unable to use an address '%s' for '%s " "[%s]'", self.__class__.__name__, addr, sid, auth) outputs = {} failed = [] for fmt, formatter in formats.iteritems(): if fmt not in addresses and fmt != 'text/plain': continue try: outputs[fmt] = formatter.format(transport, fmt, event) except Exception as e: self.log.warning( '%s caught exception while ' 'formatting %s to %s for %s: %s%s', self.__class__.__name__, event.realm, fmt, transport, formatter.__class__, exception_to_unicode(e, traceback=True)) failed.append(fmt) # Fallback to text/plain when formatter is broken if failed and 'text/plain' in outputs: for fmt in failed: addresses.setdefault('text/plain', set()) \ .update(addresses.pop(fmt, ())) for fmt, addrs in addresses.iteritems(): self.log.debug("%s is sending event as '%s' to: %s", self.__class__.__name__, fmt, ', '.join(addrs)) message = self._create_message(fmt, outputs) if message: addrs = set(addrs) cc_addrs = sorted(addrs & always_cc) bcc_addrs = sorted(addrs - always_cc) self._do_send(transport, event, message, cc_addrs, bcc_addrs) else: self.log.warning("%s cannot send event '%s' as '%s': %s", self.__class__.__name__, event.realm, fmt, ', '.join(addrs))
def get_from_author(env, event): if event.author and NotificationSystem(env).smtp_from_author: matcher = RecipientMatcher(env) from_ = matcher.match_from_author(event.author) if from_: return from_
class EmailDistributorTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(enable=['trac.*', TestEmailSender, TestFormatter, TestSubscriber, TestEmailAddressResolver]) self.config = config = self.env.config config.set('notification', 'smtp_from', '*****@*****.**') config.set('notification', 'smtp_enabled', 'enabled') config.set('notification', 'smtp_always_cc', '*****@*****.**') config.set('notification', 'smtp_always_bcc', '*****@*****.**') config.set('notification', 'email_sender', 'TestEmailSender') config.set('notification', 'email_address_resolvers', 'SessionEmailResolver,TestEmailAddressResolver') self.sender = TestEmailSender(self.env) self.notsys = NotificationSystem(self.env) with self.env.db_transaction: self._add_session('foo', email='*****@*****.**') self._add_session('bar', email='*****@*****.**', name=u"Bäŕ's name") self._add_session('baz', name='Baz') self._add_session('qux', tz='UTC') self._add_session('corge', email='corge-mail') def tearDown(self): self.env.reset_db() def _notify_event(self, text, category='created', time=None, author=None): self.sender.history[:] = () event = TestNotificationEvent('test', category, TestModel(text), time or datetime_now(utc), author=author) self.notsys.notify(event) def _add_session(self, sid, values=None, **attrs): session = DetachedSession(self.env, sid) if values is not None: attrs.update(values) for name, value in attrs.iteritems(): session[name] = value session.save() def _add_subscription(self, **kwargs): subscription = {'sid': None, 'authenticated': 1, 'distributor': 'email', 'format': 'text/plain', 'adverb': 'always', 'class': 'TestSubscriber'} subscription.update(kwargs) Subscription.add(self.env, subscription) def test_smtp_disabled(self): self.env.config.set('notification', 'smtp_enabled', 'disabled') with self.env.db_transaction: self._add_subscription(sid='foo') self._add_subscription(sid='bar') self._notify_event('blah') self.assertEqual([], self.sender.history) def _assert_mail(self, message, content_type, body): self.assertNotIn('Bcc', message) self.assertEqual('multipart/related', message.get_content_type()) payload = list(message.get_payload()) self.assertEqual([content_type], [p.get_content_type() for p in payload]) self.assertEqual([body], [p.get_payload() for p in payload]) def _assert_alternative_mail(self, message, body_plain, body_html): self.assertNotIn('Bcc', message) self.assertEqual('multipart/related', message.get_content_type()) payload = list(message.get_payload()) self.assertEqual(['multipart/alternative'], [p.get_content_type() for p in payload]) alternative = list(payload[0].get_payload()) self.assertEqual(['text/plain', 'text/html'], [p.get_content_type() for p in alternative]) self.assertEqual([body_plain, body_html], [p.get_payload() for p in alternative]) def test_plain(self): with self.env.db_transaction: self._add_subscription(sid='foo') self._add_subscription(sid='bar') self._add_subscription(sid='baz') self._add_subscription(sid='qux') self._notify_event('blah') history = self.sender.history self.assertNotEqual([], history) self.assertEqual(1, len(history)) from_addr, recipients, message = history[0] self.assertEqual('*****@*****.**', from_addr) self.assertEqual({'*****@*****.**', '*****@*****.**', '*****@*****.**', '*****@*****.**', '*****@*****.**', '*****@*****.**'}, set(recipients)) self._assert_mail(message, 'text/plain', 'blah') def test_html(self): with self.env.db_transaction: self._add_subscription(sid='foo', format='text/html') self._notify_event('blah') history = self.sender.history self.assertNotEqual([], history) self.assertEqual(2, len(history)) for from_addr, recipients, message in history: if '*****@*****.**' in recipients: self.assertEqual('*****@*****.**', from_addr) self.assertEqual(['*****@*****.**'], recipients) self._assert_alternative_mail(message, 'blah', '<p>blah</p>') if '*****@*****.**' in recipients: self.assertEqual('*****@*****.**', from_addr) self.assertEqual({'*****@*****.**', '*****@*****.**'}, set(recipients)) self._assert_mail(message, 'text/plain', 'blah') def test_plain_and_html(self): with self.env.db_transaction: self._add_subscription(sid='foo', format='text/plain') self._add_subscription(sid='bar', format='text/html') self._notify_event('blah') history = self.sender.history self.assertNotEqual([], history) self.assertEqual(2, len(history)) for from_addr, recipients, message in history: if '*****@*****.**' in recipients: self.assertEqual( {'*****@*****.**', '*****@*****.**', '*****@*****.**'}, set(recipients)) self._assert_mail(message, 'text/plain', 'blah') if '*****@*****.**' in recipients: self.assertEqual(['*****@*****.**'], recipients) self._assert_alternative_mail(message, 'blah', '<p>blah</p>') def test_formats_in_session_and_tracini(self): self.config.set('notification', 'smtp_always_cc', 'bar,quux') self.config.set('notification', 'smtp_always_bcc', '') self.config.set('notification', 'default_format.email', 'text/html') with self.env.db_transaction: for user in ('foo', 'bar', 'baz', 'qux', 'quux'): self._add_session(user, email='*****@*****.**' % user) self._add_subscription(sid='foo', format='text/plain') # bar - no subscriptions self._add_session('bar', {'notification.format.email': 'text/plain'}) self._add_subscription(sid='baz', format='text/plain') self._add_session('baz', {'notification.format.email': 'text/html'}) self._add_subscription(sid='qux', format='text/html') self._add_session('qux', {'notification.format.email': 'text/plain'}) # quux - no subscriptions and no preferred format in session self._notify_event('blah') history = self.sender.history self.assertNotEqual([], history) self.assertEqual(2, len(history)) for from_addr, recipients, message in history: self.assertEqual('*****@*****.**', from_addr) recipients = sorted(recipients) if '*****@*****.**' in recipients: self.assertEqual(['*****@*****.**', '*****@*****.**', '*****@*****.**'], recipients) self._assert_mail(message, 'text/plain', 'blah') if '*****@*****.**' in recipients: self.assertEqual(['*****@*****.**', '*****@*****.**'], recipients) self._assert_alternative_mail(message, 'blah', '<p>blah</p>') def test_broken_plain_formatter(self): with self.env.db_transaction: self._add_subscription(sid='foo', format='text/plain') self._add_subscription(sid='bar', format='text/html') self._notify_event('raise-text-plain') history = self.sender.history self.assertNotEqual([], history) self.assertEqual(1, len(history)) from_addr, recipients, message = history[0] self.assertEqual('*****@*****.**', from_addr) self.assertEqual(['*****@*****.**'], recipients) self._assert_mail(message, 'text/html', '<p>raise-text-plain</p>') def test_broken_html_formatter(self): with self.env.db_transaction: self._add_subscription(sid='foo', format='text/html') self._add_subscription(sid='bar', format='text/plain') self._notify_event('raise-text-html') # fallback to text/plain history = self.sender.history self.assertNotEqual([], history) self.assertEqual(1, len(history)) from_addr, recipients, message = history[0] self.assertEqual('*****@*****.**', from_addr) self.assertEqual({'*****@*****.**', '*****@*****.**', '*****@*****.**', '*****@*****.**'}, set(recipients)) self._assert_mail(message, 'text/plain', 'raise-text-html') def test_broken_plain_and_html_formatter(self): with self.env.db_transaction: self._add_subscription(sid='foo', format='text/plain') self._add_subscription(sid='bar', format='text/html') self._notify_event('raise-text-plain raise-text-html') history = self.sender.history self.assertEqual([], history) def test_username_in_always_cc(self): self.env.config.set('notification', 'smtp_always_cc', 'foo, [email protected]') self.env.config.set('notification', 'smtp_always_bcc', 'bar, foo, [email protected]') self._notify_event('blah') history = self.sender.history self.assertNotEqual([], history) self.assertEqual(1, len(history)) from_addr, recipients, message = history[0] self.assertEqual('*****@*****.**', from_addr) self.assertEqual({'*****@*****.**', '*****@*****.**', '*****@*****.**', '*****@*****.**'}, set(recipients)) self.assertEqual('[email protected], [email protected]', message['Cc']) self.assertIsNone(message['Bcc']) self._assert_mail(message, 'text/plain', 'blah') def test_from_author_disabled(self): self.env.config.set('notification', 'smtp_from_author', 'disabled') with self.env.db_transaction: self._add_subscription(sid='bar') self._notify_event('blah', author='bar') history = self.sender.history self.assertNotEqual([], history) from_addr, recipients, message = history[0] self.assertEqual('*****@*****.**', from_addr) self.assertEqual('"My Project" <*****@*****.**>', message['From']) self.assertEqual(1, len(history)) self._notify_event('blah', author=None) history = self.sender.history self.assertNotEqual([], history) from_addr, recipients, message = history[0] self.assertEqual('*****@*****.**', from_addr) self.assertEqual('"My Project" <*****@*****.**>', message['From']) self.assertEqual(1, len(history)) self.env.config.set('notification', 'smtp_from_name', 'Trac') self._notify_event('blah', author=None) history = self.sender.history self.assertNotEqual([], history) from_addr, recipients, message = history[0] self.assertEqual('*****@*****.**', from_addr) self.assertEqual('"Trac" <*****@*****.**>', message['From']) self.assertEqual(1, len(history)) def test_from_author_enabled(self): self.env.config.set('notification', 'smtp_from_author', 'enabled') with self.env.db_transaction: self._add_subscription(sid='foo') self._add_subscription(sid='bar') self._notify_event('blah', author='bar') history = self.sender.history self.assertNotEqual([], history) from_addr, recipients, message = history[0] self.assertEqual('*****@*****.**', from_addr) self.assertEqual('"=?utf-8?b?QsOkxZUncyBuYW1l?=" <*****@*****.**>', message['From']) self.assertEqual(1, len(history)) self._notify_event('blah', author='foo') history = self.sender.history self.assertNotEqual([], history) from_addr, recipients, message = history[0] self.assertEqual('*****@*****.**', from_addr) self.assertEqual('*****@*****.**', message['From']) self.assertEqual(1, len(history)) self._notify_event('blah', author=None) history = self.sender.history self.assertNotEqual([], history) from_addr, recipients, message = history[0] self.assertEqual('*****@*****.**', from_addr) self.assertEqual('"My Project" <*****@*****.**>', message['From']) self.assertEqual(1, len(history)) self.env.config.set('notification', 'smtp_from_name', 'Trac') self._notify_event('blah', author=None) history = self.sender.history self.assertNotEqual([], history) from_addr, recipients, message = history[0] self.assertEqual('*****@*****.**', from_addr) self.assertEqual('"Trac" <*****@*****.**>', message['From']) self.assertEqual(1, len(history)) def test_ignore_domains(self): config = self.env.config config.set('notification', 'smtp_always_cc', '[email protected], [email protected]') config.set('notification', 'smtp_always_bcc', '[email protected], [email protected]') config.set('notification', 'ignore_domains', 'example.org, example.com') with self.env.db_transaction: self._add_subscription(sid='foo') self._add_subscription(sid='bar') self._add_subscription(sid='baz') self._add_subscription(sid='qux') self._notify_event('blah') history = self.sender.history self.assertNotEqual([], history) self.assertEqual(1, len(history)) from_addr, recipients, message = history[0] self.assertEqual('*****@*****.**', from_addr) self.assertEqual(set(('*****@*****.**', '*****@*****.**', '*****@*****.**', '*****@*****.**')), set(recipients)) def _test_without_domain(self, use_short_addr='disabled', smtp_default_domain=''): config = self.env.config config.set('notification', 'use_short_addr', use_short_addr) config.set('notification', 'smtp_default_domain', smtp_default_domain) config.set('notification', 'smtp_from', 'from-trac') config.set('notification', 'smtp_always_cc', 'qux, [email protected]') config.set('notification', 'smtp_always_bcc', '[email protected], bcc2') config.set('notification', 'email_address_resolvers', 'SessionEmailResolver') with self.env.db_transaction: self._add_subscription(sid='foo') self._add_subscription(sid='baz') self._add_subscription(sid='corge') self._notify_event('blah') history = self.sender.history self.assertNotEqual([], history) self.assertEqual(1, len(history)) return history def _assert_equal_sets(self, expected, actual): expected = set(expected) actual = set(actual) if expected != actual: self.fail('%r != %r' % ((expected - actual, actual - expected))) def _cclist(self, cc): return _fixup_cc_list(cc).split(', ') def test_use_short_addr(self): history = self._test_without_domain(use_short_addr='enabled') from_addr, recipients, message = history[0] self.assertEqual('from-trac', from_addr) self.assertEqual('"My Project" <from-trac>', message['From']) self._assert_equal_sets(['qux', '*****@*****.**', '*****@*****.**', 'bcc2', '*****@*****.**', 'baz', 'corge-mail'], recipients) self._assert_equal_sets(['qux', '*****@*****.**'], self._cclist(message['Cc'])) def test_smtp_default_domain(self): history = self._test_without_domain(smtp_default_domain='example.com') from_addr, recipients, message = history[0] self.assertEqual('*****@*****.**', from_addr) self.assertEqual('"My Project" <*****@*****.**>', message['From']) self._assert_equal_sets(['*****@*****.**', '*****@*****.**', '*****@*****.**', '*****@*****.**', '*****@*****.**', '*****@*****.**', '*****@*****.**'], recipients) self._assert_equal_sets(['*****@*****.**', '*****@*****.**'], self._cclist(message['Cc'])) def test_username_is_email(self): config = self.env.config config.set('notification', 'email_address_resolvers', 'SessionEmailResolver') with self.env.db_transaction: self._add_session(sid='*****@*****.**') self._add_session(sid='*****@*****.**', email='*****@*****.**') self._add_subscription(sid='*****@*****.**') self._add_subscription(sid='*****@*****.**') self._add_subscription(sid='*****@*****.**') # no session self._notify_event('blah') history = self.sender.history self.assertNotEqual([], history) self.assertEqual(1, len(history)) from_addr, recipients, message = history[0] self.assertEqual('*****@*****.**', from_addr) self.assertEqual('"My Project" <*****@*****.**>', message['From']) self.assertEqual({'*****@*****.**', '*****@*****.**', '*****@*****.**', '*****@*****.**', '*****@*****.**'}, set(recipients)) self._assert_equal_sets(['*****@*****.**'], self._cclist(message['Cc']))
class EmailDistributorTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(enable=['trac.*', TestEmailSender, TestFormatter, TestSubscriber]) config = self.env.config config.set('notification', 'smtp_from', '*****@*****.**') config.set('notification', 'smtp_enabled', 'enabled') config.set('notification', 'smtp_always_cc', '*****@*****.**') config.set('notification', 'smtp_always_bcc', '*****@*****.**') config.set('notification', 'email_sender', 'TestEmailSender') self.sender = TestEmailSender(self.env) self.notsys = NotificationSystem(self.env) with self.env.db_transaction: self._add_session('foo', email='*****@*****.**') self._add_session('bar', email='*****@*****.**', name=u"Bäŕ's name") def tearDown(self): self.env.reset_db() def _notify_event(self, text, category='created', time=None, author=None): self.sender.history[:] = () event = TestNotificationEvent('test', category, TestModel(text), time or datetime.now(utc), author=author) self.notsys.notify(event) def _add_session(self, sid, **attrs): session = DetachedSession(self.env, sid) for name, value in attrs.iteritems(): session[name] = value session.save() def _add_subscription(self, **kwargs): subscription = {'sid': None, 'authenticated': 1, 'distributor': 'email', 'format': 'text/plain', 'adverb': 'always', 'class': 'TestSubscriber'} subscription.update(kwargs) Subscription.add(self.env, subscription) def test_smtp_disabled(self): self.env.config.set('notification', 'smtp_enabled', 'disabled') with self.env.db_transaction: self._add_subscription(sid='foo') self._add_subscription(sid='bar') self._notify_event('blah') self.assertEqual([], self.sender.history) def _assert_mail(self, message, content_type, body): self.assertNotIn('Bcc', message) self.assertEqual('multipart/related', message.get_content_type()) payload = list(message.get_payload()) self.assertEqual([content_type], [p.get_content_type() for p in payload]) self.assertEqual([body], [p.get_payload() for p in payload]) def _assert_alternative_mail(self, message, body_plain, body_html): self.assertNotIn('Bcc', message) self.assertEqual('multipart/related', message.get_content_type()) payload = list(message.get_payload()) self.assertEqual(['multipart/alternative'], [p.get_content_type() for p in payload]) alternative = list(payload[0].get_payload()) self.assertEqual(['text/plain', 'text/html'], [p.get_content_type() for p in alternative]) self.assertEqual([body_plain, body_html], [p.get_payload() for p in alternative]) def test_plain(self): with self.env.db_transaction: self._add_subscription(sid='foo') self._add_subscription(sid='bar') self._notify_event('blah') history = self.sender.history self.assertNotEqual([], history) self.assertEqual(1, len(history)) from_addr, recipients, message = history[0] self.assertEqual('*****@*****.**', from_addr) self.assertEqual(set(('*****@*****.**', '*****@*****.**', '*****@*****.**', '*****@*****.**')), set(recipients)) self._assert_mail(message, 'text/plain', 'blah') def test_html(self): with self.env.db_transaction: self._add_subscription(sid='foo', format='text/html') self._notify_event('blah') history = self.sender.history self.assertNotEqual([], history) self.assertEqual(2, len(history)) for from_addr, recipients, message in history: if '*****@*****.**' in recipients: self.assertEqual('*****@*****.**', from_addr) self.assertEqual(['*****@*****.**'], recipients) self._assert_alternative_mail(message, 'blah', '<p>blah</p>') if '*****@*****.**' in recipients: self.assertEqual('*****@*****.**', from_addr) self.assertEqual(set(('*****@*****.**', '*****@*****.**')), set(recipients)) self._assert_mail(message, 'text/plain', 'blah') def test_plain_and_html(self): with self.env.db_transaction: self._add_subscription(sid='foo', format='text/plain') self._add_subscription(sid='bar', format='text/html') self._notify_event('blah') history = self.sender.history self.assertNotEqual([], history) self.assertEqual(2, len(history)) for from_addr, recipients, message in history: if '*****@*****.**' in recipients: self.assertEqual(set(('*****@*****.**', '*****@*****.**', '*****@*****.**')), set(recipients)) self._assert_mail(message, 'text/plain', 'blah') if '*****@*****.**' in recipients: self.assertEqual(['*****@*****.**'], recipients) self._assert_alternative_mail(message, 'blah', '<p>blah</p>') def test_broken_plain_formatter(self): with self.env.db_transaction: self._add_subscription(sid='foo', format='text/plain') self._add_subscription(sid='bar', format='text/html') self._notify_event('raise-text-plain') history = self.sender.history self.assertNotEqual([], history) self.assertEqual(1, len(history)) from_addr, recipients, message = history[0] self.assertEqual('*****@*****.**', from_addr) self.assertEqual(['*****@*****.**'], recipients) self._assert_mail(message, 'text/html', '<p>raise-text-plain</p>') def test_broken_html_formatter(self): with self.env.db_transaction: self._add_subscription(sid='foo', format='text/html') self._add_subscription(sid='bar', format='text/plain') self._notify_event('raise-text-html') # fallback to text/plain history = self.sender.history self.assertNotEqual([], history) self.assertEqual(1, len(history)) from_addr, recipients, message = history[0] self.assertEqual('*****@*****.**', from_addr) self.assertEqual(set(('*****@*****.**', '*****@*****.**', '*****@*****.**', '*****@*****.**')), set(recipients)) self._assert_mail(message, 'text/plain', 'raise-text-html') def test_broken_plain_and_html_formatter(self): with self.env.db_transaction: self._add_subscription(sid='foo', format='text/plain') self._add_subscription(sid='bar', format='text/html') self._notify_event('raise-text-plain raise-text-html') history = self.sender.history self.assertEqual([], history) def test_username_in_always_cc(self): self.env.config.set('notification', 'smtp_always_cc', 'foo, [email protected]') self.env.config.set('notification', 'smtp_always_bcc', 'bar, foo, [email protected]') self._notify_event('blah') history = self.sender.history self.assertNotEqual([], history) self.assertEqual(1, len(history)) from_addr, recipients, message = history[0] self.assertEqual('*****@*****.**', from_addr) self.assertEqual(set(('*****@*****.**', '*****@*****.**', '*****@*****.**', '*****@*****.**')), set(recipients)) self.assertEqual('[email protected], [email protected]', message['Cc']) self.assertEqual(None, message['Bcc']) self._assert_mail(message, 'text/plain', 'blah') def test_from_author_disabled(self): self.env.config.set('notification', 'smtp_from_author', 'disabled') with self.env.db_transaction: self._add_subscription(sid='bar') self._notify_event('blah', author='bar') history = self.sender.history self.assertNotEqual([], history) from_addr, recipients, message = history[0] self.assertEqual('*****@*****.**', from_addr) self.assertEqual('"My Project" <*****@*****.**>', message['From']) self.assertEqual(1, len(history)) self._notify_event('blah', author=None) history = self.sender.history self.assertNotEqual([], history) from_addr, recipients, message = history[0] self.assertEqual('*****@*****.**', from_addr) self.assertEqual('"My Project" <*****@*****.**>', message['From']) self.assertEqual(1, len(history)) self.env.config.set('notification', 'smtp_from_name', 'Trac') self._notify_event('blah', author=None) history = self.sender.history self.assertNotEqual([], history) from_addr, recipients, message = history[0] self.assertEqual('*****@*****.**', from_addr) self.assertEqual('"Trac" <*****@*****.**>', message['From']) self.assertEqual(1, len(history)) def test_from_author_enabled(self): self.env.config.set('notification', 'smtp_from_author', 'enabled') with self.env.db_transaction: self._add_subscription(sid='foo') self._add_subscription(sid='bar') self._notify_event('blah', author='bar') history = self.sender.history self.assertNotEqual([], history) from_addr, recipients, message = history[0] self.assertEqual('*****@*****.**', from_addr) self.assertEqual('"=?utf-8?b?QsOkxZUncyBuYW1l?=" <*****@*****.**>', message['From']) self.assertEqual(1, len(history)) self._notify_event('blah', author='foo') history = self.sender.history self.assertNotEqual([], history) from_addr, recipients, message = history[0] self.assertEqual('*****@*****.**', from_addr) self.assertEqual('*****@*****.**', message['From']) self.assertEqual(1, len(history)) self._notify_event('blah', author=None) history = self.sender.history self.assertNotEqual([], history) from_addr, recipients, message = history[0] self.assertEqual('*****@*****.**', from_addr) self.assertEqual('"My Project" <*****@*****.**>', message['From']) self.assertEqual(1, len(history)) self.env.config.set('notification', 'smtp_from_name', 'Trac') self._notify_event('blah', author=None) history = self.sender.history self.assertNotEqual([], history) from_addr, recipients, message = history[0] self.assertEqual('*****@*****.**', from_addr) self.assertEqual('"Trac" <*****@*****.**>', message['From']) self.assertEqual(1, len(history))
def render_preference_panel(self, req, panel, path_info=None): if req.method == 'POST': action_arg = req.args.getfirst('action', '').split('_', 1) if len(action_arg) == 2: action, arg = action_arg handler = self.post_handlers.get(action) if handler: handler(arg, req) add_notice(req, _("Your preferences have been saved.")) req.redirect(req.href.prefs('notification')) rules = {} subscribers = [] formatters = {} selected_format = {} default_format = {} defaults = [] for i in self.subscribers: description = i.description() if not description: continue if not req.session.authenticated and i.requires_authentication(): continue subscribers.append({ 'class': i.__class__.__name__, 'description': description }) if hasattr(i, 'default_subscriptions'): defaults.extend(i.default_subscriptions()) desc_map = dict((s['class'], s['description']) for s in subscribers) ns = NotificationSystem(self.env) for t in self._iter_transports(): rules[t] = [] formatters[t] = self._get_supported_styles(t) selected_format[t] = req.session.get('notification.format.%s' % t) default_format[t] = ns.get_default_format(t) for r in self._iter_rules(req, t): description = desc_map.get(r['class']) if description: values = {'description': description} values.update( (key, r[key]) for key in ('id', 'adverb', 'class', 'priority')) rules[t].append(values) default_rules = {} for r in sorted(defaults, key=itemgetter(3)): # sort by priority klass, dist, format, priority, adverb = r default_rules.setdefault(dist, []) description = desc_map.get(klass) if description: default_rules[dist].append({ 'adverb': adverb, 'description': description }) data = { 'rules': rules, 'subscribers': subscribers, 'formatters': formatters, 'selected_format': selected_format, 'default_format': default_format, 'default_rules': default_rules, 'adverbs': ('always', 'never'), 'adverb_labels': { 'always': _("Notify"), 'never': _("Never notify") } } Chrome(self.env).add_jquery_ui(req) return 'prefs_notification.html', dict(data=data)
def save_milestone(self, req, milestone): # Instead of raising one single error, check all the constraints # and let the user fix them by going back to edit mode and showing # the warnings warnings = [] def warn(msg): add_warning(req, msg) warnings.append(msg) milestone.description = req.args.get('description', '') if 'due' in req.args: duedate = req.args.get('duedate') milestone.due = user_time(req, parse_date, duedate, hint='datetime') \ if duedate else None else: milestone.due = None # -- check completed date if 'completed' in req.args: completed = req.args.get('completeddate', '') completed = user_time(req, parse_date, completed, hint='datetime') if completed else None if completed and completed > datetime_now(utc): warn(_("Completion date may not be in the future")) else: completed = None milestone.completed = completed # -- check the name # If the name has changed, check that the milestone doesn't already # exist # FIXME: the whole .exists business needs to be clarified # (#4130) and should behave like a WikiPage does in # this respect. new_name = req.args.get('name') try: new_milestone = Milestone(self.env, new_name) except ResourceNotFound: milestone.name = new_name else: if new_milestone.name != milestone.name: if new_milestone.name: warn( _( 'Milestone "%(name)s" already exists, please ' 'choose another name.', name=new_milestone.name)) else: warn(_("You must provide a name for the milestone.")) if warnings: return False # -- actually save changes if milestone.exists: milestone.update(author=req.authname) if completed and 'retarget' in req.args: comment = req.args.get('comment', '') retarget_to = req.args.get('target') or None retargeted_tickets = \ milestone.move_tickets(retarget_to, req.authname, comment, exclude_closed=True) add_notice( req, _( 'The open tickets associated with ' 'milestone "%(name)s" have been retargeted ' 'to milestone "%(retarget)s".', name=milestone.name, retarget=retarget_to)) new_values = {'milestone': retarget_to} comment = comment or \ _("Open tickets retargeted after milestone closed") event = BatchTicketChangeEvent(retargeted_tickets, None, req.authname, comment, new_values, None) try: NotificationSystem(self.env).notify(event) except Exception as e: self.log.error( "Failure sending notification on ticket " "batch change: %s", exception_to_unicode(e)) add_warning( req, tag_( "The changes have been saved, but " "an error occurred while sending " "notifications: %(message)s", message=to_unicode(e))) add_notice(req, _("Your changes have been saved.")) else: milestone.insert() add_notice( req, _('The milestone "%(name)s" has been added.', name=milestone.name)) return True
class EmailDistributorTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub( enable=['trac.*', TestEmailSender, TestFormatter, TestSubscriber]) config = self.env.config config.set('notification', 'smtp_from', '*****@*****.**') config.set('notification', 'smtp_enabled', 'enabled') config.set('notification', 'smtp_always_cc', '*****@*****.**') config.set('notification', 'smtp_always_bcc', '*****@*****.**') config.set('notification', 'email_sender', 'TestEmailSender') self.sender = TestEmailSender(self.env) self.notsys = NotificationSystem(self.env) with self.env.db_transaction: self._add_session('foo', email='*****@*****.**') self._add_session('bar', email='*****@*****.**', name=u"Bäŕ's name") def tearDown(self): self.env.reset_db() def _notify_event(self, text, category='created', time=None, author=None): self.sender.history[:] = () event = TestNotificationEvent('test', category, TestModel(text), time or datetime.now(utc), author=author) self.notsys.notify(event) def _add_session(self, sid, **attrs): session = DetachedSession(self.env, sid) for name, value in attrs.iteritems(): session[name] = value session.save() def _add_subscription(self, **kwargs): subscription = { 'sid': None, 'authenticated': 1, 'distributor': 'email', 'format': 'text/plain', 'adverb': 'always', 'class': 'TestSubscriber' } subscription.update(kwargs) Subscription.add(self.env, subscription) def test_smtp_disabled(self): self.env.config.set('notification', 'smtp_enabled', 'disabled') with self.env.db_transaction: self._add_subscription(sid='foo') self._add_subscription(sid='bar') self._notify_event('blah') self.assertEqual([], self.sender.history) def _assert_mail(self, message, content_type, body): self.assertNotIn('Bcc', message) self.assertEqual('multipart/related', message.get_content_type()) payload = list(message.get_payload()) self.assertEqual([content_type], [p.get_content_type() for p in payload]) self.assertEqual([body], [p.get_payload() for p in payload]) def _assert_alternative_mail(self, message, body_plain, body_html): self.assertNotIn('Bcc', message) self.assertEqual('multipart/related', message.get_content_type()) payload = list(message.get_payload()) self.assertEqual(['multipart/alternative'], [p.get_content_type() for p in payload]) alternative = list(payload[0].get_payload()) self.assertEqual(['text/plain', 'text/html'], [p.get_content_type() for p in alternative]) self.assertEqual([body_plain, body_html], [p.get_payload() for p in alternative]) def test_plain(self): with self.env.db_transaction: self._add_subscription(sid='foo') self._add_subscription(sid='bar') self._notify_event('blah') history = self.sender.history self.assertNotEqual([], history) self.assertEqual(1, len(history)) from_addr, recipients, message = history[0] self.assertEqual('*****@*****.**', from_addr) self.assertEqual( set(('*****@*****.**', '*****@*****.**', '*****@*****.**', '*****@*****.**')), set(recipients)) self._assert_mail(message, 'text/plain', 'blah') def test_html(self): with self.env.db_transaction: self._add_subscription(sid='foo', format='text/html') self._notify_event('blah') history = self.sender.history self.assertNotEqual([], history) self.assertEqual(2, len(history)) for from_addr, recipients, message in history: if '*****@*****.**' in recipients: self.assertEqual('*****@*****.**', from_addr) self.assertEqual(['*****@*****.**'], recipients) self._assert_alternative_mail(message, 'blah', '<p>blah</p>') if '*****@*****.**' in recipients: self.assertEqual('*****@*****.**', from_addr) self.assertEqual(set(('*****@*****.**', '*****@*****.**')), set(recipients)) self._assert_mail(message, 'text/plain', 'blah') def test_plain_and_html(self): with self.env.db_transaction: self._add_subscription(sid='foo', format='text/plain') self._add_subscription(sid='bar', format='text/html') self._notify_event('blah') history = self.sender.history self.assertNotEqual([], history) self.assertEqual(2, len(history)) for from_addr, recipients, message in history: if '*****@*****.**' in recipients: self.assertEqual( set(('*****@*****.**', '*****@*****.**', '*****@*****.**')), set(recipients)) self._assert_mail(message, 'text/plain', 'blah') if '*****@*****.**' in recipients: self.assertEqual(['*****@*****.**'], recipients) self._assert_alternative_mail(message, 'blah', '<p>blah</p>') def test_broken_plain_formatter(self): with self.env.db_transaction: self._add_subscription(sid='foo', format='text/plain') self._add_subscription(sid='bar', format='text/html') self._notify_event('raise-text-plain') history = self.sender.history self.assertNotEqual([], history) self.assertEqual(1, len(history)) from_addr, recipients, message = history[0] self.assertEqual('*****@*****.**', from_addr) self.assertEqual(['*****@*****.**'], recipients) self._assert_mail(message, 'text/html', '<p>raise-text-plain</p>') def test_broken_html_formatter(self): with self.env.db_transaction: self._add_subscription(sid='foo', format='text/html') self._add_subscription(sid='bar', format='text/plain') self._notify_event('raise-text-html') # fallback to text/plain history = self.sender.history self.assertNotEqual([], history) self.assertEqual(1, len(history)) from_addr, recipients, message = history[0] self.assertEqual('*****@*****.**', from_addr) self.assertEqual( set(('*****@*****.**', '*****@*****.**', '*****@*****.**', '*****@*****.**')), set(recipients)) self._assert_mail(message, 'text/plain', 'raise-text-html') def test_broken_plain_and_html_formatter(self): with self.env.db_transaction: self._add_subscription(sid='foo', format='text/plain') self._add_subscription(sid='bar', format='text/html') self._notify_event('raise-text-plain raise-text-html') history = self.sender.history self.assertEqual([], history) def test_username_in_always_cc(self): self.env.config.set('notification', 'smtp_always_cc', 'foo, [email protected]') self.env.config.set('notification', 'smtp_always_bcc', 'bar, foo, [email protected]') self._notify_event('blah') history = self.sender.history self.assertNotEqual([], history) self.assertEqual(1, len(history)) from_addr, recipients, message = history[0] self.assertEqual('*****@*****.**', from_addr) self.assertEqual( set(('*****@*****.**', '*****@*****.**', '*****@*****.**', '*****@*****.**')), set(recipients)) self.assertEqual('[email protected], [email protected]', message['Cc']) self.assertEqual(None, message['Bcc']) self._assert_mail(message, 'text/plain', 'blah') def test_from_author_disabled(self): self.env.config.set('notification', 'smtp_from_author', 'disabled') with self.env.db_transaction: self._add_subscription(sid='bar') self._notify_event('blah', author='bar') history = self.sender.history self.assertNotEqual([], history) from_addr, recipients, message = history[0] self.assertEqual('*****@*****.**', from_addr) self.assertEqual('"My Project" <*****@*****.**>', message['From']) self.assertEqual(1, len(history)) self._notify_event('blah', author=None) history = self.sender.history self.assertNotEqual([], history) from_addr, recipients, message = history[0] self.assertEqual('*****@*****.**', from_addr) self.assertEqual('"My Project" <*****@*****.**>', message['From']) self.assertEqual(1, len(history)) self.env.config.set('notification', 'smtp_from_name', 'Trac') self._notify_event('blah', author=None) history = self.sender.history self.assertNotEqual([], history) from_addr, recipients, message = history[0] self.assertEqual('*****@*****.**', from_addr) self.assertEqual('"Trac" <*****@*****.**>', message['From']) self.assertEqual(1, len(history)) def test_from_author_enabled(self): self.env.config.set('notification', 'smtp_from_author', 'enabled') with self.env.db_transaction: self._add_subscription(sid='foo') self._add_subscription(sid='bar') self._notify_event('blah', author='bar') history = self.sender.history self.assertNotEqual([], history) from_addr, recipients, message = history[0] self.assertEqual('*****@*****.**', from_addr) self.assertEqual('"=?utf-8?b?QsOkxZUncyBuYW1l?=" <*****@*****.**>', message['From']) self.assertEqual(1, len(history)) self._notify_event('blah', author='foo') history = self.sender.history self.assertNotEqual([], history) from_addr, recipients, message = history[0] self.assertEqual('*****@*****.**', from_addr) self.assertEqual('*****@*****.**', message['From']) self.assertEqual(1, len(history)) self._notify_event('blah', author=None) history = self.sender.history self.assertNotEqual([], history) from_addr, recipients, message = history[0] self.assertEqual('*****@*****.**', from_addr) self.assertEqual('"My Project" <*****@*****.**>', message['From']) self.assertEqual(1, len(history)) self.env.config.set('notification', 'smtp_from_name', 'Trac') self._notify_event('blah', author=None) history = self.sender.history self.assertNotEqual([], history) from_addr, recipients, message = history[0] self.assertEqual('*****@*****.**', from_addr) self.assertEqual('"Trac" <*****@*****.**>', message['From']) self.assertEqual(1, len(history))