🔍 Code Extractor

function create_document_v2

Maturity: 72

Creates a new version of a controlled document by generating version metadata, storing the file in FileCloud, updating the document's revision number, and creating an audit trail entry.

File:
/tf/active/vicechatdev/CDocs/controllers/document_controller.py
Lines:
678 - 887
Complexity:
complex

Purpose

This function manages the complete lifecycle of creating a new document version in a document management system. It handles version numbering (incrementing minor version), uploads files to FileCloud storage, creates Neo4j database nodes and relationships, manages document permissions, and logs audit events. It supports both custom and standard folder structures for file organization.

Source Code

def create_document_version(
    user: DocUser,
    document_uid: str,
    file_content: bytes,
    file_name: str,
    comment: str = ""
) -> Dict[str, Any]:
    """
    Create a new version of a document, storing file in FileCloud.
    
    Args:
        user: User creating the version
        document_uid: UID of the document
        file_content: Binary content of the file
        file_name: Name of the file
        comment: Comment for this version
        
    Returns:
        Dictionary with version information
    """
    logger.info(f"Creating new version for document {document_uid}")
    
    try:
        # Check if document exists
        from CDocs.models.document import ControlledDocument
        logger.info(f"Loading document {document_uid}")
        document = ControlledDocument(uid=document_uid)
        if not document or not document.title:  # Check if document actually loaded
            logger.error(f"Document {document_uid} not found or failed to load")
            raise ResourceNotFoundError(f"Document {document_uid} not found")
        
        logger.info(f"Document loaded: {document.title}")
            
        # Extract file extension
        file_ext = file_name.split('.')[-1].lower() if '.' in file_name else ''
        logger.info(f"File extension: {file_ext}")
        
        # Process file content if needed (extract text, etc.)
        extracted_text = ""
        
        # Generate next version number
        current_version = document.current_version
        current_revision = current_version.version_number if current_version else '0.0'
        logger.info(f"Current revision: {current_revision}")
        try:
            major, minor = current_revision.split('.')
            next_revision = f"{major}.{int(minor) + 1}"
        except (ValueError, AttributeError):
            next_revision = "0.1"  # Default if parsing fails
        
        logger.info(f"Next revision: {next_revision}")
            
        # Create version UID
        version_uid = str(uuid.uuid4())
        logger.info(f"Version UID: {version_uid}")
        
        # Determine file path based on custom path or standard structure
        # Check if document has a custom path
        custom_path = getattr(document, 'custom_path', None)
        
        if custom_path:
            # Use custom path structure
            document_folder = f"{custom_path}/{document.doc_number}"
            filecloudpath = f"{document_folder}/{document.doc_number}_v{next_revision}.{file_ext}"
        else:
            # Use standard folder structure
            department_folder = f"/{settings.FILECLOUD_ROOT_FOLDER}/{document.department}"
            doc_type_folder = f"{department_folder}/{document.doc_type}"
            document_folder = f"{doc_type_folder}/{document.doc_number}"
            filecloudpath = f"{document_folder}/{document.doc_number}_v{next_revision}.{file_ext}"
        
        logger.info(f"FileCloud path: {filecloudpath}")
        
        # Create version properties
        version_properties = {
            'UID': version_uid,
            'created_by_uid': user.uid,
            'created_by_name': user.username if hasattr(user, 'username') else 'Unknown',
            'created_date': datetime.now().isoformat(),
            'version_number': next_revision,
            'file_name': file_name,
            'file_size': len(file_content) if file_content else 0,
            'file_type': file_ext,
            'comment': comment,
            'fileCloudPath': filecloudpath,
            'storage_type': 'FILECLOUD'  # Indicate storage type
        }
        
        # Add status 
        version_properties['status'] = 'ACTIVE'
        
        # Sanitize properties before creating node
        from CDocs.db.db_operations import _sanitize_properties
        sanitized_properties = _sanitize_properties(version_properties)
        
        # Create version node in Neo4j
        version_node = db.create_node(
            NodeLabels.DOCUMENT_VERSION,
            sanitized_properties
        )
        
        if not version_node:
            logger.error("Failed to create version node")
            return None
            
        # Create relationship between document and version
        relation_created = db.create_relationship(
            document_uid, 
            version_uid,
            RelTypes.HAS_VERSION
        )
        
        if not relation_created:
            logger.error("Failed to create relationship between document and version")
            return None
            
        # Update document revision
        doc_update_result = db.run_query(
            """
            MATCH (d:ControlledDocument {UID: $doc_uid})
            SET d.revision = $revision, 
                d.modifiedDate = $modified_date
            RETURN d
            """,
            {
                "doc_uid": document_uid,
                "revision": next_revision,
                "modified_date": datetime.now().isoformat()
            }
        )
        
        # Set as current version if update successful
        if doc_update_result:
            # Don't use direct property setting - use the document model method
            document_obj = ControlledDocument(uid=document_uid)
            document_obj.set_current_version(version_uid)
            
        # Now upload the file to FileCloud using the proper controller function
        if file_content:
            try:
                logger.info(f"Uploading file to FileCloud using filecloud_controller")
                
                # Create document object for FileCloud upload
                doc_obj = ControlledDocument(uid=document_uid)
                
                # Upload to FileCloud using the proper controller function
                filecloud_result = upload_document_to_filecloud(
                    user=user,
                    document=doc_obj,
                    file_content=file_content,
                    version_comment=comment,
                    metadata=None
                )
                
                if filecloud_result and filecloud_result.get('success'):
                    logger.info(f"Successfully uploaded to FileCloud")
                    
                    # Update sharing permissions for document
                    try:
                        from CDocs.controllers.share_controller import manage_document_permissions
                        permission_result = manage_document_permissions(doc_obj)
                        logger.info(f"Document permissions updated: {permission_result}")
                    except Exception as perm_error:
                        logger.warning(f"Error updating document permissions: {perm_error}")
                        
                else:
                    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, just log the error
                    
            except Exception as fc_error:
                logger.error(f"Error uploading to FileCloud: {fc_error}")
                # Don't fail the entire operation, just log the error
        else:
            logger.warning("No file content provided for FileCloud upload")
            
            
        # Create audit trail entry
        try:
        # Use the standard audit_trail.log_document_lifecycle_event function
            audit_trail.log_document_lifecycle_event(
                event_type="VERSION_CREATED",
                user=user,
                document_uid=document_uid,
                details={
                    "version_number": next_revision,
                    "comment": comment,
                    "file_name": file_name,
                    "file_size": len(file_content) if file_content else 0,
                    "file_type": file_ext
                }
                )
        except Exception as audit_err:
            logger.warning(f"Error creating audit entry: {audit_err}")
            
        # Ensure folder structure exists before returning
        # try:
        #     from CDocs.controllers.filecloud_controller import ensure_document_folders
        #     ensure_document_folders(document_uid)
        # except Exception as folder_err:
        #     logger.warning(f"Warning: Could not ensure document folders: {folder_err}")
        
        # Return version properties
        return version_properties
        
    except Exception as e:
        logger.error(f"Error creating document version: {e}")
        import traceback
        logger.error(traceback.format_exc())
        raise BusinessRuleError(f"Failed to create document version: {e}")

Parameters

Name Type Default Kind
user DocUser - positional_or_keyword
document_uid str - positional_or_keyword
file_content bytes - positional_or_keyword
file_name str - positional_or_keyword
comment str '' positional_or_keyword

Parameter Details

user: DocUser object representing the authenticated user creating the version. Must have CREATE_VERSION permission. Used for audit trails and permission management.

document_uid: String UUID identifying the existing controlled document. Must correspond to an existing ControlledDocument in the Neo4j database.

file_content: Binary data (bytes) containing the actual file content to be stored. Can be empty/None but will skip FileCloud upload if not provided.

file_name: String name of the file including extension (e.g., 'document.pdf'). Used to extract file type and construct storage paths.

comment: Optional string comment describing this version. Defaults to empty string. Stored in version metadata and audit trail.

Return Value

Type: Dict[str, Any]

Returns a dictionary containing version information with keys: 'UID' (version UUID), 'created_by_uid', 'created_by_name', 'created_date' (ISO format), 'version_number' (e.g., '1.2'), 'file_name', 'file_size' (bytes), 'file_type' (extension), 'comment', 'fileCloudPath' (storage path), 'storage_type' ('FILECLOUD'), and 'status' ('ACTIVE'). Returns None if version node or relationship creation fails. Raises BusinessRuleError on exceptions.

Dependencies

  • logging
  • uuid
  • datetime
  • CDocs.db
  • CDocs.config.settings
  • CDocs.models.document.ControlledDocument
  • CDocs.models.user_extensions.DocUser
  • CDocs.utils.audit_trail
  • CDocs.db.schema_manager.NodeLabels
  • CDocs.db.schema_manager.RelTypes
  • CDocs.db.db_operations._sanitize_properties
  • CDocs.controllers.filecloud_controller.upload_document_to_filecloud
  • CDocs.controllers.share_controller.manage_document_permissions
  • CDocs.controllers.ResourceNotFoundError
  • CDocs.controllers.BusinessRuleError
  • traceback

Required Imports

import logging
import uuid
from datetime import datetime
from typing import Dict, Any
from CDocs import db
from CDocs.config import settings
from CDocs.models.document import ControlledDocument
from CDocs.models.user_extensions import DocUser
from CDocs.utils import audit_trail
from CDocs.db.schema_manager import NodeLabels, RelTypes
from CDocs.db.db_operations import _sanitize_properties
from CDocs.controllers.filecloud_controller import upload_document_to_filecloud
from CDocs.controllers import ResourceNotFoundError, BusinessRuleError

Conditional/Optional Imports

These imports are only needed under specific conditions:

from CDocs.controllers.share_controller import manage_document_permissions

Condition: only if file_content is provided and FileCloud upload succeeds - used to update document sharing permissions

Optional
import traceback

Condition: used for detailed error logging when exceptions occur

Required (conditional)

Usage Example

from CDocs.models.user_extensions import DocUser
from CDocs.controllers.document_controller import create_document_version

# Assume user and document_uid are already available
user = DocUser(uid='user-123')
document_uid = 'doc-456'

# Read file content
with open('updated_document.pdf', 'rb') as f:
    file_content = f.read()

# Create new version
version_info = create_document_version(
    user=user,
    document_uid=document_uid,
    file_content=file_content,
    file_name='updated_document.pdf',
    comment='Updated section 3 with new compliance requirements'
)

if version_info:
    print(f"Created version {version_info['version_number']}")
    print(f"Version UID: {version_info['UID']}")
    print(f"FileCloud path: {version_info['fileCloudPath']}")
else:
    print('Failed to create version')

Best Practices

  • Ensure the document exists before calling this function - it will raise ResourceNotFoundError if document_uid is invalid
  • The function requires CREATE_VERSION permission enforced by decorator - ensure user has proper permissions
  • File content can be None/empty but FileCloud upload will be skipped - version metadata will still be created
  • Version numbering follows major.minor format (e.g., 1.2) and automatically increments minor version
  • The function is wrapped in a transaction decorator - database changes will be rolled back on failure
  • FileCloud upload failures are logged but don't fail the entire operation - check logs for upload issues
  • Document permissions are automatically updated after successful FileCloud upload
  • Audit trail entries are created automatically - failures are logged as warnings but don't stop execution
  • Custom paths take precedence over standard folder structure (department/doc_type/doc_number)
  • File extensions are extracted from file_name and used for storage path construction
  • The function sanitizes properties before creating Neo4j nodes to prevent injection issues
  • Always handle BusinessRuleError exceptions when calling this function
  • The returned dictionary contains all version metadata needed for UI display and further operations

Similar Components

AI-powered semantic similarity - components with related functionality:

  • function create_document_v1 95.9% similar

    Creates a new version of an existing document in a document management system, storing the file in FileCloud and tracking version metadata in Neo4j graph database.

    From: /tf/active/vicechatdev/document_controller_backup.py
  • function upload_document_to_filecloud 81.3% similar

    Uploads a document version to FileCloud storage system with metadata, handling file creation, folder structure, and audit logging.

    From: /tf/active/vicechatdev/CDocs/controllers/filecloud_controller.py
  • function download_document_version 75.4% similar

    Downloads a specific version of a controlled document from FileCloud storage, with optional audit trail and watermark inclusion, and logs the download event.

    From: /tf/active/vicechatdev/document_controller_backup.py
  • function download_document_version_v1 75.1% similar

    Downloads a specific version of a controlled document, with optional audit trail and watermark inclusion, returning file content and metadata.

    From: /tf/active/vicechatdev/CDocs/controllers/document_controller.py
  • function get_document_metadata_from_filecloud 73.2% similar

    Retrieves metadata for a specific document (and optionally a specific version) from FileCloud storage system.

    From: /tf/active/vicechatdev/CDocs/controllers/filecloud_controller.py
← Back to Browse