Modified: bloodhound/vendor/trac/current/trac/ticket/roadmap.py URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/ticket/roadmap.py?rev=1639602&r1=1639601&r2=1639602&view=diff ============================================================================== --- bloodhound/vendor/trac/current/trac/ticket/roadmap.py (original) +++ bloodhound/vendor/trac/current/trac/ticket/roadmap.py Fri Nov 14 11:06:23 2014 @@ -350,6 +350,13 @@ def grouped_stats_data(env, stats_provid group_names = field['options'] if field.get('optional'): group_names.insert(0, '') + elif field.get('custom'): + group_names = [name for name, in env.db_query(""" + SELECT DISTINCT COALESCE(c.value, '') FROM ticket_custom c + WHERE c.name=%s ORDER BY COALESCE(c.value, '') + """, (by, ))] + if '' not in group_names: + group_names.insert(0, '') else: group_names = [name for name, in env.db_query(""" SELECT DISTINCT COALESCE(%s, '') FROM ticket @@ -854,6 +861,9 @@ class MilestoneModule(Component): 'TICKET_ADMIN' in req.perm) else: req.perm(milestone.resource).require('MILESTONE_CREATE') + if milestone.name: + add_notice(req, _("Milestone %(name)s does not exist. You can" + " create it here.", name=milestone.name)) chrome = Chrome(self.env) chrome.add_jquery_ui(req) @@ -1027,7 +1037,7 @@ class MilestoneModule(Component): milestone_realm = Resource('milestone') for name, due, completed, description \ in MilestoneCache(self.env).milestones.itervalues(): - if any(r.search(description) or r.search(name) + if all(r.search(description) or r.search(name) for r in term_regexps): milestone = milestone_realm(id=name) if 'MILESTONE_VIEW' in req.perm(milestone):
Modified: bloodhound/vendor/trac/current/trac/ticket/templates/batch_modify.html URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/ticket/templates/batch_modify.html?rev=1639602&r1=1639601&r2=1639602&view=diff ============================================================================== --- bloodhound/vendor/trac/current/trac/ticket/templates/batch_modify.html (original) +++ bloodhound/vendor/trac/current/trac/ticket/templates/batch_modify.html Fri Nov 14 11:06:23 2014 @@ -1,3 +1,13 @@ +<!--! Copyright (C) 2012-2014 Edgewall Software + + This software is licensed as described in the file COPYING, which + you should have received as part of this distribution. The terms + are also available at http://trac.edgewall.com/license.html. + + This software consists of voluntary contributions made by many + individuals. For the exact contribution history, see the revision + history and logs, available at http://trac.edgewall.org/. +--> <form xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://genshi.edgewall.org/" xmlns:i18n="http://genshi.edgewall.org/i18n" @@ -12,7 +22,7 @@ <label for="batchmod_value_comment">Comment:</label> </th> <td class="fullrow"><textarea - id="batchmod_value_comment" name="batchmod_value_comment" cols="70" rows="5"/> + id="batchmod_value_comment" name="batchmod_value_comment" class="trac-fullwidth" cols="70" rows="5"/> </td> </tr> Modified: bloodhound/vendor/trac/current/trac/ticket/templates/milestone_delete.html URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/ticket/templates/milestone_delete.html?rev=1639602&r1=1639601&r2=1639602&view=diff ============================================================================== --- bloodhound/vendor/trac/current/trac/ticket/templates/milestone_delete.html (original) +++ bloodhound/vendor/trac/current/trac/ticket/templates/milestone_delete.html Fri Nov 14 11:06:23 2014 @@ -1,3 +1,13 @@ +<!--! Copyright (C) 2006-2014 Edgewall Software + + This software is licensed as described in the file COPYING, which + you should have received as part of this distribution. The terms + are also available at http://trac.edgewall.com/license.html. + + This software consists of voluntary contributions made by many + individuals. For the exact contribution history, see the revision + history and logs, available at http://trac.edgewall.org/. +--> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> Modified: bloodhound/vendor/trac/current/trac/ticket/templates/milestone_edit.html URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/ticket/templates/milestone_edit.html?rev=1639602&r1=1639601&r2=1639602&view=diff ============================================================================== --- bloodhound/vendor/trac/current/trac/ticket/templates/milestone_edit.html (original) +++ bloodhound/vendor/trac/current/trac/ticket/templates/milestone_edit.html Fri Nov 14 11:06:23 2014 @@ -1,3 +1,13 @@ +<!--! Copyright (C) 2006-2014 Edgewall Software + + This software is licensed as described in the file COPYING, which + you should have received as part of this distribution. The terms + are also available at http://trac.edgewall.com/license.html. + + This software consists of voluntary contributions made by many + individuals. For the exact contribution history, see the revision + history and logs, available at http://trac.edgewall.org/. +--> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> @@ -103,9 +113,10 @@ </fieldset> <div class="field"> <fieldset> - <label for="description" i18n:msg="">Description (you may use <a tabindex="42" - href="${href.wiki('WikiFormatting')}">WikiFormatting</a> here):</label> - <p><textarea id="description" name="description" class="wikitext trac-resizable" rows="10" cols="78"> + <label for="description" i18n:msg=""> + Description: (you may use <a tabindex="42" href="${href.wiki('WikiFormatting')}">WikiFormatting</a> here) + </label> + <p><textarea id="description" name="description" class="wikitext trac-fullwidth trac-resizable" rows="10" cols="78"> ${milestone.description}</textarea></p> </fieldset> </div> Modified: bloodhound/vendor/trac/current/trac/ticket/templates/milestone_view.html URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/ticket/templates/milestone_view.html?rev=1639602&r1=1639601&r2=1639602&view=diff ============================================================================== --- bloodhound/vendor/trac/current/trac/ticket/templates/milestone_view.html (original) +++ bloodhound/vendor/trac/current/trac/ticket/templates/milestone_view.html Fri Nov 14 11:06:23 2014 @@ -1,3 +1,13 @@ +<!--! Copyright (C) 2006-2014 Edgewall Software + + This software is licensed as described in the file COPYING, which + you should have received as part of this distribution. The terms + are also available at http://trac.edgewall.com/license.html. + + This software consists of voluntary contributions made by many + individuals. For the exact contribution history, see the revision + history and logs, available at http://trac.edgewall.org/. +--> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> Modified: bloodhound/vendor/trac/current/trac/ticket/templates/query.html URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/ticket/templates/query.html?rev=1639602&r1=1639601&r2=1639602&view=diff ============================================================================== --- bloodhound/vendor/trac/current/trac/ticket/templates/query.html (original) +++ bloodhound/vendor/trac/current/trac/ticket/templates/query.html Fri Nov 14 11:06:23 2014 @@ -1,3 +1,13 @@ +<!--! Copyright (C) 2006-2014 Edgewall Software + + This software is licensed as described in the file COPYING, which + you should have received as part of this distribution. The terms + are also available at http://trac.edgewall.com/license.html. + + This software consists of voluntary contributions made by many + individuals. For the exact contribution history, see the revision + history and logs, available at http://trac.edgewall.org/. +--> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> Modified: bloodhound/vendor/trac/current/trac/ticket/templates/query_results.html URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/ticket/templates/query_results.html?rev=1639602&r1=1639601&r2=1639602&view=diff ============================================================================== --- bloodhound/vendor/trac/current/trac/ticket/templates/query_results.html (original) +++ bloodhound/vendor/trac/current/trac/ticket/templates/query_results.html Fri Nov 14 11:06:23 2014 @@ -1,17 +1,26 @@ -<!--! - groups - a dict, where: - key - is the value shared by all results in this group - value - is the list of corresponding tickets - - headers - a sequence of header structure: - .name - field name for this header - .label - what to display for this header +<!--! Copyright (C) 2006-2014 Edgewall Software - fields - dict of field name to field structure: - .label - field label + This software is licensed as described in the file COPYING, which + you should have received as part of this distribution. The terms + are also available at http://trac.edgewall.com/license.html. - query - the actual Query instance used to perform the query + This software consists of voluntary contributions made by many + individuals. For the exact contribution history, see the revision + history and logs, available at http://trac.edgewall.org/. +Arguments: + - groups - a dict, where: + key - is the value shared by all results in this group + value - is the list of corresponding tickets + + - headers - a sequence of header structure: + .name - field name for this header + .label - what to display for this header + + - fields - dict of field name to field structure: + .label - field label + + - query - the actual Query instance used to perform the query --> <div xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://genshi.edgewall.org/" Modified: bloodhound/vendor/trac/current/trac/ticket/templates/report_delete.html URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/ticket/templates/report_delete.html?rev=1639602&r1=1639601&r2=1639602&view=diff ============================================================================== --- bloodhound/vendor/trac/current/trac/ticket/templates/report_delete.html (original) +++ bloodhound/vendor/trac/current/trac/ticket/templates/report_delete.html Fri Nov 14 11:06:23 2014 @@ -1,3 +1,13 @@ +<!--! Copyright (C) 2006-2014 Edgewall Software + + This software is licensed as described in the file COPYING, which + you should have received as part of this distribution. The terms + are also available at http://trac.edgewall.com/license.html. + + This software consists of voluntary contributions made by many + individuals. For the exact contribution history, see the revision + history and logs, available at http://trac.edgewall.org/. +--> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> Modified: bloodhound/vendor/trac/current/trac/ticket/templates/report_edit.html URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/ticket/templates/report_edit.html?rev=1639602&r1=1639601&r2=1639602&view=diff ============================================================================== --- bloodhound/vendor/trac/current/trac/ticket/templates/report_edit.html (original) +++ bloodhound/vendor/trac/current/trac/ticket/templates/report_edit.html Fri Nov 14 11:06:23 2014 @@ -1,3 +1,13 @@ +<!--! Copyright (C) 2006-2014 Edgewall Software + + This software is licensed as described in the file COPYING, which + you should have received as part of this distribution. The terms + are also available at http://trac.edgewall.com/license.html. + + This software consists of voluntary contributions made by many + individuals. For the exact contribution history, see the revision + history and logs, available at http://trac.edgewall.org/. +--> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> @@ -11,38 +21,45 @@ </head> <body> - <div id="content" class="report"> + <div id="content" class="report edit" + py:with="new_report = action == 'new'"> - <h1>${_('New Report') if action == 'new' else report.title}</h1> - <form action="${href.report(report.id)}" method="post" id="edit_report"> - <div> + <h1 py:choose=""> + <py:when test="new_report">New Report</py:when> + <py:otherwise>$report.title</py:otherwise> + </h1> + <form id="edit_report" method="post" action="${href.report(report.id)}"> + <fieldset> + <legend py:choose=""> + <py:when test="new_report">Create Report:</py:when> + <py:otherwise>Modify Report:</py:otherwise> + </legend> <input type="hidden" name="action" value="$action" /> <div class="field"> - <label for="title">Report Title:</label><br /> - <input type="text" id="title" name="title" class="trac-autofocus" value="$report.title" size="50" /><br /> + <label for="title">Title:</label> + <input type="text" id="title" name="title" class="trac-fullwidth trac-autofocus" value="$report.title" /> </div> <div class="field"> <label for="description" i18n:msg=""> Description: (you may use <a tabindex="42" href="${href.wiki('WikiFormatting')}">WikiFormatting</a> here) </label> - <br /> - <textarea id="description" name="description" class="wikitext trac-resizable" rows="10" cols="78"> + <textarea id="description" name="description" class="wikitext trac-fullwidth trac-resizable" rows="10" cols="78"> $report.description</textarea> </div> <div class="field"> <div class="system-message" py:if="error"> <strong>Error:</strong> $error </div> - <label for="query" i18n:msg="">Query for Report: (can be either SQL or, if starting with <tt>query:</tt>, + <label for="query" i18n:msg="">Query: (can be either SQL or, if starting with <tt>query:</tt>, a <a tabindex="42" href="${href.wiki('TracQuery') + '#QueryLanguage'}">TracQuery</a> expression) - </label><br /> - <textarea id="query" name="query" class="trac-resizable" cols="85" rows="20"> + </label> + <textarea id="query" name="query" class="trac-fullwidth trac-resizable" rows="20" cols="78"> $report.sql</textarea> </div> - <div class="buttons"> - <input type="submit" class="trac-disable-on-submit" value="${_('Save report')}"/> - <input type="submit" name="cancel" value="${_('Cancel')}"/> - </div> + </fieldset> + <div class="buttons"> + <input type="submit" class="trac-disable-on-submit" value="${_('Save report')}" /> + <input type="submit" name="cancel" value="${_('Cancel')}" /> </div> </form> Modified: bloodhound/vendor/trac/current/trac/ticket/templates/report_list.html URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/ticket/templates/report_list.html?rev=1639602&r1=1639601&r2=1639602&view=diff ============================================================================== --- bloodhound/vendor/trac/current/trac/ticket/templates/report_list.html (original) +++ bloodhound/vendor/trac/current/trac/ticket/templates/report_list.html Fri Nov 14 11:06:23 2014 @@ -1,3 +1,13 @@ +<!--! Copyright (C) 2009-2014 Edgewall Software + + This software is licensed as described in the file COPYING, which + you should have received as part of this distribution. The terms + are also available at http://trac.edgewall.com/license.html. + + This software consists of voluntary contributions made by many + individuals. For the exact contribution history, see the revision + history and logs, available at http://trac.edgewall.org/. +--> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> @@ -96,7 +106,7 @@ </a></h3> <span class="foldable" /> <div py:if="description" class="description" xml:space="preserve"> - ${wiki_to_html(context, description)} + ${wiki_to_html(context.child('report', id), description)} </div> </div> </py:when> Modified: bloodhound/vendor/trac/current/trac/ticket/templates/report_view.html URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/ticket/templates/report_view.html?rev=1639602&r1=1639601&r2=1639602&view=diff ============================================================================== --- bloodhound/vendor/trac/current/trac/ticket/templates/report_view.html (original) +++ bloodhound/vendor/trac/current/trac/ticket/templates/report_view.html Fri Nov 14 11:06:23 2014 @@ -1,3 +1,13 @@ +<!--! Copyright (C) 2006-2014 Edgewall Software + + This software is licensed as described in the file COPYING, which + you should have received as part of this distribution. The terms + are also available at http://trac.edgewall.com/license.html. + + This software consists of voluntary contributions made by many + individuals. For the exact contribution history, see the revision + history and logs, available at http://trac.edgewall.org/. +--> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> Modified: bloodhound/vendor/trac/current/trac/ticket/templates/roadmap.html URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/ticket/templates/roadmap.html?rev=1639602&r1=1639601&r2=1639602&view=diff ============================================================================== --- bloodhound/vendor/trac/current/trac/ticket/templates/roadmap.html (original) +++ bloodhound/vendor/trac/current/trac/ticket/templates/roadmap.html Fri Nov 14 11:06:23 2014 @@ -1,3 +1,13 @@ +<!--! Copyright (C) 2006-2014 Edgewall Software + + This software is licensed as described in the file COPYING, which + you should have received as part of this distribution. The terms + are also available at http://trac.edgewall.com/license.html. + + This software consists of voluntary contributions made by many + individuals. For the exact contribution history, see the revision + history and logs, available at http://trac.edgewall.org/. +--> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> @@ -60,7 +70,7 @@ </py:choose> <xi:include href="progress_bar.html" py:if="mstats.stats.count" py:with="stats = mstats.stats; interval_hrefs = mstats.interval_hrefs; - stats_href = mstats.stats_href"/> + stats_href = mstats.stats_href" /> </div> <div class="description" xml:space="preserve"> @@ -71,10 +81,10 @@ </div> <div py:if="'MILESTONE_CREATE' in perm" class="buttons"> - <form id="add" method="get" action="${href.milestone()}"><div> - <input type="hidden" name="action" value="new" /> - <input type="submit" value="${_('Add new milestone')}" /> - </div></form> + <form id="add" method="get" action="${href.milestone()}"><div> + <input type="hidden" name="action" value="new" /> + <input type="submit" value="${_('Add new milestone')}" /> + </div></form> </div> <div id="help" i18n:msg=""><strong>Note:</strong> See Modified: bloodhound/vendor/trac/current/trac/ticket/templates/ticket.html URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/ticket/templates/ticket.html?rev=1639602&r1=1639601&r2=1639602&view=diff ============================================================================== --- bloodhound/vendor/trac/current/trac/ticket/templates/ticket.html (original) +++ bloodhound/vendor/trac/current/trac/ticket/templates/ticket.html Fri Nov 14 11:06:23 2014 @@ -1,3 +1,13 @@ +<!--! Copyright (C) 2007-2014 Edgewall Software + + This software is licensed as described in the file COPYING, which + you should have received as part of this distribution. The terms + are also available at http://trac.edgewall.com/license.html. + + This software consists of voluntary contributions made by many + individuals. For the exact contribution history, see the revision + history and logs, available at http://trac.edgewall.org/. +--> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> @@ -194,7 +204,7 @@ <a tabindex="42" href="${href.wiki('WikiFormatting')}">WikiFormatting</a> here. </label> - <textarea id="comment" name="comment" class="wikitext trac-resizable" rows="10" cols="78"> + <textarea id="comment" name="comment" class="wikitext trac-fullwidth trac-resizable" rows="10" cols="78"> ${comment}</textarea> </fieldset> </div> @@ -211,38 +221,35 @@ ${comment}</textarea> <py:otherwise>Properties</py:otherwise> </legend> <table> + <col class="th" /><col class="td" /><col class="th" /><col class="td" /> <tr py:if="can_modify or can_create"> <th><label for="field-summary">Summary:</label></th> <td class="fullrow" colspan="3"> <input type="text" id="field-summary" name="field_summary" class="${'trac-autofocus' if not ticket.exists and not preview_mode else None}" - value="$ticket.summary" size="70" /> + value="$ticket.summary" /> </td> </tr> - <py:if test="only_for_admin"> - <tr> - <th><label for="field-reporter">Reporter:</label></th> - <td class="fullrow" colspan="3"> - <input type="text" id="field-reporter" name="field_reporter" - value="${ticket.reporter}" size="70" /> - </td> - </tr> - </py:if> - <py:if test="can_edit or can_create"> - <tr> - <th><label for="field-description">Description:</label></th> - <td class="fullrow" colspan="3"> - <fieldset> - <label for="field-description" id="field-description-help" i18n:msg="">You may use - <a tabindex="42" href="${href.wiki('WikiFormatting')}">WikiFormatting</a> here. - </label> - <textarea id="field-description" name="field_description" - class="wikitext trac-resizable" rows="10" cols="68"> + <tr py:if="only_for_admin"> + <th><label for="field-reporter">Reporter:</label></th> + <td class="fullrow" colspan="3"> + <input type="text" id="field-reporter" name="field_reporter" + value="${ticket.reporter}" /> + </td> + </tr> + <tr py:if="can_edit or can_create"> + <th><label for="field-description">Description:</label></th> + <td class="fullrow" colspan="3"> + <fieldset> + <label for="field-description" id="field-description-help" i18n:msg="">You may use + <a tabindex="42" href="${href.wiki('WikiFormatting')}">WikiFormatting</a> here. + </label> + <textarea id="field-description" name="field_description" + class="wikitext trac-fullwidth trac-resizable" rows="10" cols="68"> ${ticket.description}</textarea> - </fieldset> - </td> - </tr> - </py:if> + </fieldset> + </td> + </tr> <tr py:for="row in group(fields, 2, lambda f: f.type != 'textarea')" py:if="can_modify or can_create" py:with="fullrow = len(row) == 1"> Modified: bloodhound/vendor/trac/current/trac/ticket/templates/ticket_box.html URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/ticket/templates/ticket_box.html?rev=1639602&r1=1639601&r2=1639602&view=diff ============================================================================== --- bloodhound/vendor/trac/current/trac/ticket/templates/ticket_box.html (original) +++ bloodhound/vendor/trac/current/trac/ticket/templates/ticket_box.html Fri Nov 14 11:06:23 2014 @@ -1,4 +1,13 @@ -<!--! +<!--! Copyright (C) 2010-2014 Edgewall Software + + This software is licensed as described in the file COPYING, which + you should have received as part of this distribution. The terms + are also available at http://trac.edgewall.com/license.html. + + This software consists of voluntary contributions made by many + individuals. For the exact contribution history, see the revision + history and logs, available at http://trac.edgewall.org/. + Ticket Box (ticket fields along with description). Arguments: Modified: bloodhound/vendor/trac/current/trac/ticket/templates/ticket_change.html URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/ticket/templates/ticket_change.html?rev=1639602&r1=1639601&r2=1639602&view=diff ============================================================================== --- bloodhound/vendor/trac/current/trac/ticket/templates/ticket_change.html (original) +++ bloodhound/vendor/trac/current/trac/ticket/templates/ticket_change.html Fri Nov 14 11:06:23 2014 @@ -1,4 +1,13 @@ -<!--! +<!--! Copyright (C) 2010-2014 Edgewall Software + + This software is licensed as described in the file COPYING, which + you should have received as part of this distribution. The terms + are also available at http://trac.edgewall.com/license.html. + + This software consists of voluntary contributions made by many + individuals. For the exact contribution history, see the revision + history and logs, available at http://trac.edgewall.org/. + Render a ticket comment. Arguments: @@ -106,7 +115,7 @@ Arguments: <form py:if="show_editor" id="trac-comment-editor" method="post" action="${href.ticket(ticket.id) + '#comment:%d' % cnum}"> <div> - <textarea name="edited_comment" class="wikitext trac-resizable" rows="10" cols="78"> + <textarea name="edited_comment" class="wikitext trac-fullwidth trac-resizable" rows="10" cols="78"> ${edited_comment if edited_comment is not None else change.comment}</textarea> <input type="hidden" name="cnum_edit" value="${cnum}"/> </div> Modified: bloodhound/vendor/trac/current/trac/ticket/templates/ticket_preview.html URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/ticket/templates/ticket_preview.html?rev=1639602&r1=1639601&r2=1639602&view=diff ============================================================================== --- bloodhound/vendor/trac/current/trac/ticket/templates/ticket_preview.html (original) +++ bloodhound/vendor/trac/current/trac/ticket/templates/ticket_preview.html Fri Nov 14 11:06:23 2014 @@ -1,4 +1,13 @@ -<!--! +<!--! Copyright (C) 2011-2014 Edgewall Software + + This software is licensed as described in the file COPYING, which + you should have received as part of this distribution. The terms + are also available at http://trac.edgewall.com/license.html. + + This software consists of voluntary contributions made by many + individuals. For the exact contribution history, see the revision + history and logs, available at http://trac.edgewall.org/. + Render data relevant to automatic ticket preview. --> <html xmlns="http://www.w3.org/1999/xhtml" Modified: bloodhound/vendor/trac/current/trac/ticket/tests/api.py URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/ticket/tests/api.py?rev=1639602&r1=1639601&r2=1639602&view=diff ============================================================================== --- bloodhound/vendor/trac/current/trac/ticket/tests/api.py (original) +++ bloodhound/vendor/trac/current/trac/ticket/tests/api.py Fri Nov 14 11:06:23 2014 @@ -12,9 +12,9 @@ # history and logs, available at http://trac.edgewall.org/log/. from trac.perm import PermissionCache, PermissionSystem +from trac.test import EnvironmentStub, Mock from trac.ticket.api import TicketSystem from trac.ticket.model import Ticket -from trac.test import EnvironmentStub, Mock import unittest Modified: bloodhound/vendor/trac/current/trac/ticket/tests/conversion.py URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/ticket/tests/conversion.py?rev=1639602&r1=1639601&r2=1639602&view=diff ============================================================================== --- bloodhound/vendor/trac/current/trac/ticket/tests/conversion.py (original) +++ bloodhound/vendor/trac/current/trac/ticket/tests/conversion.py Fri Nov 14 11:06:23 2014 @@ -15,10 +15,10 @@ import os import unittest from trac import __version__ as TRAC_VERSION -from trac.test import EnvironmentStub, Mock +from trac.mimeview.api import Mimeview +from trac.test import EnvironmentStub, Mock, MockPerm from trac.ticket.model import Ticket from trac.ticket.web_ui import TicketModule -from trac.mimeview.api import Mimeview from trac.web.href import Href @@ -50,6 +50,16 @@ class TicketConversionTestCase(unittest. ticket.insert() return ticket + def _create_a_ticket_with_email(self): + ticket = Ticket(self.env) + ticket['owner'] = 'j...@example.org' + ticket['reporter'] = 'sa...@example.org' + ticket['cc'] = 'cc1, c...@example.org' + ticket['summary'] = 'Foo' + ticket['description'] = 'Bar' + ticket.insert() + return ticket + def test_conversions(self): conversions = self.mimeview.get_supported_conversions( 'trac.ticket.Ticket') @@ -74,6 +84,26 @@ class TicketConversionTestCase(unittest. 'keywords,cc\r\n1,Foo,santa,,Bar,,,\r\n', 'text/csv;charset=utf-8', 'csv'), csv) + def test_csv_conversion_with_obfuscation(self): + ticket = self._create_a_ticket_with_email() + csv = self.mimeview.convert_content(self.req, 'trac.ticket.Ticket', + ticket, 'csv') + self.assertEqual( + ('\xef\xbb\xbf' + 'id,summary,reporter,owner,description,status,keywords,cc\r\n' + '1,Foo,santa@â¦,joe@â¦,Bar,,,cc1 cc2@â¦\r\n', + 'text/csv;charset=utf-8', 'csv'), + csv) + self.req.perm = MockPerm() + csv = self.mimeview.convert_content(self.req, 'trac.ticket.Ticket', + ticket, 'csv') + self.assertEqual( + ('\xef\xbb\xbf' + 'id,summary,reporter,owner,description,status,keywords,cc\r\n' + '1,Foo,sa...@example.org,j...@example.org,Bar,,,' + 'cc1 c...@example.org\r\n', + 'text/csv;charset=utf-8', 'csv'), + csv) def test_tab_conversion(self): ticket = self._create_a_ticket() @@ -85,6 +115,29 @@ class TicketConversionTestCase(unittest. 'text/tab-separated-values;charset=utf-8', 'tsv'), csv) + def test_tab_conversion_with_obfuscation(self): + ticket = self._create_a_ticket_with_email() + csv = self.mimeview.convert_content(self.req, 'trac.ticket.Ticket', + ticket, 'tab') + self.assertEqual( + ('\xef\xbb\xbf' + 'id\tsummary\treporter\towner\tdescription\tstatus\tkeywords\t' + 'cc\r\n' + '1\tFoo\tsanta@â¦\tjoe@â¦\tBar\t\t\tcc1 cc2@â¦\r\n', + 'text/tab-separated-values;charset=utf-8', 'tsv'), + csv) + self.req.perm = MockPerm() + csv = self.mimeview.convert_content(self.req, 'trac.ticket.Ticket', + ticket, 'tab') + self.assertEqual( + ('\xef\xbb\xbf' + 'id\tsummary\treporter\towner\tdescription\tstatus\tkeywords\t' + 'cc\r\n' + '1\tFoo\tsa...@example.org\t...@example.org\tBar\t\t\t' + 'cc1 c...@example.org\r\n', + 'text/tab-separated-values;charset=utf-8', 'tsv'), + csv) + def test_rss_conversion(self): ticket = self._create_a_ticket() content, mimetype, ext = self.mimeview.convert_content( Modified: bloodhound/vendor/trac/current/trac/ticket/tests/functional.py URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/ticket/tests/functional.py?rev=1639602&r1=1639601&r2=1639602&view=diff ============================================================================== --- bloodhound/vendor/trac/current/trac/ticket/tests/functional.py (original) +++ bloodhound/vendor/trac/current/trac/ticket/tests/functional.py Fri Nov 14 11:06:23 2014 @@ -20,8 +20,8 @@ from datetime import datetime, timedelta from trac.admin.tests.functional import AuthorizationTestCaseSetup from trac.test import locale_en from trac.tests.functional import * -from trac.util.datefmt import utc, localtz, format_date, format_datetime, \ - pretty_timedelta +from trac.util import create_file +from trac.util.datefmt import utc, localtz, format_date, format_datetime from trac.util.text import to_utf8 try: @@ -116,6 +116,53 @@ class TestTicketNoSummary(FunctionalTwil tc.find('ticket not yet created') +class TestTicketManipulator(FunctionalTwillTestCaseSetup): + def runTest(self): + plugin_name = self.__class__.__name__ + env = self._testenv.get_trac_environment() + env.config.set('components', plugin_name + '.*', 'enabled') + env.config.save() + create_file(os.path.join(env.path, 'plugins', plugin_name + '.py'), +"""\ +from genshi.builder import tag +from trac.core import Component, implements +from trac.ticket.api import ITicketManipulator +from trac.util.translation import tag_ + + +class TicketManipulator(Component): + implements(ITicketManipulator) + + def prepare_ticket(self, req, ticket, fields, actions): + pass + + def validate_ticket(self, req, ticket): + field = 'reporter' + yield None, tag_("A ticket with the summary %(summary)s" + " already exists.", + summary=tag.em("Testing ticket manipulator")) + yield field, tag_("The ticket %(field)s is %(status)s.", + field=tag.strong(field), + status=tag.em("invalid")) +""") + self._testenv.restart() + + try: + self._tester.go_to_front() + tc.follow("New Ticket") + tc.formvalue('propertyform', 'field-description', + "Testing ticket manipulator") + tc.submit('submit') + tc.url(self._tester.url + '/newticket$') + tc.find("A ticket with the summary <em>Testing ticket " + "manipulator</em> already exists.") + tc.find("The ticket field 'reporter' is invalid: The" + " ticket <strong>reporter</strong> is <em>invalid</em>.") + finally: + env.config.set('components', plugin_name + '.*', 'disabled') + env.config.save() + + class TestTicketAltFormats(FunctionalTestCaseSetup): def runTest(self): """Download ticket in alternative formats""" @@ -1811,9 +1858,8 @@ class RegressionTestTicket6048(Functiona def runTest(self): """Test for regression of http://trac.edgewall.org/ticket/6048""" # Setup the DeleteTicket plugin - plugin = open(os.path.join(self._testenv.command_cwd, - 'sample-plugins', 'workflow', - 'DeleteTicket.py')).read() + plugin = open(os.path.join(self._testenv.trac_src, 'sample-plugins', + 'workflow', 'DeleteTicket.py')).read() open(os.path.join(self._testenv.tracdir, 'plugins', 'DeleteTicket.py'), 'w').write(plugin) env = self._testenv.get_trac_environment() @@ -2025,7 +2071,7 @@ class RegressionTestTicket8247(Functiona tc.find('<strong class="trac-field-milestone">Milestone</strong>' '[ \n\t]*<em>%s</em> deleted' % name) tc.find('Changed <a.* ago</a> by admin') - tc.notfind('anonymous') + tc.notfind('</a> ago by anonymous') class RegressionTestTicket8861(FunctionalTwillTestCaseSetup): @@ -2108,9 +2154,9 @@ class RegressionTestTicket11028(Function ('ROADMAP_VIEW', 'MILESTONE_VIEW')) -class RegressionTestTicket11153(FunctionalTwillTestCaseSetup): +class RegressionTestTicket11152(FunctionalTwillTestCaseSetup): def runTest(self): - """Test for regression of http://trac.edgewall.org/ticket/11153""" + """Test for regression of http://trac.edgewall.org/ticket/11152""" # Check that "View Tickets" mainnav entry links to the report page self._tester.go_to_view_tickets() @@ -2143,6 +2189,19 @@ class RegressionTestTicket11153(Function env.config.remove('components', 'trac.ticket.report.ReportModule') env.config.save() + # Disable the QueryModule component and check that "View Tickets" + # mainnav entry links to the `/report` page + env.config.set('components', 'trac.ticket.query.QueryModule', + 'disabled') + env.config.save() + + try: + self._tester.go_to_view_tickets('report') + tc.notfind('<li class="last first">Available Reports</li>') + finally: + env.config.remove('components', 'trac.ticket.query.QueryModule') + env.config.save() + class RegressionTestTicket11176(FunctionalTestCaseSetup): def runTest(self): @@ -2197,6 +2256,40 @@ class RegressionTestTicket11176(Function self._testenv.disable_authz_permpolicy() +class RegressionTestTicket11590(FunctionalTwillTestCaseSetup): + def runTest(self): + """Test for regression of http://trac.edgewall.org/ticket/11590""" + report_id = self._tester.create_report('#11590', 'SELECT 1', + '[./ this report]') + self._tester.go_to_view_tickets() + tc.notfind(internal_error) + tc.find('<a class="report" href="[^>"]*?/report/%s">this report</a>' % + report_id) + + +class RegressionTestTicket11618(FunctionalTwillTestCaseSetup): + def runTest(self): + """Test for regression of http://trac.edgewall.org/ticket/11618 + fix for malformed `readonly="True"` attribute in milestone admin page + """ + name = "11618Milestone" + self._tester.create_milestone(name) + try: + self._testenv.grant_perm('user', 'TICKET_ADMIN') + self._tester.go_to_front() + self._tester.logout() + self._tester.login('user') + tc.go(self._tester.url + "/admin/ticket/milestones/" + name) + tc.notfind('No administration panels available') + tc.find(' readonly="readonly"') + tc.notfind(' readonly="True"') + finally: + self._testenv.revoke_perm('user', 'TICKET_ADMIN') + self._tester.go_to_front() + self._tester.logout() + self._tester.login('admin') + + def functionalSuite(suite=None): if not suite: import trac.tests.functional @@ -2206,6 +2299,7 @@ def functionalSuite(suite=None): suite.addTest(TestTicketAddAttachment()) suite.addTest(TestTicketPreview()) suite.addTest(TestTicketNoSummary()) + suite.addTest(TestTicketManipulator()) suite.addTest(TestTicketAltFormats()) suite.addTest(TestTicketCSVFormat()) suite.addTest(TestTicketTabFormat()) @@ -2313,7 +2407,9 @@ def functionalSuite(suite=None): suite.addTest(RegressionTestTicket9084()) suite.addTest(RegressionTestTicket9981()) suite.addTest(RegressionTestTicket11028()) - suite.addTest(RegressionTestTicket11153()) + suite.addTest(RegressionTestTicket11152()) + suite.addTest(RegressionTestTicket11590()) + suite.addTest(RegressionTestTicket11618()) if ConfigObj: suite.addTest(RegressionTestTicket11176()) else: Modified: bloodhound/vendor/trac/current/trac/ticket/tests/model.py URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/ticket/tests/model.py?rev=1639602&r1=1639601&r2=1639602&view=diff ============================================================================== --- bloodhound/vendor/trac/current/trac/ticket/tests/model.py (original) +++ bloodhound/vendor/trac/current/trac/ticket/tests/model.py Fri Nov 14 11:06:23 2014 @@ -19,18 +19,19 @@ import tempfile import shutil import unittest +import trac.tests.compat from trac import core from trac.attachment import Attachment from trac.core import TracError, implements from trac.resource import ResourceNotFound -from trac.tests import compat +from trac.test import EnvironmentStub from trac.ticket.model import ( Ticket, Component, Milestone, Priority, Type, Version ) +from trac.ticket.roadmap import MilestoneModule from trac.ticket.api import ( IMilestoneChangeListener, ITicketChangeListener, TicketSystem ) -from trac.test import EnvironmentStub from trac.util.datefmt import from_utimestamp, to_utimestamp, utc @@ -53,6 +54,36 @@ class TestTicketChangeListener(core.Comp self.action = 'deleted' self.ticket = ticket + # the listener has no ticket_comment_modified and ticket_change_deleted + + +class TestTicketChangeListener_2(core.Component): + implements(ITicketChangeListener) + + def ticket_created(self, ticket): + pass + + def ticket_changed(self, ticket, comment, author, old_values): + pass + + def ticket_deleted(self, ticket): + pass + + def ticket_comment_modified(self, ticket, cdate, author, comment, + old_comment): + self.action = 'comment_modified' + self.ticket = ticket + self.cdate = cdate + self.author = author + self.comment = comment + self.old_comment = old_comment + + def ticket_change_deleted(self, ticket, cdate, changes): + self.action = 'change_deleted' + self.ticket = ticket + self.cdate = cdate + self.changes = changes + class TicketTestCase(unittest.TestCase): @@ -611,6 +642,19 @@ class TicketCommentEditTestCase(TicketCo self.assertEqual((i, t[i], 'joe (%d)' % i, 'Comment 1 (%d)' % i), history[i]) + def test_change_listener_comment_modified(self): + listener = TestTicketChangeListener_2(self.env) + ticket = Ticket(self.env, self.id) + ticket.modify_comment(cdate=self.t2, author='jack', + comment='New Comment 2', when=datetime.now(utc)) + + self.assertEqual('comment_modified', listener.action) + self.assertEqual(ticket, listener.ticket) + self.assertEqual(self.t2, listener.cdate) + self.assertEqual('jack', listener.author) + self.assertEqual('New Comment 2', listener.comment) + self.assertEqual('Comment 2', listener.old_comment) + class TicketCommentDeleteTestCase(TicketCommentTestCase): @@ -746,6 +790,25 @@ class TicketCommentDeleteTestCase(Ticket ticket.delete_change(1, when=t) self.assertEqual(t, ticket.time_changed) + def test_ticket_change_deleted(self): + listener = TestTicketChangeListener_2(self.env) + ticket = Ticket(self.env, self.id) + + ticket.delete_change(cdate=self.t3, when=datetime.now(utc)) + self.assertEqual('change_deleted', listener.action) + self.assertEqual(ticket, listener.ticket) + self.assertEqual(self.t3, listener.cdate) + self.assertEqual(dict(keywords=('a, b, c', 'a, b'), + foo=('change2', 'change3')), + listener.changes) + + ticket.delete_change(cnum=2, when=datetime.now(utc)) + self.assertEqual('change_deleted', listener.action) + self.assertEqual(ticket, listener.ticket) + self.assertEqual(self.t2, listener.cdate) + self.assertEqual(dict(owner=('john', 'jack'), + foo=('change 1', 'change2')), + listener.changes) class EnumTestCase(unittest.TestCase): @@ -974,6 +1037,20 @@ class MilestoneTestCase(unittest.TestCas self.assertEqual(tkt1['changetime'], tkt2['changetime']) self.assertNotEqual(self.updated_at, tkt1['changetime']) + def test_delete_milestone_with_attachment(self): + milestone = Milestone(self.env) + milestone.name = 'MilestoneWithAttachment' + milestone.insert() + + attachment = Attachment(self.env, 'milestone', milestone.name) + attachment.insert('foo.txt', StringIO(), 0, 1) + + milestone.delete() + self.assertEqual(False, milestone.exists) + + attachments = Attachment.select(self.env, 'milestone', milestone.name) + self.assertRaises(StopIteration, attachments.next) + def test_delete_milestone_retarget_tickets(self): self.env.db_transaction.executemany( "INSERT INTO milestone (name) VALUES (%s)", Modified: bloodhound/vendor/trac/current/trac/ticket/tests/notification.py URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/ticket/tests/notification.py?rev=1639602&r1=1639601&r2=1639602&view=diff ============================================================================== --- bloodhound/vendor/trac/current/trac/ticket/tests/notification.py (original) +++ bloodhound/vendor/trac/current/trac/ticket/tests/notification.py Fri Nov 14 11:06:23 2014 @@ -23,10 +23,10 @@ import re import unittest from datetime import datetime +import trac.tests.compat from trac.test import EnvironmentStub, Mock, MockPerm -from trac.tests import compat from trac.tests.notification import SMTP_TEST_PORT, SMTPThreadedServer,\ - parse_smtp_message, smtp_address + parse_smtp_message from trac.ticket.model import Ticket from trac.ticket.notification import TicketNotifyEmail from trac.ticket.web_ui import TicketModule @@ -36,85 +36,202 @@ MAXBODYWIDTH = 76 notifysuite = None -class NotificationTestCase(unittest.TestCase): - """Notification test cases that send email over SMTP""" +class RecipientTestCase(unittest.TestCase): + """Notification test cases for email recipients.""" def setUp(self): self.env = EnvironmentStub(default_data=True) self.env.config.set('project', 'name', 'TracTest') self.env.config.set('notification', 'smtp_enabled', 'true') - self.env.config.set('notification', 'always_notify_owner', 'true') - self.env.config.set('notification', 'always_notify_reporter', 'true') - self.env.config.set('notification', 'smtp_always_cc', - 'joe.u...@example.net, joe....@example.net') - self.env.config.set('notification', 'use_public_cc', 'true') self.env.config.set('notification', 'smtp_port', str(SMTP_TEST_PORT)) - self.env.config.set('notification', 'smtp_server', 'localhost') - self.req = Mock(href=self.env.href, abs_href=self.env.abs_href, tz=utc, - perm=MockPerm()) def tearDown(self): - """Signal the notification test suite that a test is over""" notifysuite.tear_down() self.env.reset_db() - def test_recipients(self): - """To/Cc recipients""" + def test_no_recipients(self): + """No recipient case""" ticket = Ticket(self.env) - ticket['reporter'] = '"Joe User" < joe.u...@example.org >' - ticket['owner'] = 'joe.u...@example.net' - ticket['cc'] = 'joe.u...@example.com, joe....@example.org, ' \ - 'joe....@example.net' + ticket['reporter'] = 'anonymous' ticket['summary'] = 'Foo' ticket.insert() tn = TicketNotifyEmail(self.env) tn.notify(ticket, newticket=True) recipients = notifysuite.smtpd.get_recipients() - # checks there is no duplicate in the recipient list - rcpts = [] - for r in recipients: - self.assertNotIn(r, rcpts) - rcpts.append(r) - # checks that all cc recipients have been notified - cc_list = self.env.config.get('notification', 'smtp_always_cc') - cc_list = "%s, %s" % (cc_list, ticket['cc']) - for r in cc_list.replace(',', ' ').split(): - self.assertIn(r, recipients) - # checks that owner has been notified - self.assertIn(smtp_address(ticket['owner']), recipients) - # checks that reporter has been notified - self.assertIn(smtp_address(ticket['reporter']), recipients) + sender = notifysuite.smtpd.get_sender() + message = notifysuite.smtpd.get_message() + self.assertEqual(0, len(recipients)) + self.assertIsNone(sender) + self.assertIsNone(message) - def test_no_recipient(self): - """No recipient case""" - self.env.config.set('notification', 'smtp_always_cc', '') + def test_new_ticket_recipients(self): + """Report and CC list should be in recipient list for new tickets.""" + always_cc = ('joe.u...@example.net', 'joe....@example.net') + ticket_cc = ('joe.u...@example.com', 'joe....@example.org') + self.env.config.set('notification', 'smtp_always_cc', + ', '.join(always_cc)) ticket = Ticket(self.env) - ticket['reporter'] = 'anonymous' - ticket['summary'] = 'Foo' + ticket['reporter'] = 'joe....@example.org' + ticket['owner'] = 'joe.u...@example.net' + ticket['cc'] = ' '.join(ticket_cc) + ticket['summary'] = 'New ticket recipients' ticket.insert() tn = TicketNotifyEmail(self.env) tn.notify(ticket, newticket=True) - sender = notifysuite.smtpd.get_sender() recipients = notifysuite.smtpd.get_recipients() - message = notifysuite.smtpd.get_message() - # checks that no message has been sent - self.assertEqual([], recipients) - self.assertIsNone(sender) - self.assertIsNone(message) + for r in always_cc + ticket_cc + \ + (ticket['owner'], ticket['reporter']): + self.assertIn(r, recipients) def test_cc_only(self): """Notification w/o explicit recipients but Cc: (#3101)""" + always_cc = ('joe.u...@example.net', 'joe....@example.net') + self.env.config.set('notification', 'smtp_always_cc', + ', '.join(always_cc)) ticket = Ticket(self.env) ticket['summary'] = 'Foo' ticket.insert() tn = TicketNotifyEmail(self.env) tn.notify(ticket, newticket=True) recipients = notifysuite.smtpd.get_recipients() - # checks that all cc recipients have been notified - cc_list = self.env.config.get('notification', 'smtp_always_cc') - for r in cc_list.replace(',', ' ').split(): + for r in always_cc: self.assertIn(r, recipients) + def test_always_notify_updater(self): + """The `always_notify_updater` option.""" + def _test_updater(enabled): + self.env.config.set('notification', 'always_notify_updater', + enabled) + ticket = Ticket(self.env) + ticket['reporter'] = 'joe.u...@example.org' + ticket['summary'] = u'This is a súmmäry' + ticket.insert() + now = datetime.now(utc) + ticket.save_changes('joe.b...@example.com', 'This is a change', + when=now) + tn = TicketNotifyEmail(self.env) + tn.notify(ticket, newticket=False, modtime=now) + recipients = notifysuite.smtpd.get_recipients() + if enabled: + self.assertEqual(1, len(recipients)) + self.assertIn('joe.b...@example.com', recipients) + else: + self.assertEqual(0, len(recipients)) + self.assertNotIn('joe.b...@example.com', recipients) + + # Validate with and without a default domain + for enable in False, True: + _test_updater(enable) + + def test_always_notify_owner(self): + """The `always_notify_owner` option.""" + def _test_reporter(enabled): + self.env.config.set('notification', 'always_notify_owner', + enabled) + self.env.config.set('notification', 'always_notify_updater', + 'false') + ticket = Ticket(self.env) + ticket['summary'] = 'Foo' + ticket['reporter'] = u'j...@example.org' + ticket['owner'] = u'j...@example.org' + ticket.insert() + now = datetime.now(utc) + ticket.save_changes('j...@example.org', 'this is my comment', + when=now) + tn = TicketNotifyEmail(self.env) + tn.notify(ticket, newticket=True, modtime=now) + recipients = notifysuite.smtpd.get_recipients() + if enabled: + self.assertEqual(1, len(recipients)) + self.assertEqual('j...@example.org', recipients[0]) + else: + self.assertEqual(0, len(recipients)) + + for enable in False, True: + _test_reporter(enable) + + def test_always_notify_reporter(self): + """Notification to reporter w/ updater option disabled (#3780)""" + def _test_reporter(enabled): + self.env.config.set('notification', 'always_notify_updater', + 'false') + self.env.config.set('notification', 'always_notify_reporter', + enabled) + ticket = Ticket(self.env) + ticket['summary'] = 'Foo' + ticket['reporter'] = u'j...@example.org' + ticket.insert() + now = datetime.now(utc) + ticket.save_changes('j...@example.org', 'this is my comment', + when=now) + tn = TicketNotifyEmail(self.env) + tn.notify(ticket, newticket=True, modtime=now) + recipients = notifysuite.smtpd.get_recipients() + if enabled: + self.assertEqual(1, len(recipients)) + self.assertEqual('j...@example.org', recipients[0]) + else: + self.assertEqual(0, len(recipients)) + + for enable in False, True: + _test_reporter(enable) + + def test_no_duplicates(self): + """Email addresses should be found only once in the recipient list.""" + self.env.config.set('notification', 'smtp_always_cc', + 'joe.u...@example.com') + ticket = Ticket(self.env) + ticket['reporter'] = 'joe.u...@example.com' + ticket['owner'] = 'joe.u...@example.com' + ticket['cc'] = 'joe.u...@example.com' + ticket['summary'] = 'No duplicates' + ticket.insert() + tn = TicketNotifyEmail(self.env) + tn.notify(ticket, newticket=True) + recipients = notifysuite.smtpd.get_recipients() + self.assertEqual(1, len(recipients)) + self.assertIn('joe.u...@example.com', recipients) + + def test_long_forms(self): + """Long forms of SMTP email addresses 'Display Name <address>'""" + self.env.config.set('notification', 'always_notify_owner', True) + ticket = Ticket(self.env) + ticket['reporter'] = '"Joe" <joe.u...@example.com>' + ticket['owner'] = 'Joe <joe.u...@example.net>' + ticket['cc'] = 'Joe < joe.u...@example.org >' + ticket['summary'] = 'Long form' + ticket.insert() + tn = TicketNotifyEmail(self.env) + tn.notify(ticket, newticket=True) + recipients = notifysuite.smtpd.get_recipients() + self.assertEqual(3, len(recipients)) + self.assertIn('joe.u...@example.com', recipients) + self.assertIn('joe.u...@example.net', recipients) + self.assertIn('joe.u...@example.org', recipients) + + +class NotificationTestCase(unittest.TestCase): + """Notification test cases that send email over SMTP""" + + def setUp(self): + self.env = EnvironmentStub(default_data=True) + self.env.config.set('project', 'name', 'TracTest') + self.env.config.set('notification', 'smtp_enabled', 'true') + self.env.config.set('notification', 'always_notify_owner', 'true') + self.env.config.set('notification', 'always_notify_reporter', 'true') + self.env.config.set('notification', 'smtp_always_cc', + 'joe.u...@example.net, joe....@example.net') + self.env.config.set('notification', 'use_public_cc', 'true') + self.env.config.set('notification', 'smtp_port', str(SMTP_TEST_PORT)) + self.env.config.set('notification', 'smtp_server', 'localhost') + self.req = Mock(href=self.env.href, abs_href=self.env.abs_href, tz=utc, + perm=MockPerm()) + + def tearDown(self): + """Signal the notification test suite that a test is over""" + notifysuite.tear_down() + self.env.reset_db() + def test_structure(self): """Basic SMTP message structure (headers, body)""" ticket = Ticket(self.env) @@ -174,10 +291,9 @@ class NotificationTestCase(unittest.Test def test_bcc_privacy(self): """Visibility of recipients""" - def run_bcc_feature(public): + def run_bcc_feature(public_cc): # CC list should be private - self.env.config.set('notification', 'use_public_cc', - 'true' if public else 'false') + self.env.config.set('notification', 'use_public_cc', public_cc) self.env.config.set('notification', 'smtp_always_bcc', 'joe.foo...@example.net') ticket = Ticket(self.env) @@ -188,7 +304,7 @@ class NotificationTestCase(unittest.Test tn.notify(ticket, newticket=True) message = notifysuite.smtpd.get_message() headers, body = parse_smtp_message(message) - if public: + if public_cc: # Msg should have a To list self.assertIn('To', headers) # Extract the list of 'To' recipients from the message @@ -220,8 +336,8 @@ class NotificationTestCase(unittest.Test self.assertNotIn(rcpt, to) # Check the message has actually been sent to the recipients self.assertIn(rcpt, rcptlist) - run_bcc_feature(True) - run_bcc_feature(False) + for public in False, True: + run_bcc_feature(public) def test_short_login(self): """Email addresses without a FQDN""" @@ -234,8 +350,7 @@ class NotificationTestCase(unittest.Test # send a notification even if other addresses are not valid self.env.config.set('notification', 'smtp_always_cc', 'joe....@example.net') - if enabled: - self.env.config.set('notification', 'use_short_addr', 'true') + self.env.config.set('notification', 'use_short_addr', enabled) tn = TicketNotifyEmail(self.env) tn.notify(ticket, newticket=True) message = notifysuite.smtpd.get_message() @@ -289,12 +404,12 @@ class NotificationTestCase(unittest.Test cclist = [addr.strip() for addr in headers['Cc'].split(',')] self.assertIn('joewith...@example.com', cclist) self.assertIn('joe....@example.net', cclist) - if not enabled: - self.assertEqual(2, len(cclist)) - self.assertNotIn('joenodom', cclist) - else: + if enabled: self.assertEqual(3, len(cclist)) self.assertIn('joeno...@example.org', cclist) + else: + self.assertEqual(2, len(cclist)) + self.assertNotIn('joenodom', cclist) # Validate with and without a default domain for enable in False, True: @@ -517,88 +632,47 @@ class NotificationTestCase(unittest.Test tn.notify(ticket, newticket=True) message = notifysuite.smtpd.get_message() headers, body = parse_smtp_message(message) + self.assertEqual('joe.u...@example.org', headers['To']) + + def test_previous_cc_list(self): + """Members removed from CC list receive notifications""" + ticket = Ticket(self.env) + ticket['summary'] = 'Foo' + ticket['cc'] = 'joe.us...@example.net' + ticket.insert() + ticket['cc'] = 'joe.us...@example.net' + now = datetime.now(utc) + ticket.save_changes('joe....@example.com', 'Removed from cc', now) + tn = TicketNotifyEmail(self.env) + tn.notify(ticket, newticket=False, modtime=now) + recipients = notifysuite.smtpd.get_recipients() + self.assertIn('joe.us...@example.net', recipients) + self.assertIn('joe.us...@example.net', recipients) - def test_updater(self): - """No-self-notification option""" - def _test_updater(disabled): - if disabled: - self.env.config.set('notification', 'always_notify_updater', - 'false') + def test_previous_owner(self): + """Previous owner is notified when ticket is reassigned (#2311) + if always_notify_owner is set to True""" + def _test_owner(enabled): + self.env.config.set('notification', 'always_notify_owner', enabled) ticket = Ticket(self.env) - ticket['reporter'] = 'joe.u...@example.org' - ticket['summary'] = u'This is a súmmäry' - ticket['cc'] = 'joe....@example.com' + ticket['summary'] = 'Foo' + ticket['owner'] = prev_owner = 'joe.us...@example.net' ticket.insert() - ticket['component'] = 'dummy' + ticket['owner'] = new_owner = 'joe.us...@example.net' now = datetime.now(utc) - ticket.save_changes('joe.b...@example.com', 'This is a change', - when=now) + ticket.save_changes('joe....@example.com', 'Changed owner', now) tn = TicketNotifyEmail(self.env) tn.notify(ticket, newticket=False, modtime=now) - message = notifysuite.smtpd.get_message() - headers, body = parse_smtp_message(message) - # checks for header existence - self.assertTrue(headers) - # checks for updater in the 'To' recipient list - self.assertIn('To', headers) - tolist = [addr.strip() for addr in headers['To'].split(',')] - if disabled: - self.assertNotIn('joe.b...@example.com', tolist) + recipients = notifysuite.smtpd.get_recipients() + if enabled: + self.assertIn(prev_owner, recipients) + self.assertIn(new_owner, recipients) else: - self.assertIn('joe.b...@example.com', tolist) - - # Validate with and without a default domain - for disable in False, True: - _test_updater(disable) - - def test_updater_only(self): - """Notification w/ updater, w/o any other recipient (#4188)""" - self.env.config.set('notification', 'always_notify_owner', 'false') - self.env.config.set('notification', 'always_notify_reporter', 'false') - self.env.config.set('notification', 'always_notify_updater', 'true') - self.env.config.set('notification', 'smtp_always_cc', '') - self.env.config.set('notification', 'smtp_always_bcc', '') - self.env.config.set('notification', 'use_public_cc', 'false') - self.env.config.set('notification', 'use_short_addr', 'false') - self.env.config.set('notification', 'smtp_replyto', - 'joeu...@example.net') - ticket = Ticket(self.env) - ticket['summary'] = 'Foo' - ticket.insert() - ticket['summary'] = 'Bar' - ticket['component'] = 'New value' - ticket.save_changes('j...@example.com', 'this is my comment') - tn = TicketNotifyEmail(self.env) - tn.notify(ticket, newticket=True) - recipients = notifysuite.smtpd.get_recipients() - self.assertIsNotNone(recipients) - self.assertEqual(1, len(recipients)) - self.assertEqual(recipients[0], 'j...@example.com') + self.assertNotIn(prev_owner, recipients) + self.assertNotIn(new_owner, recipients) - def test_updater_is_reporter(self): - """Notification to reporter w/ updater option disabled (#3780)""" - self.env.config.set('notification', 'always_notify_owner', 'false') - self.env.config.set('notification', 'always_notify_reporter', 'true') - self.env.config.set('notification', 'always_notify_updater', 'false') - self.env.config.set('notification', 'smtp_always_cc', '') - self.env.config.set('notification', 'smtp_always_bcc', '') - self.env.config.set('notification', 'use_public_cc', 'false') - self.env.config.set('notification', 'use_short_addr', 'false') - self.env.config.set('notification', 'smtp_replyto', - 'joeu...@example.net') - ticket = Ticket(self.env) - ticket['summary'] = 'Foo' - ticket['reporter'] = u'j...@example.org' - ticket.insert() - ticket['summary'] = 'Bar' - ticket['component'] = 'New value' - ticket.save_changes('j...@example.org', 'this is my comment') - tn = TicketNotifyEmail(self.env) - tn.notify(ticket, newticket=True) - recipients = notifysuite.smtpd.get_recipients() - self.assertIsNotNone(recipients) - self.assertEqual(1, len(recipients)) - self.assertEqual('j...@example.org', recipients[0]) + for enable in False, True: + _test_owner(enable) def _validate_mimebody(self, mime, ticket, newtk): """Body of a ticket notification message""" @@ -1128,6 +1202,53 @@ Security sensitive: 0 tn.ticket = ticket tn.get_message_id('foo') + def test_mime_meta_characters_in_from_header(self): + """MIME encoding with meta characters in From header""" + + self.env.config.set('notification', 'smtp_from', 't...@example.com') + self.env.config.set('notification', 'mime_encoding', 'base64') + ticket = Ticket(self.env) + ticket['reporter'] = 'joeuser' + ticket['summary'] = 'This is a summary' + ticket.insert() + tn = TicketNotifyEmail(self.env) + + def notify(from_name): + self.env.config.set('notification', 'smtp_from_name', from_name) + tn.notify(ticket, newticket=True) + message = notifysuite.smtpd.get_message() + headers, body = parse_smtp_message(message) + return message, headers, body + + message, headers, body = notify(u'Träc') + self.assertEqual(r'"=?utf-8?b?VHLDpGM=?=" <t...@example.com>', + headers['From']) + message, headers, body = notify(u'Trac\\') + self.assertEqual(r'"Trac\\" <t...@example.com>', headers['From']) + message, headers, body = notify(u'Trac"') + self.assertEqual(r'"Trac\"" <t...@example.com>', headers['From']) + message, headers, body = notify(u'=?utf-8?b?****?=') + self.assertEqual('"=?utf-8?b?PT91dGYtOD9iPyoqKio/PQ==?=" ' + '<t...@example.com>', headers['From']) + + def test_mime_meta_characters_in_subject_header(self): + """MIME encoding with meta characters in Subject header""" + + self.env.config.set('notification', 'smtp_from', 't...@example.com') + self.env.config.set('notification', 'mime_encoding', 'base64') + summary = u'=?utf-8?q?****?=' + ticket = Ticket(self.env) + ticket['reporter'] = 'joeuser' + ticket['summary'] = summary + ticket.insert() + tn = TicketNotifyEmail(self.env) + tn.notify(ticket, newticket=True) + message = notifysuite.smtpd.get_message() + headers, body = parse_smtp_message(message) + self.assertIn('\nSubject: =?utf-8?b?', message) # is mime-encoded + self.assertEqual(summary, + re.split(r' #[0-9]+: ', headers['Subject'], 1)[1]) + class NotificationTestSuite(unittest.TestSuite): """Thin test suite wrapper to start and stop the SMTP test server""" @@ -1137,6 +1258,7 @@ class NotificationTestSuite(unittest.Tes unittest.TestSuite.__init__(self) self.smtpd = SMTPThreadedServer(SMTP_TEST_PORT) self.smtpd.start() + self.addTest(unittest.makeSuite(RecipientTestCase)) self.addTest(unittest.makeSuite(NotificationTestCase)) self.remaining = self.countTestCases() Modified: bloodhound/vendor/trac/current/trac/ticket/tests/query.py URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/ticket/tests/query.py?rev=1639602&r1=1639601&r2=1639602&view=diff ============================================================================== --- bloodhound/vendor/trac/current/trac/ticket/tests/query.py (original) +++ bloodhound/vendor/trac/current/trac/ticket/tests/query.py Fri Nov 14 11:06:23 2014 @@ -18,6 +18,7 @@ from trac.util.datefmt import utc from trac.web.chrome import web_context from trac.web.href import Href from trac.wiki.formatter import LinkFormatter +from trac.wiki.tests import formatter import unittest import difflib @@ -581,6 +582,31 @@ ORDER BY COALESCE(t.id,0)=0,t.id""") self.assertEqual('\xef\xbb\xbfcol1\r\n"value, needs escaped"\r\n', content) + def test_csv_obfuscation(self): + class NoEmailView(MockPerm): + def has_permission(self, action, realm_or_resource=None, id=False, + version=False): + return action != 'EMAIL_VIEW' + __contains__ = has_permission + + query = Mock(get_columns=lambda: ['owner', 'reporter', 'cc'], + execute=lambda r: [{'id': 1, + 'owner': 'j...@example.org', + 'reporter': 'f...@example.org', + 'cc': 'c...@example.org, cc2'}], + time_fields=['time', 'changetime']) + req = Mock(href=self.env.href, perm=NoEmailView()) + content, mimetype = QueryModule(self.env).export_csv(req, query) + self.assertEqual(u'\uFEFFowner,reporter,cc\r\n' + u'joe@â¦,foo@â¦,"cc1@â¦, cc2"\r\n', + content.decode('utf-8')) + req = Mock(href=self.env.href, perm=MockPerm()) + content, mimetype = QueryModule(self.env).export_csv(req, query) + self.assertEqual( + u'\uFEFFowner,reporter,cc\r\n' + u'j...@example.org,f...@example.org,"c...@example.org, cc2"\r\n', + content.decode('utf-8')) + def test_template_data(self): req = Mock(href=self.env.href, perm=MockPerm(), authname='anonymous', tz=None, locale=None) @@ -654,12 +680,267 @@ class TicketQueryMacroTestCase(unittest. dict(col='status|summary', max='0', order='id'), 'list') +QUERY_TEST_CASES = u""" +============================== TicketQuery(format=progress) +[[TicketQuery(format=progress)]] +------------------------------ +<p> +</p><div class="trac-progress"> + + <table xmlns="http://www.w3.org/1999/xhtml" class="progress"> + <tr> + <td class="closed" style="width: 33%"> + <a href="/query?status=closed&group=resolution&max=0&order=time" title="1/3 closed"></a> + </td><td class="open" style="width: 67%"> + <a href="/query?status=assigned&status=new&status=accepted&status=reopened&max=0&order=id" title="2/3 active"></a> + </td> + </tr> + </table> + + <p class="percent">33%</p> + + <p class="legend"> + <span class="first interval"> + <a href="/query?max=0&order=id">Total number of tickets: 3</a> + </span> + <span class="interval"> + - <a href="/query?status=closed&group=resolution&max=0&order=time">closed: 1</a> + </span><span class="interval"> + - <a href="/query?status=assigned&status=new&status=accepted&status=reopened&max=0&order=id">active: 2</a> + </span> + </p> +</div><p> +</p> +------------------------------ +============================== TicketQuery(reporter=santa, format=progress) +[[TicketQuery(reporter=santa, format=progress)]] +------------------------------ +<p> +</p><div class="trac-progress"> + + <table xmlns="http://www.w3.org/1999/xhtml" class="progress"> + <tr> + <td class="closed" style="display: none"> + <a href="/query?status=closed&reporter=santa&group=resolution&max=0&order=time" title="0/1 closed"></a> + </td><td class="open" style="width: 100%"> + <a href="/query?status=assigned&status=new&status=accepted&status=reopened&reporter=santa&max=0&order=id" title="1/1 active"></a> + </td> + </tr> + </table> + + <p class="percent">0%</p> + + <p class="legend"> + <span class="first interval"> + <a href="/query?reporter=santa&max=0&order=id">Total number of tickets: 1</a> + </span> + <span class="interval"> + - <a href="/query?status=closed&reporter=santa&group=resolution&max=0&order=time">closed: 0</a> + </span><span class="interval"> + - <a href="/query?status=assigned&status=new&status=accepted&status=reopened&reporter=santa&max=0&order=id">active: 1</a> + </span> + </p> +</div><p> +</p> +------------------------------ +============================== TicketQuery(reporter=santa&or&owner=santa, format=progress) +[[TicketQuery(reporter=santa&or&owner=santa, format=progress)]] +------------------------------ +<p> +</p><div class="trac-progress"> + + <table xmlns="http://www.w3.org/1999/xhtml" class="progress"> + <tr> + <td class="closed" style="width: 50%"> + <a href="/query?status=closed&reporter=santa&or&owner=santa&status=closed&group=resolution&max=0&order=time" title="1/2 closed"></a> + </td><td class="open" style="width: 50%"> + <a href="/query?status=assigned&status=new&status=accepted&status=reopened&reporter=santa&or&owner=santa&status=assigned&status=new&status=accepted&status=reopened&max=0&order=id" title="1/2 active"></a> + </td> + </tr> + </table> + + <p class="percent">50%</p> + + <p class="legend"> + <span class="first interval"> + <a href="/query?reporter=santa&or&owner=santa&max=0&order=id">Total number of tickets: 2</a> + </span> + <span class="interval"> + - <a href="/query?status=closed&reporter=santa&or&owner=santa&status=closed&group=resolution&max=0&order=time">closed: 1</a> + </span><span class="interval"> + - <a href="/query?status=assigned&status=new&status=accepted&status=reopened&reporter=santa&or&owner=santa&status=assigned&status=new&status=accepted&status=reopened&max=0&order=id">active: 1</a> + </span> + </p> +</div><p> +</p> +------------------------------ +============================== TicketQuery(format=progress, group=project) +[[TicketQuery(format=progress, group=project)]] +------------------------------ +<p> +</p><div class="trac-groupprogress"> + <table xmlns="http://www.w3.org/1999/xhtml" summary="Ticket completion status for each project"> + <tr> + <th scope="row"> + <i><a href="/query?project=&max=0&order=id">(none)</a></i> + + + </th> + <td> + + + <table class="progress" style="width: 40%"> + <tr> + <td class="closed" style="display: none"> + <a href="/query?project=&status=closed&group=resolution&max=0&order=time" title="0/1 closed"></a> + </td><td class="open" style="width: 100%"> + <a href="/query?project=&status=assigned&status=new&status=accepted&status=reopened&max=0&order=id" title="1/1 active"></a> + </td> + </tr> + </table> + + <p class="percent">0 / 1</p> + + + + </td> + </tr><tr> + <th scope="row"> + + + <a href="/query?project=xmas&max=0&order=id">xmas</a> + </th> + <td> + + + <table class="progress" style="width: 80%"> + <tr> + <td class="closed" style="width: 50%"> + <a href="/query?project=xmas&status=closed&group=resolution&max=0&order=time" title="1/2 closed"></a> + </td><td class="open" style="width: 50%"> + <a href="/query?project=xmas&status=assigned&status=new&status=accepted&status=reopened&max=0&order=id" title="1/2 active"></a> + </td> + </tr> + </table> + + <p class="percent">1 / 2</p> + + + + </td> + </tr> + </table> +</div><p> +</p> +------------------------------ +============================== TicketQuery(reporter=santa, format=progress, group=project) +[[TicketQuery(reporter=santa, format=progress, group=project)]] +------------------------------ +<p> +</p><div class="trac-groupprogress"> + <table xmlns="http://www.w3.org/1999/xhtml" summary="Ticket completion status for each project"> + <tr> + <th scope="row"> + + + <a href="/query?project=xmas&reporter=santa&max=0&order=id">xmas</a> + </th> + <td> + + + <table class="progress" style="width: 80%"> + <tr> + <td class="closed" style="display: none"> + <a href="/query?project=xmas&status=closed&reporter=santa&group=resolution&max=0&order=time" title="0/1 closed"></a> + </td><td class="open" style="width: 100%"> + <a href="/query?project=xmas&status=assigned&status=new&status=accepted&status=reopened&reporter=santa&max=0&order=id" title="1/1 active"></a> + </td> + </tr> + </table> + + <p class="percent">0 / 1</p> + + + + </td> + </tr> + </table> +</div><p> +</p> +------------------------------ +============================== TicketQuery(reporter=santa&or&owner=santa, format=progress, group=project) +[[TicketQuery(reporter=santa&or&owner=santa, format=progress, group=project)]] +------------------------------ +<p> +</p><div class="trac-groupprogress"> + <table xmlns="http://www.w3.org/1999/xhtml" summary="Ticket completion status for each project"> + <tr> + <th scope="row"> + + + <a href="/query?project=xmas&reporter=santa&or&owner=santa&project=xmas&max=0&order=id">xmas</a> + </th> + <td> + + + <table class="progress" style="width: 80%"> + <tr> + <td class="closed" style="width: 50%"> + <a href="/query?project=xmas&status=closed&reporter=santa&or&owner=santa&project=xmas&status=closed&group=resolution&max=0&order=time" title="1/2 closed"></a> + </td><td class="open" style="width: 50%"> + <a href="/query?project=xmas&status=assigned&status=new&status=accepted&status=reopened&reporter=santa&or&owner=santa&project=xmas&status=assigned&status=new&status=accepted&status=reopened&max=0&order=id" title="1/2 active"></a> + </td> + </tr> + </table> + + <p class="percent">1 / 2</p> + + + + </td> + </tr> + </table> +</div><p> +</p> +------------------------------ +""" + +def ticket_setup(tc): + tc.env.config.set('ticket-custom', 'project', 'text') + ticket = Ticket(tc.env) + ticket.values.update({'reporter': 'santa', + 'summary': 'This is the summary', + 'status': 'new', + 'project': 'xmas'}) + ticket.insert() + ticket = Ticket(tc.env) + ticket.values.update({'owner': 'elf', + 'summary': 'This is another summary', + 'status': 'assigned'}) + ticket.insert() + ticket = Ticket(tc.env) + ticket.values.update({'owner': 'santa', + 'summary': 'This is th third summary', + 'status': 'closed', + 'project': 'xmas'}) + ticket.insert() + + tc.env.config.set('milestone-groups', 'closed.status', 'closed') + tc.env.config.set('milestone-groups', 'closed.query_args', 'group=resolution,order=time') + tc.env.config.set('milestone-groups', 'closed.overall_completion', 'true') + tc.env.config.set('milestone-groups', 'active.status', '*') + tc.env.config.set('milestone-groups', 'active.css_class', 'open') + +def ticket_teardown(tc): + tc.env.reset_db() def suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(QueryTestCase)) suite.addTest(unittest.makeSuite(QueryLinksTestCase)) suite.addTest(unittest.makeSuite(TicketQueryMacroTestCase)) + suite.addTest(formatter.suite(QUERY_TEST_CASES, ticket_setup, __file__, + ticket_teardown)) return suite if __name__ == '__main__':