pyside6-datatable-widget 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,237 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyside6-datatable-widget
3
+ Version: 1.0.0
4
+ Summary: A PySide6 DataTable widget with jQuery DataTable-like functionality
5
+ Home-page: https://github.com/ultra-bugs/pyside6-datatable-widget
6
+ Author: Zuko
7
+ Author-email: tansautn@gmail.com
8
+ Project-URL: Bug Tracker, https://github.com/ultra-bugs/pyside6-datatable-widget/issues
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Topic :: Software Development :: User Interfaces
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ Requires-Dist: PySide6>=6.1.0
18
+ Dynamic: author
19
+ Dynamic: author-email
20
+ Dynamic: classifier
21
+ Dynamic: description
22
+ Dynamic: description-content-type
23
+ Dynamic: home-page
24
+ Dynamic: project-url
25
+ Dynamic: requires-dist
26
+ Dynamic: requires-python
27
+ Dynamic: summary
28
+
29
+ # PySide6 DataTable
30
+
31
+ A powerful DataTable widget for PySide6 applications with functionality similar to jQuery DataTable.
32
+
33
+ ## Features
34
+
35
+ - **Customizable Table**: Easily configure columns, types, and formatting
36
+ - **Data Type Detection**: Automatically detect and handle different data types
37
+ - **Type-based Sorting**: Different column types sort appropriately
38
+ - **Search Functionality**: Global and column-specific search
39
+ - **Row Collapsing**: Support for expandable/collapsible rows
40
+ - **Pagination**: Built-in pagination with configurable page sizes
41
+ - **Column Visibility**: Show/hide columns easily
42
+ - **Aggregation Functions**: Calculate sums, averages, percentages, etc.
43
+ - **Custom Formatting**: Format data display for different column types
44
+ - **Observer Pattern**: Event-driven architecture for clear code organization
45
+
46
+ ## Installation
47
+
48
+ ```bash
49
+ pip install pyside6-datatable-widget
50
+ ```
51
+
52
+ ## Basic Usage
53
+
54
+ ```python
55
+ import sys
56
+ from PySide6.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget
57
+ from datatable import DataTable, DataType
58
+
59
+ class MainWindow(QMainWindow):
60
+ def __init__(self):
61
+ super().__init__()
62
+ self.setWindowTitle("DataTable Example")
63
+ self.resize(800, 600)
64
+
65
+ # Create central widget and layout
66
+ central_widget = QWidget()
67
+ self.setCentralWidget(central_widget)
68
+ layout = QVBoxLayout(central_widget)
69
+
70
+ # Create DataTable
71
+ self.data_table = DataTable()
72
+ layout.addWidget(self.data_table)
73
+
74
+ # Set up columns (key, header, data_type)
75
+ columns = [
76
+ ("id", "ID", DataType.NUMERIC),
77
+ ("name", "Name", DataType.STRING),
78
+ ("age", "Age", DataType.NUMERIC),
79
+ ("active", "Active", DataType.BOOLEAN)
80
+ ]
81
+
82
+ # Set up data
83
+ data = [
84
+ {"id": 1, "name": "John", "age": 30, "active": True},
85
+ {"id": 2, "name": "Jane", "age": 25, "active": False},
86
+ {"id": 3, "name": "Bob", "age": 40, "active": True}
87
+ ]
88
+
89
+ # Apply to table
90
+ self.data_table.setColumns(columns)
91
+ self.data_table.setData(data)
92
+
93
+ # Connect signals
94
+ self.data_table.rowSelected.connect(self.on_row_selected)
95
+
96
+ def on_row_selected(self, row, row_data):
97
+ print(f"Row {row} selected: {row_data}")
98
+
99
+ if __name__ == "__main__":
100
+ app = QApplication(sys.argv)
101
+ window = MainWindow()
102
+ window.show()
103
+ sys.exit(app.exec())
104
+ ```
105
+
106
+ ## Advanced Features
107
+
108
+ ### Custom Column Formatting
109
+
110
+ ```python
111
+ from datatable import DataTableModel
112
+
113
+ # Create model with custom formatting
114
+ model = DataTableModel()
115
+ model.setFormattingFunction("price", lambda value: f"${value:.2f}")
116
+ model.setFormattingFunction("percentage", lambda value: f"{value:.1f}%")
117
+
118
+ # Set model to table
119
+ data_table.setModel(model)
120
+ ```
121
+
122
+ ### Row Collapsing
123
+
124
+ ```python
125
+ # Enable row collapsing
126
+ data_table.enableRowCollapsing(True, "subrows")
127
+
128
+ # Example data with subrows
129
+ data = [
130
+ {
131
+ "id": 1,
132
+ "name": "Category A",
133
+ "total": 1000,
134
+ "subrows": [
135
+ {"id": 101, "name": "Item A1", "total": 500},
136
+ {"id": 102, "name": "Item A2", "total": 500}
137
+ ]
138
+ },
139
+ {
140
+ "id": 2,
141
+ "name": "Category B",
142
+ "total": 2000,
143
+ "subrows": [
144
+ {"id": 201, "name": "Item B1", "total": 1200},
145
+ {"id": 202, "name": "Item B2", "total": 800}
146
+ ]
147
+ }
148
+ ]
149
+
150
+ data_table.setData(data)
151
+
152
+ # Connect expansion signals
153
+ data_table.rowExpanded.connect(lambda row, data: print(f"Row {row} expanded"))
154
+ data_table.rowCollapsed.connect(lambda row, data: print(f"Row {row} collapsed"))
155
+ ```
156
+
157
+ ### Aggregation Functions
158
+
159
+ ```python
160
+ # Get aggregate values
161
+ total = data_table.getAggregateValue("amount", "sum")
162
+ average = data_table.getAggregateValue("amount", "avg")
163
+ count = data_table.getAggregateValue("id", "count")
164
+
165
+ # Calculate percentage of a row value relative to total
166
+ row_data = data_table.getSelectedRow()
167
+ if row_data:
168
+ amount = row_data["amount"]
169
+ total = data_table.getAggregateValue("amount", "sum")
170
+ percentage = data_table.calculateRowPercentage(row_index, "amount")
171
+ ```
172
+
173
+ ### Custom Search Functions
174
+
175
+ ```python
176
+ # Set custom search function for a column
177
+ model.setSearchFunction("complex_data", lambda value, term: term in str(value["name"]))
178
+
179
+ # Search in table
180
+ data_table.search("search term")
181
+
182
+ # Search specific column
183
+ matching_rows = model.searchColumn("name", "John")
184
+ ```
185
+
186
+ ## API Reference
187
+
188
+ ### DataTable
189
+
190
+ Main widget class that provides the UI and functionality.
191
+
192
+ #### Methods
193
+
194
+ - `setData(data)`: Set table data
195
+ - `setColumns(columns)`: Set table columns
196
+ - `setVisibleColumns(columns)`: Set which columns are visible
197
+ - `enableRowCollapsing(enabled, child_row_key)`: Enable/disable row collapsing
198
+ - `search(term)`: Search the table
199
+ - `sort(column_key, order)`: Sort the table
200
+ - `setPage(page)`: Set current page
201
+ - `setRowsPerPage(rows)`: Set rows per page
202
+ - `getData()`: Get current table data
203
+ - `getSelectedRow()`: Get selected row data
204
+ - `getAggregateValue(column_key, agg_type)`: Get aggregate value for column
205
+
206
+ #### Signals
207
+
208
+ - `pageChanged(page)`: Emitted when page changes
209
+ - `rowSelected(row, row_data)`: Emitted when row is selected
210
+ - `rowExpanded(row, row_data)`: Emitted when row is expanded
211
+ - `rowCollapsed(row, row_data)`: Emitted when row is collapsed
212
+ - `dataFiltered(rows)`: Emitted when data is filtered
213
+ - `sortChanged(column, order)`: Emitted when sort order changes
214
+
215
+ ### DataTableModel
216
+
217
+ Model class that manages data and operations.
218
+
219
+ #### Methods
220
+
221
+ - `setData(data)`: Set model data
222
+ - `setColumns(columns)`: Set model columns
223
+ - `setFormattingFunction(column_key, func)`: Set formatting function
224
+ - `setEditableColumns(editable_columns)`: Set which columns are editable
225
+ - `setVisibleColumns(visible_columns)`: Set which columns are visible
226
+ - `setSearchFunction(column_key, func)`: Set search function
227
+ - `setSortFunction(column_key, func)`: Set sort function
228
+ - `setAggregationFunction(column_key, agg_type, func)`: Set aggregation function
229
+ - `enableRowCollapsing(enabled, child_row_key)`: Enable row collapsing
230
+ - `search(term)`: Search all rows
231
+ - `searchColumn(column_key, term)`: Search specific column
232
+ - `aggregate(column_key, agg_type)`: Aggregate column values
233
+ - `calculateRowPercentage(row_index, column_key)`: Calculate row percentage
234
+
235
+ ## License
236
+
237
+ MIT
@@ -0,0 +1,209 @@
1
+ # PySide6 DataTable
2
+
3
+ A powerful DataTable widget for PySide6 applications with functionality similar to jQuery DataTable.
4
+
5
+ ## Features
6
+
7
+ - **Customizable Table**: Easily configure columns, types, and formatting
8
+ - **Data Type Detection**: Automatically detect and handle different data types
9
+ - **Type-based Sorting**: Different column types sort appropriately
10
+ - **Search Functionality**: Global and column-specific search
11
+ - **Row Collapsing**: Support for expandable/collapsible rows
12
+ - **Pagination**: Built-in pagination with configurable page sizes
13
+ - **Column Visibility**: Show/hide columns easily
14
+ - **Aggregation Functions**: Calculate sums, averages, percentages, etc.
15
+ - **Custom Formatting**: Format data display for different column types
16
+ - **Observer Pattern**: Event-driven architecture for clear code organization
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ pip install pyside6-datatable-widget
22
+ ```
23
+
24
+ ## Basic Usage
25
+
26
+ ```python
27
+ import sys
28
+ from PySide6.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget
29
+ from datatable import DataTable, DataType
30
+
31
+ class MainWindow(QMainWindow):
32
+ def __init__(self):
33
+ super().__init__()
34
+ self.setWindowTitle("DataTable Example")
35
+ self.resize(800, 600)
36
+
37
+ # Create central widget and layout
38
+ central_widget = QWidget()
39
+ self.setCentralWidget(central_widget)
40
+ layout = QVBoxLayout(central_widget)
41
+
42
+ # Create DataTable
43
+ self.data_table = DataTable()
44
+ layout.addWidget(self.data_table)
45
+
46
+ # Set up columns (key, header, data_type)
47
+ columns = [
48
+ ("id", "ID", DataType.NUMERIC),
49
+ ("name", "Name", DataType.STRING),
50
+ ("age", "Age", DataType.NUMERIC),
51
+ ("active", "Active", DataType.BOOLEAN)
52
+ ]
53
+
54
+ # Set up data
55
+ data = [
56
+ {"id": 1, "name": "John", "age": 30, "active": True},
57
+ {"id": 2, "name": "Jane", "age": 25, "active": False},
58
+ {"id": 3, "name": "Bob", "age": 40, "active": True}
59
+ ]
60
+
61
+ # Apply to table
62
+ self.data_table.setColumns(columns)
63
+ self.data_table.setData(data)
64
+
65
+ # Connect signals
66
+ self.data_table.rowSelected.connect(self.on_row_selected)
67
+
68
+ def on_row_selected(self, row, row_data):
69
+ print(f"Row {row} selected: {row_data}")
70
+
71
+ if __name__ == "__main__":
72
+ app = QApplication(sys.argv)
73
+ window = MainWindow()
74
+ window.show()
75
+ sys.exit(app.exec())
76
+ ```
77
+
78
+ ## Advanced Features
79
+
80
+ ### Custom Column Formatting
81
+
82
+ ```python
83
+ from datatable import DataTableModel
84
+
85
+ # Create model with custom formatting
86
+ model = DataTableModel()
87
+ model.setFormattingFunction("price", lambda value: f"${value:.2f}")
88
+ model.setFormattingFunction("percentage", lambda value: f"{value:.1f}%")
89
+
90
+ # Set model to table
91
+ data_table.setModel(model)
92
+ ```
93
+
94
+ ### Row Collapsing
95
+
96
+ ```python
97
+ # Enable row collapsing
98
+ data_table.enableRowCollapsing(True, "subrows")
99
+
100
+ # Example data with subrows
101
+ data = [
102
+ {
103
+ "id": 1,
104
+ "name": "Category A",
105
+ "total": 1000,
106
+ "subrows": [
107
+ {"id": 101, "name": "Item A1", "total": 500},
108
+ {"id": 102, "name": "Item A2", "total": 500}
109
+ ]
110
+ },
111
+ {
112
+ "id": 2,
113
+ "name": "Category B",
114
+ "total": 2000,
115
+ "subrows": [
116
+ {"id": 201, "name": "Item B1", "total": 1200},
117
+ {"id": 202, "name": "Item B2", "total": 800}
118
+ ]
119
+ }
120
+ ]
121
+
122
+ data_table.setData(data)
123
+
124
+ # Connect expansion signals
125
+ data_table.rowExpanded.connect(lambda row, data: print(f"Row {row} expanded"))
126
+ data_table.rowCollapsed.connect(lambda row, data: print(f"Row {row} collapsed"))
127
+ ```
128
+
129
+ ### Aggregation Functions
130
+
131
+ ```python
132
+ # Get aggregate values
133
+ total = data_table.getAggregateValue("amount", "sum")
134
+ average = data_table.getAggregateValue("amount", "avg")
135
+ count = data_table.getAggregateValue("id", "count")
136
+
137
+ # Calculate percentage of a row value relative to total
138
+ row_data = data_table.getSelectedRow()
139
+ if row_data:
140
+ amount = row_data["amount"]
141
+ total = data_table.getAggregateValue("amount", "sum")
142
+ percentage = data_table.calculateRowPercentage(row_index, "amount")
143
+ ```
144
+
145
+ ### Custom Search Functions
146
+
147
+ ```python
148
+ # Set custom search function for a column
149
+ model.setSearchFunction("complex_data", lambda value, term: term in str(value["name"]))
150
+
151
+ # Search in table
152
+ data_table.search("search term")
153
+
154
+ # Search specific column
155
+ matching_rows = model.searchColumn("name", "John")
156
+ ```
157
+
158
+ ## API Reference
159
+
160
+ ### DataTable
161
+
162
+ Main widget class that provides the UI and functionality.
163
+
164
+ #### Methods
165
+
166
+ - `setData(data)`: Set table data
167
+ - `setColumns(columns)`: Set table columns
168
+ - `setVisibleColumns(columns)`: Set which columns are visible
169
+ - `enableRowCollapsing(enabled, child_row_key)`: Enable/disable row collapsing
170
+ - `search(term)`: Search the table
171
+ - `sort(column_key, order)`: Sort the table
172
+ - `setPage(page)`: Set current page
173
+ - `setRowsPerPage(rows)`: Set rows per page
174
+ - `getData()`: Get current table data
175
+ - `getSelectedRow()`: Get selected row data
176
+ - `getAggregateValue(column_key, agg_type)`: Get aggregate value for column
177
+
178
+ #### Signals
179
+
180
+ - `pageChanged(page)`: Emitted when page changes
181
+ - `rowSelected(row, row_data)`: Emitted when row is selected
182
+ - `rowExpanded(row, row_data)`: Emitted when row is expanded
183
+ - `rowCollapsed(row, row_data)`: Emitted when row is collapsed
184
+ - `dataFiltered(rows)`: Emitted when data is filtered
185
+ - `sortChanged(column, order)`: Emitted when sort order changes
186
+
187
+ ### DataTableModel
188
+
189
+ Model class that manages data and operations.
190
+
191
+ #### Methods
192
+
193
+ - `setData(data)`: Set model data
194
+ - `setColumns(columns)`: Set model columns
195
+ - `setFormattingFunction(column_key, func)`: Set formatting function
196
+ - `setEditableColumns(editable_columns)`: Set which columns are editable
197
+ - `setVisibleColumns(visible_columns)`: Set which columns are visible
198
+ - `setSearchFunction(column_key, func)`: Set search function
199
+ - `setSortFunction(column_key, func)`: Set sort function
200
+ - `setAggregationFunction(column_key, agg_type, func)`: Set aggregation function
201
+ - `enableRowCollapsing(enabled, child_row_key)`: Enable row collapsing
202
+ - `search(term)`: Search all rows
203
+ - `searchColumn(column_key, term)`: Search specific column
204
+ - `aggregate(column_key, agg_type)`: Aggregate column values
205
+ - `calculateRowPercentage(row_index, column_key)`: Calculate row percentage
206
+
207
+ ## License
208
+
209
+ MIT
@@ -0,0 +1,30 @@
1
+ # M""""""""`M dP
2
+ # Mmmmmm .M 88
3
+ # MMMMP .MMM dP dP 88 .dP .d8888b.
4
+ # MMP .MMMMM 88 88 88888" 88' `88
5
+ # M' .MMMMMMM 88. .88 88 `8b. 88. .88
6
+ # M M `88888P' dP `YP `88888P'
7
+ # MMMMMMMMMMM -*- Created by Zuko -*-
8
+ #
9
+ # * * * * * * * * * * * * * * * * * * * * *
10
+ # * - - - F.R.E.E.M.I.N.D - - - *
11
+ # * - Copyright © 2025 (Z) Programing - *
12
+ # * - - All Rights Reserved - - *
13
+ # * * * * * * * * * * * * * * * * * * * * *
14
+
15
+ from .widgets.datatable import DataTable
16
+ from .models.datatable_model import DataTableModel, DataType, SortOrder
17
+ from .models.delegates import CellDelegate, NumericDelegate, DateDelegate, BooleanDelegate
18
+
19
+ __version__ = '1.0.0'
20
+
21
+ __all__ = [
22
+ 'DataTable',
23
+ 'DataTableModel',
24
+ 'DataType',
25
+ 'SortOrder',
26
+ 'CellDelegate',
27
+ 'NumericDelegate',
28
+ 'DateDelegate',
29
+ 'BooleanDelegate',
30
+ ]
@@ -0,0 +1,18 @@
1
+ # M""""""""`M dP
2
+ # Mmmmmm .M 88
3
+ # MMMMP .MMM dP dP 88 .dP .d8888b.
4
+ # MMP .MMMMM 88 88 88888" 88' `88
5
+ # M' .MMMMMMM 88. .88 88 `8b. 88. .88
6
+ # M M `88888P' dP `YP `88888P'
7
+ # MMMMMMMMMMM -*- Created by Zuko -*-
8
+ #
9
+ # * * * * * * * * * * * * * * * * * * * * *
10
+ # * - - - F.R.E.E.M.I.N.D - - - *
11
+ # * - Copyright © 2025 (Z) Programing - *
12
+ # * - - All Rights Reserved - - *
13
+ # * * * * * * * * * * * * * * * * * * * * *
14
+
15
+ from .observer import Publisher, Subscriber, singleton
16
+ from .widget_manager import WidgetManager
17
+
18
+ __all__ = ['Publisher', 'Subscriber', 'singleton', 'WidgetManager']
@@ -0,0 +1,116 @@
1
+ # M""""""""`M dP
2
+ # Mmmmmm .M 88
3
+ # MMMMP .MMM dP dP 88 .dP .d8888b.
4
+ # MMP .MMMMM 88 88 88888" 88' `88
5
+ # M' .MMMMMMM 88. .88 88 `8b. 88. .88
6
+ # M M `88888P' dP `YP `88888P'
7
+ # MMMMMMMMMMM -*- Created by Zuko -*-
8
+ #
9
+ # * * * * * * * * * * * * * * * * * * * * *
10
+ # * - - - F.R.E.E.M.I.N.D - - - *
11
+ # * - Copyright © 2025 (Z) Programing - *
12
+ # * - - All Rights Reserved - - *
13
+ # * * * * * * * * * * * * * * * * * * * * *
14
+
15
+ import importlib
16
+ from abc import ABC, abstractmethod
17
+ from typing import Dict, List, Any
18
+
19
+ from PySide6.QtWidgets import QWidget
20
+
21
+ from .observer import Publisher
22
+ from .widget_manager import WidgetManager
23
+
24
+
25
+ class ControllerMeta(type(QWidget), type(ABC)):
26
+ required_attrs = ['slot_map']
27
+
28
+ def __new__(cls, name, bases, dct):
29
+ required_attrs = cls.required_attrs
30
+ for attr in required_attrs:
31
+ if attr not in dct:
32
+ raise ValueError(f'Attribute: {attr} is required but not defined in {name}')
33
+ return super().__new__(cls, name, bases, dct)
34
+
35
+
36
+ class BaseController(QWidget, ABC, metaclass=ControllerMeta):
37
+ """Base class for all controllers in datatable package"""
38
+
39
+ slot_map: Dict[str, List[str]] = {}
40
+ signal_connected = False
41
+ is_auto_connect_signal = True
42
+
43
+ def __init__(self, parent=None):
44
+ super().__init__(parent)
45
+ self.widget_manager = WidgetManager(self)
46
+ self.controller_name = self.__class__.__name__
47
+ self.publisher = Publisher()
48
+ self.handler = None
49
+
50
+ # Setup UI
51
+ self.setupUi(self)
52
+
53
+ if not self.is_auto_connect_signal:
54
+ return
55
+
56
+ # Auto-loading handler
57
+ module_path = self.__module__
58
+ module_parts = module_path.split('.')
59
+ if len(module_parts) > 1:
60
+ # Try to find handler in the same package
61
+ base_module = '.'.join(module_parts[:-1])
62
+ handler_module_name = f'{base_module}.handlers.{self.controller_name}Handler'
63
+ print(handler_module_name)
64
+ try:
65
+ handler_module = importlib.import_module(handler_module_name)
66
+ handler_class = getattr(handler_module, f'{self.controller_name}Handler')
67
+ self.handler = handler_class(
68
+ widget_manager=self.widget_manager, events=list(self.slot_map.keys())
69
+ )
70
+ except (ImportError, AttributeError):
71
+ # If not found, try alternative locations
72
+ pass
73
+
74
+ if self.is_auto_connect_signal and self.handler:
75
+ self._connect_signals()
76
+
77
+ @abstractmethod
78
+ def setupUi(self, widget):
79
+ """Set up the UI components"""
80
+ pass
81
+
82
+ def _connect_signals(self):
83
+ """Connect signals to slots based on slot_map"""
84
+ if not hasattr(self, 'slot_map'):
85
+ raise ValueError(
86
+ f'{self.__class__.__name__} must define slot_map to use auto connect signals'
87
+ )
88
+
89
+ subscriber = self.handler
90
+ for event in self.handler.events:
91
+ if event in self.slot_map:
92
+ signal_info = self.slot_map.get(event)
93
+ if signal_info is None:
94
+ continue
95
+
96
+ if callable(signal_info):
97
+ signal_info(self.handler, self.publisher)
98
+ continue
99
+
100
+ try:
101
+ widget = self.widget_manager.get(signal_info[0])
102
+ self.publisher.connect(
103
+ widget,
104
+ signal_info[1],
105
+ event,
106
+ data={'widget': widget},
107
+ )
108
+ print(f'Connected {signal_info[1]} signal to {event} event')
109
+ print(widget, event, signal_info)
110
+ except (AttributeError, Exception) as e:
111
+ print(f'Error connecting signal: {e}')
112
+ continue
113
+
114
+ self.publisher.subscribe(subscriber=subscriber, event=event)
115
+
116
+ self.signal_connected = True