def __init__(self): # TRANSLATOR: Keep macro doc style formatting here, please. self.doc_cloud = _("""Display a tag cloud. Show a tag cloud for all tags on resources matching query. Usage: {{{ [[TagCloud(query,caseless_sort=<bool>,mincount=<n>)]] }}} caseless_sort:: Whether the tag cloud should be sorted case-sensitive. mincount:: Optional integer threshold to hide tags with smaller count. See tags documentation for the query syntax. """) self.doc_listtagged = _("""List tagged resources. Usage: {{{ [[ListTagged(query)]] }}} See tags documentation for the query syntax. """)
class TicketTagProvider(DefaultTagProvider): """[main] Tag provider using ticket fields as sources of tags. Relevant ticket data is initially copied to plugin's own tag db store for more efficient regular access, that matters especially when working with large ticket quantities, kept current using ticket change listener events. Currently does NOT support custom fields. """ implements(ITicketChangeListener) # custom_fields = ListOption('tags', 'custom_ticket_fields', # doc=_("List of custom ticket fields to expose as tags.")) fields = ListOption('tags', 'ticket_fields', 'keywords', doc=_("List of ticket fields to expose as tags.")) ignore_closed_tickets = BoolOption('tags', 'ignore_closed_tickets', True, _("Do not collect tags from closed tickets.")) map = {'view': 'TICKET_VIEW', 'modify': 'TICKET_CHGPROP'} realm = 'ticket' use_cache = False def __init__(self): try: self._fetch_tkt_tags() except self.env.db_exc.IntegrityError, e: self.log.warn('tags for ticket already exist: %s', to_unicode(e)) cfg = self.config cfg_key = 'permission_policies' default_policies = cfg.defaults().get('trac', {}).get(cfg_key) self.fast_permcheck = all(p in default_policies for p in cfg.get('trac', cfg_key))
def _paginate(self, req, results): self.query = req.args.get('q', None) current_page = as_int(req.args.get('listtagged_page'), 1) items_per_page = as_int(req.args.get('listtagged_per_page'), None) if items_per_page is None: items_per_page = self.items_per_page result = Paginator(results, current_page - 1, items_per_page) pagedata = [] shown_pages = result.get_shown_pages(21) for page in shown_pages: page_href = self.get_href(req, items_per_page, page) pagedata.append([page_href, None, str(page), _("Page %(num)d", num=page)]) attributes = ['href', 'class', 'string', 'title'] result.shown_pages = [dict(zip(attributes, p)) for p in pagedata] result.current_page = {'href': None, 'class': 'current', 'string': str(result.page + 1), 'title': None} if result.has_next_page: next_href = self.get_href(req, items_per_page, current_page + 1) add_link(req, 'next', next_href, _('Next Page')) if result.has_previous_page: prev_href = self.get_href(req, items_per_page, current_page - 1) add_link(req, 'prev', prev_href, _('Previous Page')) return result
def _paginate(self, req, results): self.query = req.args.get('q', None) current_page = as_int(req.args.get('listtagged_page'), 1) items_per_page = as_int(req.args.get('listtagged_per_page'), None) if items_per_page is None: items_per_page = self.items_per_page result = Paginator(results, current_page - 1, items_per_page) pagedata = [] shown_pages = result.get_shown_pages(21) for page in shown_pages: page_href = self.get_href(req, items_per_page, page) pagedata.append( [page_href, None, str(page), _("Page %(num)d", num=page)]) attributes = ['href', 'class', 'string', 'title'] result.shown_pages = [dict(zip(attributes, p)) for p in pagedata] result.current_page = { 'href': None, 'class': 'current', 'string': str(result.page + 1), 'title': None } if result.has_next_page: next_href = self.get_href(req, items_per_page, current_page + 1) add_link(req, 'next', next_href, _('Next Page')) if result.has_previous_page: prev_href = self.get_href(req, items_per_page, current_page - 1) add_link(req, 'prev', prev_href, _('Previous Page')) return result
def _wiki_edit(self, req, stream): # TRANSLATOR: Label text for link to '/tags'. link = tag.a(_("view all tags"), href=req.href.tags()) # TRANSLATOR: ... (view all tags) insert = tag(Markup(_("Tag under: (%(tags_link)s)", tags_link=link))) insert( tag.br(), tag.input(id='tags', type='text', name='tags', size='50', value=req.args.get('tags', ' '.join(self._page_tags(req)))) ) insert = tag.div(tag.label(insert), class_='field') return stream | Transformer('//div[@id="changeinfo1"]').append(insert)
def render_tag_changes(old_tags, new_tags): old_tags = split_into_tags(old_tags or '') new_tags = split_into_tags(new_tags or '') added = sorted(new_tags - old_tags) added = added and \ tagn_("%(tags)s added", "%(tags)s added", len(added), tags=builder.em(', '.join(added))) removed = sorted(old_tags - new_tags) removed = removed and \ tagn_("%(tags)s removed", "%(tags)s removed", len(removed), tags=builder.em(', '.join(removed))) # TRANSLATOR: How to delimit added and removed tags. delim = added and removed and _("; ") return builder(builder.strong(_("Tags")), ' ', added, delim, removed)
def render_admin_panel(self, req, cat, page, version): req.perm.require('TAGS_ADMIN') data = {} tag_system = TagSystem(self.env) if req.method == 'POST': # Replace Tag allow_delete = req.args.get('allow_delete') new_tag = req.args.get('tag_new_name').strip() new_tag = not new_tag == u'' and new_tag or None if not (allow_delete or new_tag): data['error'] = _("""Selected current tag(s) and either new tag or delete approval are required""") else: comment = req.args.get('comment', u'') old_tags = req.args.get('tag_name') if old_tags: # Provide list regardless of single or multiple selection. old_tags = isinstance(old_tags, list) and old_tags or \ [old_tags] tag_system.replace_tag(req, old_tags, new_tag, comment, allow_delete) data['selected'] = new_tag all_tags = sorted(tag_system.get_all_tags(req, '-dummy')) data['tags'] = all_tags try: Chrome(self.env).add_textarea_grips(req) except AttributeError: # Element modifiers unavailable before Trac 0.12, skip gracefully. pass return 'admin_tag_change.html', data
def _format_tagged(self, formatter, ns, target, label, fullmatch=None): """Tag and tag query expression link formatter.""" def unquote(text): """Strip all matching pairs of outer quotes from string.""" while re.match(WikiParser.QUOTED_STRING, text): # Remove outer whitespace after stripped quotation too. text = text[1:-1].strip() return text label = label and unquote(label.strip()) or '' target = unquote(target.strip()) tag_res = Resource('tag', target) if 'TAGS_VIEW' in formatter.perm(tag_res): context = formatter.context href = get_resource_url(self.env, tag_res, context.href) tag_sys = TagSystem(self.env) # Tag exists or tags query yields at least one match. if target in tag_sys.get_all_tags(formatter.req) or \ [(res, tags) for res, tags in tag_sys.query(formatter.req, target)]: if label: return tag.a(label, href=href) return render_resource_link(self.env, context, tag_res) else: return tag.a(label+'?', href=href, class_='missing tags', rel='nofollow') else: return tag.span(label, class_='forbidden tags', title=_("no permission to view tags"))
def parse_terminal(self, tokens): """Parse a terminal token. >>> q = Query('') >>> q.parse_terminal(q._tokenise('foo')) ("foo") """ if not tokens: raise InvalidQuery(_("Unexpected end of string")) if tokens[0][0] in (QueryNode.TERM, QueryNode.OR): token = tokens.pop(0)[1] if token[0] in ('"', "'"): token = re.sub(r'\\(.)', r'\1', token[1:-1]) return QueryNode(QueryNode.TERM, value=token) raise InvalidQuery(_("Expected terminal, got '%s'") % tokens[0][1])
def render_admin_panel(self, req, cat, page, version): req.perm.require("TAGS_ADMIN") data = {} tag_system = TagSystem(self.env) if req.method == "POST": # Replace Tag allow_delete = req.args.get("allow_delete") new_tag = req.args.get("tag_new_name").strip() new_tag = not new_tag == u"" and new_tag or None if not (allow_delete or new_tag): data["error"] = _( """Selected current tag(s) and either new tag or delete approval are required""" ) else: comment = req.args.get("comment", u"") old_tags = req.args.get("tag_name") if old_tags: # Provide list regardless of single or multiple selection. old_tags = isinstance(old_tags, list) and old_tags or [old_tags] tag_system.replace_tag(req, old_tags, new_tag, comment, allow_delete) data["selected"] = new_tag all_tags = sorted(tag_system.get_all_tags(req, "-dummy")) data["tags"] = all_tags try: Chrome(self.env).add_textarea_grips(req) except AttributeError: # Element modifiers unavailable before Trac 0.12, skip gracefully. pass return "admin_tag_change.html", data
def post_process_request(self, req, template, data, content_type): if data and req.path_info == '/timeline' and \ 'TAGS_VIEW' in req.perm(Resource('tags')): def realm_handler(_, node, context): return query.match(node, [context.realm]) query_str = req.args.getfirst(self.key) if query_str is None and req.args.get('format') != 'rss': query_str = req.session.get('timeline.%s' % self.key) else: query_str = (query_str or '').strip() # Record tag query expression between visits. req.session['timeline.%s' % self.key] = query_str if data.get('events') and query_str: tag_system = TagSystem(self.env) try: query = Query(query_str, attribute_handlers={'realm': realm_handler}) except InvalidQuery as e: add_warning(req, _("Tag query syntax error: %s" % e)) else: all_realms = tag_system.get_taggable_realms(req.perm) query_realms = set() for m in REALM_RE.finditer(query.as_string()): query_realms.add(m.group(1)) # Don't care about resources from non-taggable realms. realms = not query_realms and all_realms or \ query_realms.intersection(all_realms) events = [] self.log.info("Filtering timeline events by tags '%s'", query_str) for event in data['events']: resource = resource_from_event(event) if resource and resource.realm in realms: # Shortcut view permission checks here. tags = tag_system.get_tags(None, resource) if query(tags, context=resource): events.append(event) # Overwrite with filtered list. data['events'] = events if query_str: # Add current value for next form rendering. data[self.key] = query_str elif self.key in req.session: del req.session[self.key] filter_lst = [] # xpath = '//form[@id="prefs"]/div[1]' xform = JTransformer('form#prefs > div:nth-of-type(1)') insert = builder(Markup('<br />'), tag_("matching tags "), builder.input(type='text', name=self.key, value=data.get(self.key))) filter_lst.append(xform.append(Markup(insert))) add_script_data(req, {'tags_filter': filter_lst}) add_script(req, 'tags/js/tags_jtransform.js') return template, data, content_type
def _paginate(self, req, results, realms): query = req.args.get('q', None) current_page = as_int(req.args.get('listtagged_page'), 1, min=1) items_per_page = as_int(req.args.get('listtagged_per_page'), self.items_per_page) if items_per_page < 1: items_per_page = self.items_per_page try: result = Paginator(results, current_page - 1, items_per_page) except (AssertionError, TracError) as e: # AssertionError raised in Trac < 1.0.10, TracError otherwise self.log.warn("ListTagged macro: %s", e) current_page = 1 result = Paginator(results, current_page - 1, items_per_page) pagedata = [] shown_pages = result.get_shown_pages(21) for page in shown_pages: page_href = self.get_href(req, realms, query, items_per_page, page) pagedata.append( [page_href, None, str(page), _("Page %(num)d", num=page)]) attributes = ['href', 'class', 'string', 'title'] result.shown_pages = [dict(zip(attributes, p)) for p in pagedata] result.current_page = { 'href': None, 'class': 'current', 'string': str(result.page + 1), 'title': None } if result.has_next_page: next_href = self.get_href(req, realms, query, items_per_page, current_page + 1) add_link(req, 'next', next_href, _('Next Page')) if result.has_previous_page: prev_href = self.get_href(req, realms, query, items_per_page, current_page - 1) add_link(req, 'prev', prev_href, _('Previous Page')) return result
def environment_needs_upgrade(self, db=None): schema_ver = self.get_schema_version() if schema_ver == db_default.schema_version: return False elif schema_ver > db_default.schema_version: raise TracError(_("A newer plugin version has been installed " "before, but downgrading is unsupported.")) self.log.info("TracTags database schema version is %d, should be %d", schema_ver, db_default.schema_version) return True
def environment_needs_upgrade(self, db): schema_ver = self.get_schema_version(db) if schema_ver == db_default.schema_version: return False elif schema_ver > db_default.schema_version: raise TracError(_("""A newer plugin version has been installed before, but downgrading is unsupported.""")) self.log.info("TracTags database schema version is %d, should be %d" % (schema_ver, db_default.schema_version)) return True
def render_cloud(self, req, cloud, renderer=None, caseless_sort=False, mincount=None, realms=()): """Render a tag cloud. :cloud: Dictionary of {object: count} representing the cloud. :param renderer: A callable with signature (tag, count, percent) used to render the cloud objects. :param caseless_sort: Boolean, whether tag cloud should be sorted case-sensitive. :param mincount: Integer threshold to hide tags with smaller count. """ min_px = 10.0 max_px = 30.0 scale = 1.0 if renderer is None: def default_renderer(tag, count, percent): href = self.get_href(req, realms, tag=Resource('tag', tag)) return builder.a(tag, rel='tag', title='%i' % count, href=href, style='font-size: %ipx' % int(min_px + percent * (max_px - min_px))) renderer = default_renderer # A LUT from count to n/len(cloud) size_lut = dict([(c, float(i)) for i, c in enumerate(sorted(set([r for r in cloud.values()])))]) if size_lut: scale = 1.0 / len(size_lut) if caseless_sort: # Preserve upper-case precedence within similar tags. items = reversed(sorted(cloud.iteritems(), key=lambda t: t[0].lower(), reverse=True)) else: items = sorted(cloud.iteritems()) ul = li = None for i, (tag, count) in enumerate(items): percent = size_lut[count] * scale if mincount and count < as_int(mincount, 1): # Tag count is too low. continue if ul: # Found new tag for cloud; now add previously prepared one. ul('\n', li) else: # Found first tag for cloud; now create the list. ul = builder.ul(class_='tagcloud') # Prepare current tag entry. li = builder.li(renderer(tag, count, percent)) if li: # All tags checked; mark latest tag as last one (no tailing colon). li(class_='last') ul('\n', li, '\n') return ul and ul or _("No tags found")
def upgrade_environment(self, db): """Each schema version should have its own upgrade module, named upgrades/dbN.py, where 'N' is the version number (int). """ db_mgr = DatabaseManager(self.env) schema_ver = self.get_schema_version(db) cursor = db.cursor() # Is this a new installation? if not schema_ver: # Perform a single-step install: Create plugin schema and # insert default data into the database. connector = db_mgr._get_connector()[0] for table in db_default.schema: for stmt in connector.to_sql(table): cursor.execute(stmt) for table, cols, vals in db_default.get_data(db): cursor.executemany( "INSERT INTO %s (%s) VALUES (%s)" % (table, ','.join(cols), ','.join(['%s' for c in cols])), vals) else: # Perform incremental upgrades. for i in range(schema_ver + 1, db_default.schema_version + 1): name = 'db%i' % i try: upgrades = __import__('tractags.upgrades', globals(), locals(), [name]) script = getattr(upgrades, name) except AttributeError: raise TracError( _( "No upgrade module for version %(num)i " "(%(version)s.py)", num=i, version=name)) script.do_upgrade(self.env, i, cursor) cursor.execute( """ UPDATE system SET value=%s WHERE name='tags_version' """, (db_default.schema_version, )) self.log.info("Upgraded TracTags db schema from version %d to %d" % (schema_ver, db_default.schema_version)) TicketTagProvider(self.env)._fetch_tkt_tags(db) self.log.info("Synchronized ticket attributes to tags table") db.commit()
class TicketTagProvider(DefaultTagProvider): """A tag provider using ticket fields as sources of tags. Relevant ticket data is initially copied to plugin's own tag db store for more efficient regular access, that matters especially when working with large ticket quantities, kept current using ticket change listener events. Currently does NOT support custom fields. """ implements(ITicketChangeListener) # custom_fields = ListOption('tags', 'custom_ticket_fields', # doc=_("List of custom ticket fields to expose as tags.")) fields = ListOption('tags', 'ticket_fields', 'keywords', doc=_("List of ticket fields to expose as tags.")) ignore_closed_tickets = BoolOption( 'tags', 'ignore_closed_tickets', True, _("Do not collect tags from closed tickets.")) map = {'view': 'TICKET_VIEW', 'modify': 'TICKET_CHGPROP'} realm = 'ticket' use_cache = False def __init__(self): db = self.env.get_db_cnx() try: self._fetch_tkt_tags(db) db.commit() except get_db_exc(self.env).IntegrityError, e: self.log.warn('tags for ticket already exist: %s', to_unicode(e)) db.rollback() except:
def render_admin_panel(self, req, cat, page, version): req.perm.require('TAGS_ADMIN') realms = [p.get_taggable_realm() for p in self.tag_providers if (not hasattr(p, 'check_permission') or \ p.check_permission(req.perm, 'view'))] # Check request for enabled filters, or use default. if [r for r in realms if r in req.args] == []: for realm in realms: req.args[realm] = 'on' checked_realms = [r for r in realms if r in req.args] data = dict(checked_realms=checked_realms, tag_realms=list( dict(name=realm, checked=realm in checked_realms) for realm in realms)) tag_system = TagSystem(self.env) if req.method == 'POST': # Replace Tag allow_delete = req.args.get('allow_delete') new_tag = req.args.get('tag_new_name').strip() new_tag = not new_tag == u'' and new_tag or None if not (allow_delete or new_tag): data['error'] = _("Selected current tag(s) and either " "new tag or delete approval are required") else: comment = req.args.get('comment', u'') old_tags = req.args.get('tag_name') if old_tags: # Provide list regardless of single or multiple selection. old_tags = isinstance(old_tags, list) and old_tags or \ [old_tags] tag_system.replace_tag(req, old_tags, new_tag, comment, allow_delete, filter=checked_realms) data['selected'] = new_tag query = ' or '.join(['realm:%s' % r for r in checked_realms]) all_tags = sorted(tag_system.get_all_tags(req, query)) data['tags'] = all_tags try: Chrome(self.env).add_textarea_grips(req) except AttributeError: # Element modifiers unavailable before Trac 0.12, skip gracefully. pass return 'admin_tag_change.html', data
def post_process_request(self, req, template, data, content_type): if data and req.path_info == '/timeline' and \ 'TAGS_VIEW' in req.perm(Resource('tags')): def realm_handler(_, node, context): return query.match(node, [context.realm]) query_str = req.args.get(self.key) if query_str is None and req.args.get('format') != 'rss': query_str = req.session.get('timeline.%s' % self.key) else: query_str = (query_str or '').strip() # Record tag query expression between visits. req.session['timeline.%s' % self.key] = query_str if data.get('events') and query_str: tag_system = TagSystem(self.env) try: query = Query(query_str, attribute_handlers=dict(realm=realm_handler) ) except InvalidQuery, e: add_warning(req, _("Tag query syntax error: %s" % to_unicode(e))) else: all_realms = tag_system.get_taggable_realms(req.perm) query_realms = set() for m in REALM_RE.finditer(query.as_string()): query_realms.add(m.group(1)) # Don't care about resources from non-taggable realms. realms = not query_realms and all_realms or \ query_realms.intersection(all_realms) events = [] self.log.debug("Filtering timeline events by tags '%s'", query_str) for event in data['events']: resource = event['data'][0] if resource.realm in realms: # Shortcut view permission checks here. tags = tag_system.get_tags(None, resource) if query(tags, context=resource): events.append(event) # Overwrite with filtered list. data['events'] = events if query_str: # Add current value for next form rendering. data[self.key] = query_str elif self.key in req.session: del req.session[self.key]
def _post_process_request_history(self, req, data): history = [] page_histories = data.get('history', []) resource = data['resource'] tags_histories = tag_changes(self.env, resource) for page_history in page_histories: while tags_histories and \ tags_histories[0][0] >= page_history['date']: tags_history = tags_histories.pop(0) date = tags_history[0] author = tags_history[1] old_tags = split_into_tags(tags_history[2] or '') new_tags = split_into_tags(tags_history[3] or '') added = sorted(new_tags - old_tags) added = added and \ tagn_("%(tags)s added", "%(tags)s added", len(added), tags=tag.em(', '.join(added))) removed = sorted(old_tags - new_tags) removed = removed and \ tagn_("%(tags)s removed", "%(tags)s removed", len(removed), tags=tag.em(', '.join(removed))) # TRANSLATOR: How to delimit added and removed tags. delim = added and removed and _("; ") comment = tag(tag.strong(_("Tags")), ' ', added, delim, removed) url = req.href(resource.realm, resource.id, version=page_history['version'], tags_version=to_utimestamp(date)) history.append({'version': '*', 'url': url, 'date': date, 'author': author, 'comment': comment, 'ipnr': ''}) history.append(page_history) data.update(dict(history=history, wiki_to_oneliner=self._wiki_to_oneliner))
def post_process_request(self, req, template, data, content_type): if data and req.path_info == '/timeline' and \ 'TAGS_VIEW' in req.perm(Resource('tags')): def realm_handler(_, node, context): return query.match(node, [context.realm]) query_str = req.args.get(self.key) if query_str is None and req.args.get('format') != 'rss': query_str = req.session.get('timeline.%s' % self.key) else: query_str = (query_str or '').strip() # Record tag query expression between visits. req.session['timeline.%s' % self.key] = query_str if data.get('events') and query_str: tag_system = TagSystem(self.env) try: query = Query(query_str, attribute_handlers=dict(realm=realm_handler)) except InvalidQuery, e: add_warning( req, _("Tag query syntax error: %s" % to_unicode(e))) else: all_realms = tag_system.get_taggable_realms(req.perm) query_realms = set() for m in REALM_RE.finditer(query.as_string()): query_realms.add(m.group(1)) # Don't care about resources from non-taggable realms. realms = not query_realms and all_realms or \ query_realms.intersection(all_realms) events = [] self.log.debug("Filtering timeline events by tags '%s'" % query_str) for event in data['events']: resource = event['data'][0] if resource.realm in realms: # Shortcut view permission checks here. tags = tag_system.get_tags(None, resource) if query(tags, context=resource): events.append(event) # Overwrite with filtered list. data['events'] = events if query_str: # Add current value for next form rendering. data[self.key] = query_str elif self.key in req.session: del req.session[self.key]
def render_admin_panel(self, req, cat, page, version): req.perm.require('TAGS_ADMIN') tag_system = TagSystem(self.env) all_realms = tag_system.get_taggable_realms(req.perm) # Check request for enabled filters, or use default. if not [r for r in all_realms if r in req.args]: for realm in all_realms: req.args[realm] = 'on' checked_realms = [r for r in all_realms if r in req.args] data = dict(checked_realms=checked_realms, tag_realms=list( dict(name=realm, checked=realm in checked_realms) for realm in all_realms)) if req.method == 'POST': # Replace Tag allow_delete = req.args.get('allow_delete') new_tag = req.args.get('tag_new_name').strip() new_tag = not new_tag == u'' and new_tag or None if not (allow_delete or new_tag): add_warning( req, _("Selected current tag(s) and either " "new tag or delete approval are required")) else: comment = req.args.get('comment', u'') old_tags = req.args.getlist('tag_name') if old_tags: tag_system.replace_tag(req, old_tags, new_tag, comment, allow_delete, filter=checked_realms) data['selected'] = new_tag req.redirect(req.href.admin('tags', 'replace')) query = ' or '.join('realm:%s' % r for r in checked_realms) all_tags = sorted(tag_system.get_all_tags(req, query)) data['tags'] = all_tags chrome = Chrome(self.env) chrome.add_textarea_grips(req) if hasattr(chrome, 'jenv'): return 'admin_tag_change.html', data, None else: return 'admin_tag_change.html', data
def _wiki_view(self, req, stream): add_stylesheet(req, 'tags/css/tractags.css') tags = self._page_tags(req) if not tags: return stream li = [] for tag_ in tags: resource = Resource('tag', tag_) anchor = render_resource_link(self.env, web_context(req, resource), resource) anchor = anchor(rel='tag') li.append(tag.li(anchor, ' ')) # TRANSLATOR: Header label text for tag list at wiki page bottom. insert = tag.ul(class_='tags')(tag.li(_("Tags"), class_='header'), li) return stream | ( Transformer('//div[contains(@class,"wikipage")]').after(insert))
def _wiki_view(self, req, stream): add_stylesheet(req, 'tags/css/tractags.css') tags = self._page_tags(req) if not tags: return stream li = [] for tag_ in tags: resource = Resource('tag', tag_) anchor = render_resource_link(self.env, Context.from_request(req, resource), resource) anchor = anchor(rel='tag') li.append(tag.li(anchor, ' ')) # TRANSLATOR: Header label text for tag list at wiki page bottom. insert = tag.ul(class_='tags')(tag.li(_("Tags"), class_='header'), li) return stream | (Transformer('//div[contains(@class,"wikipage")]') .after(insert))
def _post_process_request_wiki_edit(self, req): tags = ' '.join(self._page_tags(req)) # TRANSLATOR: Label text for link to '/tags'. link = tag.a(_("view all tags"), href=req.href.tags()) # TRANSLATOR: ... (view all tags) insert = tag( tag_("Tag under: (%(tags_link)s)", tags_link=link), tag.br(), tag.input(id='tags', type='text', name='tags', size='50', value=req.args.get('tags', tags))) insert = tag.div(tag.label(insert), class_='field') filter_lst = [] # xpath = //div[@id="changeinfo1"] xform = JTransformer('div#changeinfo1') filter_lst.append(xform.append(Markup(insert))) self._add_jtransform(req, filter_lst)
def upgrade_environment(self, db): """Each schema version should have its own upgrade module, named upgrades/dbN.py, where 'N' is the version number (int). """ db_mgr = DatabaseManager(self.env) schema_ver = self.get_schema_version(db) cursor = db.cursor() # Is this a new installation? if not schema_ver: # Perform a single-step install: Create plugin schema and # insert default data into the database. connector = db_mgr._get_connector()[0] for table in db_default.schema: for stmt in connector.to_sql(table): cursor.execute(stmt) for table, cols, vals in db_default.get_data(db): cursor.executemany("INSERT INTO %s (%s) VALUES (%s)" % (table, ','.join(cols), ','.join(['%s' for c in cols])), vals) else: # Perform incremental upgrades. for i in range(schema_ver + 1, db_default.schema_version + 1): name = 'db%i' % i try: upgrades = __import__('tractags.upgrades', globals(), locals(), [name]) script = getattr(upgrades, name) except AttributeError: raise TracError(_("No upgrade module for version %(num)i " "(%(version)s.py)", num=i, version=name)) script.do_upgrade(self.env, i, cursor) cursor.execute(""" UPDATE system SET value=%s WHERE name='tags_version' """, (db_default.schema_version,)) self.log.info("Upgraded TracTags db schema from version %d to %d" % (schema_ver, db_default.schema_version)) TicketTagProvider(self.env)._fetch_tkt_tags(db) self.log.info("Synchronized ticket attributes to tags table") db.commit()
def render_admin_panel(self, req, cat, page, version): req.perm.require('TAGS_ADMIN') tag_system = TagSystem(self.env) all_realms = tag_system.get_taggable_realms(req.perm) # Check request for enabled filters, or use default. if not [r for r in all_realms if r in req.args]: for realm in all_realms: req.args[realm] = 'on' checked_realms = [r for r in all_realms if r in req.args] data = dict(checked_realms=checked_realms, tag_realms=list(dict(name=realm, checked=realm in checked_realms) for realm in all_realms)) if req.method == 'POST': # Replace Tag allow_delete = req.args.get('allow_delete') new_tag = req.args.get('tag_new_name').strip() new_tag = not new_tag == u'' and new_tag or None if not (allow_delete or new_tag): data['error'] = _("Selected current tag(s) and either " "new tag or delete approval are required") else: comment = req.args.get('comment', u'') old_tags = req.args.get('tag_name') if old_tags: # Provide list regardless of single or multiple selection. old_tags = isinstance(old_tags, list) and old_tags or \ [old_tags] tag_system.replace_tag(req, old_tags, new_tag, comment, allow_delete, filter=checked_realms) data['selected'] = new_tag query = ' or '.join(['realm:%s' % r for r in checked_realms]) all_tags = sorted(tag_system.get_all_tags(req, query)) data['tags'] = all_tags try: Chrome(self.env).add_textarea_grips(req) except AttributeError: # Element modifiers unavailable before Trac 0.12, skip gracefully. pass return 'admin_tag_change.html', data
def _format_tagged(self, formatter, ns, target, label, fullmatch=None): """Tag and tag query expression link formatter.""" def unquote(text): """Strip all matching pairs of outer quotes from string.""" while re.match(WikiParser.QUOTED_STRING, text): # Remove outer whitespace after stripped quotation too. text = text[1:-1].strip() return text label = label and unquote(label.strip()) or '' target = unquote(target.strip()) query = target # Pop realms from query expression. all_realms = self.tag_system.get_taggable_realms(formatter.perm) realms = query_realms(target, all_realms) if realms: kwargs = dict((realm, 'on') for realm in realms) target = re.sub('(^|\W)realm:\S+(\W|$)', ' ', target).strip() else: kwargs = {} tag_res = Resource('tag', target) if 'TAGS_VIEW' not in formatter.perm(tag_res): return tag.span(label, class_='forbidden tags', title=_("no permission to view tags")) context = formatter.context href = self.tag_system.get_resource_url(tag_res, context.href, kwargs) if all_realms and (target in self.tag_system.get_all_tags( formatter.req) or not iter_is_empty( self.tag_system.query(formatter.req, query))): # At least one tag provider is available and tag exists or # tags query yields at least one match. if label: return tag.a(label, href=href) return render_resource_link(self.env, context, tag_res) return tag.a(label + '?', href=href, class_='missing tags', rel='nofollow')
def parse(self, tokens): left = self.parse_unary(tokens) while tokens: if tokens[0][0] == QueryNode.ENDSUB: return left if tokens[0][0] == QueryNode.OR: tokens.pop(0) return QueryNode(QueryNode.OR, left=left, right=self.parse(tokens)) elif tokens[0][0] == QueryNode.ATTR: tokens.pop(0) if left.type is not QueryNode.TERM: raise InvalidQuery(_("Attribute must be a word")) left = QueryNode(QueryNode.ATTR, left=left, right=self.parse_unary(tokens)) else: return QueryNode(QueryNode.AND, left=left, right=self.parse(tokens)) return left
def _format_tagged(self, formatter, ns, target, label, fullmatch=None): """Tag and tag query expression link formatter.""" def unquote(text): """Strip all matching pairs of outer quotes from string.""" while re.match(WikiParser.QUOTED_STRING, text): # Remove outer whitespace after stripped quotation too. text = text[1:-1].strip() return text label = label and unquote(label.strip()) or '' target = unquote(target.strip()) query = target # Pop realms from query expression. all_realms = self.tag_system.get_taggable_realms(formatter.perm) realms = query_realms(target, all_realms) if realms: kwargs = dict((realm, 'on') for realm in realms) target = re.sub('(^|\W)realm:\S+(\W|$)', ' ', target).strip() else: kwargs = {} tag_res = Resource('tag', target) if 'TAGS_VIEW' not in formatter.perm(tag_res): return tag.span(label, class_='forbidden tags', title=_("no permission to view tags")) context = formatter.context href = self.tag_system.get_resource_url(tag_res, context.href, kwargs) if all_realms and ( target in self.tag_system.get_all_tags(formatter.req) or not iter_is_empty(self.tag_system.query(formatter.req, query))): # At least one tag provider is available and tag exists or # tags query yields at least one match. if label: return tag.a(label, href=href) return render_resource_link(self.env, context, tag_res) return tag.a(label+'?', href=href, class_='missing tags', rel='nofollow')
def _post_process_request_wiki_view(self, req): add_stylesheet(req, 'tags/css/tractags.css') tags = self._page_tags(req) if not tags: return li = [] for t in tags: resource = Resource('tag', t) anchor = render_resource_link(self.env, web_context(req, resource), resource) anchor = anchor(rel='tag') li.append(tag.li(anchor, ' ')) # TRANSLATOR: Header label text for tag list at wiki page bottom. insert = tag.ul(class_='tags')(tag.li(_("Tags"), class_='header'), li) filter_lst = [] # xpath = //div[contains(@class,"wikipage")] xform = JTransformer('div[class*=wikipage]') filter_lst.append(xform.after(Markup(insert))) self._add_jtransform(req, filter_lst) return
def parse_unary(self, tokens): """Parse a unary operator. Currently only NOT. >>> q = Query('') >>> q.parse_unary(q._tokenise('-foo')) (not ("foo") nil) """ if not tokens: return None if tokens[0][0] == QueryNode.BEGINSUB: tokens.pop(0) if tokens[0][0] == QueryNode.ENDSUB: return None node = self.parse(tokens) if not tokens or tokens[0][0] != QueryNode.ENDSUB: raise InvalidQuery(_("Expected ) at end of sub-expression")) tokens.pop(0) return node if tokens[0][0] == QueryNode.NOT: tokens.pop(0) return QueryNode(QueryNode.NOT, left=self.parse_terminal(tokens)) return self.parse_terminal(tokens)
def _invalid_handler(self, name, node, context): raise InvalidQuery(_("Invalid attribute '%s'") % name)
def process_request(self, req): req.perm.require('TAGS_VIEW') match = re.match(r'/tags/?(.*)', req.path_info) tag_id = match.group(1) and match.group(1) or None query = req.args.get('q', '') # Consider only providers, that are permitted for display. tag_system = TagSystem(self.env) all_realms = tag_system.get_taggable_realms(req.perm) if not (tag_id or query) or [r for r in all_realms if r in req.args] == []: for realm in all_realms: if not realm in self.exclude_realms: req.args[realm] = 'on' checked_realms = [r for r in all_realms if r in req.args] if query: # Add permitted realms from query expression. checked_realms.extend(query_realms(query, all_realms)) realm_args = dict(zip([r for r in checked_realms], ['on' for r in checked_realms])) # Switch between single tag and tag query expression mode. if tag_id and not re.match(r"""(['"]?)(\S+)\1$""", tag_id, re.UNICODE): # Convert complex, invalid tag ID's --> query expression. req.redirect(req.href.tags(realm_args, q=tag_id)) elif query: single_page = re.match(r"""(['"]?)(\S+)\1$""", query, re.UNICODE) if single_page: # Convert simple query --> single tag. req.redirect(req.href.tags(single_page.group(2), realm_args)) data = dict(page_title=_("Tags"), checked_realms=checked_realms) # Populate the TagsQuery form field. data['tag_query'] = tag_id and tag_id or query data['tag_realms'] = list(dict(name=realm, checked=realm in checked_realms) for realm in all_realms) if tag_id: data['tag_page'] = WikiPage(self.env, tag_system.wiki_page_prefix + tag_id) if query or tag_id: macro = 'ListTagged' # TRANSLATOR: The meta-nav link label. add_ctxtnav(req, _("Back to Cloud"), req.href.tags()) args = "%s,format=%s,cols=%s" % \ (tag_id and tag_id or query, self.default_format, self.default_cols) data['mincount'] = None else: macro = 'TagCloud' mincount = as_int(req.args.get('mincount', None), self.cloud_mincount) args = mincount and "mincount=%s" % mincount or None data['mincount'] = mincount formatter = Formatter(self.env, Context.from_request(req, Resource('tag'))) self.env.log.debug("%s macro arguments: %s", macro, args and args or '(none)') macros = TagWikiMacros(self.env) try: # Query string without realm throws 'NotImplementedError'. data['tag_body'] = checked_realms and \ macros.expand_macro(formatter, macro, args, realms=checked_realms) \ or '' except InvalidQuery, e: data['tag_query_error'] = to_unicode(e) data['tag_body'] = macros.expand_macro(formatter, 'TagCloud', '')
def expand_macro(self, formatter, name, content, realms=[]): """Evaluate macro call and render results. Calls from web-UI come with pre-processed realm selection. """ env = self.env req = formatter.req tag_system = TagSystem(env) all_realms = tag_system.get_taggable_realms() if not all_realms: # Tag providers are required, no result without at least one. return '' args, kw = parse_args(content) query = args and args[0].strip() or None if not realms: # Check macro arguments for realms (typical wiki macro call). realms = 'realm' in kw and kw['realm'].split('|') or [] if query: # Add realms from query expression. realms.extend(query_realms(query, all_realms)) # Remove redundant realm selection for performance. if set(realms) == all_realms: query = re.sub('(^|\W)realm:\S+(\W|$)', ' ', query).strip() if name == 'TagCloud': # Set implicit 'all tagged realms' as default. if not realms: realms = all_realms if query: all_tags = Counter() # Require per resource query including view permission checks. for resource, tags in tag_system.query(req, query): all_tags.update(tags) else: # Allow faster per tag query, side steps permission checks. all_tags = tag_system.get_all_tags(req, realms=realms) mincount = 'mincount' in kw and kw['mincount'] or None return self.render_cloud(req, all_tags, caseless_sort=self.caseless_sort, mincount=mincount, realms=realms) elif name == 'ListTagged': if content and _OBSOLETE_ARGS_RE.search(content): data = {'warning': 'obsolete_args'} else: data = {'warning': None} context = formatter.context # Use TagsQuery arguments (most likely wiki macro calls). cols = 'cols' in kw and kw['cols'] or self.default_cols format = 'format' in kw and kw['format'] or self.default_format if not realms: # Apply ListTagged defaults to macro call w/o realm. realms = list(set(all_realms) - set(self.exclude_realms)) if not realms: return '' query = '(%s) (%s)' % (query or '', ' or '.join( ['realm:%s' % (r) for r in realms])) query_result = tag_system.query(req, query) excludes = [ exc.strip() for exc in kw.get('exclude', '').split(':') if exc.strip() ] if excludes and query_result: filtered_result = [(resource, tags) for resource, tags in query_result if not any( fnmatchcase(resource.id, exc) for exc in excludes)] query_result = filtered_result if not query_result: return '' def _link(resource): if resource.realm == 'tag': # Keep realm selection in tag links. return builder.a(resource.id, href=self.get_href(req, realms, tag=resource)) elif resource.realm == 'ticket': # Return resource link including ticket status dependend # class to allow for common Trac ticket link style. ticket = Ticket(env, resource.id) return builder.a('#%s' % ticket.id, class_=ticket['status'], href=formatter.href.ticket(ticket.id), title=shorten_line(ticket['summary'])) return render_resource_link(env, context, resource, 'compact') if format == 'table': cols = [ col for col in cols.split('|') if col in self.supported_cols ] # Use available translations from Trac core. try: labels = TicketSystem(env).get_ticket_field_labels() labels['id'] = _('Id') except AttributeError: # Trac 0.11 neither has the attribute nor uses i18n. labels = {'id': 'Id', 'description': 'Description'} labels['realm'] = _('Realm') labels['tags'] = _('Tags') headers = [{'label': labels.get(col)} for col in cols] data.update({'cols': cols, 'headers': headers}) try: results = sorted( query_result, key=lambda r: embedded_numbers(to_unicode(r[0].id))) except (InvalidQuery, InvalidTagRealm), e: return system_message(_("ListTagged macro error"), e) results = self._paginate(req, results, realms) rows = [] for resource, tags in results: desc = tag_system.describe_tagged_resource(req, resource) tags = sorted(tags) wiki_desc = format_to_oneliner(env, context, desc) if tags: rendered_tags = [ _link(Resource('tag', tag)) for tag in tags ] if 'oldlist' == format: resource_link = _link(resource) else: resource_link = builder.a(wiki_desc, href=get_resource_url( env, resource, context.href)) if 'table' == format: cells = [] for col in cols: if col == 'id': cells.append(_link(resource)) # Don't duplicate links to resource in both. elif col == 'description' and 'id' in cols: cells.append(wiki_desc) elif col == 'description': cells.append(resource_link) elif col == 'realm': cells.append(resource.realm) elif col == 'tags': cells.append( builder([(tag, ' ') for tag in rendered_tags])) rows.append({'cells': cells}) continue rows.append({ 'desc': wiki_desc, 'rendered_tags': None, 'resource_link': _link(resource) }) data.update({ 'format': format, 'paginator': results, 'results': rows, 'tags_url': req.href('tags') }) # Work around a bug in trac/templates/layout.html, that causes a # TypeError for the wiki macro call, if we use add_link() alone. add_stylesheet(req, 'common/css/search.css') return Chrome(env).render_template(req, 'listtagged_results.html', data, 'text/html', True)
def expand_macro(self, formatter, name, content): env = self.env req = formatter.req args, kw = parse_args(content) # Use macro arguments (most likely wiki macro calls). realms = 'realm' in kw and kw['realm'].split('|') or [] tag_system = TagSystem(env) all_realms = [p.get_taggable_realm() for p in tag_system.tag_providers] self.all_realms = all_realms self.realms = realms if name == 'TagCloud': args.append(' or '.join(['realm:%s' % r for r in realms])) all_tags = tag_system.get_all_tags(req, ' '.join(args)) mincount = 'mincount' in kw and kw['mincount'] or None return self.render_cloud(req, all_tags, caseless_sort=self.caseless_sort, mincount=mincount) elif name == 'ListTagged': if _OBSOLETE_ARGS_RE.search(content): data = {'warning': 'obsolete_args'} else: data = {'warning': None} context=formatter.context # Use TagsQuery arguments (most likely wiki macro calls). cols = 'cols' in kw and kw['cols'] or self.default_cols format = 'format' in kw and kw['format'] or self.default_format query = args and args[0].strip() or None if query and not realms: # First read query arguments (most likely a web-UI call). for realm in all_realms: if re.search('(^|\W)realm:%s(\W|$)' % (realm), query): realms = realms and realms.append(realm) or [realm] if not realms: # Apply ListTagged defaults to macro call w/o realm. realms = list(set(all_realms)-set(self.exclude_realms)) if not realms: return '' else: self.query = query self.realms = realms query = '(%s) (%s)' % (query or '', ' or '.join(['realm:%s' % (r) for r in realms])) env.log.debug('LISTTAGGED_QUERY: ' + query) query_result = tag_system.query(req, query) if not query_result: return '' def _link(resource): if resource.realm == 'tag': # Keep realm selection in tag links. return builder.a(resource.id, href=self.get_href(req, tag=resource)) elif resource.realm == 'ticket': # Return resource link including ticket status dependend # class to allow for common Trac ticket link style. ticket = Ticket(env, resource.id) return builder.a('#%s' % ticket.id, class_=ticket['status'], href=formatter.href.ticket(ticket.id), title=shorten_line(ticket['summary'])) return render_resource_link(env, context, resource, 'compact') if format == 'table': cols = [col for col in cols.split('|') if col in self.supported_cols] # Use available translations from Trac core. try: labels = TicketSystem(env).get_ticket_field_labels() labels['id'] = _('Id') except AttributeError: # Trac 0.11 neither has the attribute nor uses i18n. labels = {'id': 'Id', 'description': 'Description'} labels['realm'] = _('Realm') labels['tags'] = _('Tags') headers = [{'label': labels.get(col)} for col in cols] data.update({'cols': cols, 'headers': headers}) results = sorted(query_result, key=lambda r: \ embedded_numbers(to_unicode(r[0].id))) results = self._paginate(req, results) rows = [] for resource, tags in results: desc = tag_system.describe_tagged_resource(req, resource) tags = sorted(tags) if tags: rendered_tags = [_link(Resource('tag', tag)) for tag in tags] if 'oldlist' == format: resource_link = _link(resource) else: desc = desc or \ get_resource_description(env, resource, context=context) resource_link = builder.a(desc, href=get_resource_url( env, resource, context.href)) if 'table' == format: cells = [] for col in cols: if col == 'id': cells.append(_link(resource)) # Don't duplicate links to resource in both. elif col == 'description' and 'id' in cols: cells.append(desc) elif col == 'description': cells.append(resource_link) elif col == 'realm': cells.append(resource.realm) elif col == 'tags': cells.append( builder([(tag, ' ') for tag in rendered_tags])) rows.append({'cells': cells}) continue rows.append({'desc': desc, 'rendered_tags': None, 'resource_link': _link(resource)}) data.update({'format': format, 'paginator': results, 'results': rows, 'tags_url': req.href('tags')}) # Work around a bug in trac/templates/layout.html, that causes a # TypeError for the wiki macro call, if we use add_link() alone. add_stylesheet(req, 'common/css/search.css') return Chrome(env).render_template( req, 'listtagged_results.html', data, 'text/html', True)
if items_per_page < 1: items_per_page = self.items_per_page try: result = Paginator(results, current_page - 1, items_per_page) except (AssertionError, TracError), e: # AssertionError raised in Trac < 1.0.10, TracError otherwise self.log.warn("ListTagged macro: %s", e) current_page = 1 result = Paginator(results, current_page - 1, items_per_page) pagedata = [] shown_pages = result.get_shown_pages(21) for page in shown_pages: page_href = self.get_href(req, realms, query, items_per_page, page) pagedata.append([page_href, None, str(page), _("Page %(num)d", num=page)]) attributes = ['href', 'class', 'string', 'title'] result.shown_pages = [dict(zip(attributes, p)) for p in pagedata] result.current_page = {'href': None, 'class': 'current', 'string': str(result.page + 1), 'title': None} if result.has_next_page: next_href = self.get_href(req, realms, query, items_per_page, current_page + 1) add_link(req, 'next', next_href, _('Next Page')) if result.has_previous_page: prev_href = self.get_href(req, realms, query, items_per_page, current_page - 1)
def get_admin_panels(self, req): if 'TAGS_ADMIN' in req.perm: yield ('tags', _('Tag System'), 'replace', _('Replace'))
def expand_macro(self, formatter, name, content): env = self.env req = formatter.req args, kw = parse_args(content) # Use macro arguments (most likely wiki macro calls). realms = 'realm' in kw and kw['realm'].split('|') or [] tag_system = TagSystem(env) all_realms = [p.get_taggable_realm() for p in tag_system.tag_providers] self.all_realms = all_realms self.realms = realms if name == 'TagCloud': args.append(' or '.join(['realm:%s' % r for r in realms])) all_tags = tag_system.get_all_tags(req, ' '.join(args)) mincount = 'mincount' in kw and kw['mincount'] or None return self.render_cloud(req, all_tags, caseless_sort=self.caseless_sort, mincount=mincount) elif name == 'ListTagged': if _OBSOLETE_ARGS_RE.search(content): data = {'warning': 'obsolete_args'} else: data = {'warning': None} context = formatter.context # Use TagsQuery arguments (most likely wiki macro calls). cols = 'cols' in kw and kw['cols'] or self.default_cols format = 'format' in kw and kw['format'] or self.default_format query = args and args[0].strip() or None if query and not realms: # First read query arguments (most likely a web-UI call). for realm in all_realms: if re.search('(^|\W)realm:%s(\W|$)' % (realm), query): realms = realms and realms.append(realm) or [realm] if not realms: # Apply ListTagged defaults to macro call w/o realm. realms = list(set(all_realms) - set(self.exclude_realms)) if not realms: return '' else: self.query = query self.realms = realms query = '(%s) (%s)' % (query or '', ' or '.join( ['realm:%s' % (r) for r in realms])) env.log.debug('LISTTAGGED_QUERY: ' + query) query_result = tag_system.query(req, query) if not query_result: return '' def _link(resource): if resource.realm == 'tag': # Keep realm selection in tag links. return builder.a(resource.id, href=self.get_href(req, tag=resource)) elif resource.realm == 'ticket': # Return resource link including ticket status dependend # class to allow for common Trac ticket link style. ticket = Ticket(env, resource.id) return builder.a('#%s' % ticket.id, class_=ticket['status'], href=formatter.href.ticket(ticket.id), title=shorten_line(ticket['summary'])) return render_resource_link(env, context, resource, 'compact') if format == 'table': cols = [ col for col in cols.split('|') if col in self.supported_cols ] # Use available translations from Trac core. try: labels = TicketSystem(env).get_ticket_field_labels() labels['id'] = _('Id') except AttributeError: # Trac 0.11 neither has the attribute nor uses i18n. labels = {'id': 'Id', 'description': 'Description'} labels['realm'] = _('Realm') labels['tags'] = _('Tags') headers = [{'label': labels.get(col)} for col in cols] data.update({'cols': cols, 'headers': headers}) results = sorted(query_result, key=lambda r: \ embedded_numbers(to_unicode(r[0].id))) results = self._paginate(req, results) rows = [] for resource, tags in results: desc = tag_system.describe_tagged_resource(req, resource) tags = sorted(tags) if tags: rendered_tags = [ _link(Resource('tag', tag)) for tag in tags ] if 'oldlist' == format: resource_link = _link(resource) else: desc = desc or \ get_resource_description(env, resource, context=context) resource_link = builder.a(desc, href=get_resource_url( env, resource, context.href)) if 'table' == format: cells = [] for col in cols: if col == 'id': cells.append(_link(resource)) # Don't duplicate links to resource in both. elif col == 'description' and 'id' in cols: cells.append(desc) elif col == 'description': cells.append(resource_link) elif col == 'realm': cells.append(resource.realm) elif col == 'tags': cells.append( builder([(tag, ' ') for tag in rendered_tags])) rows.append({'cells': cells}) continue rows.append({ 'desc': desc, 'rendered_tags': None, 'resource_link': _link(resource) }) data.update({ 'format': format, 'paginator': results, 'results': rows, 'tags_url': req.href('tags') }) # Work around a bug in trac/templates/layout.html, that causes a # TypeError for the wiki macro call, if we use add_link() alone. add_stylesheet(req, 'common/css/search.css') return Chrome(env).render_template(req, 'listtagged_results.html', data, 'text/html', True)
def get_admin_panels(self, req): if 'TAGS_ADMIN' in req.perm: yield 'tags', _('Tag System'), 'replace', _('Replace')
def get_timeline_filters(self, req): if 'TAGS_VIEW' in req.perm('tags'): yield ('tags', _("Tag changes"))
def process_request(self, req): req.perm.require('TAGS_VIEW') match = re.match(r'/tags/?(.*)', req.path_info) tag_id = match.group(1) and match.group(1) or None query = req.args.get('q', '') # Consider only providers, that are permitted for display. tag_system = TagSystem(self.env) all_realms = tag_system.get_taggable_realms(req.perm) if not (tag_id or query) or [r for r in all_realms if r in req.args ] == []: for realm in all_realms: if not realm in self.exclude_realms: req.args[realm] = 'on' checked_realms = [r for r in all_realms if r in req.args] if query: # Add permitted realms from query expression. checked_realms.extend(query_realms(query, all_realms)) realm_args = dict( zip([r for r in checked_realms], ['on' for r in checked_realms])) # Switch between single tag and tag query expression mode. if tag_id and not re.match(r"""(['"]?)(\S+)\1$""", tag_id, re.UNICODE): # Convert complex, invalid tag ID's --> query expression. req.redirect(req.href.tags(realm_args, q=tag_id)) elif query: single_page = re.match(r"""(['"]?)(\S+)\1$""", query, re.UNICODE) if single_page: # Convert simple query --> single tag. req.redirect(req.href.tags(single_page.group(2), realm_args)) data = dict(page_title=_("Tags"), checked_realms=checked_realms) # Populate the TagsQuery form field. data['tag_query'] = tag_id and tag_id or query data['tag_realms'] = list( dict(name=realm, checked=realm in checked_realms) for realm in all_realms) if tag_id: data['tag_page'] = WikiPage(self.env, tag_system.wiki_page_prefix + tag_id) if query or tag_id: macro = 'ListTagged' # TRANSLATOR: The meta-nav link label. add_ctxtnav(req, _("Back to Cloud"), req.href.tags()) args = "%s,format=%s,cols=%s" % \ (tag_id and tag_id or query, self.default_format, self.default_cols) data['mincount'] = None else: macro = 'TagCloud' mincount = as_int(req.args.get('mincount', None), self.cloud_mincount) args = mincount and "mincount=%s" % mincount or None data['mincount'] = mincount formatter = Formatter(self.env, Context.from_request(req, Resource('tag'))) self.env.log.debug("%s macro arguments: %s" % (macro, args and args or '(none)')) macros = TagWikiMacros(self.env) try: # Query string without realm throws 'NotImplementedError'. data['tag_body'] = checked_realms and \ macros.expand_macro(formatter, macro, args, realms=checked_realms) \ or '' except InvalidQuery, e: data['tag_query_error'] = to_unicode(e) data['tag_body'] = macros.expand_macro(formatter, 'TagCloud', '')
def expand_macro(self, formatter, name, content, realms=[]): """Evaluate macro call and render results. Calls from web-UI come with pre-processed realm selection. """ env = self.env req = formatter.req tag_system = TagSystem(env) all_realms = tag_system.get_taggable_realms() if not all_realms: # Tag providers are required, no result without at least one. return '' args, kw = parse_args(content) query = args and args[0].strip() or None if not realms: # Check macro arguments for realms (typical wiki macro call). realms = 'realm' in kw and kw['realm'].split('|') or [] if query: # Add realms from query expression. realms.extend(query_realms(query, all_realms)) # Remove redundant realm selection for performance. if set(realms) == all_realms: query = re.sub('(^|\W)realm:\S+(\W|$)', ' ', query).strip() if name == 'TagCloud': # Set implicit 'all tagged realms' as default. if not realms: realms = all_realms if query: all_tags = Counter() # Require per resource query including view permission checks. for resource, tags in tag_system.query(req, query): all_tags.update(tags) else: # Allow faster per tag query, side steps permission checks. all_tags = tag_system.get_all_tags(req, realms=realms) mincount = 'mincount' in kw and kw['mincount'] or None return self.render_cloud(req, all_tags, caseless_sort=self.caseless_sort, mincount=mincount, realms=realms) elif name == 'ListTagged': if content and _OBSOLETE_ARGS_RE.search(content): data = {'warning': 'obsolete_args'} else: data = {'warning': None} context = formatter.context # Use TagsQuery arguments (most likely wiki macro calls). cols = 'cols' in kw and kw['cols'] or self.default_cols format = 'format' in kw and kw['format'] or self.default_format if not realms: # Apply ListTagged defaults to macro call w/o realm. realms = list(set(all_realms)-set(self.exclude_realms)) if not realms: return '' query = '(%s) (%s)' % (query or '', ' or '.join(['realm:%s' % (r) for r in realms])) query_result = tag_system.query(req, query) excludes = [exc.strip() for exc in kw.get('exclude', '' ).split(':') if exc.strip()] if excludes and query_result: filtered_result = [(resource, tags) for resource, tags in query_result if not any(fnmatchcase(resource.id, exc) for exc in excludes)] query_result = filtered_result if not query_result: return '' def _link(resource): if resource.realm == 'tag': # Keep realm selection in tag links. return builder.a(resource.id, href=self.get_href(req, realms, tag=resource)) elif resource.realm == 'ticket': # Return resource link including ticket status dependend # class to allow for common Trac ticket link style. ticket = Ticket(env, resource.id) return builder.a('#%s' % ticket.id, class_=ticket['status'], href=formatter.href.ticket(ticket.id), title=shorten_line(ticket['summary'])) return render_resource_link(env, context, resource, 'compact') if format == 'table': cols = [col for col in cols.split('|') if col in self.supported_cols] # Use available translations from Trac core. try: labels = TicketSystem(env).get_ticket_field_labels() labels['id'] = _('Id') except AttributeError: # Trac 0.11 neither has the attribute nor uses i18n. labels = {'id': 'Id', 'description': 'Description'} labels['realm'] = _('Realm') labels['tags'] = _('Tags') headers = [{'label': labels.get(col)} for col in cols] data.update({'cols': cols, 'headers': headers}) results = sorted(query_result, key=lambda r: \ embedded_numbers(to_unicode(r[0].id))) results = self._paginate(req, results, realms) rows = [] for resource, tags in results: desc = tag_system.describe_tagged_resource(req, resource) tags = sorted(tags) wiki_desc = format_to_oneliner(env, context, desc) if tags: rendered_tags = [_link(Resource('tag', tag)) for tag in tags] if 'oldlist' == format: resource_link = _link(resource) else: resource_link = builder.a(wiki_desc, href=get_resource_url( env, resource, context.href)) if 'table' == format: cells = [] for col in cols: if col == 'id': cells.append(_link(resource)) # Don't duplicate links to resource in both. elif col == 'description' and 'id' in cols: cells.append(wiki_desc) elif col == 'description': cells.append(resource_link) elif col == 'realm': cells.append(resource.realm) elif col == 'tags': cells.append( builder([(tag, ' ') for tag in rendered_tags])) rows.append({'cells': cells}) continue rows.append({'desc': wiki_desc, 'rendered_tags': None, 'resource_link': _link(resource)}) data.update({'format': format, 'paginator': results, 'results': rows, 'tags_url': req.href('tags')}) # Work around a bug in trac/templates/layout.html, that causes a # TypeError for the wiki macro call, if we use add_link() alone. add_stylesheet(req, 'common/css/search.css') return Chrome(env).render_template( req, 'listtagged_results.html', data, 'text/html', True)
def process_request(self, req): req.perm.require("TAGS_VIEW") match = re.match(r"/tags/?(.*)", req.path_info) tag_id = match.group(1) and match.group(1) or None query = req.args.get("q", "") realms = [ p.get_taggable_realm() for p in self.tag_providers if (not hasattr(p, "check_permission") or p.check_permission(req.perm, "view")) ] if not (tag_id or query) or [r for r in realms if r in req.args] == []: for realm in realms: if not realm in self.exclude_realms: req.args[realm] = "on" checked_realms = [r for r in realms if r in req.args] realm_args = dict(zip([r for r in checked_realms], ["on" for r in checked_realms])) if tag_id and not re.match(r"""(['"]?)(\S+)\1$""", tag_id, re.UNICODE): # Convert complex, invalid tag ID's to query expression. req.redirect(req.href.tags(realm_args, q=tag_id)) elif query: single_page = re.match(r"""(['"]?)(\S+)\1$""", query, re.UNICODE) if single_page: # Convert simple query for single tag ID. req.redirect(req.href.tags(single_page.group(2), realm_args)) data = dict(page_title=_("Tags"), checked_realms=checked_realms) # Populate the TagsQuery form field. data["tag_query"] = tag_id and tag_id or query data["tag_realms"] = list(dict(name=realm, checked=realm in checked_realms) for realm in realms) if tag_id: page_name = tag_id page = WikiPage(self.env, page_name) data["tag_page"] = page macros = TagWikiMacros(self.env) if query or tag_id: # TRANSLATOR: The meta-nav link label. add_ctxtnav(req, _("Back to Cloud"), req.href.tags()) macro = "ListTagged" args = "%s,format=%s,cols=%s,realm=%s" % ( tag_id and tag_id or query, self.default_format, self.default_cols, "|".join(checked_realms), ) data["mincount"] = None else: macro = "TagCloud" mincount = as_int(req.args.get("mincount", None), self.cloud_mincount) args = "mincount=%s,realm=%s" % (mincount, "|".join(checked_realms)) data["mincount"] = mincount formatter = Formatter(self.env, Context.from_request(req, Resource("tag"))) self.env.log.debug("Tag macro arguments: %s", args) try: # Query string without realm throws 'NotImplementedError'. data["tag_body"] = len(checked_realms) > 0 and macros.expand_macro(formatter, macro, args) or "" except InvalidQuery, e: data["tag_query_error"] = to_unicode(e) data["tag_body"] = macros.expand_macro(formatter, "TagCloud", "")
def get_admin_panels(self, req): if "TAGS_ADMIN" in req.perm: yield ("tags", _("Tag System"), "replace", _("Replace"))