最终解决方案,包括 Mark W 复制附件的代码
def getDataCopy( data ):
""" Given a piece of data, figure out how to copy it. If it's a native python type
like a string or numeric, just return the value. If it's a rally object, return
the ref to it. If it's a list, iterate and call ourself recursively for the
list members. """
if isinstance( data, types.ListType ):
copyData = []
for entry in data:
copyData.append( getDataCopy(entry) )
elif hasattr( data, "ref" ):
copyData = { "_ref": data.ref }
else:
copyData = data
return copyData
def getArtifactCopy( artifact ):
""" Build a dictionary based on the values in the specified artifact. This dictionary
can then be passed to a rallyConn.put() call to actually create the new entry in
Rally. Attachments and Tasks must be copied seperately, since they require creation
of additional artifacts """
newArtifact = {}
for attrName in artifact.attributes():
# Skip the attributes that we can't or shouldn't handle directly
if attrName.startswith("_") or attrName == "oid" or attrName == "Iteration" or attrName == "Attachments":
continue
attrValue = getattr( artifact, attrName )
newArtifact[attrName] = getDataCopy( attrValue )
if getattr( artifact, "Iteration", None ) != None:
newArtifact["Iteration"] = { "_ref": "iteration/" + artifact.Iteration.oid }
return newArtifact
def copyAttachments( rallyConn, oldArtifact, newArtifact ):
""" For each attachment in the old artifact, create new attachments and attach them to the new artifact"""
# Copy Attachments
source_attachments = rallyConn.getAttachments(oldArtifact)
for source_attachment in source_attachments:
# First copy the content
source_attachment_content = source_attachment.Content
target_attachment_content_fields = { "Content": base64.encodestring(source_attachment_content) }
try:
target_attachment_content = rallyConn.put( 'AttachmentContent', target_attachment_content_fields )
print "\t===> Copied AttachmentContent: %s" % target_attachment_content.ref
except pyral.RallyRESTAPIError, details:
sys.stderr.write('ERROR: %s \n' % details)
sys.exit(2)
# Next copy the attachment object
target_attachment_fields = {
"Name": source_attachment.Name,
"Description": source_attachment.Description,
"Content": target_attachment_content.ref,
"ContentType": source_attachment.ContentType,
"Size": source_attachment.Size,
"User": source_attachment.User.ref
}
# Attach it to the new artifact
target_attachment_fields["Artifact"] = newArtifact.ref
try:
target_attachment = rallyConn.put( source_attachment._type, target_attachment_fields)
print "\t===> Copied Attachment: '%s'" % target_attachment.Name
except pyral.RallyRESTAPIError, details:
sys.stderr.write('ERROR: %s \n' % details)
sys.exit(2)
def copyTasks( rallyConn, oldArtifact, newArtifact ):
""" Iterate over the old artifacts tasks and create new ones, attaching them to the new artifact """
for currentTask in oldArtifact.Tasks:
newTask = getArtifactCopy( currentTask )
# Assign the new task to the new artifact
newTask["WorkProduct"] = newArtifact.ref
# Push the new task into rally
newTaskObj = rallyConn.put( currentTask._type, newTask )
# Copy any attachments the task had
copyAttachments( rallyConn, currentTask, newTaskObj )
def copyDefect( rallyConn, currentDefect, addlUpdates = {} ):
""" Copy a defect including its attachments and tasks. Add the new defect as a
duplicate to the original """
newArtifact = getArtifactCopy( currentDefect )
# Add the current defect as a duplicate for the new one
newArtifact["Duplicates"].append( { "_ref": currentDefect.ref } )
# Copy in any updates that might be needed
for (attrName, attrValue) in addlUpdates.items():
newArtifact[attrName] = attrValue
print "Copying %s: %s..." % (currentDefect.Project.Name, currentDefect.FormattedID),
newDefect = rallyConn.create( currentDefect._type, newArtifact )
print "done, new item", newDefect.FormattedID
print "\tCopying attachments"
copyAttachments( rallyConn, currentDefect, newDefect )
print "\tCopying tasks"
copyTasks( rallyConn, currentDefect, newDefect )