function clone_document
Clones an existing controlled document, creating a new document with optional content copying, custom properties, and FileCloud integration.
/tf/active/vicechatdev/CDocs/controllers/document_controller.py
2670 - 2873
complex
Purpose
This function creates a duplicate of an existing controlled document in a document management system. It handles the complete cloning workflow including: retrieving the source document, creating a new document with inherited or custom properties, optionally copying document content and versions, uploading to FileCloud storage, ensuring proper folder structure, and logging audit trail events. The function supports customization of title, document type, department, and additional properties, while maintaining data integrity through transaction management and comprehensive error handling.
Source Code
def clone_document(
user: DocUser,
document_uid: str,
new_title: Optional[str] = None,
doc_type: Optional[str] = None,
department: Optional[str] = None,
include_content: bool = True,
clone_as_new_revision: bool = False,
additional_properties: Optional[Dict[str, Any]] = None,
custom_doc_number: Optional[str] = None
) -> Dict[str, Any]:
"""
Clone an existing document as a new document or new revision.
Args:
user: User cloning the document
document_uid: UID of the source document
new_title: Title for the cloned document (defaults to "Copy of [original]")
doc_type: Document type code for the new document (defaults to same as original)
department: Department code for the new document (defaults to same as original)
include_content: Whether to include the content of the original document
clone_as_new_revision: Whether to create as a new revision of the same document
additional_properties: Optional additional properties for the new document (e.g., custom_path)
custom_doc_number: Optional custom document number
Returns:
Dictionary with cloned document details
"""
try:
# Get the source document
source_document = ControlledDocument(uid=document_uid)
if not source_document:
raise ResourceNotFoundError(f"Document {document_uid} not found")
# Get current version
current_version = source_document.current_version
if not current_version and include_content:
raise BusinessRuleError("Document has no current version to clone")
# Use original title if none provided
if not new_title:
new_title = f"Copy of {source_document.title}"
# Use original document type and department if none provided
if not doc_type:
doc_type = source_document.doc_type
if not department:
department = source_document.department
# Prepare properties for the new document
properties = {
"description": f"Cloned from {source_document.doc_number}: {source_document.title}",
"status": "DRAFT",
"revision": "0.0" # Start with 0.1 for cloned document
}
# Check if source document has a custom path, and if so, preserve it in the clone
# unless a different one is explicitly provided
if hasattr(source_document, 'custom_path') and source_document.custom_path and not (additional_properties and 'custom_path' in additional_properties):
properties['custom_path'] = source_document.custom_path
# Add any additional properties
if additional_properties:
properties.update(additional_properties)
# Create new document with the merged properties
new_document = ControlledDocument.create(
title=new_title,
doc_type=doc_type,
department=department,
owner=user,
doc_number=custom_doc_number,
properties=properties
)
if not new_document:
raise BusinessRuleError("Failed to create new document")
# Copy content if requested
version_result = None
if include_content and current_version:
# Download content from source document
content_result = download_document_version(
user=user,
document_uid=document_uid,
version_uid=current_version.uid if current_version else None
)
if not content_result or 'content' not in content_result:
# Clean up the new document if content download fails
try:
db.delete_node(new_document.uid, cascade=True)
except:
pass
raise BusinessRuleError("Failed to download source document content")
# Extract file content and metadata
file_content = content_result['content']
file_name = content_result.get('file_name', f"{new_document.doc_number}_v0.1.docx")
# Create initial version for new document
version_result = create_document_version(
user=user,
document_uid=new_document.uid,
file_content=file_content,
file_name=file_name,
comment="Initial version created from cloned document"
)
if not version_result or 'UID' not in version_result:
# If version creation fails, delete the new document to clean up
try:
db.delete_node(new_document.uid, cascade=True)
except:
pass
raise BusinessRuleError("Failed to create document version")
# Upload the file to FileCloud - THIS WAS MISSING
try:
from CDocs.controllers.filecloud_controller import upload_document_to_filecloud
# Set document metadata for storage
metadata = {
"doc_uid": new_document.uid,
"doc_number": new_document.doc_number,
"version_uid": version_result.get('UID'),
"version_number": "0.1", # Initial version
"title": new_title,
"status": "DRAFT"
}
# Upload to FileCloud
filecloud_result = upload_document_to_filecloud(
user=user,
document=new_document,
file_content=file_content,
version_comment="Initial document version from clone",
metadata=metadata
)
if not filecloud_result or not filecloud_result.get('success'):
error_msg = filecloud_result.get('message', 'Unknown error') if filecloud_result else 'Failed to upload to FileCloud'
logger.error(f"Failed to upload to FileCloud: {error_msg}")
# Don't fail the entire operation, but log the warning
logger.warning(f"Document {new_document.doc_number} cloned but FileCloud upload failed: {error_msg}")
except Exception as fc_error:
logger.error(f"Error uploading to FileCloud during clone: {fc_error}")
# Don't fail the entire operation, but log the warning
logger.warning(f"Document {new_document.doc_number} cloned but FileCloud upload failed: {str(fc_error)}")
# Ensure folders are created in FileCloud
try:
from CDocs.controllers.filecloud_controller import ensure_document_folders
ensure_document_folders(new_document)
except Exception as folder_err:
logger.warning(f"Warning: Could not ensure document folders: {folder_err}")
# Log cloning event with custom path info if applicable
event_details = {
"source_document_uid": document_uid,
"source_document_number": source_document.doc_number,
"include_content": include_content
}
# Add custom path information to the audit trail if present
if properties.get('custom_path'):
event_details["custom_path"] = properties['custom_path']
audit_trail.log_document_lifecycle_event(
event_type="DOCUMENT_CLONED",
user=user,
document_uid=new_document.uid,
details=event_details
)
# Return success with new document details
return {
"success": True,
"message": "Document cloned successfully",
"document_uid": new_document.uid,
"document_number": new_document.doc_number,
"document": {
"uid": new_document.uid,
"doc_number": new_document.doc_number,
"title": new_document.title,
"version_uid": version_result.get('UID') if version_result else None
}
}
except (ResourceNotFoundError, ValidationError, PermissionError, BusinessRuleError) as e:
logger.error(f"Error cloning document: {str(e)}")
return {
"success": False,
"message": str(e)
}
except Exception as e:
logger.error(f"Unexpected error cloning document: {str(e)}")
logger.error(traceback.format_exc())
return {
"success": False,
"message": f"An unexpected error occurred: {str(e)}"
}
Parameters
| Name | Type | Default | Kind |
|---|---|---|---|
user |
DocUser | - | positional_or_keyword |
document_uid |
str | - | positional_or_keyword |
new_title |
Optional[str] | None | positional_or_keyword |
doc_type |
Optional[str] | None | positional_or_keyword |
department |
Optional[str] | None | positional_or_keyword |
include_content |
bool | True | positional_or_keyword |
clone_as_new_revision |
bool | False | positional_or_keyword |
additional_properties |
Optional[Dict[str, Any]] | None | positional_or_keyword |
custom_doc_number |
Optional[str] | None | positional_or_keyword |
Parameter Details
user: DocUser object representing the authenticated user performing the clone operation. Must have CREATE_DOCUMENT permission.
document_uid: Unique identifier (UID) string of the source document to be cloned. Must reference an existing document.
new_title: Optional string for the cloned document's title. If None, defaults to 'Copy of [original title]'.
doc_type: Optional document type code string for the new document. If None, inherits from the source document.
department: Optional department code string for the new document. If None, inherits from the source document.
include_content: Boolean flag (default True) indicating whether to copy the actual file content from the source document's current version.
clone_as_new_revision: Boolean flag (default False) for creating the clone as a new revision of the same document. Note: This parameter is defined but not currently implemented in the function logic.
additional_properties: Optional dictionary of custom properties to add to the new document (e.g., {'custom_path': '/some/path', 'metadata_key': 'value'}).
custom_doc_number: Optional string to specify a custom document number instead of auto-generating one.
Return Value
Type: Dict[str, Any]
Returns a dictionary with keys: 'success' (bool indicating operation success), 'message' (str with status/error message), 'document_uid' (str UID of cloned document if successful), 'document_number' (str document number if successful), and 'document' (dict with 'uid', 'doc_number', 'title', 'version_uid' if successful). On failure, only 'success' (False) and 'message' (error description) are returned.
Dependencies
logginguuidostempfiletypingdatetimeiopanelshutiltracebackjsonrerandomCDocsCDocs.dbCDocs.configCDocs.modelsCDocs.utilsCDocs.controllersdocument_auditor
Required Imports
import logging
import traceback
from typing import Dict, Any, Optional
from CDocs import db
from CDocs.models.document import ControlledDocument
from CDocs.models.user_extensions import DocUser
from CDocs.utils import audit_trail
from CDocs.controllers import require_permission, log_controller_action, transaction
from CDocs.controllers import ResourceNotFoundError, ValidationError, PermissionError, BusinessRuleError
Conditional/Optional Imports
These imports are only needed under specific conditions:
from CDocs.controllers.filecloud_controller import upload_document_to_filecloud
Condition: only when include_content=True and content needs to be uploaded to FileCloud
Required (conditional)from CDocs.controllers.filecloud_controller import ensure_document_folders
Condition: only when include_content=True to ensure FileCloud folder structure exists
Required (conditional)from CDocs.controllers.document_controller import download_document_version, create_document_version
Condition: only when include_content=True to download source content and create new version
Required (conditional)Usage Example
from CDocs.models.user_extensions import DocUser
from CDocs.controllers.document_controller import clone_document
# Get authenticated user
user = DocUser.get_by_username('john.doe')
# Clone document with default settings (copies content)
result = clone_document(
user=user,
document_uid='doc-12345-abcde'
)
if result['success']:
print(f"Document cloned: {result['document_number']}")
print(f"New UID: {result['document_uid']}")
else:
print(f"Clone failed: {result['message']}")
# Clone with custom properties
result = clone_document(
user=user,
document_uid='doc-12345-abcde',
new_title='Updated Procedure Manual',
doc_type='PROCEDURE',
department='ENGINEERING',
include_content=True,
additional_properties={'custom_path': '/engineering/procedures'},
custom_doc_number='ENG-PROC-001'
)
# Clone without content (metadata only)
result = clone_document(
user=user,
document_uid='doc-12345-abcde',
new_title='Template Copy',
include_content=False
)
Best Practices
- Always check the 'success' field in the returned dictionary before accessing other fields
- Ensure the user has CREATE_DOCUMENT permission before calling this function (enforced by decorator)
- The function is wrapped in a transaction, so failures will rollback database changes automatically
- When include_content=True, ensure sufficient storage space is available in FileCloud
- The clone_as_new_revision parameter is currently not implemented; use include_content and additional_properties instead
- FileCloud upload failures are logged but don't fail the entire operation; check logs for warnings
- Custom paths from source documents are preserved unless explicitly overridden in additional_properties
- The cloned document always starts with status='DRAFT' and revision='0.0'
- Audit trail events are automatically logged with source document information
- If content download or version creation fails, the function attempts to clean up the partially created document
- Use custom_doc_number carefully to avoid conflicts with existing document numbers
Tags
Similar Components
AI-powered semantic similarity - components with related functionality:
-
function clone_document_v1 91.8% similar
-
function create_document_version_v3 69.2% similar
-
function create_document_version_v4 66.7% similar
-
function create_document_version_v2 66.6% similar
-
function create_document_version_v1 64.8% similar