class ImportProcessor(object): def __init__(self, env, req, filename, tickettime): self.env = env self.req = req self.filename = filename self.modified = {} self.added = {} # TODO: check that the tickets haven't changed since preview self.tickettime = tickettime # Keep the db to commit it all at once at the end self.db = self.env.get_db_cnx() self.missingemptyfields = None self.missingdefaultedfields = None self.computedfields = None self.importedfields = None def start(self, importedfields, reconciliate_by_owner_also, has_comments): # Index by row index, returns ticket id self.crossref = [] self.lowercaseimportedfields = [f.lower() for f in importedfields] def process_missing_fields(self, missingfields, missingemptyfields, missingdefaultedfields, computedfields): self.missingemptyfields = missingemptyfields self.missingdefaultedfields = missingdefaultedfields self.computedfields = computedfields def process_notimported_fields(self, notimportedfields): pass def process_comment_field(self, comment): pass def start_process_row(self, row_idx, ticket_id): from ticket import PatchedTicket if ticket_id > 0: # existing ticket self.ticket = PatchedTicket(self.env, tkt_id=ticket_id, db=self.db) # 'Ticket.time_changed' is a datetime in 0.11, and an int in 0.10. # if we have trac.util.datefmt.to_datetime, we're likely with 0.11 try: from trac.util.datefmt import to_timestamp time_changed = to_timestamp(self.ticket.time_changed) except ImportError: time_changed = int(self.ticket.time_changed) if time_changed > self.tickettime: # just in case, verify if it wouldn't be a ticket that has been modified in the future # (of course, it shouldn't happen... but who know). If it's the case, don't report it as an error if time_changed < int(time.time()): # TODO: this is not working yet... # #raise TracError("Sorry, can not execute the import. " #"The ticket #" + str(ticket_id) + " has been modified by someone else " #"since preview. You must re-upload and preview your file to avoid overwriting the other changes.") pass else: self.ticket = PatchedTicket(self.env, db=self.db) self.comment = '' def process_cell(self, column, cell): cell = unicode(cell) column = column.lower() # if status of new ticket is empty, force to use 'new' if not self.ticket.exists and column == 'status' and not cell: cell = 'new' # this will ensure that the changes are logged, see model.py Ticket.__setitem__ self.ticket[column] = cell def process_comment(self, comment): self.comment = comment def _tickettime(self): try: # 'when' is a datetime in 0.11, and an int in 0.10. # if we have trac.util.datefmt.to_datetime, we're likely with 0.11 from trac.util.datefmt import to_datetime return to_datetime(self.tickettime) except ImportError: return self.tickettime def _save_ticket(self, ticket, with_comment=True): if with_comment: if self.comment: comment = "''Batch update from file " + self.filename + ":'' " + self.comment else: comment = "''Batch update from file " + self.filename + "''" else: comment = None ticket.save_changes(get_reporter_id(self.req), comment, when=self._tickettime(), db=self.db) def end_process_row(self): if self.ticket.id == None: if self.missingemptyfields: for f in self.missingemptyfields: if f in self.ticket.values and self.ticket[f] is None: self.ticket[f] = '' if self.comment: self.ticket['description'] = self.ticket[ 'description'] + "\n[[BR]][[BR]]\n''Batch insert from file " + self.filename + ":''\n" + self.comment if self.computedfields: for f in self.computedfields: if f not in self.lowercaseimportedfields and \ self.computedfields[f] is not None and \ self.computedfields[f]['set']: self.ticket[f] = self.computedfields[f]['value'] self.ticket.insert(when=self._tickettime(), db=self.db) self.added[self.ticket.id] = 1 else: if self.ticket.is_modified() or self.comment: self._save_ticket(self.ticket) self.modified[self.ticket.id] = 1 self.crossref.append(self.ticket.id) self.ticket = None def process_new_lookups(self, newvalues): for field, names in newvalues.iteritems(): if field == 'status': continue LOOKUPS = { 'component': model.Component, 'milestone': model.Milestone, 'version': model.Version, 'type': model.Type, } try: CurrentLookupEnum = LOOKUPS[field] except KeyError: class CurrentLookupEnum(model.AbstractEnum): # here, you shouldn't put 'self.' before the class field. type = field for name in names: lookup = CurrentLookupEnum(self.env, db=self.db) lookup.name = name lookup.insert() def process_new_users(self, newusers): pass # Rows is an array of dictionaries. # Each row is indexed by the field names in relativeticketfields. def process_relativeticket_fields(self, rows, relativeticketfields): from ticket import PatchedTicket # Find the WBS columns, if any. We never expect to have more # than one but this is flexible and easy. There's no good # reason to go to the trouble of ignoring extras. wbsfields = [] for row in rows: for f in relativeticketfields: if row[f].find('.') != -1: if f not in wbsfields: wbsfields.append(f) # If WBS column present, build reverse lookup to find the # ticket ID from a WBS number. wbsref = {} if wbsfields != []: row_idx = 0 for row in rows: wbsref[row[wbsfields[0]]] = self.crossref[row_idx] row_idx += 1 row_idx = 0 for row in rows: id = self.crossref[row_idx] # Get the ticket (added or updated in the main loop ticket = PatchedTicket(self.env, tkt_id=id, db=self.db) for f in relativeticketfields: # Get the value of the relative field column (e.g., "2,3") v = row[f] # If it's not empty, process the contents if len(v) > 0: # Handle WBS numbers if f in wbsfields: if row[f].find('.') == -1: # Top level, no parent ticket[f] = '' else: # Get this task's wbs wbs = row[f] # Remove the last dot-delimited field pwbs = wbs[:wbs.rindex(".")] # Look up the parent's ticket ID ticket[f] = str(wbsref[pwbs]) # Handle dependencies else: s = [] for r in v.split(","): # Make the string value an integer r = int(r) # The relative ticket dependencies are 1-based, # array indices are 0-based. Convert and look up # the new ticket ID of the other ticket. i = self.crossref[r - 1] # TODO check that i != id s.append(str(i)) # Empty or not, use it to update the ticket ticket[f] = ', '.join(s) self._save_ticket(ticket, with_comment=False) row_idx += 1 def end_process(self, numrows): self.db.commit() data = {} data['title'] = 'Import completed' #data['report.title'] = data['title'].lower() notmodifiedcount = numrows - len(self.added) - len(self.modified) message = 'Successfully imported ' + str(numrows) + ' tickets (' + str( len(self.added)) + ' added, ' + str(len( self.modified)) + ' modified, ' + str( notmodifiedcount) + ' unchanged).' data['message'] = Markup( "<style type=\"text/css\">#report-notfound { display:none; }</style>\n" ) + wiki_to_html(message, self.env, self.req) return 'import_preview.html', data, None
class ImportProcessor(object): def __init__(self, env, req, filename, tickettime): self.env = env self.req = req self.filename = filename self.modified = {} self.added = {} # TODO: check that the tickets haven't changed since preview self.tickettime = tickettime # Keep the db to commit it all at once at the end self.db = self.env.get_db_cnx() self.missingemptyfields = None self.missingdefaultedfields = None self.computedfields = None self.importedfields = None def start(self, importedfields, reconciliate_by_owner_also, has_comments): # Index by row index, returns ticket id self.crossref = [] self.lowercaseimportedfields = [f.lower() for f in importedfields] def process_missing_fields(self, missingfields, missingemptyfields, missingdefaultedfields, computedfields): self.missingemptyfields = missingemptyfields self.missingdefaultedfields = missingdefaultedfields self.computedfields = computedfields def process_notimported_fields(self, notimportedfields): pass def process_comment_field(self, comment): pass def start_process_row(self, row_idx, ticket_id): from ticket import PatchedTicket if ticket_id > 0: # existing ticket self.ticket = PatchedTicket(self.env, tkt_id=ticket_id, db=self.db) # 'Ticket.time_changed' is a datetime in 0.11, and an int in 0.10. # if we have trac.util.datefmt.to_datetime, we're likely with 0.11 try: from trac.util.datefmt import to_timestamp time_changed = to_timestamp(self.ticket.time_changed) except ImportError: time_changed = int(self.ticket.time_changed) if time_changed > self.tickettime: # just in case, verify if it wouldn't be a ticket that has been modified in the future # (of course, it shouldn't happen... but who know). If it's the case, don't report it as an error if time_changed < int(time.time()): # TODO: this is not working yet... # #raise TracError("Sorry, can not execute the import. " #"The ticket #" + str(ticket_id) + " has been modified by someone else " #"since preview. You must re-upload and preview your file to avoid overwriting the other changes.") pass else: self.ticket = PatchedTicket(self.env, db=self.db) self.comment = '' def process_cell(self, column, cell): cell = unicode(cell) column = column.lower() # if status of new ticket is empty, force to use 'new' if not self.ticket.exists and column == 'status' and not cell: cell = 'new' # this will ensure that the changes are logged, see model.py Ticket.__setitem__ self.ticket[column] = cell def process_comment(self, comment): self.comment = comment def _tickettime(self): try: # 'when' is a datetime in 0.11, and an int in 0.10. # if we have trac.util.datefmt.to_datetime, we're likely with 0.11 from trac.util.datefmt import to_datetime return to_datetime(self.tickettime) except ImportError: return self.tickettime def _save_ticket(self, ticket, with_comment=True): if with_comment: if self.comment: comment = "''Batch update from file " + self.filename + ":'' " + self.comment else: comment = "''Batch update from file " + self.filename + "''" else: comment=None ticket.save_changes(get_reporter_id(self.req), comment, when=self._tickettime(), db=self.db) def end_process_row(self): if self.ticket.id == None: if self.missingemptyfields: for f in self.missingemptyfields: if f in self.ticket.values and self.ticket[f] is None: self.ticket[f] = '' if self.comment: self.ticket['description'] = self.ticket['description'] + "\n[[BR]][[BR]]\n''Batch insert from file " + self.filename + ":''\n" + self.comment if self.computedfields: for f in self.computedfields: if f not in self.lowercaseimportedfields and \ self.computedfields[f] is not None and \ self.computedfields[f]['set']: self.ticket[f] = self.computedfields[f]['value'] self.ticket.insert(when=self._tickettime(), db=self.db) self.added[self.ticket.id] = 1 else: if self.ticket.is_modified() or self.comment: self._save_ticket(self.ticket) self.modified[self.ticket.id] = 1 self.crossref.append(self.ticket.id) self.ticket = None def process_new_lookups(self, newvalues): for field, names in newvalues.iteritems(): if field == 'status': continue LOOKUPS = { 'component': model.Component, 'milestone': model.Milestone, 'version': model.Version, 'type': model.Type, } try: CurrentLookupEnum = LOOKUPS[field] except KeyError: class CurrentLookupEnum(model.AbstractEnum): # here, you shouldn't put 'self.' before the class field. type = field for name in names: lookup = CurrentLookupEnum(self.env, db=self.db) lookup.name = name lookup.insert() def process_new_users(self, newusers): pass # Rows is an array of dictionaries. # Each row is indexed by the field names in relativeticketfields. def process_relativeticket_fields(self, rows, relativeticketfields): from ticket import PatchedTicket # Find the WBS columns, if any. We never expect to have more # than one but this is flexible and easy. There's no good # reason to go to the trouble of ignoring extras. wbsfields=[] for row in rows: for f in relativeticketfields: if row[f].find('.') != -1: if f not in wbsfields: wbsfields.append(f) # If WBS column present, build reverse lookup to find the # ticket ID from a WBS number. wbsref = {} if wbsfields != []: row_idx = 0 for row in rows: wbsref[row[wbsfields[0]]] = self.crossref[row_idx] row_idx += 1 row_idx = 0 for row in rows: id = self.crossref[row_idx] # Get the ticket (added or updated in the main loop ticket = PatchedTicket(self.env, tkt_id=id, db=self.db) for f in relativeticketfields: # Get the value of the relative field column (e.g., "2,3") v = row[f] # If it's not empty, process the contents if len(v) > 0: # Handle WBS numbers if f in wbsfields: if row[f].find('.') == -1: # Top level, no parent ticket[f] = '' else: # Get this task's wbs wbs = row[f] # Remove the last dot-delimited field pwbs = wbs[:wbs.rindex(".")] # Look up the parent's ticket ID ticket[f] = str(wbsref[pwbs]) # Handle dependencies else: s = [] for r in v.split(","): # Make the string value an integer r = int(r) # The relative ticket dependencies are 1-based, # array indices are 0-based. Convert and look up # the new ticket ID of the other ticket. i = self.crossref[r-1] # TODO check that i != id s.append(str(i)) # Empty or not, use it to update the ticket ticket[f] = ', '.join(s) self._save_ticket(ticket, with_comment=False) row_idx += 1 def end_process(self, numrows): self.db.commit() data = {} data['title'] = 'Import completed' #data['report.title'] = data['title'].lower() notmodifiedcount = numrows - len(self.added) - len(self.modified) message = 'Successfully imported ' + str(numrows) + ' tickets (' + str(len(self.added)) + ' added, ' + str(len(self.modified)) + ' modified, ' + str(notmodifiedcount) + ' unchanged).' data['message'] = Markup("<style type=\"text/css\">#report-notfound { display:none; }</style>\n") + wiki_to_html(message, self.env, self.req) return 'import_preview.html', data, None
class ImportProcessor(object): def __init__(self, env, req, filename, tickettime): self.env = env self.req = req self.filename = filename self.modifiedcount = 0 self.notmodifiedcount = 0 self.added = 0 self.parent_tid = 0 # TODO: check that the tickets haven't changed since preview self.tickettime = tickettime # Keep the db to commit it all at once at the end self.db = self.env.get_db_cnx() self.missingemptyfields = None self.missingdefaultedfields = None self.computedfields = None self.importedfields = None def start(self, importedfields, reconciliate_by_owner_also, has_comments): self.lowercaseimportedfields = [f.lower() for f in importedfields] def process_missing_fields(self, missingfields, missingemptyfields, missingdefaultedfields, computedfields): self.missingemptyfields = missingemptyfields self.missingdefaultedfields = missingdefaultedfields self.computedfields = computedfields def process_notimported_fields(self, notimportedfields): pass def process_comment_field(self, comment): pass def start_process_row(self, row_idx, ticket_id): from ticket import PatchedTicket if ticket_id > 0: # existing ticket self.ticket = PatchedTicket(self.env, tkt_id=ticket_id, db=self.db) # 'Ticket.time_changed' is a datetime in 0.11, and an int in 0.10. # if we have trac.util.datefmt.to_datetime, we're likely with 0.11 try: from trac.util.datefmt import to_timestamp time_changed = to_timestamp(self.ticket.time_changed) except ImportError: time_changed = int(self.ticket.time_changed) if time_changed > self.tickettime: # just in case, verify if it wouldn't be a ticket that has been modified in the future # (of course, it shouldn't happen... but who know). If it's the case, don't report it as an error if time_changed < int(time.time()): # TODO: this is not working yet... # #raise TracError("Sorry, can not execute the import. " #"The ticket #" + str(ticket_id) + " has been modified by someone else " #"since preview. You must re-upload and preview your file to avoid overwriting the other changes.") pass else: self.ticket = PatchedTicket(self.env, db=self.db) self.comment = '' def process_cell(self, column, cell): cell = unicode(cell) # this will ensure that the changes are logged, see model.py Ticket.__setitem__ self.ticket[column.lower()] = cell def process_comment(self, comment): self.comment = comment def end_process_row(self, indent): try: # 'when' is a datetime in 0.11, and an int in 0.10. # if we have trac.util.datefmt.to_datetime, we're likely with 0.11 from trac.util.datefmt import to_datetime tickettime = to_datetime(self.tickettime) except ImportError: tickettime = self.tickettime if self.ticket.id == None: for f in self.missingemptyfields: if self.ticket.values.has_key(f) and self.ticket[f] == None: self.ticket[f] = '' if self.comment: self.ticket['description'] = self.ticket['description'] + "\n[[BR]][[BR]]\n''Batch insert from file " + self.filename + ":''\n" + self.comment for f in self.computedfields: if f not in self.lowercaseimportedfields and self.computedfields[f] != None and self.computedfields[f]['set']: self.ticket[f] = self.computedfields[f]['value'] if (indent!=0) and (self.parent_tid!=0) and ('parents' in self.env.config['ticket-custom']): self.ticket['parents'] = str(self.parent_tid) self.added += 1 self.ticket.insert(when=tickettime, db=self.db) if indent==0: self.parent_tid = self.ticket.id else: if self.comment: message = "''Batch update from file " + self.filename + ":'' " + self.comment else: message = "''Batch update from file " + self.filename + "''" if self.ticket.is_modified() or self.comment: self.modifiedcount += 1 self.ticket.save_changes(get_reporter_id(self.req), message, when=tickettime, db=self.db) # TODO: handle cnum, cnum = ticket.values['cnum'] + 1) else: self.notmodifiedcount += 1 self.ticket = None def process_new_lookups(self, newvalues): for field, names in newvalues.iteritems(): if field == 'status': continue if field == 'component': class CurrentLookupEnum(model.Component): pass elif field == 'milestone': class CurrentLookupEnum(model.Milestone): pass elif field == 'version': class CurrentLookupEnum(model.Version): pass elif field == 'type': class CurrentLookupEnum(model.Type): pass else: class CurrentLookupEnum(model.AbstractEnum): # here, you shouldn't put 'self.' before the class field. type = field for name in names: lookup = CurrentLookupEnum(self.env, db=self.db) lookup.name = name lookup.insert() def process_new_users(self, newusers): pass def end_process(self, numrows): self.db.commit() data = {} data['title'] = 'Import completed' #data['report.title'] = data['title'].lower() message = 'インポートに成功しました。 ' + str(numrows) + ' tickets (' + str(self.added) + ' 追加, ' + str(self.modifiedcount) + ' 更新, ' + str(self.notmodifiedcount) + ' 未更新).' data['message'] = Markup("<style type=\"text/css\">#report-notfound { display:none; }</style>\n") + wiki_to_html(message, self.env, self.req) return 'import_preview.html', data, None
class ImportProcessor(object): def __init__(self, env, req, filename, tickettime): self.env = env self.req = req self.filename = filename self.modifiedcount = 0 self.notmodifiedcount = 0 self.added = 0 # TODO: check that the tickets haven't changed since preview self.tickettime = tickettime # Keep the db to commit it all at once at the end self.db = self.env.get_db_cnx() self.missingemptyfields = None self.missingdefaultedfields = None self.computedfields = None def start(self, importedfields, reconciliate_by_owner_also): pass def process_missing_fields(self, missingfields, missingemptyfields, missingdefaultedfields, computedfields): self.missingemptyfields = missingemptyfields self.missingdefaultedfields = missingdefaultedfields self.computedfields = computedfields def process_notimported_fields(self, notimportedfields): pass def start_process_row(self, row_idx, ticket_id): from ticket import PatchedTicket if ticket_id > 0: # existing ticket self.ticket = PatchedTicket(self.env, tkt_id=ticket_id, db=self.db) # 'Ticket.time_changed' is a datetime in 0.11, and an int in 0.10. # if we have trac.util.datefmt.to_datetime, we're likely with 0.11 try: from trac.util.datefmt import to_timestamp time_changed = to_timestamp(self.ticket.time_changed) except ImportError: time_changed = int(self.ticket.time_changed) if time_changed > self.tickettime: # just in case, verify if it wouldn't be a ticket that has been modified in the future # (of course, it shouldn't happen... but who know). If it's the case, don't report it as an error if time_changed < int(time.time()): # TODO: this is not working yet... # #raise TracError("Sorry, can not execute the import. " #"The ticket #" + str(ticket_id) + " has been modified by someone else " #"since preview. You must re-upload and preview your file to avoid overwriting the other changes.") pass else: self.ticket = PatchedTicket(self.env, db=self.db) def process_cell(self, column, cell): cell = unicode(cell) # this will ensure that the changes are logged, see model.py Ticket.__setitem__ self.ticket[column.lower()] = cell def end_process_row(self): try: # 'when' is a datetime in 0.11, and an int in 0.10. # if we have trac.util.datefmt.to_datetime, we're likely with 0.11 from trac.util.datefmt import to_datetime tickettime = to_datetime(self.tickettime) except ImportError: tickettime = self.tickettime if self.ticket.id == None: for f in self.missingemptyfields: if self.ticket.values.has_key(f) and self.ticket[f] == None: self.ticket[f] = '' for f in self.computedfields: if self.computedfields[f] != None and self.computedfields[f][ 'set']: self.ticket[f] = self.computedfields[f]['value'] self.added += 1 self.ticket.insert(when=tickettime, db=self.db) else: message = "Batch update from file " + self.filename if self.ticket.is_modified(): self.modifiedcount += 1 self.ticket.save_changes( get_reporter_id(self.req), message, when=tickettime, db=self.db ) # TODO: handle cnum, cnum = ticket.values['cnum'] + 1) else: self.notmodifiedcount += 1 self.ticket = None def process_new_lookups(self, newvalues): for field, names in newvalues.iteritems(): if names == []: continue if field == 'component': class CurrentLookupEnum(model.Component): pass elif field == 'milestone': class CurrentLookupEnum(model.Milestone): pass elif field == 'version': class CurrentLookupEnum(model.Version): pass else: class CurrentLookupEnum(model.AbstractEnum): # here, you shouldn't put 'self.' before the class field. type = field for name in names: lookup = CurrentLookupEnum(self.env, db=self.db) lookup.name = name lookup.insert() def process_new_users(self, newusers): pass def end_process(self, numrows): self.db.commit() self.req.hdf['title'] = 'Import completed' self.req.hdf['report.title'] = self.req.hdf['title'].lower() message = 'Successfully imported ' + str(numrows) + ' tickets (' + str( self.added) + ' added, ' + str( self.modifiedcount) + ' modified, ' + str( self.notmodifiedcount) + ' unchanged).' self.req.hdf['report.description'] = Markup( "<style type=\"text/css\">#report-notfound { display:none; }</style>\n" ) + wiki_to_html(message, self.env, self.req) self.req.hdf['report.numrows'] = 0 self.req.hdf['report.mode'] = 'list' return 'report.cs', None