🔍 Code Extractor

class options

Maturity: 42

A Panel-based UI class for managing slide release visibility in a study management system, allowing users to view and toggle the release status of slides at various hierarchical levels (Study, Group, Animal, Block, Slide).

File:
/tf/active/vicechatdev/options.py
Lines:
7 - 152
Complexity:
complex

Purpose

This class provides a comprehensive interface for managing slide visibility/release status in a Neo4j graph database. It allows users to search for and toggle the release status of slides at different organizational levels, with logging of all changes. The class integrates with Panel widgets to create an interactive UI for viewing hierarchical study data and controlling which slides are visible to customers. It handles complex visibility logic where parent nodes (Study, Group, etc.) show as locked if any child slides are locked.

Source Code

class options(param.Parameterized):
    def __init__(self, graph, log, user):
        self.graph = graph
        self.log = log
        self.user = user
        self.search_results = pn.Column(scroll=True)
        self.create_options_log()
        self.build_body()
        
    @property
    def studies(self):
        return self.graph.run(f"MATCH (s:Study) WHERE s.N =~ '\\\d{{2}}\\\w{{3}}' WITH s.N AS studies ORDER BY s.N RETURN COLLECT(studies)").evaluate()
    
    @property
    def file_handler(self):
        """
        The logging file handler for system logs
        """
        file_handler = logging.FileHandler(f"./logs/slide_release.log")
        file_handler.setLevel(logging.INFO)
        file_handler.setFormatter(self.formatter)
        return file_handler
    
    @property
    def formatter(self):
        """
        The logging formatter
        """
        return logging.Formatter('[%(asctime)s: %(levelname)-8s] :: %(message)s')
    
    @property
    def tooltip_message(self):
        return """
        Release slides for customers. The 'View' option searches within the given study as follows:
        <ul>
            <li><b>Study:</b> Release all slides for a study</li>
            <li><b>Group:</b> Release all slides for a specific group</li>
            <li><b>Animal:</b> Release all slides for a specific animal</li>
            <li><b>Block:</b> Release all slides for a specific block</li>
            <li><b>Slide:</b> Release a specific slide.</li>
        </ul>
        If for example within a group 1 slide is visible and 2 are not, the entire group will be shown as 'locked'.<br>
        Releasing the group will release all 3 slides.<br>
        Use the Free search to for example find a specific slide<br><br>
        Replaced slides are always hidden from the customer.
        """
    
    def get_existing_logger(self, loggername):
        """
        Checks if a logger already exists for the current user
        
        Returns
        -------
        Either the matched logger, or Bool False if no matching logger was found
        """
        for name in logging.root.manager.loggerDict:
            if name == loggername:
                return logging.getLogger(loggername)
        return False
    
    def create_options_log(self):
        """Create syslog logger"""
        logger = self.get_existing_logger("SlideRelease")
        if logger:
            self.releaselog = logger
        else:
            self.releaselog = logging.getLogger("SlideRelease")
            self.releaselog.setLevel(logging.INFO)
            self.releaselog.addHandler(self.file_handler)
        
    
    def _check_child_visibility(self, label, uid):
        return all(self.graph.run(f"""
        MATCH (:{label} {{UID:'{uid}'}})-[*]->(s:Slide) WHERE NOT toLower(s.N) CONTAINS 'replaced'
        WITH s, CASE WHEN s IS NULL THEN [false] ELSE all(x in COLLECT(s) WHERE x.released IS NOT NULL AND x.released) END AS result
        RETURN COLLECT(result)
        """).evaluate())
        
    def build_body(self):
        mapper = {
            'Animal':'ExpItem',
            'Block':'Parblock',
        }
        study_select = pn.widgets.Select(name="Study", options=self.studies, stylesheets=['./assets/stylesheets/dropdown.css'], align='end', width=100)
        view_select = pn.widgets.Select(name="View", options=['Study','Group','Animal','Block','Slide'], value='Study', stylesheets=['./assets/stylesheets/dropdown.css'], align='end', width=100)
        search = pn.widgets.TextInput(name="Free search", stylesheets=['./assets/stylesheets/textinput.css'], width=200)
        tooltip = pn.widgets.TooltipIcon(value=Tooltip(content=HTML(self.tooltip_message), position='bottom'))
        @pn.depends(study_select.param.value, view_select.param.value, watch=True)
        def _update_view(study, view):
            self.search_results.clear()
            label = mapper.get(view, view)
            if label == 'Study':
                label_collection = self.graph.run(f"MATCH (s:Study {{N:'{study}'}}) RETURN COLLECT(s)").evaluate()
            else:
                label_collection = self.graph.run(f"MATCH (:Study {{N:'{study}'}})-[*]->(o:{label}) WITH o AS result ORDER BY o.N RETURN COLLECT(result)").evaluate()
            if len(label_collection) > 100:
                pn.state.notifications.error(f"Too many items to display. Either use a different view, or contact your IT administrator with which slides to release/withhold")
                return
            for i in label_collection: #this loop can quickly get out of hand, put a limit on number of items
                if label == 'Slide':
                    is_all_visible = i.get('released',False)
                else:
                    is_all_visible = self._check_child_visibility(label, i['UID'])
                row = self._create_tag_and_button(label, view, i['N'], i['UID'], is_all_visible)
                self.search_results.append(row)
        @pn.depends(search.param.value, watch=True)
        def _update_view_from_search(search):
            result = self.graph.run(f"MATCH (o) WHERE o.N = toUpper('{search}') RETURN o").evaluate()
            if not result:
                pn.state.notifications.error(f"No match found for {search}")
                return
            label = list(result.labels)[0]
            if not label in ['Study','Group','ExpItem','Parblock','Slide']:
                pn.state.notifications.error(f"Invalid object {label}. Please search for a Group, Animal, Block or Slide")
                return
            self.search_results.clear()
            row = self._create_tag_and_button(label, mapper.get(label,label), result['N'], result['UID'], result.get('released',False))
            self.search_results.append(row)
            return
        self.body = pn.Column(
            pn.Row(study_select, view_select, search,tooltip),
            pn.layout.Divider(margin=(5,5,20,5)),
            self.search_results,
        )
        
    def _release_child_images(self, label, N, UID, is_visible):
        self.releaselog.info(f"User {self.user.user} set {label} {N} visibility to {is_visible}")
        if label == 'Slide':
            self.graph.run(f"MATCH (s:Slide {{UID:'{UID}'}}) SET s.released = {is_visible}")
            self.releaselog.info(f"{self.user.user} | {N} set to {is_visible}")
            return
        all_images = self.graph.run(f"MATCH (:{label} {{UID:'{UID}'}})-[*]->(s:Slide) WHERE NOT toLower(s.N) CONTAINS 'replaced' SET s.released = {is_visible} RETURN COLLECT(s.N)").evaluate()
        for i in all_images:
            self.releaselog.info(f"{self.user.user} | {i} set to {is_visible}")
    
    def _create_tag_and_button(self, label, visual_label, N, UID, visible):
        tag = pn.widgets.StaticText(name=visual_label, value=N, align='center')
        button = pn.widgets.Toggle(name='Released' if visible else 'locked', value=visible, button_type = 'success' if visible else 'danger', min_width=130)
        @pn.depends(button.param.value, watch=True)
        def on_click(value):
            pn.state.notifications.info("Updating slide release status, please wait.")
            button.button_type = 'success' if value else 'danger'
            button.name = 'Released' if value else 'Locked'
            self._release_child_images(label, N, UID, value)
            pn.state.notifications.success("Slide release status updated!")
        return pn.WidgetBox(pn.Row(tag, button), stylesheets=['./assets/stylesheets/widgetbox.css'])

Parameters

Name Type Default Kind
bases param.Parameterized -

Parameter Details

graph: A Neo4j graph database connection object (py2neo Graph instance) used to query and update slide and study data. Must support the .run() method for Cypher queries.

log: A logging object or logger instance for general application logging (though the class creates its own specialized logger for release operations).

user: A user object that must have a 'user' attribute containing the username string. Used for audit logging of who made visibility changes.

Return Value

Instantiation returns an options object with a fully configured Panel UI in the 'body' attribute. The body contains dropdown selectors, search input, and a scrollable results column. Methods return various types: properties return computed values (lists, loggers, strings), _check_child_visibility returns boolean, other methods perform side effects and return None.

Class Interface

Methods

__init__(self, graph, log, user)

Purpose: Initialize the options class with database connection, logging, and user context, then build the UI

Parameters:

  • graph: Neo4j graph database connection object
  • log: General logging object
  • user: User object with 'user' attribute containing username

Returns: None - initializes instance attributes and builds UI

@property studies(self) -> list property

Purpose: Query and return a list of all study names from the database that match the pattern of 2 digits followed by 3 letters

Returns: List of study names (strings) sorted alphabetically

@property file_handler(self) -> logging.FileHandler property

Purpose: Create and configure a file handler for logging slide release operations to './logs/slide_release.log'

Returns: Configured logging.FileHandler instance with INFO level and custom formatter

@property formatter(self) -> logging.Formatter property

Purpose: Create a logging formatter with timestamp, log level, and message

Returns: logging.Formatter instance with format '[%(asctime)s: %(levelname)-8s] :: %(message)s'

@property tooltip_message(self) -> str property

Purpose: Provide HTML-formatted help text explaining how the slide release interface works

Returns: Multi-line HTML string with usage instructions and feature descriptions

get_existing_logger(self, loggername: str) -> logging.Logger | bool

Purpose: Check if a logger with the given name already exists in the logging system

Parameters:

  • loggername: Name of the logger to search for

Returns: The existing Logger object if found, or False if not found

create_options_log(self) -> None

Purpose: Create or retrieve the 'SlideRelease' logger for audit logging of visibility changes

Returns: None - sets self.releaselog attribute to the logger instance

_check_child_visibility(self, label: str, uid: str) -> bool

Purpose: Check if all child slides of a given node are released (visible), excluding replaced slides

Parameters:

  • label: Node label type (Study, Group, ExpItem, Parblock, Slide)
  • uid: Unique identifier of the node to check

Returns: True if all non-replaced child slides are released, False otherwise

build_body(self) -> None

Purpose: Construct the complete Panel UI with dropdowns, search input, and results display with reactive dependencies

Returns: None - sets self.body attribute to a Panel Column containing the full UI

_release_child_images(self, label: str, N: str, UID: str, is_visible: bool) -> None

Purpose: Update the release status of a node and all its child slides in the database, with audit logging

Parameters:

  • label: Node label type (Study, Group, ExpItem, Parblock, Slide)
  • N: Name/identifier of the node
  • UID: Unique identifier of the node
  • is_visible: Boolean indicating whether to release (True) or lock (False) the slides

Returns: None - updates database and writes to log file

_create_tag_and_button(self, label: str, visual_label: str, N: str, UID: str, visible: bool) -> pn.WidgetBox

Purpose: Create a UI row with a label and toggle button for controlling slide visibility

Parameters:

  • label: Internal node label type for database operations
  • visual_label: Display label shown in the UI
  • N: Name/identifier of the node
  • UID: Unique identifier of the node
  • visible: Current visibility status (True=released, False=locked)

Returns: Panel WidgetBox containing a Row with StaticText label and Toggle button with reactive click handler

Attributes

Name Type Description Scope
graph py2neo.Graph Neo4j database connection for querying and updating slide data instance
log logging.Logger General application logger (not actively used, releaselog is used instead) instance
user object User object with 'user' attribute containing username for audit logging instance
search_results pn.Column Scrollable Panel Column container that holds the search/view results with toggle buttons instance
releaselog logging.Logger Dedicated logger for slide release operations, writes to './logs/slide_release.log' instance
body pn.Column Main Panel UI component containing all widgets (dropdowns, search, results), created by build_body() instance

Dependencies

  • param
  • panel
  • logging
  • bokeh
  • py2neo

Required Imports

import param
import panel as pn
import logging
from bokeh.models import Tooltip
from bokeh.models.dom import HTML

Usage Example

# Assuming you have a Neo4j connection and user object
from py2neo import Graph
import panel as pn

# Setup
graph = Graph('bolt://localhost:7687', auth=('neo4j', 'password'))
class User:
    def __init__(self, username):
        self.user = username

user = User('john_doe')
log = logging.getLogger('app')

# Create options instance
options_panel = options(graph, log, user)

# Display the UI (in a Panel app)
pn.extension()
app = pn.template.FastListTemplate(
    title='Slide Release Manager',
    main=[options_panel.body]
)
app.servable()

# The UI will show:
# - Study dropdown populated from database
# - View selector (Study/Group/Animal/Block/Slide)
# - Free search text input
# - Results with toggle buttons to release/lock slides

Best Practices

  • Ensure the Neo4j database connection is active before instantiating the class
  • Create the './logs/' directory before instantiation to avoid file handler errors
  • The class automatically creates a shared logger 'SlideRelease' - multiple instances will use the same logger
  • Be cautious with large datasets - the class limits display to 100 items and shows an error notification if exceeded
  • The visibility logic is hierarchical: releasing a parent (e.g., Group) releases all child slides that aren't marked as 'replaced'
  • Slides with 'replaced' in their name (case-insensitive) are always excluded from visibility operations
  • All visibility changes are logged with username and timestamp for audit purposes
  • The UI uses reactive dependencies (@pn.depends) so changes to dropdowns automatically update the display
  • Use the 'body' attribute to access the complete Panel UI component for embedding in applications
  • The class maintains state through instance attributes (graph, log, user, search_results) - avoid modifying these directly

Similar Components

AI-powered semantic similarity - components with related functionality:

  • class DocumentShareManager 54.5% 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 User 53.5% similar

    A user management class that handles authentication, authorization, user profiles, preferences, file management, and logging for a Panel-based web application with Neo4j backend.

    From: /tf/active/vicechatdev/userclass.py
  • class ControlledDocApp 50.5% 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 DocumentAccessControls 49.8% similar

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

    From: /tf/active/vicechatdev/CDocs/ui/components/document_access_controls.py
  • class SharePermissionIndicator 48.0% 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
← Back to Browse