tina4-python 0.2.124__tar.gz → 0.2.125__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.
- {tina4_python-0.2.124 → tina4_python-0.2.125}/PKG-INFO +1 -1
- {tina4_python-0.2.124 → tina4_python-0.2.125}/pyproject.toml +1 -1
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/CRUD.py +9 -3
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/Template.py +48 -75
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/templates/components/crud.twig +9 -39
- {tina4_python-0.2.124 → tina4_python-0.2.125}/.gitignore +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/README.md +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/Auth.py +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/Constant.py +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/Database.py +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/DatabaseResult.py +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/DatabaseTypes.py +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/Debug.py +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/Env.py +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/HtmlElement.py +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/Localization.py +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/Messages.py +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/MiddleWare.py +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/Migration.py +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/ORM.py +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/Queue.py +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/Request.py +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/Response.py +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/Router.py +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/Session.py +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/ShellColors.py +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/Swagger.py +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/Webserver.py +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/Websocket.py +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/__init__.py +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/messages.pot +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/public/css/readme.md +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/public/favicon.ico +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/public/images/403.png +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/public/images/404.png +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/public/images/500.png +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/public/images/logo.png +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/public/images/readme.md +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/public/js/readme.md +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/public/js/reconnecting-websocket.js +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/public/js/tina4helper.js +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/public/swagger/index.html +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/templates/errors/403.twig +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/templates/errors/404.twig +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/templates/errors/500.twig +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/templates/readme.md +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
|
@@ -141,6 +141,9 @@ class CRUD:
|
|
|
141
141
|
table_nice_name = Template.get_nice_label(table_name)
|
|
142
142
|
twig_file = self.ensure_crud_template(table_name + ".twig")
|
|
143
143
|
|
|
144
|
+
if options is None:
|
|
145
|
+
options = {}
|
|
146
|
+
|
|
144
147
|
if "primary_key" not in options:
|
|
145
148
|
options["primary_key"] = "id"
|
|
146
149
|
|
|
@@ -165,9 +168,12 @@ class CRUD:
|
|
|
165
168
|
|
|
166
169
|
|
|
167
170
|
# Use defined columns or fallback
|
|
168
|
-
search_columns
|
|
169
|
-
|
|
170
|
-
|
|
171
|
+
if not "search_columns" in options:
|
|
172
|
+
search_columns = self.columns
|
|
173
|
+
if isinstance(search_columns, dict):
|
|
174
|
+
search_columns = [c["name"] for c in search_columns if "name" in c]
|
|
175
|
+
else:
|
|
176
|
+
search_columns = options["search_columns"]
|
|
171
177
|
|
|
172
178
|
# Execute fetch with search
|
|
173
179
|
result = self.dba.fetch(
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
# License: MIT https://opensource.org/licenses/MIT
|
|
5
5
|
#
|
|
6
6
|
# flake8: noqa: E501
|
|
7
|
+
import ast
|
|
8
|
+
import html
|
|
7
9
|
import os
|
|
8
10
|
import re
|
|
9
11
|
import json
|
|
@@ -33,6 +35,7 @@ class Template:
|
|
|
33
35
|
Template.twig.add_extension('jinja2.ext.do')
|
|
34
36
|
Template.twig.globals['RANDOM'] = RANDOM
|
|
35
37
|
Template.twig.globals['json'] = json
|
|
38
|
+
Template.twig.filters['detect_image'] = Template.detect_image
|
|
36
39
|
Template.twig.filters['json_encode'] = json.dumps
|
|
37
40
|
Template.twig.filters['json_decode'] = json.loads
|
|
38
41
|
Template.twig.filters['nice_label'] = Template.get_nice_label
|
|
@@ -79,7 +82,7 @@ class Template:
|
|
|
79
82
|
"""
|
|
80
83
|
Recursively convert non-JSON-serializable objects:
|
|
81
84
|
• datetime/date → ISO 8601 string
|
|
82
|
-
• bytes
|
|
85
|
+
• bytes base64 string
|
|
83
86
|
• dict/list/tuple/set → recursively processed
|
|
84
87
|
Safe for deeply nested data (arrays of arrays, dicts in lists, etc.)
|
|
85
88
|
"""
|
|
@@ -145,81 +148,51 @@ class Template:
|
|
|
145
148
|
|
|
146
149
|
@staticmethod
|
|
147
150
|
def detect_image(value: Any) -> Dict[str, str]:
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
Returns: {"content": "<base64 without prefix>", "content_type": "image/jpeg|png|gif|webp"}
|
|
151
|
-
"""
|
|
152
|
-
if value is None:
|
|
153
|
-
return {"content": "", "content_type": ""}
|
|
154
|
-
|
|
155
|
-
# Convert to string if it's bytes or something else
|
|
156
|
-
if isinstance(value, (bytes, bytearray)):
|
|
157
|
-
value = value.decode('latin1', errors='ignore')
|
|
158
|
-
elif not isinstance(value, str):
|
|
159
|
-
value = str(value)
|
|
151
|
+
if not value or len(value) <= 50:
|
|
152
|
+
return {"content": value, "content_type": ""}
|
|
160
153
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
# Case 1: JSON object like {"content": "...", "content_type": "..."}
|
|
164
|
-
if original.startswith("{"):
|
|
165
|
-
try:
|
|
166
|
-
data = json.loads(original)
|
|
167
|
-
if isinstance(data, dict) and "content" in data:
|
|
168
|
-
content = data["content"]
|
|
169
|
-
content_type = data.get("content_type", "")
|
|
170
|
-
if content_type.startswith("image/"):
|
|
171
|
-
return {
|
|
172
|
-
"content": str(content).split(",", 1)[-1] if "," in content else str(content),
|
|
173
|
-
"content_type": content_type
|
|
174
|
-
}
|
|
175
|
-
except:
|
|
176
|
-
pass # not valid JSON, continue
|
|
177
|
-
|
|
178
|
-
# Case 2: Data URL like data:image/png;base64,...
|
|
179
|
-
if original.startswith("data:image/"):
|
|
154
|
+
if value[0] == '{' and value[-1] == '}':
|
|
180
155
|
try:
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
156
|
+
value = html.unescape(value)
|
|
157
|
+
data = json.loads(value)
|
|
158
|
+
content = data.get('content', '')
|
|
159
|
+
|
|
160
|
+
if not content:
|
|
161
|
+
return {"content": value, "content_type": ""}
|
|
162
|
+
|
|
163
|
+
content_type = data.get('content_type', '')
|
|
164
|
+
if content_type.startswith('image/'):
|
|
165
|
+
mime_type = content_type.split('/')[1]
|
|
166
|
+
return {"content": content, "content_type": mime_type}
|
|
167
|
+
|
|
168
|
+
# Fallback to magic bytes if no content_type
|
|
169
|
+
if content[:4] == '/9j/':
|
|
170
|
+
mime_type = 'jpeg'
|
|
171
|
+
elif content[:11] == 'iVBORw0KGgo':
|
|
172
|
+
mime_type = 'png'
|
|
173
|
+
elif content[:6] == 'R0lGOD':
|
|
174
|
+
mime_type = 'gif'
|
|
175
|
+
elif content[:5] == 'UklGR':
|
|
176
|
+
mime_type = 'webp'
|
|
177
|
+
else:
|
|
178
|
+
return {"content": content, "content_type": ""}
|
|
179
|
+
|
|
180
|
+
return {"content": content, "content_type": mime_type}
|
|
181
|
+
except json.JSONDecodeError as e:
|
|
182
|
+
return {"content": str(e), "content_type": ""}
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
mime_type = "jpeg"
|
|
186
|
+
# Check magic bytes on value
|
|
187
|
+
if value[:4] == '/9j/':
|
|
188
|
+
mime_type = 'jpeg'
|
|
189
|
+
elif value[:11] == 'iVBORw0KGgo':
|
|
190
|
+
mime_type = 'png'
|
|
191
|
+
elif value[:6] == 'R0lGOD':
|
|
192
|
+
mime_type = 'gif'
|
|
193
|
+
elif value[:5] == 'UklGR':
|
|
194
|
+
mime_type = 'webp'
|
|
219
195
|
else:
|
|
220
|
-
return {"content":
|
|
196
|
+
return {"content": value, "content_type": mime_type}
|
|
221
197
|
|
|
222
|
-
return {
|
|
223
|
-
"content": b64,
|
|
224
|
-
"content_type": mime
|
|
225
|
-
}
|
|
198
|
+
return {"content": value, "content_type": mime_type}
|
|
@@ -48,26 +48,12 @@
|
|
|
48
48
|
|
|
49
49
|
{# --- Detect image by magic bytes (no ternary, no starts with) --- #}
|
|
50
50
|
{% set is_image = False %}
|
|
51
|
-
{% set
|
|
51
|
+
{% set check_value = value | detect_image %}
|
|
52
52
|
|
|
53
|
-
{% if
|
|
53
|
+
{% if check_value.content_type %}
|
|
54
54
|
{% set is_image = True %}
|
|
55
|
-
{% set value =
|
|
56
|
-
{% set
|
|
57
|
-
{% else %}
|
|
58
|
-
{% if value[:4] == '/9j/' %}
|
|
59
|
-
{% set is_image = True %}
|
|
60
|
-
{% set mime_type = 'jpeg' %}
|
|
61
|
-
{% elif value[:11] == 'iVBORw0KGgo' %}
|
|
62
|
-
{% set is_image = True %}
|
|
63
|
-
{% set mime_type = 'png' %}
|
|
64
|
-
{% elif value[:6] == 'R0lGOD' %}
|
|
65
|
-
{% set is_image = True %}
|
|
66
|
-
{% set mime_type = 'gif' %}
|
|
67
|
-
{% elif value[:5] == 'UklGR' %}
|
|
68
|
-
{% set is_image = True %}
|
|
69
|
-
{% set mime_type = 'webp' %}
|
|
70
|
-
{% endif %}
|
|
55
|
+
{% set value = check_value.content %}
|
|
56
|
+
{% set mime_type = check_value.mime_type %}
|
|
71
57
|
{% endif %}
|
|
72
58
|
|
|
73
59
|
{# --- Only show image if it's base64-like and long enough --- #}
|
|
@@ -132,34 +118,18 @@
|
|
|
132
118
|
{% set name = column.name %}
|
|
133
119
|
{% set value = record[name]|e %}
|
|
134
120
|
|
|
135
|
-
{# --- Detect image by magic bytes (no ternary, no starts with) --- #}
|
|
136
121
|
{% set is_image = False %}
|
|
137
|
-
{% set mime_type = 'jpeg' %}
|
|
138
122
|
|
|
123
|
+
{% set check_value = value | detect_image %}
|
|
139
124
|
|
|
140
|
-
{% if
|
|
125
|
+
{% if check_value.content_type %}
|
|
141
126
|
{% set is_image = True %}
|
|
142
|
-
{% set value =
|
|
143
|
-
{% set
|
|
144
|
-
{% else %}
|
|
145
|
-
{% if value|length > 50 and value[:4] == '/9j/' %}
|
|
146
|
-
{% set is_image = True %}
|
|
147
|
-
{% set mime_type = 'jpeg' %}
|
|
148
|
-
{% elif value[:11] == 'iVBORw0KGgo' %}
|
|
149
|
-
{% set is_image = True %}
|
|
150
|
-
{% set mime_type = 'png' %}
|
|
151
|
-
{% elif value[:6] == 'R0lGOD' %}
|
|
152
|
-
{% set is_image = True %}
|
|
153
|
-
{% set mime_type = 'gif' %}
|
|
154
|
-
{% elif value[:5] == 'UklGR' %}
|
|
155
|
-
{% set is_image = True %}
|
|
156
|
-
{% set mime_type = 'webp' %}
|
|
157
|
-
{% endif %}
|
|
127
|
+
{% set value = check_value.content %}
|
|
128
|
+
{% set mime_type = check_value.mime_type %}
|
|
158
129
|
{% endif %}
|
|
159
|
-
|
|
160
130
|
<td>
|
|
161
131
|
{# --- Only show image if it's base64-like and long enough --- #}
|
|
162
|
-
{% if is_image
|
|
132
|
+
{% if is_image %}
|
|
163
133
|
{% set src = value %}
|
|
164
134
|
{% if value[:10] != 'data:image/' %}
|
|
165
135
|
{% set src = 'data:image/' ~ mime_type ~ ';base64,' ~ value %}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/public/js/reconnecting-websocket.js
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/public/swagger/oauth2-redirect.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/translations/en/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/translations/en/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/translations/fr/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-0.2.124 → tina4_python-0.2.125}/tina4_python/translations/fr/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|