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)
Copyright (C) 2012-2017 Edgewall Software