Ticket #70: iteration_ticket_and_description-45-70-115.patch
| File iteration_ticket_and_description-45-70-115.patch, 22.2 KB (added by ja11sop, 3 years ago) |
|---|
-
patch/trac/ticket/api.py
609 609 return ticket_stages 610 610 611 611 612 def get_ticket_iterations(self, ticket_id, db=None): 613 if not db: 614 db = self.env.get_db_cnx() 615 cursor = db.cursor() 616 cursor.execute("SELECT iteration_id FROM iteration_ticket " 617 "WHERE ticket_id=%s ORDER BY iteration_id", (ticket_id,)) 618 iteration_ids = [str(row[0]) for row in cursor.fetchall()] 619 return iteration_ids 620 621 612 622 def get_ticket_info(self, tickets, milestone_sizing_stats, end_date): 613 tickets_with_stages = {} 623 tickets_with_stages = {} 614 624 ticket_ids = [t['id'] for t in tickets] 615 625 616 626 stages = self.get_completion_stage_details_for_ticket_group(ticket_ids, milestone_sizing_stats, end_date) … … 623 633 last_modified = None 624 634 for ticket in tickets: 625 635 ordered_stages = self.get_ticket_completion_stages(ticket) 636 iterations = self.get_ticket_iterations(ticket.id) 626 637 component = ticket[ 'component' ] and ticket[ 'component' ] or '' 627 tickets_with_stages.setdefault( component, {} )[ticket.id] = { 'ticket':ticket, 'ordered_stages':ordered_stages }638 tickets_with_stages.setdefault( component, {} )[ticket.id] = { 'ticket':ticket, 'ordered_stages':ordered_stages, 'iterations':iterations } 628 639 total_tickets += 1 629 640 630 641 id = ticket.id … … 694 705 def get_tickets_for_iteration(self, db, iteration_tickets): 695 706 from trac.ticket import Ticket 696 707 tickets = [] 697 for ticket_id in iteration_tickets .split():708 for ticket_id in iteration_tickets: 698 709 ticket = Ticket(self.env, ticket_id, db) 699 710 if ticket.exists: 700 711 tickets.append(ticket) … … 710 721 711 722 cursor = db.cursor() 712 723 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)) 724 "FROM ticket_change, iteration_ticket " 725 "WHERE time > %s AND time < %s " 726 "AND ticket_change.ticket = iteration_ticket.ticket_id " 727 "AND iteration_ticket.iteration_id = %s " 728 "ORDER BY ticket", (start_date, end_date, iteration.id)) 715 729 716 730 for ticket, time, author, field, oldvalue, newvalue in cursor: 717 731 if not changed_tickets.has_key( ticket ): -
patch/trac/ticket/web_ui.py
601 601 'context': Context.from_request(req, ticket.resource, 602 602 absurls=absurls), 603 603 'preserve_newlines': self.must_preserve_newlines, 604 'date_hint': get_date_format_hint()} 604 'date_hint': get_date_format_hint(), 605 'iterations': TicketSystem(self.env).get_ticket_iterations(ticket.id)} 605 606 606 607 def _toggle_cc(self, req, cc): 607 608 """Return an (action, recipient) tuple corresponding to a change -
patch/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.description = '' 986 self.tickets = [] 985 987 986 988 def _get_resource(self): 987 989 return Resource('iteration', self.id) ### .version !!! … … 991 993 if not db: 992 994 db = self.env.get_db_cnx() 993 995 cursor = db.cursor() 994 cursor.execute("SELECT id,summary, start_date,end_date,tickets"996 cursor.execute("SELECT id,summary,description,start_date,end_date " 995 997 "FROM iteration WHERE id=%s", (iteration_id,)) 996 998 row = cursor.fetchone() 997 999 if not row: 998 1000 raise ResourceNotFound('Iteration %s does not exist.' % iteration_id, 999 1001 'Invalid Iteration Id') 1000 id, summary, start_date, end_date, tickets= row1002 id, summary, description, start_date, end_date = row 1001 1003 self.id = id 1002 1004 self.summary = summary or '' 1005 self.description = description or '' 1003 1006 self.start_date = start_date and datetime.fromtimestamp(int(start_date), utc) or None 1004 1007 self.end_date = end_date and datetime.fromtimestamp(int(end_date), utc) or None 1005 self.tickets = tickets or '' 1008 cursor.execute("SELECT ticket_id FROM iteration_ticket " 1009 "WHERE iteration_id=%s ORDER BY ticket_id", (iteration_id,)) 1010 self.tickets = [str(row[0]) for row in cursor.fetchall()] 1006 1011 1007 1012 is_started = property(fget=lambda self: self.start_date and \ 1008 1013 self.start_date.date() < date.today()) … … 1024 1029 cursor = db.cursor() 1025 1030 self.env.log.info('Deleting iteration %s' % self.id) 1026 1031 cursor.execute("DELETE FROM iteration WHERE id=%s", (self.id,)) 1032 cursor.execute("DELETE FROM iteration_ticket WHERE iteration_id=%s" % (self.id,)) 1027 1033 1028 1034 if handle_ta: 1029 1035 db.commit() … … 1037 1043 handle_ta = False 1038 1044 1039 1045 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)) 1046 cursor.execute("INSERT INTO iteration (summary,description,start_date,end_date) " 1047 "VALUES (%s,%s,%s)", 1048 (self.summary, self.description, to_timestamp(self.start_date), to_timestamp(self.end_date))) 1044 1049 iteration_id = db.get_last_id(cursor, 'iteration') 1050 for ticket_id in self.tickets: 1051 cursor.execute("INSERT INTO iteration_ticket (iteration_id, ticket_id) " 1052 "VALUES (%s,%s)", 1053 (iteration_id, ticket_id)) 1045 1054 1046 1055 if handle_ta: 1047 1056 db.commit() … … 1059 1068 1060 1069 cursor = db.cursor() 1061 1070 self.env.log.info('Updating iteration "%s"' % self.id) 1062 cursor.execute("UPDATE iteration SET summary=%s,start_date=%s," 1063 "end_date=%s,tickets=%s WHERE id=%s", 1064 (self.summary, to_timestamp(self.start_date), to_timestamp(self.end_date), 1065 self.tickets, 1071 cursor.execute("UPDATE iteration SET summary=%s,description=%s,start_date=%s," 1072 "end_date=%s WHERE id=%s", 1073 (self.summary, self.description, to_timestamp(self.start_date), to_timestamp(self.end_date), 1066 1074 self.id)) 1067 1075 1076 cursor.execute("DELETE FROM iteration_ticket WHERE iteration_id=%s" % (self.id,)) 1077 for ticket_id in self.tickets: 1078 cursor.execute("INSERT INTO iteration_ticket (iteration_id, ticket_id) " 1079 "VALUES (%s,%s)", 1080 (self.id, ticket_id)) 1081 1068 1082 if handle_ta: 1069 1083 db.commit() 1070 1084 … … 1072 1086 if not db: 1073 1087 db = env.get_db_cnx() 1074 1088 now = to_timestamp( datetime.now(utc) - timedelta(days=1) ) 1075 sql = "SELECT id,summary, start_date,end_date,ticketsFROM iteration "1089 sql = "SELECT id,summary,description,start_date,end_date FROM iteration " 1076 1090 if not include_finished: 1077 1091 sql += "WHERE end_date >= %s " % now 1078 1092 cursor = db.cursor() 1079 1093 cursor.execute(sql) 1094 iteration_rows = cursor.fetchall() 1080 1095 iterations = [] 1081 for iteration_id,summary, start_date,end_date,tickets in cursor:1096 for iteration_id,summary,description,start_date,end_date in iteration_rows: 1082 1097 iteration = Iteration(env) 1083 1098 iteration.id = iteration._old_id = iteration_id 1084 1099 iteration.summary = summary or '' 1100 iteration.description = description or '' 1085 1101 iteration.start_date = start_date and datetime.fromtimestamp(int(start_date), utc) or None 1086 1102 iteration.end_date = end_date and datetime.fromtimestamp(int(end_date), utc) or None 1087 iteration.tickets = tickets or '' 1103 cursor.execute("SELECT ticket_id FROM iteration_ticket " 1104 "WHERE iteration_id = %d ORDER BY ticket_id" % (iteration_id,)) 1105 iteration.tickets = [str(row[0]) for row in cursor.fetchall()] 1088 1106 iterations.append(iteration) 1089 1107 def iteration_order(m): 1090 1108 return (m.start_date or utcmax, -
patch/trac/ticket/roadmap.py
502 502 req.perm(iteration.resource).require('ITERATION_CREATE') 503 503 504 504 iteration.summary = req.args.get('summary', '') 505 iteration.description = req.args.get('description', '') 505 506 506 507 start_date = req.args.get('start_date', '') 507 508 iteration.start_date = start_date and parse_date(start_date, tzinfo=req.tz) or None … … 525 526 ticket_tokens.append(id_token) 526 527 except ValueError: 527 528 invalid_ticket_tokens.append(id_token) 528 iteration_tickets = " ".join(ticket_tokens) 529 iteration.tickets = iteration_tickets 529 iteration.tickets = ticket_tokens 530 530 # Instead of raising one single error, check all the constraints and 531 531 # let the user fix them by going back to edit mode showing the warnings 532 532 warnings = [] -
patch/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 -
patch/trac/ticket/templates/iterations.html
105 105 ${progress_bar(iteration_info['iteration_stats'].stats, iteration_info['iteration_stats'].interval_hrefs, stats_href=iteration_info['iteration_stats'].stats_href)} 106 106 </py:if> 107 107 </div> 108 108 109 <div class="description" xml:space="preserve"> 110 ${wiki_to_html(context, iteration.description)} 111 </div> 112 109 113 ${ticket_work_table(context(iteration.resource), iterations_info[idx]['ticket_info'], completion_stages)} 110 114 </li> 111 115 </ul> 112 116 113 <div py:if="' MILESTONE_CREATE' in perm" class="buttons">117 <div py:if="'ITERATION_CREATE' in perm" class="buttons"> 114 118 <form method="get" action="${href.iteration()}"><div> 115 119 <input type="hidden" name="action" value="new" /> 116 120 <input type="submit" value="Add new iteration" /> -
patch/trac/ticket/templates/ticket.html
265 265 </td> 266 266 </py:for> 267 267 </tr> 268 <tr class="iterations_row"> 269 <th>In Iterations:</th> 270 <py:choose test=""> 271 <py:when test="len(iterations) == 0"> 272 <td class="no_iterations">None</td> 273 </py:when> 274 <py:otherwise> 275 <td class="iterations"> 276 <py:for each="iteration in iterations"> 277 <a href="${href.iteration(iteration)}">${iteration}</a> 278 </py:for> 279 </td> 280 </py:otherwise> 281 </py:choose> 282 </tr> 268 283 </table> 269 284 </div> 270 285 <h2 id="comment:description"> -
patch/trac/ticket/templates/iteration_view.html
46 46 </py:with> 47 47 </div> 48 48 </fieldset> 49 50 <div class="description" xml:space="preserve"> 51 ${wiki_to_html(context, iteration.description)} 52 </div> 53 49 54 <py:with vars="ticket_info=iteration_info['ticket_info']"> 50 55 ${ticket_work_table(context, ticket_info, completion_stages)} 51 56 </py:with> -
patch/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> … … 228 228 ${ticket_work_table(context, ticket_info, completion_stages)} 229 229 </py:with> 230 230 </fieldset> 231 232 <div class="field"> 233 <fieldset class="iefix"> 234 <label for="description">Description (you may use <a tabindex="42" 235 href="${href.wiki('WikiFormatting')}">WikiFormatting</a> here):</label> 236 <p><textarea id="description" name="description" class="wikitext" rows="10" cols="78"> 237 ${iteration.description} 238 </textarea></p> 239 </fieldset> 240 </div> 241 231 242 <div class="buttons" py:choose="iteration.exists"> 232 243 <input py:when="True" type="submit" value="Submit changes" /> 233 244 <input py:otherwise="" type="submit" value="Add iteration" /> -
patch/trac/htdocs/css/ticket.css
74 74 75 75 #ticket table.properties .relative_size { font-weight: bold; font-size: 100%; } 76 76 #ticket table.properties .completion_date { font-weight: bold; font-size: 100%; } 77 #ticket table.properties .iterations_row { border-top: 1px solid #dd9 } 78 #ticket table.properties .iterations_row .no_iterations { font-weight: bold; font-size: 100%; } 79 #ticket table.properties .iterations_row .iterations { font-size: 100%; } 77 80 78 81 div#ticket.description h2 { 79 82 border-bottom: 1px solid #d7d7d7; … … 114 117 form .field fieldset.iefix { margin-left: 1px; margin-right: 1px } 115 118 form .field #comment { margin-left: -1px; margin-right: -1px; padding: 0; width: 100% } 116 119 form .field .wikitoolbar { margin-left: -1px } 120 form .field .iterations { margin-left: -1px } 117 121 118 122 #properties { white-space: nowrap; line-height: 160%; padding: .5em } 119 123 #properties table { border-spacing: 0; width: 100%; } -
patch/trac/admin/templates/admin_iterations.html
50 50 <div class="field"> 51 51 <label>List the tickets, by id, associated with this iteration, separated by a space:<br /> 52 52 <input type="text" id="iteration_tickets" name="iteration_tickets" size="60" 53 value="${ iteration.tickets}" />53 value="${' '.join(iteration.tickets)}" /> 54 54 <em>For example: 56 3 4 12</em> 55 55 </label> 56 56 </div> … … 122 122 <td><py:if test="iteration.end_date"> 123 123 ${format_date(iteration.end_date)} 124 124 </py:if></td> 125 <td>${ iteration.tickets}</td>125 <td>${' '.join(iteration.tickets)}</td> 126 126 </tr></tbody> 127 127 </table> 128 128 <div class="buttons"> -
patch/trac/templates/macros.html
271 271 <tr> 272 272 <th rowspan="2" class="id">Id</th> 273 273 <th rowspan="2">Summary</th> 274 <th rowspan="2">Iterations</th> 274 275 <py:for each="stage in completion_stages"> 275 276 <th colspan="2" class="completion_stage">${stage['short_label']}</th> 276 277 </py:for> … … 296 297 <td><a href="${href.ticket(ticket.id)}">#${ticket.id}</a></td> 297 298 <td class="summary" xml:space="preserve"> 298 299 ${wiki_to_oneliner(context, ticket['summary'])} 299 </td> 300 </td> 301 <td class="iterations" > 302 <py:for each="iteration in ticket_with_stages['iterations']"> 303 <a href="${href.iteration(iteration)}">${iteration}</a> 304 </py:for> 305 </td> 300 306 <py:for each="stage in ticket_with_stages['ordered_stages']"> 301 307 <td py:with="ticket_stage=ticket_stages[stage['stage']]"> 302 308 <py:choose> -
setup.py
1 # -*- coding: utf-8 -*- 1 2 from setuptools import find_packages, setup 2 3 3 4 setup( 4 name='Agile-Trac', version='0.2. 1.dev',5 name='Agile-Trac', version='0.2.2.dev', 5 6 author = 'ja11sop', 6 7 author_email = 'ja11sop@agile-trac.org', 7 8 url = 'http://www.agile-trac.org/', -
agiletrac/env.py
2 2 from trac.env import IEnvironmentSetupParticipant 3 3 from trac.db import * 4 4 5 current_db_version = 15 current_db_version = 2 6 6 7 7 class AgileTracSetup(Component): 8 8 """Central functionality for the Agile-Trac plugin."""
