| 1 | #!/usr/bin/env python |
|---|
| 2 | |
|---|
| 3 | """ |
|---|
| 4 | Import Mantis bugs into a Trac database. |
|---|
| 5 | |
|---|
| 6 | Requires: Trac 0.9.X from http://trac.edgewall.com/ |
|---|
| 7 | Python 2.3 from http://www.python.org/ |
|---|
| 8 | MySQL >= 3.23 from http://www.mysql.org/ |
|---|
| 9 | |
|---|
| 10 | Version 1.1 |
|---|
| 11 | Date: January 23, 2006 |
|---|
| 12 | Author: Joao Prado Maia (jpm@pessoal.org) |
|---|
| 13 | |
|---|
| 14 | Based on version 1.0 from: |
|---|
| 15 | Paul Baranowski (paul@paulbaranowski.org) |
|---|
| 16 | |
|---|
| 17 | Based on bugzilla2trac.py by these guys (thank you!): |
|---|
| 18 | Dmitry Yusupov <dmitry_yus@yahoo.com> - bugzilla2trac.py |
|---|
| 19 | Mark Rowe <mrowe@bluewire.net.nz> - original TracDatabase class |
|---|
| 20 | Bill Soudan <bill@soudan.net> - Many enhancements |
|---|
| 21 | |
|---|
| 22 | Example use: |
|---|
| 23 | python mantis2trac.py --db mantis --tracenv /usr/local/trac-projects/myproj/ --host localhost --user root --clean |
|---|
| 24 | |
|---|
| 25 | Changes since version 1.0: |
|---|
| 26 | - Made it to work against Trac 0.9.3 (tweaks to make the Environment class work) |
|---|
| 27 | - Re-did all prepared statements-like queries to avoid a DB error |
|---|
| 28 | - Fixed a reference to the wrong variable name when adding a comment |
|---|
| 29 | |
|---|
| 30 | Notes: |
|---|
| 31 | - Private bugs will become public |
|---|
| 32 | - Some ticket changes will not be preserved since they have no |
|---|
| 33 | equivalents in Trac. |
|---|
| 34 | - I consider milestones and versions to be the same thing (actually, |
|---|
| 35 | I dont really care about the version, because for our project, bugs are |
|---|
| 36 | only in the 'previous version'). |
|---|
| 37 | - Importing attachments is not implemented (couldnt get it to work, |
|---|
| 38 | and we didnt have enough attachments to justify spending time on this) |
|---|
| 39 | "Clean" will not delete your existing attachments. There is code in here |
|---|
| 40 | to support adding attachments, but you will have to play with it to |
|---|
| 41 | make it work. If you search for the word "attachment" you will find |
|---|
| 42 | all the code related to this. |
|---|
| 43 | - Ticket descriptions & comments will be re-wrapped to 70 characters. |
|---|
| 44 | This may mess up your formatting for your bugs. If you dont want to do |
|---|
| 45 | this, search for textwrap.fill() and fix it. |
|---|
| 46 | - You will probably want to change "report.css" in trac to handle one more |
|---|
| 47 | level of priorities (default trac has 6 levels of priorities, while Mantis |
|---|
| 48 | has 7). When you look at your reports, the color schemes will look wrong. |
|---|
| 49 | |
|---|
| 50 | The lines that control the priority color scheme look like this: |
|---|
| 51 | #tktlist tr.color1-odd { background: #fdc; border-color: #e88; color: #a22 } |
|---|
| 52 | #tktlist tr.color1-even { background: #fed; border-color: #e99; color: #a22 } |
|---|
| 53 | |
|---|
| 54 | I added a new level 2 ("urgent") with an orange color, |
|---|
| 55 | and incremented all the rest of the levels: |
|---|
| 56 | #tktlist tr.color2-odd { background: #FFE08F; border-color: #e88; color: #a22 } |
|---|
| 57 | #tktlist tr.color2-even { background: #FFE59F; border-color: #e99; color: #a22 } |
|---|
| 58 | |
|---|
| 59 | """ |
|---|
| 60 | |
|---|
| 61 | import datetime |
|---|
| 62 | |
|---|
| 63 | ### |
|---|
| 64 | ### Conversion Settings -- edit these before running if desired |
|---|
| 65 | ### |
|---|
| 66 | |
|---|
| 67 | # Mantis version. |
|---|
| 68 | # |
|---|
| 69 | # Currently, the following mantis versions are known to work: |
|---|
| 70 | # 0.19.X |
|---|
| 71 | # |
|---|
| 72 | # If you run this script on a version not listed here and it is successful, |
|---|
| 73 | # please report it to the Trac mailing list so we can update the list. |
|---|
| 74 | MANTIS_VERSION = '0.19' |
|---|
| 75 | |
|---|
| 76 | # MySQL connection parameters for the Mantis database. These can also |
|---|
| 77 | # be specified on the command line. |
|---|
| 78 | MANTIS_DB = 'mantis' |
|---|
| 79 | MANTIS_HOST = 'localhost' |
|---|
| 80 | MANTIS_USER = 'root' |
|---|
| 81 | MANTIS_PASSWORD = '' |
|---|
| 82 | |
|---|
| 83 | # Path to the Trac environment. |
|---|
| 84 | TRAC_ENV = '' |
|---|
| 85 | |
|---|
| 86 | # If true, all existing Trac tickets will be removed |
|---|
| 87 | # prior to import. |
|---|
| 88 | TRAC_CLEAN = True |
|---|
| 89 | |
|---|
| 90 | # Enclose imported ticket description and comments in a {{{ }}} |
|---|
| 91 | # preformat block? This formats the text in a fixed-point font. |
|---|
| 92 | PREFORMAT_COMMENTS = True |
|---|
| 93 | |
|---|
| 94 | # By default, all bugs are imported from Mantis. If you add a list |
|---|
| 95 | # of products here, only bugs from those products will be imported. |
|---|
| 96 | # Warning: I have not tested this script where this field is blank! |
|---|
| 97 | PRODUCTS = [ 'Web Interface' ] |
|---|
| 98 | |
|---|
| 99 | # Trac doesn't have the concept of a product. Instead, this script can |
|---|
| 100 | # assign keywords in the ticket entry to represent products. |
|---|
| 101 | # |
|---|
| 102 | # ex. PRODUCT_KEYWORDS = { 'product1' : 'PRODUCT1_KEYWORD' } |
|---|
| 103 | PRODUCT_KEYWORDS = {} |
|---|
| 104 | |
|---|
| 105 | # Bug comments that should not be imported. Each entry in list should |
|---|
| 106 | # be a regular expression. |
|---|
| 107 | IGNORE_COMMENTS = [ |
|---|
| 108 | # '^Created an attachment \(id=' |
|---|
| 109 | ] |
|---|
| 110 | |
|---|
| 111 | # Ticket changes in Trac have the restriction where the |
|---|
| 112 | # bug ID, field, and time must be unique for all entries in the ticket |
|---|
| 113 | # changes table. |
|---|
| 114 | # Mantis, for unknown reasons, has fields that can change two states |
|---|
| 115 | # in under a second (e.g. "milestone":""->"1.0", "milestone":"1.0"->"2.0"). |
|---|
| 116 | # Setting this to true will attempt to fix these cases by adjusting the |
|---|
| 117 | # time for the 2nd change to be one second more than the original time. |
|---|
| 118 | # I dont know why you'd want to turn this off, but I give you the option |
|---|
| 119 | # anyhow. :) |
|---|
| 120 | TIME_ADJUSTMENT_HACK = True |
|---|
| 121 | |
|---|
| 122 | ########################################################################### |
|---|
| 123 | ### You probably don't need to change any configuration past this line. ### |
|---|
| 124 | ########################################################################### |
|---|
| 125 | |
|---|
| 126 | # Mantis status to Trac status translation map. |
|---|
| 127 | # |
|---|
| 128 | # NOTE: bug activity is translated as well, which may cause bug |
|---|
| 129 | # activity to be deleted (e.g. resolved -> closed in Mantis |
|---|
| 130 | # would translate into closed -> closed in Trac, so we just ignore the |
|---|
| 131 | # change). |
|---|
| 132 | # |
|---|
| 133 | # Possible Trac 'status' values: 'new', 'assigned', 'reopened', 'closed' |
|---|
| 134 | STATUS_TRANSLATE = { |
|---|
| 135 | 10 : 'new', # 10 == 'new' in mantis |
|---|
| 136 | 20 : 'assigned', # 20 == 'feedback' |
|---|
| 137 | 30 : 'new', # 30 == 'acknowledged' |
|---|
| 138 | 50 : 'assigned', # 50 == 'assigned' |
|---|
| 139 | 40 : 'new', # 40 == 'confirmed' |
|---|
| 140 | 80 : 'closed', # 80 == 'resolved' |
|---|
| 141 | 90 : 'closed' # 90 == 'closed' |
|---|
| 142 | } |
|---|
| 143 | |
|---|
| 144 | # Unused: |
|---|
| 145 | # Translate Mantis statuses into Trac keywords. This provides a way |
|---|
| 146 | # to retain the Mantis statuses in Trac. e.g. when a bug is marked |
|---|
| 147 | # 'verified' in Mantis it will be assigned a VERIFIED keyword. |
|---|
| 148 | ##STATUS_KEYWORDS = { |
|---|
| 149 | ## 'confirmed' : 'CONFIRMED', |
|---|
| 150 | ## 'feedback' : 'FEEDBACK', |
|---|
| 151 | ## 'acknowledged':'ACKNOWLEDGED' |
|---|
| 152 | ##} |
|---|
| 153 | |
|---|
| 154 | # Possible Trac resolutions are 'fixed', 'invalid', 'wontfix', 'duplicate', 'worksforme' |
|---|
| 155 | RESOLUTION_TRANSLATE = { |
|---|
| 156 | 10 : '', # 10 == 'open' in mantis |
|---|
| 157 | 20 : 'fixed', # 20 == 'fixed' |
|---|
| 158 | 30 : '', # 30 == 'reopened' (TODO: 'reopened' needs to be mapped to a status event) |
|---|
| 159 | 40 : 'invalid', # 40 == 'unable to duplicate' |
|---|
| 160 | 50 : 'wontfix', # 50 == 'not fixable' |
|---|
| 161 | 60 : 'duplicate', # 60 == 'duplicate' |
|---|
| 162 | 70 : 'invalid', # 70 == 'not an issue' |
|---|
| 163 | 80 : '', # 80 == 'suspended' |
|---|
| 164 | 90 : 'wontfix', # 90 == 'wont fix' |
|---|
| 165 | } |
|---|
| 166 | |
|---|
| 167 | # Mantis severities (which will also become equivalent Trac severities) |
|---|
| 168 | ##SEVERITY_LIST = (('block', '80'), |
|---|
| 169 | ## ('crash', '70'), |
|---|
| 170 | ## ('major', '60'), |
|---|
| 171 | ## ('minor', '50'), |
|---|
| 172 | ## ('tweak', '40'), |
|---|
| 173 | ## ('text', '30'), |
|---|
| 174 | ## ('trivial', '20'), |
|---|
| 175 | ## ('feature', '10')) |
|---|
| 176 | SEVERITY_LIST = (('block', '1'), |
|---|
| 177 | ('crash', '2'), |
|---|
| 178 | ('major', '3'), |
|---|
| 179 | ('minor', '4'), |
|---|
| 180 | ('tweak', '5'), |
|---|
| 181 | ('text', '6'), |
|---|
| 182 | ('trivial', '7'), |
|---|
| 183 | ('feature', '8')) |
|---|
| 184 | |
|---|
| 185 | # Translate severity numbers into their text equivalents |
|---|
| 186 | SEVERITY_TRANSLATE = { |
|---|
| 187 | 80 : 'block', |
|---|
| 188 | 70 : 'crash', |
|---|
| 189 | 60 : 'major', |
|---|
| 190 | 50 : 'minor', |
|---|
| 191 | 40 : 'tweak', |
|---|
| 192 | 30 : 'text', |
|---|
| 193 | 20 : 'trivial', |
|---|
| 194 | 10 : 'feature' |
|---|
| 195 | } |
|---|
| 196 | |
|---|
| 197 | # Mantis priorities (which will also become Trac priorities) |
|---|
| 198 | ##PRIORITY_LIST = (('immediate', '60'), |
|---|
| 199 | ## ('urgent', '50'), |
|---|
| 200 | ## ('high', '40'), |
|---|
| 201 | ## ('normal', '30'), |
|---|
| 202 | ## ('low', '20'), |
|---|
| 203 | ## ('none', '10')) |
|---|
| 204 | PRIORITY_LIST = (('immediate', '1'), |
|---|
| 205 | ('urgent', '2'), |
|---|
| 206 | ('high', '3'), |
|---|
| 207 | ('normal', '4'), |
|---|
| 208 | ('low', '5'), |
|---|
| 209 | ('none', '6')) |
|---|
| 210 | |
|---|
| 211 | # Translate priority numbers into their text equivalent |
|---|
| 212 | PRIORITY_TRANSLATE = { |
|---|
| 213 | 60 : 'immediate', |
|---|
| 214 | 50 : 'urgent', |
|---|
| 215 | 40 : 'high', |
|---|
| 216 | 30 : 'normal', |
|---|
| 217 | 20 : 'low', |
|---|
| 218 | 10 : 'none' |
|---|
| 219 | } |
|---|
| 220 | |
|---|
| 221 | |
|---|
| 222 | # Some fields in Mantis do not have equivalents in Trac. Changes in |
|---|
| 223 | # fields listed here will not be imported into the ticket change history, |
|---|
| 224 | # otherwise you'd see changes for fields that don't exist in Trac. |
|---|
| 225 | IGNORED_ACTIVITY_FIELDS = ['', 'project_id', 'reproducibility', 'view_state', 'os', 'os_build', 'duplicate_id'] |
|---|
| 226 | |
|---|
| 227 | ### |
|---|
| 228 | ### Script begins here |
|---|
| 229 | ### |
|---|
| 230 | |
|---|
| 231 | import os |
|---|
| 232 | import re |
|---|
| 233 | import sys |
|---|
| 234 | import string |
|---|
| 235 | import StringIO |
|---|
| 236 | |
|---|
| 237 | import MySQLdb |
|---|
| 238 | import MySQLdb.cursors |
|---|
| 239 | from trac.env import Environment |
|---|
| 240 | |
|---|
| 241 | if not hasattr(sys, 'setdefaultencoding'): |
|---|
| 242 | reload(sys) |
|---|
| 243 | |
|---|
| 244 | sys.setdefaultencoding('latin1') |
|---|
| 245 | |
|---|
| 246 | # simulated Attachment class for trac.add |
|---|
| 247 | class Attachment: |
|---|
| 248 | def __init__(self, name, data): |
|---|
| 249 | self.filename = name |
|---|
| 250 | self.file = StringIO.StringIO(data.tostring()) |
|---|
| 251 | |
|---|
| 252 | # simple field translation mapping. if string not in |
|---|
| 253 | # mapping, just return string, otherwise return value |
|---|
| 254 | class FieldTranslator(dict): |
|---|
| 255 | def __getitem__(self, item): |
|---|
| 256 | if not dict.has_key(self, item): |
|---|
| 257 | return item |
|---|
| 258 | |
|---|
| 259 | return dict.__getitem__(self, item) |
|---|
| 260 | |
|---|
| 261 | statusXlator = FieldTranslator(STATUS_TRANSLATE) |
|---|
| 262 | |
|---|
| 263 | class TracDatabase(object): |
|---|
| 264 | def __init__(self, path): |
|---|
| 265 | self.env = Environment(path) |
|---|
| 266 | self._db = self.env.get_db_cnx() |
|---|
| 267 | self._db.autocommit = False |
|---|
| 268 | self.loginNameCache = {} |
|---|
| 269 | self.fieldNameCache = {} |
|---|
| 270 | |
|---|
| 271 | def db(self): |
|---|
| 272 | return self._db |
|---|
| 273 | |
|---|
| 274 | def hasTickets(self): |
|---|
| 275 | c = self.db().cursor() |
|---|
| 276 | c.execute('''SELECT count(*) FROM Ticket''') |
|---|
| 277 | return int(c.fetchall()[0][0]) > 0 |
|---|
| 278 | |
|---|
| 279 | def assertNoTickets(self): |
|---|
| 280 | if self.hasTickets(): |
|---|
| 281 | raise Exception("Will not modify database with existing tickets!") |
|---|
| 282 | |
|---|
| 283 | def setSeverityList(self, s): |
|---|
| 284 | """Remove all severities, set them to `s`""" |
|---|
| 285 | self.assertNoTickets() |
|---|
| 286 | |
|---|
| 287 | c = self.db().cursor() |
|---|
| 288 | c.execute("""DELETE FROM enum WHERE type='severity'""") |
|---|
| 289 | for value, i in s: |
|---|
| 290 | print "inserting severity ", value, " ", i |
|---|
| 291 | c.execute("""INSERT INTO enum (type, name, value) VALUES (%s, %s, %s)""", |
|---|
| 292 | ("severity", value.encode('utf-8'), i,)) |
|---|
| 293 | self.db().commit() |
|---|
| 294 | |
|---|
| 295 | def setPriorityList(self, s): |
|---|
| 296 | """Remove all priorities, set them to `s`""" |
|---|
| 297 | self.assertNoTickets() |
|---|
| 298 | |
|---|
| 299 | c = self.db().cursor() |
|---|
| 300 | c.execute("""DELETE FROM enum WHERE type='priority'""") |
|---|
| 301 | for value, i in s: |
|---|
| 302 | print "inserting priority ", value, " ", i |
|---|
| 303 | c.execute("""INSERT INTO enum (type, name, value) VALUES (%s, %s, %s)""", |
|---|
| 304 | ("priority", value.encode('utf-8'), i,)) |
|---|
| 305 | self.db().commit() |
|---|
| 306 | |
|---|
| 307 | |
|---|
| 308 | def setComponentList(self, l, key): |
|---|
| 309 | """Remove all components, set them to `l`""" |
|---|
| 310 | self.assertNoTickets() |
|---|
| 311 | |
|---|
| 312 | c = self.db().cursor() |
|---|
| 313 | c.execute("""DELETE FROM component""") |
|---|
| 314 | for comp in l: |
|---|
| 315 | print "inserting component '",comp[key],"', owner", comp['owner'] |
|---|
| 316 | c.execute("""INSERT INTO component (name, owner) VALUES (%s, %s)""", |
|---|
| 317 | (comp[key].encode('utf-8'), comp['owner'].encode('utf-8'),)) |
|---|
| 318 | self.db().commit() |
|---|
| 319 | |
|---|
| 320 | def setVersionList(self, v, key): |
|---|
| 321 | """Remove all versions, set them to `v`""" |
|---|
| 322 | self.assertNoTickets() |
|---|
| 323 | |
|---|
| 324 | c = self.db().cursor() |
|---|
| 325 | c.execute("""DELETE FROM version""") |
|---|
| 326 | for vers in v: |
|---|
| 327 | print "inserting version ", vers[key] |
|---|
| 328 | c.execute("""INSERT INTO version (name) VALUES (%s)""", |
|---|
| 329 | (vers[key].encode('utf-8'),)) |
|---|
| 330 | self.db().commit() |
|---|
| 331 | |
|---|
| 332 | def setMilestoneList(self, m, key): |
|---|
| 333 | """Remove all milestones, set them to `m`""" |
|---|
| 334 | self.assertNoTickets() |
|---|
| 335 | |
|---|
| 336 | c = self.db().cursor() |
|---|
| 337 | c.execute("""DELETE FROM milestone""") |
|---|
| 338 | for ms in m: |
|---|
| 339 | print "inserting milestone ", ms[key] |
|---|
| 340 | c.execute("""INSERT INTO milestone (name) VALUES (%s)""", |
|---|
| 341 | (ms[key].encode('utf-8'),)) |
|---|
| 342 | self.db().commit() |
|---|
| 343 | |
|---|
| 344 | def addTicket(self, id, time, changetime, component, |
|---|
| 345 | severity, priority, owner, reporter, cc, |
|---|
| 346 | version, milestone, status, resolution, |
|---|
| 347 | summary, description, keywords): |
|---|
| 348 | c = self.db().cursor() |
|---|
| 349 | |
|---|
| 350 | desc = description.encode('utf-8') |
|---|
| 351 | |
|---|
| 352 | if PREFORMAT_COMMENTS: |
|---|
| 353 | desc = '{{{\n%s\n}}}' % desc |
|---|
| 354 | |
|---|
| 355 | print "inserting ticket %s -- \"%s\"" % (id, summary[0:40].replace("\n", " ")) |
|---|
| 356 | c.execute("""INSERT INTO ticket (id, time, changetime, component, |
|---|
| 357 | severity, priority, owner, reporter, cc, |
|---|
| 358 | version, milestone, status, resolution, |
|---|
| 359 | summary, description, keywords) |
|---|
| 360 | VALUES (%s, %s, %s, %s, |
|---|
| 361 | %s, %s, %s, %s, %s, |
|---|
| 362 | %s, %s, %s, %s, |
|---|
| 363 | %s, %s, %s)""", |
|---|
| 364 | (id, time.strftime('%s'), changetime.strftime('%s'), component.encode('utf-8'), |
|---|
| 365 | severity.encode('utf-8'), priority.encode('utf-8'), owner, reporter, cc, |
|---|
| 366 | version, milestone.encode('utf-8'), status.lower(), resolution, |
|---|
| 367 | summary.encode('utf-8'), desc, keywords,)) |
|---|
| 368 | |
|---|
| 369 | self.db().commit() |
|---|
| 370 | |
|---|
| 371 | c.execute('''SELECT last_insert_rowid()''') |
|---|
| 372 | return c.fetchall()[0][0] |
|---|
| 373 | #return self.db().db.sqlite_last_insert_rowid() |
|---|
| 374 | |
|---|
| 375 | def addTicketComment(self, ticket, time, author, value): |
|---|
| 376 | print " * adding comment \"%s...\"" % value[0:40] |
|---|
| 377 | comment = value.encode('utf-8') |
|---|
| 378 | |
|---|
| 379 | if PREFORMAT_COMMENTS: |
|---|
| 380 | comment = '{{{\n%s\n}}}' % comment |
|---|
| 381 | |
|---|
| 382 | c = self.db().cursor() |
|---|
| 383 | c.execute("""INSERT INTO ticket_change (ticket, time, author, field, oldvalue, newvalue) |
|---|
| 384 | VALUES (%s, %s, %s, %s, %s, %s)""", |
|---|
| 385 | (ticket, time.strftime('%s'), author, 'comment', '', comment,)) |
|---|
| 386 | self.db().commit() |
|---|
| 387 | |
|---|
| 388 | def addTicketChange(self, ticket, time, author, field, oldvalue, newvalue): |
|---|
| 389 | print " * adding ticket change \"%s\": \"%s\" -> \"%s\" (%s)" % (field, oldvalue[0:20], newvalue[0:20], time) |
|---|
| 390 | c = self.db().cursor() |
|---|
| 391 | c.execute("""INSERT INTO ticket_change (ticket, time, author, field, oldvalue, newvalue) |
|---|
| 392 | VALUES (%s, %s, %s, %s, %s, %s)""", |
|---|
| 393 | (ticket, time.strftime('%s'), author, field, oldvalue.encode('utf-8'), newvalue.encode('utf-8'),)) |
|---|
| 394 | self.db().commit() |
|---|
| 395 | # Now actually change the ticket because the ticket wont update itself! |
|---|
| 396 | sql = "UPDATE ticket SET %s='%s' WHERE id=%s" % (field, newvalue, ticket) |
|---|
| 397 | c.execute(sql) |
|---|
| 398 | self.db().commit() |
|---|
| 399 | |
|---|
| 400 | def addAttachment(self, id, attachment, description, author): |
|---|
| 401 | print 'inserting attachment for ticket %s -- %s' % (id, description) |
|---|
| 402 | attachment.filename = attachment.filename.encode('utf-8') |
|---|
| 403 | self.env.create_attachment(self.db(), 'ticket', str(id), attachment, description.encode('utf-8'), |
|---|
| 404 | author, 'unknown') |
|---|
| 405 | |
|---|
| 406 | def getLoginName(self, cursor, userid): |
|---|
| 407 | if userid not in self.loginNameCache: |
|---|
| 408 | cursor.execute("SELECT * FROM mantis_user_table WHERE id = %s" % userid) |
|---|
| 409 | loginName = cursor.fetchall() |
|---|
| 410 | |
|---|
| 411 | if loginName: |
|---|
| 412 | loginName = loginName[0]['username'] |
|---|
| 413 | else: |
|---|
| 414 | print 'warning: unknown mantis userid %d, recording as anonymous' % userid |
|---|
| 415 | loginName = 'anonymous' |
|---|
| 416 | |
|---|
| 417 | self.loginNameCache[userid] = loginName |
|---|
| 418 | |
|---|
| 419 | return self.loginNameCache[userid] |
|---|
| 420 | |
|---|
| 421 | |
|---|
| 422 | def productFilter(fieldName, products): |
|---|
| 423 | first = True |
|---|
| 424 | result = '' |
|---|
| 425 | for product in products: |
|---|
| 426 | if not first: |
|---|
| 427 | result += " or " |
|---|
| 428 | first = False |
|---|
| 429 | result += "%s = '%s'" % (fieldName, product) |
|---|
| 430 | return result |
|---|
| 431 | |
|---|
| 432 | def convert(_db, _host, _user, _password, _env, _force): |
|---|
| 433 | activityFields = FieldTranslator() |
|---|
| 434 | |
|---|
| 435 | # account for older versions of mantis |
|---|
| 436 | if MANTIS_VERSION == '0.19': |
|---|
| 437 | print 'Using Mantis v%s schema.' % MANTIS_VERSION |
|---|
| 438 | activityFields['removed'] = 'oldvalue' |
|---|
| 439 | activityFields['added'] = 'newvalue' |
|---|
| 440 | |
|---|
| 441 | # init Mantis environment |
|---|
| 442 | print "Mantis MySQL('%s':'%s':'%s':'%s'): connecting..." % (_db, _host, _user, _password) |
|---|
| 443 | mysql_con = MySQLdb.connect(host=_host, |
|---|