fletable 0.0.1__py3-none-any.whl

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.
fletable/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ from .editable_table import EditableTable, FieldConfig, ForeignKeyConfig
2
+ from .sql_table import SqlTable
3
+ from .utils import LoginView
@@ -0,0 +1,237 @@
1
+ from dataclasses import dataclass
2
+
3
+ import flet as ft
4
+
5
+
6
+ @dataclass
7
+ class ForeignKeyConfig:
8
+ table: str
9
+ id_column: str
10
+ label_column: str
11
+
12
+
13
+ @dataclass
14
+ class FieldConfig:
15
+ label: str
16
+ foreign_key: ForeignKeyConfig | None = None
17
+
18
+
19
+ class EditableTable:
20
+ def __init__(
21
+ self,
22
+ cursor,
23
+ table_name: str,
24
+ field_mapping: dict[str, FieldConfig | str],
25
+ width: int = 800,
26
+ height: int = 400,
27
+ ):
28
+ self.cursor = cursor
29
+ self.table_name = table_name
30
+ # Приводим значения словаря к FieldConfig для типобезопасных подсказок
31
+ self.field_configs: dict[str, FieldConfig] = {
32
+ name: cfg if isinstance(cfg, FieldConfig) else FieldConfig(label=str(cfg))
33
+ for name, cfg in field_mapping.items()
34
+ }
35
+ self.width = width
36
+ self.height = height
37
+ self.dropdown_options = self._generate_dropdown_options()
38
+ self.row_checkboxes: list[tuple[ft.Checkbox, dict]] = [] # (checkbox, row_data)
39
+ self.header_checkbox: ft.Checkbox = None
40
+
41
+ def _generate_dropdown_options(self):
42
+ options = {}
43
+ for field, cfg in self.field_configs.items():
44
+ # Настройки FK: явно через FieldConfig.foreign_key или по шаблону *_id
45
+ ref_cfg = cfg.foreign_key
46
+ if ref_cfg or (field.endswith("_id") and field != f"{self.table_name}_id"):
47
+ ref_table = ref_cfg.table if ref_cfg else field.replace("_id", "")
48
+ id_column = ref_cfg.id_column if ref_cfg else field
49
+ label_column = ref_cfg.label_column if ref_cfg else ref_table
50
+ try:
51
+ self.cursor.execute(
52
+ f"SELECT {id_column}, {label_column} FROM {ref_table}"
53
+ )
54
+ results = self.cursor.fetchall()
55
+ options[field] = [(str(row[0]), str(row[1])) for row in results]
56
+ except Exception as e:
57
+ print(f"[WARN] Не удалось загрузить dropdown для {field}: {e}")
58
+ return options
59
+
60
+ def create_add_form(self):
61
+ new_fields = {}
62
+ input_controls = []
63
+
64
+ for field in list(self.field_configs.keys())[1:]: # Пропускаем ID
65
+ if field in self.dropdown_options:
66
+ ctrl = ft.Dropdown(
67
+ options=[
68
+ ft.dropdown.Option(key=str(k), text=str(v))
69
+ for k, v in self.dropdown_options[field]
70
+ ],
71
+ value=None,
72
+ expand=True,
73
+ label=self.field_configs[field].label,
74
+ )
75
+ else:
76
+ ctrl = ft.TextField(label=self.field_configs[field].label, expand=True)
77
+
78
+ new_fields[field] = ctrl
79
+ input_controls.append(ctrl)
80
+
81
+ def handle_add():
82
+ try:
83
+ fields = ", ".join(new_fields.keys())
84
+ placeholders = ", ".join(["%s"] * len(new_fields))
85
+ values = [ctrl.value for ctrl in new_fields.values()]
86
+ insert_query = (
87
+ f"INSERT INTO {self.table_name} ({fields}) VALUES ({placeholders})"
88
+ )
89
+ self.cursor.execute(insert_query, values)
90
+ self.cursor.connection.commit()
91
+ for ctrl in input_controls:
92
+ ctrl.value = ""
93
+ print("[INFO] Запись добавлена:", values)
94
+ return True, "Успешно добавлено"
95
+ except Exception as ex:
96
+ print("[ERROR] Ошибка добавления:", str(ex))
97
+ return False, f"Ошибка: {str(ex)}"
98
+
99
+ form_row = ft.Row(input_controls)
100
+ return form_row, handle_add
101
+
102
+ def create_table(self):
103
+ db_fields = list(self.field_configs.keys())
104
+ query = f"SELECT {', '.join(db_fields)} FROM {self.table_name}"
105
+ self.cursor.execute(query)
106
+ data = self.cursor.fetchall()
107
+
108
+ # Очищаем список чекбоксов перед созданием новой таблицы
109
+ self.row_checkboxes = []
110
+
111
+ # Создаём чекбокс для заголовка (выбрать все)
112
+ def on_header_checkbox_change(e):
113
+ for checkbox, _ in self.row_checkboxes:
114
+ checkbox.value = self.header_checkbox.value
115
+ e.page.update()
116
+
117
+ self.header_checkbox = ft.Checkbox(
118
+ value=False, on_change=on_header_checkbox_change
119
+ )
120
+
121
+ rows = []
122
+ for row in data:
123
+ record_id = row[0]
124
+ cells = []
125
+ field_controls = {}
126
+
127
+ # Собираем данные строки в словарь
128
+ row_data = {field: value for field, value in zip(db_fields, row)}
129
+
130
+ # Создаём чекбокс для строки
131
+ row_checkbox = ft.Checkbox(value=False)
132
+ self.row_checkboxes.append((row_checkbox, row_data))
133
+ cells.append(ft.DataCell(row_checkbox))
134
+
135
+ for field, value in zip(db_fields, row):
136
+ if field == db_fields[0]:
137
+ cells.append(ft.DataCell(ft.Text(str(value))))
138
+ continue
139
+
140
+ if field in self.dropdown_options:
141
+ ctrl = ft.Container(
142
+ content=ft.Dropdown(
143
+ options=[
144
+ ft.dropdown.Option(key=str(k), text=v)
145
+ for k, v in self.dropdown_options[field]
146
+ ],
147
+ value=str(value),
148
+ expand=True,
149
+ ),
150
+ padding=5,
151
+ expand=True,
152
+ )
153
+ else:
154
+ ctrl = ft.Container(
155
+ content=ft.TextField(
156
+ value=str(value), border=ft.InputBorder.NONE, expand=True
157
+ ),
158
+ padding=5,
159
+ expand=True,
160
+ )
161
+
162
+ field_controls[field] = ctrl.content
163
+ cells.append(ft.DataCell(ctrl))
164
+
165
+ def make_save_callback(record_id, controls):
166
+ def save(e):
167
+ try:
168
+ update_fields = ", ".join(
169
+ f"{field} = %s" for field in controls.keys()
170
+ )
171
+ values = [c.value for c in controls.values()]
172
+ update_query = f"UPDATE {self.table_name} SET {update_fields} WHERE {db_fields[0]} = %s"
173
+ self.cursor.execute(update_query, (*values, record_id))
174
+ self.cursor.connection.commit()
175
+ e.page.open(ft.SnackBar(ft.Text("Изменения сохранены")))
176
+ print(f"[LOG] Updated record {record_id} with values {values}")
177
+ except Exception as ex:
178
+ e.page.open(ft.SnackBar(ft.Text(f"Ошибка: {str(ex)}")))
179
+ e.page.update()
180
+
181
+ return save
182
+
183
+ save_button = ft.IconButton(
184
+ icon=ft.Icons.SAVE,
185
+ tooltip="Сохранить",
186
+ on_click=make_save_callback(record_id, field_controls),
187
+ )
188
+ delete_button = ft.IconButton(
189
+ icon=ft.Icons.DELETE,
190
+ tooltip="Удалить",
191
+ on_click=self._handle_delete(record_id),
192
+ )
193
+ cells.append(ft.DataCell(ft.Row([save_button, delete_button], spacing=0)))
194
+
195
+ rows.append(ft.DataRow(cells=cells))
196
+
197
+ # Колонки: чекбокс + поля из mapping + действия
198
+ columns = (
199
+ [ft.DataColumn(self.header_checkbox)]
200
+ + [
201
+ ft.DataColumn(ft.Text(self.field_configs[field].label))
202
+ for field in db_fields
203
+ ]
204
+ + [ft.DataColumn(ft.Text("Действия"))]
205
+ )
206
+
207
+ return ft.DataTable(
208
+ columns=columns,
209
+ rows=rows,
210
+ # width=self.width - 20
211
+ )
212
+
213
+ def get_selected_rows(self) -> list[dict]:
214
+ """
215
+ Возвращает список словарей с данными выделенных строк.
216
+ Ключи словаря соответствуют полям из field_mapping.
217
+ """
218
+ selected = []
219
+ for checkbox, row_data in self.row_checkboxes:
220
+ if checkbox.value:
221
+ selected.append(row_data.copy())
222
+ return selected
223
+
224
+ def _handle_delete(self, record_id: int):
225
+ def callback(e):
226
+ try:
227
+ delete_query = f"DELETE FROM {self.table_name} WHERE {list(self.field_configs.keys())[0]} = %s"
228
+ self.cursor.execute(delete_query, (record_id,))
229
+ self.cursor.connection.commit()
230
+ e.page.open(ft.SnackBar(ft.Text("Запись удалена!")))
231
+ e.page.update()
232
+ except Exception as ex:
233
+ print(ex)
234
+ e.page.open(ft.SnackBar(ft.Text(f"Ошибка: {str(ex)}")))
235
+ e.page.update()
236
+
237
+ return callback
fletable/sql_table.py ADDED
@@ -0,0 +1,128 @@
1
+ from dataclasses import dataclass
2
+
3
+ import flet as ft
4
+
5
+ from .editable_table import FieldConfig
6
+
7
+
8
+ @dataclass
9
+ class DisplayValue:
10
+ id: str
11
+ label: str
12
+
13
+
14
+ class SqlTable:
15
+ """
16
+ Read-only таблица: только отображение и выбор строк.
17
+
18
+ Пример:
19
+ table = SqlTable(cursor, "users", {"user_id": "ID", "name": "Имя"})
20
+ data_table = table.create_table()
21
+ selected = table.get_selected_rows()
22
+ """
23
+
24
+ def __init__(
25
+ self,
26
+ cursor,
27
+ table_name: str,
28
+ field_mapping: dict[str, FieldConfig | str],
29
+ width: int = 800,
30
+ height: int = 400,
31
+ ):
32
+ self.cursor = cursor
33
+ self.table_name = table_name
34
+ self.field_configs: dict[str, FieldConfig] = {
35
+ name: cfg if isinstance(cfg, FieldConfig) else FieldConfig(label=str(cfg))
36
+ for name, cfg in field_mapping.items()
37
+ }
38
+ self.width = width
39
+ self.height = height
40
+ self.dropdown_options = self._generate_dropdown_options()
41
+ self.row_checkboxes: list[tuple[ft.Checkbox, dict]] = []
42
+ self.header_checkbox: ft.Checkbox = None
43
+
44
+ def _generate_dropdown_options(self) -> dict[str, list[DisplayValue]]:
45
+ """
46
+ Подтягиваем значения для внешних ключей, чтобы показывать подписанные лейблы.
47
+ """
48
+ options: dict[str, list[DisplayValue]] = {}
49
+ for field, cfg in self.field_configs.items():
50
+ ref_cfg = cfg.foreign_key
51
+ if ref_cfg or (field.endswith("_id") and field != f"{self.table_name}_id"):
52
+ ref_table = ref_cfg.table if ref_cfg else field.replace("_id", "")
53
+ id_column = ref_cfg.id_column if ref_cfg else field
54
+ label_column = ref_cfg.label_column if ref_cfg else ref_table
55
+ try:
56
+ self.cursor.execute(
57
+ f"SELECT {id_column}, {label_column} FROM {ref_table}"
58
+ )
59
+ results = self.cursor.fetchall()
60
+ options[field] = [
61
+ DisplayValue(id=str(row[0]), label=str(row[1]))
62
+ for row in results
63
+ ]
64
+ except Exception as e:
65
+ print(f"[WARN] Не удалось загрузить dropdown для {field}: {e}")
66
+ return options
67
+
68
+ def _label_for_fk(self, field: str, value) -> str:
69
+ """
70
+ Возвращает человеко-читаемое значение для внешнего ключа.
71
+ """
72
+ if field not in self.dropdown_options:
73
+ return str(value)
74
+ for option in self.dropdown_options[field]:
75
+ if option.id == str(value):
76
+ return option.label
77
+ return str(value)
78
+
79
+ def create_table(self) -> ft.DataTable:
80
+ db_fields = list(self.field_configs.keys())
81
+ query = f"SELECT {', '.join(db_fields)} FROM {self.table_name}"
82
+ self.cursor.execute(query)
83
+ data = self.cursor.fetchall()
84
+
85
+ self.row_checkboxes = []
86
+
87
+ def on_header_checkbox_change(e):
88
+ for checkbox, _ in self.row_checkboxes:
89
+ checkbox.value = self.header_checkbox.value
90
+ e.page.update()
91
+
92
+ self.header_checkbox = ft.Checkbox(
93
+ value=False, on_change=on_header_checkbox_change
94
+ )
95
+
96
+ rows = []
97
+ for row in data:
98
+ row_data = {field: value for field, value in zip(db_fields, row)}
99
+ row_checkbox = ft.Checkbox(value=False)
100
+ self.row_checkboxes.append((row_checkbox, row_data))
101
+
102
+ cells = [ft.DataCell(row_checkbox)]
103
+ for field, value in zip(db_fields, row):
104
+ display_value = self._label_for_fk(field, value)
105
+ cells.append(ft.DataCell(ft.Text(display_value)))
106
+
107
+ rows.append(ft.DataRow(cells=cells))
108
+
109
+ columns = [ft.DataColumn(self.header_checkbox)] + [
110
+ ft.DataColumn(ft.Text(self.field_configs[field].label))
111
+ for field in db_fields
112
+ ]
113
+
114
+ return ft.DataTable(
115
+ columns=columns,
116
+ rows=rows,
117
+ )
118
+
119
+ def get_selected_rows(self) -> list[dict]:
120
+ """
121
+ Возвращает список словарей с данными выделенных строк.
122
+ Ключи словаря соответствуют полям из field_mapping.
123
+ """
124
+ selected = []
125
+ for checkbox, row_data in self.row_checkboxes:
126
+ if checkbox.value:
127
+ selected.append(row_data.copy())
128
+ return selected
fletable/utils.py ADDED
@@ -0,0 +1,117 @@
1
+ import flet as ft
2
+
3
+
4
+ class LoginView(ft.View):
5
+ """Login form view for user authentication.
6
+
7
+ Creates an interface for user authentication with login and password fields.
8
+ Validates user credentials against the database and displays appropriate
9
+ error messages.
10
+
11
+ :param user_table: Name of the database table containing user data
12
+ :type user_table: str
13
+ :param user_login_col: Name of the column in the table containing user logins
14
+ :type user_login_col: str
15
+ :param user_password_col: Name of the column in the table containing user passwords
16
+ :type user_password_col: str
17
+ :param dbapi_cursor: Database cursor for executing SQL queries
18
+ :type dbapi_cursor: dbapi.cursor
19
+
20
+ Example::
21
+
22
+ login_view = LoginView(
23
+ user_table="employees",
24
+ user_login_col="login",
25
+ user_password_col="password",
26
+ dbapi_cursor=connection.cursor(),
27
+ )
28
+ page.views.append(login_view)
29
+ page.update()
30
+ """
31
+
32
+ def __init__(
33
+ self,
34
+ user_table,
35
+ user_login_col,
36
+ user_password_col,
37
+ dbapi_cursor,
38
+ next,
39
+ user_role_col=None,
40
+ user_role_key=None,
41
+ user_id_col=None,
42
+ user_id_key=None,
43
+ ):
44
+ super().__init__()
45
+ self.user_table = user_table
46
+ self.user_login_col = user_login_col
47
+ self.user_password_col = user_password_col
48
+ self.user_role_col = user_role_col
49
+ self.user_role_key = user_role_key
50
+ self.cursor = dbapi_cursor
51
+ self.next_func = next
52
+ self.user_id_col = user_id_col
53
+ self.user_id_key = user_id_key
54
+
55
+ title = ft.Text("Вход", size=20, weight="bold")
56
+ self.login = ft.TextField(label="Логин")
57
+ self.password = ft.TextField(
58
+ label="Пароль", password=True, can_reveal_password=True
59
+ )
60
+ self.submit = ft.FilledButton("Войти", on_click=self.check_credentials)
61
+
62
+ self.route = "/login"
63
+ self.form = ft.Column(
64
+ [
65
+ ft.Row(controls=[title], alignment=ft.MainAxisAlignment.CENTER),
66
+ self.login,
67
+ self.password,
68
+ ft.Row(controls=[self.submit], alignment=ft.MainAxisAlignment.END),
69
+ ],
70
+ width=300,
71
+ )
72
+ self.horizontal_alignment = ft.CrossAxisAlignment.CENTER
73
+ self.vertical_alignment = ft.MainAxisAlignment.CENTER
74
+ self.controls = [self.form]
75
+
76
+ def check_credentials(self, e):
77
+ login = self.login.value
78
+ password = self.password.value
79
+ self.password.error_text = None
80
+ self.login.error_text = None
81
+ if not (login and password):
82
+ if not login:
83
+ self.login.error_text = "введите логин"
84
+ else:
85
+ self.login.error_text = None
86
+ if not password:
87
+ self.password.error_text = "Введите пароль"
88
+ else:
89
+ self.password.error_text = None
90
+ self.page.update()
91
+ return
92
+ self.page.update()
93
+
94
+ self.cursor.execute(
95
+ f"""
96
+ SELECT {self.user_role_col if self.user_role_col else -1}, {self.user_id_col if self.user_id_col else -1}
97
+ FROM {self.user_table}
98
+ WHERE {self.user_login_col}='{login}' AND {self.user_password_col}='{password}'
99
+ """
100
+ )
101
+ result = self.cursor.fetchone()
102
+ if not result:
103
+ dlg = ft.AlertDialog(
104
+ title=ft.Text("Предупреждение"),
105
+ content=ft.Text("Неверный логин и пароль"),
106
+ alignment=ft.alignment.center,
107
+ title_padding=ft.padding.all(25),
108
+ actions=[ft.TextButton("Ok", on_click=lambda e: self.page.close(dlg))],
109
+ )
110
+ self.page.open(dlg)
111
+ return
112
+ if self.user_role_col and self.user_role_key and result[0] != -1:
113
+ self.page.client_storage.set(self.user_role_key, result[0])
114
+ self.page.client_storage.set(self.user_id_key, result[1])
115
+
116
+ self.page.views.pop()
117
+ self.next_func(self.page)
@@ -0,0 +1,383 @@
1
+ Metadata-Version: 2.4
2
+ Name: fletable
3
+ Version: 0.0.1
4
+ Summary: Tables that take data from SQL database
5
+ Home-page: https://github.com/RichCake/fletable
6
+ Author: RichCake
7
+ Author-email: abs2016123@gmail.com
8
+ Project-URL: GitHub, https://github.com/RichCake/fletable
9
+ Keywords: flet sql table
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Requires-Python: >=3.9
14
+ Description-Content-Type: text/markdown
15
+ Requires-Dist: flet==0.28.3
16
+ Requires-Dist: flet-cli==0.28.3
17
+ Requires-Dist: flet-desktop==0.28.3
18
+ Requires-Dist: flet-web==0.28.3
19
+ Dynamic: author
20
+ Dynamic: author-email
21
+ Dynamic: classifier
22
+ Dynamic: description
23
+ Dynamic: description-content-type
24
+ Dynamic: home-page
25
+ Dynamic: keywords
26
+ Dynamic: project-url
27
+ Dynamic: requires-dist
28
+ Dynamic: requires-python
29
+ Dynamic: summary
30
+
31
+ # Fletable
32
+
33
+ **Fletable** — Python-библиотека для создания интерактивных таблиц с данными из SQL-баз в приложениях на Flet. Поддерживает редактирование данных, автоматическую обработку внешних ключей и удобную работу с формами.
34
+
35
+ ## ✨ Возможности
36
+
37
+ - 📝 **Редактируемые таблицы** — изменение, добавление и удаление записей прямо в интерфейсе
38
+ - 👀 **Таблицы только для чтения** — отображение данных с возможностью выбора строк
39
+ - 🔗 **Автоматическая обработка внешних ключей** — dropdown-списки для связанных таблиц
40
+ - ✅ **Множественный выбор** — чекбоксы для выделения строк
41
+ - 🔐 **Встроенная форма авторизации** — готовый компонент для входа пользователей
42
+ - 🎨 **Настраиваемые подписи полей** — удобное отображение имен колонок
43
+
44
+ ## 📦 Установка
45
+
46
+ ```bash
47
+ pip install fletable
48
+ ```
49
+
50
+ Или установите из исходного кода:
51
+
52
+ ```bash
53
+ git clone <repository-url>
54
+ cd fletable
55
+ pip install -e .
56
+ ```
57
+
58
+ ## 🚀 Быстрый старт
59
+
60
+ ### 1. Редактируемая таблица
61
+
62
+ ```python
63
+ import flet as ft
64
+ import psycopg2
65
+ from fletable import EditableTable, FieldConfig
66
+
67
+ def main(page: ft.Page):
68
+ # Подключение к базе данных
69
+ conn = psycopg2.connect(
70
+ host="localhost",
71
+ database="mydb",
72
+ user="user",
73
+ password="password"
74
+ )
75
+ cursor = conn.cursor()
76
+
77
+ # Создание таблицы
78
+ table = EditableTable(
79
+ cursor=cursor,
80
+ table_name="employees",
81
+ field_mapping={
82
+ "employee_id": "ID",
83
+ "name": "Имя",
84
+ "email": "Email",
85
+ "department_id": FieldConfig(
86
+ label="Отдел",
87
+ foreign_key=ForeignKeyConfig(
88
+ table="departments",
89
+ id_column="department_id",
90
+ label_column="department_name"
91
+ )
92
+ )
93
+ },
94
+ )
95
+
96
+ # Форма добавления
97
+ add_form, handle_add = table.create_add_form()
98
+
99
+ def add_record(e):
100
+ success, message = handle_add()
101
+ if success:
102
+ # Обновление таблицы после добавления
103
+ container.content = table.create_table()
104
+ page.update()
105
+
106
+ add_button = ft.ElevatedButton("Добавить", on_click=add_record)
107
+
108
+ # Контейнер с таблицей
109
+ container = ft.Container(
110
+ content=table.create_table(),
111
+ padding=10
112
+ )
113
+
114
+ page.add(
115
+ ft.Column([
116
+ ft.Text("Управление сотрудниками", size=24, weight="bold"),
117
+ add_form,
118
+ add_button,
119
+ container
120
+ ])
121
+ )
122
+
123
+ ft.app(target=main)
124
+ ```
125
+
126
+ ### 2. Таблица только для чтения
127
+
128
+ ```python
129
+ import flet as ft
130
+ from fletable import SqlTable
131
+
132
+ def main(page: ft.Page):
133
+ # Подключение к БД
134
+ cursor = conn.cursor()
135
+
136
+ # Создание read-only таблицы
137
+ table = SqlTable(
138
+ cursor=cursor,
139
+ table_name="products",
140
+ field_mapping={
141
+ "product_id": "ID",
142
+ "product_name": "Название",
143
+ "price": "Цена",
144
+ "category_id": "Категория"
145
+ }
146
+ )
147
+
148
+ # Кнопка для получения выделенных строк
149
+ def show_selected(e):
150
+ selected = table.get_selected_rows()
151
+ print("Выбрано записей:", len(selected))
152
+ for row in selected:
153
+ print(row)
154
+
155
+ page.add(
156
+ ft.Column([
157
+ ft.Container(content=table.create_table(), padding=10),
158
+ ft.ElevatedButton("Показать выбранные", on_click=show_selected)
159
+ ])
160
+ )
161
+
162
+ ft.app(target=main)
163
+ ```
164
+
165
+ ### 3. Форма авторизации
166
+
167
+ ```python
168
+ import flet as ft
169
+ from fletable import LoginView
170
+
171
+ def main(page: ft.Page):
172
+ def after_login(page):
173
+ # Переход на главную страницу после успешного входа
174
+ page.views.append(
175
+ ft.View("/home", [
176
+ ft.Text("Добро пожаловать!")
177
+ ])
178
+ )
179
+ page.update()
180
+
181
+ login_view = LoginView(
182
+ user_table="users",
183
+ user_login_col="login",
184
+ user_password_col="password",
185
+ dbapi_cursor=cursor,
186
+ next=after_login,
187
+ user_role_col="role",
188
+ user_role_key="user_role",
189
+ user_id_col="user_id",
190
+ user_id_key="current_user_id"
191
+ )
192
+
193
+ page.views.append(login_view)
194
+ page.update()
195
+
196
+ ft.app(target=main)
197
+ ```
198
+
199
+ ## 📚 Документация API
200
+
201
+ ### EditableTable
202
+
203
+ Класс для создания редактируемых таблиц с поддержкой CRUD-операций.
204
+
205
+ #### Конструктор
206
+
207
+ ```python
208
+ EditableTable(
209
+ cursor, # Курсор базы данных (DB-API 2.0)
210
+ table_name: str, # Имя таблицы в БД
211
+ field_mapping: dict, # Маппинг полей {column: label или FieldConfig}
212
+ width: int = 800, # Ширина таблицы (пикселей)
213
+ height: int = 400 # Высота таблицы (пикселей)
214
+ )
215
+ ```
216
+
217
+ #### Методы
218
+
219
+ - **`create_table()`** — создает и возвращает `ft.DataTable` с данными
220
+ - **`create_add_form()`** — создает форму для добавления новых записей, возвращает `(form_row, handle_add)`
221
+ - **`get_selected_rows()`** — возвращает список словарей с данными выделенных строк
222
+
223
+ ### SqlTable
224
+
225
+ Класс для создания таблиц только для чтения с возможностью выбора строк.
226
+
227
+ #### Конструктор
228
+
229
+ ```python
230
+ SqlTable(
231
+ cursor, # Курсор базы данных
232
+ table_name: str, # Имя таблицы
233
+ field_mapping: dict, # Маппинг полей
234
+ width: int = 800,
235
+ height: int = 400
236
+ )
237
+ ```
238
+
239
+ #### Методы
240
+
241
+ - **`create_table()`** — создает и возвращает `ft.DataTable`
242
+ - **`get_selected_rows()`** — возвращает выделенные строки
243
+
244
+ ### FieldConfig
245
+
246
+ Конфигурация для настройки отображения полей.
247
+
248
+ ```python
249
+ @dataclass
250
+ class FieldConfig:
251
+ label: str # Отображаемое название поля
252
+ foreign_key: ForeignKeyConfig | None = None # Конфигурация внешнего ключа
253
+ ```
254
+
255
+ ### ForeignKeyConfig
256
+
257
+ Настройка внешнего ключа для автоматического создания dropdown-списков.
258
+
259
+ ```python
260
+ @dataclass
261
+ class ForeignKeyConfig:
262
+ table: str # Имя связанной таблицы
263
+ id_column: str # Колонка с ID
264
+ label_column: str # Колонка с отображаемым значением
265
+ ```
266
+
267
+ ### LoginView
268
+
269
+ Готовая форма авторизации пользователей.
270
+
271
+ ```python
272
+ LoginView(
273
+ user_table: str, # Таблица с пользователями
274
+ user_login_col: str, # Колонка с логином
275
+ user_password_col: str, # Колонка с паролем
276
+ dbapi_cursor, # Курсор БД
277
+ next: callable, # Функция после успешного входа
278
+ user_role_col: str = None, # Колонка с ролью (опционально)
279
+ user_role_key: str = None, # Ключ для хранения роли
280
+ user_id_col: str = None, # Колонка с ID пользователя
281
+ user_id_key: str = None # Ключ для хранения ID
282
+ )
283
+ ```
284
+
285
+ ## 🔧 Автоматическая обработка внешних ключей
286
+
287
+ Fletable автоматически создает dropdown-списки для полей с именами, заканчивающимися на `_id` (кроме первичного ключа таблицы):
288
+
289
+ ```python
290
+ field_mapping = {
291
+ "order_id": "ID заказа",
292
+ "customer_id": "Клиент", # Автоматически создаст dropdown из таблицы "customer"
293
+ "product_id": "Товар" # Автоматически создаст dropdown из таблицы "product"
294
+ }
295
+ ```
296
+
297
+ Для более точной настройки используйте `ForeignKeyConfig`:
298
+
299
+ ```python
300
+ field_mapping = {
301
+ "order_id": "ID заказа",
302
+ "customer_id": FieldConfig(
303
+ label="Клиент",
304
+ foreign_key=ForeignKeyConfig(
305
+ table="customers",
306
+ id_column="customer_id",
307
+ label_column="full_name"
308
+ )
309
+ )
310
+ }
311
+ ```
312
+
313
+ ## 💡 Примеры использования
314
+
315
+ ### Обновление таблицы после изменений
316
+
317
+ ```python
318
+ def refresh_table(e):
319
+ container.content = table.create_table()
320
+ page.update()
321
+
322
+ refresh_button = ft.IconButton(
323
+ icon=ft.Icons.REFRESH,
324
+ on_click=refresh_table
325
+ )
326
+ ```
327
+
328
+ ### Работа с выделенными строками
329
+
330
+ ```python
331
+ def process_selected(e):
332
+ selected = table.get_selected_rows()
333
+ for row in selected:
334
+ print(f"ID: {row['employee_id']}, Name: {row['name']}")
335
+ ```
336
+
337
+ ### Массовое удаление
338
+
339
+ ```python
340
+ def delete_selected(e):
341
+ selected = table.get_selected_rows()
342
+ for row in selected:
343
+ cursor.execute(
344
+ "DELETE FROM employees WHERE employee_id = %s",
345
+ (row['employee_id'],)
346
+ )
347
+ conn.commit()
348
+ refresh_table(e)
349
+ ```
350
+
351
+ ## 🗄️ Поддерживаемые базы данных
352
+
353
+ Fletable работает с любыми базами данных, поддерживающими DB-API 2.0:
354
+
355
+ - PostgreSQL (psycopg2)
356
+ - MySQL (mysql-connector-python)
357
+ - SQLite (sqlite3)
358
+ - Oracle
359
+ - Microsoft SQL Server
360
+
361
+ ## 📋 Требования
362
+
363
+ - Python >= 3.6
364
+ - flet >= 0.28.3
365
+ - Драйвер базы данных (psycopg2, mysql-connector, и т.д.)
366
+
367
+ ## 🤝 Участие в разработке
368
+
369
+ Мы приветствуем ваши предложения и pull request'ы!
370
+
371
+ ## 📄 Лицензия
372
+
373
+ MIT License
374
+
375
+ ## 👨‍💻 Автор
376
+
377
+ **RichCake**
378
+ Email: abs2016123@gmail.com
379
+
380
+ ---
381
+
382
+ ⭐ Если вам понравился проект, поставьте звезду на GitHub!
383
+
@@ -0,0 +1,8 @@
1
+ fletable/__init__.py,sha256=bAuv-OvRBPuBJWlqU2Gq3b0ppj2-L5qS0SkuPk6RBTM,134
2
+ fletable/editable_table.py,sha256=PcUPxmfIsZzBgHCAPFgD-8nG1fe-kq8Tf9A9Hq69Y1s,9399
3
+ fletable/sql_table.py,sha256=Q-XzZR8mhMnHffaT63UuBP0B1lmHCsR5m8DgTRigPx8,4675
4
+ fletable/utils.py,sha256=o7gaTyBw5VL0cYs-6IqnTuU7PtmQwsdqXKTGzBxEVI4,4186
5
+ fletable-0.0.1.dist-info/METADATA,sha256=m0U4g78ikOf1x4_P4jXB5oFDwMfShJpYF43U1G9THjc,11959
6
+ fletable-0.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
7
+ fletable-0.0.1.dist-info/top_level.txt,sha256=YofKKSe3S3HQ1YwJwI7CVrF_1KEHcYwzLEBezSPppGk,9
8
+ fletable-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ fletable