| [391] | 1 | Index: trac/ticket/api.py |
|---|
| 2 | =================================================================== |
|---|
| 3 | --- trac/ticket/api.py (revision 7385) |
|---|
| 4 | +++ trac/ticket/api.py (working copy) |
|---|
| 5 | @@ -183,6 +183,17 @@ |
|---|
| 6 | valid_states.update(controller.get_all_status()) |
|---|
| 7 | return sorted(valid_states) |
|---|
| 8 | |
|---|
| 9 | + def get_all_ticket_fields(self): |
|---|
| 10 | + """New method until full integration of the new fields.""" |
|---|
| 11 | + fields = self.get_ticket_fields() |
|---|
| 12 | + |
|---|
| 13 | + # append the new ones |
|---|
| 14 | + fields.extend([{'name': 'time', 'type': 'date', 'label': 'Created'}, |
|---|
| 15 | + {'name': 'changetime', 'type': 'date', |
|---|
| 16 | + 'label': 'Modified'}]) |
|---|
| 17 | + |
|---|
| 18 | + return fields |
|---|
| 19 | + |
|---|
| 20 | def get_ticket_fields(self): |
|---|
| 21 | """Returns the list of fields available for tickets.""" |
|---|
| 22 | from trac.ticket import model |
|---|
| 23 | Index: trac/ticket/query.py |
|---|
| 24 | =================================================================== |
|---|
| 25 | --- trac/ticket/query.py (revision 7385) |
|---|
| 26 | +++ trac/ticket/query.py (working copy) |
|---|
| 27 | @@ -43,6 +43,8 @@ |
|---|
| 28 | from trac.wiki.api import IWikiSyntaxProvider, parse_args |
|---|
| 29 | from trac.wiki.macros import WikiMacroBase # TODO: should be moved in .api |
|---|
| 30 | |
|---|
| 31 | +is_op = lambda x: x in ['!', '<', '>'] |
|---|
| 32 | + |
|---|
| 33 | class QuerySyntaxError(Exception): |
|---|
| 34 | """Exception raised when a ticket query cannot be parsed from a string.""" |
|---|
| 35 | |
|---|
| 36 | @@ -98,7 +100,7 @@ |
|---|
| 37 | rows = [] |
|---|
| 38 | if verbose and 'description' not in rows: # 0.10 compatibility |
|---|
| 39 | rows.append('description') |
|---|
| 40 | - self.fields = TicketSystem(self.env).get_ticket_fields() |
|---|
| 41 | + self.fields = TicketSystem(self.env).get_all_ticket_fields() |
|---|
| 42 | field_names = [f['name'] for f in self.fields] |
|---|
| 43 | self.cols = [c for c in cols or [] if c in field_names or |
|---|
| 44 | c in ('id', 'time', 'changetime')] |
|---|
| 45 | @@ -118,6 +120,7 @@ |
|---|
| 46 | self.group = None |
|---|
| 47 | |
|---|
| 48 | def from_string(cls, env, string, **kw): |
|---|
| 49 | + global is_op |
|---|
| 50 | filters = string.split('&') |
|---|
| 51 | kw_strs = ['order', 'group', 'page', 'max'] |
|---|
| 52 | kw_arys = ['rows'] |
|---|
| 53 | @@ -137,8 +140,8 @@ |
|---|
| 54 | if field[-1] in ('~', '^', '$'): |
|---|
| 55 | mode = field[-1] |
|---|
| 56 | field = field[:-1] |
|---|
| 57 | - if field[-1] == '!': |
|---|
| 58 | - neg = '!' |
|---|
| 59 | + if is_op(field[-1]): |
|---|
| 60 | + neg = field[-1] |
|---|
| 61 | field = field[:-1] |
|---|
| 62 | processed_values = [] |
|---|
| 63 | for val in values.split('|'): |
|---|
| 64 | @@ -368,6 +371,7 @@ |
|---|
| 65 | |
|---|
| 66 | def get_sql(self, req=None, cached_ids=None): |
|---|
| 67 | """Return a (sql, params) tuple for the query.""" |
|---|
| 68 | + global is_op |
|---|
| 69 | self.get_columns() |
|---|
| 70 | |
|---|
| 71 | enum_columns = ('resolution', 'priority', 'severity') |
|---|
| 72 | @@ -413,6 +417,19 @@ |
|---|
| 73 | % (col, col, col)) |
|---|
| 74 | |
|---|
| 75 | def get_constraint_sql(name, value, mode, neg): |
|---|
| 76 | + # strip the operator from the value |
|---|
| 77 | + value = value[len(mode) + is_op(neg):] |
|---|
| 78 | + # convert the value for non string fields |
|---|
| 79 | + current_field = {} |
|---|
| 80 | + for field in self.fields : |
|---|
| 81 | + if field['name'] == name : |
|---|
| 82 | + current_field = field |
|---|
| 83 | + break |
|---|
| 84 | + if current_field.has_key('type') \ |
|---|
| 85 | + and current_field['type'] == 'date' : |
|---|
| 86 | + value = int(value) |
|---|
| 87 | + |
|---|
| 88 | + # add the table alias to the field name |
|---|
| 89 | if name not in custom_fields: |
|---|
| 90 | name = 't.' + name |
|---|
| 91 | else: |
|---|
| 92 | @@ -420,8 +437,8 @@ |
|---|
| 93 | value = value[len(mode) + neg:] |
|---|
| 94 | |
|---|
| 95 | if mode == '': |
|---|
| 96 | - return ("COALESCE(%s,'')%s=%%s" % (name, neg and '!' or ''), |
|---|
| 97 | - value) |
|---|
| 98 | + return ("COALESCE(%s,'')%s=%%s" % (name, |
|---|
| 99 | + is_op(neg) and neg or ''), value) |
|---|
| 100 | if not value: |
|---|
| 101 | return None |
|---|
| 102 | db = self.env.get_db_cnx() |
|---|
| 103 | @@ -432,7 +449,7 @@ |
|---|
| 104 | value = value + '%' |
|---|
| 105 | elif mode == '$': |
|---|
| 106 | value = '%' + value |
|---|
| 107 | - return ("COALESCE(%s,'') %s%s" % (name, neg and 'NOT ' or '', |
|---|
| 108 | + return ("COALESCE(%s,'') %s%s" % (name, neg == '!' and 'NOT ' or '', |
|---|
| 109 | db.like()), |
|---|
| 110 | value) |
|---|
| 111 | |
|---|
| 112 | @@ -441,18 +458,22 @@ |
|---|
| 113 | for k, v in self.constraints.items(): |
|---|
| 114 | if req: |
|---|
| 115 | v = [val.replace('$USER', req.authname) for val in v] |
|---|
| 116 | - # Determine the match mode of the constraint (contains, |
|---|
| 117 | - # starts-with, negation, etc.) |
|---|
| 118 | - neg = v[0].startswith('!') |
|---|
| 119 | + # get the first character of the value, determine later if it's an |
|---|
| 120 | + # operation |
|---|
| 121 | + neg = '' |
|---|
| 122 | mode = '' |
|---|
| 123 | - if len(v[0]) > neg and v[0][neg] in ('~', '^', '$'): |
|---|
| 124 | - mode = v[0][neg] |
|---|
| 125 | + if v[0] != '' : |
|---|
| 126 | + neg = v[0][0] |
|---|
| 127 | + mode = '' |
|---|
| 128 | + if len(v[0]) > 1 and is_op(neg) and v[0][is_op(neg)] in ('~', '^', '$'): |
|---|
| 129 | + mode = v[0][is_op(neg)] |
|---|
| 130 | |
|---|
| 131 | # Special case id ranges |
|---|
| 132 | if k == 'id': |
|---|
| 133 | ranges = Ranges() |
|---|
| 134 | for r in v: |
|---|
| 135 | - r = r.replace('!', '') |
|---|
| 136 | + for ch in ['!', '<', '>'] : |
|---|
| 137 | + r = r.replace(ch, '') |
|---|
| 138 | ranges.appendrange(r) |
|---|
| 139 | ids = [] |
|---|
| 140 | id_clauses = [] |
|---|
| 141 | @@ -466,7 +487,7 @@ |
|---|
| 142 | if ids: |
|---|
| 143 | id_clauses.append('id IN (%s)' % (','.join(ids))) |
|---|
| 144 | if id_clauses: |
|---|
| 145 | - clauses.append('%s(%s)' % (neg and 'NOT ' or '', |
|---|
| 146 | + clauses.append('%s(%s)' % (neg == '!' and 'NOT ' or '', |
|---|
| 147 | ' OR '.join(id_clauses))) |
|---|
| 148 | # Special case for exact matches on multiple values |
|---|
| 149 | elif not mode and len(v) > 1: |
|---|
| 150 | @@ -475,16 +496,16 @@ |
|---|
| 151 | else: |
|---|
| 152 | col = k + '.value' |
|---|
| 153 | clauses.append("COALESCE(%s,'') %sIN (%s)" |
|---|
| 154 | - % (col, neg and 'NOT ' or '', |
|---|
| 155 | + % (col, neg == '!' and 'NOT ' or '', |
|---|
| 156 | ','.join(['%s' for val in v]))) |
|---|
| 157 | - args += [val[neg:] for val in v] |
|---|
| 158 | + args += [val[is_op(neg):] for val in v] |
|---|
| 159 | elif len(v) > 1: |
|---|
| 160 | constraint_sql = filter(None, |
|---|
| 161 | [get_constraint_sql(k, val, mode, neg) |
|---|
| 162 | for val in v]) |
|---|
| 163 | if not constraint_sql: |
|---|
| 164 | continue |
|---|
| 165 | - if neg: |
|---|
| 166 | + if is_op(neg): |
|---|
| 167 | clauses.append("(" + " AND ".join( |
|---|
| 168 | [item[0] for item in constraint_sql]) + ")") |
|---|
| 169 | else: |
|---|
| 170 | @@ -562,17 +583,21 @@ |
|---|
| 171 | |
|---|
| 172 | def template_data(self, context, tickets, orig_list=None, orig_time=None, |
|---|
| 173 | req=None): |
|---|
| 174 | + global is_op |
|---|
| 175 | constraints = {} |
|---|
| 176 | for k, v in self.constraints.items(): |
|---|
| 177 | constraint = {'values': [], 'mode': ''} |
|---|
| 178 | for val in v: |
|---|
| 179 | - neg = val.startswith('!') |
|---|
| 180 | - if neg: |
|---|
| 181 | + neg = '' |
|---|
| 182 | + mode = '' |
|---|
| 183 | + if val != '' : |
|---|
| 184 | + neg = val[0] |
|---|
| 185 | + if val != '' and is_op(neg): |
|---|
| 186 | val = val[1:] |
|---|
| 187 | mode = '' |
|---|
| 188 | - if val[:1] in ('~', '^', '$'): |
|---|
| 189 | + if val != '' and val[:1] in ('~', '^', '$'): |
|---|
| 190 | mode, val = val[:1], val[1:] |
|---|
| 191 | - constraint['mode'] = (neg and '!' or '') + mode |
|---|
| 192 | + constraint['mode'] = (is_op(neg) and neg or '') + mode |
|---|
| 193 | constraint['values'].append(val) |
|---|
| 194 | constraints[k] = constraint |
|---|
| 195 | |
|---|
| 196 | @@ -605,7 +630,9 @@ |
|---|
| 197 | {'name': _("begins with"), 'value': "^"}, |
|---|
| 198 | {'name': _("ends with"), 'value': "$"}, |
|---|
| 199 | {'name': _("is"), 'value': ""}, |
|---|
| 200 | - {'name': _("is not"), 'value': "!"} |
|---|
| 201 | + {'name': _("is not"), 'value': "!"}, |
|---|
| 202 | + {'name': _("greater"), 'value': ">"}, |
|---|
| 203 | + {'name': _("lesser"), 'value': "<"} |
|---|
| 204 | ] |
|---|
| 205 | modes['select'] = [ |
|---|
| 206 | {'name': _("is"), 'value': ""}, |
|---|
| 207 | @@ -811,7 +838,7 @@ |
|---|
| 208 | def _get_constraints(self, req): |
|---|
| 209 | constraints = {} |
|---|
| 210 | ticket_fields = [f['name'] for f in |
|---|
| 211 | - TicketSystem(self.env).get_ticket_fields()] |
|---|
| 212 | + TicketSystem(self.env).get_all_ticket_fields()] |
|---|
| 213 | ticket_fields.append('id') |
|---|
| 214 | |
|---|
| 215 | # For clients without JavaScript, we remove constraints here if |
|---|