Changeset 17594
- Timestamp:
- Nov 25, 2019, 9:37:28 AM (7 months ago)
- Location:
- trachtmlnotificationplugin/0.12
- Files:
-
- 4 added
- 4 edited
- 1 moved
Legend:
- Unmodified
- Added
- Removed
-
trachtmlnotificationplugin/0.12
- Property svn:ignore
-
old new 2 2 dist 3 3 *.egg-info 4 .tox
-
- Property svn:ignore
-
trachtmlnotificationplugin/0.12/setup.py
r13660 r17594 8 8 setup( 9 9 name = 'TracHtmlNotificationPlugin', 10 version = '0.12.0. 1',10 version = '0.12.0.2', 11 11 description = 'Send ticket notification with HTML part (t:#2625)', 12 12 license = 'BSD', # the same as Trac … … 16 16 packages = find_packages(exclude=['*.tests*']), 17 17 package_data = { 18 'trachtmlnotification': ['templates/*.html'], 18 'trachtmlnotification': [ 19 'templates/genshi/*.html', 20 'templates/jinja2/*.html', 21 ], 19 22 }, 20 23 entry_points = { -
trachtmlnotificationplugin/0.12/trachtmlnotification/notification.py
r16119 r17594 6 6 from email.MIMEText import MIMEText 7 7 from email.MIMEMultipart import MIMEMultipart 8 from genshi.builder import tag 9 Locale = None 8 from pkg_resources import parse_version, resource_filename 9 10 try: 11 from trac.util.html import tag 12 except ImportError: 13 from genshi.builder import tag 14 10 15 try: 11 16 from babel.core import Locale 12 17 except ImportError: 13 pass 14 18 Locale = None 19 20 from trac import __version__ 15 21 from trac.core import Component, implements 16 22 from trac.attachment import AttachmentModule 17 23 from trac.env import Environment 18 from trac.mimeview.api import Context19 24 from trac.notification import SmtpEmailSender, SendmailEmailSender 20 25 from trac.resource import ResourceNotFound … … 31 36 32 37 try: 38 from trac.web.chrome import web_context 39 except ImportError: 40 from trac.mimeview.api import Context 41 web_context = Context.from_request 42 43 try: 33 44 from trac.notification.api import INotificationFormatter 34 45 except ImportError: 35 46 INotificationFormatter = None 47 48 49 _parsed_version = parse_version(__version__) 50 if _parsed_version >= parse_version('1.4'): 51 _use_jinja2 = True 52 elif _parsed_version >= parse_version('1.3'): 53 _use_jinja2 = hasattr(Chrome, 'jenv') 54 else: 55 _use_jinja2 = False 56 57 58 if _use_jinja2: 59 _template_dir = resource_filename(__name__, 'templates/jinja2') 60 else: 61 _template_dir = resource_filename(__name__, 'templates/genshi') 36 62 37 63 … … 47 73 return Locale('en', 'US') 48 74 else: 49 def _parse_locale(lang): 50 return None 51 52 53 if hasattr(Environment, 'get_read_db'): 54 def _get_db(env): 55 return env.get_read_db() 56 else: 57 def _get_db(env): 58 return env.get_db_cnx() 75 _parse_locale = lambda lang: None 59 76 60 77 … … 79 96 cnum = None 80 97 if event.time: 81 db = self.env.get_read_db() 82 cursor = db.cursor() 83 cursor.execute("""\ 98 rows = self._db_query("""\ 84 99 SELECT field, oldvalue FROM ticket_change 85 100 WHERE ticket=%s AND time=%s AND field='comment' 86 101 """, (ticket.id, to_utimestamp(event.time))) 87 for field, oldvalue in cursor:102 for field, oldvalue in rows: 88 103 if oldvalue: 89 104 cnum = int(oldvalue.rsplit('.', 1)[-1]) … … 116 131 117 132 def get_templates_dirs(self): 118 from pkg_resources import resource_filename 119 return [resource_filename(__name__, 'templates')] 133 return [_template_dir] 120 134 121 135 # public methods … … 140 154 # private methods 141 155 156 if hasattr(Environment, 'db_query'): 157 def _db_query(self, query, args=()): 158 return self.env.db_query(query, args) 159 else: 160 def _db_query(self, query, args=()): 161 db = self.env.get_read_db() 162 cursor = db.cursor() 163 cursor.execute(query, args) 164 return list(cursor) 165 142 166 def _create_request(self): 143 167 languages = filter(None, [self.config.get('trac', 'default_language')]) … … 148 172 tzname = self.config.get('trac', 'default_timezone') 149 173 tz = get_timezone(tzname) or localtz 174 base_url = self.env.abs_href() 175 if ':' in base_url: 176 url_scheme = base_url.split(':', 1)[0] 177 else: 178 url_scheme = 'http' 150 179 environ = {'REQUEST_METHOD': 'POST', 'REMOTE_ADDR': '127.0.0.1', 151 180 'SERVER_NAME': 'localhost', 'SERVER_PORT': '80', 152 'wsgi.url_scheme': 'http', 153 'trac.base_url': self.env.abs_href()} 181 'wsgi.url_scheme': url_scheme, 'trac.base_url': base_url} 154 182 if languages: 155 183 environ['HTTP_ACCEPT_LANGUAGE'] = ','.join(languages) … … 160 188 req.args = {} 161 189 req.authname = 'anonymous' 190 req.form_token = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 162 191 req.session = session 163 192 req.perm = MockPerm() … … 182 211 cnum = int(cnum) 183 212 184 db = _get_db(self.env)185 213 try: 186 214 ticket = Ticket(self.env, tktid) … … 218 246 if change.get('cnum') == cnum] 219 247 data['changes'] = changes 220 context = Context.from_request(req, ticket.resource, absurls=True)248 context = web_context(req, ticket.resource, absurls=True) 221 249 alist = attmod.attachment_data(context) 222 250 alist['can_create'] = False 223 data.update({ 224 'can_append': False, 225 'show_editor': False, 226 'start_time': ticket['changetime'], 227 'context': context, 228 'alist': alist, 229 'styles': self._get_styles(chrome), 230 'link': tag.a(link, href=link), 231 'tag_': tag_, 232 }) 251 data.update({'can_append': False, 252 'show_editor': False, 253 'start_time': ticket['changetime'], 254 'context': context, 255 'alist': alist, 256 'styles': self._get_styles(chrome), 257 'link': tag.a(link, href=link), 258 'tag_': tag_}) 233 259 template = 'htmlnotification_ticket.html' 234 260 # use pretty_dateinfo in TimelineModule 235 261 TimelineModule(self.env).post_process_request(req, template, data, 236 262 None) 237 rendered = chrome.render_template(req, template, data, fragment=True) 238 return unicode(rendered) 263 if _use_jinja2: 264 return chrome.render_template(req, template, data, 265 {'iterable': False}) 266 else: 267 return unicode(chrome.render_template(req, template, data, 268 fragment=True)) 239 269 240 270 def _get_styles(self, chrome): … … 282 312 283 313 284 class HtmlNotificationSmtpEmailSender(SmtpEmailSender): 285 286 def send(self, from_addr, recipients, message): 287 if not INotificationFormatter: 314 if not INotificationFormatter: 315 316 class HtmlNotificationSmtpEmailSender(SmtpEmailSender): 317 318 def send(self, from_addr, recipients, message): 288 319 mod = HtmlNotificationModule(self.env) 289 320 message = mod.substitute_message(message) 290 SmtpEmailSender.send(self, from_addr, recipients, message) 291 292 293 class HtmlNotificationSendmailEmailSender(SendmailEmailSender): 294 295 def send(self, from_addr, recipients, message): 296 if not INotificationFormatter: 321 SmtpEmailSender.send(self, from_addr, recipients, message) 322 323 324 class HtmlNotificationSendmailEmailSender(SendmailEmailSender): 325 326 def send(self, from_addr, recipients, message): 297 327 mod = HtmlNotificationModule(self.env) 298 328 message = mod.substitute_message(message) 299 SendmailEmailSender.send(self, from_addr, recipients, message)329 SendmailEmailSender.send(self, from_addr, recipients, message) -
trachtmlnotificationplugin/0.12/trachtmlnotification/templates/genshi/htmlnotification_ticket.html
r17593 r17594 17 17 <h1 id="trac-ticket-title"> 18 18 <a href="${abs_href.ticket(ticket.id)}">Ticket #${ticket.id}</a> 19 <span class=" status">(${ticket.status}<py:if20 test="ticket.type"> ${ticket.type}</py:if><py:if21 test="ticket.resolution">: ${ticket.resolution}</py:if>)</span>19 <span class="trac-status">(${ticket.status}</span> 20 <span class="trac-type" py:if="ticket.type"> ${ticket.type}</span> 21 <span class="trac-resolution" py:if="ticket.resolution"> ${ticket.resolution}</span> 22 22 </h1> 23 23 <hr /> -
trachtmlnotificationplugin/0.12/trachtmlnotification/tests/notification.py
r13660 r17594 3 3 import unittest 4 4 5 from genshi.builder import tag 5 from trac.core import Component, implements 6 from trac.test import EnvironmentStub, MockRequest 7 from trac.ticket.model import Ticket 8 from trac.ticket.web_ui import TicketModule 9 from trac.util.datefmt import to_utimestamp 10 from trac.web.api import ITemplateStreamFilter, RequestDone 11 try: 12 from trac.notification.api import IEmailSender, NotificationSystem 13 except ImportError: 14 from trac.notification import IEmailSender, NotificationSystem 6 15 7 from trac.core import Component, implements 8 from trac.notification import IEmailSender 9 from trac.test import EnvironmentStub 10 from trac.ticket.model import Ticket 11 from trac.web.api import ITemplateStreamFilter 12 13 from trachtmlnotification.notification import HtmlNotificationModule 14 15 message = """\ 16 From: <trac@localhost> 17 To: <trac@localhost> 18 X-Trac-Ticket-URL: %(url)s 19 Content-Type: text/plain; charset=utf-8 20 21 email body 22 """ 16 from trachtmlnotification.notification import ( 17 HtmlNotificationModule, INotificationFormatter, tag) 23 18 24 19 … … 39 34 40 35 36 class EmailSenderStub(Component): 37 38 implements(IEmailSender) 39 40 history = None 41 42 def __init__(self): 43 self.history = [] 44 45 def send(self, from_addr, recipients, message): 46 if not INotificationFormatter: 47 mod = HtmlNotificationModule(self.env) 48 message = mod.substitute_message(message) 49 self.history.append((from_addr, recipients, message)) 50 51 41 52 class NormalTestCase(unittest.TestCase): 53 54 users = [('joe', 'Joe User', 'joe@example.org')] 42 55 43 56 def setUp(self): 44 57 self.env = EnvironmentStub(default_data=True, 45 enable=['trac.*', 'trachtmlnotification.*']) 46 self.env.config.set('notification', 'mime_encoding', 'none') 58 enable=['trac.*', 'trachtmlnotification.*', 59 EmailSenderStub]) 60 self.config = self.env.config 61 section = self.config['notification'] 62 section.set('smtp_enabled', 'enabled') 63 section.set('mime_encoding', 'none') 64 section.set('email_sender', 'EmailSenderStub') 65 if hasattr(NotificationSystem, 'subscribers'): 66 section = self.config['notification-subscriber'] 67 section.set('s', 'TicketReporterSubscriber') 68 section.set('s.priority', '1') 69 section.set('s.adverb', 'always') 70 section.set('s.format', 'text/html') 71 else: 72 section.set('always_notify_reporter', 'enabled') 73 users = [('joe', 'Joe User', 'joe@example.org')] 74 if hasattr(self.env, 'insert_users'): 75 self.env.insert_users(self.users) 76 else: 77 self.env.known_users = users 47 78 self.mod = HtmlNotificationModule(self.env) 79 self.tktmod = TicketModule(self.env) 80 self.sender = EmailSenderStub(self.env) 48 81 49 82 def tearDown(self): 50 83 self.env.reset_db() 51 84 52 def test_newticket(self): 53 ticket = Ticket(self.env) 54 ticket['status'] = 'new' 55 ticket['summary'] = 'Blah blah blah' 56 ticket['reporter'] = 'joe' 57 ticket['description'] = '<<<Description>>>' 58 ticket.insert() 59 orig = message % {'url': 'http://localhost/ticket/%d' % ticket.id} 60 result = self.mod.substitute_message(orig, ignore_exc=False) 61 self.assertNotEqual(result, orig) 62 self.assertTrue('\nContent-Type: text/html;' in result) 63 self.assertTrue('<<<Description>>>' in result) 85 def _create_ticket(self, **kwargs): 86 args = dict(('field_' + name, value) 87 for name, value in kwargs.iteritems()) 88 req = MockRequest(self.env, method='POST', authname=kwargs['reporter'], 89 path_info='/newticket', args=args) 90 self.assertEqual(True, self.tktmod.match_request(req)) 91 try: 92 self.tktmod.process_request(req) 93 self.fail('RequestDone not raised') 94 except RequestDone: 95 pass 64 96 65 def test_ticket_comment(self): 66 ticket = Ticket(self.env) 67 ticket['status'] = 'new' 68 ticket['summary'] = 'Blah blah blah' 69 ticket['reporter'] = 'joe' 70 ticket['description'] = '`<<<Description>>>`' 71 ticket.insert() 72 ticket.save_changes(author='anonymous', comment='`>>>first comment<<<`') 73 ticket.save_changes(author='anonymous', comment='`>>>second comment<<<`') 74 url = 'http://localhost/ticket/%d#comment:2' % ticket.id 75 orig = message % {'url': url} 76 result = self.mod.substitute_message(orig, ignore_exc=False) 77 self.assertNotEqual(result, orig) 78 self.assertTrue('\nContent-Type: text/html;' in result) 79 self.assertTrue('<<<Description>>>' in result) 80 self.assertFalse('>>>first comment<<<' in result) 81 self.assertTrue('>>>second comment<<<' in result) 97 def _comment_ticket(self, id_, author, comment): 98 ticket = Ticket(self.env, id_) 99 changetime = str(to_utimestamp(ticket['changetime'])) 100 req = MockRequest(self.env, method='POST', authname=author, 101 path_info='/ticket/%d' % id_, 102 args={'action': 'leave', 'submit': '*', 103 'comment': comment, 'start_time': changetime, 104 'view_time': changetime}) 105 self.assertEqual(True, self.tktmod.match_request(req)) 106 try: 107 self.tktmod.process_request(req) 108 self.fail('RequestDone not raised') 109 except RequestDone: 110 pass 82 111 83 84 class RequestAttributeTestCase(unittest.TestCase): 85 86 def setUp(self): 87 self.env = EnvironmentStub( 88 default_data=True, 89 enable=['trac.*', 'trachtmlnotification.*', 90 'trachtmlnotification.tests.*']) 91 self.env.config.set('notification', 'mime_encoding', 'none') 92 self.mod = HtmlNotificationModule(self.env) 93 94 def tearDown(self): 95 self.env.reset_db() 96 97 def test_read_attribute_in_filter_stream(self): 98 ticket = Ticket(self.env) 99 ticket['status'] = 'new' 100 ticket['summary'] = 'Blah blah blah' 101 ticket['reporter'] = 'joe' 102 ticket['description'] = '<<<Description>>>' 103 ticket.insert() 104 orig = message % {'url': 'http://localhost/ticket/%d' % ticket.id} 105 result = self.mod.substitute_message(orig, ignore_exc=False) 106 self.assertNotEqual(result, orig) 107 self.assertTrue('\nContent-Type: text/html;' in result) 108 self.assertTrue('<script>// BLAH-BLAH-BLAH</script>' in result) 112 def test_ticket_notification(self): 113 self._create_ticket(status='new', summary='Blah blah blah', 114 reporter='joe', 115 description='<<<Description>>>\r\n') 116 from_addr, recipients, message = self.sender.history[-1] 117 self.assertIn('\nContent-Type: text/html;', message) 118 self.assertIn('<<<Description>>>', message) 119 self._comment_ticket(1, 'joe', '`>>>first comment<<<`') 120 from_addr, recipients, message = self.sender.history[-1] 121 self.assertIn('\nContent-Type: text/html;', message) 122 self.assertIn('<<<Description>>>', message) 123 self.assertIn('>>>first comment<<<', message) 124 self._comment_ticket(1, 'joe', '`>>>second comment<<<`') 125 from_addr, recipients, message = self.sender.history[-1] 126 self.assertIn('\nContent-Type: text/html;', message) 127 self.assertIn('<<<Description>>>', message) 128 self.assertIn('>>>second comment<<<', message) 109 129 110 130 … … 112 132 suite = unittest.TestSuite() 113 133 suite.addTest(unittest.makeSuite(NormalTestCase)) 114 suite.addTest(unittest.makeSuite(RequestAttributeTestCase))115 134 return suite 116 135
Note: See TracChangeset
for help on using the changeset viewer.