diff --git a/.gitignore b/.gitignore index 6634266..8bec443 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ dist *.pyc *.swp *.egg-info +*.mo tags diff --git a/setup.py b/setup.py index 63e3024..f646dc6 100755 --- a/setup.py +++ b/setup.py @@ -71,6 +71,7 @@ package_data={ 'tracsubtickets': [ 'htdocs/css/*.css', + 'htdocs/js/*.js', 'locale/*/LC_MESSAGES/*.mo', ], }, diff --git a/tracsubtickets/api.py b/tracsubtickets/api.py index ae169b8..8d451fc 100644 --- a/tracsubtickets/api.py +++ b/tracsubtickets/api.py @@ -48,7 +48,7 @@ NotificationSystem = TicketChangeEvent = None from trac.ticket.notification import TicketNotifyEmail -import db_default +from . import db_default NUMBERS_RE = re.compile(r'\d+', re.U) diff --git a/tracsubtickets/checker.py b/tracsubtickets/checker.py index e941f30..e266731 100644 --- a/tracsubtickets/checker.py +++ b/tracsubtickets/checker.py @@ -32,7 +32,7 @@ from trac.env import open_environment -from api import NUMBERS_RE +from .api import NUMBERS_RE def check_subtickets(db): @@ -68,9 +68,9 @@ def check_subtickets(db): result = True if not result: - print "Mismatch in ticket #%i" % id - print " custom field :", cfield.get(id, '--') - print " subtickets :", subtickets.get(id, '--') + print("Mismatch in ticket #%i" % id) + print(" custom field :", cfield.get(id, '--')) + print(" subtickets :", subtickets.get(id, '--')) def main(args=sys.argv[1:]): diff --git a/tracsubtickets/htdocs/js/appendsubticketsdata.js b/tracsubtickets/htdocs/js/appendsubticketsdata.js new file mode 100644 index 0000000..be5b001 --- /dev/null +++ b/tracsubtickets/htdocs/js/appendsubticketsdata.js @@ -0,0 +1,60 @@ +(function() { + var $, args; + + $ = jQuery; + + // subtickets_script_args must have been defined via add_script_data() + args = $.parseJSON(subtickets_script_args); + + createButton = function() { + var form = $(`
`); + var div = $(`
`); + var submitButton = $(``); + var parentsArg = $(``); + + div.append(submitButton); + div.append(parentsArg); + $.each($.parseJSON(args.inherited_args), function(key, value) { + var inheritedArg = $(``); + div.append(inheritedArg); + }); + + form.append(div); + + return form; + } + + createLink = function() { + return $(`(${args.localized_link_label})`); + } + + $(document).ready(function() { + var div = $('
'); + var table = $('
'); + + $.each($.parseJSON(args.subtickets_table), function(key, value) { + var row = $(''); + row.append(value.summary); + row.append(value.status); + row.append(value.owner); + table.append(row); + }); + + if (args.add_style == 'link') { + var link = createLink(); + var separator = $(`

${args.localized_separator_label}

`); + separator.append(link); + div.append(separator); + } + else { + var button = createButton(); + var separator = $(`

${args.localized_separator_label}

`); + div.append(button); + div.append(separator); + } + div.append(table); + + $('div#ticket').append(div); + }); + +}).call(this); diff --git a/tracsubtickets/web_ui.py b/tracsubtickets/web_ui.py index 53efbd4..0f473dd 100644 --- a/tracsubtickets/web_ui.py +++ b/tracsubtickets/web_ui.py @@ -27,24 +27,24 @@ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. +import json from trac.config import Option, IntOption, ChoiceOption, ListOption from trac.core import Component, implements -from trac.web.api import IRequestFilter, ITemplateStreamFilter +from trac.web.api import IRequestFilter from trac.web.chrome import ITemplateProvider, add_stylesheet -from trac.util.html import html as tag +from trac.web.chrome import add_script, add_script_data +from trac.util.html import html as tag, escape from trac.ticket.api import ITicketManipulator from trac.ticket.model import Ticket from trac.ticket.model import Type as TicketType from trac.resource import ResourceNotFound -from genshi.filters import Transformer -from api import NUMBERS_RE, _ +from .api import NUMBERS_RE, _ class SubTicketsModule(Component): - implements(IRequestFilter, ITicketManipulator, ITemplateProvider, - ITemplateStreamFilter) + implements(IRequestFilter, ITicketManipulator, ITemplateProvider) # Simple Options @@ -110,7 +110,7 @@ def __init__(self): def get_htdocs_dirs(self): from pkg_resources import resource_filename - return [('subtickets', resource_filename(__name__, 'htdocs'))] + yield 'subtickets', resource_filename(__name__, 'htdocs') def get_templates_dirs(self): return [] @@ -143,6 +143,9 @@ def post_process_request(self, req, template, data, content_type): and data['add'] == 'Add': self._add_per_ticket_type_option(data['name']) + # append subtickets data (happended in filter_stream() before) + self._append_subtickets_data(req, data) + return template, data, content_type def _append_parent_links(self, req, data, ids): @@ -206,9 +209,7 @@ def validate_ticket(self, req, ticket): "is closed", id=id) yield None, msg - # ITemplateStreamFilter method - - def _create_subtickets_table(self, req, children, tbody, depth=0): + def _create_subtickets_table(self, req, children, tdict, depth=0): """Recursively create list table of subtickets """ if not children: @@ -217,7 +218,7 @@ def _create_subtickets_table(self, req, children, tbody, depth=0): ticket = Ticket(self.env, id) # the row - r = [] + rdict = dict() # Always show ID and summary attrs = {'href': req.href.ticket(id)} if ticket['status'] == 'closed': @@ -225,7 +226,7 @@ def _create_subtickets_table(self, req, children, tbody, depth=0): link = tag.a('#%s' % id, **attrs) summary = tag.td(link, ': %s' % ticket['summary'], style='padding-left: %dpx;' % (depth * 15)) - r.append(summary) + rdict['summary'] = str(summary) # Add other columns as configured. for column in \ @@ -246,69 +247,54 @@ def _create_subtickets_table(self, req, children, tbody, depth=0): href=href)) else: e = tag.td(ticket[column]) - r.append(e) - tbody.append(tag.tr(*r)) + rdict[column] = str(e) + tdict[id] = rdict - self._create_subtickets_table(req, children[id], tbody, depth + 1) + self._create_subtickets_table(req, children[id], tdict, depth + 1) - def filter_stream(self, req, method, filename, stream, data): + def _append_subtickets_data(self, req, data): if not req.path_info.startswith('/ticket/'): - return stream + return - div = None - link = None - button = None + # arguments we need in javascript + script_args = dict() + # common arguments + script_args['localized_separator_label'] = _('Subtickets ') + script_args['add_style'] = self.opt_add_style if 'ticket' in data: # get parents data ticket = data['ticket'] - # title - div = tag.div(class_='description') + if 'TICKET_CREATE' in req.perm(ticket.resource) \ and ticket['status'] != 'closed': opt_inherit = self.env.config.getlist( 'subtickets', 'type.%(type)s.child_inherits' % ticket) + inh = {f: ticket[f] for f in opt_inherit} + if self.opt_add_style == 'link': - inh = {f: ticket[f] for f in opt_inherit} - link = tag.a(_('add'), - href=req.href.newticket(parents=ticket.id, - **inh)) - link = tag.span('(', link, ')', class_='addsubticket') + # link-specific arguments + script_args['href_req_newticket_with_parent'] \ + = req.href.newticket(parents=ticket.id, **inh) + script_args['localized_link_label'] = _('add') else: - inh = [tag.input(type='hidden', - name=f, - value=ticket[f]) for f in opt_inherit] - - button = tag.form( - tag.div( - tag.input(type="submit", - value=_("Create"), - title=_("Create a child ticket")), - inh, - tag.input(type="hidden", - name="parents", - value=str(ticket.id)), - class_="inlinebuttons"), - method="get", action=req.href.newticket()) - div.append(button) - div.append(tag.h3(_('Subtickets '), link)) - - if 'subtickets' in data: + # button-specific arguments + script_args['localized_button_label'] = _("Create") + script_args['localized_button_title'] = \ + _("Create new child ticket") + script_args['href_req_newticket'] = req.href.newticket() + script_args['parent_id'] = str(ticket.id) + script_args['inherited_args'] = json.dumps(inh) + # table - tbody = tag.tbody() - div.append(tag.table(tbody, class_='subtickets')) - # tickets - self._create_subtickets_table(req, data['subtickets'], tbody) + tdict = dict() + if 'subtickets' in data: + # tickets + self._create_subtickets_table(req, data['subtickets'], tdict) + + # subtickets table argument: can be empty but must be set! + script_args['subtickets_table']=json.dumps(tdict) - if div: add_stylesheet(req, 'subtickets/css/subtickets.css') - ''' - If rendered in preview mode, DIV we're interested in isn't a child - but the root and transformation won't succeed. - According to HTML specification, id's must be unique within a - document, so it's safe to omit the leading '.' in XPath expression - to select all matching regardless of hierarchy their in. - ''' - stream |= Transformer('//div[@id="ticket"]').append(div) - - return stream + add_script(req, 'subtickets/js/appendsubticketsdata.js') + add_script_data(req, subtickets_script_args=json.dumps(script_args))