🔍 Code Extractor

class DocumentAccessControls

Maturity: 50

A Panel-based UI component that manages document access controls, providing view and edit buttons with role-based permissions and status indicators.

File:
/tf/active/vicechatdev/CDocs/ui/components/document_access_controls.py
Lines:
39 - 241
Complexity:
moderate

Purpose

DocumentAccessControls is a parameterized UI component that creates an interactive interface for document access management. It displays buttons for viewing and editing documents based on user roles and document status, integrates with a document control system to fetch access permissions, and uses JavaScript callbacks to open documents in new browser tabs. The component handles permission checking, URL generation, and provides visual feedback about document status and user access levels.

Source Code

class DocumentAccessControls(param.Parameterized):
    """
    UI Component for document access controls.
    
    This component provides buttons for viewing and editing documents based
    on the user's role and the document's current status.
    """
    document_uid = param.String(default="")
    user_uid = param.String(default="")
    show_access_indicator = param.Boolean(default=True)
    
    def __init__(self, **params):
        super().__init__(**params)
        
        # Create simplest possible UI components
        self.view_button = pn.widgets.Button(
            name="View Document",
            button_type="primary",
            width=150
        )
        
        self.edit_button = pn.widgets.Button(
            name="Edit Document",
            button_type="success",
            width=150,
            disabled=True
        )
        
        self.status_label = pn.pane.Markdown("Loading status...")
        
        # Add a hidden HTML pane for JavaScript integration
        self.js_pane = pn.pane.HTML("", height=0, width=0)
        
        # Create layout - avoid css_classes
        self.controls = pn.Column(
            pn.Row(
                self.view_button,
                self.edit_button
            ),
            self.status_label,
            self.js_pane  # Add the hidden pane to the layout
        )
        
        # Simple layout without any fancy indicators
        self.layout = pn.Column(
            pn.pane.Markdown("**Document Access**"),
            self.controls
        )
        
        # Set up button callbacks using JavaScript
        self.view_button.js_on_click(args={'btn': self.view_button}, code="""
        if (btn.share_url) {
            window.open(btn.share_url, '_blank');
        }
        """)
        
        self.edit_button.js_on_click(args={'btn': self.edit_button}, code="""
        if (btn.share_url) {
            window.open(btn.share_url, '_blank');
        }
        """)
        
        # Also set Python callbacks for logging
        self.view_button.on_click(self._log_view_click)
        self.edit_button.on_click(self._log_edit_click)
        
        # Initialize
        self._update_controls()
    
    def _log_view_click(self, event):
        """Log view button click."""
        logger.info(f"View button clicked. URL: {getattr(self.view_button, 'share_url', None)}")
    
    def _log_edit_click(self, event):
        """Log edit button click."""
        logger.info(f"Edit button clicked. URL: {getattr(self.edit_button, 'share_url', None)}")
    
    def _update_controls(self):
        """Update controls based on document status and user role."""
        if not self.document_uid or not self.user_uid:
            self._set_disabled_state("No document or user specified")
            return
            
        try:
            # Get document and user
            document = ControlledDocument(uid=self.document_uid)
            
            if not document:
                self._set_disabled_state("Document not found")
                return
                
            # Get current version
            version = document.current_version
            
            if not version:
                self._set_disabled_state("No document version available")
                return
                
            # Get user's access information
            result = get_user_access_url(version, self.user_uid)
            #logger.info("result: %s", result)
            
            if not result.get('success', False):
                error_message = result.get('message', 'Unable to get access information')
                logger.warning(f"Access error for user {self.user_uid} on document {self.document_uid}: {error_message}")
                self._set_disabled_state(f"You have no access to this document")
                return
            
            # Get share URL and check if it's empty
            share_url = result.get('share_url', '')
            if not share_url:
                self._set_disabled_state("Document has no accessible URL")
                return
            
            # Update buttons based on access
            self.view_button.disabled = False
            self.edit_button.disabled = not result.get('write_access', False)
            
            # Update the button JavaScript callbacks with direct URLs
            self.view_button.js_on_click(args={'url': share_url}, code="""
            if (url && url.trim() !== '') {
                window.open(url, '_blank');
            }
            """)
            
            self.edit_button.js_on_click(args={'url': share_url}, code="""
            if (url && url.trim() !== '') {
                window.open(url, '_blank');
            }
            """)
            
            # Update status label with role information
            status_text = f"Document Status: {document.get_status_name()}"
            
            # Add access mode information
            if result.get('write_access', False):
                status_text += " | Access: Edit Mode"
            else:
                status_text += " | Access: Read Only"
        
            # Add role information
            role_info = []
            if result.get('is_owner', False):
                role_info.append("Owner")
            if result.get('is_reviewer', False):
                role_info.append("Reviewer")
            if result.get('is_approver', False):
                role_info.append("Approver")
                
            if role_info:
                status_text += f" | Role: {', '.join(role_info)}"
                
            self.status_label.object = status_text
            
        except Exception as e:
            logger.error(f"Error updating access controls: {e}")
            import traceback
            logger.error(traceback.format_exc())
            self._set_disabled_state(f"Error: {str(e)}")
    
    def _set_disabled_state(self, message):
        """Set controls to disabled state with message."""
        self.view_button.disabled = True
        self.edit_button.disabled = True
        self.status_label.object = message
    
    def _handle_view_click(self, event):
        """Handle view button click."""
        logger.info("View button clicked")
        share_url = getattr(self.view_button, 'share_url', None)
        if not share_url:
            logger.error("No share URL available for view button")
            return
            
        # For Panel 1.6.1, use JavaScript to open the URL
        js_code = f"""
        window.open("{share_url}", "_blank");
        """
        pn.pane.HTML(f"<script>{js_code}</script>", height=0, width=0)
        
        # Log that we're opening the URL
        logger.info(f"Opening URL: {share_url}")
    
    def _handle_edit_click(self, event):
        """Handle edit button click."""
        logger.info("Edit button clicked")
        share_url = getattr(self.edit_button, 'share_url', None)
        if not share_url:
            logger.error("No share URL available for edit button")
            return
            
        # For Panel 1.6.1, use JavaScript to open the URL
        js_code = f"""
        window.open("{share_url}", "_blank");
        """
        pn.pane.HTML(f"<script>{js_code}</script>", height=0, width=0)
        
        # Log that we're opening the URL
        logger.info(f"Opening URL: {share_url}")
    
    def view(self):
        """Return the layout for display."""
        return self.layout

Parameters

Name Type Default Kind
bases param.Parameterized -

Parameter Details

document_uid: String parameter specifying the unique identifier of the document to control access for. Must be set for the component to function properly.

user_uid: String parameter specifying the unique identifier of the user whose access permissions should be checked. Required to determine view/edit capabilities.

show_access_indicator: Boolean parameter (default True) that controls whether to display access status indicators in the UI.

**params: Additional keyword arguments passed to the parent param.Parameterized class during initialization.

Return Value

Instantiation returns a DocumentAccessControls object with initialized UI components. The view() method returns a Panel Column layout containing the document access interface. Internal methods like _update_controls() return None but modify component state. get_user_access_url() returns a dictionary with keys: 'success' (bool), 'message' (str), 'share_url' (str), 'write_access' (bool), 'is_owner' (bool), 'is_reviewer' (bool), 'is_approver' (bool).

Class Interface

Methods

__init__(self, **params) -> None

Purpose: Initialize the DocumentAccessControls component with UI elements and callbacks

Parameters:

  • params: Keyword arguments including document_uid, user_uid, and show_access_indicator

Returns: None - initializes instance with UI components and layout

_log_view_click(self, event) -> None

Purpose: Log when the view button is clicked for audit purposes

Parameters:

  • event: Panel button click event object

Returns: None - logs information to logger

_log_edit_click(self, event) -> None

Purpose: Log when the edit button is clicked for audit purposes

Parameters:

  • event: Panel button click event object

Returns: None - logs information to logger

_update_controls(self) -> None

Purpose: Update button states and status label based on document status and user permissions

Returns: None - modifies button states, JavaScript callbacks, and status label

_set_disabled_state(self, message: str) -> None

Purpose: Disable all buttons and display an error or informational message

Parameters:

  • message: String message to display in the status label explaining why controls are disabled

Returns: None - disables buttons and updates status label

_handle_view_click(self, event) -> None

Purpose: Handle view button click by opening the document URL in a new tab (legacy method)

Parameters:

  • event: Panel button click event object

Returns: None - executes JavaScript to open URL

_handle_edit_click(self, event) -> None

Purpose: Handle edit button click by opening the document URL in a new tab (legacy method)

Parameters:

  • event: Panel button click event object

Returns: None - executes JavaScript to open URL

view(self) -> pn.Column

Purpose: Return the Panel layout for rendering in a Panel application or notebook

Returns: Panel Column object containing the complete UI layout with buttons and status

Attributes

Name Type Description Scope
document_uid param.String Parameter storing the unique identifier of the document being accessed instance
user_uid param.String Parameter storing the unique identifier of the user requesting access instance
show_access_indicator param.Boolean Parameter controlling whether to show access status indicators (default True) instance
view_button pn.widgets.Button Primary button for viewing the document, enabled based on user permissions instance
edit_button pn.widgets.Button Success-styled button for editing the document, enabled only with write access instance
status_label pn.pane.Markdown Markdown pane displaying document status, access mode, and user roles instance
js_pane pn.pane.HTML Hidden HTML pane used for JavaScript integration (height=0, width=0) instance
controls pn.Column Column layout containing the button row, status label, and JavaScript pane instance
layout pn.Column Main layout column containing the title and controls, returned by view() method instance

Dependencies

  • panel
  • param
  • logging
  • os
  • traceback
  • typing

Required Imports

import panel as pn
import param
from typing import Dict, List, Any, Optional, Callable
import logging
import os
from CDocs.models.document import ControlledDocument, DocumentVersion
from CDocs.models.user_extensions import DocUser
from CDocs.controllers.share_controller import get_user_access_url
from CDocs.models.document_status import STATUS_DRAFT, STATUS_IN_REVIEW, STATUS_IN_APPROVAL, STATUS_APPROVED, STATUS_PUBLISHED, STATUS_EFFECTIVE, STATUS_ARCHIVED, STATUS_OBSOLETE
import traceback

Usage Example

import panel as pn
import logging
from CDocs.ui.document_access_controls import DocumentAccessControls

# Configure logging
logger = logging.getLogger(__name__)

# Initialize Panel extension
pn.extension()

# Create the access controls component
access_controls = DocumentAccessControls(
    document_uid='doc-12345',
    user_uid='user-67890',
    show_access_indicator=True
)

# Display the component in a Panel app
app = pn.template.FastListTemplate(
    title='Document Access',
    main=[access_controls.view()]
)

# Serve the app
app.servable()

# Or in Jupyter notebook:
# access_controls.view()

# Update document/user dynamically
access_controls.document_uid = 'doc-99999'
access_controls.user_uid = 'user-11111'
access_controls._update_controls()  # Refresh the UI

Best Practices

  • Always set both document_uid and user_uid before displaying the component, otherwise it will show a disabled state
  • Call _update_controls() after changing document_uid or user_uid to refresh the UI state
  • Ensure the CDocs models and controllers are properly initialized before instantiating this component
  • The component uses JavaScript callbacks for opening URLs in new tabs, which requires a browser environment
  • Handle exceptions from ControlledDocument and get_user_access_url as they may raise errors if documents don't exist
  • The component automatically logs button clicks and access attempts for audit purposes
  • Do not directly modify view_button or edit_button properties; use _update_controls() to refresh state
  • The status_label provides user feedback about access levels and should be visible to users
  • Component state is managed internally; external code should primarily interact through parameter updates
  • The js_pane is a hidden HTML pane used for JavaScript integration and should not be removed from the layout

Similar Components

AI-powered semantic similarity - components with related functionality:

  • class DocumentShareManager 71.0% similar

    A Panel-based UI component for managing document sharing permissions, displaying share URLs, and viewing user access information for controlled documents.

    From: /tf/active/vicechatdev/CDocs/ui/components/share_manager.py
  • class SharePermissionIndicator 66.3% similar

    A Panel-based visual indicator component that displays document sharing permissions, showing whether a user has write access or read-only access to a document with color-coded status indicators.

    From: /tf/active/vicechatdev/CDocs/ui/components/share_manager.py
  • class ControlledDocApp 66.3% similar

    A standalone Panel web application class that provides a complete controlled document management system with user authentication, navigation, and document lifecycle management features.

    From: /tf/active/vicechatdev/panel_app.py
  • class DocumentDashboard 62.8% similar

    Dashboard for viewing and managing controlled documents.

    From: /tf/active/vicechatdev/CDocs/ui/document_dashboard.py
  • class CDocsApp 61.6% similar

    Panel-based web application class for the CDocs Controlled Document System that provides a complete UI with navigation, authentication, and document management features.

    From: /tf/active/vicechatdev/cdocs_panel_app.py
← Back to Browse