diff -ur -ubB trac/trac/ticket/api.py trac/trac/ticket/api.py
|
|
|
|
| 694 | 695 | def get_tickets_for_iteration(self, db, iteration_tickets): |
| 695 | 696 | from trac.ticket import Ticket |
| 696 | 697 | tickets = [] |
| 697 | | for ticket_id in iteration_tickets.split(): |
| | 698 | for ticket_id in iteration_tickets: |
| 698 | 699 | ticket = Ticket(self.env, ticket_id, db) |
| 699 | 700 | if ticket.exists: |
| 700 | 701 | tickets.append(ticket) |
| … |
… |
|
| 710 | 711 | |
| 711 | 712 | cursor = db.cursor() |
| 712 | 713 | cursor.execute("SELECT ticket,time,author,field,oldvalue,newvalue " |
| 713 | | "FROM ticket_change WHERE time > %s AND time < %s " |
| 714 | | "ORDER BY ticket", (start_date, end_date)) |
| | 714 | "FROM ticket_change, iteration_ticket " |
| | 715 | "WHERE time > %s AND time < %s " |
| | 716 | "AND ticket_change.ticket = iteration_ticket.ticket_id " |
| | 717 | "AND iteration_ticket.iteration_id = %s " |
| | 718 | "ORDER BY ticket", (start_date, end_date, iteration.id)) |
| 715 | 719 | |
| 716 | 720 | for ticket, time, author, field, oldvalue, newvalue in cursor: |
| 717 | 721 | if not changed_tickets.has_key( ticket ): |
diff -ur -ubB trac/trac/ticket/model.py trac/trac/ticket/model.py
|
|
|
|
| 981 | 981 | self._old_id = iteration_id |
| 982 | 982 | else: |
| 983 | 983 | self.id = self._old_id = self.start_date = self.end_date = None |
| 984 | | self.summary = self.tickets = '' |
| | 984 | self.summary = '' |
| | 985 | self.tickets = [] |
| 985 | 986 | |
| 986 | 987 | def _get_resource(self): |
| 987 | 988 | return Resource('iteration', self.id) ### .version !!! |
| … |
… |
|
| 991 | 992 | if not db: |
| 992 | 993 | db = self.env.get_db_cnx() |
| 993 | 994 | cursor = db.cursor() |
| 994 | | cursor.execute("SELECT id,summary,start_date,end_date,tickets " |
| | 995 | cursor.execute("SELECT id,summary,start_date,end_date " |
| 995 | 996 | "FROM iteration WHERE id=%s", (iteration_id,)) |
| 996 | 997 | row = cursor.fetchone() |
| 997 | 998 | if not row: |
| 998 | 999 | raise ResourceNotFound('Iteration %s does not exist.' % iteration_id, |
| 999 | 1000 | 'Invalid Iteration Id') |
| 1000 | | id, summary, start_date, end_date, tickets = row |
| | 1001 | id, summary, start_date, end_date = row |
| 1001 | 1002 | self.id = id |
| 1002 | 1003 | self.summary = summary or '' |
| 1003 | 1004 | self.start_date = start_date and datetime.fromtimestamp(int(start_date), utc) or None |
| 1004 | 1005 | self.end_date = end_date and datetime.fromtimestamp(int(end_date), utc) or None |
| 1005 | | self.tickets = tickets or '' |
| | 1006 | cursor.execute("SELECT ticket_id FROM iteration_ticket " |
| | 1007 | "WHERE iteration_id=%s ORDER BY ticket_id", (iteration_id,)) |
| | 1008 | self.tickets = [str(row[0]) for row in cursor.fetchall()] |
| 1006 | 1009 | |
| 1007 | 1010 | is_started = property(fget=lambda self: self.start_date and \ |
| 1008 | 1011 | self.start_date.date() < date.today()) |
| … |
… |
|
| 1024 | 1027 | cursor = db.cursor() |
| 1025 | 1028 | self.env.log.info('Deleting iteration %s' % self.id) |
| 1026 | 1029 | cursor.execute("DELETE FROM iteration WHERE id=%s", (self.id,)) |
| | 1030 | cursor.execute("DELETE FROM iteration_ticket WHERE iteration_id=%s" % (self.id,)) |
| 1027 | 1031 | |
| 1028 | 1032 | if handle_ta: |
| 1029 | 1033 | db.commit() |
| … |
… |
|
| 1037 | 1041 | handle_ta = False |
| 1038 | 1042 | |
| 1039 | 1043 | cursor = db.cursor() |
| 1040 | | cursor.execute("INSERT INTO iteration (summary,start_date,end_date,tickets) " |
| 1041 | | "VALUES (%s,%s,%s,%s)", |
| 1042 | | (self.summary, to_timestamp(self.start_date), to_timestamp(self.end_date), |
| 1043 | | self.tickets)) |
| | 1044 | cursor.execute("INSERT INTO iteration (summary,start_date,end_date) " |
| | 1045 | "VALUES (%s,%s,%s)", |
| | 1046 | (self.summary, to_timestamp(self.start_date), to_timestamp(self.end_date))) |
| 1044 | 1047 | iteration_id = db.get_last_id(cursor, 'iteration') |
| | 1048 | for ticket_id in self.tickets: |
| | 1049 | cursor.execute("INSERT INTO iteration_ticket (iteration_id, ticket_id) " |
| | 1050 | "VALUES (%s,%s)", |
| | 1051 | (iteration_id, ticket_id)) |
| 1045 | 1052 | |
| 1046 | 1053 | if handle_ta: |
| 1047 | 1054 | db.commit() |
| … |
… |
|
| 1060 | 1067 | cursor = db.cursor() |
| 1061 | 1068 | self.env.log.info('Updating iteration "%s"' % self.id) |
| 1062 | 1069 | cursor.execute("UPDATE iteration SET summary=%s,start_date=%s," |
| 1063 | | "end_date=%s,tickets=%s WHERE id=%s", |
| | 1070 | "end_date=%s WHERE id=%s", |
| 1064 | 1071 | (self.summary, to_timestamp(self.start_date), to_timestamp(self.end_date), |
| 1065 | | self.tickets, |
| 1066 | 1072 | self.id)) |
| 1067 | 1073 | |
| | 1074 | cursor.execute("DELETE FROM iteration_ticket WHERE iteration_id=%s" % (self.id,)) |
| | 1075 | for ticket_id in self.tickets: |
| | 1076 | cursor.execute("INSERT INTO iteration_ticket (iteration_id, ticket_id) " |
| | 1077 | "VALUES (%s,%s)", |
| | 1078 | (self.id, ticket_id)) |
| | 1079 | |
| 1068 | 1080 | if handle_ta: |
| 1069 | 1081 | db.commit() |
| 1070 | 1082 | |
| … |
… |
|
| 1072 | 1084 | if not db: |
| 1073 | 1085 | db = env.get_db_cnx() |
| 1074 | 1086 | now = to_timestamp( datetime.now(utc) - timedelta(days=1) ) |
| 1075 | | sql = "SELECT id,summary,start_date,end_date,tickets FROM iteration " |
| | 1087 | sql = "SELECT id,summary,start_date,end_date FROM iteration " |
| 1076 | 1088 | if not include_finished: |
| 1077 | 1089 | sql += "WHERE end_date >= %s " % now |
| 1078 | 1090 | cursor = db.cursor() |
| 1079 | 1091 | cursor.execute(sql) |
| | 1092 | iteration_rows = cursor.fetchall() |
| 1080 | 1093 | iterations = [] |
| 1081 | | for iteration_id,summary,start_date,end_date,tickets in cursor: |
| | 1094 | for iteration_id,summary,start_date,end_date in iteration_rows: |
| 1082 | 1095 | iteration = Iteration(env) |
| 1083 | 1096 | iteration.id = iteration._old_id = iteration_id |
| 1084 | 1097 | iteration.summary = summary or '' |
| 1085 | 1098 | iteration.start_date = start_date and datetime.fromtimestamp(int(start_date), utc) or None |
| 1086 | 1099 | iteration.end_date = end_date and datetime.fromtimestamp(int(end_date), utc) or None |
| 1087 | | iteration.tickets = tickets or '' |
| | 1100 | cursor.execute("SELECT ticket_id FROM iteration_ticket " |
| | 1101 | "WHERE iteration_id = %d ORDER BY ticket_id" % (iteration_id,)) |
| | 1102 | iteration.tickets = [str(row[0]) for row in cursor.fetchall()] |
| 1088 | 1103 | iterations.append(iteration) |
| 1089 | 1104 | def iteration_order(m): |
| 1090 | 1105 | return (m.start_date or utcmax, |
diff -ur -ubB trac/trac/ticket/templates/iteration_edit.html trac/trac/ticket/templates/iteration_edit.html
|
|
|
|
| 212 | 212 | <div class="field"> |
| 213 | 213 | <label>List the tickets, by id, associated with this iteration, separated by a space:<br /> |
| 214 | 214 | <input type="text" id="iteration_tickets" name="iteration_tickets" size="60" |
| 215 | | value="${iteration.tickets}" /> |
| | 215 | value="${' '.join(iteration.tickets)}" /> |
| 216 | 216 | <em>For example: 56 3 4 12</em> |
| 217 | 217 | </label> |
| 218 | 218 | </div> |
diff -ur -ubB trac/trac/ticket/query.py trac/trac/ticket/query.py
|
|
|
|
| 404 | 404 | |
| 405 | 405 | sql = [] |
| 406 | 406 | sql.append("SELECT " + ",".join(['t.%s AS %s' % (c, c) for c in cols |
| 407 | | if c not in custom_fields])) |
| | 407 | if c not in custom_fields and c != 'iteration'])) |
| 408 | 408 | sql.append(",priority.value AS priority_value") |
| 409 | 409 | for k in [k for k in cols if k in custom_fields]: |
| 410 | 410 | sql.append(",%s.value AS %s" % (k, k)) |
| … |
… |
|
| 429 | 429 | % (col, col, col)) |
| 430 | 430 | |
| 431 | 431 | def get_constraint_sql(name, value, mode, neg): |
| | 432 | if name == "iteration": |
| | 433 | name = 'iteration_ticket.iteration_id' |
| | 434 | if mode != '': |
| | 435 | raise ValueError("Iteration searching only supports is or is not") |
| | 436 | clause = "(SELECT DISTINCT ticket_id FROM iteration_ticket WHERE %s = %%s)" % (name,) |
| | 437 | if neg: |
| | 438 | clause = "t.id NOT IN %s" % (clause,) |
| | 439 | else: |
| | 440 | clause = "t.id IN %s" % (clause,) |
| | 441 | value = int(value) |
| | 442 | return (clause, value) |
| 432 | 443 | if name not in custom_fields: |
| 433 | 444 | name = 't.' + name |
| 434 | 445 | else: |
| … |
… |
|
| 486 | 497 | ' OR '.join(id_clauses))) |
| 487 | 498 | # Special case for exact matches on multiple values |
| 488 | 499 | elif not mode and len(v) > 1: |
| 489 | | if k not in custom_fields: |
| | 500 | if k == 'iteration': |
| | 501 | col = 'iteration_ticket.iteration_id' |
| | 502 | elif k not in custom_fields: |
| 490 | 503 | col = 't.' + k |
| 491 | 504 | else: |
| 492 | 505 | col = k + '.value' |
| 493 | | clauses.append("COALESCE(%s,'') %sIN (%s)" |
| 494 | | % (col, neg and 'NOT ' or '', |
| 495 | | ','.join(['%s' for val in v]))) |
| 496 | | args += [val[neg:] for val in v] |
| | 506 | if k == 'iteration': |
| | 507 | clause = "%s IN (%s)" % (col, ','.join(['%s' for val in v])) |
| | 508 | clause = "(SELECT DISTINCT ticket_id FROM iteration_ticket WHERE %s)" % (clause,) |
| | 509 | clause = "t.id %sIN %s" % (neg and 'NOT ' or '', clause) |
| | 510 | clauses.append(clause) |
| | 511 | args += [int(val[neg:]) for val in v] |
| | 512 | else: |
| | 513 | clauses.append("COALESCE(%s,'') %sIN (%s)" |
| | 514 | % (col, neg and 'NOT ' or '', |
| | 515 | ','.join(['%s' for val in v]))) |
| | 516 | args += [val[neg:] for val in v] |
| 497 | 517 | elif len(v) > 1: |
| 498 | 518 | constraint_sql = filter(None, |
| 499 | 519 | [get_constraint_sql(k, val, mode, neg) |
| … |
… |
|
| 585 | 605 | # TODO: remove after adding time/changetime to the api.py |
| 586 | 606 | labels['changetime'] = _('Modified') |
| 587 | 607 | labels['time'] = _('Created') |
| | 608 | labels['iteration'] = _('Iteration') |
| 588 | 609 | |
| 589 | 610 | headers = [{ |
| 590 | 611 | 'name': col, 'label': labels.get(col, _('Ticket')), |
| … |
… |
|
| 602 | 623 | field_data.update(field) |
| 603 | 624 | del field_data['name'] |
| 604 | 625 | fields[field['name']] = field_data |
| 605 | | |
| | 626 | fields['iteration'] = {'label': _('Iteration'), 'type': 'text', 'name': 'iteration'} |
| 606 | 627 | modes = {} |
| 607 | 628 | modes['text'] = [ |
| 608 | 629 | {'name': _("contains"), 'value': "~"}, |
| … |
… |
|
| 840 | 861 | ticket_fields = [f['name'] for f in |
| 841 | 862 | TicketSystem(self.env).get_ticket_fields()] |
| 842 | 863 | ticket_fields.append('id') |
| | 864 | ticket_fields.append('iteration') |
| 843 | 865 | |
| 844 | 866 | # For clients without JavaScript, we remove constraints here if |
| 845 | 867 | # requested |