Mercurial > genshi > mirror
comparison examples/trac/trac/ticket/model.py @ 39:93b4dcbafd7b trunk
Copy Trac to main branch.
author | cmlenz |
---|---|
date | Mon, 03 Jul 2006 18:53:27 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
38:ee669cb9cccc | 39:93b4dcbafd7b |
---|---|
1 # -*- coding: utf-8 -*- | |
2 # | |
3 # Copyright (C) 2003-2006 Edgewall Software | |
4 # Copyright (C) 2003-2006 Jonas Borgström <jonas@edgewall.com> | |
5 # Copyright (C) 2005 Christopher Lenz <cmlenz@gmx.de> | |
6 # Copyright (C) 2006 Christian Boos <cboos@neuf.fr> | |
7 # All rights reserved. | |
8 # | |
9 # This software is licensed as described in the file COPYING, which | |
10 # you should have received as part of this distribution. The terms | |
11 # are also available at http://trac.edgewall.com/license.html. | |
12 # | |
13 # This software consists of voluntary contributions made by many | |
14 # individuals. For the exact contribution history, see the revision | |
15 # history and logs, available at http://projects.edgewall.com/trac/. | |
16 # | |
17 # Author: Jonas Borgström <jonas@edgewall.com> | |
18 # Christopher Lenz <cmlenz@gmx.de> | |
19 | |
20 import time | |
21 import sys | |
22 import re | |
23 | |
24 from trac.core import TracError | |
25 from trac.ticket import TicketSystem | |
26 from trac.util import sorted, embedded_numbers | |
27 | |
28 __all__ = ['Ticket', 'Type', 'Status', 'Resolution', 'Priority', 'Severity', | |
29 'Component', 'Milestone', 'Version'] | |
30 | |
31 | |
32 class Ticket(object): | |
33 | |
34 def __init__(self, env, tkt_id=None, db=None): | |
35 self.env = env | |
36 self.fields = TicketSystem(self.env).get_ticket_fields() | |
37 self.values = {} | |
38 if tkt_id: | |
39 self._fetch_ticket(tkt_id, db) | |
40 else: | |
41 self._init_defaults(db) | |
42 self.id = self.time_created = self.time_changed = None | |
43 self._old = {} | |
44 | |
45 def _get_db(self, db): | |
46 return db or self.env.get_db_cnx() | |
47 | |
48 def _get_db_for_write(self, db): | |
49 if db: | |
50 return (db, False) | |
51 else: | |
52 return (self.env.get_db_cnx(), True) | |
53 | |
54 exists = property(fget=lambda self: self.id is not None) | |
55 | |
56 def _init_defaults(self, db=None): | |
57 for field in self.fields: | |
58 default = None | |
59 if not field.get('custom'): | |
60 default = self.env.config.get('ticket', | |
61 'default_' + field['name']) | |
62 else: | |
63 default = field.get('value') | |
64 options = field.get('options') | |
65 if default and options and default not in options: | |
66 try: | |
67 default_idx = int(default) | |
68 if default_idx > len(options): | |
69 raise ValueError | |
70 default = options[default_idx] | |
71 except ValueError: | |
72 self.env.log.warning('Invalid default value for ' | |
73 'custom field "%s"' | |
74 % field['name']) | |
75 if default: | |
76 self.values.setdefault(field['name'], default) | |
77 | |
78 def _fetch_ticket(self, tkt_id, db=None): | |
79 db = self._get_db(db) | |
80 | |
81 # Fetch the standard ticket fields | |
82 std_fields = [f['name'] for f in self.fields if not f.get('custom')] | |
83 cursor = db.cursor() | |
84 cursor.execute("SELECT %s,time,changetime FROM ticket WHERE id=%%s" | |
85 % ','.join(std_fields), (tkt_id,)) | |
86 row = cursor.fetchone() | |
87 if not row: | |
88 raise TracError('Ticket %d does not exist.' % tkt_id, | |
89 'Invalid Ticket Number') | |
90 | |
91 self.id = tkt_id | |
92 for i in range(len(std_fields)): | |
93 self.values[std_fields[i]] = row[i] or '' | |
94 self.time_created = row[len(std_fields)] | |
95 self.time_changed = row[len(std_fields) + 1] | |
96 | |
97 # Fetch custom fields if available | |
98 custom_fields = [f['name'] for f in self.fields if f.get('custom')] | |
99 cursor.execute("SELECT name,value FROM ticket_custom WHERE ticket=%s", | |
100 (tkt_id,)) | |
101 for name, value in cursor: | |
102 if name in custom_fields: | |
103 self.values[name] = value | |
104 | |
105 def __getitem__(self, name): | |
106 return self.values[name] | |
107 | |
108 def __setitem__(self, name, value): | |
109 """Log ticket modifications so the table ticket_change can be updated""" | |
110 if self.values.has_key(name) and self.values[name] == value: | |
111 return | |
112 if not self._old.has_key(name): # Changed field | |
113 self._old[name] = self.values.get(name) | |
114 elif self._old[name] == value: # Change of field reverted | |
115 del self._old[name] | |
116 if value: | |
117 field = [field for field in self.fields if field['name'] == name] | |
118 if field and field[0].get('type') != 'textarea': | |
119 value = value.strip() | |
120 self.values[name] = value | |
121 | |
122 def populate(self, values): | |
123 """Populate the ticket with 'suitable' values from a dictionary""" | |
124 field_names = [f['name'] for f in self.fields] | |
125 for name in [name for name in values.keys() if name in field_names]: | |
126 self[name] = values.get(name, '') | |
127 | |
128 # We have to do an extra trick to catch unchecked checkboxes | |
129 for name in [name for name in values.keys() if name[9:] in field_names | |
130 and name.startswith('checkbox_')]: | |
131 if not values.has_key(name[9:]): | |
132 self[name[9:]] = '0' | |
133 | |
134 def insert(self, when=0, db=None): | |
135 """Add ticket to database""" | |
136 assert not self.exists, 'Cannot insert an existing ticket' | |
137 db, handle_ta = self._get_db_for_write(db) | |
138 | |
139 # Add a timestamp | |
140 if not when: | |
141 when = int(time.time()) | |
142 self.time_created = self.time_changed = when | |
143 | |
144 cursor = db.cursor() | |
145 | |
146 # The owner field defaults to the component owner | |
147 if self.values.get('component') and not self.values.get('owner'): | |
148 try: | |
149 component = Component(self.env, self['component'], db=db) | |
150 if component.owner: | |
151 self['owner'] = component.owner | |
152 except TracError, e: | |
153 # Assume that no such component exists | |
154 pass | |
155 | |
156 # Insert ticket record | |
157 std_fields = [f['name'] for f in self.fields if not f.get('custom') | |
158 and self.values.has_key(f['name'])] | |
159 cursor.execute("INSERT INTO ticket (%s,time,changetime) VALUES (%s)" | |
160 % (','.join(std_fields), | |
161 ','.join(['%s'] * (len(std_fields) + 2))), | |
162 [self[name] for name in std_fields] + | |
163 [self.time_created, self.time_changed]) | |
164 tkt_id = db.get_last_id(cursor, 'ticket') | |
165 | |
166 # Insert custom fields | |
167 custom_fields = [f['name'] for f in self.fields if f.get('custom') | |
168 and self.values.has_key(f['name'])] | |
169 if custom_fields: | |
170 cursor.executemany("INSERT INTO ticket_custom (ticket,name,value) " | |
171 "VALUES (%s,%s,%s)", [(tkt_id, name, self[name]) | |
172 for name in custom_fields]) | |
173 | |
174 if handle_ta: | |
175 db.commit() | |
176 | |
177 self.id = tkt_id | |
178 self._old = {} | |
179 | |
180 for listener in TicketSystem(self.env).change_listeners: | |
181 listener.ticket_created(self) | |
182 | |
183 return self.id | |
184 | |
185 def save_changes(self, author, comment, when=0, db=None, cnum=''): | |
186 """ | |
187 Store ticket changes in the database. The ticket must already exist in | |
188 the database. | |
189 """ | |
190 assert self.exists, 'Cannot update a new ticket' | |
191 | |
192 if not self._old and not comment: | |
193 return # Not modified | |
194 | |
195 db, handle_ta = self._get_db_for_write(db) | |
196 cursor = db.cursor() | |
197 when = int(when or time.time()) | |
198 | |
199 if self.values.has_key('component'): | |
200 # If the component is changed on a 'new' ticket then owner field | |
201 # is updated accordingly. (#623). | |
202 if self.values.get('status') == 'new' \ | |
203 and self._old.has_key('component') \ | |
204 and not self._old.has_key('owner'): | |
205 try: | |
206 old_comp = Component(self.env, self._old['component'], db) | |
207 old_owner = old_comp.owner or '' | |
208 current_owner = self.values.get('owner') or '' | |
209 if old_owner == current_owner: | |
210 new_comp = Component(self.env, self['component'], db) | |
211 self['owner'] = new_comp.owner | |
212 except TracError, e: | |
213 # If the old component has been removed from the database we | |
214 # just leave the owner as is. | |
215 pass | |
216 | |
217 # Fix up cc list separators and remove duplicates | |
218 if self.values.has_key('cc'): | |
219 cclist = [] | |
220 for cc in re.split(r'[;,\s]+', self.values['cc']): | |
221 if cc not in cclist: | |
222 cclist.append(cc) | |
223 self.values['cc'] = ', '.join(cclist) | |
224 | |
225 custom_fields = [f['name'] for f in self.fields if f.get('custom')] | |
226 for name in self._old.keys(): | |
227 if name in custom_fields: | |
228 cursor.execute("SELECT * FROM ticket_custom " | |
229 "WHERE ticket=%s and name=%s", (self.id, name)) | |
230 if cursor.fetchone(): | |
231 cursor.execute("UPDATE ticket_custom SET value=%s " | |
232 "WHERE ticket=%s AND name=%s", | |
233 (self[name], self.id, name)) | |
234 else: | |
235 cursor.execute("INSERT INTO ticket_custom (ticket,name," | |
236 "value) VALUES(%s,%s,%s)", | |
237 (self.id, name, self[name])) | |
238 else: | |
239 cursor.execute("UPDATE ticket SET %s=%%s WHERE id=%%s" % name, | |
240 (self[name], self.id)) | |
241 cursor.execute("INSERT INTO ticket_change " | |
242 "(ticket,time,author,field,oldvalue,newvalue) " | |
243 "VALUES (%s, %s, %s, %s, %s, %s)", | |
244 (self.id, when, author, name, self._old[name], | |
245 self[name])) | |
246 # always save comment, even if empty (numbering support for timeline) | |
247 cursor.execute("INSERT INTO ticket_change " | |
248 "(ticket,time,author,field,oldvalue,newvalue) " | |
249 "VALUES (%s,%s,%s,'comment',%s,%s)", | |
250 (self.id, when, author, cnum, comment)) | |
251 | |
252 cursor.execute("UPDATE ticket SET changetime=%s WHERE id=%s", | |
253 (when, self.id)) | |
254 | |
255 if handle_ta: | |
256 db.commit() | |
257 self._old = {} | |
258 self.time_changed = when | |
259 | |
260 for listener in TicketSystem(self.env).change_listeners: | |
261 listener.ticket_changed(self, comment, self._old) | |
262 | |
263 def get_changelog(self, when=0, db=None): | |
264 """Return the changelog as a list of tuples of the form | |
265 (time, author, field, oldvalue, newvalue, permanent). | |
266 | |
267 While the other tuple elements are quite self-explanatory, | |
268 the `permanent` flag is used to distinguish collateral changes | |
269 that are not yet immutable (like attachments, currently). | |
270 """ | |
271 db = self._get_db(db) | |
272 cursor = db.cursor() | |
273 if when: | |
274 cursor.execute("SELECT time,author,field,oldvalue,newvalue,1 " | |
275 "FROM ticket_change WHERE ticket=%s AND time=%s " | |
276 "UNION " | |
277 "SELECT time,author,'attachment',null,filename,0 " | |
278 "FROM attachment WHERE id=%s AND time=%s " | |
279 "UNION " | |
280 "SELECT time,author,'comment',null,description,0 " | |
281 "FROM attachment WHERE id=%s AND time=%s " | |
282 "ORDER BY time", | |
283 (self.id, when, str(self.id), when, self.id, when)) | |
284 else: | |
285 cursor.execute("SELECT time,author,field,oldvalue,newvalue,1 " | |
286 "FROM ticket_change WHERE ticket=%s " | |
287 "UNION " | |
288 "SELECT time,author,'attachment',null,filename,0 " | |
289 "FROM attachment WHERE id=%s " | |
290 "UNION " | |
291 "SELECT time,author,'comment',null,description,0 " | |
292 "FROM attachment WHERE id=%s " | |
293 "ORDER BY time", (self.id, str(self.id), self.id)) | |
294 log = [] | |
295 for t, author, field, oldvalue, newvalue, permanent in cursor: | |
296 log.append((int(t), author, field, oldvalue or '', newvalue or '', | |
297 permanent)) | |
298 return log | |
299 | |
300 def delete(self, db=None): | |
301 db, handle_ta = self._get_db_for_write(db) | |
302 cursor = db.cursor() | |
303 cursor.execute("DELETE FROM ticket WHERE id=%s", (self.id,)) | |
304 cursor.execute("DELETE FROM ticket_change WHERE ticket=%s", (self.id,)) | |
305 cursor.execute("DELETE FROM attachment " | |
306 " WHERE type='ticket' and id=%s", (self.id,)) | |
307 cursor.execute("DELETE FROM ticket_custom WHERE ticket=%s", (self.id,)) | |
308 | |
309 if handle_ta: | |
310 db.commit() | |
311 | |
312 for listener in TicketSystem(self.env).change_listeners: | |
313 listener.ticket_deleted(self) | |
314 | |
315 | |
316 class AbstractEnum(object): | |
317 type = None | |
318 ticket_col = None | |
319 | |
320 def __init__(self, env, name=None, db=None): | |
321 if not self.ticket_col: | |
322 self.ticket_col = self.type | |
323 self.env = env | |
324 if name: | |
325 if not db: | |
326 db = self.env.get_db_cnx() | |
327 cursor = db.cursor() | |
328 cursor.execute("SELECT value FROM enum WHERE type=%s AND name=%s", | |
329 (self.type, name)) | |
330 row = cursor.fetchone() | |
331 if not row: | |
332 raise TracError, '%s %s does not exist.' % (self.type, name) | |
333 self.value = self._old_value = row[0] | |
334 self.name = self._old_name = name | |
335 else: | |
336 self.value = self._old_value = None | |
337 self.name = self._old_name = None | |
338 | |
339 exists = property(fget=lambda self: self._old_value is not None) | |
340 | |
341 def delete(self, db=None): | |
342 assert self.exists, 'Cannot deleting non-existent %s' % self.type | |
343 if not db: | |
344 db = self.env.get_db_cnx() | |
345 handle_ta = True | |
346 else: | |
347 handle_ta = False | |
348 | |
349 cursor = db.cursor() | |
350 self.env.log.info('Deleting %s %s' % (self.type, self.name)) | |
351 cursor.execute("DELETE FROM enum WHERE type=%s AND value=%s", | |
352 (self.type, self._old_value)) | |
353 | |
354 if handle_ta: | |
355 db.commit() | |
356 self.value = self._old_value = None | |
357 self.name = self._old_name = None | |
358 | |
359 def insert(self, db=None): | |
360 assert not self.exists, 'Cannot insert existing %s' % self.type | |
361 assert self.name, 'Cannot create %s with no name' % self.type | |
362 self.name = self.name.strip() | |
363 if not db: | |
364 db = self.env.get_db_cnx() | |
365 handle_ta = True | |
366 else: | |
367 handle_ta = False | |
368 | |
369 cursor = db.cursor() | |
370 self.env.log.debug("Creating new %s '%s'" % (self.type, self.name)) | |
371 if not self.value: | |
372 cursor.execute(("SELECT COALESCE(MAX(%s),0) FROM enum " | |
373 "WHERE type=%%s") % db.cast('value', 'int'), | |
374 (self.type,)) | |
375 self.value = int(float(cursor.fetchone()[0])) + 1 | |
376 cursor.execute("INSERT INTO enum (type,name,value) VALUES (%s,%s,%s)", | |
377 (self.type, self.name, self.value)) | |
378 | |
379 if handle_ta: | |
380 db.commit() | |
381 self._old_name = self.name | |
382 self._old_value = self.value | |
383 | |
384 def update(self, db=None): | |
385 assert self.exists, 'Cannot update non-existent %s' % self.type | |
386 assert self.name, 'Cannot update %s with no name' % self.type | |
387 self.name = self.name.strip() | |
388 if not db: | |
389 db = self.env.get_db_cnx() | |
390 handle_ta = True | |
391 else: | |
392 handle_ta = False | |
393 | |
394 cursor = db.cursor() | |
395 self.env.log.info('Updating %s "%s"' % (self.type, self.name)) | |
396 cursor.execute("UPDATE enum SET name=%s,value=%s " | |
397 "WHERE type=%s AND name=%s", | |
398 (self.name, self.value, self.type, self._old_name)) | |
399 if self.name != self._old_name: | |
400 # Update tickets | |
401 cursor.execute("UPDATE ticket SET %s=%%s WHERE %s=%%s" % | |
402 (self.ticket_col, self.ticket_col), | |
403 (self.name, self._old_name)) | |
404 | |
405 if handle_ta: | |
406 db.commit() | |
407 self._old_name = self.name | |
408 self._old_value = self.value | |
409 | |
410 def select(cls, env, db=None): | |
411 if not db: | |
412 db = env.get_db_cnx() | |
413 cursor = db.cursor() | |
414 cursor.execute("SELECT name,value FROM enum WHERE type=%s " | |
415 "ORDER BY value", (cls.type,)) | |
416 for name, value in cursor: | |
417 obj = cls(env) | |
418 obj.name = obj._old_name = name | |
419 obj.value = obj._old_value = value | |
420 yield obj | |
421 select = classmethod(select) | |
422 | |
423 | |
424 class Type(AbstractEnum): | |
425 type = 'ticket_type' | |
426 ticket_col = 'type' | |
427 | |
428 | |
429 class Status(AbstractEnum): | |
430 type = 'status' | |
431 | |
432 | |
433 class Resolution(AbstractEnum): | |
434 type = 'resolution' | |
435 | |
436 | |
437 class Priority(AbstractEnum): | |
438 type = 'priority' | |
439 | |
440 | |
441 class Severity(AbstractEnum): | |
442 type = 'severity' | |
443 | |
444 | |
445 class Component(object): | |
446 | |
447 def __init__(self, env, name=None, db=None): | |
448 self.env = env | |
449 if name: | |
450 if not db: | |
451 db = self.env.get_db_cnx() | |
452 cursor = db.cursor() | |
453 cursor.execute("SELECT owner,description FROM component " | |
454 "WHERE name=%s", (name,)) | |
455 row = cursor.fetchone() | |
456 if not row: | |
457 raise TracError, 'Component %s does not exist.' % name | |
458 self.name = self._old_name = name | |
459 self.owner = row[0] or None | |
460 self.description = row[1] or '' | |
461 else: | |
462 self.name = self._old_name = None | |
463 self.owner = None | |
464 self.description = None | |
465 | |
466 exists = property(fget=lambda self: self._old_name is not None) | |
467 | |
468 def delete(self, db=None): | |
469 assert self.exists, 'Cannot deleting non-existent component' | |
470 if not db: | |
471 db = self.env.get_db_cnx() | |
472 handle_ta = True | |
473 else: | |
474 handle_ta = False | |
475 | |
476 cursor = db.cursor() | |
477 self.env.log.info('Deleting component %s' % self.name) | |
478 cursor.execute("DELETE FROM component WHERE name=%s", (self.name,)) | |
479 | |
480 self.name = self._old_name = None | |
481 | |
482 if handle_ta: | |
483 db.commit() | |
484 | |
485 def insert(self, db=None): | |
486 assert not self.exists, 'Cannot insert existing component' | |
487 assert self.name, 'Cannot create component with no name' | |
488 self.name = self.name.strip() | |
489 if not db: | |
490 db = self.env.get_db_cnx() | |
491 handle_ta = True | |
492 else: | |
493 handle_ta = False | |
494 | |
495 cursor = db.cursor() | |
496 self.env.log.debug("Creating new component '%s'" % self.name) | |
497 cursor.execute("INSERT INTO component (name,owner,description) " | |
498 "VALUES (%s,%s,%s)", | |
499 (self.name, self.owner, self.description)) | |
500 | |
501 if handle_ta: | |
502 db.commit() | |
503 | |
504 def update(self, db=None): | |
505 assert self.exists, 'Cannot update non-existent component' | |
506 assert self.name, 'Cannot update component with no name' | |
507 self.name = self.name.strip() | |
508 if not db: | |
509 db = self.env.get_db_cnx() | |
510 handle_ta = True | |
511 else: | |
512 handle_ta = False | |
513 | |
514 cursor = db.cursor() | |
515 self.env.log.info('Updating component "%s"' % self.name) | |
516 cursor.execute("UPDATE component SET name=%s,owner=%s,description=%s " | |
517 "WHERE name=%s", | |
518 (self.name, self.owner, self.description, | |
519 self._old_name)) | |
520 if self.name != self._old_name: | |
521 # Update tickets | |
522 cursor.execute("UPDATE ticket SET component=%s WHERE component=%s", | |
523 (self.name, self._old_name)) | |
524 self._old_name = self.name | |
525 | |
526 if handle_ta: | |
527 db.commit() | |
528 | |
529 def select(cls, env, db=None): | |
530 if not db: | |
531 db = env.get_db_cnx() | |
532 cursor = db.cursor() | |
533 cursor.execute("SELECT name,owner,description FROM component " | |
534 "ORDER BY name") | |
535 for name, owner, description in cursor: | |
536 component = cls(env) | |
537 component.name = name | |
538 component.owner = owner or None | |
539 component.description = description or '' | |
540 yield component | |
541 select = classmethod(select) | |
542 | |
543 | |
544 class Milestone(object): | |
545 | |
546 def __init__(self, env, name=None, db=None): | |
547 self.env = env | |
548 if name: | |
549 self._fetch(name, db) | |
550 self._old_name = name | |
551 else: | |
552 self.name = self._old_name = None | |
553 self.due = self.completed = 0 | |
554 self.description = '' | |
555 | |
556 def _fetch(self, name, db=None): | |
557 if not db: | |
558 db = self.env.get_db_cnx() | |
559 cursor = db.cursor() | |
560 cursor.execute("SELECT name,due,completed,description " | |
561 "FROM milestone WHERE name=%s", (name,)) | |
562 row = cursor.fetchone() | |
563 if not row: | |
564 raise TracError('Milestone %s does not exist.' % name, | |
565 'Invalid Milestone Name') | |
566 self.name = row[0] | |
567 self.due = row[1] and int(row[1]) or 0 | |
568 self.completed = row[2] and int(row[2]) or 0 | |
569 self.description = row[3] or '' | |
570 | |
571 exists = property(fget=lambda self: self._old_name is not None) | |
572 is_completed = property(fget=lambda self: self.completed != 0) | |
573 is_late = property(fget=lambda self: self.due and \ | |
574 self.due < time.time() - 86400) | |
575 | |
576 def delete(self, retarget_to=None, author=None, db=None): | |
577 if not db: | |
578 db = self.env.get_db_cnx() | |
579 handle_ta = True | |
580 else: | |
581 handle_ta = False | |
582 | |
583 cursor = db.cursor() | |
584 self.env.log.info('Deleting milestone %s' % self.name) | |
585 cursor.execute("DELETE FROM milestone WHERE name=%s", (self.name,)) | |
586 | |
587 # Retarget/reset tickets associated with this milestone | |
588 now = time.time() | |
589 cursor.execute("SELECT id FROM ticket WHERE milestone=%s", (self.name,)) | |
590 tkt_ids = [int(row[0]) for row in cursor] | |
591 for tkt_id in tkt_ids: | |
592 ticket = Ticket(self.env, tkt_id, db) | |
593 ticket['milestone'] = retarget_to | |
594 ticket.save_changes(author, 'Milestone %s deleted' % self.name, | |
595 now, db=db) | |
596 | |
597 if handle_ta: | |
598 db.commit() | |
599 | |
600 def insert(self, db=None): | |
601 assert self.name, 'Cannot create milestone with no name' | |
602 self.name = self.name.strip() | |
603 if not db: | |
604 db = self.env.get_db_cnx() | |
605 handle_ta = True | |
606 else: | |
607 handle_ta = False | |
608 | |
609 cursor = db.cursor() | |
610 self.env.log.debug("Creating new milestone '%s'" % self.name) | |
611 cursor.execute("INSERT INTO milestone (name,due,completed,description) " | |
612 "VALUES (%s,%s,%s,%s)", | |
613 (self.name, self.due, self.completed, self.description)) | |
614 | |
615 if handle_ta: | |
616 db.commit() | |
617 | |
618 def update(self, db=None): | |
619 assert self.name, 'Cannot update milestone with no name' | |
620 self.name = self.name.strip() | |
621 if not db: | |
622 db = self.env.get_db_cnx() | |
623 handle_ta = True | |
624 else: | |
625 handle_ta = False | |
626 | |
627 cursor = db.cursor() | |
628 self.env.log.info('Updating milestone "%s"' % self.name) | |
629 cursor.execute("UPDATE milestone SET name=%s,due=%s," | |
630 "completed=%s,description=%s WHERE name=%s", | |
631 (self.name, self.due, self.completed, self.description, | |
632 self._old_name)) | |
633 self.env.log.info('Updating milestone field of all tickets ' | |
634 'associated with milestone "%s"' % self.name) | |
635 cursor.execute("UPDATE ticket SET milestone=%s WHERE milestone=%s", | |
636 (self.name, self._old_name)) | |
637 self._old_name = self.name | |
638 | |
639 if handle_ta: | |
640 db.commit() | |
641 | |
642 def select(cls, env, include_completed=True, db=None): | |
643 if not db: | |
644 db = env.get_db_cnx() | |
645 sql = "SELECT name,due,completed,description FROM milestone " | |
646 if not include_completed: | |
647 sql += "WHERE COALESCE(completed,0)=0 " | |
648 cursor = db.cursor() | |
649 cursor.execute(sql) | |
650 milestones = [] | |
651 for name,due,completed,description in cursor: | |
652 milestone = Milestone(env) | |
653 milestone.name = milestone._old_name = name | |
654 milestone.due = due and int(due) or 0 | |
655 milestone.completed = completed and int(completed) or 0 | |
656 milestone.description = description or '' | |
657 milestones.append(milestone) | |
658 def milestone_order(m): | |
659 return (m.completed or sys.maxint, | |
660 m.due or sys.maxint, | |
661 embedded_numbers(m.name)) | |
662 return sorted(milestones, key=milestone_order) | |
663 select = classmethod(select) | |
664 | |
665 | |
666 class Version(object): | |
667 | |
668 def __init__(self, env, name=None, db=None): | |
669 self.env = env | |
670 if name: | |
671 if not db: | |
672 db = self.env.get_db_cnx() | |
673 cursor = db.cursor() | |
674 cursor.execute("SELECT time,description FROM version " | |
675 "WHERE name=%s", (name,)) | |
676 row = cursor.fetchone() | |
677 if not row: | |
678 raise TracError, 'Version %s does not exist.' % name | |
679 self.name = self._old_name = name | |
680 self.time = row[0] and int(row[0]) or None | |
681 self.description = row[1] or '' | |
682 else: | |
683 self.name = self._old_name = None | |
684 self.time = None | |
685 self.description = None | |
686 | |
687 exists = property(fget=lambda self: self._old_name is not None) | |
688 | |
689 def delete(self, db=None): | |
690 assert self.exists, 'Cannot deleting non-existent version' | |
691 if not db: | |
692 db = self.env.get_db_cnx() | |
693 handle_ta = True | |
694 else: | |
695 handle_ta = False | |
696 | |
697 cursor = db.cursor() | |
698 self.env.log.info('Deleting version %s' % self.name) | |
699 cursor.execute("DELETE FROM version WHERE name=%s", (self.name,)) | |
700 | |
701 self.name = self._old_name = None | |
702 | |
703 if handle_ta: | |
704 db.commit() | |
705 | |
706 def insert(self, db=None): | |
707 assert not self.exists, 'Cannot insert existing version' | |
708 assert self.name, 'Cannot create version with no name' | |
709 self.name = self.name.strip() | |
710 if not db: | |
711 db = self.env.get_db_cnx() | |
712 handle_ta = True | |
713 else: | |
714 handle_ta = False | |
715 | |
716 cursor = db.cursor() | |
717 self.env.log.debug("Creating new version '%s'" % self.name) | |
718 cursor.execute("INSERT INTO version (name,time,description) " | |
719 "VALUES (%s,%s,%s)", | |
720 (self.name, self.time, self.description)) | |
721 | |
722 if handle_ta: | |
723 db.commit() | |
724 | |
725 def update(self, db=None): | |
726 assert self.exists, 'Cannot update non-existent version' | |
727 assert self.name, 'Cannot update version with no name' | |
728 self.name = self.name.strip() | |
729 if not db: | |
730 db = self.env.get_db_cnx() | |
731 handle_ta = True | |
732 else: | |
733 handle_ta = False | |
734 | |
735 cursor = db.cursor() | |
736 self.env.log.info('Updating version "%s"' % self.name) | |
737 cursor.execute("UPDATE version SET name=%s,time=%s,description=%s " | |
738 "WHERE name=%s", | |
739 (self.name, self.time, self.description, | |
740 self._old_name)) | |
741 if self.name != self._old_name: | |
742 # Update tickets | |
743 cursor.execute("UPDATE ticket SET version=%s WHERE version=%s", | |
744 (self.name, self._old_name)) | |
745 self._old_name = self.name | |
746 | |
747 if handle_ta: | |
748 db.commit() | |
749 | |
750 def select(cls, env, db=None): | |
751 if not db: | |
752 db = env.get_db_cnx() | |
753 cursor = db.cursor() | |
754 cursor.execute("SELECT name,time,description FROM version") | |
755 versions = [] | |
756 for name, time, description in cursor: | |
757 version = cls(env) | |
758 version.name = name | |
759 version.time = time and int(time) or None | |
760 version.description = description or '' | |
761 versions.append(version) | |
762 def version_order(v): | |
763 return (v.time or sys.maxint, embedded_numbers(v.name)) | |
764 return sorted(versions, key=version_order, reverse=True) | |
765 select = classmethod(select) |