Commits

Azoff committed 95ec5f2

Some initial work on supporting the new format for defining group and order as
a string (+field / -field).

  • Participants
  • Parent commits 7ef192d

Comments (0)

Files changed (2)

File new_group_format.diff

+# HG changeset patch
+# Parent bf61e03008de3ac54ea554fde74960b5bec6d514
+
+diff -r bf61e03008de trac/ticket/query.py
+--- a/trac/ticket/query.py	Sun Dec 18 14:43:29 2011 +0100
++++ b/trac/ticket/query.py	Sun Dec 18 17:23:05 2011 +0100
+@@ -48,6 +48,29 @@
+ from trac.wiki.api import IWikiSyntaxProvider
+ from trac.wiki.macros import WikiMacroBase # TODO: should be moved in .api
+ 
++    
++def field_to_string(d):
++    try:
++        s = '+'
++        if d['desc']:
++            s = '-'
++        s += d['field']
++        return s
++    except (KeyError, TypeError), e:
++        pass
++    return None
++
++def field_from_string(s=''):
++    try:
++        if s[0] == '-':
++            return {'field': s[1:], 'desc': 1}
++        elif s[0] == '+':
++            return {'field': s[1:], 'desc': 0}
++        else:
++            return {'field': s, 'desc': 0}
++    except (IndexError, TypeError), e:
++        pass
++    return None
+ 
+ class QuerySyntaxError(TracError):
+     """Exception raised when a ticket query cannot be parsed from a string."""
+@@ -66,7 +89,7 @@
+ 
+     def __init__(self, env, report=None, constraints=None, cols=None,
+                  order=None, desc=0, group=None, groupdesc=0, verbose=0,
+-                 rows=None, page=None, max=None, format=None):
++                 rows=None, page=None, max=None, format=None, new_group=None):
+         self.env = env
+         self.id = report # if not None, it's the corresponding saved query
+         constraints = constraints or []
+@@ -76,11 +99,17 @@
+         synonyms = TicketSystem(self.env).get_field_synonyms()
+         self.order = synonyms.get(order, order)     # 0.11 compatibility
+         self.desc = desc
+-        self.group = group
+-        self.groupdesc = groupdesc
+         self.format = format
+         self.default_page = 1
+         self.items_per_page = QueryModule(self.env).items_per_page
++        
++        if not new_group:
++            if group or groupdesc: # 0.12 compatibility
++                new_group = field_to_string(dict({'field': group, 'desc': groupdesc}))
++            else:
++                new_group = field_to_string(dict({'field': None, 'desc': 0}))
++        
++        self.new_group = field_from_string(new_group)
+ 
+         # getting page number (default_page if unspecified)
+         if not page:
+@@ -142,18 +171,13 @@
+                 (self.order, self.desc) = ('priority', 0)
+ 
+         # Handle empty string as a request to not group the result
+-        if self.group != '' and self.group not in field_names:
++        if self.new_group is None or (self.new_group.get('field') != '' and self.new_group.get('field') not in field_names):
+             default_group = QueryModule(self.env).default_group
+             if default_group:
+-                if default_group[0] == '-':
+-                    (self.group, self.groupdesc) = (default_group[1:], 1)
+-                elif default_group[0] == '+':
+-                    (self.group, self.groupdesc) = (default_group[1:], 0)
+-                else:
+-                    (self.group, self.groupdesc) = (default_group, 0)
++                self.new_group = field_from_string(default_group)
+             
+-            if self.group not in field_names:
+-                (self.group, self.groupdesc) = (None, 0)
++            if not self.new_group or self.new_group.get('field') not in field_names:
++                self.new_group = None
+ 
+         constraint_cols = {}
+         for clause in self.constraints:
+@@ -169,7 +193,7 @@
+     
+     @classmethod
+     def from_string(cls, env, string, **kw):
+-        kw_strs = ['order', 'group', 'page', 'max', 'format']
++        kw_strs = ['order', 'group', 'new_group', 'page', 'max', 'format']
+         kw_arys = ['rows']
+         kw_bools = ['desc', 'groupdesc', 'verbose']
+         kw_synonyms = {'row': 'rows'}
+@@ -293,8 +317,8 @@
+             cols.append(self.order)
+ 
+         # Make sure to not show the column we group on.
+-        if self.group and self.group in cols:
+-            cols.remove(self.group)
++        if self.new_group and self.new_group.get('name') in cols:
++            cols.remove(self.new_group.get('name'))
+ 
+         return cols
+ 
+@@ -336,7 +360,7 @@
+ 
+             if self.has_more_pages:
+                 max = self.max
+-                if self.group:
++                if self.new_group:
+                     max += 1
+                 sql = sql + " LIMIT %d OFFSET %d" % (max, self.offset)
+                 if (self.page > int(ceil(float(self.num_items) / self.max)) and
+@@ -434,8 +458,7 @@
+         return href.query(constraints,
+                           report=id,
+                           order=order, desc=1 if desc else None,
+-                          group=self.group,
+-                          groupdesc=1 if self.groupdesc else None,
++                          new_group=self.new_group,
+                           col=cols,
+                           row=self.rows,
+                           max=max,
+@@ -461,6 +484,9 @@
+             locale = req.locale
+         self.get_columns()
+         db = self.env.get_read_db()
++        group_field = None
++        if self.new_group:
++            group_field = self.new_group.get('field')
+ 
+         enum_columns = ('resolution', 'priority', 'severity')
+         # Build the list of actual columns to query
+@@ -469,8 +495,8 @@
+             for col in args:
+                 if not col in cols:
+                     cols.append(col)
+-        if self.group and not self.group in cols:
+-            add_cols(self.group)
++        if group_field and group_field not in cols:
++            add_cols(group_field)
+         if self.rows:
+             add_cols('reporter', *self.rows)
+         add_cols('status', 'priority', 'time', 'changetime', self.order)
+@@ -494,14 +520,14 @@
+ 
+         # Join with the enum table for proper sorting
+         for col in [c for c in enum_columns
+-                    if c == self.order or c == self.group or c == 'priority']:
++                    if c == self.order or c == group_field or c == 'priority']:
+             sql.append("\n  LEFT OUTER JOIN enum AS %s ON "
+                        "(%s.type='%s' AND %s.name=%s)"
+                        % (col, col, col, col, col))
+ 
+         # Join with the version/milestone tables for proper sorting
+         for col in [c for c in ['milestone', 'version']
+-                    if c == self.order or c == self.group]:
++                    if c == self.order or c == group_field]:
+             sql.append("\n  LEFT OUTER JOIN %s ON (%s.name=%s)"
+                        % (col, col, col))
+ 
+@@ -653,8 +679,8 @@
+             
+         sql.append("\nORDER BY ")
+         order_cols = [(self.order, self.desc)]
+-        if self.group and self.group != self.order:
+-            order_cols.insert(0, (self.group, self.groupdesc))
++        if self.new_group and self.new_group.get('field') != self.order:
++            order_cols.insert(0, (self.new_group.get('field'), self.new_group.get('desc')))
+ 
+         for name, desc in order_cols:
+             if name in enum_columns:
+@@ -684,7 +710,7 @@
+                            % (desc, desc, col, desc))
+             else:
+                 sql.append("%s%s" % (col, desc))
+-            if name == self.group and not name == self.order:
++            if name == group_field and not name == self.order:
+                 sql.append(",")
+         if self.order != 'id':
+             sql.append(",t.id")  
+@@ -781,8 +807,8 @@
+                     ticket['added'] = True
+                 elif ticket['changetime'] > orig_time:
+                     ticket['changed'] = True
+-            if self.group:
+-                group_key = ticket[self.group]
++            if self.new_group:
++                group_key = ticket[self.new_group.get('field')]
+                 groups.setdefault(group_key, []).append(ticket)
+                 if not groupsequence or group_key not in groupsequence:
+                     groupsequence.append(group_key)
+@@ -941,12 +967,12 @@
+                 constraints = self._get_constraints(arg_list=arg_list)
+             else:
+                 query = Query.from_string(self.env, qstring)
+-                args = {'order': query.order, 'group': query.group,
+-                        'col': query.cols, 'max': query.max}
++                args = {'order': query.order,
++                        'group': query.new_group,
++                        'col': query.cols,
++                        'max': query.max}
+                 if query.desc:
+                     args['desc'] = '1'
+-                if query.groupdesc:
+-                    args['groupdesc'] = '1'
+                 constraints = query.constraints
+ 
+             # Substitute $USER, or ensure no field constraints that depend
+@@ -974,13 +1000,12 @@
+         max = args.get('max')
+         if max is None and format in ('csv', 'tab'):
+             max = 0 # unlimited unless specified explicitly
+-        query = Query(self.env, req.args.get('report'),
+-                      constraints, cols, args.get('order'),
+-                      'desc' in args, args.get('group'),
+-                      'groupdesc' in args, 'verbose' in args,
+-                      rows,
+-                      args.get('page'), 
+-                      max)
++        query = Query(self.env, report=req.args.get('report'),
++                      constraints=constraints, cols=cols,
++                      order=args.get('order'), desc='desc' in args,
++                      verbose='verbose' in args, rows=rows,
++                      page=args.get('page'), max=max,
++                      new_group=field_to_string(args.get('group')))
+ 
+         if 'update' in req.args:
+             # Reset session vars
+@@ -1367,9 +1392,9 @@
+ 
+             def query_href(extra_args, group_value = None):
+                 q = Query.from_string(self.env, query_string)
+-                if q.group:
+-                    extra_args[q.group] = group_value
+-                    q.group = None
++                if q.new_group:
++                    extra_args[q.new_group] = group_value
++                    q.new_group = None
+                 for constraint in q.constraints:
+                     constraint.update(extra_args)
+                 if not q.constraints:
+@@ -1378,7 +1403,7 @@
+             chrome = Chrome(self.env)
+             tickets = apply_ticket_permissions(self.env, req, tickets)
+             stats_provider = RoadmapModule(self.env).stats_provider
+-            by = query.group
++            by = query.new_group
+             if not by:
+                 stat = get_ticket_stats(stats_provider, tickets)
+                 data = {
+@@ -1434,25 +1459,25 @@
+ 
+         def ticket_groups():
+             groups = []
+-            for v, g in groupby(tickets, lambda t: t[query.group]):
++            for v, g in groupby(tickets, lambda t: t[query.new_group.get('field')]):
+                 q = Query.from_string(self.env, query_string)
+                 # produce the hint for the group
+-                q.group = q.groupdesc = None
++                q.new_group = None
+                 order = q.order
+                 q.order = None
+                 title = _("%(groupvalue)s %(groupname)s tickets matching "
+-                          "%(query)s", groupvalue=v, groupname=query.group,
++                          "%(query)s", groupvalue=v, groupname=query.new_group.get('field'),
+                           query=q.to_string())
+                 # produce the href for the query corresponding to the group
+                 for constraint in q.constraints:
+-                    constraint[str(query.group)] = v
++                    constraint[str(query.new_group.get('field'))] = 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:
++            if query.new_group:
+                 groups = [(v, ' ', 
+                            tag.a('#%s' % ','.join([str(t['id']) for t in g]),
+                                  href=href, class_='query', title=title))
+@@ -1462,12 +1487,12 @@
+                 alist = [ticket_anchor(ticket) for ticket in tickets]
+                 return tag.span(alist[0], *[(', ', a) for a in alist[1:]])
+         else:
+-            if query.group:
++            if query.new_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)),
++                                 groupname=query.new_group.get('field'))),
+                       tag.dl([(tag.dt(ticket_anchor(t)),
+                                tag.dd(t['summary'])) for t in g],
+                              class_='wiki compact'))
+diff -r bf61e03008de trac/ticket/templates/query.html
+--- a/trac/ticket/templates/query.html	Sun Dec 18 14:43:29 2011 +0100
++++ b/trac/ticket/templates/query.html	Sun Dec 18 17:23:05 2011 +0100
+@@ -184,12 +184,12 @@
+             <option></option>
+             <py:for each="field_name in field_names" py:with="field = fields[field_name]">
+               <option py:if="field.type in ('select', 'radio') or field_name in ('owner', 'reporter')"
+-                      selected="${field_name == query.group or None}"
++                      selected="${if query.new_group and field_name == query.new_group.get('field') else None}"
+                       value="${field_name}">${field.label}</option>
+             </py:for>
+           </select>
+           <input type="checkbox" name="groupdesc" id="groupdesc"
+-                 checked="${query.groupdesc or None}" />
++                 checked="${query.new_group.get('desc') if query.new_group else None}" />
+           <label for="groupdesc">descending</label>
+         </p>
+ 
+diff -r bf61e03008de trac/ticket/templates/query_results.html
+--- a/trac/ticket/templates/query_results.html	Sun Dec 18 14:43:29 2011 +0100
++++ b/trac/ticket/templates/query_results.html	Sun Dec 18 17:23:05 2011 +0100
+@@ -24,8 +24,8 @@
+   <py:def function="group_heading(groupname, results)">
+     <h2 class="report-result" py:if="groupname is not None"
+         i18n:msg="grouplabel, groupname, count"
+-        py:with="grouplabel = fields[query.group].label;
+-                 groupname = authorinfo(groupname) if query.group in ['owner', 'reporter'] else (groupname or _('None'));
++        py:with="grouplabel = fields[query.new_group.get('field')].label;
++                 groupname = authorinfo(groupname) if query.new_group and query.new_group.get('field') in ['owner', 'reporter'] else (groupname or _('None'));
+                  count = ngettext('%(num)s match', '%(num)s matches', len(results))">
+       ${grouplabel}: ${groupname} <span class="numrows">(${count})</span>
+     </h2>
+diff -r bf61e03008de trac/ticket/tests/query.py
+--- a/trac/ticket/tests/query.py	Sun Dec 18 14:43:29 2011 +0100
++++ b/trac/ticket/tests/query.py	Sun Dec 18 17:23:05 2011 +0100
+@@ -176,7 +176,7 @@
+         query = Query(self.env, group='priority')
+         sql, args = query.get_sql()
+         self.assertEqualSQL(sql,
+-"""SELECT t.id AS id,t.summary AS summary,t.owner AS owner,t.type AS type,t.status AS status,t.milestone AS milestone,t.priority AS priority,t.time AS time,t.changetime AS changetime,priority.value AS priority_value
++"""SELECT t.id AS id,t.summary AS summary,t.owner AS owner,t.type AS type,t.status AS status,t.priority AS priority,t.milestone AS milestone,t.time AS time,t.changetime AS changetime,priority.value AS priority_value
+ FROM ticket AS t
+   LEFT OUTER JOIN enum AS priority ON (priority.type='priority' AND priority.name=priority)
+ ORDER BY COALESCE(priority.value,'')='',%(cast_priority)s,t.id""" % {
+@@ -503,7 +503,7 @@
+         query = Query(self.env, group='summary')
+         sql, args = query.get_sql()
+         self.assertEqualSQL(sql,
+-"""SELECT t.id AS id,t.owner AS owner,t.type AS type,t.status AS status,t.priority AS priority,t.milestone AS milestone,t.summary AS summary,t.time AS time,t.changetime AS changetime,priority.value AS priority_value
++"""SELECT t.id AS id,t.summary AS summary,t.owner AS owner,t.type AS type,t.status AS status,t.priority AS priority,t.milestone AS milestone,t.time AS time,t.changetime AS changetime,priority.value AS priority_value
+ FROM ticket AS t
+   LEFT OUTER JOIN enum AS priority ON (priority.type='priority' AND priority.name=priority)
+ ORDER BY COALESCE(t.summary,'')='',t.summary,COALESCE(priority.value,'')='',CAST(priority.value AS integer),t.id""")
+@@ -527,7 +527,7 @@
+         query = Query(self.env, order='component', group='milestone')
+         sql, args = query.get_sql()
+         self.assertEqualSQL(sql,
+-"""SELECT t.id AS id,t.summary AS summary,t.owner AS owner,t.type AS type,t.status AS status,t.priority AS priority,t.component AS component,t.milestone AS milestone,t.time AS time,t.changetime AS changetime,priority.value AS priority_value
++"""SELECT t.id AS id,t.summary AS summary,t.owner AS owner,t.type AS type,t.status AS status,t.priority AS priority,t.milestone AS milestone,t.component AS component,t.time AS time,t.changetime AS changetime,priority.value AS priority_value
+ FROM ticket AS t
+   LEFT OUTER JOIN enum AS priority ON (priority.type='priority' AND priority.name=priority)
+   LEFT OUTER JOIN milestone ON (milestone.name=milestone)
+@@ -541,7 +541,7 @@
+         query = Query(self.env)
+         sql, args = query.get_sql()
+         self.assertEqualSQL(sql,
+-"""SELECT t.id AS id,t.summary AS summary,t.owner AS owner,t.type AS type,t.priority AS priority,t.milestone AS milestone,t.status AS status,t.time AS time,t.changetime AS changetime,priority.value AS priority_value
++"""SELECT t.id AS id,t.summary AS summary,t.owner AS owner,t.type AS type,t.status AS status,t.priority AS priority,t.milestone AS milestone,t.time AS time,t.changetime AS changetime,priority.value AS priority_value
+ FROM ticket AS t
+   LEFT OUTER JOIN enum AS priority ON (priority.type='priority' AND priority.name=priority)
+ ORDER BY COALESCE(t.status,'')='',t.status,COALESCE(priority.value,'')='',CAST(priority.value AS integer),t.id""")
 default_columns.diff
 default_order.diff
 default_group.diff
+new_group_format.diff