class DocumentAccessControls
A Panel-based UI component that manages document access controls, providing view and edit buttons with role-based permissions and status indicators.
/tf/active/vicechatdev/CDocs/ui/components/document_access_controls.py
39 - 241
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
panelparamloggingostracebacktyping
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
Tags
Similar Components
AI-powered semantic similarity - components with related functionality:
-
class DocumentShareManager 71.0% similar
-
class SharePermissionIndicator 66.3% similar
-
class ControlledDocApp 66.3% similar
-
class DocumentDashboard 62.8% similar
-
class CDocsApp 61.6% similar