class TrainingCompletion
UI component for completing training requirements.
/tf/active/vicechatdev/CDocs/ui/training_completion.py
24 - 783
moderate
Purpose
UI component for completing training requirements.
Source Code
class TrainingCompletion(BaseUIComponent):
"""UI component for completing training requirements."""
def __init__(self, parent_app=None, document_uid: str = None, **params):
super().__init__(parent_app, **params)
self.current_user = None
self.document_uid = document_uid
self.document = None
self.current_version = None # ADD THIS LINE
self.training_config = None
self.user_training = None
# UI components
self.main_content = pn.Column(sizing_mode='stretch_width')
self.document_content = None
self.quiz_section = None
self.completion_form = None
self._build_ui()
def _build_ui(self):
"""Build the training completion UI."""
try:
# Create header
self.header = pn.pane.Markdown("# Training Completion", sizing_mode='stretch_width')
# Create loading message
self.loading_panel = pn.pane.HTML(
"<div class='alert alert-info'>Loading training content...</div>",
sizing_mode='stretch_width'
)
# Add components to main content
self.main_content.extend([
self.header,
self.loading_panel
])
except Exception as e:
logger.error(f"Error building training completion UI: {e}")
self.main_content.append(pn.pane.Markdown(f"**Error:** {str(e)}"))
def set_user_and_document(self, user: DocUser, document_uid: str):
"""Set the current user and document for training."""
try:
self.current_user = user
self.document_uid = document_uid
self._load_training_content()
except Exception as e:
logger.error(f"Error setting user and document: {e}")
self.show_error(f"Error loading training: {str(e)}")
def _load_training_content(self):
"""Load the document and training configuration."""
try:
if not self.current_user or not self.document_uid:
self.show_error("Missing user or document information")
return
# Load document
self.document = ControlledDocument(uid=self.document_uid)
if not self.document or not self.document.uid:
self.show_error("Document not found")
return
# Get current version - ADD THIS
self.current_version = self.document.current_version
if not self.current_version:
self.show_error("No document version available")
return
logger.info(f"Loaded document: {self.document.doc_number} v{self.current_version.version_number}")
# Load training configuration
self.training_config = DocumentTraining(document_uid=self.document_uid)
if not self.training_config._data.get('training_required'):
self.show_error("Training is not required for this document")
return
# Load user training status
self.user_training = UserTraining(self.current_user.uid, self.document_uid)
if not self.user_training._data:
self.show_error("You are not assigned to training for this document")
return
# Build the training interface
self._build_training_interface()
except Exception as e:
logger.error(f"Error loading training content: {e}")
self.show_error(f"Error loading training: {str(e)}")
def _build_training_interface(self):
"""Build the main training interface."""
try:
# Clear main content
self.main_content.clear()
logger.info("document uid: %s", self.document.uid)
# Update header with document info
header_text = f"# Training: {self.document.doc_number} - {self.document.title}"
self.header = pn.pane.Markdown(header_text, sizing_mode='stretch_width')
self.main_content.append(self.header)
# Add notification area for messages
self.notification_area = pn.pane.HTML("", sizing_mode='stretch_width')
self.main_content.append(self.notification_area)
# Add training instructions if available
instructions = self.training_config._data.get('instructions', '')
if instructions:
instructions_panel = pn.pane.Markdown(
f"## Training Instructions\n\n{instructions}",
sizing_mode='stretch_width'
)
self.main_content.append(instructions_panel)
# Add document content section - FIX: Call the method and append result
document_content_section = self._build_document_content()
self.main_content.append(document_content_section)
# Add quiz section if required
if self.training_config._data.get('quiz_required'):
self._build_quiz_section()
# Add completion form
self._build_completion_form()
except Exception as e:
logger.error(f"Error building training interface: {e}")
self.show_error(f"Error building training interface: {str(e)}")
def _build_document_content(self) -> pn.viewable.Viewable:
"""Build document content display similar to document detail view."""
try:
if not self.document:
return pn.pane.Markdown("No document available.")
if not hasattr(self, 'current_version') or not self.current_version:
# Try to get current version if not set
self.current_version = self.document.current_version
if not self.current_version:
return pn.pane.Markdown("No document version available.")
# Create document information section
doc_info = pn.Column(
pn.pane.Markdown(f"## {self.document.title}"),
pn.pane.Markdown(f"**Document Number:** {self.document.doc_number}"),
pn.pane.Markdown(f"**Version:** {self.current_version.version_number}"),
sizing_mode='stretch_width'
)
# Add optional fields only if they exist
try:
if hasattr(self.document, 'doc_type_name') and self.document.doc_type_name:
doc_info.append(pn.pane.Markdown(f"**Type:** {self.document.doc_type_name}"))
except:
pass
try:
if hasattr(self.document, 'get_department_name'):
dept_name = self.document.get_department_name()
if dept_name:
doc_info.append(pn.pane.Markdown(f"**Department:** {dept_name}"))
except:
pass
try:
if hasattr(self.document, 'get_status_name'):
status_name = self.document.get_status_name()
if status_name:
doc_info.append(pn.pane.Markdown(f"**Status:** {status_name}"))
except:
pass
# Add document description if available
try:
description = getattr(self.document, 'description', None)
if description:
doc_info.append(pn.pane.Markdown(f"**Description:** {description}"))
except:
pass
# Style the info section
doc_info.styles = {'background':'#f8f9fa'}
doc_info.css_classes = ['p-3', 'border', 'rounded']
# Use DocumentAccessControls for view/edit functionality (same as document_detail)
access_controls_section = None
if self.current_version and self.current_user:
try:
from CDocs.ui.components.document_access_controls import DocumentAccessControls
access_controls = DocumentAccessControls(
document_uid=self.document.uid,
user_uid=self.current_user.uid,
show_access_indicator=True
)
access_controls_section = pn.Column(
pn.pane.Markdown("### Document Access"),
access_controls.view(),
sizing_mode='stretch_width'
)
except Exception as e:
logger.warning(f"Error creating access controls: {e}")
# Fallback to simple view button
access_controls_section = self._create_fallback_view_button()
else:
access_controls_section = pn.pane.Markdown("*Document access not available*")
# Create document preview section
preview_section = self._create_document_preview()
# Combine all sections
content_layout = pn.Column(
pn.pane.Markdown("## Document Information"),
doc_info,
access_controls_section,
pn.pane.Markdown("---"), # Separator
preview_section,
sizing_mode='stretch_width'
)
return content_layout
except Exception as e:
logger.error(f"Error building document content: {e}")
import traceback
logger.error(f"Traceback: {traceback.format_exc()}")
return pn.pane.Markdown(f"Error loading document content: {str(e)}")
def _create_document_preview(self) -> pn.viewable.Viewable:
"""Create document preview section."""
try:
if not hasattr(self, 'current_version') or not self.current_version:
return pn.pane.Markdown("No document version available for preview.")
# Get file information safely
file_path = None
file_type = "Unknown"
try:
# Determine primary file path based on document status
if hasattr(self.document, 'is_published') and self.document.is_published():
file_path = getattr(self.current_version, 'pdf_file_path', None)
file_type = "PDF"
else:
file_path = getattr(self.current_version, 'word_file_path', None)
file_type = "Document"
if not file_path:
# Try alternative file path
if hasattr(self.document, 'is_published') and self.document.is_published():
file_path = getattr(self.current_version, 'word_file_path', None)
file_type = "Document"
else:
file_path = getattr(self.current_version, 'pdf_file_path', None)
file_type = "PDF"
except Exception as e:
logger.warning(f"Error determining file path: {e}")
if file_path:
preview_info = pn.Column(
pn.pane.Markdown("### Document Preview"),
pn.pane.Markdown(f"**File Type:** {file_type}"),
pn.pane.Markdown(f"**File Path:** {file_path}"),
pn.pane.Markdown("*Use the 'View Document' button above to open the document.*"),
styles={'background':'#e9ecef'},
css_classes=['p-3', 'border', 'rounded']
)
else:
preview_info = pn.pane.Markdown(
"**No document file available for preview.**",
styles={'background':'#fff3cd'},
css_classes=['p-3', 'border', 'rounded']
)
return preview_info
except Exception as e:
logger.error(f"Error creating document preview: {e}")
return pn.pane.Markdown(f"Error creating preview: {str(e)}")
def _view_document(self, event=None):
"""View the current document version using the same method as document detail view."""
try:
if not self.document or not hasattr(self, 'current_version') or not self.current_version:
self.show_error("No document available to view")
return
logger.info(f"Getting document view URL for document {self.document.uid}")
# Import the document controller to get the view URL
from CDocs.controllers.document_controller import get_document_edit_url
# Get the document view URL
result = get_document_edit_url(
document_uid=self.document.uid,
user=self.current_user,
version_uid=self.current_version.uid
)
if result.get('success'):
view_url = result.get('view_url')
if view_url:
# Open document in new tab/window
notification_html = f"""
<div class='alert alert-info'>
<strong>Opening document...</strong><br>
If the document doesn't open automatically, <a href='{view_url}' target='_blank'>click here</a>.
</div>
<script>
window.open('{view_url}', '_blank');
</script>
"""
if hasattr(self, 'notification_area') and self.notification_area:
self.notification_area.object = notification_html
else:
self.show_info(f"Opening document: {view_url}")
else:
self.show_error("Document view URL not available")
else:
error_msg = result.get('message', 'Unknown error')
self.show_error(f"Error getting document URL: {error_msg}")
except Exception as e:
logger.error(f"Error viewing document: {e}")
import traceback
logger.error(f"Traceback: {traceback.format_exc()}")
self.show_error(f"Error viewing document: {str(e)}")
def _build_quiz_section(self):
"""Build the quiz section if required."""
try:
quiz_header = pn.pane.Markdown("## Training Quiz", sizing_mode='stretch_width')
self.main_content.append(quiz_header)
# Simple quiz placeholder - in a real implementation, this would load actual quiz questions
quiz_questions = [
{
"question": "Have you read and understood the document content?",
"type": "boolean",
"required": True
},
{
"question": "Do you understand the procedures described in this document?",
"type": "boolean",
"required": True
},
{
"question": "Any questions or concerns about this document?",
"type": "text",
"required": False
}
]
self.quiz_widgets = []
for i, q in enumerate(quiz_questions):
question_panel = pn.Column(sizing_mode='stretch_width')
question_panel.append(pn.pane.Markdown(f"**Question {i+1}:** {q['question']}"))
if q['type'] == 'boolean':
widget = pn.widgets.RadioButtonGroup(
name=f"question_{i}",
options=['Yes', 'No'],
value='Yes' if not q['required'] else None,
sizing_mode='stretch_width'
)
elif q['type'] == 'text':
widget = pn.widgets.TextAreaInput(
name=f"question_{i}",
placeholder="Enter your response...",
rows=3,
sizing_mode='stretch_width'
)
self.quiz_widgets.append(widget)
question_panel.append(widget)
self.main_content.append(question_panel)
except Exception as e:
logger.error(f"Error building quiz section: {e}")
self.main_content.append(pn.pane.Markdown(f"**Error building quiz:** {str(e)}"))
def _build_completion_form(self):
"""Build the training completion form."""
try:
completion_header = pn.pane.Markdown("## Complete Training", sizing_mode='stretch_width')
self.main_content.append(completion_header)
# Confirmation checkbox
self.confirm_checkbox = pn.widgets.Checkbox(
name="I confirm that I have completed this training and understand the content",
value=False,
sizing_mode='stretch_width'
)
# Comments field
self.comments_input = pn.widgets.TextAreaInput(
name="Comments (optional)",
placeholder="Enter any comments about this training...",
rows=3,
sizing_mode='stretch_width'
)
# Buttons
self.complete_btn = pn.widgets.Button(
name="Complete Training",
button_type="success",
width=150
)
self.cancel_btn = pn.widgets.Button(
name="Cancel",
button_type="default",
width=100
)
# Button handlers
self.complete_btn.on_click(self._handle_complete_training)
self.cancel_btn.on_click(self._handle_cancel)
# Add completion form components
completion_form = pn.Column(
self.confirm_checkbox,
self.comments_input,
pn.Row(
pn.layout.HSpacer(),
self.cancel_btn,
self.complete_btn
),
sizing_mode='stretch_width',
styles={'background': '#f8f9fa'},
css_classes=['p-3', 'border', 'rounded']
)
self.main_content.append(completion_form)
except Exception as e:
logger.error(f"Error building completion form: {e}")
self.main_content.append(pn.pane.Markdown(f"**Error building completion form:** {str(e)}"))
def _handle_complete_training(self, event):
"""Handle training completion."""
try:
# Validate form
if not self.confirm_checkbox.value:
self.show_error("You must confirm completion to proceed")
return
# Validate quiz if required
quiz_passed = True
if self.training_config._data.get('quiz_required') and hasattr(self, 'quiz_widgets'):
quiz_passed = self._validate_quiz()
if not quiz_passed:
self.show_error("Please complete all required quiz questions")
return
# Complete the training
result = training_controller.complete_user_training(
user=self.current_user,
document_uid=self.document_uid,
quiz_passed=quiz_passed
)
if result.get('success'):
self.show_success("Training completed successfully!")
# Navigate back to training dashboard
if self.parent_app:
self.parent_app.navigate_to('training_dashboard')
else:
error_msg = result.get('message', 'Unknown error')
self.show_error(f"Error completing training: {error_msg}")
except Exception as e:
logger.error(f"Error completing training: {e}")
self.show_error(f"Error completing training: {str(e)}")
def _handle_cancel(self, event):
"""Handle training cancellation."""
try:
# Navigate back to training dashboard
if self.parent_app:
self.parent_app.navigate_to('training_dashboard')
except Exception as e:
logger.error(f"Error canceling training: {e}")
def _validate_quiz(self) -> bool:
"""Validate quiz responses."""
try:
for widget in self.quiz_widgets:
if hasattr(widget, 'value'):
if widget.value is None or widget.value == '':
return False
return True
except Exception as e:
logger.error(f"Error validating quiz: {e}")
return False
def get_view(self) -> pn.viewable.Viewable:
"""Get the main view component."""
return self.main_content
def _build_training_instructions(self) -> pn.viewable.Viewable:
"""Build training instructions section with enhanced information."""
try:
if not self.training_config:
return pn.pane.Markdown("No training configuration available.")
# Get training instructions
instructions = self.training_config.get('instructions', '')
if not instructions:
instructions = "Please review the document carefully and complete any required assessments."
# Get training requirements
quiz_required = self.training_config.get('quiz_required', False)
validity_days = self.training_config.get('validity_days', 365)
# Create instructions content
instructions_content = [
pn.pane.Markdown("## Training Instructions"),
pn.pane.Markdown(instructions),
pn.pane.Markdown("---"),
pn.pane.Markdown("### Training Requirements"),
]
# Add requirement details
if quiz_required:
instructions_content.append(
pn.pane.Markdown("â ī¸ **Quiz Required:** You must complete a quiz after reviewing the document.")
)
else:
instructions_content.append(
pn.pane.Markdown("âšī¸ **No Quiz Required:** Simply review the document and mark as complete.")
)
instructions_content.append(
pn.pane.Markdown(f"đ
**Training Validity:** {validity_days} days from completion")
)
# Add steps to complete training
instructions_content.extend([
pn.pane.Markdown("---"),
pn.pane.Markdown("### Steps to Complete Training"),
pn.pane.Markdown("""
1. **Review the Document:** Use the 'View Document' button to open and read the document thoroughly
2. **Understand the Content:** Make sure you understand the procedures and requirements
3. **Complete Assessment:** If required, complete the quiz below
4. **Mark as Complete:** Click the 'Complete Training' button when finished
""")
])
return pn.Column(
*instructions_content,
styles={'background':'#f9f9fa'},
css_classes=['p-3', 'border', 'rounded'],
sizing_mode='stretch_width'
)
except Exception as e:
logger.error(f"Error building training instructions: {e}")
return pn.pane.Markdown(f"Error loading training instructions: {str(e)}")
def load_training(self, document_uid: str, user: DocUser):
"""Load training data for a specific document and user."""
try:
self.document_uid = document_uid
self.current_user = user
logger.info(f"Loading training for document {document_uid} and user {user.uid}")
# Load document and version information
from CDocs.models.document import ControlledDocument
self.document = ControlledDocument(uid=document_uid)
if not self.document or not self.document.uid:
self.show_error("Document not found")
return False
# Get current version
self.current_version = self.document.current_version
if not self.current_version:
self.show_error("No document version available")
return False
logger.info(f"Loaded document: {self.document.doc_number} v{self.current_version.version_number}")
# Load training configuration
from CDocs.models.training import DocumentTraining
self.training_config_obj = DocumentTraining(document_uid=document_uid)
self.training_config = self.training_config_obj._data
if not self.training_config.get('training_required', False):
self.show_error("Training is not enabled for this document")
return False
# Load user training data
from CDocs.models.training import UserTraining
self.user_training = UserTraining(user_uid=user.uid, document_uid=document_uid)
if not self.user_training._data:
self.show_error("No training assignment found for this user and document")
return False
# Check if already completed
if self.user_training._data.get('status') == 'TRAINED':
completion_date = self.user_training._data.get('trained_on')
self.show_info(f"Training already completed on {self._format_date(completion_date)}")
# Rebuild UI with loaded data
self._build_ui()
return True
except Exception as e:
logger.error(f"Error loading training: {e}")
import traceback
logger.error(f"Traceback: {traceback.format_exc()}")
self.show_error(f"Error loading training: {str(e)}")
return False
def show_error(self, message: str):
"""Show error message to user."""
try:
if hasattr(self, 'notification_area') and self.notification_area:
self.notification_area.object = f"<div class='alert alert-danger'><strong>Error:</strong> {message}</div>"
else:
logger.error(f"Error: {message}")
except Exception as e:
logger.error(f"Error showing error message: {e}")
logger.error(f"Original error: {message}")
def show_success(self, message: str):
"""Show success message to user."""
try:
if hasattr(self, 'notification_area') and self.notification_area:
self.notification_area.object = f"<div class='alert alert-success'><strong>Success:</strong> {message}</div>"
else:
logger.info(f"Success: {message}")
except Exception as e:
logger.error(f"Error showing success message: {e}")
logger.info(f"Original success: {message}")
def show_info(self, message: str):
"""Show info message to user."""
try:
if hasattr(self, 'notification_area') and self.notification_area:
self.notification_area.object = f"<div class='alert alert-info'><strong>Info:</strong> {message}</div>"
else:
logger.info(f"Info: {message}")
except Exception as e:
logger.error(f"Error showing info message: {e}")
logger.info(f"Original info: {message}")
def _create_fallback_view_button(self) -> pn.viewable.Viewable:
"""Create a fallback view button when access controls fail."""
try:
# Create simple view document button
view_btn = pn.widgets.Button(
name="View Document",
button_type="primary",
width=200
)
view_btn.on_click(self._view_document_fallback)
# Create action buttons row
action_buttons = pn.Row(
view_btn,
sizing_mode='stretch_width',
align='center'
)
return pn.Column(
pn.pane.Markdown("### Document Access"),
action_buttons,
sizing_mode='stretch_width'
)
except Exception as e:
logger.error(f"Error creating fallback view button: {e}")
return pn.pane.Markdown("*Error creating document access*")
def _view_document_fallback(self, event=None):
"""Fallback view document method when access controls are not available."""
try:
if not self.document or not hasattr(self, 'current_version') or not self.current_version:
self.show_error("No document available to view")
return
logger.info(f"Getting document view URL for document {self.document.uid} (fallback)")
# Try to get document edit URL first
try:
from CDocs.controllers.document_controller import get_document_edit_url
result = get_document_edit_url(
document_uid=self.document.uid,
user=self.current_user,
version_uid=self.current_version.uid
)
if result.get('success'):
view_url = result.get('view_url')
if view_url:
self._open_document_url(view_url)
return
except Exception as e:
logger.warning(f"Failed to get edit URL, trying download: {e}")
# Fallback to download
try:
from CDocs.controllers.document_controller import download_document_version
download_result = download_document_version(
user=self.current_user,
document_uid=self.document.uid,
version_uid=self.current_version.uid
)
if download_result.get('success'):
download_url = download_result.get('download_url')
if download_url:
self._open_document_url(download_url)
return
except Exception as e:
logger.warning(f"Failed to get download URL: {e}")
self.show_error("Unable to access document")
except Exception as e:
logger.error(f"Error in fallback view document: {e}")
import traceback
logger.error(f"Traceback: {traceback.format_exc()}")
self.show_error(f"Error viewing document: {str(e)}")
def _open_document_url(self, url: str):
"""Open document URL in new tab/window."""
try:
notification_html = f"""
<div class='alert alert-info'>
<strong>Opening document...</strong><br>
If the document doesn't open automatically, <a href='{url}' target='_blank'>click here</a>.
</div>
<script>
window.open('{url}', '_blank');
</script>
"""
if hasattr(self, 'notification_area') and self.notification_area:
self.notification_area.object = notification_html
else:
self.show_info(f"Opening document: {url}")
except Exception as e:
logger.error(f"Error opening document URL: {e}")
self.show_error(f"Error opening document: {str(e)}")
Parameters
| Name | Type | Default | Kind |
|---|---|---|---|
bases |
BaseUIComponent | - |
Parameter Details
bases: Parameter of type BaseUIComponent
Return Value
Returns unspecified type
Class Interface
Methods
__init__(self, parent_app, document_uid)
Purpose: Internal method: init
Parameters:
parent_app: Parameterdocument_uid: Type: str
Returns: None
_build_ui(self)
Purpose: Build the training completion UI.
Returns: None
set_user_and_document(self, user, document_uid)
Purpose: Set the current user and document for training.
Parameters:
user: Type: DocUserdocument_uid: Type: str
Returns: None
_load_training_content(self)
Purpose: Load the document and training configuration.
Returns: None
_build_training_interface(self)
Purpose: Build the main training interface.
Returns: None
_build_document_content(self) -> pn.viewable.Viewable
Purpose: Build document content display similar to document detail view.
Returns: Returns pn.viewable.Viewable
_create_document_preview(self) -> pn.viewable.Viewable
Purpose: Create document preview section.
Returns: Returns pn.viewable.Viewable
_view_document(self, event)
Purpose: View the current document version using the same method as document detail view.
Parameters:
event: Parameter
Returns: None
_build_quiz_section(self)
Purpose: Build the quiz section if required.
Returns: None
_build_completion_form(self)
Purpose: Build the training completion form.
Returns: None
_handle_complete_training(self, event)
Purpose: Handle training completion.
Parameters:
event: Parameter
Returns: None
_handle_cancel(self, event)
Purpose: Handle training cancellation.
Parameters:
event: Parameter
Returns: None
_validate_quiz(self) -> bool
Purpose: Validate quiz responses.
Returns: Returns bool
get_view(self) -> pn.viewable.Viewable
Purpose: Get the main view component.
Returns: Returns pn.viewable.Viewable
_build_training_instructions(self) -> pn.viewable.Viewable
Purpose: Build training instructions section with enhanced information.
Returns: Returns pn.viewable.Viewable
load_training(self, document_uid, user)
Purpose: Load training data for a specific document and user.
Parameters:
document_uid: Type: struser: Type: DocUser
Returns: None
show_error(self, message)
Purpose: Show error message to user.
Parameters:
message: Type: str
Returns: None
show_success(self, message)
Purpose: Show success message to user.
Parameters:
message: Type: str
Returns: None
show_info(self, message)
Purpose: Show info message to user.
Parameters:
message: Type: str
Returns: None
_create_fallback_view_button(self) -> pn.viewable.Viewable
Purpose: Create a fallback view button when access controls fail.
Returns: Returns pn.viewable.Viewable
_view_document_fallback(self, event)
Purpose: Fallback view document method when access controls are not available.
Parameters:
event: Parameter
Returns: None
_open_document_url(self, url)
Purpose: Open document URL in new tab/window.
Parameters:
url: Type: str
Returns: None
Required Imports
import logging
from typing import Dict
from typing import List
from typing import Any
from typing import Optional
Usage Example
# Example usage:
# result = TrainingCompletion(bases)
Tags
Similar Components
AI-powered semantic similarity - components with related functionality:
-
class TrainingManagement 72.7% similar
-
class TrainingDashboard 70.8% similar
-
function create_training_completion 62.8% similar
-
function complete_user_training_by_uid 54.1% similar
-
class UserTraining 53.5% similar