def format(self): if not self.changesets: message = _("No changesets for #%s" % self.tkt_id) yield tag.span(format_to_oneliner(self.env, self.context, message, shorten=False), class_='ticketchangesets hint') return n = len(self.changesets) ix = 0 # current index for adding separation markers between repos for (reponame, changesets) in self.changesets: if n > 1: if self.hint == 'ticket': if reponame and reponame != '(default)': yield tag.h3(reponame, class_='change') else: yield tag.h3(_("Default Repository"), class_='change') elif ix > 0: yield ', ' revs = changesets.wiki_revs(reponame, self.compact) log = changesets.wiki_log(reponame) message = revs + ' (' + log + ')' yield tag.span(format_to_oneliner(self.env, self.context, message, shorten=False), class_='ticketchangesets') ix += 1
def expand_macro(self, formatter, name, arguments): self.mc.options(arguments) extras = self.mc.extras() extra = '' if 'extra' in extras: extra = extras['extra'] return tag.div( tag.h3('[[%s(%s)]]' % (name, arguments)), tag.table(tag.tr(tag.th('Name'), tag.th('Value'), tag.th('Qualified'), tag.th('Default?'), tag.th('Macroarg?'), tag.th('Extra?'), tag.th('Known?'), tag.th('Default'), tag.th('Documentation')), self._show_option('text', self.mo_text, TracMacroConfigExample.mo_text), self._show_option('bool', self.mo_bool, TracMacroConfigExample.mo_bool), self._show_option('int', self.mo_int, TracMacroConfigExample.mo_int), self._show_option('list', self.mo_list, TracMacroConfigExample.mo_list), self._show_option('nodtext', self.mo_nodtext, TracMacroConfigExample.mo_nodtext), self._show_option('nodbool', self.mo_nodbool, TracMacroConfigExample.mo_nodbool), self._show_option('nodint', self.mo_nodint, TracMacroConfigExample.mo_nodint), self._show_option('nodlist', self.mo_nodlist, TracMacroConfigExample.mo_nodlist), self._show_extra('extra', extra), border=1, cellpadding=1, cellspacing=0))
def expand_macro(self, formatter, name, arguments): self.mc.options(arguments) extras = self.mc.extras() extra = '' if 'extra' in extras: extra = extras['extra'] return tag.div( tag.h3('[[%s(%s)]]' % ( name, arguments )), tag.table( tag.tr( tag.th('Name'), tag.th('Value'), tag.th('Qualified'), tag.th('Default?'), tag.th('Macroarg?'), tag.th('Extra?'), tag.th('Known?'), tag.th('Default'), tag.th('Documentation') ), self._show_option('text', self.mo_text, TracMacroConfigExample.mo_text), self._show_option('bool', self.mo_bool, TracMacroConfigExample.mo_bool), self._show_option('int', self.mo_int, TracMacroConfigExample.mo_int), self._show_option('list', self.mo_list, TracMacroConfigExample.mo_list), self._show_option('nodtext', self.mo_nodtext, TracMacroConfigExample.mo_nodtext), self._show_option('nodbool', self.mo_nodbool, TracMacroConfigExample.mo_nodbool), self._show_option('nodint', self.mo_nodint, TracMacroConfigExample.mo_nodint), self._show_option('nodlist', self.mo_nodlist, TracMacroConfigExample.mo_nodlist), self._show_extra('extra', extra), border=1, cellpadding=1, cellspacing=0 ) )
def expand_macro(self, formatter, name, args): from trac.config import Option section_filter = key_filter = '' args, kw = parse_args(args) if args: section_filter = args.pop(0).strip() if args: key_filter = args.pop(0).strip() registry = Option.get_registry(self.compmgr) sections = {} for (section, key), option in registry.iteritems(): if section.startswith(section_filter): sections.setdefault(section, {})[key] = option return tag.div(class_='tracini')( (tag.h3(tag.code('[%s]' % section), id='%s-section' % section), tag.table(class_='wiki')( tag.tbody(tag.tr(tag.td(tag.tt(option.name)), tag.td(format_to_oneliner( self.env, formatter.context, to_unicode(option.__doc__)))) for option in sorted(sections[section].itervalues(), key=lambda o: o.name) if option.name.startswith(key_filter)))) for section in sorted(sections))
class MacroListMacro(WikiMacroBase): _domain = 'messages' _description = cleandoc_( """Display a list of all installed Wiki macros, including documentation if available. Optionally, the name of a specific macro can be provided as an argument. In that case, only the documentation for that macro will be rendered. Note that this macro will not be able to display the documentation of macros if the `PythonOptimize` option is enabled for mod_python! """) def expand_macro(self, formatter, name, content): from trac.wiki.formatter import system_message content = content.strip() if content else '' name_filter = content.strip('*') def get_macro_descr(): for macro_provider in formatter.wiki.macro_providers: names = list(macro_provider.get_macros() or []) if name_filter and not any(name.startswith(name_filter) for name in names): continue try: name_descriptions = [ (name, macro_provider.get_macro_description(name)) for name in names] except Exception, e: yield system_message( _("Error: Can't get description for macro %(name)s", name=names[0]), e), names else: for descr, pairs in groupby(name_descriptions, key=lambda p: p[1]): if descr: if isinstance(descr, (tuple, list)): descr = dgettext(descr[0], descr[1]) else: descr = to_unicode(descr) or '' if content == '*': descr = format_to_oneliner( self.env, formatter.context, descr, shorten=True) else: descr = format_to_html( self.env, formatter.context, descr) yield descr, [name for name, descr in pairs] return tag.div(class_='trac-macrolist')( (tag.h3(tag.code('[[', names[0], ']]'), id='%s-macro' % names[0]), len(names) > 1 and tag.p(tag.strong(_("Aliases:")), [tag.code(' [[', alias, ']]') for alias in names[1:]]) or None, description or tag.em(_("Sorry, no documentation found"))) for description, names in sorted(get_macro_descr(), key=lambda item: item[1][0]))
def filter_stream(self, req, method, filename, stream, data): if req.path_info.startswith('/ticket/'): div = None if 'ticket' in data: # get parents data ticket = data['ticket'] # title div = tag.div(class_='description') if ticket['status'] != 'closed': link = tag.a(_('add'), href=req.href.newticket(parents=ticket.id), title=_('Create new child ticket')) link = tag.span('(', link, ')', class_='addsubticket') else: link = None div.append(tag.h3(_('Subtickets '), link)) if 'subtickets' in data: # table tbody = tag.tbody() div.append(tag.table(tbody, class_='subtickets')) # tickets def _func(children, depth=0): for id in sorted(children, key=lambda x: int(x)): ticket = Ticket(self.env, id) # 1st column attrs = {'href': req.href.ticket(id)} if ticket['status'] == 'closed': attrs['class_'] = 'closed' link = tag.a('#%s' % id, **attrs) summary = tag.td(link, ': %s' % ticket['summary'], style='padding-left: %dpx;' % (depth * 15)) # 2nd column type = tag.td(ticket['type']) # 3rd column status = tag.td(ticket['status']) # 4th column href = req.href.query(status='!closed', owner=ticket['owner']) owner = tag.td(tag.a(ticket['owner'], href=href)) tbody.append(tag.tr(summary, type, status, owner)) if depth >= 0: _func(children[id], depth + 1) if self.recursion: _func(data['subtickets']) else: _func(data['subtickets'],-1) if div: add_stylesheet(req, 'subtickets/css/subtickets.css') stream |= Transformer('.//div[@id="ticket"]').append(div) return stream
def setTitle(self, title): if self.title is None: self.title = tag.title() self.head.append(self.title) if self.header is None: self.header = tag.h3() self.body.append(self.header) self.title.children = [title] self.header.children = [title]
def __project_display(self, req, milestone): row = self.__SmpModel.get_project_milestone(milestone) if row: return tag.span( tag.h3(wiki_to_oneliner("Project: %s" % (row[0],), self.env, req=req)) ) else: return []
def expand_macro(self, formatter, name, args): from trac.config import ConfigSection, Option section_filter = key_filter = '' args, kw = parse_args(args) if args: section_filter = args.pop(0).strip() if args: key_filter = args.pop(0).strip() def getdoc(option_or_section): doc = to_unicode(option_or_section.__doc__) if doc: doc = dgettext(option_or_section.doc_domain, doc) return doc registry = ConfigSection.get_registry(self.compmgr) sections = dict((name, getdoc(section)) for name, section in registry.iteritems() if name.startswith(section_filter)) registry = Option.get_registry(self.compmgr) options = {} for (section, key), option in registry.iteritems(): if section.startswith(section_filter): options.setdefault(section, {})[key] = option sections.setdefault(section, '') def default_cell(option): default = option.default if default is True: default = 'true' elif default is False: default = 'false' elif default == 0: default = '0.0' if isinstance(default, float) else '0' elif default: default = ', '.join(to_unicode(val) for val in default) \ if isinstance(default, (list, tuple)) \ else to_unicode(default) else: return tag.td(_("(no default)"), class_='nodefault') return tag.td(tag.code(default), class_='default') return tag.div(class_='tracini')( (tag.h3(tag.code('[%s]' % section), id='%s-section' % section), format_to_html(self.env, formatter.context, section_doc), tag.table(class_='wiki')(tag.tbody( tag.tr( tag.td(tag.tt(option.name)), tag.td( format_to_oneliner(self.env, formatter.context, getdoc(option))), default_cell(option)) for option in sorted(options.get(section, {}).itervalues(), key=lambda o: o.name) if option.name.startswith(key_filter)))) for section, section_doc in sorted(sections.iteritems()))
def expand_macro(self, formatter, name, content): prefix = limit = None if content: argv = [arg.strip() for arg in content.split(',')] if len(argv) > 0: prefix = argv[0] if len(argv) > 1: limit = int(argv[1]) cursor = formatter.db.cursor() sql = 'SELECT name, ' \ ' max(version) AS max_version, ' \ ' max(time) AS max_time ' \ 'FROM wiki' args = [] if prefix: sql += ' WHERE name LIKE %s' args.append(prefix + '%') sql += ' GROUP BY name ORDER BY max_time DESC' if limit: sql += ' LIMIT %s' args.append(limit) cursor.execute(sql, args) entries_per_date = [] prevdate = None for name, version, ts in cursor: if not 'WIKI_VIEW' in formatter.perm('wiki', name, version): continue time = datetime.fromtimestamp(ts, utc) date = format_date(time) if date != prevdate: prevdate = date entries_per_date.append((date, [])) version = int(version) diff_href = None if version > 1: diff_href = formatter.href.wiki(name, action='diff', version=version) page_name = formatter.wiki.format_page_name(name) entries_per_date[-1][1].append((page_name, name, version, diff_href)) return tag.div([tag.h3(date) + tag.ul([tag.li(tag.a(page_name, href=formatter.href.wiki(name)), ' ', diff_href and tag.small('(', tag.a('diff', href=diff_href), ')') or None) for page_name, name, version, diff_href in entries]) for date, entries in entries_per_date])
def filter_stream(self, req, method, filename, stream, data): if req.path_info.startswith('/ticket/'): div = None if 'ticket' in data: # get parents data ticket = data['ticket'] # title div = tag.div(class_='description') if ticket['status'] != 'closed': link = tag.a(_('add'), href=req.href.newticket(parents=ticket.id), title=_('Create new child ticket')) link = tag.span('(', link, ')', class_='addsubticket') else: link = None div.append(tag.h3(_('Subtickets '), link)) if 'subtickets' in data: # table tbody = tag.tbody() div.append(tag.table(tbody, class_='subtickets')) # tickets def _func(children, depth=0): for id in sorted(children, key=lambda x: int(x)): ticket = Ticket(self.env, id) # 1st column attrs = {'href': req.href.ticket(id)} if ticket['status'] == 'closed': attrs['class_'] = 'closed' link = tag.a('#%s' % id, **attrs) summary = tag.td(link, ': %s' % ticket['summary'], style='padding-left: %dpx;' % (depth * 15)) # 2nd column type = tag.td(ticket['type']) # 3rd column status = tag.td(ticket['status']) # 4th column href = req.href.query(status='!closed', owner=ticket['owner']) owner = tag.td(tag.a(ticket['owner'], href=href)) tbody.append(tag.tr(summary, type, status, owner)) _func(children[id], depth + 1) _func(data['subtickets']) if div: add_stylesheet(req, 'subtickets/css/subtickets.css') stream |= Transformer('.//div[@id="ticket"]').append(div) return stream
def expand_macro(self, formatter, name, args): from trac.config import ConfigSection, Option section_filter = key_filter = '' args, kw = parse_args(args) if args: section_filter = args.pop(0).strip() if args: key_filter = args.pop(0).strip() def getdoc(option_or_section): doc = to_unicode(option_or_section.__doc__) if doc: doc = dgettext(option_or_section.doc_domain, doc) return doc registry = ConfigSection.get_registry(self.compmgr) sections = dict((name, getdoc(section)) for name, section in registry.iteritems() if name.startswith(section_filter)) registry = Option.get_registry(self.compmgr) options = {} for (section, key), option in registry.iteritems(): if section.startswith(section_filter): options.setdefault(section, {})[key] = option sections.setdefault(section, '') def default_cell(option): default = option.default if default is True: default = 'true' elif default is False: default = 'false' elif default == 0: default = '0.0' if isinstance(default, float) else '0' elif default: default = ', '.join(to_unicode(val) for val in default) \ if isinstance(default, (list, tuple)) \ else to_unicode(default) else: return tag.td(_("(no default)"), class_='nodefault') return tag.td(tag.code(default), class_='default') return tag.div(class_='tracini')( (tag.h3(tag.code('[%s]' % section), id='%s-section' % section), format_to_html(self.env, formatter.context, section_doc), tag.table(class_='wiki')(tag.tbody( tag.tr(tag.td(tag.tt(option.name)), tag.td(format_to_oneliner( self.env, formatter.context, getdoc(option))), default_cell(option)) for option in sorted(options.get(section, {}).itervalues(), key=lambda o: o.name) if option.name.startswith(key_filter)))) for section, section_doc in sorted(sections.iteritems()))
def expand_macro(self, formatter, name, content): from trac.wiki.formatter import system_message content = content.strip() if content else '' name_filter = content.strip('*') def get_macro_descr(): for macro_provider in formatter.wiki.macro_providers: names = list(macro_provider.get_macros() or []) if name_filter and not any( name.startswith(name_filter) for name in names): continue try: name_descriptions = [ (name, macro_provider.get_macro_description(name)) for name in names ] except Exception as e: yield system_message( _("Error: Can't get description for macro %(name)s", name=names[0]), e), names else: for descr, pairs in groupby(name_descriptions, key=lambda p: p[1]): if descr: if isinstance(descr, (tuple, list)): descr = dgettext(descr[0], to_unicode(descr[1])) \ if descr[1] else '' else: descr = to_unicode(descr) or '' if content == '*': descr = format_to_oneliner(self.env, formatter.context, descr, shorten=True) else: descr = format_to_html(self.env, formatter.context, descr) yield descr, [name for name, descr in pairs] return tag.div(class_='trac-macrolist')( (tag.h3(tag.code('[[', names[0], ']]'), id='%s-macro' % names[0]), len(names) > 1 and tag.p( tag.strong(_("Aliases:")), [tag.code(' [[', alias, ']]') for alias in names[1:]]) or None, description or tag.em(_("Sorry, no documentation found"))) for description, names in sorted(get_macro_descr(), key=lambda item: item[1][0]))
def expand_macro(self, formatter, name, content): prefix = limit = None if content: argv = [arg.strip() for arg in content.split(',')] if len(argv) > 0: prefix = argv[0] if len(argv) > 1: limit = int(argv[1]) cursor = formatter.db.cursor() sql = 'SELECT name, ' \ ' max(version) AS max_version, ' \ ' max(time) AS max_time ' \ 'FROM wiki' args = [] if prefix: sql += ' WHERE name LIKE %s' args.append(prefix + '%') sql += ' GROUP BY name ORDER BY max_time DESC' if limit: sql += ' LIMIT %s' args.append(limit) cursor.execute(sql, args) entries_per_date = [] prevdate = None for name, version, ts in cursor: if not 'WIKI_VIEW' in formatter.perm('wiki', name, version): continue date = format_date(from_utimestamp(ts)) if date != prevdate: prevdate = date entries_per_date.append((date, [])) version = int(version) diff_href = None if version > 1: diff_href = formatter.href.wiki(name, action='diff', version=version) page_name = formatter.wiki.format_page_name(name) entries_per_date[-1][1].append((page_name, name, version, diff_href)) return tag.div( (tag.h3(date), tag.ul( tag.li(tag.a(page, href=formatter.href.wiki(name)), ' ', diff_href and tag.small('(', tag.a('diff', href=diff_href), ')') or None) for page, name, version, diff_href in entries)) for date, entries in entries_per_date)
def expand_macro(self, formatter, name, content): args, kw = parse_args(content) prefix = args[0].strip() if args else None limit = _arg_as_int(args[1].strip(), min=1) if len(args) > 1 else None group = kw.get('group', 'date') sql = """SELECT name, max(version) AS max_version, max(time) AS max_time FROM wiki""" args = [] if prefix: with self.env.db_query as db: sql += " WHERE name %s" % db.prefix_match() args.append(db.prefix_match_value(prefix)) sql += " GROUP BY name ORDER BY max_time DESC" if limit: sql += " LIMIT %s" args.append(limit) entries_per_date = [] prevdate = None for name, version, ts in self.env.db_query(sql, args): if not 'WIKI_VIEW' in formatter.perm('wiki', name, version): continue req = formatter.req date = user_time(req, format_date, from_utimestamp(ts)) if date != prevdate: prevdate = date entries_per_date.append((date, [])) version = int(version) diff_href = None if version > 1: diff_href = formatter.href.wiki(name, action='diff', version=version) page_name = formatter.wiki.format_page_name(name) entries_per_date[-1][1].append((page_name, name, version, diff_href)) items_per_date = ( (date, (tag.li(tag.a(page, href=formatter.href.wiki(name)), tag.small(' (', tag.a(_("diff"), href=diff_href), ')') if diff_href else None, '\n') for page, name, version, diff_href in entries)) for date, entries in entries_per_date) if group == 'date': out = ((tag.h3(date), tag.ul(entries)) for date, entries in items_per_date) else: out = tag.ul(entries for date, entries in items_per_date) return tag.div(out)
def expand_macro(self, formatter, name, content): args, kw = parse_args(content) prefix = args[0].strip() if args else None limit = int(args[1].strip()) if len(args) > 1 else None group = kw.get('group', 'date') sql = """SELECT name, max(version) AS max_version, max(time) AS max_time FROM wiki""" args = [] if prefix: with self.env.db_query as db: sql += " WHERE name %s" % db.prefix_match() args.append(db.prefix_match_value(prefix)) sql += " GROUP BY name ORDER BY max_time DESC" if limit: sql += " LIMIT %s" args.append(limit) entries_per_date = [] prevdate = None for name, version, ts in self.env.db_query(sql, args): if not 'WIKI_VIEW' in formatter.perm('wiki', name, version): continue req = formatter.req date = user_time(req, format_date, from_utimestamp(ts)) if date != prevdate: prevdate = date entries_per_date.append((date, [])) version = int(version) diff_href = None if version > 1: diff_href = formatter.href.wiki(name, action='diff', version=version) page_name = formatter.wiki.format_page_name(name) entries_per_date[-1][1].append((page_name, name, version, diff_href)) items_per_date = ( (date, (tag.li(tag.a(page, href=formatter.href.wiki(name)), tag.small(' (', tag.a(_("diff"), href=diff_href), ')') if diff_href else None, '\n') for page, name, version, diff_href in entries)) for date, entries in entries_per_date) if group == 'date': out = ((tag.h3(date), tag.ul(entries)) for date, entries in items_per_date) else: out = tag.ul(entries for date, entries in items_per_date) return tag.div(out)
def expand_macro(self, formatter, name, content): from trac.config import ConfigSection, Option section_filter = key_filter = '' args, kw = parse_args(content) if args: section_filter = args.pop(0).strip() if args: key_filter = args.pop(0).strip() def getdoc(option_or_section): doc = to_unicode(option_or_section.__doc__) if doc: doc = dgettext(option_or_section.doc_domain, doc) return doc registry = ConfigSection.get_registry(self.compmgr) sections = dict((name, getdoc(section)) for name, section in registry.iteritems() if name.startswith(section_filter)) registry = Option.get_registry(self.compmgr) options = {} for (section, key), option in registry.iteritems(): if section.startswith(section_filter): options.setdefault(section, {})[key] = option sections.setdefault(section, '') def default_cell(option): default = option.default if default is not None and default != '': return tag.td(tag.code(option.dumps(default)), class_='default') else: return tag.td(_("(no default)"), class_='nodefault') return tag.div(class_='tracini')( (tag.h3(tag.code('[%s]' % section), id='%s-section' % section), format_to_html(self.env, formatter.context, section_doc), tag.table(class_='wiki')(tag.tbody( tag.tr(tag.td(tag.code(option.name)), tag.td( format_to_oneliner(self.env, formatter.context, getdoc(option))), default_cell(option), class_='odd' if idx % 2 else 'even') for idx, option in enumerate( sorted(options.get(section, {}).itervalues(), key=lambda o: o.name)) if option.name.startswith(key_filter)))) for section, section_doc in sorted(sections.iteritems()))
def expand_macro(self, formatter, name, content): from trac.config import ConfigSection, Option section_filter = key_filter = '' args, kw = parse_args(content) if args: section_filter = args.pop(0).strip() if args: key_filter = args.pop(0).strip() def getdoc(option_or_section): doc = to_unicode(option_or_section.__doc__) if doc: doc = dgettext(option_or_section.doc_domain, doc) return doc registry = ConfigSection.get_registry(self.compmgr) sections = dict((name, getdoc(section)) for name, section in registry.iteritems() if name.startswith(section_filter)) registry = Option.get_registry(self.compmgr) options = {} for (section, key), option in registry.iteritems(): if section.startswith(section_filter): options.setdefault(section, {})[key] = option sections.setdefault(section, '') def default_cell(option): default = option.default if default is not None and default != '': return tag.td(tag.code(option.dumps(default)), class_='default') else: return tag.td(_("(no default)"), class_='nodefault') return tag.div(class_='tracini')( (tag.h3(tag.code('[%s]' % section), id='%s-section' % section), format_to_html(self.env, formatter.context, section_doc), tag.table(class_='wiki')(tag.tbody( tag.tr(tag.td(tag.tt(option.name)), tag.td(format_to_oneliner( self.env, formatter.context, getdoc(option))), default_cell(option), class_='odd' if idx % 2 else 'even') for idx, option in enumerate(sorted(options.get(section, {}).itervalues(), key=lambda o: o.name)) if option.name.startswith(key_filter)))) for section, section_doc in sorted(sections.iteritems()))
def expand_macro(self, formatter, name, content): prefix = limit = None if content: argv = [arg.strip() for arg in content.split(',')] if len(argv) > 0: prefix = argv[0] if len(argv) > 1: limit = int(argv[1]) sql = """SELECT name, max(version) AS max_version, max(time) AS max_time FROM wiki""" args = [] if prefix: sql += " WHERE name LIKE %s" args.append(prefix + '%') sql += " GROUP BY name ORDER BY max_time DESC" if limit: sql += " LIMIT %s" args.append(limit) entries_per_date = [] prevdate = None for name, version, ts in self.env.db_query(sql, args): if not 'WIKI_VIEW' in formatter.perm('wiki', name, version): continue req = formatter.req date = user_time(req, format_date, from_utimestamp(ts)) if date != prevdate: prevdate = date entries_per_date.append((date, [])) version = int(version) diff_href = None if version > 1: diff_href = formatter.href.wiki(name, action='diff', version=version) page_name = formatter.wiki.format_page_name(name) entries_per_date[-1][1].append((page_name, name, version, diff_href)) return tag.div( (tag.h3(date), tag.ul( tag.li(tag.a(page, href=formatter.href.wiki(name)), ' ', diff_href and tag.small('(', tag.a('diff', href=diff_href), ')') or None) for page, name, version, diff_href in entries)) for date, entries in entries_per_date)
def _add_custom_field_tables(self, stream, ticket): ticket_body = Transformer('//div[@id="ticket"]') for table_info in self._get_table_fields(ticket): headers = table_info["headers"] columns = table_info["columns"] tickets = table_info["tickets"] table = tag.div( tag.h3(table_info["title"]), tag.table( tag.thead(self._get_header(headers)), tag.tbody(self._get_body(tickets, columns)), class_="listing tickets", ), ) stream = stream | ticket_body.after(table) return stream
def expand_macro(self, formatter, name, args): from trac.config import ConfigSection, Option section_filter = key_filter = '' args, kw = parse_args(args) if args: section_filter = args.pop(0).strip() if args: key_filter = args.pop(0).strip() registry = ConfigSection.get_registry(self.compmgr) sections = dict( (name, dgettext(section.doc_domain, to_unicode(section.__doc__))) for name, section in registry.iteritems() if name.startswith(section_filter)) registry = Option.get_registry(self.compmgr) options = {} for (section, key), option in registry.iteritems(): if section.startswith(section_filter): options.setdefault(section, {})[key] = option sections.setdefault(section, '') return tag.div(class_='tracini')( (tag.h3(tag.code('[%s]' % section), id='%s-section' % section), format_to_html(self.env, formatter.context, section_doc), tag.table(class_='wiki')(tag.tbody( tag.tr( tag.td(tag.tt(option.name)), tag.td( format_to_oneliner( self.env, formatter.context, dgettext(option.doc_domain, to_unicode(option.__doc__)))), tag.td( tag.code(option.default or 'false') if option.default or option.default is False else _("(no default)"), class_='default' if option.default or option.default is False else 'nodefault')) for option in sorted(options.get(section, {}).itervalues(), key=lambda o: o.name) if option.name.startswith(key_filter)))) for section, section_doc in sorted(sections.iteritems()))
def expand_macro(self, formatter, name, args): from trac.config import ConfigSection, Option section_filter = key_filter = '' args, kw = parse_args(args) if args: section_filter = args.pop(0).strip() if args: key_filter = args.pop(0).strip() registry = ConfigSection.get_registry(self.compmgr) sections = dict((name, dgettext(section.doc_domain, to_unicode(section.__doc__))) for name, section in registry.iteritems() if name.startswith(section_filter)) registry = Option.get_registry(self.compmgr) options = {} for (section, key), option in registry.iteritems(): if section.startswith(section_filter): options.setdefault(section, {})[key] = option sections.setdefault(section, '') return tag.div(class_='tracini')( (tag.h3(tag.code('[%s]' % section), id='%s-section' % section), format_to_html(self.env, formatter.context, section_doc), tag.table(class_='wiki')(tag.tbody( tag.tr(tag.td(tag.tt(option.name)), tag.td(format_to_oneliner( self.env, formatter.context, dgettext(option.doc_domain, to_unicode(option.__doc__)))), tag.td(tag.code(option.default or 'false') if option.default or option.default is False else _("(no default)"), class_='default' if option.default or option.default is False else 'nodefault')) for option in sorted(options.get(section, {}).itervalues(), key=lambda o: o.name) if option.name.startswith(key_filter)))) for section, section_doc in sorted(sections.iteritems()))
def filter_stream(self, req, method, filename, stream, data): if filename == "ticket.html" and 'ticket' in data: ticket = data['ticket'] if ticket.id > 0 and self._have_schedule(ticket): add_stylesheet(req, 'ticketrelation/css/schedule.css') schedule = self._get_schedule_info(ticket) stream |= Transformer('//div[@id="ticket"]').after( tag.div( tag.h3( tag.a('Schedule', id='schedule_label', href='#schedule_label'), class_='foldable'), tag.div(tag.schedule(**{':schedule': 'schedule', ':config': 'config'}), class_='schedule_container', id='schedule_container'), id='schedule') ) config = { 'url': req.base_url, 'startDate': None, 'finishDate': None, 'showUnavailable': 1 } stream |= Transformer('//body').append(tag.script(""" $(window).load(function() { var data = %s; var config = %s; var app = new Vue({ el: '#schedule_container', data: { schedule: data, config: config, } }); }); """ % (json.dumps(schedule, cls=DateTimeEncoder), json.dumps(config, cls=DateTimeEncoder)))) return stream
def filter_stream(self, req, method, filename, stream, data): if not req.path_info.startswith('/ticket/'): return stream div = None link = None button = None 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) 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') 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: # table tbody = tag.tbody() div.append(tag.table(tbody, class_='subtickets')) # tickets self._create_subtickets_table(req, data['subtickets'], tbody) 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
def filter_stream(self, req, method, filename, stream, data): if req.path_info.startswith('/ticket/'): div = None if 'ticket' in data: # get parents data ticket = data['ticket'] # title div = tag.div(class_='description') if ticket['status'] != 'closed': link = tag.a(_('add'), href=req.href.newticket(parents=ticket.id), title=_('Create new child ticket')) link = tag.span('(', link, ')', class_='addsubticket') else: link = None div.append(tag.h3(_('Subtickets '), link)) if 'subtickets' in data: # table tbody = tag.tbody() div.append(tag.table(tbody, class_='subtickets')) # tickets def _func(children, depth=0): def _sort(children): for sort in reversed(literal_eval(self.sort_children)): transform_key = lambda x: x if isinstance(sort, str): sort_by = sort else: assert(isinstance(sort, list)) assert(isinstance(sort[0], str)) sort_by = sort[0] if isinstance(sort[1], str): if sort[1] == "int": transform_key = int else: assert(sort[1] == "float") transform_key = float else: assert(isinstance(sort[1], list)) lookup_dict = {v: k for (k, v) in enumerate(sort[1])} def _lookup(value): try: return lookup_dict[value] except KeyError: return len(lookup_dict) transform_key = _lookup if sort_by == 'id': children = sorted(children, key=lambda x: transform_key(Ticket(self.env, int(x)).id)) else: children = sorted(children, key=lambda x: transform_key(Ticket(self.env, int(x))[sort_by])) return children for id in _sort(children): ticket = Ticket(self.env, id) properties_to_show = [] # 1st column attrs = {'href': req.href.ticket(id)} if ticket['status'] == 'closed': attrs['class_'] = 'closed' link = tag.a('#%s' % id, **attrs) properties_to_show.append(tag.td(link, ': %s' % ticket['summary'], style='padding-left: %dpx;' % (depth * 15))) for property in literal_eval(self.show_fields): properties_to_show.append(tag.td(ticket[property])) tbody.append(apply(tag.tr, properties_to_show)) _func(children[id], depth + 1) _func(data['subtickets']) if div: add_stylesheet(req, 'subtickets/css/subtickets.css') stream |= Transformer('.//div[@id="ticket"]').append(div) div_accumulations = None accumulations = literal_eval(self.show_accumulations) if 'subtickets' in data and accumulations: def _accumulate(children, field, method): assert(method == 'sum') result = 0 for id in children: ticket = Ticket(self.env, id) try: result += float(ticket[field]) except ValueError: pass result += _accumulate(children[id], field, method) return result div_accumulations = tag.div(class_='description') tbody = tag.tbody() div_accumulations(tag.table(tbody, class_='properties')) for accumulation in accumulations: tbody.append(tag.tr(tag.td(accumulation[1]), tag.td(_accumulate(data['subtickets'], accumulation[0], accumulation[2])))) if div_accumulations: stream |= Transformer('.//div[@id="ticket"]').append(div_accumulations) return stream
def get_box(self, req): if req.authname == "anonymous": return # hoped that ITicketGroupStatsProvider should tell us what the # set of "active" statuses, but seems # not. DefaultTicketGroupStatsProvider has an idea, from the # ini file, but we want to provide new grouping from the # LogicaOrderTracker module so it has to be at the interface # level rather than component level. db = self.env.get_read_db() cursor = db.cursor() counts_ul = tag.ul() cursor.execute("""SELECT status, COUNT(status) FROM ticket WHERE owner = %s GROUP BY status ORDER BY CASE WHEN status = 'assigned' THEN 1 ELSE 0 END DESC, CASE WHEN status = 'closed' THEN 1 ELSE 0 END ASC, status ASC""", (req.authname,)) for status, count in cursor: link = tag(tag.span(class_="ticket-state state-" + status), tag.a(status,href=req.href.query(owner=req.authname, status=status))) counts_ul.append(tag.li(link, ": ", count)) recent_ul = tag.ul() cursor.execute("""SELECT id FROM ticket WHERE ticket.owner = %s GROUP BY id ORDER BY max(changetime) DESC""", (req.authname,)) shown_count = 0 ts = TicketSystem(self.env) for ticket, in cursor: resource = Resource('ticket', ticket) if "TICKET_VIEW" in req.perm(resource): shown_count = shown_count + 1 if shown_count > 5: break compact = ts.get_resource_description(resource, 'compact') summary = ts.get_resource_description(resource, 'summary') link = tag.a(tag.strong(compact), " ", tag.span(summary), href=req.href.ticket(ticket)) recent_ul.append(tag.li(link)) return tag( tag.div( tag.h3( tag.i( class_="fa fa-ticket" ), " Your Ticket Counts" ), counts_ul, class_='box-sidebar color-none', id="sidebar-count" ), tag.div( tag.h3( tag.i( class_="fa fa-star" ), " Your Recently Modified Tickets" ), recent_ul, class_='box-sidebar', id="sidebar-recent" ) )
def get_timeline_markup(self, req, call, maxrows=10): """ Generates the markup needed when this component is called both explicitly inside wiki pages and implicitly by the ISideBarBoxProvider. Note this code uses methods from the trac TimelineModule module. """ chrome = Chrome(self.env) # last 14 days should be enough stop = datetime.now(req.tz) start = stop - timedelta(days=50) # use code from trac/timeline to generate event data timeline = TimelineModule(self.env) available_filters, filters = timeline.get_filters(req) include_authors, exclude_authors = timeline.authors() events = timeline.get_events(req, start, stop, filters, available_filters, include_authors, exclude_authors, maxrows) show_gravatar = self.config.get('avatar','mode').lower() != 'off' # create the mark up context = Context.from_request(req) event_list = tag.ul(class_="timeline-list no-style") for event in events: event_title = event['render']('title', context) event_url = event['render']('url', context) event_list.append(tag.li( show_gravatar and tag.img( src=req.href.avatar(event['author'] if event['author'] else 'anonymous'), class_="avatar", ) or "", tag.span( chrome.authorinfo(req, event['author']), class_="author" ), tag.span( pretty_age(event['date']), class_="date", ), tag.div( tag.i(class_="event-type fa fa-" + event['kind']), tag.a( event_title, href=event_url, ), class_="event-summary" ), class_="cf" )) # if the markup is being generated via ISideBarBoxProvider we don't # need to add a span3 class div_classes = "box-sidebar" if call == "macro": div_classes += " span3 right" return tag.div( tag.h3( tag.i( class_="fa fa-calendar" ), " Recent Project Activity" ), event_list, class_=div_classes, )
def filter_stream(self, req, method, filename, stream, data): # Tickets will be modified to show the child tickets as a list under the 'Description' section. if filename == 'ticket.html': # Get the ticket info. ticket = data.get('ticket') # Modify ticket.html with sub-ticket table, create button, etc... # As follows: # - If ticket has no child tickets and child tickets are NOT allowed then skip. # - If ticket has child tickets and child tickets are NOT allowed (ie. rules changed or ticket type changed after children were assigned), # print list of tickets but do not allow any tickets to be created. # - If child tickets are allowed then print list of child tickets or 'No Child Tickets' if non are currently assigned. # if ticket and ticket.exists: filter = Transformer('//div[@class="description"]') snippet = tag() # Are there any child tickets to display? childtickets = [ Ticket(self.env,n) for n in self.env.childtickets.get(ticket.id,[]) ] # (tempish) fix for #8612 : force sorting by ticket id childtickets = sorted(childtickets, key=lambda t: t.id) # trac.ini : Which columns to display in child ticket listing? columns = self.config.getlist('childtickets', 'parent.%s.table_headers' % ticket['type'], default=['summary','owner']) # trac.ini : child tickets are allowed. if self.config.getbool('childtickets', 'parent.%s.allow_child_tickets' % ticket['type']): # trac.ini : Default 'type' of child tickets? default_child_type = self.config.get('childtickets', 'parent.%s.default_child_type' % ticket['type'], default=self.config.get('ticket','default_type')) self.env.log.debug("TracchildticketsModule : default_child_type: %s" % default_child_type) # Can user create a new ticket? If not, just display title (ie. no 'create' button). if 'TICKET_CREATE' in req.perm(ticket.resource): # Always pass these fields default_child_fields = ( tag.input(type="hidden", name="parent", value='#'+str(ticket.id)), ) #Pass extra fields defined in inherit parameter of parent inherited_child_fields = [ tag.input(type="hidden",name="%s"%field,value=ticket[field]) for field in self.config.getlist('childtickets','parent.%s.inherit' % ticket['type']) ] # If child types are restricted then create a set of buttons for the allowed types (This will override 'default_child_type). restrict_child_types = self.config.getlist('childtickets','parent.%s.restrict_child_type' % ticket['type'],default=[]) if not restrict_child_types: # ... create a default submit button submit_button_fields = ( tag.input(type="submit",name="childticket",value="New Child Ticket",title="Create a child ticket"), tag.input(type="hidden", name="type", value=default_child_type), ) else: submit_button_fields = [ tag.input(type="submit",name="type",value="%s" % ticket_type,title="Create a %s child ticket" % ticket_type) for ticket_type in restrict_child_types ] snippet.append(tag.div( tag.form( tag.div( default_child_fields, inherited_child_fields, submit_button_fields, class_="inlinebuttons"), method="get", action=req.href.newticket(), ), tag.h3("Child Tickets",id="comment:child_tickets"), )) else: snippet.append(tag.div(tag.h3("Child Tickets",id="comment:child_tickets"))) # trac.ini : child tickets are NOT allowed but (somehow?!) this parent ticket has children assigned. elif childtickets: snippet.append(tag.div(tag.h3("Child Tickets",id="comment:child_tickets"))) # Test if the ticket has children: If so, then list in pretty table. if childtickets: snippet.append( tag.div( tag.table( tag.thead( tag.tr( tag.th("Ticket",class_="id"), [ tag.th(s.title(),class_=s) for s in columns ]) ), tag.tbody([ self._table_row(req,tkt,columns) for tkt in childtickets ]), class_="listing tickets", ), ) ) elif self.config.getbool('childtickets', 'parent.%s.allow_child_tickets' % ticket['type']): snippet.append(tag.div(tag.p("NO SUB-TICKETS."))) return stream | filter.append(snippet) return stream
def filter_stream(self, req, method, filename, stream, data): # Tickets will be modified to show the child tickets as a list under the 'Description' section. if filename == 'ticket.html': # Get the ticket info. ticket = data.get('ticket') # Modify ticket.html with sub-ticket table, create button, etc... # As follows: # - If ticket has no child tickets and child tickets are NOT allowed then skip. # - If ticket has child tickets and child tickets are NOT allowed (ie. rules changed or ticket type changed after children were assigned), # print list of tickets but do not allow any tickets to be created. # - If child tickets are allowed then print list of child tickets or 'No Child Tickets' if non are currently assigned. # if ticket and ticket.exists: filter = Transformer('//div[@class="description"]') snippet = tag() # Are there any child tickets to display? childtickets = [ Ticket(self.env, n) for n in self.env.childtickets.get(ticket.id, []) ] # (tempish) fix for #8612 : force sorting by ticket id childtickets = sorted(childtickets, key=lambda t: t.id) # trac.ini : Which columns to display in child ticket listing? columns = self.config.getlist('childtickets', 'parent.%s.table_headers' % ticket['type'], default=['summary', 'owner']) # trac.ini : child tickets are allowed. if self.config.getbool( 'childtickets', 'parent.%s.allow_child_tickets' % ticket['type']): # trac.ini : Default 'type' of child tickets? default_child_type = self.config.get( 'childtickets', 'parent.%s.default_child_type' % ticket['type'], default=self.config.get('ticket', 'default_type')) self.env.log.debug( "TracchildticketsModule : default_child_type: %s" % default_child_type) # Can user create a new ticket? If not, just display title (ie. no 'create' button). if 'TICKET_CREATE' in req.perm(ticket.resource): # Always pass these fields default_child_fields = (tag.input(type="hidden", name="parent", value='#' + str(ticket.id)), ) #Pass extra fields defined in inherit parameter of parent inherited_child_fields = [ tag.input(type="hidden", name="%s" % field, value=ticket[field]) for field in self.config.getlist( 'childtickets', 'parent.%s.inherit' % ticket['type']) ] # If child types are restricted then create a set of buttons for the allowed types (This will override 'default_child_type). restrict_child_types = self.config.getlist( 'childtickets', 'parent.%s.restrict_child_type' % ticket['type'], default=[]) if not restrict_child_types: # ... create a default submit button submit_button_fields = ( tag.input(type="submit", name="childticket", value="New Child Ticket", title="Create a child ticket"), tag.input(type="hidden", name="type", value=default_child_type), ) else: submit_button_fields = [ tag.input(type="submit", name="type", value="%s" % ticket_type, title="Create a %s child ticket" % ticket_type) for ticket_type in restrict_child_types ] snippet.append( tag.div( tag.form( tag.div(default_child_fields, inherited_child_fields, submit_button_fields, class_="inlinebuttons"), method="get", action=req.href.newticket(), ), tag.h3("Child Tickets", id="comment:child_tickets"), )) else: snippet.append( tag.div( tag.h3("Child Tickets", id="comment:child_tickets"))) # trac.ini : child tickets are NOT allowed but (somehow?!) this parent ticket has children assigned. elif childtickets: snippet.append( tag.div( tag.h3("Child Tickets", id="comment:child_tickets"))) # Test if the ticket has children: If so, then list in pretty table. if childtickets: snippet.append( tag.div( tag.table( tag.thead( tag.tr(tag.th("Ticket", class_="id"), [ tag.th(s.title(), class_=s) for s in columns ])), tag.tbody([ self._table_row(req, tkt, columns) for tkt in childtickets ]), class_="listing tickets", ), )) elif self.config.getbool( 'childtickets', 'parent.%s.allow_child_tickets' % ticket['type']): snippet.append(tag.div(tag.p("NO SUB-TICKETS."))) return stream | filter.append(snippet) return stream
def _expand_recent_topics(self, formatter, name, content): self.log.debug("Rendering RecentTopics macro...") # Check permission if not formatter.perm.has_permission('DISCUSSION_VIEW'): return # Create request context. context = Context.from_request(formatter.req) context.realm = 'discussion-wiki' # Check if TracTags plugin is enabled. context.has_tags = is_tags_enabled(self.env) # Get database access. db = self.env.get_db_cnx() context.cursor = db.cursor() # Get API object. api = self.env[DiscussionApi] # Get list of Trac users. context.users = api.get_users(context) # Parse macro arguments. arguments = [] forum_id = None limit = 10 if content: arguments = [argument.strip() for argument in content.split(',')] if len(arguments) == 1: limit = arguments[0] elif len(arguments) == 2: forum_id = arguments[0] limit = arguments[1] else: raise TracError("Invalid number of macro arguments.") # Construct and execute SQL query. columns = ('forum', 'topic', 'time') values = [] if forum_id: values.append(forum_id) if limit: values.append(limit) values = tuple(values) sql = ("SELECT forum, topic, MAX(time) as max_time " "FROM " " (SELECT forum, topic, time " " FROM message " " UNION " " SELECT forum, id as topic, time " " FROM topic)" + (forum_id and " WHERE forum = %s" or "") + " GROUP BY topic " " ORDER BY max_time DESC" + (limit and " LIMIT %s" or "")) self.log.debug(sql % values) context.cursor.execute(sql, values) # Collect recent topics. entries = [] for row in context.cursor: row = dict(zip(columns, row)) entries.append(row) self.log.debug(entries) # Format entries data. entries_per_date = [] prevdate = None for entry in entries: date = format_date(entry['time']) if date != prevdate: prevdate = date entries_per_date.append((date, [])) forum_name = api.get_forum(context, entry['forum'])['name'] topic_subject = api.get_topic_subject(context, entry['topic']) entries_per_date[-1][1].append((entry['forum'], forum_name, entry['topic'], topic_subject)) # Format result. return tag.div((tag.h3(date), tag.ul(tag.li(tag.a(forum_name, href = formatter.href.discussion('forum', forum_id)), ': ', tag.a( topic_subject, href = formatter.href.discussion('topic', topic_id))) for forum_id, forum_name, topic_id, topic_subject in entries)) for date, entries in entries_per_date)
def expand_macro(self, formatter, name, content): from trac.config import ConfigSection, Option args, kw = parse_args(content) filters = {} for name, index in (('section', 0), ('option', 1)): pattern = kw.get(name, '').strip() if pattern: filters[name] = fnmatch.translate(pattern) continue prefix = args[index].strip() if index < len(args) else '' if prefix: filters[name] = re.escape(prefix) has_option_filter = 'option' in filters for name in ('section', 'option'): filters[name] = re.compile(filters[name], re.IGNORECASE).match \ if name in filters \ else lambda v: True section_filter = filters['section'] option_filter = filters['option'] section_registry = ConfigSection.get_registry(self.compmgr) option_registry = Option.get_registry(self.compmgr) options = {} for (section, key), option in option_registry.iteritems(): if section_filter(section) and option_filter(key): options.setdefault(section, {})[key] = option if not has_option_filter: for section in section_registry: if section_filter(section): options.setdefault(section, {}) for section in options: options[section] = sorted(options[section].itervalues(), key=lambda option: option.name) sections = [(section, section_registry[section].doc if section in section_registry else '') for section in sorted(options)] def default_cell(option): default = option.default if default is not None and default != '': return tag.td(tag.code(option.dumps(default)), class_='default') else: return tag.td(_("(no default)"), class_='nodefault') def options_table(section, options): if options: return tag.table(class_='wiki')( tag.tbody( tag.tr( tag.td(tag.a(tag.code(option.name), class_='tracini-option', href='#%s-%s-option' % (section, option.name))), tag.td(format_to_html(self.env, formatter.context, option.doc)), default_cell(option), id='%s-%s-option' % (section, option.name), class_='odd' if idx % 2 else 'even') for idx, option in enumerate(options))) return tag.div(class_='tracini')( (tag.h3(tag.code('[%s]' % section), id='%s-section' % section), format_to_html(self.env, formatter.context, section_doc), options_table(section, options.get(section))) for section, section_doc in sections)
def filter_stream(self, req, method, filename, stream, data): # Tickets will be modified to show the child tickets as a list under the 'Description' section. if filename == 'ticket.html': # Get the ticket info. ticket = data.get('ticket') # Modify ticket.html with sub-ticket table, create button, etc... # As follows: # - If ticket has no child tickets and child tickets are NOT allowed then skip. # - If ticket has child tickets and child tickets are NOT allowed (ie. rules changed or ticket type changed after children were assigned), # print list of tickets but do not allow any tickets to be created. # - If child tickets are allowed then print list of child tickets or 'No Child Tickets' if non are currently assigned. # if ticket and ticket.exists: filter = Transformer('//div[@class="description"]') snippet = tag() # Are there any child tickets to display? childtickets = [ Ticket(self.env, n) for n in self.env.childtickets.get(ticket.id, []) ] # trac.ini : Which columns to display in child ticket listing? columns = self.config.getlist('childtickets', 'parent.%s.table_headers' % ticket['type'], default = ['summary', 'owner']) # Forcing default setting to inherit from the parent ticket default_child_milestone = ticket['milestone'] default_child_type = ticket['type'] # Test if the ticket has children: If so, then list in pretty table. if childtickets: snippet.append( tag.div(tag.h3("Child Tickets:", id = "comment:child_tickets"), tag.table( tag.thead( tag.tr( tag.th("Ticket", class_ = "id"), [ tag.th(s.title(), class_ = s) for s in columns ]) ), tag.tbody([ self._table_row(req, tkt, columns) for tkt in childtickets ]), class_ = "listing", ), ) ) else: snippet.append(tag.div(tag.h3("Child Tickets:", id = "comment:child_tickets"), tag.p("NO SUB-TICKETS."))) # Can user create a new ticket? If not, just display title (ie. no 'create' button). if 'TICKET_CREATE' in req.perm(ticket.resource): snippet.append( tag.div( tag.form( tag.div( tag.span( tag.input(type = "hidden", name = "parent", value = '#' + str(ticket.id)), tag.input(type = "hidden", name = "milestone", value = default_child_milestone), tag.input(type = "hidden", name = "type", value = default_child_type), tag.input(type = "submit", name = "childticket", value = "Create", title = "Create a child ticket"), class_ = "primaryButton"), class_ = "buttons"), method = "get", action = req.href.newticket() ) ) ) return stream | filter.append(snippet) return stream