🔍 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 automatic propagation to child elements. The class integrates logging for audit trails, provides a Panel-based UI with dropdowns and search functionality, and manages the visibility state of slides that can be shown to customers.

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

log: A logging object for general application logging (though the class creates its own specialized logger)

user: A user object containing user information, must have a 'user' attribute for logging purposes

Return Value

Instantiation returns an options object with a 'body' attribute containing a Panel Column layout that can be displayed in a Panel application. Methods return various types: properties return data from database queries or logging objects, _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: Retrieve 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 to slide_release.log

Returns: Configured logging.FileHandler object writing to ./logs/slide_release.log

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

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

Returns: logging.Formatter object with format '[timestamp: level] :: message'

@property tooltip_message(self) -> str property

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

Returns: HTML string with detailed usage instructions

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, False otherwise

create_options_log(self) -> None

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

Returns: None - sets self.releaselog attribute

_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: Neo4j node label (Study, Group, ExpItem, Parblock, Slide)
  • uid: Unique identifier of the node to check

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

build_body(self) -> None

Purpose: Construct the main UI layout with dropdowns, search field, and results panel with reactive dependencies

Returns: None - sets self.body attribute with Panel Column layout

_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: Neo4j node label
  • N: Node name for logging
  • UID: Unique identifier of the node
  • is_visible: Boolean indicating whether to release (True) or lock (False) slides

Returns: None - updates database and writes to log

_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: Neo4j node label for database operations
  • visual_label: Display label for the UI
  • N: Node name to display
  • UID: Unique identifier for database operations
  • visible: Current visibility status

Returns: Panel WidgetBox containing a row with StaticText and Toggle button

Attributes

Name Type Description Scope
graph py2neo.Graph Neo4j database connection for querying and updating slide data instance
log logging.Logger General application logger passed during initialization instance
user object User object containing username for audit logging instance
search_results pn.Column Scrollable Panel Column container for displaying search results and toggle buttons instance
releaselog logging.Logger Dedicated logger for slide release audit trail, writes to slide_release.log instance
body pn.Column Main UI layout containing dropdowns, search field, and results panel 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

from py2neo import Graph
import panel as pn

# Setup
graph = Graph('bolt://localhost:7687', auth=('neo4j', 'password'))
log = logging.getLogger('app')
user = type('User', (), {'user': 'john_doe'})()

# Instantiate the options class
options_panel = options(graph, log, user)

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

# The UI will automatically handle:
# - Study selection from dropdown
# - View level selection (Study/Group/Animal/Block/Slide)
# - Free text search
# - Toggle buttons for releasing/locking slides
# - Logging all changes to ./logs/slide_release.log

Best Practices

  • Ensure Neo4j database connection is active before instantiation
  • Create logs directory before instantiation to avoid file handler errors
  • The class automatically creates a singleton logger 'SlideRelease' - multiple instances will share the same logger
  • The search results are limited to 100 items to prevent UI performance issues
  • Replaced slides (containing 'replaced' in name) are automatically excluded from visibility operations
  • The class uses reactive Panel dependencies - UI updates are automatic when dropdowns or search fields change
  • All visibility changes are logged with username and timestamp for audit purposes
  • The body attribute must be accessed after instantiation to get the UI component
  • State management is handled through Panel's reactive framework - no manual state updates needed
  • The class expects specific Neo4j node labels and relationships - ensure database schema matches

Similar Components

AI-powered semantic similarity - components with related functionality:

  • class User 55.3% 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 51.6% 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
  • function controlled_docs_navigation 46.8% similar

    Navigation controller for a Streamlit-based controlled documents module that manages document and review dashboards with URL parameter-based routing.

    From: /tf/active/vicechatdev/datacapture_integrated.py
  • class CDocsApp 46.5% similar

    Panel application for the CDocs Controlled Document System. This class provides a complete Panel application with navigation, user authentication, and all document management features.

    From: /tf/active/vicechatdev/cdocs_panel_app.py
  • class ObjectSharingInformation 41.6% similar

    A class that provides comprehensive information about the sharing state of SharePoint securable objects (documents, list items, sites), including permissions, sharing links, and user access details.

    From: /tf/active/vicechatdev/SPFCsync/venv/lib64/python3.11/site-packages/office365/sharepoint/sharing/object_sharing_information.py
← Back to Browse