python-simpletables 0.1.27__tar.gz → 0.1.28__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.
- {python_simpletables-0.1.27 → python_simpletables-0.1.28}/PKG-INFO +1 -1
- {python_simpletables-0.1.27 → python_simpletables-0.1.28}/pyproject.toml +1 -1
- {python_simpletables-0.1.27 → python_simpletables-0.1.28}/python_simpletables/__main__.py +6 -6
- python_simpletables-0.1.28/python_simpletables/table.py +155 -0
- {python_simpletables-0.1.27 → python_simpletables-0.1.28}/python_simpletables.egg-info/PKG-INFO +1 -1
- python_simpletables-0.1.27/python_simpletables/table.py +0 -67
- {python_simpletables-0.1.27 → python_simpletables-0.1.28}/python_simpletables/__init__.py +0 -0
- {python_simpletables-0.1.27 → python_simpletables-0.1.28}/python_simpletables/app.py +0 -0
- {python_simpletables-0.1.27 → python_simpletables-0.1.28}/python_simpletables.egg-info/SOURCES.txt +0 -0
- {python_simpletables-0.1.27 → python_simpletables-0.1.28}/python_simpletables.egg-info/dependency_links.txt +0 -0
- {python_simpletables-0.1.27 → python_simpletables-0.1.28}/python_simpletables.egg-info/requires.txt +0 -0
- {python_simpletables-0.1.27 → python_simpletables-0.1.28}/python_simpletables.egg-info/top_level.txt +0 -0
- {python_simpletables-0.1.27 → python_simpletables-0.1.28}/setup.cfg +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "python_simpletables"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.28"
|
|
8
8
|
description = "Simple connection between SQL and tkinter."
|
|
9
9
|
authors = [{name = "Захар Васильев", email = "vasilyev.zakhar@gmail.com"}]
|
|
10
10
|
license = {text = "MIT"}
|
|
@@ -25,24 +25,24 @@ class ExampleApp(tk.Tk):
|
|
|
25
25
|
self.all_tables=[]
|
|
26
26
|
self.all_buttons={}
|
|
27
27
|
selector = lambda name: lambda: self.select_table(name)
|
|
28
|
-
for name,title,fields in [
|
|
28
|
+
for name,title,fields,primary in [
|
|
29
29
|
("Customers","Заказчики",[
|
|
30
|
-
Field("id","ID",show=showid
|
|
30
|
+
Field("id","ID",show=showid),
|
|
31
31
|
Field("name","ФИО / Организация"),
|
|
32
32
|
Field("inn","ИНН"),
|
|
33
33
|
Field("addres","Адрес"),
|
|
34
34
|
Field("phone","Телефон"),
|
|
35
35
|
Field("salesman","Продавец?",show=showbool),
|
|
36
36
|
Field("buyer","Покупатель?",show=showbool),
|
|
37
|
-
]),
|
|
37
|
+
],"id"),
|
|
38
38
|
("Goods","Товары",[
|
|
39
|
-
Field("id","ID",show=showid
|
|
39
|
+
Field("id","ID",show=showid),
|
|
40
40
|
Field("name","Наименование",multiply=2.5),
|
|
41
41
|
Field("price_for_unit","Цена за ед."),
|
|
42
42
|
Field("unit","Ед.изм."),
|
|
43
|
-
]),
|
|
43
|
+
],"id"),
|
|
44
44
|
]:
|
|
45
|
-
self.all_tables.append(Table(conn,box,name,fields))
|
|
45
|
+
self.all_tables.append(Table(conn,box,name,fields,primary))
|
|
46
46
|
self.all_buttons[name]=tk.Button(tabs,text=title,command=selector(name))
|
|
47
47
|
self.all_buttons[name].pack(side="left")
|
|
48
48
|
self.select_table(self.all_tables[0].name)
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import tkinter as tk,json
|
|
2
|
+
from tkinter import ttk
|
|
3
|
+
from tkinter import messagebox as mb
|
|
4
|
+
from tkinter import simpledialog as sd
|
|
5
|
+
from tkinter import filedialog as fd
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
|
|
8
|
+
def q(s): return json.dumps(s,ensure_ascii=False)
|
|
9
|
+
@dataclass
|
|
10
|
+
class Field:
|
|
11
|
+
name:str
|
|
12
|
+
title:str
|
|
13
|
+
show:object = str
|
|
14
|
+
width:int = 0
|
|
15
|
+
multiply:float = 1
|
|
16
|
+
is_valid:object = lambda e: True
|
|
17
|
+
class Table(tk.Frame):
|
|
18
|
+
def __init__(self,conn,master,name,fields,primary):
|
|
19
|
+
self.conn = conn
|
|
20
|
+
self.name = name
|
|
21
|
+
self.fields = fields
|
|
22
|
+
self.primary = primary
|
|
23
|
+
super().__init__(master)
|
|
24
|
+
self.buttons = tk.Frame(self)
|
|
25
|
+
self.buttons.pack(side="bottom",fill="x")
|
|
26
|
+
for name,side,cmd in [
|
|
27
|
+
("Создать","left",self.ui_create),
|
|
28
|
+
("Изменить","left",self.ui_edit),
|
|
29
|
+
("Удалить","left",self.ui_delete),
|
|
30
|
+
("Импорт из JSON","right",self.ui_import_json),
|
|
31
|
+
("Импорт из Excel","right",self.ui_import_excel),
|
|
32
|
+
("Экспорт в JSON","right",self.ui_export_json),
|
|
33
|
+
("Экспорт в Excel","right",self.ui_export_excel),
|
|
34
|
+
]: tk.Button(self.buttons,text=name,command=cmd).pack(side=side)
|
|
35
|
+
self.tree = ttk.Treeview(self,columns=[f.name for f in self.fields],show="headings")
|
|
36
|
+
self.tree.pack(side="bottom",fill="both",expand=True)
|
|
37
|
+
for field in fields:
|
|
38
|
+
self.tree.column(field.name,width=field.width or int(max(30,len(field.title)*8)*field.multiply))
|
|
39
|
+
self.tree.heading(field.name,text=field.title)
|
|
40
|
+
self.update_values()
|
|
41
|
+
def update_values(self):
|
|
42
|
+
self.tree.delete(*self.tree.get_children())
|
|
43
|
+
for raw in self.conn.execute(f"SELECT * FROM {q(self.name)}").fetchall():
|
|
44
|
+
values = [e.show(self,e,raw[i]) for i,e in enumerate(self.fields)]
|
|
45
|
+
self.tree.insert("","end",values=values)
|
|
46
|
+
def ui_create(self): pass
|
|
47
|
+
def ui_create(self): pass
|
|
48
|
+
def ui_edit(self): pass
|
|
49
|
+
def ui_delete(self): pass
|
|
50
|
+
def ui_import_json(self):
|
|
51
|
+
file = fd.askopenfile(title="Имортировать файл",filetypes=[
|
|
52
|
+
("JSON-файлы","*.json"),
|
|
53
|
+
("Все файлы","*"),
|
|
54
|
+
])
|
|
55
|
+
if file and not self.import_json(file): self.badfile()
|
|
56
|
+
def import_json(self,file):
|
|
57
|
+
with open(file,"r",encoding="utf8") as f:
|
|
58
|
+
data = json.loads(f.read())
|
|
59
|
+
if type(data)!=list: return False
|
|
60
|
+
for e in data:
|
|
61
|
+
if not self.is_valid(e): return False
|
|
62
|
+
self.data_add(data)
|
|
63
|
+
return True
|
|
64
|
+
def badfile(self): mb.showerror("Ошибка импортирования","Файл повреждён или имеет некорректную структуру.")
|
|
65
|
+
def ui_import_excel(self):
|
|
66
|
+
file = fd.askopenfile(title="Имортировать файл",filetypes=[
|
|
67
|
+
("Таблицы","*.xlsx","*.xls"),
|
|
68
|
+
("Все файлы","*"),
|
|
69
|
+
])
|
|
70
|
+
if file and not self.import_excel(file): self.badfile()
|
|
71
|
+
def import_excel(file):
|
|
72
|
+
df=pd.read_excel(file,sheet_name=0)
|
|
73
|
+
started=False
|
|
74
|
+
for i in range(df.shape()[0]):
|
|
75
|
+
row=df.iloc(i)
|
|
76
|
+
if self.is_valid(row):
|
|
77
|
+
started=True
|
|
78
|
+
self.data_add(row)
|
|
79
|
+
elif started: break
|
|
80
|
+
else: pass
|
|
81
|
+
if not started: return False
|
|
82
|
+
return True
|
|
83
|
+
def data_get(self,primary_value,error_ok=False):
|
|
84
|
+
matches = self.conn.execute(f"SELECT * FROM {q(self.name)} WHERE {q(self.primary)} = ?",primary_value).fetchall()
|
|
85
|
+
if len(matches)==0:
|
|
86
|
+
if error_ok: return None
|
|
87
|
+
else: raise KeyError(f"cannot find row with key {primary_value!r}")
|
|
88
|
+
elif len(matches)==1:
|
|
89
|
+
return matches[0]
|
|
90
|
+
else:
|
|
91
|
+
raise RuntimeError("unexpected situation")
|
|
92
|
+
def data_add(self,row):
|
|
93
|
+
if not self.is_valid(row): raise ValueError(f"invalid line: {row!r}")
|
|
94
|
+
row = self.normalize(row)
|
|
95
|
+
actual = self.data_get(self.primary,True)
|
|
96
|
+
if actual==None:
|
|
97
|
+
self.data_create(row)
|
|
98
|
+
self.conn.commit()
|
|
99
|
+
else: self.data_set(row[0],row)
|
|
100
|
+
def data_create(self,row):
|
|
101
|
+
if not self.is_valid(row): raise ValueError(f"invalid line: {row!r}")
|
|
102
|
+
row = self.normalize(row)
|
|
103
|
+
self.conn.execute(f"INSERT INTO {q(self.name)} VALUES ("+",".join('?' for _ in self.fields)+")",row)
|
|
104
|
+
self.conn.commit()
|
|
105
|
+
def data_set(self,key,row):
|
|
106
|
+
if not self.is_valid(row): raise ValueError(f"invalid line: {row!r}")
|
|
107
|
+
row = self.normalize(row)
|
|
108
|
+
self.conn.execute(f"UPDATE {q(self.name)} SET "+", ".join(f"{q(f.name)} = ?" for _ in self.fields)+" WHERE {q(self.primary)} = ?",[*row,key])
|
|
109
|
+
self.conn.commit()
|
|
110
|
+
def data_readall(self):
|
|
111
|
+
return [self.normalize(e) for e in self.conn.execute(f"SELECT * FROM {q(self.name)}").fetchall()]
|
|
112
|
+
def is_valid(self,row):
|
|
113
|
+
if type(row)==dict:
|
|
114
|
+
row = [*row.items()]
|
|
115
|
+
pattern = dict((e.name,e) for e in self.fields)
|
|
116
|
+
while row:
|
|
117
|
+
key,value = row.pop(0)
|
|
118
|
+
field = pattern.get(key,None)
|
|
119
|
+
if field and field.is_valid(value):
|
|
120
|
+
del pattenr[key]
|
|
121
|
+
continue
|
|
122
|
+
else:
|
|
123
|
+
return False
|
|
124
|
+
return len(pattern)==0
|
|
125
|
+
else:
|
|
126
|
+
if len(row)<len(self.fields): return False
|
|
127
|
+
for i in range(len(self.fields)):
|
|
128
|
+
if not self.fields[i].is_valid(row[i]): return False
|
|
129
|
+
return True
|
|
130
|
+
def normalize(self,row):
|
|
131
|
+
if type(row)!=dict:
|
|
132
|
+
new={}
|
|
133
|
+
for i,field in self.fields:
|
|
134
|
+
new[field.name]=row[i]
|
|
135
|
+
return new
|
|
136
|
+
def ui_export_json(self):
|
|
137
|
+
file = fd.asksaveasfilename(title="Имортировать файл",filetypes=[
|
|
138
|
+
("JSON-файлы","*.json"),
|
|
139
|
+
("Все файлы","*"),
|
|
140
|
+
])
|
|
141
|
+
if file: self.export_json(file)
|
|
142
|
+
def export_json(self,file):
|
|
143
|
+
with open(file,"w",encoding="utf8") as f:
|
|
144
|
+
file.write(json.dumps(self.data_readall(),ensure_ascii=False))
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
#
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import tkinter as tk,json
|
|
2
|
-
from tkinter import ttk
|
|
3
|
-
from tkinter import messagebox as mb
|
|
4
|
-
from tkinter import simpledialog as sd
|
|
5
|
-
from tkinter import filedialog as fd
|
|
6
|
-
from dataclasses import dataclass
|
|
7
|
-
|
|
8
|
-
def q(s): return json.dumps(s,ensure_ascii=False)
|
|
9
|
-
@dataclass
|
|
10
|
-
class Field:
|
|
11
|
-
name:str
|
|
12
|
-
title:str
|
|
13
|
-
show:object = str
|
|
14
|
-
primary:bool = False
|
|
15
|
-
width:int = 0
|
|
16
|
-
multiply:float = 1
|
|
17
|
-
class Table(tk.Frame):
|
|
18
|
-
def __init__(self,conn,master,name,fields):
|
|
19
|
-
self.conn = conn
|
|
20
|
-
self.name = name
|
|
21
|
-
self.fields = fields
|
|
22
|
-
super().__init__(master)
|
|
23
|
-
self.buttons = tk.Frame(self)
|
|
24
|
-
self.buttons.pack(side="bottom",fill="x")
|
|
25
|
-
for name,side,cmd in [
|
|
26
|
-
("Создать","left",self.ui_create),
|
|
27
|
-
("Изменить","left",self.ui_edit),
|
|
28
|
-
("Удалить","left",self.ui_delete),
|
|
29
|
-
("Импорт из JSON","right",self.ui_import_json),
|
|
30
|
-
("Импорт из Excel","right",self.ui_import_excel),
|
|
31
|
-
("Экспорт в JSON","right",self.ui_export_json),
|
|
32
|
-
("Экспорт в Excel","right",self.ui_export_excel),
|
|
33
|
-
]: tk.Button(self.buttons,text=name,command=cmd).pack(side=side)
|
|
34
|
-
self.tree = ttk.Treeview(self,columns=[f.name for f in self.fields],show="headings")
|
|
35
|
-
self.tree.pack(side="bottom",fill="both",expand=True)
|
|
36
|
-
for field in fields:
|
|
37
|
-
self.tree.column(field.name,width=field.width or int(max(30,len(field.title)*8)*field.multiply))
|
|
38
|
-
self.tree.heading(field.name,text=field.title)
|
|
39
|
-
self.update()
|
|
40
|
-
def update(self):
|
|
41
|
-
self.tree.delete(*self.tree.get_children())
|
|
42
|
-
for raw in self.conn.execute(f"SELECT * FROM {q(self.name)}").fetchall():
|
|
43
|
-
values = [e.show(self,e,raw[i]) for i,e in enumerate(self.fields)]
|
|
44
|
-
self.tree.insert("","end",values=values)
|
|
45
|
-
def ui_create(self): pass
|
|
46
|
-
def ui_create(self): pass
|
|
47
|
-
def ui_edit(self): pass
|
|
48
|
-
def ui_delete(self): pass
|
|
49
|
-
def ui_import_json(self): pass
|
|
50
|
-
def ui_import_excel(self):
|
|
51
|
-
file = fd.askopenfile(title="Имортировать файл",filetypes=[
|
|
52
|
-
("Таблицы","*.xlsx","*.xls","*.csv"),
|
|
53
|
-
("Все файлы","*"),
|
|
54
|
-
])
|
|
55
|
-
def ui_export_json(self): pass
|
|
56
|
-
def ui_export_excel(self): pass
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
#
|
|
File without changes
|
|
File without changes
|
{python_simpletables-0.1.27 → python_simpletables-0.1.28}/python_simpletables.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{python_simpletables-0.1.27 → python_simpletables-0.1.28}/python_simpletables.egg-info/requires.txt
RENAMED
|
File without changes
|
{python_simpletables-0.1.27 → python_simpletables-0.1.28}/python_simpletables.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|