| 1 | # svntrac
|
|---|
| 2 | #
|
|---|
| 3 | # Copyright (C) 2003 Jonas Borgström <jonas@xyche.com>
|
|---|
| 4 | #
|
|---|
| 5 | # svntrac is free software; you can redistribute it and/or
|
|---|
| 6 | # modify it under the terms of the GNU General Public License as
|
|---|
| 7 | # published by the Free Software Foundation; either version 2 of the
|
|---|
| 8 | # License, or (at your option) any later version.
|
|---|
| 9 | #
|
|---|
| 10 | # svntrac is distributed in the hope that it will be useful,
|
|---|
| 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|---|
| 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|---|
| 13 | # General Public License for more details.
|
|---|
| 14 | #
|
|---|
| 15 | # You should have received a copy of the GNU General Public License
|
|---|
| 16 | # along with this program; if not, write to the Free Software
|
|---|
| 17 | # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|---|
| 18 | #
|
|---|
| 19 | # Author: Jonas Borgström <jonas@xyche.com>
|
|---|
| 20 |
|
|---|
| 21 | import time
|
|---|
| 22 | import string
|
|---|
| 23 | import StringIO
|
|---|
| 24 |
|
|---|
| 25 | from util import *
|
|---|
| 26 | from Module import Module
|
|---|
| 27 | import perm
|
|---|
| 28 | import auth
|
|---|
| 29 | import db
|
|---|
| 30 |
|
|---|
| 31 | fields = ['time', 'component', 'severity', 'priority', 'milestone', 'reporter',
|
|---|
| 32 | 'owner', 'cc', 'url', 'version', 'status', 'resolution',
|
|---|
| 33 | 'summary', 'description']
|
|---|
| 34 |
|
|---|
| 35 | class Newticket (Module):
|
|---|
| 36 | template_key = 'newticket_template'
|
|---|
| 37 | def render (self):
|
|---|
| 38 | default_component = self.config.get('ticket', 'default_component')
|
|---|
| 39 | default_milestone = self.config.get('ticket', 'default_milestone')
|
|---|
| 40 | default_priority = self.config.get('ticket', 'default_priority')
|
|---|
| 41 | default_severity = self.config.get('ticket', 'default_severity')
|
|---|
| 42 | default_version = self.config.get('ticket', 'default_version')
|
|---|
| 43 |
|
|---|
| 44 | self.namespace['title'] = 'create a new ticket'
|
|---|
| 45 | self.namespace['component_select'] = enum_selector ('SELECT name FROM component ORDER by name',
|
|---|
| 46 | 'component',
|
|---|
| 47 | default_component)
|
|---|
| 48 | self.namespace['milestone_select'] = enum_selector ('SELECT name FROM milestone ORDER BY name',
|
|---|
| 49 | 'milestone',
|
|---|
| 50 | default_milestone)
|
|---|
| 51 | self.namespace['severity_select'] = enum_selector ('SELECT name FROM enum WHERE type=\'severity\' ORDER BY name',
|
|---|
| 52 | 'severity',
|
|---|
| 53 | default_severity)
|
|---|
| 54 | self.namespace['priority_select'] = enum_selector ('SELECT name FROM enum WHERE type=\'priority\' ORDER BY name',
|
|---|
| 55 | 'priority',
|
|---|
| 56 | default_priority)
|
|---|
| 57 | self.namespace['version_select'] = enum_selector ('SELECT name FROM version ORDER BY name',
|
|---|
| 58 | 'version')
|
|---|
| 59 | if auth.get_authname() == 'anonymous':
|
|---|
| 60 | self.namespace['reporter'] = ''
|
|---|
| 61 | else:
|
|---|
| 62 | self.namespace['reporter'] = auth.get_authname()
|
|---|
| 63 |
|
|---|
| 64 |
|
|---|
| 65 | class Ticket (Module):
|
|---|
| 66 | template_key = 'ticket_template'
|
|---|
| 67 |
|
|---|
| 68 | def get_ticket (self, id):
|
|---|
| 69 | global fields
|
|---|
| 70 | cnx = db.get_connection ()
|
|---|
| 71 | cursor = cnx.cursor ()
|
|---|
| 72 |
|
|---|
| 73 | fetch = string.join(fields, ',')
|
|---|
| 74 |
|
|---|
| 75 | cursor.execute(('SELECT %s FROM ticket ' % fetch) + 'WHERE id=%s', id)
|
|---|
| 76 | row = cursor.fetchone ()
|
|---|
| 77 | cursor.close ()
|
|---|
| 78 |
|
|---|
| 79 | info = {'ticket': id }
|
|---|
| 80 | for i in range(len(fields)):
|
|---|
| 81 | info[fields[i]] = row[i] or ''
|
|---|
| 82 | return info
|
|---|
| 83 |
|
|---|
| 84 | def save_changes (self, id, old, new):
|
|---|
| 85 | global fields
|
|---|
| 86 |
|
|---|
| 87 | if new.has_key('action'):
|
|---|
| 88 | if new['action'] == 'accept':
|
|---|
| 89 | new['status'] = 'assigned'
|
|---|
| 90 | new['owner'] = auth.get_authname()
|
|---|
| 91 | if new['action'] == 'resolve':
|
|---|
| 92 | new['status'] = 'closed'
|
|---|
| 93 | new['resolution'] = new['resolve_resolution']
|
|---|
| 94 | elif new['action'] == 'reassign':
|
|---|
| 95 | new['owner'] = new['reassign_owner']
|
|---|
| 96 | new['status'] = 'assigned'
|
|---|
| 97 | elif new['action'] == 'reopen':
|
|---|
| 98 | new['status'] = 'reopened'
|
|---|
| 99 | new['resolution'] = ''
|
|---|
| 100 |
|
|---|
| 101 | changed = 0
|
|---|
| 102 | change = ''
|
|---|
| 103 | cnx = db.get_connection ()
|
|---|
| 104 | cursor = cnx.cursor()
|
|---|
| 105 | now = int(time.time())
|
|---|
| 106 | authname = auth.get_authname ()
|
|---|
| 107 | for name in fields.keys():
|
|---|
| 108 | if new.has_key(name) and (not old.has_key(name) or old[name] != new[name]):
|
|---|
| 109 | cursor.execute ('INSERT INTO ticket_change '
|
|---|
| 110 | '(ticket, time, author, field, oldvalue, newvalue) '
|
|---|
| 111 | 'VALUES (%s, %s, %s, %s, %s, %s)',
|
|---|
| 112 | id, now, authname, name, old[name], new[name])
|
|---|
| 113 | cursor.execute ('UPDATE ticket SET %s=%s WHERE id=%s',
|
|---|
| 114 | name, new[name], id)
|
|---|
| 115 | changed = 1
|
|---|
| 116 | if new.has_key('comment') and len(new['comment']) > 0:
|
|---|
| 117 | cursor.execute ('INSERT INTO ticket_change '
|
|---|
| 118 | '(ticket,time,author,field,oldvalue,newvalue) '
|
|---|
| 119 | "VALUES (%s, %s, %s, 'comment', '', %s)",
|
|---|
| 120 | id, now, authorname, new['comment'])
|
|---|
| 121 | changed = 1
|
|---|
| 122 | if changed:
|
|---|
| 123 | cursor.execute ('UPDATE ticket SET changetime=%s WHERE id=%s',
|
|---|
| 124 | now, id)
|
|---|
| 125 | cnx.commit()
|
|---|
| 126 |
|
|---|
| 127 | def create_ticket (self):
|
|---|
| 128 | perm.assert_permission (perm.TICKET_CREATE)
|
|---|
| 129 |
|
|---|
| 130 | cnx = db.get_connection ()
|
|---|
| 131 | cursor = cnx.cursor()
|
|---|
| 132 | global fields
|
|---|
| 133 | data = {}
|
|---|
| 134 | for field in fields:
|
|---|
| 135 | if self.args.has_key(field):
|
|---|
| 136 | data[field] = self.args[field]
|
|---|
| 137 | now = int(time.time())
|
|---|
| 138 | data['time'] = now
|
|---|
| 139 | data['changetime'] = now
|
|---|
| 140 |
|
|---|
| 141 | if not data.has_key('owner') or data['owner'] == '':
|
|---|
| 142 | # Assign it to the default owner
|
|---|
| 143 | cursor.execute('SELECT owner FROM component '
|
|---|
| 144 | 'WHERE name=%s', data['component'])
|
|---|
| 145 | owner = cursor.fetchone()[0]
|
|---|
| 146 | data['owner'] = owner
|
|---|
| 147 |
|
|---|
| 148 | nstr = string.join (data.keys(), ',')
|
|---|
| 149 | vstr = ('%s,' * len(data.keys()))[:-1]
|
|---|
| 150 |
|
|---|
| 151 | cursor.execute ('INSERT INTO ticket (%s) VALUES(%s)' % (nstr, vstr),
|
|---|
| 152 | *data.values())
|
|---|
| 153 | id = cnx.db.sqlite_last_insert_rowid ()
|
|---|
| 154 | cnx.commit()
|
|---|
| 155 |
|
|---|
| 156 | # redirect to the Ticket module to get a GET request
|
|---|
| 157 | redirect (ticket_href(id))
|
|---|
| 158 |
|
|---|
| 159 | def get_changes(self, id):
|
|---|
| 160 | cnx = db.get_connection ()
|
|---|
| 161 | cursor = cnx.cursor()
|
|---|
| 162 | cursor.execute('SELECT time, author, field, oldvalue, newvalue '
|
|---|
| 163 | 'FROM ticket_change '
|
|---|
| 164 | 'WHERE ticket=%s ORDER BY time', id)
|
|---|
| 165 |
|
|---|
| 166 | out = StringIO.StringIO()
|
|---|
| 167 | curr_author = None
|
|---|
| 168 | curr_date = 0
|
|---|
| 169 | comment = None
|
|---|
| 170 | while 1:
|
|---|
| 171 | row = cursor.fetchone()
|
|---|
| 172 | if row == None:
|
|---|
| 173 | break
|
|---|
| 174 |
|
|---|
| 175 | date = int(row[0])
|
|---|
| 176 | author = row[1]
|
|---|
| 177 | field = row[2]
|
|---|
| 178 | old = row[3]
|
|---|
| 179 | new = row[4]
|
|---|
| 180 | if date != curr_date or author != curr_author:
|
|---|
| 181 | if comment:
|
|---|
| 182 | out.write ('\ncomment:\n%s\n' % comment)
|
|---|
| 183 | comment = None
|
|---|
| 184 | curr_date = date
|
|---|
| 185 | curr_author = author
|
|---|
| 186 | out.write('\n--- modified by %s %s ---\n\n'
|
|---|
| 187 | % (curr_author,
|
|---|
| 188 | time.strftime('%F %H:%M', time.localtime(curr_date))))
|
|---|
| 189 | if field == 'comment':
|
|---|
| 190 | comment = new
|
|---|
| 191 | continue
|
|---|
| 192 | if new == '':
|
|---|
| 193 | out.write ("cleared <b>%s</b>\n" %
|
|---|
| 194 | (field))
|
|---|
| 195 | elif old == '':
|
|---|
| 196 | out.write ("<b>%s</b> set to <b>%s</b>\n" %
|
|---|
| 197 | (field, new))
|
|---|
| 198 | else:
|
|---|
| 199 | out.write ("<b>%s</b> changed from <b>%s</b> to <b>%s</b>\n" %
|
|---|
| 200 | (field, old, new))
|
|---|
| 201 | if comment:
|
|---|
| 202 | out.write ('\ncomment:\n%s\n' % comment)
|
|---|
| 203 | comment = None
|
|---|
| 204 |
|
|---|
| 205 | return out.getvalue()
|
|---|
| 206 |
|
|---|
| 207 | def get_actions(self, info):
|
|---|
| 208 | out = StringIO.StringIO()
|
|---|
| 209 | out.write ('<input type="radio" name="action" value="leave" '
|
|---|
| 210 | 'checked="checked"> leave as %s<br>' % info['status'])
|
|---|
| 211 |
|
|---|
| 212 | if info['status'] == 'new':
|
|---|
| 213 | out.write ('<input type="radio" name="action" value="accept">'
|
|---|
| 214 | ' accept ticket<br>')
|
|---|
| 215 | if info['status'] == 'closed':
|
|---|
| 216 | out.write ('<input type="radio" name="action" value="reopen">'
|
|---|
| 217 | ' reopen ticket<br>')
|
|---|
| 218 | if info['status'] in ['new', 'assigned', 'reopened']:
|
|---|
| 219 | out.write ('<input type="radio" name="action" value="resolve">'
|
|---|
| 220 | ' resolve as: '
|
|---|
| 221 | '<select name="resolve_resolution">'
|
|---|
| 222 | '<option selected>fixed</option>'
|
|---|
| 223 | '<option>invalid</option>'
|
|---|
| 224 | '<option>wontfix</option>'
|
|---|
| 225 | '<option>duplicate</option>'
|
|---|
| 226 | '<option>worksforme</option>'
|
|---|
| 227 | '</select><br>')
|
|---|
| 228 | out.write ('<input type="radio" name="action" value="reassign">'
|
|---|
| 229 | ' reassign ticket to:'
|
|---|
| 230 | ' <input type="text" name="reassign_owner" '
|
|---|
| 231 | 'value="%s">' % info['owner'])
|
|---|
| 232 | return out.getvalue()
|
|---|
| 233 |
|
|---|
| 234 | def render (self):
|
|---|
| 235 |
|
|---|
| 236 | if self.args.has_key('action'):
|
|---|
| 237 | action = self.args['action']
|
|---|
| 238 | else:
|
|---|
| 239 | action = 'view'
|
|---|
| 240 |
|
|---|
| 241 | if action == 'create':
|
|---|
| 242 | self.create_ticket ()
|
|---|
| 243 | try:
|
|---|
| 244 | id = int(self.args['id'])
|
|---|
| 245 | except:
|
|---|
| 246 | redirect (menu_href ())
|
|---|
| 247 |
|
|---|
| 248 | if action in ['leave', 'accept', 'reopen', 'resolve', 'reassign']:
|
|---|
| 249 | # save changes and redirect to avoid the POST request
|
|---|
| 250 | old = self.get_ticket(id)
|
|---|
| 251 | perm.assert_permission (perm.TICKET_MODIFY)
|
|---|
| 252 | self.save_changes (id, old, self.args)
|
|---|
| 253 | redirect (ticket_href (id))
|
|---|
| 254 |
|
|---|
| 255 | perm.assert_permission (perm.TICKET_VIEW)
|
|---|
| 256 |
|
|---|
| 257 | info = self.get_ticket(id)
|
|---|
| 258 | for key in info.keys():
|
|---|
| 259 | self.namespace[key] = info[key]
|
|---|
| 260 |
|
|---|
| 261 | self.namespace['title'] = 'Ticket #%d' % id
|
|---|
| 262 |
|
|---|
| 263 | self.namespace['component_select'] = enum_selector ('SELECT name FROM component ORDER BY name',
|
|---|
| 264 | 'component',
|
|---|
| 265 | info['component'])
|
|---|
| 266 | self.namespace['milestone_select'] = enum_selector ('SELECT name FROM milestone ORDER BY name',
|
|---|
| 267 | 'milestone',
|
|---|
| 268 | info['milestone'])
|
|---|
| 269 | self.namespace['severity_select'] = enum_selector ('SELECT name FROM enum WHERE type=\'severity\' ORDER BY name',
|
|---|
| 270 | 'severity',
|
|---|
| 271 | info['severity'])
|
|---|
| 272 | self.namespace['priority_select'] = enum_selector ('SELECT name FROM enum WHERE type=\'priority\' ORDER BY name',
|
|---|
| 273 | 'priority',
|
|---|
| 274 | info['priority'])
|
|---|
| 275 | self.namespace['version_select'] = enum_selector ('SELECT name FROM version ORDER BY name',
|
|---|
| 276 | 'version',
|
|---|
| 277 | info['version'])
|
|---|
| 278 |
|
|---|
| 279 | self.namespace['actions'] = self.get_actions(info)
|
|---|
| 280 | self.namespace['changes'] = self.get_changes(id)
|
|---|
| 281 | self.namespace['opened'] = time.strftime('%F %H:%M',
|
|---|
| 282 | time.localtime(int(info['time'])))
|
|---|
| 283 |
|
|---|