Changeset 3041

Show
Ignore:
Timestamp:
01/12/08 00:19:17 (10 months ago)
Author:
ixokai
Message:

Basic email notification (fragile still) with html and plain text (configurable) is in place for tickets.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • announcerplugin/0.11/announcerplugin/api.py

    r3040 r3041  
    33from trac.db import Table, Column, Index 
    44from trac.env import IEnvironmentSetupParticipant 
     5import time 
    56 
    67class IAnnouncementSubscriber(Interface): 
     
    104105        """ 
    105106         
     107    def format_subject(transport, realm, style, event): 
     108        """Returns a suitable subject line for the specified event.""" 
     109 
    106110class IAnnouncementDistributor(Interface): 
    107111    """The Distributor is responsible for actually delivering an event to the 
     
    307311 
    308312    def send(self, evt): 
     313        start = time.time() 
     314        self._real_send(evt) 
     315        stop = time.time() 
     316        self.log.debug("AnnouncementSystem sent event in %s seconds." % (round(stop-start,2))) 
     317 
     318    def _real_send(self, evt): 
    309319        """Accepts a single AnnouncementEvent instance (or subclass), and 
    310320        returns nothing.  
  • announcerplugin/0.11/announcerplugin/distributors/email_distributor.py

    r3040 r3041  
    1010from email.MIMEMultipart import MIMEMultipart 
    1111from email.MIMEText import MIMEText 
    12  
     12import time, Queue, threading, smtplib 
     13 
     14class DeliveryThread(threading.Thread): 
     15    def __init__(self, queue, sender): 
     16        threading.Thread.__init__(self) 
     17        self._sender = sender 
     18        self._queue = queue 
     19        self.setDaemon(True) 
     20         
     21    def run(self): 
     22        while 1: 
     23            sendfrom, recipients, message = self._queue.get() 
     24             
     25            self._sender(sendfrom, recipients, message) 
     26             
    1327class EmailDistributor(Component): 
    1428     
     
    93107        If no prefix is desired, then specifying an empty option  
    94108        will disable it.(''since 0.10.1'').""") 
    95  
     109     
     110    use_threaded_delivery = BoolOption('announcer', 'use_threaded_delivery', False,  
     111    """If true, the actual delivery of the message will occur in a separate thread.""") 
     112     
     113    def __init__(self): 
     114        if self.use_threaded_delivery: 
     115            self._deliveryQueue = Queue.Queue() 
     116            thread = DeliveryThread(self._deliveryQueue, self._transmit) 
     117            thread.start() 
     118     
    96119    # IAnnouncementDistributor 
    97120    def get_distribution_transport(self): 
     
    127150                    messages[format] = set() 
    128151                     
    129                 # if name and not address: 
    130                 #     address = self._resolve 
    131                      
    132                 messages[format].add((name, address)) 
     152                if name and not address: 
     153                    for resolver in self.resolvers: 
     154                        address = resolver.get_address_for_name(name) 
     155                        if address: 
     156                            break 
     157                             
     158                if address: 
     159                    messages[format].add((name, address)) 
     160                else: 
     161                    self.log.debug("EmailDistributor was unable to find an address for: %s" % name) 
    133162                     
    134163            for format in messages.keys(): 
    135                 # print 'ER', messages[format] 
    136                 # self.log.debug( 
    137                 #     "EmailDistributor is sending event as '%s' to: %s" % ( 
    138                 #         format, ', '.join(messages[format]) 
    139                 #     ) 
    140                 # ) 
    141                 self._do_send(transport, event, format, messages[format], formats[format]) 
     164                if messages[format]: 
     165                    self.log.debug( 
     166                        "EmailDistributor is sending event as '%s' to: %s" % ( 
     167                            format, ', '.join( 
     168                                ('%s <%s>' % (name, address) for name, address in messages[format]) 
     169                            ) 
     170                        ) 
     171                    ) 
     172                    self._do_send(transport, event, format, messages[format], formats[format]) 
    142173 
    143174    def _get_default_format(self): 
     
    166197    def _do_send(self, transport, event, format, recipients, formatter, backup=None): 
    167198        output = formatter.format(transport, event.realm, format, event) 
     199        subject = formatter.format_subject(transport, event.realm, format, event) 
    168200         
    169201        parentMessage = MIMEMultipart("related") 
    170         parentMessage['Subject'] = "Message Subject" 
    171         parentMessage['From'] = 'Bob' 
    172         parentMessage['To'] = 'Sam' 
     202        parentMessage['Subject'] = subject 
     203        parentMessage['From'] = self.smtp_from 
     204        parentMessage['To'] = self.env.project_name 
     205        parentMessage['Reply-To'] = self.smtp_replyto 
    173206        parentMessage.preamble = 'This is a multi-part message in MIME format.' 
    174207         
     
    176209        parentMessage.attach(msgText) 
    177210         
    178         import smtplib 
     211        start = time.time() 
     212         
     213        package = (self.smtp_from, [x[1] for x in recipients if x], parentMessage.as_string() ) 
     214        if self.use_threaded_delivery: 
     215            self._deliveryQueue.put(package) 
     216        else: 
     217            self._transmit(*package) 
     218             
     219        stop = time.time() 
     220        self.log.debug("EmailDistributor took %s seconds to send." % (round(stop-start,2))) 
     221 
     222    def _transmit(self, smtpfrom, addresses, message): 
    179223        smtp = smtplib.SMTP() 
    180224        smtp.connect(self.smtp_server) 
    181225        smtp.login(self.smtp_user, self.smtp_password) 
    182         for name, address in recipients: 
    183             smtp.sendmail(self.smtp_from, address, parentMessage.as_string) 
     226        smtp.sendmail(smtpfrom, addresses, message) 
    184227        smtp.quit() 
    185  
     228         
    186229    # IAnnouncementDistributor 
    187230    def get_announcement_preference_boxes(self, req): 
  • announcerplugin/0.11/announcerplugin/distributors/__init__.py

    r3015 r3041  
    1 import email 
     1import email_distributor 
  • announcerplugin/0.11/announcerplugin/formatters/ticket_email.py

    r3040 r3041  
    3333    default_email_format = Option('announcer', 'default_email_format', 'plaintext') 
    3434     
     35    ticket_email_subject = Option('announcer', 'ticket_email_subject', "Ticket #${ticket.id}: ${ticket['summary']}") 
     36     
    3537    def get_format_transport(self): 
    3638        return "email" 
     
    4850                 
    4951        return 
    50  
     52         
     53    def format_subject(self, transport, realm, style, event): 
     54        if transport == "email": 
     55            if realm == "ticket": 
     56                template = NewTextTemplate(self.ticket_email_subject) 
     57                return template.generate(ticket=event.target, event=event).render() 
     58                 
    5159    def format(self, transport, realm, style, event): 
    52         if realm == "ticket": 
    53             if hasattr(self, '_format_%s' % style): 
    54                 return getattr(self, '_format_%s' % style)(event) 
    55  
    56     def _load_text_template(self, chrome, filename): 
    57         # print 'Load', chrome.templates 
    58         if not chrome.templates: 
    59             return None 
    60              
    61         return chrome.templates.load(filename, cls=NewTextTemplate) 
     60        if transport == "email": 
     61            if realm == "ticket": 
     62                if hasattr(self, '_format_%s' % style): 
     63                    return getattr(self, '_format_%s' % style)(event) 
    6264 
    6365    def _format_plaintext(self, event): 
  • announcerplugin/0.11/announcerplugin/pref.py

    r3016 r3041  
    88 
    99def truth(v): 
    10     print 'V=', v 
    1110    if v in (False, 'False', 'false', 0, '0', ''): 
    12         print 'false' 
    1311        return None 
    14     print 'true' 
    1512    return True 
    1613 
    1714class AnnouncerPreferences(Component): 
    18     implements(IPreferencePanelProvider, ITemplateProvider, IRequestHandler
     15    implements(IPreferencePanelProvider, ITemplateProvider
    1916     
    2017    preference_boxes = ExtensionPoint(IAnnouncementPreferenceProvider) 
     
    2623        resource_dir = resource_filename(__name__, 'templates') 
    2724        return [resource_dir] 
    28  
     25         
    2926    def get_preference_panels(self, req): 
    30         print 'REQ', req.authname 
    3127        if req.authname and req.authname != 'anonymous': 
    3228            yield ('announcer', 'Announcements') 
     
    6258        return 'prefs_announcer.html', {"boxes": streams, "style": style.render()} 
    6359         
    64     def match_request(self, req): 
    65         print "MATCH?!" 
    66         print req 
    67         print req.path_info 
    68         return False 
    6960         
    70     def process_request(self, req): 
    71         return '', {} 
    72          
  • announcerplugin/0.11/announcerplugin/resolvers/__init__.py

    r3015 r3041  
    11import specified 
    22import sessionemail 
     3import defaultdomain 
  • announcerplugin/0.11/announcerplugin/resolvers/sessionemail.py

    r3015 r3041  
    1616             WHERE sid=%s 
    1717               AND name=%s 
    18         """, (sid, 'email')) 
     18        """, (name, 'email')) 
    1919                 
    2020        for record in sorted(cursor.fetchall(), key=lambda x: x[1]): 
  • announcerplugin/0.11/announcerplugin/resolvers/specified.py

    r3040 r3041  
    1919               AND authenticated=1 
    2020               AND name=%s 
    21         """, (sid,'specified_email')) 
     21        """, (name,'specified_email')) 
    2222         
    2323        result = cursor.fetchone() 
  • announcerplugin/0.11/announcerplugin/templates/ticket_email_mimic.html

    r3040 r3041  
    113113 
    114114  </head> 
    115   <body bgcolor="white"
    116     <span class="header">Ticket #${ticket.id} (${ticket['status']} ${ticket['type']})</span> 
    117     <div class="ticketbox"
    118       <div class="title">${ticket['summary']}</div> 
    119       <table class="header"
     115  <body bgcolor="white" style='font: medium "Lucida Grande", Lucida, Verdana, sans-serif'
     116    <span class="header" style="font-size: large;">Ticket #${ticket.id} (${ticket['status']} ${ticket['type']})</span> 
     117    <div class="ticketbox" style="background-color: #fefcd3; width: 90%; padding: .5em 1em; border-style: outset; border-width: 1px; margin: 2px;"
     118      <div class="title" style="font-weight: bold; border-bottom: 1pt inset #dfdfdf; padding-bottom: .5em;">${ticket['summary']}</div> 
     119      <table class="header" width="100%"
    120120        <tr> 
    121           <td class="label">Owned by:</td> 
    122           <td class="value">${ticket['owner']}</td> 
     121          <td class="label" width="30%" style="font-size: x-small; color: #7e7e7e; border-bottom: 1pt inset #dfdfdf; padding-bottom: .5em; width:  30%;">Owned by:</td> 
     122          <td class="value" width="70%" style="border-bottom: 1pt inset #dfdfdf; padding-bottom: .5em; font-size: small;">${ticket['owner']}</td> 
    123123        </tr> 
    124124        <tr> 
    125           <td class="label">Reported by:</td> 
    126           <td class="value">${ticket['reporter']}</td> 
     125          <td class="label" style="font-size: x-small; color: #7e7e7e; border-bottom: 1pt inset #dfdfdf; padding-bottom: .5em; width:  30%;">Reported by:</td> 
     126          <td class="value" style="border-bottom: 1pt inset #dfdfdf; padding-bottom: .5em; font-size: small;">${ticket['reporter']}</td> 
    127127        </tr> 
    128128        <tr py:if="ticket['milestone']"> 
    129           <td class="label">Milestone:</td> 
    130           <td class="value">${ticket['milestone']}</td> 
     129          <td class="label" style="font-size: x-small; color: #7e7e7e; border-bottom: 1pt inset #dfdfdf; padding-bottom: .5em; width:  30%;">Milestone:</td> 
     130          <td class="value" style="border-bottom: 1pt inset #dfdfdf; padding-bottom: .5em; font-size: small;">${ticket['milestone']}</td> 
    131131        </tr> 
    132132        <tr py:if="ticket['priority']"> 
    133           <td class="label">Priority:</td> 
    134           <td class="value">${ticket['priority']}</td> 
     133          <td class="label" style="font-size: x-small; color: #7e7e7e; border-bottom: 1pt inset #dfdfdf; padding-bottom: .5em; width:  30%;">Priority:</td> 
     134          <td class="value" style="border-bottom: 1pt inset #dfdfdf; padding-bottom: .5em; font-size: small;">${ticket['priority']}</td> 
    135135        </tr> 
    136136        <tr py:if="ticket['severity']"> 
    137           <td class="label">Severity:</td> 
    138           <td class="value">${ticket['severity']}</td> 
     137          <td class="label" style="font-size: x-small; color: #7e7e7e; border-bottom: 1pt inset #dfdfdf; padding-bottom: .5em; width:  30%;">Severity:</td> 
     138          <td class="value" style="border-bottom: 1pt inset #dfdfdf; padding-bottom: .5em; font-size: small;">${ticket['severity']}</td> 
    139139        </tr> 
    140140      </table> 
    141141      <py:if test="category == 'created'"> 
    142       <div class="descheader">Description</div> 
    143       <div class="descbody">${ticket['description']}</div> 
     142      <div class="descheader" style="font-size: x-small; color: #7e7e7e; border-bottom: 1pt inset #dfdfdf; padding-bottom: .5em; font-weight: bold;">Description</div> 
     143      <div class="descbody" style="font-size: small; padding: 1em;">${ticket['description']}</div> 
    144144      </py:if> 
    145145    </div> 
    146146    <py:if test="has_changes"> 
    147       <div class="changetitle">Changes (by ${author}):</div> 
     147      <div class="changetitle" style="font-size: small; margin: 1em;">Changes (by ${author}):</div> 
    148148      <ul> 
    149         <li py:for="change in short_changes" class="changeitem"
    150           <span class="fieldname">${change}</span> changed  
    151           from <span class="from">${short_changes[change][0]}</span> 
    152           to <span class="to">${short_changes[change][1]}</span>. 
     149        <li py:for="change in short_changes" class="changeitem" style="font-size: small;"
     150          <span class="fieldname" style="font-weight: bold; font-style: italic;">${change}</span> changed  
     151          from <span class="from" style="font-weight: bold;">${short_changes[change][0]}</span> 
     152          to <span class="to" style="font-weight: bold;">${short_changes[change][1]}</span>. 
    153153        </li> 
    154154      </ul> 
    155155      <ul> 
    156         <li py:for="(change, content) in long_changes.items()" class="longchange"
    157           <span class="fieldname">${change}:</span> 
    158           <div class="htmldiff">${content}</div> 
     156        <li py:for="(change, content) in long_changes.items()" class="longchange" style="font-size: small;"
     157          <span class="fieldname" style="font-weight: bold; font-style: italic;">${change}:</span> 
     158          <div class="htmldiff" style="margin-left: 2em; padding: 1em; width: 90%; background-color: #f3f3f3; margin: 5px; border-style: outset; border-width: 1px;">${content}</div> 
    159159        </li> 
    160160      </ul> 
    161161    </py:if> 
    162162    <py:if test="comment"> 
    163       <div class="commentstitle">Comments:</div> 
    164       <div class="comments">${comment}</div> 
     163      <div class="commentstitle" style="font-size: small; margin: 1em;">Comments:</div> 
     164      <div class="comments" style="font-size: small; margin: 0 0 0 2em; font-style: italic;">${comment}</div> 
    165165    </py:if> 
    166166    <br /> 
    167167    <hr /> 
    168168    <br /> 
    169     <div class="footer">Ticket URL: <a href="${ticket_link}">${ticket_link}</a><br /> 
     169    <div class="footer" style="font-size: small;">Ticket URL: <a href="${ticket_link}">${ticket_link}</a><br /> 
    170170    ${project_name} <a href="${project_link}">${project_link}</a><br /> 
    171171    ${project_desc}<br />