from PyQt5.QtWidgets import QMessageBox, QCheckBox, QGroupBox, QDateTimeEdit,QProgressBar, QMainWindow, QTableWidget, QTableWidgetItem, QLineEdit, QStyledItemDelegate, QTextEdit, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QComboBox, QLabel from PyQt5.QtCore import pyqtSignal, Qt, pyqtSignal, QDateTime, QThread from PyQt5.QtGui import QTextDocument, QTextOption import re import logging import html import datetime from logline_leviathan.database.query import DatabaseGUIQuery, QueryThread from logline_leviathan.database.database_manager import EntitiesTable, session_scope COLUMN_WIDTHS = [200, 100, 250, 100, 120, 600, 80, 100, 40] # Adjust these values as needed COLUMN_NAMES = ['Distinct Entity', 'Entity Type', 'File Name', 'Line Number', 'Timestamp', 'Context', 'Match Score', 'Flag', 'Identifier'] DEFAULT_ROW_HEIGHT = 120 FILTER_EDIT_WIDTH = 150 class DataProcessor(QThread): dataProcessed = pyqtSignal(list) def __init__(self, total_data, search_terms, chunk_size=50): super().__init__() self.total_data = total_data self.search_terms = search_terms # Ensure chunk_size is an integer if not isinstance(chunk_size, int): raise ValueError(f"chunk_size must be an integer, got {type(chunk_size)}") self.chunk_size = chunk_size def run(self): # Process initial chunk for i in range(0, len(self.total_data), self.chunk_size): chunk = sorted(self.total_data[i:i+self.chunk_size], key=lambda x: x[1], reverse=True) self.dataProcessed.emit(chunk) # Continue processing remaining data remaining_data = self.total_data[self.chunk_size:] for i in range(0, len(remaining_data), self.chunk_size): chunk = sorted(remaining_data[i:i+self.chunk_size], key=lambda x: x[1], reverse=True) self.dataProcessed.emit(chunk) class ResultsWindow(QMainWindow): def __init__(self, db_query_instance, parent=None): super(ResultsWindow, self).__init__(parent) self.db_query_instance = db_query_instance self.total_data = [] self.current_filters = {} self.query_text = None self.database_query = DatabaseGUIQuery() self.query_thread = QueryThread(self.db_query_instance, self.query_text) self.sorted_results = [] self.setWindowTitle("Suchergebnis") self.setGeometry(800, 600, 1600, 700) # Adjust size as needed # Create central widget and set layout centralWidget = QWidget(self) self.setCentralWidget(centralWidget) mainLayout = QVBoxLayout(centralWidget) queryFieldLayout = QHBoxLayout() self.databaseQueryLineEdit = QueryLineEdit(self) self.databaseQueryLineEdit.setPlaceholderText(" Suchbegriff eingeben...") self.databaseQueryLineEdit.returnPressed.connect(self.execute_query_from_results_window) self.databaseQueryLineEdit.setStyleSheet(""" QLineEdit { background-color: #3C4043; color: white; min-height: 20px; } """) queryFieldLayout.addWidget(self.databaseQueryLineEdit) # Create a progress bar for query in progress self.query_progress_bar = QProgressBar(self) self.query_progress_bar.setRange(0, 1) # Indeterminate mode self.query_progress_bar.setFixedWidth(100) # Initially hidden queryFieldLayout.addWidget(self.query_progress_bar) executeQueryButton = QPushButton("Suche ausführen", self) executeQueryButton.clicked.connect(self.execute_query_from_results_window) queryFieldLayout.addWidget(executeQueryButton) mainLayout.addLayout(queryFieldLayout) mainLayout.addWidget(QLabel(' Die Suche nach mehreren bestimmten Begriffen, Entitätentypen (Kurzform z.B. ), Dateinamen und Timestamps ist möglich.\n Nach erfolgreicher Abfrage der Datenbank werden die Ergebnisse tabellarisch dargestellt. Sollte die Anzahl der Suchergebnisse sehr hoch sein, dauert der Prozess einige Sekunden. Die Anzahl der Ergebnisse ist aus Performancegründen auf die besten 512 beschränkt.\n Groß- und Kleinschreibung wird bei Zitatsuchen berücksichtigt; Werden mehrere Suchbegriffe eingegeben, fließt deren Abstand und Reihenfolge ins Ergebnis mit ein.', self)) # Create a horizontal layout for filter options filterLayout = QHBoxLayout() mainLayout.addLayout(filterLayout) # Updated stylesheet for the entire ResultsWindow stylesheet = """ /* Styles for QTableWidget and headers */ QTableWidget, QHeaderView::section { background-color: #2A2F35; color: white; border: 1px solid #4A4A4A; } /* Style for QLineEdit */ QLineEdit { background-color: #3A3F44; color: white; border: 1px solid #4A4A4A; } /* Style for QPushButton */ QPushButton { background-color: #4B5563; color: white; border-radius: 4px; padding: 5px; margin: 5px; } QPushButton:hover { background-color: #5C677D; } QPushButton:pressed { background-color: #2A2F35; } /* Style for empty rows and other areas */ QWidget { background-color: #2A2F35; color: white; } """ self.setStyleSheet(stylesheet) self.resultsTable = QTableWidget(self) self.clearAllButton = QPushButton("Alle Filteroptionen loeschen", self) self.clearAllButton.clicked.connect(self.clearAllFilters) filterLayout.addWidget(self.clearAllButton) # GroupBox for Entitätenfilter entitaten_filter_groupbox = QGroupBox("Entitätenfilter", self) entitaten_filter_layout = QVBoxLayout() entitaten_filter_groupbox.setLayout(entitaten_filter_layout) self.distinct_entity_edit = QLineEdit(self) self.distinct_entity_edit.setPlaceholderText(" Enter distinct entity...") self.distinct_entity_edit.textChanged.connect(self.applyDistinctEntityTextFilter) entitaten_filter_layout.addWidget(self.distinct_entity_edit) self.entityTypeComboBox = QComboBox() entitaten_filter_layout.addWidget(self.entityTypeComboBox) filterLayout.addWidget(entitaten_filter_groupbox) # GroupBox for Fundstelle fundstelle_groupbox = QGroupBox("Fundstelle", self) fundstelle_layout = QVBoxLayout() fundstelle_groupbox.setLayout(fundstelle_layout) self.file_name_edit = QLineEdit(self) self.file_name_edit.setPlaceholderText(" Enter file name...") self.file_name_edit.textChanged.connect(self.applyFileNameTextFilter) fundstelle_layout.addWidget(self.file_name_edit) self.line_number_edit = QLineEdit(self) self.line_number_edit.setPlaceholderText(" Enter line number...") self.line_number_edit.textChanged.connect(self.applyLineNumberTextFilter) fundstelle_layout.addWidget(self.line_number_edit) filterLayout.addWidget(fundstelle_groupbox) # GroupBox for timestamp filtering self.timestampFilterGroupbox = QGroupBox("Zeitrahmen", self) timestampFilterLayout = QVBoxLayout() self.timestampFilterGroupbox.setLayout(timestampFilterLayout) filterLayout.addWidget(self.timestampFilterGroupbox) dateedit_layout = QHBoxLayout() self.startDateEdit = QDateTimeEdit(self) self.startDateEdit.setDisplayFormat("yyyy-MM-dd HH:mm:ss") self.startDateEdit.setCalendarPopup(True) self.startDateEdit.setDateTime(QDateTime.currentDateTime().addDays(-10000)) dateedit_layout.addWidget(self.startDateEdit) self.startDateEdit.dateTimeChanged.connect(self.applyTimestampFilter) self.endDateEdit = QDateTimeEdit(self) self.endDateEdit.setDisplayFormat("yyyy-MM-dd HH:mm:ss") self.endDateEdit.setCalendarPopup(True) self.endDateEdit.setDateTime(QDateTime.currentDateTime().addDays(1)) dateedit_layout.addWidget(self.endDateEdit) self.endDateEdit.dateTimeChanged.connect(self.applyTimestampFilter) self.timestamp_edit = QLineEdit(self) self.timestamp_edit.setPlaceholderText(" Enter timestamp...") self.timestamp_edit.textChanged.connect(self.applyTimestampTextFilter) timestampFilterLayout.addWidget(self.timestamp_edit) timestampFilterLayout.addLayout(dateedit_layout) # GroupBox for Match Score, Flags, and Identifier more_filters_groupbox = QGroupBox("Weitere Filter", self) more_filters_layout = QHBoxLayout() more_filters_groupbox.setLayout(more_filters_layout) filterLayout.addWidget(more_filters_groupbox) flag_layout = QVBoxLayout() flag_true_checkbox = QCheckBox("Flagged", self) flag_layout.addWidget(flag_true_checkbox) flag_false_checkbox = QCheckBox("Not Flagged", self) flag_layout.addWidget(flag_false_checkbox) more_filters_layout.addLayout(flag_layout) meta_layout = QVBoxLayout() self.match_score_edit = QLineEdit(self) self.match_score_edit.setPlaceholderText(" Enter match score...") self.match_score_edit.textChanged.connect(self.applyMatchScoreTextFilter) meta_layout.addWidget(self.match_score_edit) self.identifier_edit = QLineEdit(self) self.identifier_edit.setPlaceholderText(" Enter identifier...") self.identifier_edit.textChanged.connect(self.applyIdentifierTextFilter) meta_layout.addWidget(self.identifier_edit) more_filters_layout.addLayout(meta_layout) self.resultsTable.setColumnCount(len(COLUMN_NAMES)) self.resultsTable.setHorizontalHeaderLabels(COLUMN_NAMES) self.resultsTable.setSortingEnabled(True) mainLayout.addWidget(self.resultsTable) bottomLayout = QHBoxLayout() self.query_status_label = QLabel(" Hintergrundsuchprozess läuft...") bottomLayout.addWidget(self.query_status_label) self.bottomButtonLayout = QHBoxLayout() flag_visible_items_button = QPushButton("Sichtbare Objekte markieren", self) flag_visible_items_button.clicked.connect(self.flagVisibleItems) self.bottomButtonLayout.addWidget(flag_visible_items_button) unflag_visible_items_button = QPushButton("Sichtbare Objekte demarkieren", self) unflag_visible_items_button.clicked.connect(self.unflagVisibleItems) self.bottomButtonLayout.addWidget(unflag_visible_items_button) clear_all_flags_button = QPushButton("Sämtliche Markierungen entfernen", self) clear_all_flags_button.clicked.connect(self.clearAllFlags) self.bottomButtonLayout.addWidget(clear_all_flags_button) bottomLayout.addLayout(self.bottomButtonLayout) mainLayout.addLayout(bottomLayout) # Create and add the Dismiss button self.dismissButton = QPushButton("Schließen", self) self.dismissButton.clicked.connect(self.close) mainLayout.addWidget(self.dismissButton) self.populate_entity_type_combobox() def populate_entity_type_combobox(self): entity_types = self.database_query.get_entity_types() self.entityTypeComboBox.addItem("Alle verfügbaren Typen", None) # Default option for entity_type in entity_types: self.entityTypeComboBox.addItem(entity_type, entity_type) self.entityTypeComboBox.currentIndexChanged.connect(self.applyEntityTypeFilter) def execute_query_from_results_window(self): self.resultsTable.clear() self.resultsTable.setRowCount(0) self.query_text = self.databaseQueryLineEdit.text().strip() if not self.query_text: # Handle empty query case return self.query_status_label.setText(" Suche wird mit Suchbegriffen " + self.query_text + " durchgeführt...") self.query_thread = QueryThread(self.database_query, self.query_text) self.query_thread.queryCompleted.connect(self.on_query_completed) self.query_thread.start() self.query_progress_bar.setRange(0, 0) def set_query_and_execute(self, query_text): self.databaseQueryLineEdit.setText(query_text) self.execute_query_from_results_window() def on_query_completed(self, results_dict): self.resultsTable.setUpdatesEnabled(False) self.resultsTable.setSortingEnabled(False) # Disable sorting self.resultsTable.setRowCount(0) self.resultsTable.setColumnCount(len(COLUMN_NAMES)) self.sorted_results = sorted(results_dict.items(), key=lambda x: x[1], reverse=True) self.top_results = self.sorted_results[:512] self.query_status_label.setText(" Suche abgeschlossen // Anzahl Suchergebnisse: " + str(len(results_dict)) + " // Tabelle wird befüllt (das kann einige Zeit dauern)") for entities_id, score in self.top_results: self.insert_row(entities_id, score) self.query_progress_bar.setRange(0, 1) self.query_status_label.setText(" Suche abgeschlossen // Anzahl Suchergebnisse: " + str(len(results_dict)) + " (begrenzt auf die besten 512)") self.resultsTable.setHorizontalHeaderLabels(COLUMN_NAMES) self.adjust_column_widths() self.resultsTable.setSortingEnabled(True) self.resultsTable.sortByColumn(6, Qt.DescendingOrder) # Enable sorting self.resultsTable.setUpdatesEnabled(True) self.resultsTable.update() self.applyAllFilters() def insert_row(self, entities_id, score): try: with session_scope() as session: # Fetch the entity from the database entity = session.query(EntitiesTable).filter(EntitiesTable.entities_id == entities_id).first() if not entity: return # Skip if the entity is not found # Fetch related data distinct_entity = entity.entity.distinct_entity if entity.entity else "" entity_type = entity.regex_library.gui_name if entity.regex_library else "" file_name = entity.file.file_name if entity.file else "" line_number = str(entity.line_number) if entity.line_number is not None else "" entry_timestamp = entity.entry_timestamp.strftime("%Y-%m-%d %H:%M:%S") if entity.entry_timestamp else "" context_large = entity.context.context_large if entity.context else "" # Insert a new row in the table row_position = self.resultsTable.rowCount() self.resultsTable.insertRow(row_position) search_terms = self.query_text.split() self.highlight_delegate = HighlightDelegate(self.resultsTable, search_terms) # Set the values for each column self.resultsTable.setItem(row_position, 0, QTableWidgetItem(distinct_entity)) self.resultsTable.setItem(row_position, 1, QTableWidgetItem(entity_type)) self.resultsTable.setItem(row_position, 2, QTableWidgetItem(file_name)) self.resultsTable.setItem(row_position, 3, QTableWidgetItem(line_number)) self.resultsTable.setItem(row_position, 4, QTableWidgetItem(entry_timestamp)) context_widget = ScrollableTextWidget(context_large, search_terms, distinct_entity) self.resultsTable.setCellWidget(row_position, 5, context_widget) match_score_item = NumericTableWidgetItem(str(score)) self.resultsTable.setItem(row_position, 6, match_score_item) flag_button_widget = FlagButton(entities_id, entity.flag) self.resultsTable.setCellWidget(row_position, COLUMN_NAMES.index('Flag'), flag_button_widget) self.resultsTable.setItem(row_position, 8, QTableWidgetItem(str(entities_id))) self.resultsTable.setRowHeight(row_position, DEFAULT_ROW_HEIGHT) for column_index in range(self.resultsTable.columnCount()): if column_index in [0, 1, 2, 3, 4]: self.resultsTable.setItemDelegateForColumn(column_index, self.highlight_delegate) self.resultsTable.update() except Exception as e: logging.error(f"Error inserting row: {e}") def adjust_column_widths(self): for i, width in enumerate(COLUMN_WIDTHS): self.resultsTable.setColumnWidth(i, width) def flagVisibleItems(self): """Flag all visible items in the table.""" for row in range(self.resultsTable.rowCount()): if not self.resultsTable.isRowHidden(row): flag_button_widget = self.resultsTable.cellWidget(row, COLUMN_NAMES.index('Flag')) if flag_button_widget and not flag_button_widget.flag: flag_button_widget.toggle_flag() def unflagVisibleItems(self): """Unflag all visible items in the table.""" for row in range(self.resultsTable.rowCount()): if not self.resultsTable.isRowHidden(row): flag_button_widget = self.resultsTable.cellWidget(row, COLUMN_NAMES.index('Flag')) if flag_button_widget and flag_button_widget.flag: flag_button_widget.toggle_flag() def clearAllFlags(self): """Ask for confirmation and clear all flags in the table if confirmed.""" reply = QMessageBox.question(self, 'Confirm Action', "Sollen tatsächlich alle Markierungen entfernt werden?\nDas wirkt sich auf alle Einträge in der Datenbank aus!", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: # Clear all flags in the database FlagButton.clearAllFlagsInDatabase() # Update all flag buttons in the table for row in range(self.resultsTable.rowCount()): flag_button_widget = self.resultsTable.cellWidget(row, COLUMN_NAMES.index('Flag')) if flag_button_widget and flag_button_widget.flag: flag_button_widget.flag = False flag_button_widget.button.setText("_") flag_button_widget.update_button_style() # Update all flag buttons in the table for row in range(self.resultsTable.rowCount()): flag_button_widget = self.resultsTable.cellWidget(row, COLUMN_NAMES.index('Flag')) if flag_button_widget and flag_button_widget.flag: flag_button_widget.flag = False flag_button_widget.button.setText("_") flag_button_widget.update_button_style() def applyTextFilter(self, column_index, filter_text): if filter_text: for row in range(self.resultsTable.rowCount()): item = self.resultsTable.item(row, column_index) show_row = not filter_text or (item and filter_text.lower() in item.text().lower()) self.resultsTable.setRowHidden(row, not show_row) def applyDistinctEntityTextFilter(self): filter_text = self.distinct_entity_edit.text() self.applyTextFilter(COLUMN_NAMES.index('Distinct Entity'), filter_text) def applyFileNameTextFilter(self): filter_text = self.file_name_edit.text() self.applyTextFilter(COLUMN_NAMES.index('File Name'), filter_text) def applyLineNumberTextFilter(self): filter_text = self.line_number_edit.text() self.applyTextFilter(COLUMN_NAMES.index('Line Number'), filter_text) def applyMatchScoreTextFilter(self): filter_text = self.match_score_edit.text() self.applyTextFilter(COLUMN_NAMES.index('Match Score'), filter_text) def applyTimestampTextFilter(self): filter_text = self.timestamp_edit.text() self.applyTextFilter(COLUMN_NAMES.index('Timestamp'), filter_text) def applyIdentifierTextFilter(self): filter_text = self.identifier_edit.text() self.applyTextFilter(COLUMN_NAMES.index('Identifier'), filter_text) def applyEntityTypeFilter(self): selected_type = self.entityTypeComboBox.currentData() entity_type_column = COLUMN_NAMES.index('Entity Type') for row in range(self.resultsTable.rowCount()): item = self.resultsTable.item(row, entity_type_column) show_row = (selected_type is None or (item and item.text() == selected_type)) self.resultsTable.setRowHidden(row, not show_row) def applyTimestampFilter(self): start_date = self.startDateEdit.dateTime().toPyDateTime() end_date = self.endDateEdit.dateTime().toPyDateTime() timestamp_column = COLUMN_NAMES.index('Timestamp') for row in range(self.resultsTable.rowCount()): item = self.resultsTable.item(row, timestamp_column) if item and item.text() != "": row_timestamp = datetime.datetime.strptime(item.text(), "%Y-%m-%d %H:%M:%S") show_row = start_date <= row_timestamp <= end_date self.resultsTable.setRowHidden(row, not show_row) else: self.resultsTable.setRowHidden(row, False) def applyAllFilters(self): self.applyDistinctEntityTextFilter() self.applyFileNameTextFilter() self.applyLineNumberTextFilter() self.applyMatchScoreTextFilter() self.applyTimestampTextFilter() self.applyEntityTypeFilter() self.applyTimestampFilter() def clearAllFilters(self): self.entityTypeComboBox.setCurrentIndex(0) self.startDateEdit.setDateTime(QDateTime(QDateTime(2009, 1, 1, 0, 0, 0))) self.endDateEdit.setDateTime(QDateTime.currentDateTime()) self.distinct_entity_edit.clear() self.file_name_edit.clear() self.line_number_edit.clear() self.match_score_edit.clear() self.timestamp_edit.clear() self.identifier_edit.clear() self.applyAllFilters() class QueryLineEdit(QLineEdit): returnPressed = pyqtSignal() def keyPressEvent(self, event): if event.key() == Qt.Key_Return: self.returnPressed.emit() else: super().keyPressEvent(event) class HighlightDelegate(QStyledItemDelegate): def __init__(self, parent=None, search_terms=None): super().__init__(parent) self.search_terms = search_terms or [] def paint(self, painter, option, index): painter.save() # Set text color and other options options = QTextOption() options.setWrapMode(QTextOption.WrapAtWordBoundaryOrAnywhere) document = QTextDocument() document.setDefaultTextOption(options) document.setDefaultFont(option.font) # Prepare highlighted text text = index.model().data(index) highlighted_text = self.get_highlighted_text(text) document.setHtml(highlighted_text) # Set the width of the document to the cell width document.setTextWidth(option.rect.width()) # Draw the contents painter.translate(option.rect.topLeft()) document.drawContents(painter) painter.restore() def get_highlighted_text(self, text): if text is None: text = "" text_with_color = f"{text}" for term in self.search_terms: # Retain the '+' at the beginning and strip other special characters is_positive = term.startswith('+') clean_term = re.sub(r'[^\w\s]', '', term.lstrip('+-')).lower() if is_positive and clean_term.lower() in text.lower(): # Use regex for case-insensitive search and replace regex = re.compile(re.escape(clean_term), re.IGNORECASE) highlighted_term = f"{clean_term}" text_with_color = regex.sub(highlighted_term, text_with_color) return text_with_color.replace("\n", "
") class ScrollableTextWidget(QWidget): def __init__(self, text, search_terms, distinct_entity, parent=None): super().__init__(parent) layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) self.text_edit = CustomTextEdit(self) self.text_edit.setReadOnly(True) # Apply styles including scrollbar styles self.text_edit.setStyleSheet(""" QTextEdit { background-color: #2A2F35; /* Dark blue-ish background */ color: white; /* White text */ } QTextEdit QScrollBar:vertical { border: none; background-color: #3A3F44; /* Dark scrollbar background */ width: 8px; /* Width of the scrollbar */ } QTextEdit QScrollBar::handle:vertical { background-color: #6E6E6E; /* Scroll handle color */ border-radius: 4px; /* Rounded corners for the handle */ } QTextEdit QScrollBar::add-line:vertical, QTextEdit QScrollBar::sub-line:vertical { background: none; } """) # Set the text with highlighting self.setHighlightedText(text, search_terms, distinct_entity) layout.addWidget(self.text_edit) # Scroll to the distinct entity self.scroll_to_text(distinct_entity) def setHighlightedText(self, text, search_terms, distinct_entity): if text is None: text = "" # Wrap the original text in a span to maintain color text_with_color = f"{text}" # Highlight distinct entity in a different color if distinct_entity: distinct_entity_escaped = html.escape(distinct_entity) text_with_color = re.sub( re.escape(distinct_entity_escaped), lambda match: f"{match.group()}", text_with_color, flags=re.IGNORECASE ) for term in search_terms: # Check if the term starts with '+' is_positive = term.startswith('+') clean_term = re.sub(r'[^\w\s]', '', term.lstrip('+-')) # If the term starts with '+', highlight all matches regardless of case if is_positive or clean_term.lower() in text.lower(): regex = re.compile(re.escape(clean_term), re.IGNORECASE) highlighted_term = f"{clean_term}" text_with_color = regex.sub(highlighted_term, text_with_color) self.text_edit.setHtml(text_with_color.replace("\n", "
")) def scroll_to_text(self, text): if text: cursor = self.text_edit.document().find(text) self.text_edit.setTextCursor(cursor) class CustomTextEdit(QTextEdit): def __init__(self, parent=None): super().__init__(parent) self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) # Enable vertical scrollbar as needed def wheelEvent(self, event): # Always handle the wheel event within QTextEdit super().wheelEvent(event) # Stop propagation of the event to parent if self.verticalScrollBar().isVisible(): event.accept() else: event.ignore() class FlagButton(QWidget): def __init__(self, entities_id, flag, parent=None): super().__init__(parent) self.entities_id = entities_id self.flag = flag self.layout = QHBoxLayout(self) self.button = QPushButton("FLAG" if flag else "_", self) self.update_button_style() self.button.clicked.connect(self.toggle_flag) self.layout.addWidget(self.button) self.layout.setContentsMargins(0, 0, 0, 0) self.setLayout(self.layout) def toggle_flag(self): # Toggle the flag self.flag = not self.flag self.button.setText("FLAG" if self.flag else "_") self.update_button_style() # Update the flag in the database with session_scope() as session: entity = session.query(EntitiesTable).filter(EntitiesTable.entities_id == self.entities_id).first() if entity: entity.flag = self.flag session.commit() @staticmethod def clearAllFlagsInDatabase(): with session_scope() as session: entities = session.query(EntitiesTable).all() for entity in entities: entity.flag = False session.commit() def update_button_style(self): if self.flag: self.button.setStyleSheet("QPushButton { background-color: yellow; color: black; }") else: self.button.setStyleSheet("") class NumericTableWidgetItem(QTableWidgetItem): def __lt__(self, other): return float(self.text()) < float(other.text())