Changeset 3046
- Timestamp:
- 01/13/08 00:59:36 (11 months ago)
- Files:
-
- announcerplugin/0.11/announcerplugin/api.py (modified) (2 diffs)
- announcerplugin/0.11/announcerplugin/distributors/email_distributor.py (modified) (7 diffs)
- announcerplugin/0.11/announcerplugin/formatters/ticket_email.py (modified) (5 diffs)
- announcerplugin/0.11/announcerplugin/producers/attachment.py (modified) (2 diffs)
- announcerplugin/0.11/announcerplugin/producers/__init__.py (modified) (1 diff)
- announcerplugin/0.11/announcerplugin/producers/ticket.py (modified) (4 diffs)
- announcerplugin/0.11/announcerplugin/resolvers/defaultdomain.py (added)
- announcerplugin/0.11/announcerplugin/resolvers/specified.py (modified) (1 diff)
- announcerplugin/0.11/announcerplugin/subscribers/__init__.py (modified) (1 diff)
- announcerplugin/0.11/announcerplugin/subscribers/ticket_compat.py (modified) (5 diffs)
- announcerplugin/0.11/announcerplugin/subscribers/ticket_groups.py (added)
- announcerplugin/0.11/announcerplugin/subscribers/ticket.py (modified) (1 diff)
- announcerplugin/0.11/announcerplugin/templates/prefs_announcer_joinable_groups.html (added)
- announcerplugin/0.11/announcerplugin/templates/ticket_email_mimic.html (modified) (1 diff)
- announcerplugin/0.11/announcerplugin/templates/ticket_email_plaintext.txt (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
announcerplugin/0.11/announcerplugin/api.py
r3041 r3046 94 94 95 95 If a single item is to be returned, use yield instead of return.""" 96 96 97 def get_format_alternative(transport, realm, style): 98 """...""" 99 97 100 def format(transport, realm, style, event): 98 101 """Converts the event into the specified style. If the transport or … … 107 110 def format_subject(transport, realm, style, event): 108 111 """Returns a suitable subject line for the specified event.""" 112 113 def format_headers(transport, realm, style, event): 114 """...""" 109 115 110 116 class IAnnouncementDistributor(Interface): announcerplugin/0.11/announcerplugin/distributors/email_distributor.py
r3041 r3046 2 2 from trac.util.compat import set, sorted 3 3 from trac.config import Option, BoolOption, IntOption, OrderedExtensionsOption 4 from trac.util import get_pkginfo 4 5 from announcerplugin.api import IAnnouncementDistributor 5 6 from announcerplugin.api import IAnnouncementFormatter … … 7 8 from announcerplugin.api import IAnnouncementAddressResolver 8 9 from announcerplugin.api import AnnouncementSystem 10 import announcerplugin, trac 9 11 10 12 from email.MIMEMultipart import MIMEMultipart … … 65 67 """Email address(es) to always send notifications to, 66 68 addresses do not appear publicly (Bcc:). (''since 0.10'').""") 67 68 smtp_default_domain = Option('announcer', 'smtp_default_domain', '', 69 """Default host/domain to append to address that do not specify one""") 70 69 71 70 ignore_domains = Option('announcer', 'ignore_domains', '', 72 71 """Comma-separated list of domains that should not be considered … … 111 110 """If true, the actual delivery of the message will occur in a separate thread.""") 112 111 112 default_email_format = Option('announcer', 'default_email_format', 'text/plain') 113 113 114 def __init__(self): 114 115 if self.use_threaded_delivery: … … 149 150 if format not in messages: 150 151 messages[format] = set() 151 152 152 153 if name and not address: 153 154 for resolver in self.resolvers: 154 155 address = resolver.get_address_for_name(name) 155 156 if address: 157 self.log.debug("EmailDistributor found the address '%s' for '%s' via: %s" % ( 158 address, name, resolver.__class__.__name__ 159 ) 160 ) 156 161 break 157 162 … … 171 176 ) 172 177 self._do_send(transport, event, format, messages[format], formats[format]) 173 178 174 179 def _get_default_format(self): 175 return 'plaintext'180 return self.default_email_format 176 181 177 182 def _get_preferred_format(self, realm, sid): … … 199 204 subject = formatter.format_subject(transport, event.realm, format, event) 200 205 201 parentMessage = MIMEMultipart("related") 202 parentMessage['Subject'] = subject 203 parentMessage['From'] = self.smtp_from 204 parentMessage['To'] = self.env.project_name 205 parentMessage['Reply-To'] = self.smtp_replyto 206 parentMessage.preamble = 'This is a multi-part message in MIME format.' 207 208 msgText = MIMEText(output, 'html') 206 alternate_format = formatter.get_format_alternative(transport, event.realm, format) 207 if alternate_format: 208 alternate_output = formatter.format(transport, event.realm, alternate_format, event) 209 else: 210 alternate_output = None 211 212 rootMessage = MIMEMultipart("related") 213 trac_version = get_pkginfo(trac.core).get('version', trac.__version__) 214 announcer_version = get_pkginfo(announcerplugin).get('version', 'Undefined') 215 216 rootMessage['X-Mailer'] = 'AnnouncerPlugin v%s on Trac v%s' % (announcer_version, trac_version) 217 rootMessage['X-Trac-Version'] = trac_version 218 rootMessage['X-Announcer-Version'] = announcer_version 219 rootMessage['X-Trac-Project'] = self.env.project_name 220 rootMessage['Precedence'] = 'bulk' 221 rootMessage['Auto-Submitted'] = 'auto-generated' 222 223 provided_headers = formatter.format_headers(transport, event.realm, format, event) 224 for key in provided_headers: 225 rootMessage['X-Announcement-%s' % key.capitalize()] = str(provided_headers[key]) 226 227 rootMessage['Subject'] = subject 228 rootMessage['From'] = self.smtp_from 229 rootMessage['To'] = self.env.project_name 230 rootMessage['Reply-To'] = self.smtp_replyto 231 rootMessage.preamble = 'This is a multi-part message in MIME format.' 232 233 if alternate_output: 234 parentMessage = MIMEMultipart('alternative') 235 rootMessage.attach(parentMessage) 236 else: 237 parentMessage = rootMessage 238 239 if alternate_output: 240 msgText = MIMEText(alternate_output, 'html' in alternate_format and 'html' or 'plain') 241 parentMessage.attach(msgText) 242 243 msgText = MIMEText(output, 'html' in format and 'html' or 'plain') 209 244 parentMessage.attach(msgText) 210 245 211 246 start = time.time() 212 247 213 package = (self.smtp_from, [x[1] for x in recipients if x], parentMessage.as_string() )248 package = (self.smtp_from, [x[1] for x in recipients if x], rootMessage.as_string() ) 214 249 if self.use_threaded_delivery: 215 250 self._deliveryQueue.put(package) 216 251 else: 217 252 self._transmit(*package) 218 253 219 254 stop = time.time() 220 255 self.log.debug("EmailDistributor took %s seconds to send." % (round(stop-start,2))) announcerplugin/0.11/announcerplugin/formatters/ticket_email.py
r3041 r3046 30 30 class TicketEmailFormatter(Component): 31 31 implements(IAnnouncementFormatter) 32 33 default_email_format = Option('announcer', 'default_email_format', 'plaintext') 34 32 35 33 ticket_email_subject = Option('announcer', 'ticket_email_subject', "Ticket #${ticket.id}: ${ticket['summary']}") 36 34 … … 46 44 if transport == "email": 47 45 if realm == "ticket": 48 yield " plaintext"49 yield " html"46 yield "text/plain" 47 yield "text/html" 50 48 51 49 return 50 51 def get_format_alternative(self, transport, realm, style): 52 if transport == "email": 53 if realm == "ticket": 54 if style == "text/html": 55 return "text/plain" 56 57 return None 58 59 def format_headers(self, transport, realm, style, event): 60 ticket = event.target 61 return dict( 62 realm=realm, 63 ticket=ticket.id, 64 priority=ticket['priority'], 65 severity=ticket['severity'] 66 ) 52 67 53 68 def format_subject(self, transport, realm, style, event): … … 60 75 if transport == "email": 61 76 if realm == "ticket": 62 if hasattr(self, '_format_%s' % style): 63 return getattr(self, '_format_%s' % style)(event) 77 if style == "text/plain": 78 return self._format_plaintext(event) 79 elif style == "text/html": 80 return self._format_html(event) 64 81 65 82 def _format_plaintext(self, event): … … 100 117 long_changes = long_changes, 101 118 short_changes = short_changes, 119 attachment= event.attachment 102 120 ) 103 121 … … 155 173 long_changes = long_changes, 156 174 short_changes = short_changes, 175 attachment= event.attachment 157 176 ) 158 177 announcerplugin/0.11/announcerplugin/producers/attachment.py
r3015 r3046 1 1 from trac.core import * 2 2 from trac.attachment import IAttachmentChangeListener 3 from announcerplugin.api import AnnouncementSystem, AnnouncementEvent 3 from announcerplugin.api import AnnouncementSystem 4 from announcerplugin.producers.ticket import TicketChangeEvent 5 from trac.ticket.model import Ticket 4 6 5 class AttachmentChangeEvent(AnnouncementEvent):6 def __init__(self, realm, category, target,7 author=None):8 AnnouncementEvent.__init__(self, realm, category, target)9 10 self.author = author11 12 7 class AttachmentChangeProducer(Component): 13 8 implements(IAttachmentChangeListener) … … 17 12 18 13 def attachment_added(self, attachment): 19 announcer = AnnouncementSystem(ticket.env) 20 announcer.send( 21 AttachmentChangeEvent(attachment.parent_realm, "attachment added", 22 attachment, author=attachment.author, 23 ) 24 ) 14 parent = attachment.resource.parent 15 16 if parent.realm == "ticket": 17 ticket = Ticket(self.env, parent.id) 18 announcer = AnnouncementSystem(ticket.env) 19 announcer.send( 20 TicketChangeEvent("ticket", "attachment added", ticket, 21 attachment=attachment, author=attachment.author, 22 ) 23 ) 25 24 26 25 def attachment_deleted(self, attachment): 27 announcer = AnnouncementSystem(ticket.env) 28 announcer.send( 29 AttachmentChangeEvent(attachment.parent_realm, "attachment added", 30 attachment, author=attachment.author, 31 ) 32 ) 26 # announcer = AnnouncementSystem(ticket.env) 27 # announcer.send( 28 # AttachmentChangeEvent(attachment.parent_realm, "attachment added", 29 # attachment, author=attachment.author, 30 # ) 31 # ) 32 pass announcerplugin/0.11/announcerplugin/producers/__init__.py
r3015 r3046 1 1 import ticket 2 import attachment announcerplugin/0.11/announcerplugin/producers/ticket.py
r3015 r3046 1 1 from trac.core import * 2 from trac.config import BoolOption 2 3 from trac.ticket.api import ITicketChangeListener 3 4 from announcerplugin.api import AnnouncementSystem, AnnouncementEvent … … 5 6 class TicketChangeEvent(AnnouncementEvent): 6 7 def __init__(self, realm, category, target, 7 comment=None, author=None, changes=None): 8 comment=None, author=None, changes={}, 9 attachment=None): 8 10 AnnouncementEvent.__init__(self, realm, category, target) 9 11 … … 11 13 self.comment = comment 12 14 self.changes = changes 15 self.attachment = attachment 13 16 14 17 class TicketChangeProducer(Component): 15 18 implements(ITicketChangeListener) 19 20 ignore_cc_changes = BoolOption('announcer', 'ignore_cc_changes', False, 21 doc="""When true, the system will not send out announcement events if 22 the only field that was changed was CC. A change to the CC field that 23 happens at the same as another field will still result in an event 24 being created.""") 16 25 17 26 def __init__(self, *args, **kwargs): … … 27 36 28 37 def ticket_changed(self, ticket, comment, author, old_values): 38 if old_values.keys() == ['cc'] and not comment: 39 return 40 29 41 announcer = AnnouncementSystem(ticket.env) 30 42 announcer.send( announcerplugin/0.11/announcerplugin/resolvers/specified.py
r3041 r3046 19 19 AND authenticated=1 20 20 AND name=%s 21 """, (name,' specified_email'))21 """, (name,'announcer_specified_email')) 22 22 23 23 result = cursor.fetchone() announcerplugin/0.11/announcerplugin/subscribers/__init__.py
r3015 r3046 1 1 import ticket 2 2 import ticket_compat 3 import ticket_groups announcerplugin/0.11/announcerplugin/subscribers/ticket_compat.py
r3040 r3046 4 4 from trac.web.chrome import add_warning 5 5 from trac.config import BoolOption 6 import re 6 7 7 8 class StaticTicketSubscriber(Component): … … 89 90 def get_subscription_categories(self, realm): 90 91 if realm == 'ticket': 91 return ('created', 'changed' )92 return ('created', 'changed', 'attachment added') 92 93 else: 93 94 return tuple() … … 96 97 if event.realm == "ticket": 97 98 ticket = event.target 98 if event.category == "created":99 if event.category in ('changed', 'attachment added'): 99 100 component = model.Component(self.env, ticket['component']) 100 101 if component.owner: … … 133 134 r = result[0] == '0' 134 135 self.log.debug("LegacyTicketSubscriber excluded '%s' because of opt-out rule: %s" % (sid,preference)) 135 return r136 return True 136 137 137 138 return False … … 144 145 145 146 def get_subscription_categories(self, *args): 146 return ('changed', )147 return ('changed', 'attachment added') 147 148 148 149 def get_subscriptions_for_event(self, event): 149 150 if event.realm == 'ticket': 150 if event.category == 'changed':151 if event.category in ('changed', 'attachment added'): 151 152 cc = event.target['cc'] 152 for chunk in cc.split(','): 153 for chunk in re.split('\s|,', cc): 154 chunk = chunk.strip() 155 if not chunk or chunk.startswith('@'): 156 continue 157 153 158 if '@' in chunk: 154 address = chunk .strip()159 address = chunk 155 160 name = None 156 161 else: 157 name = chunk .strip()162 name = chunk 158 163 address = None 164 159 165 if name or address: 160 166 self.log.debug("CarbonCopySubscriber added '%s <%s>' because of rule: carbon copied" % (name,address)) announcerplugin/0.11/announcerplugin/subscribers/ticket.py
r3026 r3046 11 11 12 12 def get_subscription_categories(self, realm): 13 return ('created', 'changed' )13 return ('created', 'changed', 'attachment added') 14 14 15 15 def get_subscriptions_for_event(self, event): announcerplugin/0.11/announcerplugin/templates/ticket_email_mimic.html
r3041 r3046 160 160 </ul> 161 161 </py:if> 162 <py:if test="attachment"> 163 <div class="commentstitle" style="font-size: small; margin: 1em;">Attachments:</div> 164 <ul> 165 <li>File <span class="fieldname" style="font-weight: bold; font-style: italic;">${attachment.filename}</span> added<py:if test="attachment.description">: <span style="font-style: italic">${attachment.description}</span></py:if></li> 166 </ul> 167 </py:if> 162 168 <py:if test="comment"> 163 169 <div class="commentstitle" style="font-size: small; margin: 1em;">Comments:</div> announcerplugin/0.11/announcerplugin/templates/ticket_email_plaintext.txt
r3040 r3046 18 18 ${ticket['description']} 19 19 {% end %}\ 20 {% if has_changes %}\20 {% if has_changes or attachment %}\ 21 21 --------------------------------------------------------------------- 22 22 Changes (by ${author}): … … 35 35 ${long_changes[change]} 36 36 {% end %}\ 37 {% end %}\ 38 {% if attachment %}\ 39 Attachment: 40 * File '${attachment.filename}' added{% if attachment.description %}: ${attachment.description} {% end %} 37 41 {% end %} 38 42 {% if comment %}\
