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))