def add_template_data(self, req, data, tickets): if isinstance(self.env, ProductEnvironment): super(ProductBatchModifyModule, self).add_template_data(req, data, tickets) return data['batch_modify'] = True data['query_href'] = req.session['query_href'] or req.href.query() tickets_by_product = {} for t in tickets: tickets_by_product.setdefault(t['product'], []).append(t) data['action_controls'] = [] global_env = ProductEnvironment.lookup_global_env(self.env) cache = {} for k, v in tickets_by_product.iteritems(): batch_module = cache.get(k or '') if batch_module is None: env = ProductEnvironment(global_env, k) if k else global_env cache[k] = batch_module = ProductBatchModifyModule(env) data['action_controls'] += batch_module._get_action_controls(req, v) batch_list_modes = [ {'name': _("add"), 'value': "+"}, {'name': _("remove"), 'value': "-"}, {'name': _("add / remove"), 'value': "+-"}, {'name': _("set to"), 'value': "="}, ] add_script_data(req, batch_list_modes=batch_list_modes, batch_list_properties=self._get_list_fields())
def _do_save(self, req, product): """common processing for product save events""" req.perm.require('PRODUCT_VIEW') name = req.args.get('name') prefix = req.args.get('prefix') description = req.args.get('description', '') owner = req.args.get('owner') or req.authname keys = {'prefix': prefix} field_data = {'name': name, 'description': description, 'owner': owner, } warnings = [] def warn(msg): add_warning(req, msg) warnings.append(msg) if product._exists: if name != product.name and Product.select(self.env, where={'name': name}): warn(_('A product with name "%(name)s" already exists, please ' 'choose a different name.', name=name)) elif not name: warn(_('You must provide a name for the product.')) else: req.perm.require('PRODUCT_MODIFY') product.update_field_dict(field_data) product.update(req.authname) add_notice(req, _('Your changes have been saved.')) else: req.perm.require('PRODUCT_CREATE') if not prefix: warn(_('You must provide a prefix for the product.')) elif Product.select(self.env, where={'prefix': prefix}): warn(_('Product "%(id)s" already exists, please choose another ' 'prefix.', id=prefix)) if not name: warn(_('You must provide a name for the product.')) elif Product.select(self.env, where={'name': name}): warn(_('A product with name "%(name)s" already exists, please ' 'choose a different name.', name=name)) if not warnings: prod = Product(self.env) prod.update_field_dict(keys) prod.update_field_dict(field_data) prod.insert() add_notice(req, _('The product "%(id)s" has been added.', id=prefix)) if warnings: product.update_field_dict(keys) product.update_field_dict(field_data) return self._render_editor(req, product) req.redirect(req.href.products(prefix))
def load_manager(self, neighborhood): """Load global environment or product environment given its prefix """ if neighborhood._realm == 'global': # FIXME: ResourceNotFound if neighborhood ID != None ? prefix = GLOBAL_PRODUCT elif neighborhood._realm == 'product': prefix = neighborhood._id else: raise ResourceNotFound(_(u'Unsupported neighborhood %(realm)s', realm=neighborhood._realm)) try: return lookup_product_env(self.env, prefix) except LookupError: raise ResourceNotFound(_(u'Unknown product prefix %(prefix)s', prefix=prefix))
def get_resource_description(self, resource, format='default', context=None, **kwargs): """Describe product resource. """ desc = resource.id if format != 'compact': desc = _('Product %(name)s', name=resource.id) if context: return self._render_link(context, resource.id, desc) else: return desc
def process_request(self, req): """Override for TicketModule process_request""" ticketid = req.args.get('id') productid = req.args.get('productid', '') if not ticketid: # if /newticket is executed in global scope (from QCT), redirect # the request to /products/<first_product_in_DB>/newticket if not productid and \ not isinstance(self.env, ProductEnvironment): default_product = self.env.config.get('ticket', 'default_product') products = Product.select(self.env, {'fields': ['prefix']}) prefixes = [prod.prefix for prod in products] if not default_product or default_product not in prefixes: default_product = products[0].prefix req.redirect(req.href.products(default_product, 'newticket')) return self._process_newticket_request(req) if req.path_info in ('/newticket', '/products'): raise TracError(_("id can't be set for a new ticket request.")) if isinstance(self.env, ProductEnvironment): ticket = Ticket(self.env, ticketid) if productid and ticket['product'] != productid: msg = "Ticket %(id)s in product '%(prod)s' does not exist." raise ResourceNotFound(_(msg, id=ticketid, prod=productid), _("Invalid ticket number")) return self._process_ticket_request(req) # executed in global scope -> assume ticketid=UID, redirect to product with self.env.db_direct_query as db: rows = db("""SELECT id,product FROM ticket WHERE uid=%s""", (ticketid, )) if not rows: msg = "Ticket with uid %(uid)s does not exist." raise ResourceNotFound(_(msg, uid=ticketid), _("Invalid ticket number")) tid, prefix = rows[0] req.redirect(req.href.products(prefix, 'ticket', tid))
def process_request(self, req): """Override for TicketModule process_request""" ticketid = req.args.get('id') productid = req.args.get('productid', '') if not ticketid: # if /newticket is executed in global scope (from QCT), redirect # the request to /products/<first_product_in_DB>/newticket if not productid and \ not isinstance(self.env, ProductEnvironment): default_product = self.env.config.get('ticket', 'default_product') products = Product.select(self.env, {'fields': ['prefix']}) prefixes = [prod.prefix for prod in products] if not default_product or default_product not in prefixes: default_product = products[0].prefix req.redirect(req.href.products(default_product, 'newticket')) return self._process_newticket_request(req) if req.path_info in ('/newticket', '/products'): raise TracError(_("id can't be set for a new ticket request.")) if isinstance(self.env, ProductEnvironment): ticket = Ticket(self.env, ticketid) if productid and ticket['product'] != productid: msg = "Ticket %(id)s in product '%(prod)s' does not exist." raise ResourceNotFound(_(msg, id=ticketid, prod=productid), _("Invalid ticket number")) return self._process_ticket_request(req) # executed in global scope -> assume ticketid=UID, redirect to product with self.env.db_direct_query as db: rows = db("""SELECT id,product FROM ticket WHERE uid=%s""", (ticketid,)) if not rows: msg = "Ticket with uid %(uid)s does not exist." raise ResourceNotFound(_(msg, uid=ticketid), _("Invalid ticket number")) tid, prefix = rows[0] req.redirect(req.href.products(prefix, 'ticket', tid))
def process_request(self, req): """Anticipate permission error to hijack admin panel dispatching process in product context if `TRAC_ADMIN` expectations are not met. """ # TODO: Verify `isinstance(self.env, ProductEnvironment)` once again ? cat_id = req.args.get('cat_id') panel_id = req.args.get('panel_id') if self._check_panel(cat_id, panel_id): with sudo(req): return self.global_process_request(req) else: raise HTTPNotFound(_('Unknown administration panel'))
def validate_ticket(self, req, ticket): # check whether the owner exists in db, add a warning if not if req.args.get('action') == 'reassign' and \ ticket['owner'] != self.env.config.get('ticket', 'default_owner'): owner = self.env.db_direct_query( "SELECT sid FROM session WHERE sid=%s", (ticket['owner'], )) if not owner: # Note: add_warning() is used intead of returning a list of # error tuples, since the latter results in trac rendering # errors (ticket's change.date is not populated) add_warning(req, _('The user "%s" does not exist.') % ticket['owner']) return []
def _do_product_admin(self, prefix, *args): mgr = self.product_admincmd_mgr(prefix) if args and args[0] in self.GLOBAL_COMMANDS: raise AdminCommandError('%s command not supported for products' % (args[0],)) if args and args[0] == 'help': help_args = args[1:] if help_args: doc = mgr.get_command_help(list(help_args)) if doc: TracAdmin.print_doc(doc) else: printerr(_("No documentation found for '%(cmd)s'." " Use 'help' to see the list of commands.", cmd=' '.join(help_args))) cmds = mgr.get_similar_commands(help_args[0]) if cmds: printout('') printout(ngettext("Did you mean this?", "Did you mean one of these?", len(cmds))) for cmd in cmds: printout(' ' + cmd) else: printout(_("trac-admin - The Trac Administration Console " "%(version)s", version=TRAC_VERSION)) env = mgr.env TracAdmin.print_doc(TracAdmin.all_docs(env), short=True) else: try: mgr.execute_command(*args) except AdminCommandError, e: printerr(_("Error: %(msg)s", msg=to_unicode(e))) if e.show_usage: print self._do_product_admin(prefix, 'help', *args[:2]) except:
def ticket_groups(): groups = [] for v, g in groupby(tickets, lambda t: t[query.group]): q = ProductQuery.from_string(env, query_string) # produce the hint for the group q.group = q.groupdesc = None order = q.order q.order = None title = _("%(groupvalue)s %(groupname)s tickets matching " "%(query)s", groupvalue=v, groupname=query.group, query=q.to_string()) # produce the href for the query corresponding to the group for constraint in q.constraints: constraint[str(query.group)] = v q.order = order href = q.get_href(formatter.context) groups.append((v, [t for t in g], href, title)) return groups
def process_request(self, req): """process request handler""" req.perm.require('PRODUCT_VIEW') pid = req.args.get('productid', None) if pid: req.perm('product', pid).require('PRODUCT_VIEW') try: product = Product(self.env, {'prefix': pid}) except ResourceNotFound: product = Product(self.env) path_info = req.args.get('pathinfo') if path_info and path_info != '/': if not product._exists: # bh:ticket:561 - Display product list and warning message if pid: add_warning(req, _("Product %(pid)s not found", pid=pid)) return self._render_list(req) else: raise HTTPNotFound( _('Unable to render product page. Wrong setup?')) if pid: add_link(req, 'up', req.href.products(), _('Products')) action = req.args.get('action', 'view') if req.method == 'POST': if 'cancel' in req.args: req.redirect(req.href.products(product.prefix)) elif action == 'edit': return self._do_save(req, product) elif action == 'delete': raise TracError(_('Product removal is not allowed!')) elif action in ('new', 'edit'): return self._render_editor(req, product) elif action == 'delete': raise TracError(_('Product removal is not allowed!')) if not product._exists: if pid: # bh:ticket:561 - Display product list and warning message add_warning(req, _("Product %(pid)s not found", pid=pid)) return self._render_list(req) data = { 'product': product, 'context': web_context(req, product.resource) } return 'product_view.html', data, None
def process_request(self, req): """process request handler""" req.perm.require('PRODUCT_VIEW') pid = req.args.get('productid', None) if pid: req.perm('product', pid).require('PRODUCT_VIEW') try: product = Product(self.env, {'prefix': pid}) except ResourceNotFound: product = Product(self.env) path_info = req.args.get('pathinfo') if path_info and path_info != '/': if not product._exists: # bh:ticket:561 - Display product list and warning message if pid: add_warning(req, _("Product %(pid)s not found", pid=pid)) return self._render_list(req) else: raise HTTPNotFound( _('Unable to render product page. Wrong setup?')) if pid: add_link(req, 'up', req.href.products(), _('Products')) action = req.args.get('action', 'view') if req.method == 'POST': if 'cancel' in req.args: req.redirect(req.href.products(product.prefix)) elif action == 'edit': return self._do_save(req, product) elif action == 'delete': raise TracError(_('Product removal is not allowed!')) elif action in ('new', 'edit'): return self._render_editor(req, product) elif action == 'delete': raise TracError(_('Product removal is not allowed!')) if not product._exists: if pid: # bh:ticket:561 - Display product list and warning message add_warning(req, _("Product %(pid)s not found", pid=pid)) return self._render_list(req) data = {'product': product, 'context': web_context(req, product.resource)} return 'product_view.html', data, None
def ticket_groups(): groups = [] for v, g in groupby(tickets, lambda t: t[query.group]): q = ProductQuery.from_string(env, query_string) # produce the hint for the group q.group = q.groupdesc = None order = q.order q.order = None title = _( "%(groupvalue)s %(groupname)s tickets matching " "%(query)s", groupvalue=v, groupname=query.group, query=q.to_string()) # produce the href for the query corresponding to the group for constraint in q.constraints: constraint[str(query.group)] = v q.order = order href = q.get_href(formatter.context) groups.append((v, [t for t in g], href, title)) return groups
def _do_product_list(self): if not isinstance(self.env, ProductEnvironment): print_table([(p.prefix, p.owner, p.name) for p in Product.select(self.env)], [_('Prefix'), _('Owner'), _('Name')])
def execute(self, req=None, db=None, cached_ids=None, authname=None, tzinfo=None, href=None, locale=None): """Retrieve the list of matching tickets. :since 1.0: the `db` parameter is no longer needed and will be removed in version 1.1.1 """ with self.env.db_direct_query as db: cursor = db.cursor() self.num_items = 0 sql, args = self.get_sql(req, cached_ids, authname, tzinfo, locale) if sql.startswith('SELECT ') and not sql.startswith('SELECT DISTINCT '): sql = 'SELECT DISTINCT * FROM (' + sql + ') AS subquery' if isinstance(self.env, ProductEnvironment): sql = sql + """ WHERE product='%s'""" % (self.env.product.prefix, ) self.num_items = self._count(sql, args) if self.num_items <= self.max: self.has_more_pages = False if self.has_more_pages: max = self.max if self.group: max += 1 sql = sql + " LIMIT %d OFFSET %d" % (max, self.offset) if self.page > int(ceil(float(self.num_items) / self.max)) and \ self.num_items != 0: raise TracError(_("Page %(page)s is beyond the number of " "pages in the query", page=self.page)) # self.env.log.debug("SQL: " + sql % tuple([repr(a) for a in args])) cursor.execute(sql, args) columns = get_column_names(cursor) fields = [] for column in columns: fields += [f for f in self.fields if f['name'] == column] or \ [None] results = [] product_idx = columns.index('product') column_indices = range(len(columns)) for row in cursor: result = {} for i in column_indices: name, field, val = columns[i], fields[i], row[i] if name == 'reporter': val = val or 'anonymous' elif name == 'id': val = int(val) result['href'] = self._get_ticket_href( row[product_idx], val) elif name in self.time_fields: val = from_utimestamp(val) elif field and field['type'] == 'checkbox': try: val = bool(int(val)) except (TypeError, ValueError): val = False elif val is None: val = '' result[name] = val results.append(result) cursor.close() return results
def get_navigation_items(self, req): if 'REPORT_VIEW' in req.perm: href = ProductModule.get_product_path(self.env, req, 'report') yield ('mainnav', 'tickets', tag.a(_('View Tickets'), href=href))
def get_select_fields(self): """Product select fields""" return [(35, {'name': 'product', 'label': _('Product'), 'cls': Product, 'pk': 'prefix', 'optional': False, 'value': self.default_product})]
def execute(self, req=None, db=None, cached_ids=None, authname=None, tzinfo=None, href=None, locale=None): """Retrieve the list of matching tickets. :since 1.0: the `db` parameter is no longer needed and will be removed in version 1.1.1 """ with self.env.db_direct_query as db: cursor = db.cursor() self.num_items = 0 sql, args = self.get_sql(req, cached_ids, authname, tzinfo, locale) if sql.startswith( 'SELECT ') and not sql.startswith('SELECT DISTINCT '): sql = 'SELECT DISTINCT * FROM (' + sql + ') AS subquery' if isinstance(self.env, ProductEnvironment): sql = sql + """ WHERE product='%s'""" % ( self.env.product.prefix, ) self.num_items = self._count(sql, args) if self.num_items <= self.max: self.has_more_pages = False if self.has_more_pages: max = self.max if self.group: max += 1 sql = sql + " LIMIT %d OFFSET %d" % (max, self.offset) if self.page > int(ceil(float(self.num_items) / self.max)) and \ self.num_items != 0: raise TracError( _( "Page %(page)s is beyond the number of " "pages in the query", page=self.page)) # self.env.log.debug("SQL: " + sql % tuple([repr(a) for a in args])) cursor.execute(sql, args) columns = get_column_names(cursor) fields = [] for column in columns: fields += [f for f in self.fields if f['name'] == column] or \ [None] results = [] product_idx = columns.index('product') column_indices = range(len(columns)) for row in cursor: result = {} for i in column_indices: name, field, val = columns[i], fields[i], row[i] if name == 'reporter': val = val or 'anonymous' elif name == 'id': val = int(val) result['href'] = self._get_ticket_href( row[product_idx], val) elif name in self.time_fields: val = from_utimestamp(val) elif field and field['type'] == 'checkbox': try: val = bool(int(val)) except (TypeError, ValueError): val = False elif val is None: val = '' result[name] = val results.append(result) cursor.close() return results
def expand_macro(self, formatter, name, content): req = formatter.req query_string, kwargs, format = self.parse_args(content) if query_string: query_string += '&' query_string += '&'.join('%s=%s' % item for item in kwargs.iteritems()) env = ProductEnvironment.lookup_global_env(self.env) query = ProductQuery.from_string(env, query_string) if format == 'count': cnt = query.count(req) return tag.span(cnt, title='%d tickets for which %s' % (cnt, query_string), class_='query_count') tickets = query.execute(req) if format == 'table': data = query.template_data(formatter.context, tickets, req=formatter.context.req) add_stylesheet(req, 'common/css/report.css') return Chrome(env).render_template(req, 'query_results.html', data, None, fragment=True) if format == 'progress': from trac.ticket.roadmap import (RoadmapModule, apply_ticket_permissions, get_ticket_stats, grouped_stats_data) add_stylesheet(req, 'common/css/roadmap.css') def query_href(extra_args, group_value=None): q = ProductQuery.from_string(env, query_string) if q.group: extra_args[q.group] = group_value q.group = None for constraint in q.constraints: constraint.update(extra_args) if not q.constraints: q.constraints.append(extra_args) return q.get_href(formatter.context) chrome = Chrome(env) tickets = apply_ticket_permissions(env, req, tickets) stats_provider = RoadmapModule(env).stats_provider by = query.group if not by: stat = get_ticket_stats(stats_provider, tickets) data = { 'stats': stat, 'stats_href': query_href(stat.qry_args), 'interval_hrefs': [ query_href(interval['qry_args']) for interval in stat.intervals ], 'legend': True, } return tag.div(chrome.render_template(req, 'progress_bar.html', data, None, fragment=True), class_='trac-progress') def per_group_stats_data(gstat, group_name): return { 'stats': gstat, 'stats_href': query_href(gstat.qry_args, group_name), 'interval_hrefs': [ query_href(interval['qry_args'], group_name) for interval in gstat.intervals ], 'percent': '%d / %d' % (gstat.done_count, gstat.count), 'legend': False, } groups = grouped_stats_data(env, stats_provider, tickets, by, per_group_stats_data) data = { 'groups': groups, 'grouped_by': by, 'summary': _("Ticket completion status for each %(group)s", group=by), } return tag.div(chrome.render_template(req, 'progress_bar_grouped.html', data, None, fragment=True), class_='trac-groupprogress') # Formats above had their own permission checks, here we need to # do it explicitly: tickets = [ t for t in tickets if 'TICKET_VIEW' in req.perm('ticket', t['id']) ] if not tickets: return tag.span(_("No results"), class_='query_no_results') # Cache resolved href targets hrefcache = {} def ticket_anchor(ticket): try: pvalue = ticket.get('product') or GLOBAL_PRODUCT envhref = hrefcache[pvalue] except KeyError: try: env = lookup_product_env(self.env, prefix=pvalue, name=pvalue) except LookupError: return tag.a('#%s' % ticket['id'], class_='missing product') hrefcache[pvalue] = envhref = \ resolve_product_href(to_env=env, at_env=self.env) return tag.a('#%s' % ticket['id'], class_=ticket['status'], href=envhref.ticket(int(ticket['id'])), title=shorten_line(ticket['summary'])) def ticket_groups(): groups = [] for v, g in groupby(tickets, lambda t: t[query.group]): q = ProductQuery.from_string(env, query_string) # produce the hint for the group q.group = q.groupdesc = None order = q.order q.order = None title = _( "%(groupvalue)s %(groupname)s tickets matching " "%(query)s", groupvalue=v, groupname=query.group, query=q.to_string()) # produce the href for the query corresponding to the group for constraint in q.constraints: constraint[str(query.group)] = v q.order = order href = q.get_href(formatter.context) groups.append((v, [t for t in g], href, title)) return groups if format == 'compact': if query.group: groups = [ (v, ' ', tag.a('#%s' % u',\u200b'.join(str(t['id']) for t in g), href=href, class_='query', title=title)) for v, g, href, title in ticket_groups() ] return tag(groups[0], [(', ', g) for g in groups[1:]]) else: alist = [ticket_anchor(ticket) for ticket in tickets] return tag.span(alist[0], *[(', ', a) for a in alist[1:]]) else: if query.group: return tag.div([ (tag.p( tag_('%(groupvalue)s %(groupname)s tickets:', groupvalue=tag.a(v, href=href, class_='query', title=title), groupname=query.group)), tag.dl([(tag.dt(ticket_anchor(t)), tag.dd(t['summary'])) for t in g], class_='wiki compact')) for v, g, href, title in ticket_groups() ]) else: return tag.div( tag.dl([(tag.dt( ticket_anchor(ticket)), tag.dd(ticket['summary'])) for ticket in tickets], class_='wiki compact'))
def _render_admin_panel(self, req, cat, page, product): req.perm.require('PRODUCT_VIEW') name = req.args.get('name') description = req.args.get('description','') prefix = req.args.get('prefix') if product is None else product owner = req.args.get('owner') keys = {'prefix':prefix} field_data = {'name':name, 'description':description, 'owner':owner, } data = {} # Detail view? if product: prod = Product(self.env, keys) if req.method == 'POST': if req.args.get('save'): req.perm.require('PRODUCT_MODIFY') prod.update_field_dict(field_data) prod.update(req.authname) add_notice(req, _('Your changes have been saved.')) req.redirect(req.href.admin(cat, page)) elif req.args.get('cancel'): req.redirect(req.href.admin(cat, page)) Chrome(self.env).add_wiki_toolbars(req) data = {'view': 'detail', 'product': prod} else: default = self.config.get('ticket', 'default_product') if req.method == 'POST': # Add Product if req.args.get('add'): req.perm.require('PRODUCT_CREATE') if not (prefix and name and owner): if not prefix: add_warning(req, _("You must provide a prefix " "for the product.")) if not name: add_warning(req, _("You must provide a name " "for the product.")) if not owner: add_warning(req, _("You must provide an owner " "for the product.")) data['prefix'] = prefix data['name'] = name data['owner'] = owner else: try: prod = Product(self.env, keys) except ResourceNotFound: prod = Product(self.env) prod.update_field_dict(keys) prod.update_field_dict(field_data) prod.insert() add_notice(req, _('The product "%(id)s" has been ' 'added.', id=prefix)) req.redirect(req.href.admin(cat, page)) else: if prod.prefix is None: raise TracError(_('Invalid product id.')) raise TracError(_("Product %(id)s already exists.", id=prefix)) # Remove product elif req.args.get('remove'): raise TracError(_('Product removal is not allowed!')) # Set default product elif req.args.get('apply'): prefix = req.args.get('default') if prefix and prefix != default: self.log.info("Setting default product to %s", prefix) self.config.set('ticket', 'default_product', prefix) _save_config(self.config, req, self.log) req.redirect(req.href.admin(cat, page)) # Clear default product elif req.args.get('clear'): self.log.info("Clearing default product") self.config.set('ticket', 'default_product', '') _save_config(self.config, req, self.log) req.redirect(req.href.admin(cat, page)) data['view'] = 'list' data['products'] = Product.select(self.env) data['default'] = default if self.config.getbool('ticket', 'restrict_owner'): perm = PermissionSystem(self.env) def valid_owner(username): return perm.get_user_permissions(username).get('TICKET_MODIFY') data['owners'] = [username for username, name, email in self.env.get_known_users() if valid_owner(username)] data['owners'].insert(0, '') data['owners'].sort() else: data['owners'] = None return 'admin_products.html', data
def get_admin_panels(self, req): if 'VERSIONCONTROL_ADMIN' in req.perm: yield ('versioncontrol', _('Version Control'), 'repository', _('Repository Links') if isinstance(self.env, ProductEnvironment) else _('Repositories'))
def _do_save(self, req, product): """common processing for product save events""" req.perm.require('PRODUCT_VIEW') name = req.args.get('name') prefix = req.args.get('prefix') description = req.args.get('description', '') owner = req.args.get('owner') or req.authname keys = {'prefix': prefix} field_data = { 'name': name, 'description': description, 'owner': owner, } warnings = [] def warn(msg): add_warning(req, msg) warnings.append(msg) if product._exists: if name != product.name and Product.select(self.env, where={'name': name}): warn( _( 'A product with name "%(name)s" already exists, please ' 'choose a different name.', name=name)) elif not name: warn(_('You must provide a name for the product.')) else: req.perm.require('PRODUCT_MODIFY') product.update_field_dict(field_data) product.update(req.authname) add_notice(req, _('Your changes have been saved.')) else: req.perm.require('PRODUCT_CREATE') if not prefix: warn(_('You must provide a prefix for the product.')) elif Product.select(self.env, where={'prefix': prefix}): warn( _( 'Product "%(id)s" already exists, please choose another ' 'prefix.', id=prefix)) if not name: warn(_('You must provide a name for the product.')) elif Product.select(self.env, where={'name': name}): warn( _( 'A product with name "%(name)s" already exists, please ' 'choose a different name.', name=name)) if not warnings: prod = Product(self.env) prod.update_field_dict(keys) prod.update_field_dict(field_data) prod.insert() add_notice( req, _('The product "%(id)s" has been added.', id=prefix)) if warnings: product.update_field_dict(keys) product.update_field_dict(field_data) return self._render_editor(req, product) req.redirect(req.href.products(prefix))
def expand_macro(self, formatter, name, content): req = formatter.req query_string, kwargs, format = self.parse_args(content) if query_string: query_string += '&' query_string += '&'.join('%s=%s' % item for item in kwargs.iteritems()) env = ProductEnvironment.lookup_global_env(self.env) query = ProductQuery.from_string(env, query_string) if format == 'count': cnt = query.count(req) return tag.span(cnt, title='%d tickets for which %s' % (cnt, query_string), class_='query_count') tickets = query.execute(req) if format == 'table': data = query.template_data(formatter.context, tickets, req=formatter.context.req) add_stylesheet(req, 'common/css/report.css') return Chrome(env).render_template( req, 'query_results.html', data, None, fragment=True) if format == 'progress': from trac.ticket.roadmap import (RoadmapModule, apply_ticket_permissions, get_ticket_stats, grouped_stats_data) add_stylesheet(req, 'common/css/roadmap.css') def query_href(extra_args, group_value=None): q = ProductQuery.from_string(env, query_string) if q.group: extra_args[q.group] = group_value q.group = None for constraint in q.constraints: constraint.update(extra_args) if not q.constraints: q.constraints.append(extra_args) return q.get_href(formatter.context) chrome = Chrome(env) tickets = apply_ticket_permissions(env, req, tickets) stats_provider = RoadmapModule(env).stats_provider by = query.group if not by: stat = get_ticket_stats(stats_provider, tickets) data = { 'stats': stat, 'stats_href': query_href(stat.qry_args), 'interval_hrefs': [query_href(interval['qry_args']) for interval in stat.intervals], 'legend': True, } return tag.div( chrome.render_template(req, 'progress_bar.html', data, None, fragment=True), class_='trac-progress') def per_group_stats_data(gstat, group_name): return { 'stats': gstat, 'stats_href': query_href(gstat.qry_args, group_name), 'interval_hrefs': [query_href(interval['qry_args'], group_name) for interval in gstat.intervals], 'percent': '%d / %d' % (gstat.done_count, gstat.count), 'legend': False, } groups = grouped_stats_data(env, stats_provider, tickets, by, per_group_stats_data) data = { 'groups': groups, 'grouped_by': by, 'summary': _("Ticket completion status for each %(group)s", group=by), } return tag.div( chrome.render_template(req, 'progress_bar_grouped.html', data, None, fragment=True), class_='trac-groupprogress') # Formats above had their own permission checks, here we need to # do it explicitly: tickets = [t for t in tickets if 'TICKET_VIEW' in req.perm('ticket', t['id'])] if not tickets: return tag.span(_("No results"), class_='query_no_results') # Cache resolved href targets hrefcache = {} def ticket_anchor(ticket): try: pvalue = ticket.get('product') or GLOBAL_PRODUCT envhref = hrefcache[pvalue] except KeyError: try: env = lookup_product_env(self.env, prefix=pvalue, name=pvalue) except LookupError: return tag.a('#%s' % ticket['id'], class_='missing product') hrefcache[pvalue] = envhref = \ resolve_product_href(to_env=env, at_env=self.env) return tag.a('#%s' % ticket['id'], class_=ticket['status'], href=envhref.ticket(int(ticket['id'])), title=shorten_line(ticket['summary'])) def ticket_groups(): groups = [] for v, g in groupby(tickets, lambda t: t[query.group]): q = ProductQuery.from_string(env, query_string) # produce the hint for the group q.group = q.groupdesc = None order = q.order q.order = None title = _("%(groupvalue)s %(groupname)s tickets matching " "%(query)s", groupvalue=v, groupname=query.group, query=q.to_string()) # produce the href for the query corresponding to the group for constraint in q.constraints: constraint[str(query.group)] = v q.order = order href = q.get_href(formatter.context) groups.append((v, [t for t in g], href, title)) return groups if format == 'compact': if query.group: groups = [(v, ' ', tag.a('#%s' % u',\u200b'.join(str(t['id']) for t in g), href=href, class_='query', title=title)) for v, g, href, title in ticket_groups()] return tag(groups[0], [(', ', g) for g in groups[1:]]) else: alist = [ticket_anchor(ticket) for ticket in tickets] return tag.span(alist[0], *[(', ', a) for a in alist[1:]]) else: if query.group: return tag.div( [(tag.p(tag_('%(groupvalue)s %(groupname)s tickets:', groupvalue=tag.a(v, href=href, class_='query', title=title), groupname=query.group)), tag.dl([(tag.dt(ticket_anchor(t)), tag.dd(t['summary'])) for t in g], class_='wiki compact')) for v, g, href, title in ticket_groups()]) else: return tag.div(tag.dl([(tag.dt(ticket_anchor(ticket)), tag.dd(ticket['summary'])) for ticket in tickets], class_='wiki compact'))
def _do_product_remove(self, prefix): raise AdminCommandError(_("Command 'product remove' not supported yet"))