Ticket #153: last-steps-for-ticket153-r6139.diff
| File last-steps-for-ticket153-r6139.diff, 19.4 KB (added by cboos, 4 years ago) |
|---|
-
trac/ticket/api.py
26 26 from trac.util import Ranges 27 27 from trac.util.compat import set, sorted 28 28 from trac.util.datefmt import utc 29 from trac.util.text import shorten_line 29 from trac.util.text import shorten_line, obfuscate_email_address 30 30 from trac.util.translation import _ 31 31 from trac.wiki import IWikiSyntaxProvider, WikiParser 32 32 -
trac/ticket/web_ui.py
14 14 # 15 15 # Author: Jonas Borgström <jonas@edgewall.com> 16 16 17 import csv 17 18 from datetime import datetime 18 19 import os 19 20 import pkg_resources … … 31 32 from trac.resource import Resource, get_resource_url, \ 32 33 render_resource_link, get_resource_shortname 33 34 from trac.search import ISearchSource, search_to_sql, shorten_result 34 from trac.ticket import Milestone, Ticket, TicketSystem, ITicketManipulator 35 from trac.ticket import ITicketActionController 35 from trac.ticket.api import TicketSystem, ITicketManipulator, \ 36 ITicketActionController 37 from trac.ticket.model import Milestone, Ticket 36 38 from trac.ticket.notification import TicketNotifyEmail 37 39 from trac.timeline.api import ITimelineEventProvider, TimelineEvent 38 40 from trac.util import get_reporter_id … … 52 54 title = "Invalid Ticket" 53 55 54 56 55 def cc_list(cc_field):56 """Split a CC: value in a list of addresses.57 58 TODO: will become `CcField.cc_list(value)59 """60 if not cc_field:61 return []62 return [cc.strip() for cc in cc_field.split(',') if cc]63 64 65 57 class TicketModule(Component): 66 58 67 59 implements(IContentConverter, INavigationContributor, IRequestHandler, … … 111 103 112 104 def convert_content(self, req, mimetype, ticket, key): 113 105 if key == 'csv': 114 return self.export_csv( ticket, mimetype='text/csv')106 return self.export_csv(req, ticket, mimetype='text/csv') 115 107 elif key == 'tab': 116 return self.export_csv( ticket, sep='\t',108 return self.export_csv(req, ticket, sep='\t', 117 109 mimetype='text/tab-separated-values') 118 110 elif key == 'rss': 119 111 return self.export_rss(req, ticket) … … 705 697 706 698 return 'diff_view.html', data, None 707 699 708 def export_csv(self, ticket, sep=',', mimetype='text/plain'): 700 def export_csv(self, req, ticket, sep=',', mimetype='text/plain'): 701 # FIXME: consider dumping history of changes here as well 702 # as one row of output doesn't seem to be terribly useful... 709 703 content = StringIO() 710 content.write(sep.join(['id'] + [f['name'] for f in 711 ticket.fields]) 712 + CRLF) 713 content.write(sep.join([unicode(ticket.id)] + 714 [ticket.values.get(f['name'], '') 715 .replace(sep, '_').replace('\\', '\\\\') 716 .replace('\n', '\\n').replace('\r', '\\r') 717 for f in ticket.fields]) + CRLF) 704 writer = csv.writer(content, delimiter=sep, quoting=csv.QUOTE_MINIMAL) 705 writer.writerow(['id'] + [unicode(f['name']) for f in ticket.fields]) 706 707 context = Context.from_request(req, ticket.resource) 708 cols = [unicode(ticket.id)] 709 for f in ticket.fields: 710 name = f['name'] 711 value = ticket.values.get(name, '') 712 if name in ('cc', 'reporter'): 713 value = Chrome(self.env).format_emails(context, value, ' ') 714 cols.append(value.encode('utf-8')) 715 writer.writerow(cols) 718 716 return (content.getvalue(), '%s;charset=utf-8' % mimetype) 719 717 720 718 def export_rss(self, req, ticket): … … 928 926 ticket[key] = field_changes[key]['new'] 929 927 930 928 def _prepare_fields(self, req, ticket): 929 context = Context.from_request(req, ticket.resource) 931 930 fields = [] 932 931 for field in ticket.fields: 933 932 name = field['name'] … … 963 962 field['options'] = [opt for opt in field['options'] if not 964 963 Milestone(self.env, opt).is_completed] 965 964 milestone = Resource('milestone', ticket[name]) 966 context = Context.from_request(req, ticket.resource)967 965 field['rendered'] = render_resource_link(self.env, context, 968 966 milestone, 'compact') 969 967 elif name == 'cc': 970 all_cc = cc_list(ticket[name]) 971 if not (Chrome(self.env).show_email_addresses or \ 972 'EMAIL_VIEW' in req.perm(ticket.resource)): 973 all_cc = [obfuscate_email_address(cc) for cc in all_cc] 974 field['rendered'] = ', '.join(all_cc) 975 968 emails = Chrome(self.env).format_emails(context, ticket[name]) 969 field['rendered'] = emails 976 970 # ensure sane defaults 977 971 field.setdefault('optional', False) 978 972 field.setdefault('options', []) … … 1148 1142 old_list, new_list = None, None 1149 1143 sep = ', ' 1150 1144 if field == 'cc': 1151 old_list, new_list = cc_list(old), cc_list(new) 1145 chrome = Chrome(self.env) 1146 old_list, new_list = chrome.cc_list(old), chrome.cc_list(new) 1152 1147 if not (Chrome(self.env).show_email_addresses or 1153 1148 'EMAIL_VIEW' in req.perm(ticket.resource)): 1154 1149 old_list = [obfuscate_email_address(cc) -
trac/ticket/report.py
25 25 26 26 from trac.core import * 27 27 from trac.db import get_column_names 28 from trac.mimeview import Context 28 29 from trac.perm import IPermissionRequestor 29 30 from trac.resource import Resource, ResourceNotFound 30 31 from trac.util import sorted … … 264 265 if id > 0: 265 266 title = '{%i} %s' % (id, title) 266 267 268 report_resource = Resource('report', id) 269 context = Context.from_request(req, report_resource) 267 270 data = {'action': 'view', 'title': title, 268 'report': Resource('report', id), 271 'report': {'id': id, 'resource': report_resource}, 272 'context': context, 269 273 'title': title, 'description': description, 270 274 'args': args, 'message': None} 271 275 try: 272 276 cols, results = self.execute_report(req, db, id, sql, args) 277 results = [list(row) for row in results] 273 278 except Exception, e: 274 279 data['message'] = _('Report execution failed: %(error)s', 275 280 error=to_unicode(e)) … … 324 329 cell_groups = [] 325 330 row = {'cell_groups': cell_groups} 326 331 realm = 'ticket' 332 email_cells = [] 327 333 for header_group in header_groups: 328 334 cell_group = [] 329 335 for header in header_group: 330 336 value = unicode(result[col_idx]) 337 cell = {'value': value, 'header': header, 'index': col_idx} 338 col = header['col'] 331 339 col_idx += 1 332 cell = {'value': value, 'header': header}333 col = header['col']334 340 # Detect and create new group 335 341 if col == '__group__' and value != prev_group_value: 336 342 prev_group_value = value … … 344 350 row['id'] = value 345 351 # Special casing based on column name 346 352 col = col.strip('_') 347 if col == 'reporter':348 cell['author'] = value353 if col in ('reporter', 'cc'): 354 email_cells.append(cell) 349 355 elif col == 'realm': 350 356 realm = value 351 357 cell_group.append(cell) … … 353 359 resource = Resource(realm, row.get('id')) 354 360 if 'view' not in req.perm(resource): 355 361 continue 362 if email_cells: 363 for cell in email_cells: 364 emails = Chrome(self.env).format_emails(context(resource), 365 cell['value']) 366 result[cell['index']] = cell['value'] = emails 356 367 row['resource'] = resource 357 368 if row_groups: 358 369 row_group = row_groups[-1][1] … … 378 389 self.add_alternate_links(req, args) 379 390 380 391 if format == 'rss': 392 data['context'] = Context.from_request(req, report_resource, 393 absurls=True) 381 394 return 'report.rss', data, 'application/rss+xml' 382 395 elif format == 'csv': 383 396 filename = id and 'report_%s.csv' % id or 'report.csv' -
trac/ticket/query.py
787 787 content = StringIO() 788 788 cols = query.get_columns() 789 789 writer = csv.writer(content, delimiter=sep) 790 writer = csv.writer(content, delimiter=sep, quoting=csv.QUOTE_MINIMAL) 790 791 writer.writerow([unicode(c).encode('utf-8') for c in cols]) 791 792 793 context = Context.from_request(req) 792 794 results = query.execute(req, self.env.get_db_cnx()) 793 795 for result in results: 794 if 'TICKET_VIEW' in req.perm('ticket', result['id']): 795 writer.writerow([unicode(result[col]).encode('utf-8') 796 for col in cols]) 796 ticket = Resource('ticket', result['id']) 797 if 'TICKET_VIEW' in req.perm(ticket): 798 values = [] 799 for col in cols: 800 value = result[col] 801 if col in ('cc', 'reporter'): 802 value = Chrome(self.env).format_emails(context(ticket), 803 value) 804 values.append(unicode(value).encode('utf-8')) 805 writer.writerow(values) 797 806 return (content.getvalue(), '%s;charset=utf-8' % mimetype) 798 807 799 808 def export_rss(self, req, query): … … 806 815 or None), 807 816 row=query.rows, 808 817 **query.constraints) 809 810 818 data = { 811 819 'context': Context.from_request(req, 'query', absurls=True), 812 820 'results': results, -
trac/ticket/templates/report.rss
30 30 <title>#$row.id: $cell.value</title> 31 31 </py:when> 32 32 <py:when test="col == 'description'"> 33 <description>${unicode(wiki_to_html( row.context, cell.value, href=abs_href))}</description>33 <description>${unicode(wiki_to_html(context(row.resource), cell.value))}</description> 34 34 </py:when> 35 35 </py:choose> 36 36 </py:with> -
trac/ticket/templates/report_view.html
27 27 </h1> 28 28 29 29 <div py:if="description" id="description" xml:space="preserve"> 30 ${wiki_to_html(context (report), description)}30 ${wiki_to_html(context, description)} 31 31 </div> 32 32 33 33 <div py:if="report.id != -1" class="buttons"> 34 <form py:if="'REPORT_MODIFY' in perm(report )" action="" method="get">34 <form py:if="'REPORT_MODIFY' in perm(report.resource)" action="" method="get"> 35 35 <div> 36 36 <input type="hidden" name="action" value="edit" /> 37 37 <input type="submit" value="Edit report" accesskey="e" /> 38 38 </div> 39 39 </form> 40 <form py:if="'REPORT_CREATE' in perm(report )" action="" method="get">40 <form py:if="'REPORT_CREATE' in perm(report.resource)" action="" method="get"> 41 41 <div> 42 42 <input type="hidden" name="action" value="copy" /> 43 43 <input type="submit" value="Copy report" /> 44 44 </div> 45 45 </form> 46 <form py:if="'REPORT_DELETE' in perm(report )" action="" method="get">46 <form py:if="'REPORT_DELETE' in perm(report.resource)" action="" method="get"> 47 47 <div> 48 48 <input type="hidden" name="action" value="delete" /> 49 49 <input type="submit" value="Delete report" /> … … 156 156 </table> 157 157 </py:for> 158 158 159 <div py:if="report.id == -1 and 'REPORT_CREATE' in perm(report )" class="buttons">159 <div py:if="report.id == -1 and 'REPORT_CREATE' in perm(report.resource)" class="buttons"> 160 160 <form action="" method="get"> 161 161 <div> 162 162 <input type="hidden" name="action" value="new" /> -
trac/ticket/templates/query_results.html
53 53 <a py:when="name == 'summary'" href="$result.href" title="View ticket">$value</a> 54 54 <span py:when="isinstance(value, datetime)">${format_datetime(value)}</span> 55 55 <span py:when="name == 'reporter'">${authorinfo(value)}</span> 56 <span py:when="name == 'cc'">${format_emails(ticket_context, value)}</span> 56 57 <span py:when="name == 'owner' and value">${authorinfo(value)}</span> 57 58 <span py:otherwise="">$value</span> 58 59 </td> -
trac/wiki/tests/wiki-tests.txt
306 306 <i>RFCs von <a class="ext-link" href="ftp://ftp.rfc-editor.org/in-notes/rfcXXXX.txt"><span class="icon">ftp://ftp.rfc-editor.org/in-notes/rfcXXXX.txt</span></a></i> 307 307 </p> 308 308 ------------------------------ 309 ============================== mailto: links 310 Author: mailto:cboos@neuf.fr, 311 i.e. [mailto:cboos@neuf.fr me] 312 ------------------------------ 313 <p> 314 Author: <a class="mail-link" href="mailto:cboos@neuf.fr"><span class="icon">mailto:cboos@neuf.fr</span></a>, 315 i.e. <a class="mail-link" href="mailto:cboos@neuf.fr"><span class="icon">me</span></a> 316 </p> 317 ------------------------------ 318 ============================== Arbitrary protocol Link 319 ''RFCs von ftp://ftp.rfc-editor.org/in-notes/rfcXXXX.txt'' 320 ------------------------------ 321 <p> 322 <i>RFCs von <a class="ext-link" href="ftp://ftp.rfc-editor.org/in-notes/rfcXXXX.txt"><span class="icon">ftp://ftp.rfc-editor.org/in-notes/rfcXXXX.txt</span></a></i> 323 </p> 324 ------------------------------ 309 325 ============================== Generic InterTrac links 310 326 th:roadmap 311 327 th:roadmap: … … 724 740 * item 2 725 741 item 2 line 2 726 742 Paragraph 727 ============================== Changelog sample 743 ============================== Changelog sample (and e-mail link) 728 744 2003-09-18 23:26 Joe Bar <joeb@gloogle.gom> 729 745 730 746 * src/code.py: Fix problem with obsolete use of … … 735 751 Paragraph 736 752 ------------------------------ 737 753 <p> 738 2003-09-18 23:26 Joe Bar < joeb@gloogle.gom>754 2003-09-18 23:26 Joe Bar <<a class="mail-link" href="mailto:joeb@gloogle.gom"><span class="icon">joeb@gloogle.gom</span></a>> 739 755 </p> 740 756 <ul><li>src/code.py: Fix problem with obsolete use of 741 757 backslash in symbols. -
trac/wiki/parser.py
72 72 # Rules provided by IWikiSyntaxProviders will be inserted here 73 73 74 74 _post_rules = [ 75 # e-mails 76 r"(?P<email>\w[\w.]+@\w[\w.]+\w)", 75 77 # > ... 76 78 r"(?P<citation>^(?P<cdepth>>(?: *>)*))", 77 79 # &, < and > to &, < and > -
trac/wiki/formatter.py
284 284 285 285 # -- Post- IWikiSyntaxProvider rules 286 286 287 # E-mails 288 289 def _email_formatter(self, match, fullmatch): 290 from trac.web.chrome import Chrome 291 omatch = Chrome(self.env).format_emails(self.context, match) 292 if omatch == match: # not obfuscated, make a link 293 return self._make_mail_link('mailto:'+match, match) 294 else: 295 return omatch 296 287 297 # HTML escape of &, < and > 288 298 289 299 def _htmlescape_formatter(self, match, fullmatch): … … 341 351 elif target.startswith('//'): 342 352 return self._make_ext_link(ns+':'+target, label) 343 353 elif ns == "mailto": 344 return self._make_mail_link('mailto:'+target, label) 354 from trac.web.chrome import Chrome 355 otarget = Chrome(self.env).format_emails(self.context, target) 356 olabel = Chrome(self.env).format_emails(self.context, label) 357 if (otarget, olabel) == (target, label): 358 return self._make_mail_link('mailto:'+target, label) 359 else: 360 return olabel or otarget 345 361 else: 346 362 return self._make_intertrac_link(ns, target, label) or \ 347 363 self._make_interwiki_link(ns, target, label) or \ -
trac/web/chrome.py
535 535 'authname': req and req.authname or '<trac>', 536 536 'show_email_addresses': show_email_addresses, 537 537 'format_author': partial(self.format_author, req), 538 'format_emails': self.format_emails, 538 539 539 540 # Date/time formatting 540 541 'dateinfo': dateinfo, … … 624 625 req.chrome['scripts'] = scripts 625 626 raise 626 627 628 # E-mail formatting utilities 629 630 def cc_list(self, cc_field): 631 """Split a CC: value in a list of addresses.""" 632 if not cc_field: 633 return [] 634 return [cc.strip() for cc in cc_field.split(',') if cc] 635 636 def format_emails(self, context, value, sep=', '): 637 """Normalize a list of e-mails and obfuscate them if needed. 638 639 :param context: the context in which the check for obfuscation should 640 be done 641 :param value: a string containing a comma-separated list of e-mails 642 :param sep: the separator to use when rendering the list again 643 """ 644 all_cc = self.cc_list(value) 645 if not (self.show_email_addresses or 'EMAIL_VIEW' in context.perm): 646 all_cc = [obfuscate_email_address(cc) for cc in all_cc] 647 return sep.join(all_cc) 648 627 649 def format_author(self, req, author): 628 650 if self.show_email_addresses or not req or 'EMAIL_VIEW' in req.perm: 629 651 return author
