ramifice 0.5.12__py3-none-any.whl → 0.6.0__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.
- ramifice/fields/file_field.py +14 -13
- ramifice/fields/image_field.py +13 -32
- ramifice/models/decorator.py +25 -53
- ramifice/paladins/check.py +14 -27
- ramifice/paladins/delete.py +4 -4
- ramifice/paladins/groups/file_group.py +1 -1
- ramifice/paladins/groups/img_group.py +24 -12
- ramifice/paladins/groups/text_group.py +15 -4
- ramifice/paladins/tools.py +0 -2
- ramifice/utils/migration.py +1 -3
- {ramifice-0.5.12.dist-info → ramifice-0.6.0.dist-info}/METADATA +4 -17
- {ramifice-0.5.12.dist-info → ramifice-0.6.0.dist-info}/RECORD +14 -15
- ramifice/models/pseudo.py +0 -181
- {ramifice-0.5.12.dist-info → ramifice-0.6.0.dist-info}/WHEEL +0 -0
- {ramifice-0.5.12.dist-info → ramifice-0.6.0.dist-info}/licenses/LICENSE +0 -0
ramifice/fields/file_field.py
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
"""Field of Model for upload file."""
|
2
2
|
|
3
3
|
import os
|
4
|
-
import shutil
|
5
4
|
import uuid
|
6
5
|
from base64 import b64decode
|
7
6
|
from datetime import date
|
8
7
|
from pathlib import Path
|
9
|
-
|
8
|
+
|
9
|
+
import aiofiles
|
10
|
+
from aioshutil import copyfile
|
10
11
|
|
11
12
|
from ramifice.fields.general.field import Field
|
12
13
|
from ramifice.fields.general.file_group import FileGroup
|
@@ -91,7 +92,7 @@ class FileField(Field, FileGroup, JsonMixin):
|
|
91
92
|
|
92
93
|
self.value: dict[str, str | int | bool] | None = None
|
93
94
|
|
94
|
-
def from_base64(
|
95
|
+
async def from_base64(
|
95
96
|
self,
|
96
97
|
base64_str: str | None = None,
|
97
98
|
filename: str | None = None,
|
@@ -125,14 +126,14 @@ class FileField(Field, FileGroup, JsonMixin):
|
|
125
126
|
# Create path to target directory.
|
126
127
|
dir_target_path = f"{self.media_root}/{self.target_dir}/{date_str}"
|
127
128
|
# Create target directory if it does not exist.
|
128
|
-
if not os.path.exists(dir_target_path):
|
129
|
-
os.makedirs(dir_target_path)
|
129
|
+
if not await aiofiles.os.path.exists(dir_target_path):
|
130
|
+
await aiofiles.os.makedirs(dir_target_path)
|
130
131
|
# Create path to target file.
|
131
132
|
f_target_path = f"{dir_target_path}/{f_uuid_name}"
|
132
133
|
# Save file in target directory.
|
133
|
-
with open(f_target_path, mode="wb") as open_f:
|
134
|
+
async with aiofiles.open(f_target_path, mode="wb") as open_f:
|
134
135
|
f_content = b64decode(base64_str)
|
135
|
-
open_f.write(f_content)
|
136
|
+
await open_f.write(f_content)
|
136
137
|
# Add paths to target file.
|
137
138
|
file_info["path"] = f_target_path
|
138
139
|
file_info["url"] = f"{self.media_url}/{self.target_dir}/{date_str}/{f_uuid_name}"
|
@@ -141,12 +142,12 @@ class FileField(Field, FileGroup, JsonMixin):
|
|
141
142
|
# Add file extension.
|
142
143
|
file_info["extension"] = extension
|
143
144
|
# Add file size (in bytes).
|
144
|
-
file_info["size"] = os.path.getsize(f_target_path)
|
145
|
+
file_info["size"] = await aiofiles.os.path.getsize(f_target_path)
|
145
146
|
#
|
146
147
|
# to value.
|
147
148
|
self.value = file_info
|
148
149
|
|
149
|
-
def from_path(
|
150
|
+
async def from_path(
|
150
151
|
self,
|
151
152
|
src_path: str | None = None,
|
152
153
|
is_delete: bool = False,
|
@@ -170,12 +171,12 @@ class FileField(Field, FileGroup, JsonMixin):
|
|
170
171
|
# Create path to target directory.
|
171
172
|
dir_target_path = f"{self.media_root}/{self.target_dir}/{date_str}"
|
172
173
|
# Create target directory if it does not exist.
|
173
|
-
if not os.path.exists(dir_target_path):
|
174
|
-
os.makedirs(dir_target_path)
|
174
|
+
if not await aiofiles.os.path.exists(dir_target_path):
|
175
|
+
await aiofiles.os.makedirs(dir_target_path)
|
175
176
|
# Create path to target file.
|
176
177
|
f_target_path = f"{dir_target_path}/{f_uuid_name}"
|
177
178
|
# Save file in target directory.
|
178
|
-
|
179
|
+
await copyfile(src_path, f_target_path)
|
179
180
|
# Add paths to target file.
|
180
181
|
file_info["path"] = f_target_path
|
181
182
|
file_info["url"] = f"{self.media_url}/{self.target_dir}/{date_str}/{f_uuid_name}"
|
@@ -184,7 +185,7 @@ class FileField(Field, FileGroup, JsonMixin):
|
|
184
185
|
# Add file extension.
|
185
186
|
file_info["extension"] = extension
|
186
187
|
# Add file size (in bytes).
|
187
|
-
file_info["size"] = os.path.getsize(f_target_path)
|
188
|
+
file_info["size"] = await aiofiles.os.path.getsize(f_target_path)
|
188
189
|
#
|
189
190
|
# to value.
|
190
191
|
self.value = file_info
|
ramifice/fields/image_field.py
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
"""Field of Model for upload image."""
|
2
2
|
|
3
3
|
import os
|
4
|
-
import shutil
|
5
4
|
import uuid
|
6
5
|
from base64 import b64decode
|
7
6
|
from datetime import date
|
8
7
|
from pathlib import Path
|
9
|
-
from typing import Any
|
10
8
|
|
11
|
-
|
9
|
+
import aiofiles
|
10
|
+
from aioshutil import copyfile
|
12
11
|
|
13
12
|
from ramifice.fields.general.field import Field
|
14
13
|
from ramifice.fields.general.file_group import FileGroup
|
@@ -38,8 +37,6 @@ class ImageField(Field, FileGroup, JsonMixin):
|
|
38
37
|
# Available 4 sizes from lg to xs or None.
|
39
38
|
# Example: {"lg": 1200, "md": 600, "sm": 300, "xs": 150 }
|
40
39
|
thumbnails: dict[str, int] | None = None,
|
41
|
-
# True - high quality and low performance for thumbnails.
|
42
|
-
high_quality: bool = False,
|
43
40
|
):
|
44
41
|
if globals.DEBUG:
|
45
42
|
if default is not None:
|
@@ -98,8 +95,6 @@ class ImageField(Field, FileGroup, JsonMixin):
|
|
98
95
|
raise AssertionError("Parameter `target_dir` - Not а `str` type!")
|
99
96
|
if not isinstance(accept, str):
|
100
97
|
raise AssertionError("Parameter `accept` - Not а `str` type!")
|
101
|
-
if not isinstance(high_quality, bool):
|
102
|
-
raise AssertionError("Parameter `high_quality` - Not а `bool` type!")
|
103
98
|
|
104
99
|
Field.__init__(
|
105
100
|
self,
|
@@ -127,10 +122,8 @@ class ImageField(Field, FileGroup, JsonMixin):
|
|
127
122
|
# Available 4 sizes from lg to xs or None.
|
128
123
|
# Example: {"lg": 1200, "md": 600, "sm": 300, "xs": 150 }
|
129
124
|
self.thumbnails = thumbnails
|
130
|
-
# True is high quality and low performance.
|
131
|
-
self.high_quality = high_quality
|
132
125
|
|
133
|
-
def from_base64(
|
126
|
+
async def from_base64(
|
134
127
|
self,
|
135
128
|
base64_str: str | None = None,
|
136
129
|
filename: str | None = None,
|
@@ -170,21 +163,15 @@ class ImageField(Field, FileGroup, JsonMixin):
|
|
170
163
|
# Create path to main image.
|
171
164
|
main_img_path = f"{imgs_dir_path}/{new_original_name}"
|
172
165
|
# Create target directory if it does not exist.
|
173
|
-
if not os.path.exists(imgs_dir_path):
|
174
|
-
os.makedirs(imgs_dir_path)
|
166
|
+
if not await aiofiles.os.path.exists(imgs_dir_path):
|
167
|
+
await aiofiles.os.makedirs(imgs_dir_path)
|
175
168
|
# Save main image in target directory.
|
176
|
-
with open(main_img_path, mode="wb") as open_f:
|
169
|
+
async with aiofiles.open(main_img_path, mode="wb") as open_f:
|
177
170
|
f_content = b64decode(base64_str)
|
178
|
-
open_f.write(f_content)
|
171
|
+
await open_f.write(f_content)
|
179
172
|
# Add paths for main image.
|
180
173
|
img_info["path"] = main_img_path
|
181
174
|
img_info["url"] = f"{imgs_dir_url}/{new_original_name}"
|
182
|
-
# Add width and height.
|
183
|
-
if self.__dict__.get("add_width_height", False):
|
184
|
-
with Image.open(main_img_path) as img:
|
185
|
-
width, height = img.size
|
186
|
-
img_info["width"] = width
|
187
|
-
img_info["height"] = height
|
188
175
|
# Add original image name.
|
189
176
|
img_info["name"] = filename
|
190
177
|
# Add image extension.
|
@@ -199,12 +186,12 @@ class ImageField(Field, FileGroup, JsonMixin):
|
|
199
186
|
# Add url path to target directory with images.
|
200
187
|
img_info["imgs_dir_url"] = imgs_dir_url
|
201
188
|
# Add size of main image (in bytes).
|
202
|
-
img_info["size"] = os.path.getsize(main_img_path)
|
189
|
+
img_info["size"] = await aiofiles.os.path.getsize(main_img_path)
|
203
190
|
#
|
204
191
|
# to value.
|
205
192
|
self.value = img_info
|
206
193
|
|
207
|
-
def from_path(
|
194
|
+
async def from_path(
|
208
195
|
self,
|
209
196
|
src_path: str | None = None,
|
210
197
|
is_delete: bool = False,
|
@@ -234,19 +221,13 @@ class ImageField(Field, FileGroup, JsonMixin):
|
|
234
221
|
# Create path to main image.
|
235
222
|
main_img_path = f"{imgs_dir_path}/{new_original_name}"
|
236
223
|
# Create target directory if it does not exist.
|
237
|
-
if not os.path.exists(imgs_dir_path):
|
238
|
-
os.makedirs(imgs_dir_path)
|
224
|
+
if not await aiofiles.os.path.exists(imgs_dir_path):
|
225
|
+
await aiofiles.os.makedirs(imgs_dir_path)
|
239
226
|
# Save main image in target directory.
|
240
|
-
|
227
|
+
await copyfile(src_path, main_img_path)
|
241
228
|
# Add paths for main image.
|
242
229
|
img_info["path"] = main_img_path
|
243
230
|
img_info["url"] = f"{imgs_dir_url}/{new_original_name}"
|
244
|
-
# Add width and height.
|
245
|
-
if self.__dict__.get("add_width_height", False):
|
246
|
-
with Image.open(main_img_path) as img:
|
247
|
-
width, height = img.size
|
248
|
-
img_info["width"] = width
|
249
|
-
img_info["height"] = height
|
250
231
|
# Add original image name.
|
251
232
|
img_info["name"] = os.path.basename(src_path)
|
252
233
|
# Add image extension.
|
@@ -261,7 +242,7 @@ class ImageField(Field, FileGroup, JsonMixin):
|
|
261
242
|
# Add url path to target directory with images.
|
262
243
|
img_info["imgs_dir_url"] = imgs_dir_url
|
263
244
|
# Add size of main image (in bytes).
|
264
|
-
img_info["size"] = os.path.getsize(main_img_path)
|
245
|
+
img_info["size"] = await aiofiles.os.path.getsize(main_img_path)
|
265
246
|
#
|
266
247
|
# to value.
|
267
248
|
self.value = img_info
|
ramifice/models/decorator.py
CHANGED
@@ -7,8 +7,7 @@ from typing import Any
|
|
7
7
|
from ramifice.commons import QCommonsMixin
|
8
8
|
from ramifice.fields import DateTimeField, IDField
|
9
9
|
from ramifice.models.model import Model
|
10
|
-
from ramifice.
|
11
|
-
from ramifice.paladins import CheckMixin, QPaladinsMixin, ValidationMixin
|
10
|
+
from ramifice.paladins import QPaladinsMixin
|
12
11
|
from ramifice.utils.errors import DoesNotMatchRegexError, PanicError
|
13
12
|
from ramifice.utils.globals import REGEX
|
14
13
|
from ramifice.utils.mixins.add_valid import AddValidMixin
|
@@ -20,7 +19,6 @@ def model(
|
|
20
19
|
service_name: str,
|
21
20
|
fixture_name: str | None = None,
|
22
21
|
db_query_docs_limit: int = 1000,
|
23
|
-
is_migrate_model: bool = True,
|
24
22
|
is_create_doc: bool = True,
|
25
23
|
is_update_doc: bool = True,
|
26
24
|
is_delete_doc: bool = True,
|
@@ -32,8 +30,6 @@ def model(
|
|
32
30
|
raise AssertionError("Parameter `fixture_name` - Must be `str | None` type!")
|
33
31
|
if not isinstance(db_query_docs_limit, int):
|
34
32
|
raise AssertionError("Parameter `db_query_docs_limit` - Must be `int` type!")
|
35
|
-
if not isinstance(is_migrate_model, bool):
|
36
|
-
raise AssertionError("Parameter `is_migrate_model` - Must be `bool` type!")
|
37
33
|
if not isinstance(is_create_doc, bool):
|
38
34
|
raise AssertionError("Parameter `is_create_doc` - Must be `bool` type!")
|
39
35
|
if not isinstance(is_update_doc, bool):
|
@@ -55,53 +51,37 @@ def model(
|
|
55
51
|
raise PanicError(msg)
|
56
52
|
|
57
53
|
attrs = {key: val for key, val in cls.__dict__.items()}
|
58
|
-
|
59
|
-
attrs["__dict__"] = Model.__dict__["__dict__"]
|
60
|
-
else:
|
61
|
-
attrs["__dict__"] = PseudoModel.__dict__["__dict__"]
|
54
|
+
attrs["__dict__"] = Model.__dict__["__dict__"]
|
62
55
|
metadata = {
|
63
56
|
"service_name": service_name,
|
64
57
|
"fixture_name": fixture_name,
|
65
58
|
"db_query_docs_limit": db_query_docs_limit,
|
66
|
-
"
|
67
|
-
"
|
68
|
-
"
|
69
|
-
"is_delete_doc": is_delete_doc if is_migrate_model else False,
|
59
|
+
"is_create_doc": is_create_doc,
|
60
|
+
"is_update_doc": is_update_doc,
|
61
|
+
"is_delete_doc": is_delete_doc,
|
70
62
|
}
|
71
63
|
attrs["META"] = {
|
72
64
|
**metadata,
|
73
|
-
**caching(cls, service_name
|
65
|
+
**caching(cls, service_name),
|
74
66
|
}
|
75
67
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
)
|
89
|
-
else:
|
90
|
-
return type(
|
91
|
-
cls.__name__,
|
92
|
-
(
|
93
|
-
PseudoModel,
|
94
|
-
ValidationMixin,
|
95
|
-
CheckMixin,
|
96
|
-
AddValidMixin,
|
97
|
-
),
|
98
|
-
attrs,
|
99
|
-
)
|
68
|
+
return type(
|
69
|
+
cls.__name__,
|
70
|
+
(
|
71
|
+
Model,
|
72
|
+
QPaladinsMixin,
|
73
|
+
QCommonsMixin,
|
74
|
+
AddValidMixin,
|
75
|
+
IndexMixin,
|
76
|
+
HooksMixin,
|
77
|
+
),
|
78
|
+
attrs,
|
79
|
+
)
|
100
80
|
|
101
81
|
return decorator
|
102
82
|
|
103
83
|
|
104
|
-
def caching(cls: Any, service_name: str
|
84
|
+
def caching(cls: Any, service_name: str) -> dict[str, Any]:
|
105
85
|
"""Add additional metadata to `Model.META`."""
|
106
86
|
metadata: dict[str, Any] = {}
|
107
87
|
model_name: str = cls.__name__
|
@@ -128,11 +108,11 @@ def caching(cls: Any, service_name: str, is_migrate_model: bool) -> dict[str, An
|
|
128
108
|
|
129
109
|
raw_model = cls()
|
130
110
|
raw_model.fields()
|
131
|
-
default_fields: dict[str, Any] = {
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
111
|
+
default_fields: dict[str, Any] = {
|
112
|
+
"_id": IDField(),
|
113
|
+
"created_at": DateTimeField(),
|
114
|
+
"updated_at": DateTimeField(),
|
115
|
+
}
|
136
116
|
fields = {**raw_model.__dict__, **default_fields}
|
137
117
|
for f_name, f_data in fields.items():
|
138
118
|
if not callable(f_data):
|
@@ -147,19 +127,11 @@ def caching(cls: Any, service_name: str, is_migrate_model: bool) -> dict[str, An
|
|
147
127
|
#
|
148
128
|
if not f_data.ignored:
|
149
129
|
# Count fields for migrating.
|
150
|
-
|
151
|
-
count_fields_no_ignored += 1
|
130
|
+
count_fields_no_ignored += 1
|
152
131
|
# Get a dictionary of field names and types.
|
153
132
|
field_name_and_type[f_name] = f_type_str
|
154
133
|
# Build data migration storage for dynamic fields.
|
155
134
|
if "Dyn" in f_data.field_type:
|
156
|
-
if not is_migrate_model:
|
157
|
-
msg = (
|
158
|
-
f"Model: `{cls.__module__}.{model_name}` > "
|
159
|
-
+ f"Field: `{f_name}` => "
|
160
|
-
+ "Dynamic field only for a migrated Model."
|
161
|
-
)
|
162
|
-
raise PanicError(msg)
|
163
135
|
data_dynamic_fields[f_name] = None
|
164
136
|
if f_data.field_type == "TextField" and f_data.multi_language:
|
165
137
|
supported_lang_fields.append(f_name)
|
ramifice/paladins/check.py
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
"""Validation of Model data before saving to the database."""
|
2
2
|
|
3
|
-
import os
|
4
|
-
import shutil
|
5
3
|
from typing import Any
|
6
4
|
|
5
|
+
from aiofiles import os
|
6
|
+
from aioshutil import rmtree
|
7
7
|
from bson.objectid import ObjectId
|
8
8
|
from pymongo.asynchronous.collection import AsyncCollection
|
9
9
|
|
@@ -48,27 +48,15 @@ class CheckMixin(
|
|
48
48
|
It is also used to verify Models that do not migrate to the database.
|
49
49
|
"""
|
50
50
|
cls_model = self.__class__
|
51
|
-
is_migrate_model: bool = cls_model.META["is_migrate_model"]
|
52
51
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
doc_id: ObjectId | None = None
|
62
|
-
is_update: bool = False
|
63
|
-
if is_migrate_model:
|
64
|
-
# Get the document ID.
|
65
|
-
doc_id = self._id.value
|
66
|
-
# Does the document exist in the database?
|
67
|
-
is_update = doc_id is not None
|
68
|
-
# Create an identifier for a new document.
|
69
|
-
if is_save and not is_update:
|
70
|
-
doc_id = ObjectId()
|
71
|
-
self._id.value = doc_id
|
52
|
+
# Get the document ID.
|
53
|
+
doc_id: ObjectId | None = self._id.value
|
54
|
+
# Does the document exist in the database?
|
55
|
+
is_update: bool = doc_id is not None
|
56
|
+
# Create an identifier for a new document.
|
57
|
+
if is_save and not is_update:
|
58
|
+
doc_id = ObjectId()
|
59
|
+
self._id.value = doc_id
|
72
60
|
|
73
61
|
result_map: dict[str, Any] = {}
|
74
62
|
# Errors from additional validation of fields.
|
@@ -86,7 +74,6 @@ class CheckMixin(
|
|
86
74
|
"collection": collection,
|
87
75
|
"field_data": None,
|
88
76
|
"full_model_name": cls_model.META["full_model_name"],
|
89
|
-
"is_migrate_model": is_migrate_model,
|
90
77
|
"is_migration_process": is_migration_process,
|
91
78
|
"curr_doc": (
|
92
79
|
await collection.find_one({"_id": doc_id}) if is_save and is_update else None
|
@@ -116,9 +103,9 @@ class CheckMixin(
|
|
116
103
|
elif group == "date":
|
117
104
|
self.date_group(params)
|
118
105
|
elif group == "img":
|
119
|
-
self.img_group(params)
|
106
|
+
await self.img_group(params)
|
120
107
|
elif group == "file":
|
121
|
-
self.file_group(params)
|
108
|
+
await self.file_group(params)
|
122
109
|
elif group == "choice":
|
123
110
|
self.choice_group(params)
|
124
111
|
elif group == "bool":
|
@@ -146,7 +133,7 @@ class CheckMixin(
|
|
146
133
|
file_data = result_map.get(field_name)
|
147
134
|
if file_data is not None:
|
148
135
|
if file_data["is_new_file"]:
|
149
|
-
os.remove(file_data["path"])
|
136
|
+
await os.remove(file_data["path"])
|
150
137
|
field_data.value = None
|
151
138
|
if curr_doc is not None:
|
152
139
|
field_data.value = curr_doc[field_name]
|
@@ -154,7 +141,7 @@ class CheckMixin(
|
|
154
141
|
img_data = result_map.get(field_name)
|
155
142
|
if img_data is not None:
|
156
143
|
if img_data["is_new_img"]:
|
157
|
-
|
144
|
+
await rmtree(img_data["imgs_dir_path"]) # type: ignore[call-arg]
|
158
145
|
field_data.value = None
|
159
146
|
if curr_doc is not None:
|
160
147
|
field_data.value = curr_doc[field_name]
|
ramifice/paladins/delete.py
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
"""Delete document from database."""
|
2
2
|
|
3
|
-
import os
|
4
|
-
import shutil
|
5
3
|
from typing import Any
|
6
4
|
|
5
|
+
from aiofiles import os
|
6
|
+
from aioshutil import rmtree
|
7
7
|
from pymongo.asynchronous.collection import AsyncCollection
|
8
8
|
|
9
9
|
from ramifice.utils import globals
|
@@ -77,12 +77,12 @@ class DeleteMixin:
|
|
77
77
|
if group == "file":
|
78
78
|
file_data = mongo_doc[field_name]
|
79
79
|
if file_data is not None and len(file_data["path"]) > 0:
|
80
|
-
os.remove(file_data["path"])
|
80
|
+
await os.remove(file_data["path"])
|
81
81
|
file_data = None
|
82
82
|
elif group == "img":
|
83
83
|
file_data = mongo_doc[field_name]
|
84
84
|
if file_data is not None and len(file_data["imgs_dir_path"]) > 0:
|
85
|
-
|
85
|
+
await rmtree(file_data["imgs_dir_path"]) # type: ignore[call-arg]
|
86
86
|
file_data = None
|
87
87
|
field_data.value = None
|
88
88
|
# Run hook.
|
@@ -19,7 +19,7 @@ class FileGroupMixin:
|
|
19
19
|
Supported fields: FileField
|
20
20
|
"""
|
21
21
|
|
22
|
-
def file_group(self, params: dict[str, Any]) -> None:
|
22
|
+
async def file_group(self, params: dict[str, Any]) -> None:
|
23
23
|
"""Checking file fields."""
|
24
24
|
field = params["field_data"]
|
25
25
|
value = field.value or None
|
@@ -3,6 +3,7 @@
|
|
3
3
|
Supported fields: ImageField
|
4
4
|
"""
|
5
5
|
|
6
|
+
import asyncio
|
6
7
|
from typing import Any
|
7
8
|
|
8
9
|
from PIL import Image
|
@@ -18,7 +19,7 @@ class ImgGroupMixin:
|
|
18
19
|
Supported fields: ImageField
|
19
20
|
"""
|
20
21
|
|
21
|
-
def img_group(self, params: dict[str, Any]) -> None:
|
22
|
+
async def img_group(self, params: dict[str, Any]) -> None:
|
22
23
|
"""Checking image fields."""
|
23
24
|
field = params["field_data"]
|
24
25
|
value = field.value or None
|
@@ -79,37 +80,48 @@ class ImgGroupMixin:
|
|
79
80
|
# Extension to the upper register and delete the point.
|
80
81
|
ext_upper = value["ext_upper"]
|
81
82
|
# Get image file.
|
82
|
-
with Image.open
|
83
|
+
with await asyncio.to_thread(Image.open, path) as img:
|
83
84
|
width, height = img.size
|
84
85
|
value["width"] = width
|
85
86
|
value["height"] = height
|
86
|
-
resample = (
|
87
|
-
Image.Resampling.LANCZOS # High quality and low performance.
|
88
|
-
if params["field_data"].high_quality
|
89
|
-
else Image.Resampling.BICUBIC
|
90
|
-
)
|
91
87
|
for size_name in ["lg", "md", "sm", "xs"]:
|
92
88
|
max_size = thumbnails.get(size_name)
|
93
89
|
if max_size is None:
|
94
90
|
continue
|
95
91
|
size = max_size, max_size
|
96
|
-
img.thumbnail(size=size, resample=
|
92
|
+
img.thumbnail(size=size, resample=Image.Resampling.LANCZOS)
|
97
93
|
if size_name == "lg":
|
98
94
|
value["path_lg"] = f"{imgs_dir_path}/lg{extension}"
|
99
95
|
value["url_lg"] = f"{imgs_dir_url}/lg{extension}"
|
100
|
-
|
96
|
+
await asyncio.to_thread(
|
97
|
+
img.save,
|
98
|
+
fp=value["path_lg"],
|
99
|
+
format=ext_upper,
|
100
|
+
)
|
101
101
|
elif size_name == "md":
|
102
102
|
value["path_md"] = f"{imgs_dir_path}/md{extension}"
|
103
103
|
value["url_md"] = f"{imgs_dir_url}/md{extension}"
|
104
|
-
|
104
|
+
await asyncio.to_thread(
|
105
|
+
img.save,
|
106
|
+
fp=value["path_md"],
|
107
|
+
format=ext_upper,
|
108
|
+
)
|
105
109
|
elif size_name == "sm":
|
106
110
|
value["path_sm"] = f"{imgs_dir_path}/sm{extension}"
|
107
111
|
value["url_sm"] = f"{imgs_dir_url}/sm{extension}"
|
108
|
-
|
112
|
+
await asyncio.to_thread(
|
113
|
+
img.save,
|
114
|
+
fp=value["path_sm"],
|
115
|
+
format=ext_upper,
|
116
|
+
)
|
109
117
|
elif size_name == "xs":
|
110
118
|
value["path_xs"] = f"{imgs_dir_path}/xs{extension}"
|
111
119
|
value["url_xs"] = f"{imgs_dir_url}/xs{extension}"
|
112
|
-
|
120
|
+
await asyncio.to_thread(
|
121
|
+
img.save,
|
122
|
+
fp=value["path_xs"],
|
123
|
+
format=ext_upper,
|
124
|
+
)
|
113
125
|
# Insert result.
|
114
126
|
if params["is_save"] and (value["is_new_img"] or value["save_as_is"]):
|
115
127
|
value["is_delete"] = False
|
@@ -5,13 +5,23 @@ Supported fields:
|
|
5
5
|
IPField | EmailField | ColorField
|
6
6
|
"""
|
7
7
|
|
8
|
+
import asyncio
|
8
9
|
from typing import Any
|
9
10
|
|
10
11
|
from email_validator import EmailNotValidError, validate_email
|
11
12
|
|
12
|
-
from ramifice.paladins.tools import
|
13
|
+
from ramifice.paladins.tools import (
|
14
|
+
accumulate_error,
|
15
|
+
check_uniqueness,
|
16
|
+
panic_type_error,
|
17
|
+
)
|
13
18
|
from ramifice.utils import translations
|
14
|
-
from ramifice.utils.tools import
|
19
|
+
from ramifice.utils.tools import (
|
20
|
+
is_color,
|
21
|
+
is_ip,
|
22
|
+
is_phone,
|
23
|
+
is_url,
|
24
|
+
)
|
15
25
|
|
16
26
|
|
17
27
|
class TextGroupMixin:
|
@@ -62,9 +72,10 @@ class TextGroupMixin:
|
|
62
72
|
# Validation Email, Url, IP, Color, Phone.
|
63
73
|
if "EmailField" == field_type:
|
64
74
|
try:
|
65
|
-
emailinfo =
|
75
|
+
emailinfo = await asyncio.to_thread(
|
76
|
+
validate_email,
|
66
77
|
str(value),
|
67
|
-
check_deliverability=
|
78
|
+
check_deliverability=True,
|
68
79
|
)
|
69
80
|
value = emailinfo.normalized
|
70
81
|
params["field_data"].value = value
|
ramifice/paladins/tools.py
CHANGED
@@ -58,8 +58,6 @@ async def check_uniqueness(
|
|
58
58
|
is_multi_language: bool = False,
|
59
59
|
) -> bool:
|
60
60
|
"""Check the uniqueness of the value in the collection."""
|
61
|
-
if not params["is_migrate_model"]:
|
62
|
-
return True
|
63
61
|
q_filter = None
|
64
62
|
if is_multi_language:
|
65
63
|
lang_filter = [{f"{field_name}.{lang}": value} for lang in translations.LANGUAGES]
|
ramifice/utils/migration.py
CHANGED
@@ -31,9 +31,7 @@ class Monitor:
|
|
31
31
|
globals.MONGO_CLIENT = mongo_client
|
32
32
|
globals.MONGO_DATABASE = globals.MONGO_CLIENT[globals.DATABASE_NAME]
|
33
33
|
# Get Model list.
|
34
|
-
self.model_list: list[Any] =
|
35
|
-
cls_model for cls_model in Model.__subclasses__() if cls_model.META["is_migrate_model"]
|
36
|
-
]
|
34
|
+
self.model_list: list[Any] = Model.__subclasses__()
|
37
35
|
# Raise the exception if there are no models for migration.
|
38
36
|
if len(self.model_list) == 0:
|
39
37
|
raise NoModelsForMigrationError() # type: ignore[no-untyped-call]
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: ramifice
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.6.0
|
4
4
|
Summary: ORM-like API MongoDB for Python language.
|
5
5
|
Project-URL: Homepage, https://github.com/kebasyaty/ramifice
|
6
6
|
Project-URL: Documentation, https://kebasyaty.github.io/ramifice/
|
@@ -23,6 +23,8 @@ Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
23
23
|
Classifier: Topic :: Database
|
24
24
|
Classifier: Typing :: Typed
|
25
25
|
Requires-Python: <4.0,>=3.12
|
26
|
+
Requires-Dist: aiofiles>=24.1.0
|
27
|
+
Requires-Dist: aioshutil>=1.5
|
26
28
|
Requires-Dist: argon2-cffi>=25.1.0
|
27
29
|
Requires-Dist: babel>=2.17.0
|
28
30
|
Requires-Dist: email-validator>=2.2.0
|
@@ -58,6 +60,7 @@ Description-Content-Type: text/markdown
|
|
58
60
|
<a href="https://pepy.tech/projects/ramifice"><img src="https://static.pepy.tech/badge/ramifice" alt="PyPI Downloads"></a>
|
59
61
|
<a href="https://github.com/kebasyaty/ramifice/blob/main/LICENSE" alt="GitHub license"><img src="https://img.shields.io/github/license/kebasyaty/ramifice" alt="GitHub license"></a>
|
60
62
|
<a href="https://docs.astral.sh/ruff/" alt="Code style: Ruff"><img src="https://img.shields.io/badge/code%20style-Ruff-FDD835.svg" alt="Code style: Ruff"></a>
|
63
|
+
<a href="https://github.com/kebasyaty/ramifice" alt="PyPI implementation"><img src="https://img.shields.io/pypi/implementation/ramifice" alt="PyPI implementation"></a>
|
61
64
|
<a href="https://github.com/kebasyaty/ramifice" alt="GitHub repository"><img src="https://img.shields.io/badge/--ecebeb?logo=github&logoColor=000000" alt="GitHub repository"></a>
|
62
65
|
</p>
|
63
66
|
<p align="center">
|
@@ -148,16 +151,12 @@ class User:
|
|
148
151
|
# ngettext = translations.ngettext
|
149
152
|
self.avatar = ImageField(
|
150
153
|
label=gettext("Avatar"),
|
151
|
-
placeholder=gettext("Upload your photo"),
|
152
154
|
default="public/media/default/no-photo.png",
|
153
155
|
# Directory for images inside media directory.
|
154
156
|
target_dir="users/avatars",
|
155
157
|
# Available 4 sizes from lg to xs or None.
|
156
158
|
# Hint: By default = None
|
157
159
|
thumbnails={"lg": 512, "md": 256, "sm": 128, "xs": 64},
|
158
|
-
# True - high quality and low performance for thumbnails.
|
159
|
-
# Hint: By default = False
|
160
|
-
high_quality=True,
|
161
160
|
# The maximum size of the original image in bytes.
|
162
161
|
# Hint: By default = 2 MB
|
163
162
|
max_size=524288, # 0.5 MB = 512 KB = 524288 Bytes (in binary)
|
@@ -167,7 +166,6 @@ class User:
|
|
167
166
|
)
|
168
167
|
self.username = TextField(
|
169
168
|
label=gettext("Username"),
|
170
|
-
placeholder=gettext("Enter your username"),
|
171
169
|
maxlength=150,
|
172
170
|
required=True,
|
173
171
|
unique=True,
|
@@ -177,11 +175,9 @@ class User:
|
|
177
175
|
)
|
178
176
|
self.password = PasswordField(
|
179
177
|
label=gettext("Password"),
|
180
|
-
placeholder=gettext("Enter your password"),
|
181
178
|
)
|
182
179
|
self.сonfirm_password = PasswordField(
|
183
180
|
label=gettext("Confirm password"),
|
184
|
-
placeholder=gettext("Repeat your password"),
|
185
181
|
# If true, the value of this field is not saved in the database.
|
186
182
|
ignored=True,
|
187
183
|
)
|
@@ -342,14 +338,6 @@ See the documentation [here](https://kebasyaty.github.io/ramifice/ "here").
|
|
342
338
|
<td align="left">1000</td>
|
343
339
|
<td align="left">limiting query results.</td>
|
344
340
|
</tr>
|
345
|
-
<tr>
|
346
|
-
<td align="left">is_migrate_model</td>
|
347
|
-
<td align="left">True</td>
|
348
|
-
<td align="left">
|
349
|
-
Set to <b>False</b> if you do not need to migrate the Model to the database.<br>
|
350
|
-
This can be use to validate a web forms - Search form, Contact form, etc.
|
351
|
-
</td>
|
352
|
-
</tr>
|
353
341
|
<tr>
|
354
342
|
<td align="left">is_create_doc</td>
|
355
343
|
<td align="left">True</td>
|
@@ -380,7 +368,6 @@ See the documentation [here](https://kebasyaty.github.io/ramifice/ "here").
|
|
380
368
|
service_name="ServiceName",
|
381
369
|
fixture_name="FixtureName",
|
382
370
|
db_query_docs_limit=1000,
|
383
|
-
is_migrate_model=True,
|
384
371
|
is_create_doc = True,
|
385
372
|
is_update_doc = True,
|
386
373
|
is_delete_doc = True,
|
@@ -25,10 +25,10 @@ ramifice/fields/color_field.py,sha256=Syam8MtB--B0HzAT3w3QuWz3pPixSMbh8xGPmbGYLC
|
|
25
25
|
ramifice/fields/date_field.py,sha256=xhMRsEwk4CztDae5va2Tx0v9Oh55M4p0S4Y0qu-ZlIk,5251
|
26
26
|
ramifice/fields/date_time_field.py,sha256=RKxXVWTWGNxTri-MDlOAZNSwgtL46m-0grh3wKX-gi8,5286
|
27
27
|
ramifice/fields/email_field.py,sha256=qXG7zHkr5ox4CFrb6QYarBMl8gwcq8KlVFVCj-zfvs0,3477
|
28
|
-
ramifice/fields/file_field.py,sha256=
|
28
|
+
ramifice/fields/file_field.py,sha256=YLdZvotj6ymEVRjYGU4xsiHkP_zI8krk3TuHaowvfDo,8126
|
29
29
|
ramifice/fields/float_field.py,sha256=wadjiYakzv4uzWD8wI2E0B9w78Or4hZiAmrNXlOy8EI,4629
|
30
30
|
ramifice/fields/id_field.py,sha256=NqdM0UJAi-iXJc7wa6CAJNG0-i4YNt08XGkHqV5ingc,4157
|
31
|
-
ramifice/fields/image_field.py,sha256=
|
31
|
+
ramifice/fields/image_field.py,sha256=MfKILPPz2ye8x696eEu8loRRpIWgP4neVoZ7ucNkDIw,11545
|
32
32
|
ramifice/fields/integer_field.py,sha256=QpLqLItG4mVMYOhpGDVFNr-wDFqeUuERtfZy7C-EslE,4605
|
33
33
|
ramifice/fields/ip_field.py,sha256=q-2OgT4VBDo8yzrX7wwuSHwexwsM8wDwnHOyQRACItI,3386
|
34
34
|
ramifice/fields/password_field.py,sha256=EzIsegHdsVpnj9D-cTI1Q9RBbW07qUx7I7UFcMWW8Hk,3238
|
@@ -44,33 +44,32 @@ ramifice/fields/general/file_group.py,sha256=T-IwFnlJAWaRcQKVn09IJCdFNGgnUfMkjLz
|
|
44
44
|
ramifice/fields/general/number_group.py,sha256=LOPHbKAnIuqW4DEh2fH78w6qOQjp0OhkuP7MoikhlkA,761
|
45
45
|
ramifice/fields/general/text_group.py,sha256=m9EnlYGwqlck-JIYOCUd0hUYAVr8jH4YCnTmm9QlCkQ,1103
|
46
46
|
ramifice/models/__init__.py,sha256=h_QQ5rSJNZ-7kmx7wIFd8E8RmUS94b_x4jdwMbq8zm4,15
|
47
|
-
ramifice/models/decorator.py,sha256=
|
47
|
+
ramifice/models/decorator.py,sha256=vEblLbpQQ0BkdTI2ch-BtDvCqWSEXe2aaEjxND705_Q,5954
|
48
48
|
ramifice/models/model.py,sha256=JPEUYONRHVj7uE5vnuodc65dGHyBQCRmV8mdVUAgyXw,7118
|
49
|
-
ramifice/models/pseudo.py,sha256=PEtK_ilezUlKKUyvwBZv5fc93SbZh2yoIQuc2c4D-Ko,6779
|
50
49
|
ramifice/paladins/__init__.py,sha256=EJ6EEKXUcMG5P1bDmlEa4W3fsxSz5u6mzDG_oU1efMQ,618
|
51
|
-
ramifice/paladins/check.py,sha256=
|
52
|
-
ramifice/paladins/delete.py,sha256=
|
50
|
+
ramifice/paladins/check.py,sha256=OeEDtL3lNR94bL1shrHloAHXHZtHnl6IWzEgukD9HSU,6580
|
51
|
+
ramifice/paladins/delete.py,sha256=hnLpyMoNbgegQ_GF-A7iEHj7cbk1oSc5xyTy1BNLxsE,3426
|
53
52
|
ramifice/paladins/password.py,sha256=pp0AVMf98w-2Ps3hHqwwIDSsXh93s-1TrBbeEI83Ly8,3041
|
54
53
|
ramifice/paladins/refrash.py,sha256=4xojazc5BqCkE8_st11gH5tj2LpflzWVn2ZD-uNoOsg,1096
|
55
54
|
ramifice/paladins/save.py,sha256=Z8vmqEksCK8XY27-BsgN2lyaNxLregOo5p0_XB9qUys,3895
|
56
|
-
ramifice/paladins/tools.py,sha256=
|
55
|
+
ramifice/paladins/tools.py,sha256=zc2VXp1U_BCT4IQWopwhxJ5vqci9jX128U5_nMeHawY,2652
|
57
56
|
ramifice/paladins/validation.py,sha256=gcEJXIEPu1g7Z54vl14YTs5rCmxOEYsgQH1usFfyy7k,1730
|
58
57
|
ramifice/paladins/groups/__init__.py,sha256=GxJWM3IkYT3HW9pnJ1QTEjdOKJnjEoAaQ8SRukXnIW0,712
|
59
58
|
ramifice/paladins/groups/bool_group.py,sha256=rz1XIIdhOqbCOYZT1Yl84XaJg-0vwdcqCnt0BH-xHVA,808
|
60
59
|
ramifice/paladins/groups/choice_group.py,sha256=KmrjbuHLLl6yGbRkdoMflf1thh17GLWrcbfaqrHlDiE,1813
|
61
60
|
ramifice/paladins/groups/date_group.py,sha256=V3MCp8eA7txVLh6xmg76G9_y62TzWIQWqH59Ip26Xag,3779
|
62
|
-
ramifice/paladins/groups/file_group.py,sha256=
|
61
|
+
ramifice/paladins/groups/file_group.py,sha256=ySS7L5zcm2XrZRQUUOU8kgzqrucZ95UYzXD6pALWqE4,2982
|
63
62
|
ramifice/paladins/groups/id_group.py,sha256=zN_Glu_cMGk8jMSDli3vYPvYrQXvQJNJFzb-t-qtZlQ,1290
|
64
|
-
ramifice/paladins/groups/img_group.py,sha256=
|
63
|
+
ramifice/paladins/groups/img_group.py,sha256=YDJTvbRoqQ2nf7-2QbIQ72I4j_c98_d5LZNrD9kCy0Q,6040
|
65
64
|
ramifice/paladins/groups/num_group.py,sha256=sbxzTdl33TVySfaNfqMixyBkJ69v6AqEgraFUN3Kftk,2317
|
66
65
|
ramifice/paladins/groups/pass_group.py,sha256=uuIIqMBXsYG7vTHc_AhdgWuNCivxTgQMjkEu0-ElSmY,1887
|
67
66
|
ramifice/paladins/groups/slug_group.py,sha256=ztiA2v7e5lQYRZtlLw8WGOhSWaYQfOdZ6wkKbx3ZbTM,2329
|
68
|
-
ramifice/paladins/groups/text_group.py,sha256=
|
67
|
+
ramifice/paladins/groups/text_group.py,sha256=FSrZbm3Fd0SU0IIFFBxs9XN2HPSFHshgwIRiLaUDwNY,4456
|
69
68
|
ramifice/utils/__init__.py,sha256=xixHoOX4ja5jIUZemem1qn4k4aonv3G3Q76azQK_pkU,43
|
70
69
|
ramifice/utils/errors.py,sha256=iuhq7fzpUmsOyeXeg2fJjta8yAuqlXLKsZVMpfUhtHE,1901
|
71
70
|
ramifice/utils/fixtures.py,sha256=IG55gdFUbrWRQ2cL3Df9mYBgjVlFrlRuWB7UeK1Nzd8,3173
|
72
71
|
ramifice/utils/globals.py,sha256=uR20um3Qg_1SG1t7WyWbpq8kQD-9Mslyr_c1yh5Hw9w,1751
|
73
|
-
ramifice/utils/migration.py,sha256=
|
72
|
+
ramifice/utils/migration.py,sha256=JUhYaJ61IS1W_Khof0sbqeHDQrI7-XcmTQ41PhCdjcQ,11017
|
74
73
|
ramifice/utils/tools.py,sha256=1yFIOrkWToM55OpMMId9T26hZqPQWnxLU3_jQ4Ejazg,2743
|
75
74
|
ramifice/utils/translations.py,sha256=aEkNKilD7RlJjBixqhh0cfIMw9lg13woaIwd3dyR8G4,4247
|
76
75
|
ramifice/utils/unit.py,sha256=PPNKWYFJ8cz0nwbBPaTdL58_Nr7N0fIHFJBpKG2ZLKI,2482
|
@@ -79,7 +78,7 @@ ramifice/utils/mixins/add_valid.py,sha256=TLOObedzXNA9eCylfAVbVCqIKE5sV-P5AdIN7a
|
|
79
78
|
ramifice/utils/mixins/hooks.py,sha256=33jvJRhfnJeL2Hd_YFXk3M_7wjqHaByU2wRjKyboL6s,914
|
80
79
|
ramifice/utils/mixins/indexing.py,sha256=Z0427HoaVRyNmSNN8Fx0mSICgAKV-gDdu3iR5qYUEbs,329
|
81
80
|
ramifice/utils/mixins/json_converter.py,sha256=WhigXyDAV-FfILaZuwvRFRIk0D90Rv3dG5t-mv5fVyc,1107
|
82
|
-
ramifice-0.
|
83
|
-
ramifice-0.
|
84
|
-
ramifice-0.
|
85
|
-
ramifice-0.
|
81
|
+
ramifice-0.6.0.dist-info/METADATA,sha256=mGAJxiS68kKIy6ZO94iagVCIAXPPVM2hoxSZnwHGKyk,20987
|
82
|
+
ramifice-0.6.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
83
|
+
ramifice-0.6.0.dist-info/licenses/LICENSE,sha256=LrEL0aTZx90HDwFUQCJutORiDjJL9AnuVvCtspXIqt4,1095
|
84
|
+
ramifice-0.6.0.dist-info/RECORD,,
|
ramifice/models/pseudo.py
DELETED
@@ -1,181 +0,0 @@
|
|
1
|
-
"""For converting Python classes into Ramifice models."""
|
2
|
-
|
3
|
-
import json
|
4
|
-
import os
|
5
|
-
import shutil
|
6
|
-
from abc import ABCMeta, abstractmethod
|
7
|
-
from typing import Any
|
8
|
-
|
9
|
-
from babel.dates import format_date, format_datetime
|
10
|
-
from bson.objectid import ObjectId
|
11
|
-
from dateutil.parser import parse
|
12
|
-
|
13
|
-
from ramifice.utils import translations
|
14
|
-
from ramifice.utils.errors import PanicError
|
15
|
-
|
16
|
-
|
17
|
-
class PseudoModel(metaclass=ABCMeta):
|
18
|
-
"""Convert the Python Class into a pseudo Model Ramifice.
|
19
|
-
|
20
|
-
Used for a Model that do not migrate into the database.
|
21
|
-
"""
|
22
|
-
|
23
|
-
META: dict[str, Any] = {}
|
24
|
-
|
25
|
-
def __init__(self) -> None: # noqa: D107
|
26
|
-
self.fields()
|
27
|
-
self.inject()
|
28
|
-
|
29
|
-
for _, f_type in self.__dict__.items():
|
30
|
-
if not callable(f_type) and f_type.group == "img":
|
31
|
-
f_type.__dict__["add_width_height"] = True
|
32
|
-
|
33
|
-
def __del__(self) -> None: # noqa: D105
|
34
|
-
# If the model is not migrated,
|
35
|
-
# it must delete files and images in the destructor.
|
36
|
-
for _, f_type in self.__dict__.items():
|
37
|
-
if callable(f_type):
|
38
|
-
continue
|
39
|
-
value = f_type.value
|
40
|
-
if value is not None:
|
41
|
-
if f_type.group == "file":
|
42
|
-
value = value.get("path")
|
43
|
-
if value is not None:
|
44
|
-
os.remove(value)
|
45
|
-
elif f_type.group == "img":
|
46
|
-
value = value.get("imgs_dir_path")
|
47
|
-
if value is not None:
|
48
|
-
shutil.rmtree(value)
|
49
|
-
|
50
|
-
@abstractmethod
|
51
|
-
def fields(self) -> None:
|
52
|
-
"""For adding fields."""
|
53
|
-
pass
|
54
|
-
|
55
|
-
def model_name(self) -> str:
|
56
|
-
"""Get Model name - Class name."""
|
57
|
-
return self.__class__.__name__
|
58
|
-
|
59
|
-
def full_model_name(self) -> str:
|
60
|
-
"""Get full Model name - module_name + . + ClassName."""
|
61
|
-
cls = self.__class__
|
62
|
-
return f"{cls.__module__}.{cls.__name__}"
|
63
|
-
|
64
|
-
def inject(self) -> None:
|
65
|
-
"""Injecting metadata from Model.META in params of fields."""
|
66
|
-
metadata = self.__class__.META
|
67
|
-
if bool(metadata):
|
68
|
-
field_attrs = metadata["field_attrs"]
|
69
|
-
for f_name, f_type in self.__dict__.items():
|
70
|
-
if callable(f_type):
|
71
|
-
continue
|
72
|
-
f_type.id = field_attrs[f_name]["id"]
|
73
|
-
f_type.name = field_attrs[f_name]["name"]
|
74
|
-
if "Dyn" in f_type.field_type:
|
75
|
-
msg = (
|
76
|
-
f"Model: `{metadata['full_model_name']}` > "
|
77
|
-
+ f"Field: `{f_name}` => "
|
78
|
-
+ "Dynamic field only for a migrated Model."
|
79
|
-
)
|
80
|
-
raise PanicError(msg)
|
81
|
-
|
82
|
-
# Complect of methods for converting Model to JSON and back.
|
83
|
-
# --------------------------------------------------------------------------
|
84
|
-
def to_dict(self) -> dict[str, Any]:
|
85
|
-
"""Convert object instance to a dictionary."""
|
86
|
-
json_dict: dict[str, Any] = {}
|
87
|
-
for name, data in self.__dict__.items():
|
88
|
-
if not callable(data):
|
89
|
-
json_dict[name] = data.to_dict()
|
90
|
-
return json_dict
|
91
|
-
|
92
|
-
def to_json(self) -> str:
|
93
|
-
"""Convert object instance to a JSON string."""
|
94
|
-
return json.dumps(self.to_dict())
|
95
|
-
|
96
|
-
@classmethod
|
97
|
-
def from_dict(cls, json_dict: dict[str, Any]) -> Any:
|
98
|
-
"""Convert JSON string to a object instance."""
|
99
|
-
obj = cls()
|
100
|
-
for name, data in json_dict.items():
|
101
|
-
obj.__dict__[name] = obj.__dict__[name].__class__.from_dict(data)
|
102
|
-
return obj
|
103
|
-
|
104
|
-
@classmethod
|
105
|
-
def from_json(cls, json_str: str) -> Any:
|
106
|
-
"""Convert JSON string to a object instance."""
|
107
|
-
json_dict = json.loads(json_str)
|
108
|
-
return cls.from_dict(json_dict)
|
109
|
-
|
110
|
-
# --------------------------------------------------------------------------
|
111
|
-
def to_dict_only_value(self) -> dict[str, Any]:
|
112
|
-
"""Convert model.field.value (only the `value` attribute) to a dictionary."""
|
113
|
-
json_dict: dict[str, Any] = {}
|
114
|
-
current_locale = translations.CURRENT_LOCALE
|
115
|
-
for name, data in self.__dict__.items():
|
116
|
-
if callable(data):
|
117
|
-
continue
|
118
|
-
value = data.value
|
119
|
-
if value is not None:
|
120
|
-
group = data.group
|
121
|
-
if group == "date":
|
122
|
-
value = (
|
123
|
-
format_date(
|
124
|
-
date=value,
|
125
|
-
format="short",
|
126
|
-
locale=current_locale,
|
127
|
-
)
|
128
|
-
if data.field_type == "DateField"
|
129
|
-
else format_datetime(
|
130
|
-
datetime=value,
|
131
|
-
format="short",
|
132
|
-
locale=current_locale,
|
133
|
-
)
|
134
|
-
)
|
135
|
-
elif group == "id":
|
136
|
-
value = str(value)
|
137
|
-
elif group == "pass":
|
138
|
-
value = None
|
139
|
-
json_dict[name] = value
|
140
|
-
return json_dict
|
141
|
-
|
142
|
-
def to_json_only_value(self) -> str:
|
143
|
-
"""Convert model.field.value (only the `value` attribute) to a JSON string."""
|
144
|
-
return json.dumps(self.to_dict_only_value())
|
145
|
-
|
146
|
-
@classmethod
|
147
|
-
def from_dict_only_value(cls, json_dict: dict[str, Any]) -> Any:
|
148
|
-
"""Convert JSON string to a object instance."""
|
149
|
-
obj = cls()
|
150
|
-
for name, data in obj.__dict__.items():
|
151
|
-
if callable(data):
|
152
|
-
continue
|
153
|
-
value = json_dict.get(name)
|
154
|
-
if value is not None:
|
155
|
-
group = data.group
|
156
|
-
if group == "date":
|
157
|
-
value = parse(value)
|
158
|
-
elif group == "id":
|
159
|
-
value = ObjectId(value)
|
160
|
-
obj.__dict__[name].value = value
|
161
|
-
return obj
|
162
|
-
|
163
|
-
@classmethod
|
164
|
-
def from_json_only_value(cls, json_str: str) -> Any:
|
165
|
-
"""Convert JSON string to a object instance."""
|
166
|
-
json_dict = json.loads(json_str)
|
167
|
-
return cls.from_dict_only_value(json_dict)
|
168
|
-
|
169
|
-
def refrash_fields_only_value(self, only_value_dict: dict[str, Any]) -> None:
|
170
|
-
"""Partial or complete update a `value` of fields."""
|
171
|
-
for name, data in self.__dict__.items():
|
172
|
-
if callable(data):
|
173
|
-
continue
|
174
|
-
value = only_value_dict.get(name)
|
175
|
-
if value is not None:
|
176
|
-
group = data.group
|
177
|
-
if group == "date":
|
178
|
-
value = parse(value)
|
179
|
-
elif group == "id":
|
180
|
-
value = ObjectId(value)
|
181
|
-
self.__dict__[name].value = value
|
File without changes
|
File without changes
|