source:
trunk/plugins/dashboardreportsplugin/dashboardreports/patch/ticket-query.diff
@
302
| Revision 302, 11.9 KB checked in by aculapov, 5 years ago (diff) |
|---|
-
trac/ticket/api.py
182 182 for controller in self.action_controllers: 183 183 valid_states.update(controller.get_all_status()) 184 184 return sorted(valid_states) 185 186 def get_all_ticket_fields(self): 187 """New method until full integration of the new fields.""" 188 fields = self.get_ticket_fields() 189 190 # append the new ones 191 fields.extend([{'name': 'time', 'type': 'date', 'label': 'Created'}, 192 {'name': 'changetime', 'type': 'date', 193 'label': 'Modified'}]) 194 195 return fields 185 196 186 197 def get_ticket_fields(self): 187 198 """Returns the list of fields available for tickets.""" -
trac/ticket/query.py
43 43 from trac.wiki.api import IWikiSyntaxProvider, parse_args 44 44 from trac.wiki.macros import WikiMacroBase # TODO: should be moved in .api 45 45 46 is_op = lambda x: x in ['!', '<', '>'] 47 46 48 class QuerySyntaxError(Exception): 47 49 """Exception raised when a ticket query cannot be parsed from a string.""" 48 50 … … 103 105 rows = [] 104 106 if verbose and 'description' not in rows: # 0.10 compatibility 105 107 rows.append('description') 106 self.fields = TicketSystem(self.env).get_ ticket_fields()108 self.fields = TicketSystem(self.env).get_all_ticket_fields() 107 109 field_names = [f['name'] for f in self.fields] 108 110 self.cols = [c for c in cols or [] if c in field_names or 109 111 c in ('id', 'time', 'changetime')] … … 123 125 self.group = None 124 126 125 127 def from_string(cls, env, string, **kw): 128 global is_op 126 129 filters = string.split('&') 127 130 kw_strs = ['order', 'group', 'page', 'max'] 128 131 kw_arys = ['rows'] … … 132 135 for filter_ in filters: 133 136 filter_ = filter_.split('=') 134 137 if len(filter_) != 2: 135 raise QuerySyntaxError( _('Query filter requires field and '136 'constraints separated by a "="'))138 raise QuerySyntaxError('Query filter requires field and ' 139 'constraints separated by a "="') 137 140 field,values = filter_ 138 141 if not field: 139 raise QuerySyntaxError( _('Query filter requires field name'))142 raise QuerySyntaxError('Query filter requires field name') 140 143 # from last char of `field`, get the mode of comparison 141 144 mode, neg = '', '' 142 145 if field[-1] in ('~', '^', '$'): 143 146 mode = field[-1] 144 147 field = field[:-1] 145 if field[-1] == '!':146 neg = '!'148 if is_op(field[-1]): 149 neg = field[-1] 147 150 field = field[:-1] 148 151 processed_values = [] 149 152 for val in values.split('|'): … … 171 174 def get_columns(self): 172 175 if not self.cols: 173 176 self.cols = self.get_default_columns() 174 if not 'id' in self.cols:175 # make sure 'id' is always present (needed for permission checks)176 self.cols.insert(0, 'id')177 177 return self.cols 178 178 179 179 def get_all_textareas(self): … … 187 187 if col in cols: 188 188 cols.remove(col) 189 189 cols.append(col) 190 # TODO: fix after adding time/changetime to the api.py191 cols += ['time', 'changetime']192 190 193 191 # Semi-intelligently remove columns that are restricted to a single 194 192 # value by a query constraint. … … 251 249 return cnt 252 250 253 251 def execute(self, req, db=None, cached_ids=None): 252 if not self.cols: 253 self.get_columns() 254 254 255 if not db: 255 256 db = self.env.get_db_cnx() 256 257 cursor = db.cursor() … … 279 280 fields += [f for f in self.fields if f['name'] == column] or [None] 280 281 results = [] 281 282 282 column_indices = range(len(columns))283 283 for row in cursor: 284 result = {} 285 for i in column_indices: 284 id = int(row[0]) 285 result = {'id': id, 'href': req.href.ticket(id)} 286 for i in range(1, len(columns)): 286 287 name, field, val = columns[i], fields[i], row[i] 287 288 if name == self.group: 288 289 val = val or 'None' 289 290 elif name == 'reporter': 290 291 val = val or 'anonymous' 291 elif name == 'id':292 val = int(val)293 result['href'] = req.href.ticket(val)294 292 elif val is None: 295 293 val = '--' 296 294 elif name in ('changetime', 'time'): … … 373 371 374 372 def get_sql(self, req=None, cached_ids=None): 375 373 """Return a (sql, params) tuple for the query.""" 376 self.get_columns() 374 global is_op 375 if not self.cols: 376 self.get_columns() 377 377 378 378 enum_columns = ('resolution', 'priority', 'severity') 379 379 # Build the list of actual columns to query … … 418 418 % (col, col, col)) 419 419 420 420 def get_constraint_sql(name, value, mode, neg): 421 # strip the operator from the value 422 value = value[len(mode) + is_op(neg):] 423 # convert the value for non string fields 424 current_field = {} 425 for field in self.fields : 426 if field['name'] == name : 427 current_field = field 428 break 429 if current_field.has_key('type') \ 430 and current_field['type'] == 'date' : 431 value = int(value) 432 433 # add the table alias to the field name 421 434 if name not in custom_fields: 422 435 name = 't.' + name 423 436 else: 424 437 name = name + '.value' 425 value = value[len(mode) + neg:]426 438 427 439 if mode == '': 428 return ("COALESCE(%s,'')%s=%%s" % (name, neg and '!' or ''),429 value)440 return ("COALESCE(%s,'')%s=%%s" % (name, 441 is_op(neg) and neg or ''), value) 430 442 if not value: 431 443 return None 432 444 db = self.env.get_db_cnx() … … 437 449 value = value + '%' 438 450 elif mode == '$': 439 451 value = '%' + value 440 return ("COALESCE(%s,'') %s%s" % (name, neg and 'NOT ' or '',452 return ("COALESCE(%s,'') %s%s" % (name, neg == '!' and 'NOT ' or '', 441 453 db.like()), 442 454 value) 443 455 … … 446 458 for k, v in self.constraints.items(): 447 459 if req: 448 460 v = [val.replace('$USER', req.authname) for val in v] 449 # Determine the match mode of the constraint (contains,450 # starts-with, negation, etc.)451 neg = v[0].startswith('!')461 # get the first character of the value, determine later if it's an 462 # operation 463 neg = '' 452 464 mode = '' 453 if len(v[0]) > neg and v[0][neg] in ('~', '^', '$'): 454 mode = v[0][neg] 465 if v[0] != '' : 466 neg = v[0][0] 467 mode = '' 468 if len(v[0]) > 1 and is_op(neg) and v[0][is_op(neg)] in ('~', '^', '$'): 469 mode = v[0][is_op(neg)] 455 470 456 471 # Special case id ranges 457 472 if k == 'id': 458 473 ranges = Ranges() 459 474 for r in v: 460 r = r.replace('!', '') 475 for ch in ['!', '<', '>'] : 476 r = r.replace(ch, '') 461 477 ranges.appendrange(r) 462 478 ids = [] 463 479 id_clauses = [] … … 471 487 if ids: 472 488 id_clauses.append('id IN (%s)' % (','.join(ids))) 473 489 if id_clauses: 474 clauses.append('%s(%s)' % (neg and 'NOT ' or '',490 clauses.append('%s(%s)' % (neg == '!' and 'NOT ' or '', 475 491 ' OR '.join(id_clauses))) 476 492 # Special case for exact matches on multiple values 477 493 elif not mode and len(v) > 1: … … 480 496 else: 481 497 col = k + '.value' 482 498 clauses.append("COALESCE(%s,'') %sIN (%s)" 483 % (col, neg and 'NOT ' or '',499 % (col, neg == '!' and 'NOT ' or '', 484 500 ','.join(['%s' for val in v]))) 485 args += [val[ neg:] for val in v]501 args += [val[is_op(neg):] for val in v] 486 502 elif len(v) > 1: 487 503 constraint_sql = filter(None, 488 504 [get_constraint_sql(k, val, mode, neg) 489 505 for val in v]) 490 506 if not constraint_sql: 491 507 continue 492 if neg:508 if is_op(neg): 493 509 clauses.append("(" + " AND ".join( 494 510 [item[0] for item in constraint_sql]) + ")") 495 511 else: … … 567 583 568 584 def template_data(self, context, tickets, orig_list=None, orig_time=None, 569 585 req=None): 586 global is_op 570 587 constraints = {} 571 588 for k, v in self.constraints.items(): 572 589 constraint = {'values': [], 'mode': ''} 573 590 for val in v: 574 neg = val.startswith('!') 575 if neg: 591 neg = '' 592 mode = '' 593 if val != '' : 594 neg = val[0] 595 if val != '' and is_op(neg): 576 596 val = val[1:] 577 597 mode = '' 578 if val [:1] in ('~', '^', '$'):598 if val != '' and val[:1] in ('~', '^', '$'): 579 599 mode, val = val[:1], val[1:] 580 constraint['mode'] = ( neg and '!'or '') + mode600 constraint['mode'] = (is_op(neg) and neg or '') + mode 581 601 constraint['values'].append(val) 582 602 constraints[k] = constraint 583 603 584 604 cols = self.get_columns() 585 605 labels = dict([(f['name'], f['label']) for f in self.fields]) 586 606 587 # TODO: remove after adding time/changetime to the api.py588 labels['changetime'] = _('Modified')589 labels['time'] = _('Created')590 591 607 headers = [{ 592 608 'name': col, 'label': labels.get(col, _('Ticket')), 593 609 'href': self.get_href(context.href, order=col, … … 610 626 {'name': _("begins with"), 'value': "^"}, 611 627 {'name': _("ends with"), 'value': "$"}, 612 628 {'name': _("is"), 'value': ""}, 613 {'name': _("is not"), 'value': "!"} 629 {'name': _("is not"), 'value': "!"}, 630 {'name': _("greater"), 'value': ">"}, 631 {'name': _("lesser"), 'value': "<"} 614 632 ] 615 633 modes['select'] = [ 616 634 {'name': _("is"), 'value': ""}, … … 813 831 def _get_constraints(self, req): 814 832 constraints = {} 815 833 ticket_fields = [f['name'] for f in 816 TicketSystem(self.env).get_ ticket_fields()]834 TicketSystem(self.env).get_all_ticket_fields()] 817 835 ticket_fields.append('id') 818 836 819 837 # For clients without JavaScript, we remove constraints here if … … 1043 1061 kwargs['format'] = argv[0] 1044 1062 1045 1063 format = kwargs.pop('format', 'list').strip().lower() 1046 if format in ('list', 'compact'): # we need 'status' and 'summary'1047 kwargs['col'] = '|'.join(['status', 'summary',1048 kwargs.get('col', '')])1049 1064 query_string = '&'.join(['%s=%s' % item 1050 1065 for item in kwargs.iteritems()]) 1051 1066
Note: See TracBrowser
for help on using the repository browser.
