def expand_macro(self, formatter, name, content, args): title = 'Color Scheme' classes = 'colormacro' if args and 'title' in args: title = args['title'] if args and 'class' in args: classes += ' ' + args['class'] tbody = [] have_comment = False colors = self._parse_arguments(content) for color in colors: if len(color['title']) > 0: have_comment = True ## Create row tbody.append( [ tag.td()(tag.strong(color['title'])), tag.td( style='background-color:' + color['orig'] )( tag.div(style='color: black')(color['hex']), tag.div(style='color: white')(color['hex']) ), tag.td( style='background-color:' + color['orig'] )( tag.div(style='color: black')(color['rgbp']), tag.div(style='color: white')(color['rgbp']) ), ] ) ## end for loop if len(tbody) > 0: colcount = len(tbody[0]) if not have_comment: colcount -= 1 table = tag.table(class_=classes) table()(tag.thead()(tag.th(colspan='%d' % colcount)(title))) ## Attach row in table. if have_comment: table()(tag.tbody(class_='colorlist')([tag.tr(row) for row in tbody])) else: table()(tag.tbody(class_='colorlist')([tag.tr(row[1:]) for row in tbody])) return table; else: return tag.div(class_='colormacro')('Nothing to display')
def render(self, context, mimetype, content, filename=None, url=None): content = content.read() content = re.split('\r[\n]', content) if not content: return None head = content[0] if not head: return None head = re.split(',', head) if not head: return None thead = tag.thead(tag.tr([tag.th(h) for h in head])) content = content[1:] if not content: return None tbody = [] for r in content: if r: r = re.split(',', r) if r: tbody.append(tag.tr([tag.td(c) for c in r ])) return tag.table(thead,tag.tbody(tbody), class_="wiki")
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))
def expand_macro(self, formatter, name, content): from trac.mimeview.api import Mimeview mime_map = Mimeview(self.env).mime_map mime_type_filter = '' args, kw = parse_args(content) if args: mime_type_filter = args.pop(0).strip().rstrip('*') mime_types = {} for key, mime_type in mime_map.iteritems(): if (not mime_type_filter or mime_type.startswith(mime_type_filter)) and key != mime_type: mime_types.setdefault(mime_type, []).append(key) return tag.div(class_='mimetypes')( tag.table(class_='wiki')( tag.thead(tag.tr( tag.th(_("MIME Types")), # always use plural tag.th(tag.a("WikiProcessors", href=formatter.context.href.wiki( 'WikiProcessors'))))), tag.tbody( tag.tr(tag.th(tag.tt(mime_type), style="text-align: left"), tag.td(tag.code( ' '.join(sorted(mime_types[mime_type]))))) for mime_type in sorted(mime_types.keys()))))
def expand_macro(self, formatter, name, content, args={}): self.href = formatter.req.href db = self.env.get_db_cnx() cursor = db.cursor() params = [x.strip() for x in content.split(',')] pages = [x for x in params if '=' not in x] kwargs = dict([x.split('=', 1) for x in params if '=' in x]) if not pages: pages = ['*'] order = kwargs.get('order') != 'reverse' and 'DESC' or '' self.date_format = kwargs.get('date_format', '%Y-%m-%d %H:%M:%S') self.pagename = kwargs.get('pagename') cursor.execute( 'SELECT name, time, author, version, comment FROM wiki AS w1' + ' WHERE version=(SELECT MAX(version) FROM wiki AS w2 WHERE w1.name=w2.name) AND (' + ' OR '.join(['name LIKE "%s"' % x.replace('*', '%') for x in pages]) + ')' + ' ORDER BY time ' + order) rows = [self._build_row(*x) for x in cursor] return tag.table(self.thead, tag.tbody(rows), class_='wikistatuslist', bgcolor=kwargs.get('bgcolor', '#F0F0F0'))
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 _render_source(self, context, stream, annotations): from trac.web.chrome import add_warning annotators, labels, titles = {}, {}, {} for annotator in self.annotators: atype, alabel, atitle = annotator.get_annotation_type() if atype in annotations: labels[atype] = alabel titles[atype] = atitle annotators[atype] = annotator annotations = [a for a in annotations if a in annotators] if isinstance(stream, list): stream = HTMLParser(StringIO(u"\n".join(stream))) elif isinstance(stream, unicode): text = stream def linesplitter(): for line in text.splitlines(True): yield TEXT, line, (None, -1, -1) stream = linesplitter() annotator_datas = [] for a in annotations: annotator = annotators[a] try: data = (annotator, annotator.get_annotation_data(context)) except TracError as e: self.log.warning("Can't use annotator '%s': %s", a, e) add_warning( context.req, tag.strong( tag_("Can't use %(annotator)s annotator: %(error)s", annotator=tag.em(a), error=tag.pre(e)) ), ) data = None, None annotator_datas.append(data) def _head_row(): return tag.tr( [tag.th(labels[a], class_=a, title=titles[a]) for a in annotations] + [tag.th(u"\xa0", class_="content")] ) def _body_rows(): for idx, line in enumerate(_group_lines(stream)): row = tag.tr() for annotator, data in annotator_datas: if annotator: annotator.annotate_row(context, row, idx + 1, line, data) else: row.append(tag.td()) row.append(tag.td(line)) yield row return tag.table(class_="code")(tag.thead(_head_row()), tag.tbody(_body_rows()))
def expand_macro(self, formatter, name, args): if not args: return Markup() config = None if name == self.CONFIG_KEY: lines = args.splitlines() if not lines or not lines[0].startswith('#!'): return Markup() config = self._parse_config([i.strip() for i in lines[0][2:].split(',')]) else: config = self.CONFIG[name] if not config: return Markup() def to_html(text): if not text: return '' return Markup('<br>'.join([format_to_oneliner(self.env, formatter.context, line) \ for line in text.splitlines()])) def has_keys(dict, keys): for key in keys: if dict.has_key(key): return True return False rows = self.parse_doc(args) if not rows: return Markup() seen = [] for desc, keys in config: if [row for row in rows if has_keys(row, keys)]: seen.append(desc) thead = tag.thead() for desc, keys in config: if not desc in seen: continue thead(tag.td(tag.b(desc))) tbody = tag.tbody() for row in rows: trow = tag.tr() for desc, keys in config: if not desc in seen: continue tcol = tag.td() for key in keys: if row.has_key(key): tcol(to_html(row[key])) trow(tcol) tbody(trow) return tag.table([thead, tbody], class_='wiki')
def _render_mergeinfo(self, name, mode, context, props): rows = [] for row in props[name].splitlines(): try: (path, revs) = row.rsplit(":", 1) rows.append([tag.td(path), tag.td(revs.replace(",", u",\u200b"))]) except ValueError: rows.append(tag.td(row, colspan=2)) return tag.table(tag.tbody([tag.tr(row) for row in rows]), class_="props")
def render_refs_box(self, req, ids, order = 'year', desc = 1, headhref=False, path=[],args=[],page=None,max=None ): # Check parameters if not ids: return [] columns = self.env.config.get('zotero', 'columns','firstCreator, year, publicationTitle, title' ) columns = columns.split(',') columns = [c.strip() for c in columns] model = ZoteroModelProvider(self.env) if page: page = (page-1)*max data = model.get_item_columns_by_iids(ids,columns, order, desc = desc, offset=page,limit=max) apath = args_path(args) heads = [] for idx, column in enumerate(columns): label = column if zotero_fields_mapping_name.has_key(column): label = zotero_fields_mapping_name[column]['label'] if headhref and path: head = [] th_class = '' th_href = req.href(path, order=column)+apath if order == column: if desc: th_class = 'desc' else: th_class = 'asc' th_href = req.href(path, order=column, desc = str(1))+apath head = tag.th(tag.a(label, href = th_href),class_= th_class) heads.append(head) else: heads.append(tag.th(label)) body = [] for idx, item in enumerate(data): item_class = 'even' if idx % 2 == 1: item_class = 'odd' item_td = [] for idx, col in enumerate(columns): col_td = [] if not col or item[idx+1] == 'None': col_td = tag.td() elif col == 'title': col_td = tag.td(tag.a(item[idx+1], href = req.href.zotero('item',str(item[0])))) else: col_td = tag.td(item[idx+1]) item_td.append(col_td) item_tr = tag.tr( item_td,class_=item_class) body.append(item_tr) return tag.table( tag.thead( heads ), tag.tbody(body), class_="listing dirlist", id="dirlist")
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, 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, '') 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, dgettext(option.doc_domain, to_unicode(option.__doc__)))), 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 _render_mergeinfo(self, name, mode, context, props): rows = [] for row in props[name].splitlines(): try: (path, revs) = row.rsplit(':', 1) rows.append([tag.td(path), tag.td(revs.replace(',', u',\u200b'))]) except ValueError: rows.append(tag.td(row, colspan=2)) return tag.table(tag.tbody([tag.tr(row) for row in rows]), class_='props')
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 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)))
def expand_macro(self, formatter, name, content, args): title = 'Color Gradient' classes = 'colorgradient'; if args and 'title' in args: title = args['title'] if args and 'class' in args: classes += ' ' + args['class'] colors = self._parse_arguments(content) lastcolor = {} tbody = [] for color in colors: if 'orig' in lastcolor: tbody.append(self._create_gradient(lastcolor, color)) tbody.append([ tag.td( style='background-color:' + color['orig'] )( tag.div(style='color: black')(color['hex']), tag.div(style='color: white')(color['hex']) ), tag.td( style='background-color:' + color['orig'] )( tag.div(style='color: black')(color['rgbp']), tag.div(style='color: white')(color['rgbp']) ) ]) lastcolor = color ## end for loop if len(tbody) > 0: table = tag.table(class_=classes) table()(tag.thead()(tag.th(colspan="2")(title))) table()(tag.tbody(class_='colorgradient')([tag.tr()(td) for td in tbody])) return table; else: return tag.div(class_='colorgradient')('Nothing to display')
def expand_macro(self, formatter, name, content): content = content.strip() if content else '' name_filter = content.strip('*') items = {} for subscriber in NotificationSystem(self.env).subscribers: name = subscriber.__class__.__name__ if not name_filter or name.startswith(name_filter): items[name] = subscriber.description() return tag.div(class_='trac-subscriberlist')(tag.table(class_='wiki')( tag.thead(tag.tr(tag.th(_("Subscriber")), tag.th(_("Description")))), tag.tbody( tag.tr(tag.td(tag.code(name)), tag.td(items[name]), class_='odd' if idx % 2 else 'even') for idx, name in enumerate(sorted(items.keys())))))
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, content): content = content.strip() if content else '' name_filter = content.strip('*') items = {} for subscriber in NotificationSystem(self.env).subscribers: name = subscriber.__class__.__name__ if not name_filter or name.startswith(name_filter): items[name] = subscriber.description() return tag.div(class_='trac-subscriberlist')( tag.table(class_='wiki')( tag.thead(tag.tr( tag.th(_("Subscriber")), tag.th(_("Description")))), tag.tbody( tag.tr(tag.td(tag.code(name)), tag.td(items[name]), class_='odd' if idx % 2 else 'even') for idx, name in enumerate(sorted(items.keys())))))
def expand_macro(self, formatter, name, content): if self.ACCESS_KEY is None: return tag.p("[EC2 access key is missing from trac.ini]") if self.SECRET_KEY is None: return tag.p("[EC2 secret key is missing from trac.ini]") ec2 = EC2Connection(self.ACCESS_KEY, self.SECRET_KEY) headings = ("Instance", "AMI", "Key", "IP", "State", "Monitored") table = tag.table(tag.thead(tag.tr([tag.th(title) for title in headings])), class_="listing") tbody = tag.tbody() try: instance_data = ec2.get_all_instances() except UnicodeDecodeError, e: self.log.exception("Failed to ec2.get_all_instances() for AWSInstanceTableMacro") return tag.div("AWSInstanceTable macro failed.")
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, content): evs = EarnValueSystem(self.env) kwargs = self._parse_arg(content) users = evs.get_users(formatter.req, kwargs.get('users', 'authenticated')) format = kwargs.get('format', 'table') start_time = self._parse_date(kwargs.get('start'), 0) end_time = self._parse_date(kwargs.get('end'), None) if format == 'plain': ev = dict([(u, evs.get_user_ev(u, start_time, end_time)) for u in users]) tags = [] for k, v in ev.items(): tags.append(tag.span(k + ': ' + str(v))) tags.append(tag.br) return tag.p(*tags) elif format == 'table': evc = evs.get_users_ev_detail(users, start_time, end_time) rows = [tag.tr( tag.td(ev['action_name']), tag.td(ev['value']), tag.td(ev['fullname']), tag.td(ev['time']), tag.td(ev['summary']) ) for evcs in evc.values() for ev in evcs ] return tag.div( tag.table(tag.thead( tag.tr( tag.th('Action'), tag.th('Value'), tag.th('User'), tag.th('Date'), tag.th('Summary'), class_='trac-columns') ), tag.tbody( *rows ), class_='listing tickets') ) return None
def _contruct_tickets_table(self, req, ticket, childtickets, priorities): # trac.ini : Which columns to display in child ticket listing? columns = self.config.getlist('childtickets', 'parent.%s.table_headers' % ticket['type'], default=['summary','owner']) tablediv = tag.div() tablediv.append(tag.label(tag.input(type="checkbox", id="cb_show_closed"), "show closed tickets")) for tkt_types, type_tickets in groupby(childtickets, lambda t: t['type']): tablediv.append(tag.h2("Type: %s" % tkt_types, class_="report-result")) tablediv.append( 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,priorities) for tkt in type_tickets]), class_="listing tickets", ) ) return tablediv
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() sections = set([section for section, option in Option.registry.keys() if section.startswith(section_filter)]) return tag.div(class_='tracini')( [(tag.h2('[%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(Option.registry.values(), key=lambda o: o.name) if option.section == section and option.name.startswith(key_filter)]))) for section in sorted(sections)])
def render(self, context, mimetype, content, filename=None, url=None): content = content.read() content = re.split('\r[\n]', content) if not content: return None head = content[0] if not head: return None head = re.split(',', head) if not head: return None thead = tag.thead(tag.tr([tag.th(h) for h in head])) content = content[1:] if not content: return None tbody = [] for r in content: if r: r = re.split(',', r) if r: tbody.append(tag.tr([tag.td(c) for c in r])) return tag.table(thead, tag.tbody(tbody), class_="wiki")
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 _body_rows(): for idx, line in enumerate(_group_lines(stream)): row = tag.tr() if marks and idx + 1 in marks: row(class_='hilite') for annotator, data in annotator_datas: if annotator: annotator.annotate_row(context, row, idx + 1, line, data) else: row.append(tag.td()) row.append(tag.td(line)) yield row return tag.table(class_='code')(tag.thead(_head_row()), tag.tbody(_body_rows())) def get_max_preview_size(self): """:deprecated: use `max_preview_size` attribute directly.""" return self.max_preview_size def get_charset(self, content='', mimetype=None): """Infer the character encoding from the `content` or the `mimetype`. `content` is either a `str` or an `unicode` object. The charset will be determined using this order: * from the charset information present in the `mimetype` argument * auto-detection of the charset from the `content` * the configured `default_charset` """
def gen_calendar(self, tickets, query, month, width=None, nav=True): milestones = self._get_milestones() req = self.req locale = self._get_locale() first_week_day = self._get_first_week_day(locale) start_date_format = self.mod.start_date_format due_date_format = self.mod.due_date_format if not month: month = datetime.now(req.tz) month = datetime(month.year, month.month, 1).date() # init data today = datetime.now(req.tz).date() # generate calendar data weeks = self._get_month_calendar(month.year, month.month, first_week_day, locale) days = sorted(sum(weeks, [])) tickets = self._filter_tickets(days, tickets, req.tz) milestones = self._filter_milestones(days, milestones, req.tz) cal = [[{ 'date': day, 'tickets': tickets[day], 'milestones': milestones[day] } for day in week] for week in weeks] def genli(t): if isinstance(t, Milestone): return self._create_milestone_item(t) else: return self._create_ticket_item(t) def gentd(week_idx, day_info): day = day_info['date'] tt = day_info['milestones'] + day_info['tickets'] if len(tt) < 6: ttshow = tt ttall = [] else: ttshow = tt[:4] ttall = tt tdclass = [] if day == today: tdclass.append('today') if day.weekday() in (5, 6): tdclass.append('weekend') formatted_day = format_date(day, format='long', locale=locale) td = tag.td(class_=' '.join(tdclass), data_for_start_date=day.strftime(start_date_format), data_for_due_date=day.strftime(due_date_format), data_fdate=formatted_day) label = [] if day == today: label.append(tag.span(_("Today"), class_='today')) label.append( tag.span(unicode(day.day), class_=('day normal', 'day')[day == today])) td(tag.div(label)) if ttshow: td(tag.ul([genli(t) for t in ttshow])) if ttall: id = 'calendar-more-%s' % str(day) td( tag.a(_("%d more tickets...") % (len(ttall) - 4), href='#' + id, class_='show-all-list'), tag.div(tag.h4(formatted_day), tag.ul([genli(t) for t in ttall]), class_='ticketcalendar-popup-list', id=id, data_title=format_date(day, locale=locale, format='full'))) return td day_names = get_day_names(locale=locale, width='abbreviated') day_names = [ day_names[(idx + first_week_day) % 7] for idx in xrange(7) ] header = tag.div(class_='ticketcalendar-header') if nav: def nav_href(d): return self.get_box_href(query, d) def nav_pager(d): return tag.span(tag.a(u'\u25c4', href=nav_href(d - timedelta(days=1))), tag.a(_("Current month"), href=nav_href(date.today())), tag.a(u'\u25ba', href=nav_href(d + timedelta(days=31))), class_='ticketcalendar-pager') def nav_macro(): macro = ( '[[TicketCalendar(type=box,month=%(month)s,' 'query=%(query)s,order=%(order)s%(extra)s)]]' % (dict(month=month.strftime('%Y-%m'), query=self.build_query_string(query.constraints), order=query.order, extra=query.desc and ',desc=1' or ''))) text = tag.input(type='text', readonly='readonly', size='80', value=macro, style='width:0;display:none') return tag.span(_("Macro"), text, class_='ticketcalendar-macro') header( tag.div(nav_macro(), nav_pager(month), class_='ticketcalendar-nav')) header( tag.h4( _("%(month_name)s, %(year)s", month_name=_get_month_name(month, locale), year=month.year))) calendar = tag.table( tag.thead(tag.tr(tag.th(name) for name in day_names)), tag.tbody([ tag.tr([gentd(idx, d) for d in w]) for idx, w in enumerate(cal) ]), class_='calendar') can_create = 'TICKET_CREATE' in req.perm ticket_box = tag.div( tag.h4(tag.span('', class_='tc-today-date')), tag.ul( tag.li( tag.a(tag_( "New ticket with \"%(date)s\" as the start date", date=tag.span('', data='start-date')), data_href=req.href('newticket', [(self.mod.start_date_name, '')]), class_='newticket-start-date')), tag.li( tag.a(tag_("New ticket with \"%(date)s\" as the due date", date=tag.span('', data='due-date')), data_href=req.href('newticket', [(self.mod.due_date_name, '')]), class_='newticket-due-date'))), title=_("Create new ticket"), class_='ticketcalendar-newticket-box', style='display:none', data_writable=(None, 'writable')[can_create]) class_ = ('ticketcalendar', 'ticketcalendar ticketcalendar-can-create')[can_create] return tag.div(header, calendar, ticket_box, class_=class_, style=width and ('width: %s' % width) or None)
def expand_macro(self, formatter, name, arguments): self.ref = formatter self.tz_info = formatter.req.tz self.thistime = datetime.datetime.now(self.tz_info) # Parse arguments from macro invocation args, kwargs = parse_args(arguments, strict=False) # Find out whether use http param, current or macro param year/month http_param_year = formatter.req.args.get('year','') http_param_month = formatter.req.args.get('month','') if http_param_year == "": # not clicked on a prev or next button if len(args) >= 1 and args[0] <> "*": # year given in macro parameters year = int(args[0]) else: # use current year year = self.thistime.year else: # year in http params (clicked by user) overrides everything year = int(http_param_year) if http_param_month == "": # not clicked on a prev or next button if len(args) >= 2 and args[1] <> "*": # month given in macro parameters month = int(args[1]) else: # use current month month = self.thistime.month else: # month in http params (clicked by user) overrides everything month = int(http_param_month) showbuttons = True if len(args) >= 3 or kwargs.has_key('nav'): try: showbuttons = kwargs['nav'] in ["True", "true", "yes", "1"] except KeyError: showbuttons = args[2] in ["True", "true", "yes", "1"] wiki_page_format = "%Y-%m-%d" if len(args) >= 4 and args[3] != "*" or kwargs.has_key('wiki'): try: wiki_page_format = str(kwargs['wiki']) except KeyError: wiki_page_format = str(args[3]) # Support relative paths in macro arguments for wiki page links. wiki_page_format = self._resolve_relative_name(wiki_page_format, formatter.resource.id) list_condense = 0 show_t_open_dates = True wiki_page_template = "" wiki_subpages = [] # Read optional check plan. check = [] if kwargs.has_key('check'): check = kwargs['check'].split('.') if name == 'WikiTicketCalendar': if len(args) >= 5 or kwargs.has_key('cdate'): try: show_t_open_dates = kwargs['cdate'] in \ ["True", "true", "yes", "1"] except KeyError: show_t_open_dates = args[4] in \ ["True", "true", "yes", "1"] # Optional page template to create new wiki pages. # The default (empty page) is used, if the template name is invalid. if len(args) >= 6 or kwargs.has_key('base'): try: wiki_page_template = kwargs['base'] except KeyError: wiki_page_template = args[5] if name == 'WikiTicketCalendar': # TracQuery support for ticket selection query_args = "id!=0" if len(args) >= 7 or kwargs.has_key('query'): # prefer query arguments provided by kwargs try: query_args = kwargs['query'] except KeyError: query_args = args[6] tickets = WikiCalendarTicketProvider(self.env) self.tickets = tickets.harvest(formatter.req, query_args) # compress long ticket lists if len(args) >= 8 or kwargs.has_key('short'): # prefer query arguments provided by kwargs try: list_condense = int(kwargs['short']) except KeyError: list_condense = int(args[7]) # control calendar display width cal_width = "100%;" if len(args) >= 9 or kwargs.has_key('width'): # prefer query arguments provided by kwargs try: cal_width = kwargs['width'] except KeyError: cal_width = args[8] # multiple wiki (sub)pages per day if kwargs.has_key('subpages'): wiki_subpages = kwargs['subpages'].split('|') # Can use this to change the day the week starts on, # but this is a system-wide setting. calendar.setfirstweekday(calendar.MONDAY) cal = calendar.monthcalendar(year, month) curr_day = None if year == self.thistime.year and month == self.thistime.month: curr_day = self.thistime.day # for prev/next navigation links prevMonth = month - 1 nextMonth = month + 1 nextYear = prevYear = year # check for year change (KISS version) if prevMonth == 0: prevMonth = 12 prevYear -= 1 if nextMonth == 13: nextMonth = 1 nextYear += 1 # for fast-forward/-rewind navigation links ffYear = frYear = year if month < 4: frMonth = month + 9 frYear -= 1 else: frMonth = month - 3 if month > 9: ffMonth = month - 9 ffYear += 1 else: ffMonth = month + 3 last_week_prevMonth = calendar.monthcalendar(prevYear, prevMonth)[-1] first_week_nextMonth = calendar.monthcalendar(nextYear, nextMonth)[0] # Switch to user's locale, if available. try: loc_req = str(formatter.req.locale) except AttributeError: # Available since in Trac 0.12 . loc_req = None if loc_req: loc = locale.getlocale() loc_prop = locale.normalize(loc_req) try: locale.setlocale(locale.LC_TIME, loc_prop) except locale.Error: try: # Re-try with UTF-8 as last resort. loc_prop = '.'.join([loc_prop.split('.')[0],'utf8']) locale.setlocale(locale.LC_TIME, loc_prop) except locale.Error: loc_prop = None self.env.log.debug('Locale setting for calendar: ' + str(loc_prop)) # Finally building the output # Begin with caption and optional navigation links buff = tag.tr() if showbuttons is True: # calendar navigation buttons nx = 'next' pv = 'prev' nav_pvY = self._mknav('<<', pv, month, year-1) nav_frM = self._mknav(' <', pv, frMonth, frYear) nav_pvM = self._mknav(' «', pv, prevMonth, prevYear) nav_nxM = self._mknav('» ', nx, nextMonth, nextYear) nav_ffM = self._mknav('> ', nx, ffMonth, ffYear) nav_nxY = self._mknav('>>', nx, month, year+1) # add buttons for going to previous months and year buff(nav_pvY, nav_frM, nav_pvM) # The caption will always be there. heading = tag.td( to_unicode(format_date(self._mkdatetime(year, month), '%B %Y'))) buff = buff(heading(class_='y')) if showbuttons is True: # add buttons for going to next months and year buff(nav_nxM, nav_ffM, nav_nxY) buff = tag.caption(tag.table(tag.tbody(buff))) buff = tag.table(buff) if name == 'WikiTicketCalendar': if cal_width.startswith('+') is True: width=":".join(['min-width', cal_width]) buff(class_='wikitcalendar', style=width) else: buff(class_='wikitcalendar') if name == 'WikiCalendar': buff(class_='wiki-calendar') heading = tag.tr() heading(align='center') for day in calendar.weekheader(2).split()[:-2]: col = tag.th(to_unicode(day)) col(class_='workday', scope='col') heading(col) for day in calendar.weekheader(2).split()[-2:]: col = tag.th(to_unicode(day)) col(class_='weekend', scope='col') heading(col) heading = buff(tag.thead(heading)) # Building main calendar table body buff = tag.tbody() w = -1 for week in cal: w = w + 1 line = tag.tr() line(align='right') d = -1 for day in week: d = d + 1 if day: # check for wikipage with name specified in # 'wiki_page_format' wiki = format_date(self._mkdatetime(year, month, day), wiki_page_format) if day == curr_day: a_class = 'day today' td_class = 'today' else: a_class = 'day' td_class = 'day' day_dt = self._mkdatetime(year, month, day) if uts: day_ts = to_utimestamp(day_dt) day_ts_eod = day_ts + 86399999999 else: day_ts = to_timestamp(day_dt) day_ts_eod = day_ts + 86399 # check for milestone(s) on that day db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute(""" SELECT name FROM milestone WHERE due >= %s and due <= %s """, (day_ts, day_ts_eod)) milestones = tag() for row in cursor: if not a_class.endswith('milestone'): a_class += ' milestone' milestone = to_unicode(row[0]) url = self.env.href.milestone(milestone) milestone = '* ' + milestone milestones = tag(milestones, tag.div(tag.a(milestone, href=url), class_='milestone')) day = tag.span(day) day(class_='day') if len(wiki_subpages) > 0: pages = tag(day, Markup('<br />')) for page in wiki_subpages: label = tag(' ', page[0]) page = '/'.join([wiki, page]) url = self.env.href.wiki(page) pages(self._gen_wiki_links(page, label, 'subpage', url, wiki_page_template, check)) else: url = self.env.href.wiki(wiki) pages = self._gen_wiki_links(wiki, day, a_class, url, wiki_page_template, check) cell = tag.td(pages) cell(class_=td_class, valign='top') if name == 'WikiCalendar': line(cell) else: if milestones: cell(milestones) else: cell(tag.br()) match = [] match_od = [] ticket_heap = tag('') ticket_list = tag.div('') ticket_list(align='left', class_='condense') # get tickets with due date set to day for t in self.tickets: due = t.get(self.tkt_due_field) if due is None or due in ['', '--']: continue else: if self.tkt_due_format == 'ts': if not isinstance(due, datetime.datetime): continue if uts: due_ts = to_utimestamp(due) else: due_ts = to_timestamp(due) if due_ts < day_ts or due_ts > day_ts_eod: continue else: # Beware: Format might even be unicode str duedate = format_date(day_dt, str(self.tkt_due_format)) if not due == duedate: continue id = t.get('id') ticket, short = self._gen_ticket_entry(t) ticket_heap(ticket) if not id in match: if len(match) == 0: ticket_list(short) else: ticket_list(', ', short) match.append(id) # optionally get tickets created on day if show_t_open_dates is True: ticket_od_list = tag.div('') ticket_od_list(align='left', class_='opendate_condense') for t in self.tickets: if uts: ticket_ts = to_utimestamp(t.get('time')) else: ticket_ts = to_timestamp(t.get('time')) if ticket_ts < day_ts or \ ticket_ts > day_ts_eod: continue a_class = 'opendate_' id = t.get('id') ticket, short = self._gen_ticket_entry(t, a_class) ticket_heap(ticket) if not id in match: if len(match_od) == 0: ticket_od_list(short) else: ticket_od_list(', ', short) match_od.append(id) matches = len(match) + len(match_od) if list_condense > 0 and matches >= list_condense: if len(match_od) > 0: if len(match) > 0: ticket_list(', ') ticket_list = tag(ticket_list, ticket_od_list) line(cell(ticket_list)) else: line(cell(ticket_heap)) else: if name == 'WikiCalendar': if w == 0: day = last_week_prevMonth[d] wiki = format_date(self._mkdatetime( prevYear, prevMonth, day), wiki_page_format) else: day = first_week_nextMonth[d] wiki = format_date(self._mkdatetime( nextYear, nextMonth, day), wiki_page_format) url = self.env.href.wiki(wiki) a_class = 'day adjacent_month' pages = self._gen_wiki_links(wiki, day, a_class, url, wiki_page_template) cell = tag.td(pages) cell(class_='day adjacent_month') line(cell) else: cell = tag.td('') cell(class_='day adjacent_month') line(cell) buff(line) if loc_req and loc_prop: # We may have switched to users locale, resetting now. try: locale.setlocale(locale.LC_ALL, loc) self.env.log.debug('Locale setting restored: ' + str(loc)) except locale.Error: pass buff = tag.div(heading(buff)) if name == 'WikiTicketCalendar': if cal_width.startswith('+') is True: width=":".join(['width', cal_width]) buff(class_='wikitcalendar', style=width) else: buff(class_='wikitcalendar') if name == 'WikiCalendar': buff(class_='wiki-calendar') # Add common CSS stylesheet if self.internal_css and not self.ref.req.args.get('wikicalendar'): # Put definitions directly into the output. f = open('/'.join([self.htdocs_path, 'wikicalendar.css']), 'Ur') css = tag.style(Markup('<!--\n'), '\n'.join(f.readlines()), Markup('-->\n'))(type="text/css") f.close() # Add hint to prevent multiple inclusions. self.ref.req.args['wikicalendar'] = True return tag(css, buff) elif not self.ref.req.args.get('wikicalendar'): add_stylesheet(self.ref.req, 'wikicalendar/wikicalendar.css') return buff
def make_list(items): parts = [item.rsplit('.', 1) for item in items] return tag.table(tag.tbody( tag.tr(tag.td(c, class_='trac-name'), tag.td('(%s.*)' % m, class_='trac-name')) for m, c in parts), class_='trac-pluglist')
calendar[segment]['isocalendar'] = (None, None, None) calendar[segment]['date'] = None subtitle = "--" mystyle = 'color:#000;' mytitle = 'date could not be resolved' tr(tag.th(tag.h4(segment, class_ = myclass ), tag.h5( subtitle, style = mystyle, title = mytitle, class_ = myclass ))) counttickets[segment] = 0 if self.showsummarypiechart: tr(tag.th(tag.h4('Summary', class_ = myclass_org ), tag.h5( 'of all tickets', style = mystyle_org, title = mytitle_org, class_ = myclass_org ))) table(tag.thead(tr)) # Summary self.macroenv.tracenv.log.debug('tickets in table: '+repr(orderedtickets)) # table body tbody = tag.tbody() counter=0 for o in self.rows: if counter % 2 == 1: class_ = 'odd' else: class_ = 'even' counter += 1 tr = tag.tr(class_ = class_) tr( tag.td(tag.h5(tag.a( o, href=self.macroenv.tracenv.href()+('/query?%s=%s&order=status' % (self.rowtype,o,)) )))) # query owner's tickets countStatus = {} # summarize the status of a ticket for segment in self.segments: class_ = '' if calendar[segment]['date'] == currentDate: # Today
def render(self, ticketset): return_div = tag.div(class_=self.cssclass + ' projectplanrender') # check for missing parameters missingparameter = False if self.rows == [] or self.rows == None: return_div( tag.div( 'Missing parameter "rows": use a semicolon-separated list to input the "' + self.rowtype + '".', class_='ppwarning')) missingparameter = True if self.rowtype == None or str(self.rowtype).strip() == '': return_div( tag.div( 'Missing parameter "rowtype": specifies the ticket attribute that should be showed at the rows.', class_='ppwarning')) missingparameter = True if self.cols == [] or self.cols == None: return_div( tag.div( 'Missing parameter: use a semicolon-separated list to input the "cols".', class_='ppwarning')) missingparameter = True if self.coltype == None or str(self.coltype).strip() == '': return_div( tag.div( 'Missing parameter "coltype": specifies the ticket attribute that should be showed in the columns.', class_='ppwarning')) missingparameter = True if missingparameter: return return_div #ul = tag.ul() #for tid in ticketset.getIDSortedList(): #ticket = ticketset.getTicket(tid) #ul( tag.li(tid, " ",ticket.getfield('component') , " ", ticket.getfield('owner') )) #return_div(ul) def getstatistictitle(statusdict): mytitle = '' mysum = 0 for status in statusdict: mytitle += "%s: %s\n" % (status, str(statusdict[status])) mysum += int(statusdict[status]) mytitle += "%s: %s" % ('number', mysum) return mytitle def setKV(myStruct, myKey, newValue): ''' shortcut to set the values correctly used to reduce the code needed while using a list as key of a dict ''' myStruct[str(myKey)] = newValue def tableKeyPrettyPrint(mylist): ''' transform a list of keys to a user readable string in: ['a','b'] --> out: 'a|b' ''' return '|'.join(mylist) def tableKeyQueryParameter(parameter, mylist): ''' transform a list of keys to a Trac query string parameter (OR) in: x, ['a','b'] --> out: 'x=a&x=b' ''' return '&'.join(["%s=%s" % (parameter, s) for s in mylist]) chartheight = 80 chartwidth = 170 data = {} statistics = {} # init table data for row in self.rows: colstatistics = {} colkeys = {} for col in self.cols: # colkeys[col] = [] setKV(colkeys, col, []) # colstatistics[col] = {} setKV(colstatistics, col, {}) # data[row] = colkeys setKV(data, row, colkeys) # statistics[row] = colstatistics setKV(statistics, row, colstatistics) for tid in ticketset.getIDSortedList(): ticket = ticketset.getTicket(tid) ticket_rowtype = ticket.getfield(self.rowtype) ticket_coltype = ticket.getfield(self.coltype) # determine the data cell where the ticket has to be added, keep in mind that rows and cols are list of lists for row in self.rows: for col in self.cols: if ticket_rowtype in row and ticket_coltype in col: data[str(row)][str(col)].append( ticket ) # save tickets at precise values of row and col self.log_debug('row:%s col:%s append:%s' % (row, col, tid)) # if ticket_rowtype in self.rows and ticket_coltype in self.cols : # create HTML table table = tag.table(class_="data pptableticketperday", border="1", style='width:auto;') # create HTML table head thead = tag.thead() tr = tag.tr() tr(tag.th("%s vs %s" % (self.rowtype, self.coltype))) for colkey in self.cols: tr( tag.th(tag.h4( tag.a(tableKeyPrettyPrint(colkey), href=self.macroenv.tracenv.href() + ('/query?%s&order=%s' % (tableKeyQueryParameter( self.coltype, colkey), self.rowtype)))), title="%s is %s" % (self.coltype, tableKeyPrettyPrint(colkey))) ) # first line with all colkeys if self.showsummarypiechart: tr(tag.th(tag.h4("Ticket Overview"))) thead(tr) table(thead) # create HTML table body tbody = tag.tbody() counter = 0 for rowkey in self.rows: # switch line color if counter % 2 == 1: class_ = 'odd' else: class_ = 'even' counter += 1 tr = tag.tr(class_=class_) # new line td = tag.td() # new cell td(tag.h5( tag.a(tableKeyPrettyPrint(rowkey), href=self.macroenv.tracenv.href() + ('/query?%s&order=%s' % (tableKeyQueryParameter( self.rowtype, rowkey), self.coltype)))), title="%s is %s" % (self.rowtype, tableKeyPrettyPrint(rowkey))) # first cell contains row key tr(td) for colkey in self.cols: td = tag.td() for ticket in data[str(rowkey)][str(colkey)]: td(tag.span(self.createTicketLink(ticket), class_='ticket_inner'), " ", mytitle="%s is %s and %s is %s" % (self.rowtype, rowkey, self.coltype, colkey)) # mytitle might be used later by javascript if not statistics[str(rowkey)][str(colkey)].has_key( ticket.getstatus()): statistics[str(rowkey)][str(colkey)][ ticket.getstatus()] = 0 statistics[str(rowkey)][str(colkey)][ ticket.getstatus()] += 1 tr(td) # compute statistics rowstatistics = {} count = 0 for colkey in statistics[str(rowkey)]: for status in statistics[str(rowkey)][str(colkey)]: if not rowstatistics.has_key(status): rowstatistics[status] = 0 try: rowstatistics[status] += statistics[str(rowkey)][str( colkey)][status] count += statistics[str(rowkey)][str(colkey)][status] except: pass if self.showsummarypiechart: tr( tag.td(tag.img(src=self.createGoogleChartFromDict( 'ColorForStatus', rowstatistics, '%s tickets' % (count, ), height=chartheight)), class_='ppstatistics', title=getstatistictitle(rowstatistics))) # Summary tbody(tr) table(tbody) # create HTML table foot if self.showsummarypiechart: fullstatistics = {} tfoot = tag.tfoot() tr = tag.tr() tr(tag.td(tag.h5('Ticket Overview'))) # create statistics for col fullcount = 0 for colkey in self.cols: colstatistics = {} colcount = 0 for rowkey in self.rows: for status in statistics[str(rowkey)][str(colkey)]: if not fullstatistics.has_key(status): fullstatistics[status] = 0 if not colstatistics.has_key(status): colstatistics[status] = 0 try: colstatistics[status] += statistics[str(rowkey)][ str(colkey)][status] colcount += statistics[str(rowkey)][str( colkey)][status] fullstatistics[status] += statistics[str(rowkey)][ str(colkey)][status] fullcount += statistics[str(rowkey)][str( colkey)][status] except: pass tr( tag.td( tag.img(src=self.createGoogleChartFromDict( 'ColorForStatus', colstatistics, '%s tickets' % (colcount, ), height=chartheight)), title=getstatistictitle(colstatistics))) # Col Summary tr( tag.td( tag.img(src=self.createGoogleChartFromDict( 'ColorForStatus', fullstatistics, '%s tickets' % (fullcount, ), height=chartheight)), class_='ppstatistics', title=getstatistictitle(fullstatistics))) # Full Summary tfoot(tr) table(tfoot) return_div(table) return return_div
def expand_macro(self, formatter, name, arguments): self.ref = formatter self.tz_info = formatter.req.tz self.thistime = datetime.datetime.now(self.tz_info) # Parse arguments from macro invocation args, kwargs = parse_args(arguments, strict=False) # Find out whether use http param, current or macro param year/month http_param_year = formatter.req.args.get('year', '') http_param_month = formatter.req.args.get('month', '') if http_param_year == "": # not clicked on a prev or next button if len(args) >= 1 and args[0] <> "*": # year given in macro parameters year = int(args[0]) else: # use current year year = self.thistime.year else: # year in http params (clicked by user) overrides everything year = int(http_param_year) if http_param_month == "": # not clicked on a prev or next button if len(args) >= 2 and args[1] <> "*": # month given in macro parameters month = int(args[1]) else: # use current month month = self.thistime.month else: # month in http params (clicked by user) overrides everything month = int(http_param_month) showbuttons = True if len(args) >= 3 or kwargs.has_key('nav'): try: showbuttons = kwargs['nav'] in ["True", "true", "yes", "1"] except KeyError: showbuttons = args[2] in ["True", "true", "yes", "1"] wiki_page_format = "%Y-%m-%d" if len(args) >= 4 and args[3] != "*" or kwargs.has_key('wiki'): try: wiki_page_format = str(kwargs['wiki']) except KeyError: wiki_page_format = str(args[3]) show_t_open_dates = True if len(args) >= 5 or kwargs.has_key('cdate'): try: show_t_open_dates = kwargs['cdate'] in \ ["True", "true", "yes", "1"] except KeyError: show_t_open_dates = args[4] in ["True", "true", "yes", "1"] # template name tried to create new pages # optional, default (empty page) is used, if name is invalid wiki_page_template = "" if len(args) >= 6 or kwargs.has_key('base'): try: wiki_page_template = kwargs['base'] except KeyError: wiki_page_template = args[5] # TracQuery support for ticket selection query_args = "id!=0" if len(args) >= 7 or kwargs.has_key('query'): # prefer query arguments provided by kwargs try: query_args = kwargs['query'] except KeyError: query_args = args[6] self.tickets = self._ticket_query(formatter, query_args) # compress long ticket lists list_condense = 0 if len(args) >= 8 or kwargs.has_key('short'): # prefer query arguments provided by kwargs try: list_condense = int(kwargs['short']) except KeyError: list_condense = int(args[7]) # control calendar display width cal_width = "100%;" if len(args) >= 9 or kwargs.has_key('width'): # prefer query arguments provided by kwargs try: cal_width = kwargs['width'] except KeyError: cal_width = args[8] # Can use this to change the day the week starts on, # but this is a system-wide setting. calendar.setfirstweekday(calendar.MONDAY) cal = calendar.monthcalendar(year, month) curr_day = None if year == self.thistime.year and month == self.thistime.month: curr_day = self.thistime.day # Compile regex pattern before use for better performance pattern_del = '(?:<span .*?>)|(?:</span>)' pattern_del += '|(?:<p>)|(?:<p .*?>)|(?:</p>)' pattern_del += '|(?:</table>)|(?:<td.*?\n)|(?:<tr.*?</tr>)' self.end_RE = re.compile('(?:</a>)') self.del_RE = re.compile(pattern_del) self.item_RE = re.compile('(?:<img .*?>)') self.open_RE = re.compile('(?:<a .*?>)') self.tab_RE = re.compile('(?:<table .*?>)') # for prev/next navigation links prevMonth = month - 1 nextMonth = month + 1 nextYear = prevYear = year # check for year change (KISS version) if prevMonth == 0: prevMonth = 12 prevYear -= 1 if nextMonth == 13: nextMonth = 1 nextYear += 1 # for fast-forward/-rewind navigation links ffYear = frYear = year if month < 4: frMonth = month + 9 frYear -= 1 else: frMonth = month - 3 if month > 9: ffMonth = month - 9 ffYear += 1 else: ffMonth = month + 3 # Finally building the output # Prepending inline CSS definitions styles = """ \ <!-- table.wikiTicketCalendar th { font-weight: bold; } table.wikiTicketCalendar th.workday { width: 17%; } table.wikiTicketCalendar th.weekend { width: 7%; } table.wikiTicketCalendar caption { font-size: 120%; white-space: nowrap; } table.wikiTicketCalendar caption a { display: inline; margin: 0; border: 0; padding: 0; background-color: transparent; color: #b00; text-decoration: none; } table.wikiTicketCalendar caption a.prev { padding-right: 5px; font: bold; } table.wikiTicketCalendar caption a.next { padding-left: 5px; font: bold; } table.wikiTicketCalendar caption a:hover { background-color: #eee; } table.wikiTicketCalendar td.today { background: #fbfbfb; border-color: #444444; color: #444; border-style: solid; border-width: 1px; } table.wikiTicketCalendar td.day { background: #e5e5e5; border-color: #444444; color: #333; border-style: solid; border-width: 1px; } table.wikiTicketCalendar td.fill { background: #eee; border-color: #ccc; border-style: solid; border-width: 1px; } table.wikiTicketCalendar div.milestone { font-size: 9px; background: #f7f7f0; border: 1px solid #d7d7d7; border-bottom-color: #999; text-align: left; } table.wikiTicketCalendar a.day { width: 2em; height: 100%; margin: 0; border: 0px; padding: 0; color: #333; text-decoration: none; } table.wikiTicketCalendar a.day_haspage { width: 2em; height: 100%; margin: 0; border: 0px; padding: 0; color: #b00 !important; text-decoration: none; } table.wikiTicketCalendar a.day:hover { border-color: #eee; background-color: #eee; color: #000; } table.wikiTicketCalendar div.open, span.open { font-size: 9px; color: #000000; } table.wikiTicketCalendar div.closed, span.closed { font-size: 9px; color: #777777; text-decoration: line-through; } table.wikiTicketCalendar div.opendate_open, span.opendate_open { font-size: 9px; color: #000077; } table.wikiTicketCalendar div.opendate_closed, span.opendate_closed { font-size: 9px; color: #000077; text-decoration: line-through; } table.wikiTicketCalendar div.condense { background-color: #e5e5e5; } table.wikiTicketCalendar div.opendate_condense { background-color: #cdcdfa; } /* pure CSS style tooltip */ a.tip { position: relative; cursor: help; } a.tip span { display: none; } a.tip:hover span { display: block; z-index: 1; font-size: 0.75em; text-decoration: none; /* big left move because of z-index render bug in IE<8 */ position: absolute; top: 0.8em; left: 6em; border: 1px solid #000; background-color: #fff; padding: 5px; } --> """ # create inline style definition as Genshi fragment styles = tag.style(Markup(styles)) styles(type='text/css') # Build caption and optional navigation links buff = tag.caption() if showbuttons is True: # calendar navigation buttons nx = 'next' pv = 'prev' nav_pvY = self._mknav(' <<', pv, month, year - 1) nav_frM = self._mknav(' < ', pv, frMonth, frYear) nav_pvM = self._mknav(' « ', pv, prevMonth, prevYear) nav_nxM = self._mknav(' » ', nx, nextMonth, nextYear) nav_ffM = self._mknav(' > ', nx, ffMonth, ffYear) nav_nxY = self._mknav(' >>', nx, month, year + 1) # add buttons for going to previous months and year buff(nav_pvY, nav_frM, nav_pvM) # The caption will always be there. buff( tag.strong( to_unicode(format_date(self._mkdatetime(year, month), '%B %Y')))) if showbuttons is True: # add buttons for going to next months and year buff(nav_nxM, nav_ffM, nav_nxY) buff = tag.table(buff) width = ":".join(['min-width', cal_width]) buff(class_='wikiTicketCalendar', style=width) heading = tag.tr() heading(align='center') for day in calendar.weekheader(2).split()[:-2]: col = tag.th(day) col(class_='workday', scope='col') heading(col) for day in calendar.weekheader(2).split()[-2:]: col = tag.th(day) col(class_='weekend', scope='col') heading(col) heading = buff(tag.thead(heading)) # Building main calendar table body buff = tag.tbody() for row in cal: line = tag.tr() line(align='right') for day in row: if not day: cell = tag.td('') cell(class_='fill') line(cell) else: # check for wikipage with name specified in # 'wiki_page_format' wiki = format_date(self._mkdatetime(year, month, day), wiki_page_format) url = self.env.href.wiki(wiki) if WikiSystem(self.env).has_page(wiki): a_class = "day_haspage" title = "Go to page %s" % wiki else: a_class = "day" url += "?action=edit" # adding template name, if specified if wiki_page_template != "": url += "&template=" + wiki_page_template title = "Create page %s" % wiki if day == curr_day: td_class = 'today' else: td_class = 'day' cell = tag.a(tag.b(day), href=url) cell(class_=a_class, title_=title) cell = tag.td(cell) cell(class_=td_class, valign='top') day_dt = self._mkdatetime(year, month, day) if uts: day_ts = to_utimestamp(day_dt) day_ts_eod = day_ts + 86399999999 else: day_ts = to_timestamp(day_dt) day_ts_eod = day_ts + 86399 # check for milestone(s) on that day db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute( """ SELECT name FROM milestone WHERE due >= %s and due <= %s """, (day_ts, day_ts_eod)) while (1): row = cursor.fetchone() if row is None: cell(tag.br()) break else: name = to_unicode(row[0]) url = self.env.href.milestone(name) milestone = '* ' + name milestone = tag.div(tag.a(milestone, href=url)) milestone(class_='milestone') cell(milestone) match = [] match_od = [] ticket_heap = tag('') ticket_list = tag.div('') ticket_list(align='left', class_='condense') # get tickets with due date set to day for t in self.tickets: due = t.get(self.due_field_name) if due is None or due in ['', '--']: continue else: if self.due_field_fmt == 'ts': if not isinstance(due, datetime.datetime): continue else: if uts: due_ts = to_utimestamp(due) else: due_ts = to_timestamp(due) if due_ts < day_ts or \ due_ts > day_ts_eod: continue else: duedate = format_date(day_dt, self.due_field_fmt) if not due == duedate: continue id = t.get('id') ticket, short = self._gen_ticket_entry(t) ticket_heap(ticket) if not id in match: if len(match) == 0: ticket_list(short) else: ticket_list(', ', short) match.append(id) # optionally get tickets created on day if show_t_open_dates is True: ticket_od_list = tag.div('') ticket_od_list(align='left', class_='opendate_condense') for t in self.tickets: if uts: ticket_ts = to_utimestamp(t.get('time')) else: ticket_ts = to_timestamp(t.get('time')) if ticket_ts < day_ts or ticket_ts > day_ts_eod: continue a_class = 'opendate_' id = t.get('id') ticket, short = self._gen_ticket_entry(t, a_class) ticket_heap(ticket) if not id in match: if len(match_od) == 0: ticket_od_list(short) else: ticket_od_list(', ', short) match_od.append(id) matches = len(match) + len(match_od) if list_condense > 0 and matches >= list_condense: if len(match_od) > 0: if len(match) > 0: ticket_list(', ') ticket_list = tag(ticket_list, ticket_od_list) line(cell(ticket_list)) else: line(cell(ticket_heap)) buff(line) buff = tag.div(heading(buff)) if cal_width.startswith('+') is True: width = ":".join(['width', cal_width]) buff(class_='wikiTicketCalendar', style=width) else: buff(class_='wikiTicketCalendar') # Finally prepend prepared CSS styles buff = tag(styles, buff) return buff
counttickets[segment] = 0 if self.showsummarypiechart: tr( tag.th( tag.h4('Summary', class_=myclass_org), tag.h5('of all tickets', style=mystyle_org, title=mytitle_org, class_=myclass_org))) table(tag.thead(tr)) # Summary self.macroenv.tracenv.log.debug('tickets in table: ' + repr(orderedtickets)) # table body tbody = tag.tbody() counter = 0 for o in self.rows: if counter % 2 == 1: class_ = 'odd' else: class_ = 'even' counter += 1 tr = tag.tr(class_=class_) tr( tag.td( tag.h5( tag.a(o, href=self.macroenv.tracenv.href() +
def render_property_diff(self, name, old_context, old_props, new_context, new_props, options): # Build 5 columns table showing modifications on merge sources # || source || added || removed || added (ni) || removed (ni) || # || source || removed || rm = RepositoryManager(self.env) repos = rm.get_repository(old_context.resource.parent.id) def parse_sources(props): sources = {} for line in props[name].splitlines(): path, revs = line.split(':', 1) spath = _path_within_scope(repos.scope, path) if spath is not None: inheritable, non_inheritable = _partition_inheritable(revs) sources[spath] = (set(Ranges(inheritable)), set(Ranges(non_inheritable))) return sources old_sources = parse_sources(old_props) new_sources = parse_sources(new_props) # Go through new sources, detect modified ones or added ones blocked = name.endswith('blocked') added_label = [_("merged: "), _("blocked: ")][blocked] removed_label = [_("reverse-merged: "), _("un-blocked: ")][blocked] added_ni_label = _("marked as non-inheritable: ") removed_ni_label = _("unmarked as non-inheritable: ") def revs_link(revs, context): if revs: revs = to_ranges(revs) return _get_revs_link(revs.replace(',', u',\u200b'), context, spath, revs) modified_sources = [] for spath, (new_revs, new_revs_ni) in new_sources.iteritems(): if spath in old_sources: (old_revs, old_revs_ni), status = old_sources.pop(spath), None else: old_revs = old_revs_ni = set() status = _(' (added)') added = new_revs - old_revs removed = old_revs - new_revs added_ni = new_revs_ni - old_revs_ni removed_ni = old_revs_ni - new_revs_ni try: all_revs = set(repos._get_node_revs(spath)) # TODO: also pass first_rev here, for getting smaller a set # (this is an optmization fix, result is already correct) added &= all_revs removed &= all_revs added_ni &= all_revs removed_ni &= all_revs except NoSuchNode: pass if added or removed: modified_sources.append( (spath, [_get_source_link(spath, new_context), status], added and tag(added_label, revs_link(added, new_context)), removed and tag(removed_label, revs_link(removed, old_context)), added_ni and tag(added_ni_label, revs_link(added_ni, new_context)), removed_ni and tag(removed_ni_label, revs_link(removed_ni, old_context)))) # Go through remaining old sources, those were deleted removed_sources = [] for spath, old_revs in old_sources.iteritems(): removed_sources.append( (spath, _get_source_link(spath, old_context))) if modified_sources or removed_sources: modified_sources.sort() removed_sources.sort() changes = tag.table(tag.tbody([ tag.tr(tag.td(c) for c in cols[1:]) for cols in modified_sources ], [ tag.tr(tag.td(src), tag.td(_('removed'), colspan=4)) for spath, src in removed_sources ]), class_='props') else: changes = tag.em(_(' (with no actual effect on merging)')) return tag.li(tag_('Property %(prop)s changed', prop=tag.strong(name)), changes)
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 render_property_diff(self, name, old_context, old_props, new_context, new_props, options): # Build 5 columns table showing modifications on merge sources # || source || added || removed || added (ni) || removed (ni) || # || source || removed || rm = RepositoryManager(self.env) repos = rm.get_repository(old_context.resource.parent.id) def parse_sources(props): sources = {} for line in props[name].splitlines(): path, revs = line.split(':', 1) spath = _path_within_scope(repos.scope, path) if spath is not None: inheritable, non_inheritable = _partition_inheritable(revs) sources[spath] = (set(Ranges(inheritable)), set(Ranges(non_inheritable))) return sources old_sources = parse_sources(old_props) new_sources = parse_sources(new_props) # Go through new sources, detect modified ones or added ones blocked = name.endswith('blocked') added_label = [_("merged: "), _("blocked: ")][blocked] removed_label = [_("reverse-merged: "), _("un-blocked: ")][blocked] added_ni_label = _("marked as non-inheritable: ") removed_ni_label = _("unmarked as non-inheritable: ") sources = [] changed_revs = {} changed_nodes = [] for spath, (new_revs, new_revs_ni) in new_sources.iteritems(): new_spath = spath not in old_sources if new_spath: old_revs = old_revs_ni = set() else: old_revs, old_revs_ni = old_sources.pop(spath) added = new_revs - old_revs removed = old_revs - new_revs # unless new revisions differ from old revisions if not added and not removed: continue added_ni = new_revs_ni - old_revs_ni removed_ni = old_revs_ni - new_revs_ni revs = sorted(added | removed | added_ni | removed_ni) try: node = repos.get_node(spath, revs[-1]) changed_nodes.append((node, revs[0])) except NoSuchNode: pass sources.append((spath, new_spath, added, removed, added_ni, removed_ni)) if changed_nodes: changed_revs = repos._get_changed_revs(changed_nodes) def revs_link(revs, context): if revs: revs = to_ranges(revs) return _get_revs_link(revs.replace(',', u',\u200b'), context, spath, revs) modified_sources = [] for spath, new_spath, added, removed, added_ni, removed_ni in sources: if spath in changed_revs: revs = set(changed_revs[spath]) added &= revs removed &= revs added_ni &= revs removed_ni &= revs if added or removed: if new_spath: status = _(" (added)") else: status = None modified_sources.append(( spath, [_get_source_link(spath, new_context), status], added and tag(added_label, revs_link(added, new_context)), removed and tag(removed_label, revs_link(removed, old_context)), added_ni and tag(added_ni_label, revs_link(added_ni, new_context)), removed_ni and tag(removed_ni_label, revs_link(removed_ni, old_context)) )) # Go through remaining old sources, those were deleted removed_sources = [] for spath, old_revs in old_sources.iteritems(): removed_sources.append((spath, _get_source_link(spath, old_context))) if modified_sources or removed_sources: modified_sources.sort() removed_sources.sort() changes = tag.table(tag.tbody( [tag.tr(tag.td(c) for c in cols[1:]) for cols in modified_sources], [tag.tr(tag.td(src), tag.td(_('removed'), colspan=4)) for spath, src in removed_sources]), class_='props') else: changes = tag.em(_(' (with no actual effect on merging)')) return tag.li(tag_('Property %(prop)s changed', prop=tag.strong(name)), changes)
def render_property(self, name, mode, context, props): """Parse svn:mergeinfo and svnmerge-* properties, converting branch names to links and providing links to the revision log for merged and eligible revisions. """ has_eligible = name in ('svnmerge-integrated', 'svn:mergeinfo') revs_label = _('blocked') if name.endswith('blocked') else _('merged') revs_cols = 2 if has_eligible else None reponame = context.resource.parent.id target_path = context.resource.id repos = RepositoryManager(self.env).get_repository(reponame) target_rev = context.resource.version if has_eligible: node = repos.get_node(target_path, target_rev) branch_starts = {} for path, rev in node.get_copy_ancestry(): if path not in branch_starts: branch_starts[path] = rev + 1 rows = [] eligible_infos = [] if name.startswith('svnmerge-'): sources = props[name].split() else: sources = props[name].splitlines() for line in sources: path, revs = line.split(':', 1) spath = _path_within_scope(repos.scope, path) if spath is None: continue revs = revs.strip() inheritable, non_inheritable = _partition_inheritable(revs) revs = ','.join(inheritable) deleted = False try: node = repos.get_node(spath, target_rev) resource = context.resource.parent.child('source', spath) if 'LOG_VIEW' in context.perm(resource): row = [ _get_source_link(spath, context), _get_revs_link(revs_label, context, spath, revs) ] if non_inheritable: non_inheritable = ','.join(non_inheritable) row.append( _get_revs_link( _('non-inheritable'), context, spath, non_inheritable, _('merged on the directory ' 'itself but not below'))) if has_eligible: first_rev = branch_starts.get(spath) if not first_rev: first_rev = node.get_branch_origin() eligible = set(xrange(first_rev or 1, target_rev + 1)) eligible -= set(Ranges(revs)) blocked = _get_blocked_revs(props, name, spath) if blocked: eligible -= set(Ranges(blocked)) if eligible: node = repos.get_node(spath, max(eligible)) eligible_infos.append((spath, node, eligible, row)) continue eligible = to_ranges(eligible) row.append( _get_revs_link(_('eligible'), context, spath, eligible)) rows.append((False, spath, [tag.td(each) for each in row])) continue except NoSuchNode: deleted = True revs = revs.replace(',', u',\u200b') rows.append( (deleted, spath, [tag.td('/' + spath), tag.td(revs, colspan=revs_cols)])) # fetch eligible revisions for each path at a time changed_revs = {} changed_nodes = [(node, min(eligible)) for spath, node, eligible, row in eligible_infos] if changed_nodes: changed_revs = repos._get_changed_revs(changed_nodes) for spath, node, eligible, row in eligible_infos: if spath in changed_revs: eligible &= set(changed_revs[spath]) else: eligible.clear() row.append( _get_revs_link(_("eligible"), context, spath, to_ranges(eligible))) rows.append((False, spath, [tag.td(each) for each in row])) if not rows: return None rows.sort() if rows and rows[-1][0]: toggledeleted = tag.a(_("(toggle deleted branches)"), class_='trac-toggledeleted', href='#') else: toggledeleted = None return tag( toggledeleted, tag.table(tag.tbody([ tag.tr(row, class_='trac-deleted' if deleted else None) for deleted, spath, row in rows ]), class_='props'))
def render_property_diff(self, name, old_context, old_props, new_context, new_props, options): # Build 5 columns table showing modifications on merge sources # || source || added || removed || added (ni) || removed (ni) || # || source || removed || rm = RepositoryManager(self.env) repos = rm.get_repository(old_context.resource.parent.id) def parse_sources(props): sources = {} value = props[name] lines = value.splitlines() if name == 'svn:mergeinfo' \ else value.split() for line in lines: path, revs = line.split(':', 1) spath = _path_within_scope(repos.scope, path) if spath is not None: inheritable, non_inheritable = _partition_inheritable(revs) sources[spath] = (set(Ranges(inheritable)), set(Ranges(non_inheritable))) return sources old_sources = parse_sources(old_props) new_sources = parse_sources(new_props) # Go through new sources, detect modified ones or added ones blocked = name.endswith('blocked') added_label = [_("merged: "), _("blocked: ")][blocked] removed_label = [_("reverse-merged: "), _("un-blocked: ")][blocked] added_ni_label = _("marked as non-inheritable: ") removed_ni_label = _("unmarked as non-inheritable: ") sources = [] changed_revs = {} changed_nodes = [] for spath, (new_revs, new_revs_ni) in new_sources.iteritems(): new_spath = spath not in old_sources if new_spath: old_revs = old_revs_ni = set() else: old_revs, old_revs_ni = old_sources.pop(spath) added = new_revs - old_revs removed = old_revs - new_revs # unless new revisions differ from old revisions if not added and not removed: continue added_ni = new_revs_ni - old_revs_ni removed_ni = old_revs_ni - new_revs_ni revs = sorted(added | removed | added_ni | removed_ni) try: node = repos.get_node(spath, revs[-1]) changed_nodes.append((node, revs[0])) except NoSuchNode: pass sources.append( (spath, new_spath, added, removed, added_ni, removed_ni)) if changed_nodes: changed_revs = repos._get_changed_revs(changed_nodes) def revs_link(revs, context): if revs: revs = to_ranges(revs) return _get_revs_link(revs.replace(',', u',\u200b'), context, spath, revs) modified_sources = [] for spath, new_spath, added, removed, added_ni, removed_ni in sources: if spath in changed_revs: revs = set(changed_revs[spath]) added &= revs removed &= revs added_ni &= revs removed_ni &= revs if added or removed: if new_spath: status = _(" (added)") else: status = None modified_sources.append( (spath, [_get_source_link(spath, new_context), status], added and tag(added_label, revs_link(added, new_context)), removed and tag(removed_label, revs_link(removed, old_context)), added_ni and tag(added_ni_label, revs_link(added_ni, new_context)), removed_ni and tag(removed_ni_label, revs_link(removed_ni, old_context)))) # Go through remaining old sources, those were deleted removed_sources = [] for spath, old_revs in old_sources.iteritems(): removed_sources.append( (spath, _get_source_link(spath, old_context))) if modified_sources or removed_sources: modified_sources.sort() removed_sources.sort() changes = tag.table(tag.tbody([ tag.tr(tag.td(c) for c in cols[1:]) for cols in modified_sources ], [ tag.tr(tag.td(src), tag.td(_('removed'), colspan=4)) for spath, src in removed_sources ]), class_='props') else: changes = tag.em(_(' (with no actual effect on merging)')) return tag.li(tag_('Property %(prop)s changed', prop=tag.strong(name)), changes)
def expand_macro(self, formatter, name, arguments): self.ref = formatter self.tz_info = formatter.req.tz self.thistime = datetime.datetime.now(self.tz_info) # Add CSS stylesheet add_stylesheet(self.ref.req, 'wikiticketcalendar/css/wikiticketcalendar.css') # Parse arguments from macro invocation args, kwargs = parse_args(arguments, strict=False) # Find out whether use http param, current or macro param year/month http_param_year = formatter.req.args.get('year','') http_param_month = formatter.req.args.get('month','') if http_param_year == "": # not clicked on a prev or next button if len(args) >= 1 and args[0] <> "*": # year given in macro parameters year = int(args[0]) else: # use current year year = self.thistime.year else: # year in http params (clicked by user) overrides everything year = int(http_param_year) if http_param_month == "": # not clicked on a prev or next button if len(args) >= 2 and args[1] <> "*": # month given in macro parameters month = int(args[1]) else: # use current month month = self.thistime.month else: # month in http params (clicked by user) overrides everything month = int(http_param_month) showbuttons = True if len(args) >= 3 or kwargs.has_key('nav'): try: showbuttons = kwargs['nav'] in ["True", "true", "yes", "1"] except KeyError: showbuttons = args[2] in ["True", "true", "yes", "1"] wiki_page_format = "%Y-%m-%d" if len(args) >= 4 and args[3] != "*" or kwargs.has_key('wiki'): try: wiki_page_format = str(kwargs['wiki']) except KeyError: wiki_page_format = str(args[3]) show_t_open_dates = True if len(args) >= 5 or kwargs.has_key('cdate'): try: show_t_open_dates = kwargs['cdate'] in \ ["True", "true", "yes", "1"] except KeyError: show_t_open_dates = args[4] in ["True", "true", "yes", "1"] # template name tried to create new pages # optional, default (empty page) is used, if name is invalid wiki_page_template = "" if len(args) >= 6 or kwargs.has_key('base'): try: wiki_page_template = kwargs['base'] except KeyError: wiki_page_template = args[5] # TracQuery support for ticket selection query_args = "id!=0" if len(args) >= 7 or kwargs.has_key('query'): # prefer query arguments provided by kwargs try: query_args = kwargs['query'] except KeyError: query_args = args[6] self.tickets = self._ticket_query(formatter, query_args) # compress long ticket lists list_condense = 0 if len(args) >= 8 or kwargs.has_key('short'): # prefer query arguments provided by kwargs try: list_condense = int(kwargs['short']) except KeyError: list_condense = int(args[7]) # control calendar display width cal_width = "100%;" if len(args) >= 9 or kwargs.has_key('width'): # prefer query arguments provided by kwargs try: cal_width = kwargs['width'] except KeyError: cal_width = args[8] # Can use this to change the day the week starts on, # but this is a system-wide setting. calendar.setfirstweekday(calendar.MONDAY) cal = calendar.monthcalendar(year, month) curr_day = None if year == self.thistime.year and month == self.thistime.month: curr_day = self.thistime.day # Compile regex pattern before use for better performance pattern_del = '(?:<span .*?>)|(?:</span>)' pattern_del += '|(?:<p>)|(?:<p .*?>)|(?:</p>)' pattern_del += '|(?:</table>)|(?:<td.*?\n)|(?:<tr.*?</tr>)' self.end_RE = re.compile('(?:</a>)') self.del_RE = re.compile(pattern_del) self.item_RE = re.compile('(?:<img .*?>)') self.open_RE = re.compile('(?:<a .*?>)') self.tab_RE = re.compile('(?:<table .*?>)') # for prev/next navigation links prevMonth = month - 1 nextMonth = month + 1 nextYear = prevYear = year # check for year change (KISS version) if prevMonth == 0: prevMonth = 12 prevYear -= 1 if nextMonth == 13: nextMonth = 1 nextYear += 1 # for fast-forward/-rewind navigation links ffYear = frYear = year if month < 4: frMonth = month + 9 frYear -= 1 else: frMonth = month - 3 if month > 9: ffMonth = month - 9 ffYear += 1 else: ffMonth = month + 3 # Finally building the output # Begin with caption and optional navigation links buff = tag.caption() if showbuttons is True: # calendar navigation buttons nx = 'next' pv = 'prev' nav_pvY = self._mknav(' <<', pv, month, year-1) nav_frM = self._mknav(' < ', pv, frMonth, frYear) nav_pvM = self._mknav(' « ', pv, prevMonth, prevYear) nav_nxM = self._mknav(' » ', nx, nextMonth, nextYear) nav_ffM = self._mknav(' > ', nx, ffMonth, ffYear) nav_nxY = self._mknav(' >>', nx, month, year+1) # add buttons for going to previous months and year buff(nav_pvY, nav_frM, nav_pvM) # The caption will always be there. buff(tag.strong(to_unicode(format_date(self._mkdatetime( year, month), '%B %Y')))) if showbuttons is True: # add buttons for going to next months and year buff(nav_nxM, nav_ffM, nav_nxY) buff = tag.table(buff) width=":".join(['min-width', cal_width]) buff(class_='wikiTicketCalendar', style=width) heading = tag.tr() heading(align='center') for day in calendar.weekheader(2).split()[:-2]: col = tag.th(day) col(class_='workday', scope='col') heading(col) for day in calendar.weekheader(2).split()[-2:]: col = tag.th(day) col(class_='weekend', scope='col') heading(col) heading = buff(tag.thead(heading)) # Building main calendar table body buff = tag.tbody() for row in cal: line = tag.tr() line(align='right') for day in row: if not day: cell = tag.td('') cell(class_='fill') line(cell) else: # check for wikipage with name specified in # 'wiki_page_format' wiki = format_date(self._mkdatetime(year, month, day), wiki_page_format) url = self.env.href.wiki(wiki) if WikiSystem(self.env).has_page(wiki): a_class = "day_haspage" title = _("Go to page %s") % wiki else: a_class = "day" url += "?action=edit" # adding template name, if specified if wiki_page_template != "": url += "&template=" + wiki_page_template title = _("Create page %s") % wiki if day == curr_day: td_class = 'today' else: td_class = 'day' cell = tag.a(tag.b(day), href=url) cell(class_=a_class, title_=title) cell = tag.td(cell) cell(class_=td_class, valign='top') day_dt = self._mkdatetime(year, month, day) day_ts = to_utimestamp(day_dt) day_ts_eod = day_ts + 86399999999 # check for milestone(s) on that day db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute(""" SELECT name FROM milestone WHERE due >= %s and due <= %s """, (day_ts, day_ts_eod)) while (1): row = cursor.fetchone() if row is None: cell(tag.br()) break else: name = to_unicode(row[0]) url = self.env.href.milestone(name) milestone = '* ' + name milestone = tag.div(tag.a(milestone, href=url)) milestone(class_='milestone') cell(milestone) match = [] match_od = [] ticket_heap = tag('') ticket_list = tag.div('') ticket_list(align='left', class_='condense') # get tickets with due date set to day for t in self.tickets: due = t.get(self.due_field_name) if due is None or due in ['', '--']: continue else: if self.due_field_fmt == 'ts': if not isinstance(due, datetime.datetime): continue due_ts = to_utimestamp(due) if due_ts < day_ts or \ due_ts > day_ts_eod: continue else: duedate = format_date(day_dt, self.due_field_fmt) if not due == duedate: continue id = t.get('id') ticket, short = self._gen_ticket_entry(t) ticket_heap(ticket) if not id in match: if len(match) == 0: ticket_list(short) else: ticket_list(', ', short) match.append(id) # optionally get tickets created on day if show_t_open_dates is True: ticket_od_list = tag.div('') ticket_od_list(align='left', class_='opendate_condense') for t in self.tickets: ticket_ts = to_utimestamp(t.get('time')) if ticket_ts < day_ts or ticket_ts > day_ts_eod: continue a_class = 'opendate_' id = t.get('id') ticket, short = self._gen_ticket_entry(t, a_class) ticket_heap(ticket) if not id in match: if len(match_od) == 0: ticket_od_list(short) else: ticket_od_list(', ', short) match_od.append(id) matches = len(match) + len(match_od) if list_condense > 0 and matches >= list_condense: if len(match_od) > 0: if len(match) > 0: ticket_list(', ') ticket_list = tag(ticket_list, ticket_od_list) line(cell(ticket_list)) else: line(cell(ticket_heap)) buff(line) buff = tag.div(heading(buff)) if cal_width.startswith('+') is True: width=":".join(['width', cal_width]) buff(class_='wikiTicketCalendar', style=width) else: buff(class_='wikiTicketCalendar') return buff
def render(self, ticketset): return_div = tag.div(class_=self.cssclass+' projectplanrender' ) # check for missing parameters missingparameter = False if self.rows == [] or self.rows == None: return_div(tag.div('Missing parameter "rows": use a semicolon-separated list to input the "'+self.rowtype+'".', class_='ppwarning')) missingparameter = True if self.rowtype == None or str(self.rowtype).strip() == '': return_div(tag.div('Missing parameter "rowtype": specifies the ticket attribute that should be showed at the rows.', class_='ppwarning')) missingparameter = True if self.cols == [] or self.cols == None: return_div(tag.div('Missing parameter: use a semicolon-separated list to input the "cols".', class_='ppwarning')) missingparameter = True if self.coltype == None or str(self.coltype).strip() == '': return_div(tag.div('Missing parameter "coltype": specifies the ticket attribute that should be showed in the columns.', class_='ppwarning')) missingparameter = True if missingparameter: return return_div #ul = tag.ul() #for tid in ticketset.getIDSortedList(): #ticket = ticketset.getTicket(tid) #ul( tag.li(tid, " ",ticket.getfield('component') , " ", ticket.getfield('owner') )) #return_div(ul) def getstatistictitle( statusdict ): mytitle = '' mysum = 0 for status in statusdict: mytitle += "%s: %s\n" % (status, str(statusdict[status]) ) mysum += int(statusdict[status]) mytitle += "%s: %s" % ('number', mysum) return mytitle def setKV( myStruct, myKey, newValue ): ''' shortcut to set the values correctly used to reduce the code needed while using a list as key of a dict ''' myStruct[str(myKey)] = newValue def tableKeyPrettyPrint( mylist ) : ''' transform a list of keys to a user readable string in: ['a','b'] --> out: 'a|b' ''' return '|'.join(mylist) def tableKeyQueryParameter( parameter, mylist ) : ''' transform a list of keys to a Trac query string parameter (OR) in: x, ['a','b'] --> out: 'x=a&x=b' ''' return '&'.join([ "%s=%s" % (parameter, s) for s in mylist ]) chartheight=80 chartwidth=170 data = {} statistics = {} # init table data for row in self.rows : colstatistics = {} colkeys = {} for col in self.cols : # colkeys[col] = [] setKV( colkeys, col, [] ) # colstatistics[col] = {} setKV( colstatistics, col, {} ) # data[row] = colkeys setKV( data, row, colkeys ) # statistics[row] = colstatistics setKV( statistics, row, colstatistics ) for tid in ticketset.getIDSortedList(): ticket = ticketset.getTicket(tid) ticket_rowtype = ticket.getfield(self.rowtype) ticket_coltype = ticket.getfield(self.coltype) # determine the data cell where the ticket has to be added, keep in mind that rows and cols are list of lists for row in self.rows : for col in self.cols : if ticket_rowtype in row and ticket_coltype in col : data[str(row)][str(col)].append(ticket) # save tickets at precise values of row and col self.log_debug('row:%s col:%s append:%s' % (row,col,tid)) # if ticket_rowtype in self.rows and ticket_coltype in self.cols : # create HTML table table = tag.table( class_="data pptableticketperday" , border = "1", style = 'width:auto;') # create HTML table head thead = tag.thead() tr = tag.tr() tr( tag.th("%s vs %s" % (self.rowtype,self.coltype) ) ) for colkey in self.cols : tr( tag.th(tag.h4(tag.a( tableKeyPrettyPrint(colkey), href=self.macroenv.tracenv.href()+('/query?%s&order=%s' % ( tableKeyQueryParameter(self.coltype, colkey),self.rowtype)) )),title="%s is %s" % (self.coltype, tableKeyPrettyPrint(colkey) ) ) ) # first line with all colkeys if self.showsummarypiechart: tr( tag.th(tag.h4( "Ticket Overview" ) ) ) thead(tr) table(thead) # create HTML table body tbody = tag.tbody() counter=0 for rowkey in self.rows : # switch line color if counter % 2 == 1: class_ = 'odd' else: class_ = 'even' counter += 1 tr = tag.tr( class_=class_ ) # new line td = tag.td() # new cell td(tag.h5(tag.a( tableKeyPrettyPrint(rowkey), href=self.macroenv.tracenv.href()+('/query?%s&order=%s' % ( tableKeyQueryParameter( self.rowtype,rowkey),self.coltype)) )),title="%s is %s" % (self.rowtype, tableKeyPrettyPrint(rowkey) ) ) # first cell contains row key tr(td) for colkey in self.cols : td = tag.td() for ticket in data[str(rowkey)][str(colkey)] : td( tag.span(self.createTicketLink(ticket), class_ = 'ticket_inner' ), " " , mytitle="%s is %s and %s is %s" % (self.rowtype,rowkey,self.coltype,colkey) ) # mytitle might be used later by javascript if not statistics[str(rowkey)][str(colkey)].has_key( ticket.getstatus() ) : statistics[str(rowkey)][str(colkey)][ticket.getstatus()] = 0 statistics[str(rowkey)][str(colkey)][ticket.getstatus()] += 1 tr(td) # compute statistics rowstatistics = {} count = 0 for colkey in statistics[str(rowkey)] : for status in statistics[str(rowkey)][str(colkey)] : if not rowstatistics.has_key(status) : rowstatistics[status] = 0 try: rowstatistics[status] += statistics[str(rowkey)][str(colkey)][status] count += statistics[str(rowkey)][str(colkey)][status] except: pass if self.showsummarypiechart: tr(tag.td(tag.img(src=self.createGoogleChartFromDict('ColorForStatus', rowstatistics, '%s tickets' % (count,), height=chartheight )), class_='ppstatistics' , title=getstatistictitle(rowstatistics)) ) # Summary tbody(tr) table(tbody) # create HTML table foot if self.showsummarypiechart : fullstatistics = {} tfoot = tag.tfoot() tr = tag.tr() tr( tag.td(tag.h5('Ticket Overview') ) ) # create statistics for col fullcount = 0 for colkey in self.cols : colstatistics = {} colcount = 0 for rowkey in self.rows : for status in statistics[str(rowkey)][str(colkey)] : if not fullstatistics.has_key(status) : fullstatistics[status] = 0 if not colstatistics.has_key(status) : colstatistics[status] = 0 try: colstatistics[status] += statistics[str(rowkey)][str(colkey)][status] colcount += statistics[str(rowkey)][str(colkey)][status] fullstatistics[status] += statistics[str(rowkey)][str(colkey)][status] fullcount += statistics[str(rowkey)][str(colkey)][status] except: pass tr(tag.td(tag.img(src=self.createGoogleChartFromDict('ColorForStatus', colstatistics, '%s tickets' % (colcount,), height=chartheight)), title=getstatistictitle(colstatistics) )) # Col Summary tr(tag.td(tag.img(src=self.createGoogleChartFromDict('ColorForStatus', fullstatistics, '%s tickets' % (fullcount,), height=chartheight)), class_='ppstatistics', title=getstatistictitle(fullstatistics))) # Full Summary tfoot(tr) table(tfoot) return_div(table) return return_div
def expand_macro(self, formatter, name, arguments): self.ref = formatter self.tz_info = formatter.req.tz self.thistime = datetime.datetime.now(self.tz_info) # Parse arguments from macro invocation args, kwargs = parse_args(arguments, strict=False) # Find out whether use http param, current or macro param year/month http_param_year = formatter.req.args.get('year', '') http_param_month = formatter.req.args.get('month', '') if http_param_year == "": # not clicked on a prev or next button if len(args) >= 1 and args[0] <> "*": # year given in macro parameters year = int(args[0]) else: # use current year year = self.thistime.year else: # year in http params (clicked by user) overrides everything year = int(http_param_year) if http_param_month == "": # not clicked on a prev or next button if len(args) >= 2 and args[1] <> "*": # month given in macro parameters month = int(args[1]) else: # use current month month = self.thistime.month else: # month in http params (clicked by user) overrides everything month = int(http_param_month) showbuttons = True if len(args) >= 3 or kwargs.has_key('nav'): try: showbuttons = kwargs['nav'] in ["True", "true", "yes", "1"] except KeyError: showbuttons = args[2] in ["True", "true", "yes", "1"] wiki_page_format = "%Y-%m-%d" if len(args) >= 4 and args[3] != "*" or kwargs.has_key('wiki'): try: wiki_page_format = str(kwargs['wiki']) except KeyError: wiki_page_format = str(args[3]) # Support relative paths in macro arguments for wiki page links. wiki_page_format = self._resolve_relative_name(wiki_page_format, formatter.resource.id) list_condense = 0 show_t_open_dates = True wiki_page_template = "" wiki_subpages = [] # Read optional check plan. check = [] if kwargs.has_key('check'): check = kwargs['check'].split('.') if name == 'WikiTicketCalendar': if len(args) >= 5 or kwargs.has_key('cdate'): try: show_t_open_dates = kwargs['cdate'] in \ ["True", "true", "yes", "1"] except KeyError: show_t_open_dates = args[4] in \ ["True", "true", "yes", "1"] # Optional page template to create new wiki pages. # The default (empty page) is used, if the template name is invalid. if len(args) >= 6 or kwargs.has_key('base'): try: wiki_page_template = kwargs['base'] except KeyError: wiki_page_template = args[5] if name == 'WikiTicketCalendar': # TracQuery support for ticket selection query_args = "id!=0" if len(args) >= 7 or kwargs.has_key('query'): # prefer query arguments provided by kwargs try: query_args = kwargs['query'] except KeyError: query_args = args[6] tickets = WikiCalendarTicketProvider(self.env) self.tickets = tickets.harvest(formatter.req, query_args) # compress long ticket lists if len(args) >= 8 or kwargs.has_key('short'): # prefer query arguments provided by kwargs try: list_condense = int(kwargs['short']) except KeyError: list_condense = int(args[7]) # control calendar display width cal_width = "100%;" if len(args) >= 9 or kwargs.has_key('width'): # prefer query arguments provided by kwargs try: cal_width = kwargs['width'] except KeyError: cal_width = args[8] # multiple wiki (sub)pages per day if kwargs.has_key('subpages'): wiki_subpages = kwargs['subpages'].split('|') # Can use this to change the day the week starts on, # but this is a system-wide setting. calendar.setfirstweekday(calendar.MONDAY) cal = calendar.monthcalendar(year, month) curr_day = None if year == self.thistime.year and month == self.thistime.month: curr_day = self.thistime.day # for prev/next navigation links prevMonth = month - 1 nextMonth = month + 1 nextYear = prevYear = year # check for year change (KISS version) if prevMonth == 0: prevMonth = 12 prevYear -= 1 if nextMonth == 13: nextMonth = 1 nextYear += 1 # for fast-forward/-rewind navigation links ffYear = frYear = year if month < 4: frMonth = month + 9 frYear -= 1 else: frMonth = month - 3 if month > 9: ffMonth = month - 9 ffYear += 1 else: ffMonth = month + 3 last_week_prevMonth = calendar.monthcalendar(prevYear, prevMonth)[-1] first_week_nextMonth = calendar.monthcalendar(nextYear, nextMonth)[0] # Switch to user's locale, if available. try: loc_req = str(formatter.req.locale) except AttributeError: # Available since in Trac 0.12 . loc_req = None if loc_req: loc = locale.getlocale() loc_prop = locale.normalize(loc_req) try: locale.setlocale(locale.LC_TIME, loc_prop) except locale.Error: try: # Re-try with UTF-8 as last resort. loc_prop = '.'.join([loc_prop.split('.')[0], 'utf8']) locale.setlocale(locale.LC_TIME, loc_prop) except locale.Error: loc_prop = None self.env.log.debug('Locale setting for calendar: ' + str(loc_prop)) # Finally building the output # Begin with caption and optional navigation links buff = tag.tr() if showbuttons is True: # calendar navigation buttons nx = 'next' pv = 'prev' nav_pvY = self._mknav('<<', pv, month, year - 1) nav_frM = self._mknav(' <', pv, frMonth, frYear) nav_pvM = self._mknav(' «', pv, prevMonth, prevYear) nav_nxM = self._mknav('» ', nx, nextMonth, nextYear) nav_ffM = self._mknav('> ', nx, ffMonth, ffYear) nav_nxY = self._mknav('>>', nx, month, year + 1) # add buttons for going to previous months and year buff(nav_pvY, nav_frM, nav_pvM) # The caption will always be there. heading = tag.td( to_unicode(format_date(self._mkdatetime(year, month), '%B %Y'))) buff = buff(heading(class_='y')) if showbuttons is True: # add buttons for going to next months and year buff(nav_nxM, nav_ffM, nav_nxY) buff = tag.caption(tag.table(tag.tbody(buff))) buff = tag.table(buff) if name == 'WikiTicketCalendar': if cal_width.startswith('+') is True: width = ":".join(['min-width', cal_width]) buff(class_='wikitcalendar', style=width) else: buff(class_='wikitcalendar') if name == 'WikiCalendar': buff(class_='wiki-calendar') heading = tag.tr() heading(align='center') for day in calendar.weekheader(2).split()[:-2]: col = tag.th(to_unicode(day)) col(class_='workday', scope='col') heading(col) for day in calendar.weekheader(2).split()[-2:]: col = tag.th(to_unicode(day)) col(class_='weekend', scope='col') heading(col) heading = buff(tag.thead(heading)) # Building main calendar table body buff = tag.tbody() w = -1 for week in cal: w = w + 1 line = tag.tr() line(align='right') d = -1 for day in week: d = d + 1 if day: # check for wikipage with name specified in # 'wiki_page_format' wiki = format_date(self._mkdatetime(year, month, day), wiki_page_format) if day == curr_day: a_class = 'day today' td_class = 'today' else: a_class = 'day' td_class = 'day' day_dt = self._mkdatetime(year, month, day) if uts: day_ts = to_utimestamp(day_dt) day_ts_eod = day_ts + 86399999999 else: day_ts = to_timestamp(day_dt) day_ts_eod = day_ts + 86399 # check for milestone(s) on that day db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute( """ SELECT name FROM milestone WHERE due >= %s and due <= %s """, (day_ts, day_ts_eod)) milestones = tag() for row in cursor: if not a_class.endswith('milestone'): a_class += ' milestone' milestone = to_unicode(row[0]) url = self.env.href.milestone(milestone) milestone = '* ' + milestone milestones = tag( milestones, tag.div(tag.a(milestone, href=url), class_='milestone')) day = tag.span(day) day(class_='day') if len(wiki_subpages) > 0: pages = tag(day, Markup('<br />')) for page in wiki_subpages: label = tag(' ', page[0]) page = '/'.join([wiki, page]) url = self.env.href.wiki(page) pages( self._gen_wiki_links(page, label, 'subpage', url, wiki_page_template, check)) else: url = self.env.href.wiki(wiki) pages = self._gen_wiki_links(wiki, day, a_class, url, wiki_page_template, check) cell = tag.td(pages) cell(class_=td_class, valign='top') if name == 'WikiCalendar': line(cell) else: if milestones: cell(milestones) else: cell(tag.br()) match = [] match_od = [] ticket_heap = tag('') ticket_list = tag.div('') ticket_list(align='left', class_='condense') # get tickets with due date set to day for t in self.tickets: due = t.get(self.tkt_due_field) if due is None or due in ['', '--']: continue else: if self.tkt_due_format == 'ts': if not isinstance(due, datetime.datetime): continue if uts: due_ts = to_utimestamp(due) else: due_ts = to_timestamp(due) if due_ts < day_ts or due_ts > day_ts_eod: continue else: # Beware: Format might even be unicode str duedate = format_date( day_dt, str(self.tkt_due_format)) if not due == duedate: continue id = t.get('id') ticket, short = self._gen_ticket_entry(t) ticket_heap(ticket) if not id in match: if len(match) == 0: ticket_list(short) else: ticket_list(', ', short) match.append(id) # optionally get tickets created on day if show_t_open_dates is True: ticket_od_list = tag.div('') ticket_od_list(align='left', class_='opendate_condense') for t in self.tickets: if uts: ticket_ts = to_utimestamp(t.get('time')) else: ticket_ts = to_timestamp(t.get('time')) if ticket_ts < day_ts or \ ticket_ts > day_ts_eod: continue a_class = 'opendate_' id = t.get('id') ticket, short = self._gen_ticket_entry( t, a_class) ticket_heap(ticket) if not id in match: if len(match_od) == 0: ticket_od_list(short) else: ticket_od_list(', ', short) match_od.append(id) matches = len(match) + len(match_od) if list_condense > 0 and matches >= list_condense: if len(match_od) > 0: if len(match) > 0: ticket_list(', ') ticket_list = tag(ticket_list, ticket_od_list) line(cell(ticket_list)) else: line(cell(ticket_heap)) else: if name == 'WikiCalendar': if w == 0: day = last_week_prevMonth[d] wiki = format_date( self._mkdatetime(prevYear, prevMonth, day), wiki_page_format) else: day = first_week_nextMonth[d] wiki = format_date( self._mkdatetime(nextYear, nextMonth, day), wiki_page_format) url = self.env.href.wiki(wiki) a_class = 'day adjacent_month' pages = self._gen_wiki_links(wiki, day, a_class, url, wiki_page_template) cell = tag.td(pages) cell(class_='day adjacent_month') line(cell) else: cell = tag.td('') cell(class_='day adjacent_month') line(cell) buff(line) if loc_req and loc_prop: # We may have switched to users locale, resetting now. try: locale.setlocale(locale.LC_ALL, loc) self.env.log.debug('Locale setting restored: ' + str(loc)) except locale.Error: pass buff = tag.div(heading(buff)) if name == 'WikiTicketCalendar': if cal_width.startswith('+') is True: width = ":".join(['width', cal_width]) buff(class_='wikitcalendar', style=width) else: buff(class_='wikitcalendar') if name == 'WikiCalendar': buff(class_='wiki-calendar') # Add common CSS stylesheet if self.internal_css and not self.ref.req.args.get('wikicalendar'): # Put definitions directly into the output. f = open('/'.join([self.htdocs_path, 'wikicalendar.css']), 'Ur') css = tag.style(Markup('<!--\n'), '\n'.join(f.readlines()), Markup('-->\n'))(type="text/css") f.close() # Add hint to prevent multiple inclusions. self.ref.req.args['wikicalendar'] = True return tag(css, buff) elif not self.ref.req.args.get('wikicalendar'): add_stylesheet(self.ref.req, 'wikicalendar/wikicalendar.css') return buff
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
def render_property(self, name, mode, context, props): """Parse svn:mergeinfo and svnmerge-* properties, converting branch names to links and providing links to the revision log for merged and eligible revisions. """ has_eligible = name in ('svnmerge-integrated', 'svn:mergeinfo') revs_label = (_('merged'), _('blocked'))[name.endswith('blocked')] revs_cols = has_eligible and 2 or None reponame = context.resource.parent.id target_path = context.resource.id repos = RepositoryManager(self.env).get_repository(reponame) target_rev = context.resource.version if has_eligible: node = repos.get_node(target_path, target_rev) branch_starts = {} for path, rev in node.get_copy_ancestry(): if path not in branch_starts: branch_starts[path] = rev + 1 rows = [] if name.startswith('svnmerge-'): sources = props[name].split() else: sources = props[name].splitlines() for line in sources: path, revs = line.split(':', 1) spath = _path_within_scope(repos.scope, path) if spath is None: continue revs = revs.strip() inheritable, non_inheritable = _partition_inheritable(revs) revs = ','.join(inheritable) deleted = False try: node = repos.get_node(spath, target_rev) resource = context.resource.parent.child('source', spath) if 'LOG_VIEW' in context.perm(resource): row = [_get_source_link(spath, context), _get_revs_link(revs_label, context, spath, revs)] if non_inheritable: non_inheritable = ','.join(non_inheritable) row.append(_get_revs_link(_('non-inheritable'), context, spath, non_inheritable, _('merged on the directory ' 'itself but not below'))) if has_eligible: first_rev = branch_starts.get(spath) if not first_rev: first_rev = node.get_branch_origin() eligible = set(xrange(first_rev or 1, target_rev + 1)) eligible -= set(Ranges(revs)) blocked = _get_blocked_revs(props, name, spath) if blocked: eligible -= set(Ranges(blocked)) if eligible: nrevs = repos._get_node_revs(spath, max(eligible), min(eligible)) eligible &= set(nrevs) eligible = to_ranges(eligible) row.append(_get_revs_link(_('eligible'), context, spath, eligible)) rows.append((False, spath, [tag.td(each) for each in row])) continue except NoSuchNode: deleted = True revs = revs.replace(',', u',\u200b') rows.append((deleted, spath, [tag.td('/' + spath), tag.td(revs, colspan=revs_cols)])) if not rows: return None rows.sort() has_deleted = rows and rows[-1][0] or None return tag(has_deleted and tag.a(_('(toggle deleted branches)'), class_='trac-toggledeleted', href='#'), tag.table(tag.tbody( [tag.tr(row, class_=deleted and 'trac-deleted' or None) for deleted, spath, row in rows]), class_='props'))
def expand_macro(self, formatter, name, args): """ Called by the formatter to render the parsed wiki text. """ def to_html(text): """ Format the parsed text from the rows into HTML. """ if not text: return '' # simple check to determine whether format_to_html or # format_to_oneliner should be used. If there are multiple # lines, use format_to_html, otherwise use format_to_oneliner stripped_text = text.strip() splitlines = stripped_text.splitlines() if len(splitlines) > 1: formatted_text = format_to_html(self.env, formatter.context, text) else: formatted_text = '<br />'.join( [format_to_oneliner(self.env, formatter.context, line) \ for line in text.splitlines()] ) return Markup( formatted_text ) if not args: return Markup() # use the formatter to find the TablePluginStyles if not formatter.wiki.has_page('TablePluginStyles'): # if our build_table_plugin_styles_page(self.env) if formatter.wiki.has_page('TablePluginStyles'): # at this point, the new style page should exist # so use the styles defined there. config_args = self._parse_wiki_style_page(self.env) else: # nice error handling in here possible incase # the page cannot be created for whatever reason? pass config_table_styles, config_css_styles = self._parse_styles(config_args) args_table_styles, args_css_styles = self._parse_styles(args) global_css_styles = dict(config_css_styles.items() + args_css_styles.items()) column_list = [] row_count = 0 row_dict = {} row_list = [] heading = False body_name = '' heading_set = False body_set_first = False first_row_as_header = False is_row = False table_id = '' table_data = '' table_style = '' column_style_dict = {} for attribute in self._parse_args(args): if 'table' in attribute: # get the table id to use table_id = attribute['table']['name'] table_data = attribute['table']['data'] table_style = attribute['table']['style'].replace('@', '') elif 'css' in attribute: pass elif 'column' in attribute: column_name = attribute['column']['name'] column_list.append(column_name) if attribute['column']['style']: column_style_dict[column_name] = attribute['column']['style'] elif 'header' in attribute: heading = True heading_set = True elif 'body' in attribute: body_name = str(uuid4()) if not heading_set and not first_row_as_header: body_set_first = True heading = False elif 'row' in attribute: is_row = True row_count = 0 row_style = attribute['row']['style'] else: if not heading_set and not body_set_first: first_row_as_header = True for key, value in attribute.items(): value['heading'] = heading if body_name: value['body_name'] = body_name if is_row: # if its a row, apply row style original_style = value['style'] value['style'] = ' '.join([original_style, row_style]) if row_count == (len(column_list) - 1): row_dict.update(attribute) row_list.append(row_dict) row_dict = {} row_count = 0 is_row = False else: row_dict.update(attribute) row_count += 1 thead = tag.thead() for row in row_list: if body_set_first: break header_row = tag.tr() for column in column_list: header_cell = '' if row[column]['heading'] or first_row_as_header: header_cell = tag.th() header_cell(to_html(row[column]['data']), class_=row[column]['style'].replace('@', '')) header_row(header_cell) if header_cell: thead(header_row) if first_row_as_header: break if table_style: table_id = table_style table_data = config_table_styles[table_style]['data'] css_data = self._build_css_template(global_css_styles) full_style_data = '' full_style_data += table_data full_style_data += css_data tstyle = tag.style() tstyle = tag.style(full_style_data) tbody = tag.tbody() body_name = '' body_set = False tbody_list = [] for row in row_list: if first_row_as_header: heading_set = False first_row_as_header = False continue trow = tag.tr() tcol = '' for column in column_list: if row[column]['heading']: continue if row[column]['body_name'] == body_name and not body_set: body_set = True elif row[column]['body_name'] != body_name and not body_set: body_name = row[column]['body_name'] body_set = True elif row[column]['body_name'] != body_name and body_set: tbody_list.append(tbody) tbody = tag.tbody() trow = tag.tr() body_name = row[column]['body_name'] tcol = tag.td() # if there is a column style available, # add it to what is already there formatted_column_style = '' if column in column_style_dict: column_style = column_style_dict[column].replace('@', '') formatted_column_style = column_style.replace(';', ' ') class_styles = row[column]['style'].replace('@', '') formatted_class_styles = class_styles.replace(';', ' ') formatted_item = ' '.join([formatted_class_styles, formatted_column_style]) tcol(to_html(row[column]['data']), class_=formatted_item) trow(tcol) if tcol: tbody(trow) tbody_list.append(tbody) return tag.table([tstyle, thead, tbody_list], class_=table_id)
def render(self, ticketset): # add needed javascript and css files self.addJsChartFiles() if self.firstsegment == None or self.lastsegment == None: return self.divWarning( 'you have to set the time period via the macro parameters. Example: first=2012-02-03, last=2012-02-19 .' ) tickets = ticketset.getIDList() # stop if no tickets available if len(tickets) == 0: return self.divWarning('No tickets available.') changes = {} initalvalues = {} creationdates = {} (firstday, lastday) = self.getBoundaries({}) alldates = self.getAllRelevantDates(firstday, lastday) lastdaySecs = lastday firstday = self.normalizeToDay(firstday) lastday = self.normalizeToDay(lastday) (ticketCount, relevantChanges) = self.computeInformationAboutSegment( ticketset, firstday, lastday) # count only the tickets that are relevant within the requested time period (firstday,lastday) relevantTickets = ticketCount closedTickets = {} # change this openTickets = {} # change this changeDateStrings = relevantChanges.keys() changeDateStrings.sort() # ticketCount = 70 # DEBUG for changeDateStr in changeDateStrings: # for each day self.macroenv.tracenv.log.debug( "changes %4s: %3s (%s)" % (changeDateStr, relevantChanges[changeDateStr], ticketCount)) self.setKeyIfNotExistsInt(closedTickets, changeDateStr, -1 * relevantChanges[changeDateStr]) ticketCount = ticketCount + relevantChanges[ changeDateStr] # reduce the number of open tickets self.setKeyIfNotExistsInt(openTickets, changeDateStr, ticketCount) # generate HTML holderid = "%s_%s_%d_%d" % ( self.getNameOfRenderFrame(), 'holder', int(time.time() * 1000000), random.randint(1, 1024)) # compute unique element id currentday = firstday frame = tag.div(class_='invisible ppConfBurnDown', id=self.getNameOfRenderFrame() + holderid) tableData = tag.table(class_="invisible data", border="1", style='width:auto;') trDays = tag.tr() trDaysAxis = tag.tr() trOpenTickets = tag.tr() trClosedTickets = tag.tr() trReopenedTickets = tag.tr() alldates.sort() lastOpenTicket = relevantTickets # fallback: all tickets lastClosedTickets = 0 counter = 0 todaySecs = self.datetime2seconds(datetime.datetime.today()) todayStr = self.getDateString(todaySecs) todayid = 0 maxValue = 0 if lastdaySecs <= todaySecs: # if today is later then the shown time frame then mark the last column todayid = len(alldates) for currentday in alldates: if currentday == todayStr: # capture the column with the current date todayid = counter counter = counter + 1 trDays( tag.th(currentday.replace("\n", " ")) ) # text for info box, no line breaks here, because it is limited to 3 lines trDaysAxis(tag.th(currentday)) if openTickets.has_key(currentday): lastOpenTicket = openTickets[currentday] trOpenTickets(tag.td(lastOpenTicket)) if closedTickets.has_key(currentday): lastClosedTickets = closedTickets[currentday] else: lastClosedTickets = 0 trClosedTickets(tag.td(lastClosedTickets)) maxValue = max(len(str(lastClosedTickets)), len(str(lastOpenTicket)), maxValue) trReopenedTickets(tag.td('0')) tableData(tag.thead(trDays)) tableData(tag.tfoot(trDaysAxis)) tableData(tag.tbody(trOpenTickets, trClosedTickets, trReopenedTickets)) (label1, label2) = self.getLabel() # caculate the scale factor for the info box within the chart maxGlobal = max( len(str(label1)) + maxValue, len(str(label2)) + maxValue) if self.segment in ['week', 'twoweek' ]: # they use long descriptions in the info box maxGlobal = max(maxGlobal, 27) # configuration of renderer frame(tag.div(str(relevantTickets), class_='maxTasks')) frame(tag.div(self.width, class_='width')) frame(tag.div(self.height, class_='height')) frame(tag.div(str(maxGlobal), class_='maxlength')) frame(tag.div(label1, class_='label1')) frame(tag.div(label2, class_='label2')) frame( tag.div(str(todayid), class_='today')) # number of column with the current date frame(tag.div(holderid, class_='holder')) # where to put the chart in frame(tableData) outerframe = tag.div() # div as global container #outerframe(outer) # DEBUG outerframe(frame) outerframe(tag.div(id=holderid), style="border-width:1px") return outerframe
def _body_rows(): for idx, line in enumerate(_group_lines(stream)): row = tag.tr() if marks and idx + 1 in marks: row(class_='hilite') for annotator, data in annotator_datas: if annotator: annotator.annotate_row(context, row, idx+1, line, data) else: row.append(tag.td()) row.append(tag.td(line)) yield row return tag.table(class_='code')( tag.thead(_head_row()), tag.tbody(_body_rows()) ) def get_max_preview_size(self): """:deprecated: use `max_preview_size` attribute directly.""" return self.max_preview_size def get_charset(self, content='', mimetype=None): """Infer the character encoding from the `content` or the `mimetype`. `content` is either a `str` or an `unicode` object. The charset will be determined using this order: * from the charset information present in the `mimetype` argument * auto-detection of the charset from the `content` * the configured `default_charset`
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': # Add our own styles for the ticket lists. add_stylesheet(req, 'ct/css/childtickets.css') # 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: # The additional section on the ticket is built up of (potentially) three parts: header, ticket table, buttons. These # are all 'wrapped up' in a 'div' with the 'attachments' id (we'll just pinch this to make look and feel consistent with any # future changes!) filter = Transformer('//div[@id="ticket"]') snippet = tag.div() # Are there any child tickets to display? childtickets = [ Ticket(self.env,n) for n in self.childtickets.get(ticket.id,[]) ] # (tempish) fix for #8612 : force sorting by ticket id childtickets = sorted(childtickets, key=lambda t: t.id) # Are child tickets allowed? childtickets_allowed = self.config.getbool('childtickets', 'parent.%s.allow_child_tickets' % ticket['type']) # If there are no childtickets and the ticket should not have any child tickets, we can simply drop out here. if not childtickets_allowed and not childtickets: return stream # Our 'main' display consists of two divs. buttondiv = tag.div() tablediv = tag.div() # Test if the ticket has children: If so, then list in pretty table. if childtickets: # trac.ini : Which columns to display in child ticket listing? columns = self.config.getlist('childtickets', 'parent.%s.table_headers' % ticket['type'], default=['summary','owner']) tablediv = 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", ), tag.br(), ) # trac.ini : child tickets are allowed - Set up 'create new ticket' buttons. if childtickets_allowed: # 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: # 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')) # ... create a default submit button if ticket['status'] == 'closed': submit_button_fields = ( tag.input(type="submit",disabled="disabled",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="childticket",value="New Child Ticket",title="Create a child ticket"), tag.input(type="hidden",name="type",value=default_child_type), ) else: if ticket['status'] == 'closed': submit_button_fields = [ tag.input(type="submit",disabled="disabled",name="type",value="%s" % ticket_type,title="Create a %s child ticket" % ticket_type) for ticket_type in restrict_child_types ] 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 ] buttondiv = tag.form( tag.div( default_child_fields, inherited_child_fields, submit_button_fields), method="get", action=req.href.newticket(), ) snippet.append(tag.h2("Child Tickets",class_="foldable")) snippet.append(tag.div(tablediv, buttondiv, id="childtickets")) return stream | filter.after(snippet) return stream
def render_property_diff(self, name, old_context, old_props, new_context, new_props, options): # Build 5 columns table showing modifications on merge sources # || source || added || removed || added (ni) || removed (ni) || # || source || removed || rm = RepositoryManager(self.env) repos = rm.get_repository(old_context.resource.parent.id) def parse_sources(props): sources = {} for line in props[name].splitlines(): path, revs = line.split(':', 1) spath = _path_within_scope(repos.scope, path) if spath is not None: inheritable, non_inheritable = _partition_inheritable(revs) sources[spath] = (set(Ranges(inheritable)), set(Ranges(non_inheritable))) return sources old_sources = parse_sources(old_props) new_sources = parse_sources(new_props) # Go through new sources, detect modified ones or added ones blocked = name.endswith('blocked') added_label = [_("merged: "), _("blocked: ")][blocked] removed_label = [_("reverse-merged: "), _("un-blocked: ")][blocked] added_ni_label = _("marked as non-inheritable: ") removed_ni_label = _("unmarked as non-inheritable: ") def revs_link(revs, context): if revs: revs = to_ranges(revs) return _get_revs_link(revs.replace(',', u',\u200b'), context, spath, revs) modified_sources = [] for spath, (new_revs, new_revs_ni) in new_sources.iteritems(): if spath in old_sources: (old_revs, old_revs_ni), status = old_sources.pop(spath), None else: old_revs = old_revs_ni = set() status = _(' (added)') added = new_revs - old_revs removed = old_revs - new_revs added_ni = new_revs_ni - old_revs_ni removed_ni = old_revs_ni - new_revs_ni try: all_revs = set(repos._get_node_revs(spath)) # TODO: also pass first_rev here, for getting smaller a set # (this is an optmization fix, result is already correct) added &= all_revs removed &= all_revs added_ni &= all_revs removed_ni &= all_revs except NoSuchNode: pass if added or removed: modified_sources.append(( spath, [_get_source_link(spath, new_context), status], added and tag(added_label, revs_link(added, new_context)), removed and tag(removed_label, revs_link(removed, old_context)), added_ni and tag(added_ni_label, revs_link(added_ni, new_context)), removed_ni and tag(removed_ni_label, revs_link(removed_ni, old_context)) )) # Go through remaining old sources, those were deleted removed_sources = [] for spath, old_revs in old_sources.iteritems(): removed_sources.append((spath, _get_source_link(spath, old_context))) if modified_sources or removed_sources: modified_sources.sort() removed_sources.sort() changes = tag.table(tag.tbody( [tag.tr(tag.td(c) for c in cols[1:]) for cols in modified_sources], [tag.tr(tag.td(src), tag.td(_('removed'), colspan=4)) for spath, src in removed_sources]), class_='props') else: changes = tag.em(_(' (with no actual effect on merging)')) return tag.li(tag_('Property %(prop)s changed', prop=tag.strong(name)), changes)
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 gen_calendar(self, tickets, query, month, width=None, nav=True): milestones = self._get_milestones() req = self.req locale = self._get_locale() first_week_day = self._get_first_week_day(locale) start_date_format = self.mod.start_date_format due_date_format = self.mod.due_date_format if not month: month = get_today(req.tz).replace(day=1) # init data today = get_today(req.tz) # generate calendar data weeks = self._get_month_calendar(month.year, month.month, first_week_day, locale) days = sorted(sum(weeks, [])) tickets = self._filter_tickets(days, tickets, req.tz) milestones = self._filter_milestones(days, milestones, req.tz) cal = [[{'date': day, 'tickets': tickets[day], 'milestones': milestones[day]} for day in week] for week in weeks] def genli(t): if isinstance(t, Milestone): return self._create_milestone_item(t) else: return self._create_ticket_item(t) def gentd(week_idx, day_info): day = day_info['date'] tt = day_info['milestones'] + day_info['tickets'] if len(tt) < 6: ttshow = tt ttall = [] else: ttshow = tt[:4] ttall = tt tdclass = [] if day == today: tdclass.append('today') if is_weekend(day, locale): tdclass.append('weekend') formatted_day = format_date(day, format='long', locale=locale) td = tag.td( class_=' '.join(tdclass), data_for_start_date=day.strftime(start_date_format), data_for_due_date=day.strftime(due_date_format), data_fdate=formatted_day) label = [] if day == today: label.append(tag.span(_("Today"), class_='today')) label.append(tag.span(unicode(day.day), class_=('day normal', 'day')[day == today])) td(tag.div(label)) if ttshow: td(tag.ul([genli(t) for t in ttshow])) if ttall: id = 'calendar-more-%s' % str(day) td(tag.a(_("%d more tickets...") % (len(ttall) - 4), href='#' + id, class_='show-all-list'), tag.div(tag.h4(formatted_day), tag.ul([genli(t) for t in ttall]), class_='ticketcalendar-popup-list', id=id, data_title=format_date(day, locale=locale, format='full'))) return td day_names = get_day_names(locale=locale, width='abbreviated') day_names = [day_names[(idx + first_week_day) % 7] for idx in xrange(7)] header = tag.div(class_='ticketcalendar-header') if nav: def nav_href(d): return self.get_box_href(query, d) def nav_pager(d): return tag.span( tag.a(u'\u25c4', href=nav_href(d - timedelta(days=1))), tag.a(_("Current month"), href=nav_href(get_today(req.tz))), tag.a(u'\u25ba', href=nav_href(d + timedelta(days=31))), class_='ticketcalendar-pager') def nav_macro(): macro = ( '[[TicketCalendar(type=box,month=%(month)s,' 'query=%(query)s,order=%(order)s%(extra)s)]]' % (dict(month=month.strftime('%Y-%m'), query=self.build_query_string(query.constraints), order=query.order, extra=query.desc and ',desc=1' or ''))) text = tag.input(type='text', readonly='readonly', size='80', value=macro, style='width:0;display:none') return tag.span(_("Macro"), text, class_='ticketcalendar-macro') header(tag.div(nav_macro(), nav_pager(month), class_='ticketcalendar-nav')) header(tag.h4(_("%(month_name)s, %(year)s", month_name=_get_month_name(month, locale), year=month.year))) calendar = tag.table( tag.thead(tag.tr(tag.th(name) for name in day_names)), tag.tbody([tag.tr([gentd(idx, d) for d in w]) for idx, w in enumerate(cal)]), class_='calendar') can_create = 'TICKET_CREATE' in req.perm ticket_box = tag.div( tag.h4(tag.span('', class_='tc-today-date')), tag.ul( tag.li(tag.a( tag_("New ticket with \"%(date)s\" as the start date", date=tag.span('', data='start-date')), data_href=req.href('newticket', [(self.mod.start_date_name, '')]), class_='newticket-start-date')), tag.li(tag.a( tag_("New ticket with \"%(date)s\" as the due date", date=tag.span('', data='due-date')), data_href=req.href('newticket', [(self.mod.due_date_name, '')]), class_='newticket-due-date'))), title=_("Create new ticket"), class_='ticketcalendar-newticket-box', style='display:none', data_writable=(None, 'writable')[can_create]) class_ = ('ticketcalendar', 'ticketcalendar ticketcalendar-can-create')[can_create] return tag.div(header, calendar, ticket_box, class_=class_, style=width and ('width: %s' % width) or None)
def expand_macro(self, formatter, name, arguments): """Returns macro content.""" env = self.env req = formatter.req tz = req.tz # Parse arguments from macro invocation. args, kwargs = parse_args(arguments, strict=False) # Enable week number display regardless of argument position. week_pref = 'w' in args and args.pop(args.index('w')) week_pref = week_pref and week_pref or kwargs.get('w') week_start = None week_num_start = None # Parse per-instance week calculation rules, if available. if week_pref: if ':' not in week_pref: # Treat undelimitted setting as week start. week_pref += ':' w_start, wn_start = week_pref.split(':') try: week_start = int(w_start) except ValueError: week_start = None else: week_start = week_start > -1 and week_start < 7 and \ week_start or None try: week_num_start = int(wn_start) except ValueError: week_num_start = None else: week_num_start = week_num_start in (1, 4, 7) and \ week_num_start or None # Respect user's locale, if available. try: locale = Locale.parse(str(req.locale)) except (AttributeError, UnknownLocaleError): # Attribute 'req.locale' vailable since Trac 0.12. locale = None if has_babel: if locale: if not locale.territory: # Search first locale, which has the same `language` and # territory in preferred languages. for l in req.languages: l = l.replace('-', '_').lower() if l.startswith(locale.language.lower() + '_'): try: l = Locale.parse(l) if l.territory: locale = l break # first one rules except UnknownLocaleError: pass if not locale.territory and locale.language in LOCALE_ALIASES: locale = Locale.parse(LOCALE_ALIASES[locale.language]) else: # Default fallback. locale = Locale('en', 'US') env.log.debug('Locale setting for wiki calendar: %s' % locale.get_display_name('en')) if not week_start: if week_pref and week_pref.lower().startswith('iso'): week_start = 0 week_num_start = 4 elif has_babel: week_start = locale.first_week_day else: import calendar week_start = calendar.firstweekday() # ISO calendar will remain as default. if not week_num_start: if week_start == 6: week_num_start = 1 else: week_num_start = 4 env.log.debug('Effective settings: first_week_day=%s, ' '1st_week_of_year_rule=%s' % (week_start, week_num_start)) # Find year and month of interest. year = req.args.get('year') # Not clicked on any previous/next button, next look for macro args. if not year and len(args) >= 1 and args[0] != "*": year = args[0] year = year and year.isnumeric() and int(year) or None month = req.args.get('month') # Not clicked on any previous/next button, next look for macro args. if not month and len(args) >= 2 and args[1] != "*": month = args[1] month = month and month.isnumeric() and int(month) or None now = datetime.now(tz) # Force offset from start-of-day to avoid a false 'today' marker, # but use it only on request of different month/year. now.replace(second=1) today = None if (month and month != now.month) or (year and year != now.year): today = now.replace(year=year, month=month, day=1) # Use current month and year, if nothing else has been requested. if not today: today = now.replace(hour=0, minute=0, second=0, microsecond=0) showbuttons = True if len(args) >= 3 or kwargs.has_key('nav'): try: showbuttons = kwargs['nav'] in _TRUE_VALUES except KeyError: showbuttons = args[2] in _TRUE_VALUES wiki_page_format = "%Y-%m-%d" if len(args) >= 4 and args[3] != "*" or kwargs.has_key('wiki'): try: wiki_page_format = str(kwargs['wiki']) except KeyError: wiki_page_format = str(args[3]) # Support relative paths in macro arguments for wiki page links. wiki_page_format = resolve_relative_name(wiki_page_format, formatter.resource.id) list_condense = 0 show_t_open_dates = True wiki_subpages = [] # Read optional check plan. check = [] if kwargs.has_key('check'): check = kwargs['check'].split('.') if name == 'WikiTicketCalendar': if len(args) >= 5 or kwargs.has_key('cdate'): try: show_t_open_dates = kwargs['cdate'] in _TRUE_VALUES except KeyError: show_t_open_dates = args[4] in _TRUE_VALUES # TracQuery support for ticket selection query_args = "id!=0" if len(args) >= 7 or kwargs.has_key('query'): # prefer query arguments provided by kwargs try: query_args = kwargs['query'] except KeyError: query_args = args[6] # compress long ticket lists if len(args) >= 8 or kwargs.has_key('short'): # prefer query arguments provided by kwargs try: list_condense = int(kwargs['short']) except KeyError: list_condense = int(args[7]) # control calendar display width cal_width = "100%;" if len(args) >= 9 or kwargs.has_key('width'): # prefer query arguments provided by kwargs try: cal_width = kwargs['width'] except KeyError: cal_width = args[8] # multiple wiki (sub)pages per day if kwargs.has_key('subpages'): wiki_subpages = kwargs['subpages'].split('|') # Prepare datetime objects for previous/next navigation link creation. prev_year = month_offset(today, -12) prev_quarter = month_offset(today, -3) prev_month = month_offset(today, -1) next_month = month_offset(today, 1) next_quarter = month_offset(today, 3) next_year = month_offset(today, 12) # Find first and last calendar day, probably in last/next month, # using datetime objects exactly at start-of-day here. # Note: Calendar days are numbered 0 (Mo) - 6 (Su). first_day_month = today.replace(day=1, second=0) first_day = first_day_month - timedelta( week_index(first_day_month, week_start)) last_day_month = next_month.replace(day=1) - timedelta(1) if ((last_day_month - first_day).days + 1) % 7 > 0: last_day = last_day_month + timedelta(7 - ( (last_day_month - first_day).days + 1) % 7) else: last_day = last_day_month # Find relevant tickets. if name == 'WikiTicketCalendar': daystr = (uts and '..' or ':').join([ format_datetime(first_day, locale=locale), format_datetime(last_day, locale=locale) ]) provider = WikiCalendarTicketProvider(env) query_args = query_args and query_args + '&' or '' tkt_due = provider.harvest( req, query_args + '='.join([self.tkt_due_field, daystr])) if show_t_open_dates: tkt_new = provider.harvest( req, query_args + '='.join(['created', daystr])) # Finally building the output now. # Begin with caption and optional navigation links. buff = tag.tr() if showbuttons is True: # Create calendar navigation buttons. nx = 'next' pv = 'prev' nav_pv_y = _nav_link(req, '<<', pv, prev_year, locale) nav_pv_q = _nav_link(req, ' «', pv, prev_quarter, locale) nav_pv_m = _nav_link(req, ' <', pv, prev_month, locale) nav_nx_m = _nav_link(req, '> ', nx, next_month, locale) nav_nx_q = _nav_link(req, '» ', nx, next_quarter, locale) nav_nx_y = _nav_link(req, '>>', nx, next_year, locale) # Add buttons for going to previous months and year. buff(nav_pv_y, nav_pv_q, nav_pv_m) # The caption will always be there. if has_babel: heading = tag.td(format_datetime(today, 'MMMM y', locale=locale)) else: heading = tag.td(format_date(today, '%B %Y')) buff = buff(heading(class_='y')) if showbuttons is True: # Add buttons for going to next months and year. buff(nav_nx_m, nav_nx_q, nav_nx_y) buff = tag.caption(tag.table(tag.tbody(buff))) buff = tag.table(buff) if name == 'WikiTicketCalendar': if cal_width.startswith('+') is True: width = ":".join(['min-width', cal_width]) buff(class_='wikitcalendar', style=width) else: buff(class_='wikitcalendar') if name == 'WikiCalendar': buff(class_='wiki-calendar') heading = tag.tr() heading(align='center') if week_pref: # Add an empty cell matching the week number column below. heading(tag.th()) day_names = [(idx, day_name) for idx, day_name in get_day_names( 'abbreviated', 'format', locale).iteritems()] # Read day names after shifting into correct position. for idx, name_ in day_names[week_start:7] + day_names[0:week_start]: col = tag.th(name_) if has_babel: weekend = idx >= locale.weekend_start and \ idx <= locale.weekend_end else: weekend = idx > 4 col(class_=('workday', 'weekend')[weekend], scope='col') heading(col) heading = buff(tag.thead(heading)) # Building main calendar table body buff = tag.tbody() day = first_day while day.date() <= last_day.date(): # Insert a new row for every week. if (day - first_day).days % 7 == 0: line = tag.tr() line(align='right') if week_pref: cell = tag.td( week_num(env, day, week_start, week_num_start)) line(cell(class_='week')) if not (day < first_day_month or day > last_day_month): wiki = format_date(day, wiki_page_format) if day == today: a_class = 'day today' td_class = 'today' else: a_class = 'day' td_class = 'day' if uts: day_ts = to_utimestamp(day) day_ts_eod = day_ts + 86399999999 else: day_ts = to_timestamp(day) day_ts_eod = day_ts + 86399 # Check for milestone(s) on that day. #db = env.get_read_db() #cursor = db.cursor() #cursor.execute(""" # SELECT name # FROM milestone # WHERE due >= %s and due <= %s #""", (day_ts, day_ts_eod)) cursor = self.env.db_query( """ SELECT name FROM milestone WHERE due >= %s and due <= %s """, (day_ts, day_ts_eod)) milestones = tag() for row in cursor: if not a_class.endswith('milestone'): a_class += ' milestone' milestone = to_unicode(row[0]) url = env.href.milestone(milestone) milestone = '* ' + milestone milestones = tag( milestones, tag.div(tag.a(milestone, href=url), class_='milestone')) label = tag.span(day.day) label(class_='day') # Generate wiki page links with name specified in # 'wiki_page_format', and check their existence. if len(wiki_subpages) > 0: pages = tag(label, Markup('<br />')) for page in wiki_subpages: label = tag(' ', page[0]) page = '/'.join([wiki, page]) pages( self._wiki_link(req, args, kwargs, page, label, 'subpage', check)) else: pages = self._wiki_link(req, args, kwargs, wiki, label, a_class, check) cell = tag.td(pages) cell(class_=td_class, valign='top') if name == 'WikiCalendar': line(cell) else: if milestones: cell(milestones) else: cell(tag.br()) match = [] match_od = [] ticket_heap = tag('') ticket_list = tag.div('') ticket_list(align='left', class_='condense') # Get tickets with due date set to day. for t in tkt_due: due = t.get(self.tkt_due_field) if due is None or due in ('', '--'): continue else: if self.tkt_due_format == 'ts': if not isinstance(due, datetime): continue if uts: due_ts = to_utimestamp(due) else: due_ts = to_timestamp(due) if due_ts < day_ts or due_ts > day_ts_eod: continue else: # Beware: Format might even be unicode string, # but str is required by the function. duedate = format_date(day, str(self.tkt_due_format)) if not due == duedate: continue tkt_id = t.get('id') ticket, short = _ticket_links(env, formatter, t) ticket_heap(ticket) if not tkt_id in match: if len(match) == 0: ticket_list(short) else: ticket_list(', ', short) match.append(tkt_id) # Optionally, get tickets created on day too. if show_t_open_dates: ticket_od_list = tag.div('') ticket_od_list(align='left', class_='opendate_condense') for t in tkt_new: if uts: ticket_ts = to_utimestamp(t.get('time')) else: ticket_ts = to_timestamp(t.get('time')) if ticket_ts < day_ts or ticket_ts > day_ts_eod: continue a_class = 'opendate_' tkt_id = t.get('id') ticket, short = _ticket_links( env, formatter, t, a_class) ticket_heap(ticket) if not tkt_id in match: if len(match_od) == 0: ticket_od_list(short) else: ticket_od_list(', ', short) match_od.append(tkt_id) matches = len(match) + len(match_od) if list_condense > 0 and matches >= list_condense: if len(match_od) > 0: if len(match) > 0: ticket_list(', ') ticket_list = tag(ticket_list, ticket_od_list) line(cell(ticket_list)) else: line(cell(ticket_heap)) else: if name == 'WikiCalendar': wiki = format_date(day, wiki_page_format) a_class = 'day adjacent_month' pages = self._wiki_link(req, args, kwargs, wiki, day.day, a_class) cell = tag.td(pages, class_='day adjacent_month') line(cell) else: cell = tag.td('', class_='day adjacent_month') line(cell) # Append completed week rows. if (day - first_day).days % 7 == 6: buff(line) day += timedelta(1) buff = tag.div(heading(buff)) if name == 'WikiTicketCalendar': if cal_width.startswith('+') is True: width = ":".join(['width', cal_width]) buff(class_='wikitcalendar', style=width) else: buff(class_='wikitcalendar') if name == 'WikiCalendar': buff(class_='wiki-calendar') # Add common CSS stylesheet. if self.internal_css and not req.args.get('wikicalendar'): # Put definitions directly into the output. f = open('/'.join([self.htdocs_path, 'wikicalendar.css']), 'Ur') css = tag.style(Markup('<!--\n'), '\n'.join(f.readlines()), Markup('-->\n'))(type="text/css") f.close() # Add hint to prevent multiple inclusions. req.args['wikicalendar'] = True return tag(css, buff) elif not req.args.get('wikicalendar'): add_stylesheet(req, 'wikicalendar/wikicalendar.css') return buff