ramifice 0.3.14__py3-none-any.whl → 0.3.15__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/commons/one.py +1 -1
- ramifice/commons/tools.py +2 -2
- ramifice/decorators.py +2 -2
- ramifice/paladins/__init__.py +2 -2
- ramifice/paladins/check.py +6 -2
- ramifice/paladins/delete.py +3 -3
- ramifice/paladins/groups/bool_group.py +3 -1
- ramifice/paladins/groups/choice_group.py +3 -2
- ramifice/paladins/groups/date_group.py +5 -4
- ramifice/paladins/groups/file_group.py +6 -5
- ramifice/paladins/groups/id_group.py +4 -3
- ramifice/paladins/groups/img_group.py +6 -5
- ramifice/paladins/groups/num_group.py +12 -7
- ramifice/paladins/groups/pass_group.py +6 -5
- ramifice/paladins/groups/slug_group.py +24 -6
- ramifice/paladins/groups/text_group.py +15 -10
- ramifice/paladins/refrash.py +2 -1
- ramifice/paladins/save.py +21 -4
- ramifice/paladins/tools.py +44 -81
- ramifice/paladins/validation.py +47 -0
- ramifice/translations.py +9 -7
- ramifice/types.py +1 -1
- ramifice/{tools.py → utilities.py} +1 -1
- {ramifice-0.3.14.dist-info → ramifice-0.3.15.dist-info}/METADATA +154 -6
- {ramifice-0.3.14.dist-info → ramifice-0.3.15.dist-info}/RECORD +27 -26
- {ramifice-0.3.14.dist-info → ramifice-0.3.15.dist-info}/WHEEL +0 -0
- {ramifice-0.3.14.dist-info → ramifice-0.3.15.dist-info}/licenses/LICENSE +0 -0
ramifice/commons/one.py
CHANGED
@@ -35,7 +35,7 @@ class OneMixin:
|
|
35
35
|
*args: tuple,
|
36
36
|
**kwargs: dict[str, Any],
|
37
37
|
) -> dict[str, Any] | None:
|
38
|
-
"""Find a single document."""
|
38
|
+
"""Find a single document and converting to raw document."""
|
39
39
|
# Get collection for current model.
|
40
40
|
collection: AsyncCollection = store.MONGO_DATABASE[cls.META["collection_name"]] # type: ignore[index]
|
41
41
|
# Get document.
|
ramifice/commons/tools.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
"""
|
1
|
+
"""Tool of Commons - A set of auxiliary methods."""
|
2
2
|
|
3
3
|
from typing import Any
|
4
4
|
|
@@ -8,7 +8,7 @@ from .. import translations
|
|
8
8
|
|
9
9
|
|
10
10
|
class ToolMixin:
|
11
|
-
"""A set of
|
11
|
+
"""Tool of Commons - A set of auxiliary methods."""
|
12
12
|
|
13
13
|
@classmethod
|
14
14
|
def password_to_none(cls: Any, mongo_doc: dict[str, Any]) -> dict[str, Any]:
|
ramifice/decorators.py
CHANGED
@@ -11,7 +11,7 @@ from .fields import DateTimeField, IDField # type: ignore[attr-defined]
|
|
11
11
|
from .hooks import HooksMixin
|
12
12
|
from .indexing import IndexMixin
|
13
13
|
from .model import Model
|
14
|
-
from .paladins import CheckMixin, QPaladinsMixin,
|
14
|
+
from .paladins import CheckMixin, QPaladinsMixin, ValidationMixin # type: ignore[attr-defined]
|
15
15
|
from .store import REGEX
|
16
16
|
|
17
17
|
|
@@ -67,7 +67,7 @@ def model(
|
|
67
67
|
attrs,
|
68
68
|
)
|
69
69
|
else:
|
70
|
-
return type(cls.__name__, (Model,
|
70
|
+
return type(cls.__name__, (Model, ValidationMixin, CheckMixin, AddValidMixin), attrs)
|
71
71
|
|
72
72
|
return decorator
|
73
73
|
|
ramifice/paladins/__init__.py
CHANGED
@@ -5,16 +5,16 @@ from .delete import DeleteMixin
|
|
5
5
|
from .password import PasswordMixin
|
6
6
|
from .refrash import RefrashMixin
|
7
7
|
from .save import SaveMixin
|
8
|
-
from .
|
8
|
+
from .validation import ValidationMixin
|
9
9
|
|
10
10
|
|
11
11
|
class QPaladinsMixin(
|
12
|
-
ToolMixin,
|
13
12
|
CheckMixin,
|
14
13
|
SaveMixin,
|
15
14
|
PasswordMixin,
|
16
15
|
DeleteMixin,
|
17
16
|
RefrashMixin,
|
17
|
+
ValidationMixin,
|
18
18
|
):
|
19
19
|
"""Paladins - Model instance methods."""
|
20
20
|
|
ramifice/paladins/check.py
CHANGED
@@ -40,7 +40,10 @@ class CheckMixin(
|
|
40
40
|
async def check(
|
41
41
|
self, is_save: bool = False, collection: AsyncCollection | None = None
|
42
42
|
) -> dict[str, Any]:
|
43
|
-
"""Validation of Model data before saving to the database.
|
43
|
+
"""Validation of Model data before saving to the database.
|
44
|
+
|
45
|
+
It is also used to verify Models that do not migrate to the database.
|
46
|
+
"""
|
44
47
|
cls_model = self.__class__
|
45
48
|
if not cls_model.META["is_migrate_model"] and is_save: # type: ignore[attr-defined]
|
46
49
|
msg = (
|
@@ -74,6 +77,7 @@ class CheckMixin(
|
|
74
77
|
"result_map": result_map, # Data to save or update to the database.
|
75
78
|
"collection": collection,
|
76
79
|
"field_data": None,
|
80
|
+
"full_model_name": cls_model.META["full_model_name"], # type: ignore[attr-defined]
|
77
81
|
}
|
78
82
|
#
|
79
83
|
# Run checking fields.
|
@@ -107,7 +111,7 @@ class CheckMixin(
|
|
107
111
|
elif group == "id":
|
108
112
|
self.id_group(params)
|
109
113
|
elif group == "slug":
|
110
|
-
self.slug_group(params)
|
114
|
+
await self.slug_group(params)
|
111
115
|
elif group == "pass":
|
112
116
|
self.pass_group(params)
|
113
117
|
|
ramifice/paladins/delete.py
CHANGED
@@ -15,7 +15,7 @@ class DeleteMixin:
|
|
15
15
|
|
16
16
|
async def delete(
|
17
17
|
self,
|
18
|
-
|
18
|
+
remove_files: bool = True,
|
19
19
|
projection: Any | None = None,
|
20
20
|
sort: Any | None = None,
|
21
21
|
hint: Any | None = None,
|
@@ -67,12 +67,12 @@ class DeleteMixin:
|
|
67
67
|
+ "The document was not deleted, the document is absent in the database."
|
68
68
|
)
|
69
69
|
raise PanicError(msg)
|
70
|
-
# Delete orphaned files.
|
70
|
+
# Delete orphaned files and add None to field.value.
|
71
71
|
file_data: dict[str, Any] | None = None
|
72
72
|
for field_name, field_data in self.__dict__.items():
|
73
73
|
if callable(field_data):
|
74
74
|
continue
|
75
|
-
if
|
75
|
+
if remove_files and not field_data.ignored:
|
76
76
|
group = field_data.group
|
77
77
|
if group == "file":
|
78
78
|
file_data = mongo_doc[field_name]
|
@@ -6,6 +6,8 @@ Supported fields:
|
|
6
6
|
|
7
7
|
from typing import Any
|
8
8
|
|
9
|
+
from ..tools import panic_type_error
|
10
|
+
|
9
11
|
|
10
12
|
class BoolGroupMixin:
|
11
13
|
"""Group for checking boolean fields.
|
@@ -21,7 +23,7 @@ class BoolGroupMixin:
|
|
21
23
|
value = field.value
|
22
24
|
|
23
25
|
if not isinstance(value, (bool, type(None))):
|
24
|
-
|
26
|
+
panic_type_error(params["full_model_name"], "bool | None", params)
|
25
27
|
|
26
28
|
if not params["is_update"] and value is None:
|
27
29
|
value = field.default
|
@@ -10,6 +10,7 @@ Supported fields:
|
|
10
10
|
from typing import Any
|
11
11
|
|
12
12
|
from ... import translations
|
13
|
+
from ..tools import accumulate_error
|
13
14
|
|
14
15
|
|
15
16
|
class ChoiceGroupMixin:
|
@@ -31,14 +32,14 @@ class ChoiceGroupMixin:
|
|
31
32
|
if value is None:
|
32
33
|
if field.required:
|
33
34
|
err_msg = translations._("Required field !")
|
34
|
-
|
35
|
+
accumulate_error(params["full_model_name"], err_msg, params)
|
35
36
|
if params["is_save"]:
|
36
37
|
params["result_map"][field.name] = None
|
37
38
|
return
|
38
39
|
# Does the field value match the possible options in choices.
|
39
40
|
if not field.has_value():
|
40
41
|
err_msg = translations._("Your choice does not match the options offered !")
|
41
|
-
|
42
|
+
accumulate_error(params["full_model_name"], err_msg, params)
|
42
43
|
# Insert result.
|
43
44
|
if params["is_save"]:
|
44
45
|
params["result_map"][field.name] = value
|
@@ -10,6 +10,7 @@ from typing import Any
|
|
10
10
|
from babel.dates import format_date, format_datetime
|
11
11
|
|
12
12
|
from ... import translations
|
13
|
+
from ..tools import accumulate_error, panic_type_error
|
13
14
|
|
14
15
|
|
15
16
|
class DateGroupMixin:
|
@@ -26,12 +27,12 @@ class DateGroupMixin:
|
|
26
27
|
value = field.value or field.default or None
|
27
28
|
|
28
29
|
if not isinstance(value, (datetime, type(None))):
|
29
|
-
|
30
|
+
panic_type_error(params["full_model_name"], "datetime | None", params)
|
30
31
|
|
31
32
|
if value is None:
|
32
33
|
if field.required:
|
33
34
|
err_msg = translations._("Required field !")
|
34
|
-
|
35
|
+
accumulate_error(params["full_model_name"], err_msg, params)
|
35
36
|
if params["is_save"]:
|
36
37
|
params["result_map"][field.name] = None
|
37
38
|
return
|
@@ -69,7 +70,7 @@ class DateGroupMixin:
|
|
69
70
|
"The date %s must not be greater than max=%s !" % value_str,
|
70
71
|
max_date_str,
|
71
72
|
)
|
72
|
-
|
73
|
+
accumulate_error(params["full_model_name"], err_msg, params)
|
73
74
|
# Validation the `min_date` field attribute.
|
74
75
|
min_date = field.min_date
|
75
76
|
if min_date is not None and value < min_date:
|
@@ -102,7 +103,7 @@ class DateGroupMixin:
|
|
102
103
|
err_msg = translations._(
|
103
104
|
"The date %s must not be less than min=%s !" % value_str, min_date_str
|
104
105
|
)
|
105
|
-
|
106
|
+
accumulate_error(params["full_model_name"], err_msg, params)
|
106
107
|
# Insert result.
|
107
108
|
if params["is_save"]:
|
108
109
|
params["result_map"][field.name] = value
|
@@ -7,7 +7,8 @@ import os
|
|
7
7
|
from typing import Any
|
8
8
|
|
9
9
|
from ... import translations
|
10
|
-
from ...
|
10
|
+
from ...utilities import to_human_size
|
11
|
+
from ..tools import accumulate_error, panic_type_error
|
11
12
|
|
12
13
|
|
13
14
|
class FileGroupMixin:
|
@@ -22,7 +23,7 @@ class FileGroupMixin:
|
|
22
23
|
value = field.value or None
|
23
24
|
|
24
25
|
if not isinstance(value, (dict, type(None))):
|
25
|
-
|
26
|
+
panic_type_error(params["full_model_name"], "dict | None", params)
|
26
27
|
|
27
28
|
if not params["is_update"]:
|
28
29
|
if value is None:
|
@@ -36,7 +37,7 @@ class FileGroupMixin:
|
|
36
37
|
if value is None:
|
37
38
|
if field.required:
|
38
39
|
err_msg = translations._("Required field !")
|
39
|
-
|
40
|
+
accumulate_error(params["full_model_name"], err_msg, params)
|
40
41
|
if params["is_save"]:
|
41
42
|
params["result_map"][field.name] = None
|
42
43
|
return
|
@@ -57,14 +58,14 @@ class FileGroupMixin:
|
|
57
58
|
params["result_map"][field.name] = None
|
58
59
|
else:
|
59
60
|
err_msg = translations._("Required field !")
|
60
|
-
|
61
|
+
accumulate_error(params["full_model_name"], err_msg, params)
|
61
62
|
return
|
62
63
|
# Accumulate an error if the file size exceeds the maximum value.
|
63
64
|
if value["size"] > field.max_size:
|
64
65
|
err_msg = translations._(
|
65
66
|
"File size exceeds the maximum value %s !" % to_human_size(field.max_size)
|
66
67
|
)
|
67
|
-
|
68
|
+
accumulate_error(params["full_model_name"], err_msg, params)
|
68
69
|
return
|
69
70
|
# Return if there is no need to save.
|
70
71
|
if not params["is_save"]:
|
@@ -9,6 +9,7 @@ from typing import Any
|
|
9
9
|
from bson.objectid import ObjectId
|
10
10
|
|
11
11
|
from ... import translations
|
12
|
+
from ..tools import accumulate_error, panic_type_error
|
12
13
|
|
13
14
|
|
14
15
|
class IDGroupMixin:
|
@@ -25,19 +26,19 @@ class IDGroupMixin:
|
|
25
26
|
value = field.value or None
|
26
27
|
|
27
28
|
if not isinstance(value, (ObjectId, type(None))):
|
28
|
-
|
29
|
+
panic_type_error(params["full_model_name"], "ObjectId | None", params)
|
29
30
|
|
30
31
|
if value is None:
|
31
32
|
if field.required:
|
32
33
|
err_msg = translations._("Required field !")
|
33
|
-
|
34
|
+
accumulate_error(params["full_model_name"], err_msg, params)
|
34
35
|
if params["is_save"]:
|
35
36
|
params["result_map"][field.name] = None
|
36
37
|
return
|
37
38
|
# Validation of the MongoDB identifier in a string form.
|
38
39
|
if not ObjectId.is_valid(value):
|
39
40
|
err_msg = translations._("Invalid document ID !")
|
40
|
-
|
41
|
+
accumulate_error(params["full_model_name"], err_msg, params)
|
41
42
|
# Insert result.
|
42
43
|
if params["is_save"]:
|
43
44
|
params["result_map"][field.name] = value
|
@@ -9,7 +9,8 @@ from typing import Any
|
|
9
9
|
from PIL import Image
|
10
10
|
|
11
11
|
from ... import translations
|
12
|
-
from ...
|
12
|
+
from ...utilities import to_human_size
|
13
|
+
from ..tools import accumulate_error, panic_type_error
|
13
14
|
|
14
15
|
|
15
16
|
class ImgGroupMixin:
|
@@ -24,7 +25,7 @@ class ImgGroupMixin:
|
|
24
25
|
value = field.value or None
|
25
26
|
|
26
27
|
if not isinstance(value, (dict, type(None))):
|
27
|
-
|
28
|
+
panic_type_error(params["full_model_name"], "dict | None", params)
|
28
29
|
|
29
30
|
if not params["is_update"]:
|
30
31
|
if value is None:
|
@@ -38,7 +39,7 @@ class ImgGroupMixin:
|
|
38
39
|
if value is None:
|
39
40
|
if field.required:
|
40
41
|
err_msg = translations._("Required field !")
|
41
|
-
|
42
|
+
accumulate_error(params["full_model_name"], err_msg, params)
|
42
43
|
if params["is_save"]:
|
43
44
|
params["result_map"][field.name] = None
|
44
45
|
return
|
@@ -59,14 +60,14 @@ class ImgGroupMixin:
|
|
59
60
|
params["result_map"][field.name] = None
|
60
61
|
else:
|
61
62
|
err_msg = translations._("Required field !")
|
62
|
-
|
63
|
+
accumulate_error(params["full_model_name"], err_msg, params)
|
63
64
|
return
|
64
65
|
# Accumulate an error if the file size exceeds the maximum value.
|
65
66
|
if value["size"] > field.max_size:
|
66
67
|
err_msg = translations._(
|
67
68
|
"Image size exceeds the maximum value %s !" % to_human_size(field.max_size)
|
68
69
|
)
|
69
|
-
|
70
|
+
accumulate_error(params["full_model_name"], err_msg, params)
|
70
71
|
return
|
71
72
|
# Return if there is no need to save.
|
72
73
|
if not params["is_save"]:
|
@@ -7,6 +7,7 @@ Supported fields:
|
|
7
7
|
from typing import Any
|
8
8
|
|
9
9
|
from ... import translations
|
10
|
+
from ..tools import accumulate_error, check_uniqueness, panic_type_error
|
10
11
|
|
11
12
|
|
12
13
|
class NumGroupMixin:
|
@@ -26,15 +27,15 @@ class NumGroupMixin:
|
|
26
27
|
|
27
28
|
if "Float" in field.field_type:
|
28
29
|
if not isinstance(value, (float, type(None))):
|
29
|
-
|
30
|
+
panic_type_error(params["full_model_name"], "float | None", params)
|
30
31
|
else:
|
31
32
|
if not isinstance(value, (int, type(None))):
|
32
|
-
|
33
|
+
panic_type_error(params["full_model_name"], "int | None", params)
|
33
34
|
|
34
35
|
if value is None:
|
35
36
|
if field.required:
|
36
37
|
err_msg = translations._("Required field !")
|
37
|
-
|
38
|
+
accumulate_error(params["full_model_name"], err_msg, params)
|
38
39
|
if params["is_save"]:
|
39
40
|
params["result_map"][field.name] = None
|
40
41
|
return
|
@@ -44,18 +45,22 @@ class NumGroupMixin:
|
|
44
45
|
err_msg = translations._(
|
45
46
|
"The value %d must not be greater than max=%d !" % value, max_number
|
46
47
|
)
|
47
|
-
|
48
|
+
accumulate_error(params["full_model_name"], err_msg, params)
|
48
49
|
# Validation the `min_number` field attribute.
|
49
50
|
min_number = field.min_number
|
50
51
|
if min_number is not None and value < min_number:
|
51
52
|
err_msg = translations._(
|
52
53
|
"The value %d must not be less than min=%d !" % value, min_number
|
53
54
|
)
|
54
|
-
|
55
|
+
accumulate_error(params["full_model_name"], err_msg, params)
|
55
56
|
# Validation the `unique` field attribute.
|
56
|
-
if field.unique and not await
|
57
|
+
if field.unique and not await check_uniqueness(
|
58
|
+
self.__class__.META["is_migrate_model"], # type: ignore[attr-defined]
|
59
|
+
value,
|
60
|
+
params,
|
61
|
+
):
|
57
62
|
err_msg = translations._("Is not unique !")
|
58
|
-
|
63
|
+
accumulate_error(params["full_model_name"], err_msg, params)
|
59
64
|
# Insert result.
|
60
65
|
if params["is_save"]:
|
61
66
|
params["result_map"][field.name] = value
|
@@ -8,6 +8,7 @@ from typing import Any
|
|
8
8
|
from argon2 import PasswordHasher
|
9
9
|
|
10
10
|
from ... import translations
|
11
|
+
from ..tools import accumulate_error, panic_type_error
|
11
12
|
|
12
13
|
|
13
14
|
class PassGroupMixin:
|
@@ -27,24 +28,24 @@ class PassGroupMixin:
|
|
27
28
|
value = field.value or None
|
28
29
|
|
29
30
|
if not isinstance(value, (str, type(None))):
|
30
|
-
|
31
|
+
panic_type_error(params["full_model_name"], "str | None", params)
|
31
32
|
|
32
33
|
if value is None:
|
33
34
|
if field.required:
|
34
35
|
err_msg = translations._("Required field !")
|
35
|
-
|
36
|
+
accumulate_error(params["full_model_name"], err_msg, params)
|
36
37
|
if params["is_save"]:
|
37
38
|
params["result_map"][field.name] = None
|
38
39
|
return
|
39
40
|
# Validation Passwor.
|
40
41
|
if not field.is_valid(value):
|
41
42
|
err_msg = translations._("Invalid Password !")
|
42
|
-
|
43
|
+
accumulate_error(params["full_model_name"], err_msg, params)
|
43
44
|
chars = "a-z A-Z 0-9 - . _ ! \" ` ' # % & , : ; < > = @ { } ~ $ ( ) * + / \\ ? [ ] ^ |"
|
44
45
|
err_msg = translations._("Valid characters: %s" % chars)
|
45
|
-
|
46
|
+
accumulate_error(params["full_model_name"], err_msg, params)
|
46
47
|
err_msg = translations._("Number of characters: from 8 to 256")
|
47
|
-
|
48
|
+
accumulate_error(params["full_model_name"], err_msg, params)
|
48
49
|
# Insert result.
|
49
50
|
if params["is_save"]:
|
50
51
|
ph = PasswordHasher()
|
@@ -1,6 +1,7 @@
|
|
1
1
|
"""Group for checking slug fields.
|
2
2
|
|
3
|
-
Supported fields:
|
3
|
+
Supported fields:
|
4
|
+
SlugField
|
4
5
|
"""
|
5
6
|
|
6
7
|
from typing import Any
|
@@ -8,15 +9,17 @@ from typing import Any
|
|
8
9
|
from slugify import slugify
|
9
10
|
|
10
11
|
from ...errors import PanicError
|
12
|
+
from ..tools import check_uniqueness
|
11
13
|
|
12
14
|
|
13
15
|
class SlugGroupMixin:
|
14
16
|
"""Group for checking slug fields.
|
15
17
|
|
16
|
-
Supported fields:
|
18
|
+
Supported fields:
|
19
|
+
SlugField
|
17
20
|
"""
|
18
21
|
|
19
|
-
def slug_group(self, params: dict[str, Any]) -> None:
|
22
|
+
async def slug_group(self, params: dict[str, Any]) -> None:
|
20
23
|
"""Checking slug fields."""
|
21
24
|
if not params["is_save"]:
|
22
25
|
return
|
@@ -36,7 +39,7 @@ class SlugGroupMixin:
|
|
36
39
|
raw_str_list.append(value if field_name != "_id" else str(value))
|
37
40
|
else:
|
38
41
|
err_msg = (
|
39
|
-
f"Model: `{
|
42
|
+
f"Model: `{params['full_model_name']}` > "
|
40
43
|
+ f"Field: `{field.name}` => "
|
41
44
|
+ f"{field_name} - "
|
42
45
|
+ "This field is specified in slug_sources. "
|
@@ -45,5 +48,20 @@ class SlugGroupMixin:
|
|
45
48
|
raise PanicError(err_msg)
|
46
49
|
# Insert result.
|
47
50
|
if params["is_save"]:
|
48
|
-
|
49
|
-
|
51
|
+
# Convert to slug.
|
52
|
+
value = slugify("-".join(raw_str_list))
|
53
|
+
# Validation of uniqueness of the value.
|
54
|
+
if not await check_uniqueness(
|
55
|
+
self.__class__.META["is_migrate_model"], # type: ignore[attr-defined]
|
56
|
+
value,
|
57
|
+
params,
|
58
|
+
):
|
59
|
+
err_msg = (
|
60
|
+
f"Model: `{params['full_model_name']}` > "
|
61
|
+
+ f"Field: `{field.name}` > "
|
62
|
+
+ f"Parameter: `slug_sources` => "
|
63
|
+
+ "At least one field should be unique!"
|
64
|
+
)
|
65
|
+
raise PanicError(err_msg)
|
66
|
+
# Add value to map.
|
67
|
+
params["result_map"][field.name] = value
|
@@ -10,6 +10,7 @@ from typing import Any
|
|
10
10
|
from email_validator import EmailNotValidError, validate_email
|
11
11
|
|
12
12
|
from ... import translations
|
13
|
+
from ..tools import accumulate_error, check_uniqueness, panic_type_error
|
13
14
|
|
14
15
|
|
15
16
|
class TextGroupMixin:
|
@@ -27,12 +28,12 @@ class TextGroupMixin:
|
|
27
28
|
value = field.value or field.default or None
|
28
29
|
|
29
30
|
if not isinstance(value, (str, type(None))):
|
30
|
-
|
31
|
+
panic_type_error(params["full_model_name"], "str | None", params)
|
31
32
|
|
32
33
|
if value is None:
|
33
34
|
if field.required:
|
34
35
|
err_msg = translations._("Required field !")
|
35
|
-
|
36
|
+
accumulate_error(params["full_model_name"], err_msg, params)
|
36
37
|
if params["is_save"]:
|
37
38
|
params["result_map"][field.name] = None
|
38
39
|
return
|
@@ -40,11 +41,15 @@ class TextGroupMixin:
|
|
40
41
|
maxlength: int | None = field.__dict__.get("maxlength")
|
41
42
|
if maxlength is not None and len(value) > maxlength:
|
42
43
|
err_msg = translations._("The length of the string exceeds maxlength=%d !" % maxlength)
|
43
|
-
|
44
|
+
accumulate_error(params["full_model_name"], err_msg, params)
|
44
45
|
# Validation the `unique` field attribute.
|
45
|
-
if field.unique and not await
|
46
|
+
if field.unique and not await check_uniqueness(
|
47
|
+
self.__class__.META["is_migrate_model"], # type: ignore[attr-defined]
|
48
|
+
value,
|
49
|
+
params,
|
50
|
+
):
|
46
51
|
err_msg = translations._("Is not unique !")
|
47
|
-
|
52
|
+
accumulate_error(params["full_model_name"], err_msg, params)
|
48
53
|
# Validation Email, Url, IP, Color, Phone.
|
49
54
|
field_type = field.field_type
|
50
55
|
if "Email" in field_type:
|
@@ -57,19 +62,19 @@ class TextGroupMixin:
|
|
57
62
|
params["field_data"].value = value
|
58
63
|
except EmailNotValidError:
|
59
64
|
err_msg = translations._("Invalid Email address !")
|
60
|
-
|
65
|
+
accumulate_error(params["full_model_name"], err_msg, params)
|
61
66
|
elif "URL" in field_type and not field.is_valid(value):
|
62
67
|
err_msg = translations._("Invalid URL address !")
|
63
|
-
|
68
|
+
accumulate_error(params["full_model_name"], err_msg, params)
|
64
69
|
elif "IP" in field_type and not field.is_valid(value):
|
65
70
|
err_msg = translations._("Invalid IP address !")
|
66
|
-
|
71
|
+
accumulate_error(params["full_model_name"], err_msg, params)
|
67
72
|
elif "Color" in field_type and not field.is_valid(value):
|
68
73
|
err_msg = translations._("Invalid Color code !")
|
69
|
-
|
74
|
+
accumulate_error(params["full_model_name"], err_msg, params)
|
70
75
|
elif "Phone" in field_type and not field.is_valid(value):
|
71
76
|
err_msg = translations._("Invalid Phone number !")
|
72
|
-
|
77
|
+
accumulate_error(params["full_model_name"], err_msg, params)
|
73
78
|
# Insert result.
|
74
79
|
if params["is_save"]:
|
75
80
|
params["result_map"][field.name] = value
|
ramifice/paladins/refrash.py
CHANGED
@@ -6,6 +6,7 @@ from pymongo.asynchronous.collection import AsyncCollection
|
|
6
6
|
|
7
7
|
from .. import store
|
8
8
|
from ..errors import PanicError
|
9
|
+
from .tools import refresh_from_mongo_doc
|
9
10
|
|
10
11
|
|
11
12
|
class RefrashMixin:
|
@@ -25,4 +26,4 @@ class RefrashMixin:
|
|
25
26
|
)
|
26
27
|
raise PanicError(msg)
|
27
28
|
self.inject() # type: ignore[attr-defined]
|
28
|
-
self
|
29
|
+
refresh_from_mongo_doc(self, mongo_doc)
|
ramifice/paladins/save.py
CHANGED
@@ -7,6 +7,7 @@ from pymongo.asynchronous.collection import AsyncCollection
|
|
7
7
|
|
8
8
|
from .. import store
|
9
9
|
from ..errors import PanicError
|
10
|
+
from .tools import ignored_fields_to_none, refresh_from_mongo_doc
|
10
11
|
|
11
12
|
|
12
13
|
class SaveMixin:
|
@@ -33,7 +34,7 @@ class SaveMixin:
|
|
33
34
|
result_check["is_valid"] = False
|
34
35
|
# Leave the method if the check fails.
|
35
36
|
if not result_check["is_valid"]:
|
36
|
-
|
37
|
+
ignored_fields_to_none(self)
|
37
38
|
return False
|
38
39
|
# Get data for document.
|
39
40
|
checked_data: dict[str, Any] = result_check["data"]
|
@@ -48,8 +49,17 @@ class SaveMixin:
|
|
48
49
|
# Run hook.
|
49
50
|
await self.post_update() # type: ignore[attr-defined]
|
50
51
|
# Refresh Model.
|
51
|
-
mongo_doc = await collection.find_one(
|
52
|
-
|
52
|
+
mongo_doc: dict[str, Any] | None = await collection.find_one(
|
53
|
+
{"_id": checked_data["_id"]}
|
54
|
+
)
|
55
|
+
if mongo_doc is None:
|
56
|
+
msg = (
|
57
|
+
f"Model: `{self.full_model_name()}` > " # type: ignore[attr-defined]
|
58
|
+
+ "Method: `save` => "
|
59
|
+
+ "Geted value is None - it is impossible to refresh the current Model."
|
60
|
+
)
|
61
|
+
raise PanicError(msg)
|
62
|
+
refresh_from_mongo_doc(self, mongo_doc)
|
53
63
|
else:
|
54
64
|
# Add date and time.
|
55
65
|
today = datetime.now()
|
@@ -63,8 +73,15 @@ class SaveMixin:
|
|
63
73
|
await self.post_create() # type: ignore[attr-defined]
|
64
74
|
# Refresh Model.
|
65
75
|
mongo_doc = await collection.find_one({"_id": checked_data["_id"]})
|
76
|
+
if mongo_doc is None:
|
77
|
+
msg = (
|
78
|
+
f"Model: `{self.full_model_name()}` > " # type: ignore[attr-defined]
|
79
|
+
+ "Method: `save` => "
|
80
|
+
+ "Geted value is None - it is impossible to refresh the current Model."
|
81
|
+
)
|
82
|
+
raise PanicError(msg)
|
66
83
|
if mongo_doc is not None:
|
67
|
-
self
|
84
|
+
refresh_from_mongo_doc(self, mongo_doc)
|
68
85
|
else:
|
69
86
|
msg = (
|
70
87
|
f"Model: `{self.full_model_name()}` > " # type: ignore[attr-defined]
|
ramifice/paladins/tools.py
CHANGED
@@ -1,6 +1,5 @@
|
|
1
|
-
"""
|
1
|
+
"""Tool of Paladins - A set of auxiliary methods."""
|
2
2
|
|
3
|
-
from datetime import datetime
|
4
3
|
from typing import Any
|
5
4
|
|
6
5
|
from termcolor import colored
|
@@ -8,93 +7,57 @@ from termcolor import colored
|
|
8
7
|
from ..errors import PanicError
|
9
8
|
|
10
9
|
|
11
|
-
|
12
|
-
"""
|
10
|
+
def ignored_fields_to_none(inst_model: Any) -> None:
|
11
|
+
"""Reset the values of ignored fields to None."""
|
12
|
+
for _, field_data in inst_model.__dict__.items():
|
13
|
+
if not callable(field_data) and field_data.ignored and field_data.name != "_id":
|
14
|
+
field_data.value = None
|
13
15
|
|
14
|
-
async def is_valid(self) -> bool:
|
15
|
-
"""Check data validity.
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
17
|
+
def refresh_from_mongo_doc(inst_model: Any, mongo_doc: dict[str, Any]) -> None:
|
18
|
+
"""Update object instance from Mongo document."""
|
19
|
+
for name, data in mongo_doc.items():
|
20
|
+
field = inst_model.__dict__[name]
|
21
|
+
field.value = data if field.group != "pass" else None
|
21
22
|
|
22
|
-
def print_err(self) -> None:
|
23
|
-
"""Printing errors to the console.
|
24
23
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
if not is_err:
|
34
|
-
print(colored("\nERRORS:", "red", attrs=["bold"]))
|
35
|
-
print(colored("Model: ", "blue", attrs=["bold"]), end="")
|
36
|
-
print(colored(f"`{self.full_model_name()}`", "blue")) # type: ignore[attr-defined]
|
37
|
-
is_err = True
|
38
|
-
# field name
|
39
|
-
print(colored("Field: ", "green", attrs=["bold"]), end="")
|
40
|
-
print(colored(f"`{field_name}`:", "green"))
|
41
|
-
# error messages
|
42
|
-
print(colored("\n".join(field_data.errors), "red"))
|
43
|
-
if len(self._id.alerts) > 0: # type: ignore[attr-defined]
|
44
|
-
# title
|
45
|
-
print(colored("AlERTS:", "yellow", attrs=["bold"]))
|
46
|
-
# messages
|
47
|
-
print(colored("\n".join(self._id.alerts), "yellow"), end="\n\n") # type: ignore[attr-defined]
|
48
|
-
else:
|
49
|
-
print(end="\n\n")
|
24
|
+
def panic_type_error(model_name: str, value_type: str, params: dict[str, Any]) -> None:
|
25
|
+
"""Unacceptable type of value."""
|
26
|
+
msg = (
|
27
|
+
f"Model: `{model_name}` > "
|
28
|
+
+ f"Field: `{params['field_data'].name}` > "
|
29
|
+
+ f"Parameter: `value` => Must be `{value_type}` type!"
|
30
|
+
)
|
31
|
+
raise PanicError(msg)
|
50
32
|
|
51
|
-
def accumulate_error(self, err_msg: str, params: dict[str, Any]) -> None:
|
52
|
-
"""For accumulating errors to ModelName.field_name.errors ."""
|
53
|
-
if not params["field_data"].hide:
|
54
|
-
params["field_data"].errors.append(err_msg)
|
55
|
-
if not params["is_error_symptom"]:
|
56
|
-
params["is_error_symptom"] = True
|
57
|
-
else:
|
58
|
-
msg = (
|
59
|
-
f">>hidden field<< - Model: `{self.full_model_name()}` > " # type: ignore[attr-defined]
|
60
|
-
+ f"Field: `{params['field_data'].name}`"
|
61
|
-
+ f" => {err_msg}"
|
62
|
-
)
|
63
|
-
raise PanicError(msg)
|
64
33
|
|
65
|
-
|
66
|
-
|
34
|
+
def accumulate_error(model_name: str, err_msg: str, params: dict[str, Any]) -> None:
|
35
|
+
"""For accumulating errors to ModelName.field_name.errors ."""
|
36
|
+
if not params["field_data"].hide:
|
37
|
+
params["field_data"].errors.append(err_msg)
|
38
|
+
if not params["is_error_symptom"]:
|
39
|
+
params["is_error_symptom"] = True
|
40
|
+
else:
|
67
41
|
msg = (
|
68
|
-
f"Model: `{
|
69
|
-
+ f"Field: `{params['field_data'].name}`
|
70
|
-
+ f"
|
42
|
+
f">>hidden field<< -> Model: `{model_name}` > "
|
43
|
+
+ f"Field: `{params['field_data'].name}`"
|
44
|
+
+ f" => {err_msg}"
|
71
45
|
)
|
72
46
|
raise PanicError(msg)
|
73
47
|
|
74
|
-
async def check_uniqueness(
|
75
|
-
self,
|
76
|
-
value: str | int | float | datetime,
|
77
|
-
params: dict[str, Any],
|
78
|
-
) -> bool:
|
79
|
-
"""Check the uniqueness of the value in the collection."""
|
80
|
-
if not self.__class__.META["is_migrate_model"]: # type: ignore[attr-defined]
|
81
|
-
return True
|
82
|
-
q_filter = {
|
83
|
-
"$and": [
|
84
|
-
{"_id": {"$ne": params["doc_id"]}},
|
85
|
-
{params["field_data"].name: value},
|
86
|
-
],
|
87
|
-
}
|
88
|
-
return await params["collection"].find_one(q_filter) is None
|
89
48
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
49
|
+
async def check_uniqueness(
|
50
|
+
is_migrate_model: bool,
|
51
|
+
value: str | int | float,
|
52
|
+
params: dict[str, Any],
|
53
|
+
) -> bool:
|
54
|
+
"""Check the uniqueness of the value in the collection."""
|
55
|
+
if not is_migrate_model:
|
56
|
+
return True
|
57
|
+
q_filter = {
|
58
|
+
"$and": [
|
59
|
+
{"_id": {"$ne": params["doc_id"]}},
|
60
|
+
{params["field_data"].name: value},
|
61
|
+
],
|
62
|
+
}
|
63
|
+
return await params["collection"].find_one(q_filter) is None
|
@@ -0,0 +1,47 @@
|
|
1
|
+
"""Validation of Model and printing errors to console."""
|
2
|
+
|
3
|
+
from typing import Any
|
4
|
+
|
5
|
+
from termcolor import colored
|
6
|
+
|
7
|
+
|
8
|
+
class ValidationMixin:
|
9
|
+
"""Validation of Model and printing errors to console."""
|
10
|
+
|
11
|
+
async def is_valid(self) -> bool:
|
12
|
+
"""Check data validity.
|
13
|
+
|
14
|
+
The main use is to check data from web forms.
|
15
|
+
It is also used to verify Models that do not migrate to the database.
|
16
|
+
"""
|
17
|
+
result_check: dict[str, Any] = await self.check() # type: ignore[attr-defined]
|
18
|
+
return result_check["is_valid"]
|
19
|
+
|
20
|
+
def print_err(self) -> None:
|
21
|
+
"""Printing errors to console.
|
22
|
+
|
23
|
+
Convenient to use during development.
|
24
|
+
"""
|
25
|
+
is_err: bool = False
|
26
|
+
for field_name, field_data in self.__dict__.items():
|
27
|
+
if callable(field_data):
|
28
|
+
continue
|
29
|
+
if len(field_data.errors) > 0:
|
30
|
+
# title
|
31
|
+
if not is_err:
|
32
|
+
print(colored("\nERRORS:", "red", attrs=["bold"]))
|
33
|
+
print(colored("Model: ", "blue", attrs=["bold"]), end="")
|
34
|
+
print(colored(f"`{self.full_model_name()}`", "blue")) # type: ignore[attr-defined]
|
35
|
+
is_err = True
|
36
|
+
# field name
|
37
|
+
print(colored("Field: ", "green", attrs=["bold"]), end="")
|
38
|
+
print(colored(f"`{field_name}`:", "green"))
|
39
|
+
# error messages
|
40
|
+
print(colored("\n".join(field_data.errors), "red"))
|
41
|
+
if len(self._id.alerts) > 0: # type: ignore[attr-defined]
|
42
|
+
# title
|
43
|
+
print(colored("AlERTS:", "yellow", attrs=["bold"]))
|
44
|
+
# messages
|
45
|
+
print(colored("\n".join(self._id.alerts), "yellow"), end="\n\n") # type: ignore[attr-defined]
|
46
|
+
else:
|
47
|
+
print(end="\n\n")
|
ramifice/translations.py
CHANGED
@@ -25,6 +25,7 @@ zh | zh_cn
|
|
25
25
|
|
26
26
|
import copy
|
27
27
|
import gettext as _gettext
|
28
|
+
from gettext import NullTranslations
|
28
29
|
from typing import Any
|
29
30
|
|
30
31
|
# Language code by default.
|
@@ -35,7 +36,7 @@ CURRENT_LOCALE: str = copy.deepcopy(DEFAULT_LOCALE)
|
|
35
36
|
LANGUAGES: list[str] = ["en", "ru"]
|
36
37
|
|
37
38
|
# Add translations for Ramifice.
|
38
|
-
ramifice_translations = {
|
39
|
+
ramifice_translations: dict[str, NullTranslations] = {
|
39
40
|
lang: _gettext.translation(
|
40
41
|
domain="messages",
|
41
42
|
localedir="config/translations/ramifice",
|
@@ -47,7 +48,7 @@ ramifice_translations = {
|
|
47
48
|
}
|
48
49
|
|
49
50
|
# Add translations for custom project.
|
50
|
-
custom_translations = {
|
51
|
+
custom_translations: dict[str, NullTranslations] = {
|
51
52
|
lang: _gettext.translation(
|
52
53
|
domain="messages",
|
53
54
|
localedir="config/translations/custom",
|
@@ -104,11 +105,11 @@ def get_custom_translator(lang_code: str) -> Any:
|
|
104
105
|
|
105
106
|
|
106
107
|
# The object of the current translation, for Ramifice.
|
107
|
-
_ = get_ramifice_translator(DEFAULT_LOCALE).gettext
|
108
|
+
_: Any = get_ramifice_translator(DEFAULT_LOCALE).gettext
|
108
109
|
|
109
110
|
# The object of the current translation, for custom project.
|
110
|
-
gettext = get_custom_translator(DEFAULT_LOCALE).gettext
|
111
|
-
ngettext = get_custom_translator(DEFAULT_LOCALE).ngettext
|
111
|
+
gettext: Any = get_custom_translator(DEFAULT_LOCALE).gettext
|
112
|
+
ngettext: Any = get_custom_translator(DEFAULT_LOCALE).ngettext
|
112
113
|
|
113
114
|
|
114
115
|
def change_locale(lang_code: str) -> None:
|
@@ -128,5 +129,6 @@ def change_locale(lang_code: str) -> None:
|
|
128
129
|
if lang_code != CURRENT_LOCALE:
|
129
130
|
CURRENT_LOCALE = lang_code if lang_code in LANGUAGES else DEFAULT_LOCALE
|
130
131
|
_ = get_ramifice_translator(CURRENT_LOCALE).gettext
|
131
|
-
|
132
|
-
|
132
|
+
translator: NullTranslations = get_custom_translator(CURRENT_LOCALE)
|
133
|
+
gettext = translator.gettext
|
134
|
+
ngettext = translator.ngettext
|
ramifice/types.py
CHANGED
@@ -15,7 +15,7 @@ class Unit(JsonMixin):
|
|
15
15
|
"""Unit of information for `choices` parameter in dynamic field types.
|
16
16
|
|
17
17
|
Attributes:
|
18
|
-
field -- The name of the field.
|
18
|
+
field -- The name of the dynamic field.
|
19
19
|
title -- The name of the choice item.
|
20
20
|
value -- The value of the choice item.
|
21
21
|
is_delete -- True - if you need to remove the item of choice.
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: ramifice
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.15
|
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/
|
@@ -210,8 +210,7 @@ async def main():
|
|
210
210
|
user.сonfirm_password.value = "12345678"
|
211
211
|
|
212
212
|
if not await user.save():
|
213
|
-
# Convenient to use during development.
|
214
|
-
user.print_err()
|
213
|
+
user.print_err() # Convenient to use during development.
|
215
214
|
|
216
215
|
doc_count = await User.estimated_document_count()
|
217
216
|
print(f"Document count: {doc_count}") # => 1
|
@@ -220,9 +219,9 @@ async def main():
|
|
220
219
|
user_details = await User.find_one_to_raw_doc({"_id": user._id.value})
|
221
220
|
pprint.pprint(user_details)
|
222
221
|
|
223
|
-
|
224
|
-
|
225
|
-
|
222
|
+
await user.delete(remove_files=False)
|
223
|
+
doc_count = await User.estimated_document_count()
|
224
|
+
print(f"Document count: {doc_count}") # => 0
|
226
225
|
|
227
226
|
await client.close()
|
228
227
|
|
@@ -351,6 +350,155 @@ class User:
|
|
351
350
|
)
|
352
351
|
```
|
353
352
|
|
353
|
+
## Class methods
|
354
|
+
|
355
|
+
_List of frequently used methods:_
|
356
|
+
|
357
|
+
```python
|
358
|
+
# Gets an estimate of the count of documents in a collection using collection metadata.
|
359
|
+
count: int = await User.estimated_document_count()
|
360
|
+
|
361
|
+
# Gets an estimate of the count of documents in a collection using collection metadata.
|
362
|
+
q_filter = {"first_name": "John"}
|
363
|
+
count: int = await User.count_documents(q_filter)
|
364
|
+
|
365
|
+
# Runs an aggregation framework pipeline.
|
366
|
+
from bson.bson import BSON
|
367
|
+
pipeline = [
|
368
|
+
{"$unwind": "$tags"},
|
369
|
+
{"$group": {"_id": "$tags", "count": {"$sum": 1}}},
|
370
|
+
{"$sort": BSON([("count", -1), ("_id", -1)])},
|
371
|
+
]
|
372
|
+
docs = await User.aggregate(pipeline)
|
373
|
+
|
374
|
+
# Finds the distinct values for a specified field across a single collection.
|
375
|
+
q_filter = "key_name"
|
376
|
+
values = await User.distinct(q_filter)
|
377
|
+
|
378
|
+
# Get collection name.
|
379
|
+
name = await User.collection_name()
|
380
|
+
|
381
|
+
# The full name is of the form database_name.collection_name.
|
382
|
+
name = await User.collection_full_name()
|
383
|
+
|
384
|
+
# Get AsyncBatabase for the current Model.
|
385
|
+
database = await User.database()
|
386
|
+
|
387
|
+
# Get AsyncCollection for the current Model.
|
388
|
+
collection = await User.collection()
|
389
|
+
|
390
|
+
# Find a single document.
|
391
|
+
q_filter = {"email": "John_Smith@gmail.com"}
|
392
|
+
mongo_doc = await User.find_one(q_filter)
|
393
|
+
|
394
|
+
# Find a single document and converting to raw document.
|
395
|
+
q_filter = {"email": "John_Smith@gmail.com"}
|
396
|
+
raw_doc = await User.find_one_to_raw_doc(q_filter)
|
397
|
+
|
398
|
+
# Find a single document and convert it to a Model instance.
|
399
|
+
q_filter = {"email": "John_Smith@gmail.com"}
|
400
|
+
user = await User.find_one_to_instance(q_filter)
|
401
|
+
|
402
|
+
# Find a single document and convert it to a JSON string.
|
403
|
+
q_filter = {"email": "John_Smith@gmail.com"}
|
404
|
+
json = await User.find_one_to_json(q_filter)
|
405
|
+
|
406
|
+
# Find a single document and delete it.
|
407
|
+
q_filter = {"email": "John_Smith@gmail.com"}
|
408
|
+
delete_result = await User.delete_one(q_filter)
|
409
|
+
|
410
|
+
# Find a single document and delete it, return original.
|
411
|
+
q_filter = {"email": "John_Smith@gmail.com"}
|
412
|
+
mongo_doc = await User.find_one_and_delete(q_filter)
|
413
|
+
|
414
|
+
# Find documents.
|
415
|
+
q_filter = {"first_name": "John"}
|
416
|
+
mongo_docs = await User.find_many(q_filter)
|
417
|
+
|
418
|
+
# Find documents and convert to a raw documents.
|
419
|
+
q_filter = {"first_name": "John"}
|
420
|
+
raw_docs = await User.find_many_to_raw_docs(q_filter)
|
421
|
+
|
422
|
+
# Find documents and convert to a json string.
|
423
|
+
q_filter = {"email": "John_Smith@gmail.com"}
|
424
|
+
json = await User.find_many_to_json(q_filter)
|
425
|
+
|
426
|
+
# Find documents matching with Model.
|
427
|
+
q_filter = {"email": "John_Smith@gmail.com"}
|
428
|
+
delete_result = await User.delete_many(q_filter)
|
429
|
+
|
430
|
+
# Creates an index on this collection.
|
431
|
+
from pymongo import ASCENDING
|
432
|
+
keys = [("email", ASCENDING)]
|
433
|
+
result: str = await User.create_index(keys, name="idx_email")
|
434
|
+
|
435
|
+
# Drops the specified index on this collection.
|
436
|
+
User.drop_index("idx_email")
|
437
|
+
|
438
|
+
# Create one or more indexes on this collection.
|
439
|
+
from pymongo import ASCENDING, DESCENDING
|
440
|
+
index_1 = IndexModel([("username", DESCENDING), ("email", ASCENDING)], name="idx_username_email")
|
441
|
+
index_2 = IndexModel([("first_name", DESCENDING)], name="idx_first_name")
|
442
|
+
result: list[str] = await User.create_indexes([index_1, index_2])
|
443
|
+
|
444
|
+
# Drops all indexes on this collection.
|
445
|
+
User.drop_index()
|
446
|
+
|
447
|
+
# Get information on this collection’s indexes.
|
448
|
+
result = await User.index_information()
|
449
|
+
|
450
|
+
# Get a cursor over the index documents for this collection.
|
451
|
+
async for index in await User.list_indexes():
|
452
|
+
print(index)
|
453
|
+
|
454
|
+
# Units Management.
|
455
|
+
# Management for `choices` parameter in dynamic field types.
|
456
|
+
# Units are stored in a separate collection.
|
457
|
+
from ramifice.types import Unit
|
458
|
+
unit = Unit(
|
459
|
+
field="field_name", # The name of the dynamic field.
|
460
|
+
title="Title", # The name of the choice item.
|
461
|
+
value="Some text ...", # The value of the choice item.
|
462
|
+
# Hint: float | int | str
|
463
|
+
is_delete=False, # True - if you need to remove the item of choice.
|
464
|
+
# by default = False (add item to choice)
|
465
|
+
)
|
466
|
+
User.unit_manager(unit)
|
467
|
+
```
|
468
|
+
|
469
|
+
## Instance methods
|
470
|
+
|
471
|
+
_List of frequently used methods:_
|
472
|
+
|
473
|
+
```python
|
474
|
+
# Check data validity.
|
475
|
+
# The main use is to check data from web forms.
|
476
|
+
# It is also used to verify Models that do not migrate to the database.
|
477
|
+
user = User()
|
478
|
+
if not await user.is_valid():
|
479
|
+
user.print_err() # Convenient to use during development.
|
480
|
+
|
481
|
+
# Create or update document in database.
|
482
|
+
# This method pre-uses the `check` method.
|
483
|
+
user = User()
|
484
|
+
if not await user.save():
|
485
|
+
user.print_err() # Convenient to use during development.
|
486
|
+
|
487
|
+
# Delete document from database.
|
488
|
+
user = User()
|
489
|
+
await user.delete()
|
490
|
+
# or
|
491
|
+
await user.delete(remove_files=False)
|
492
|
+
|
493
|
+
# Verification, replacement and recoverang of password.
|
494
|
+
user = User()
|
495
|
+
await user.verify_password(password="12345678")
|
496
|
+
await user.update_password(
|
497
|
+
old_password="12345678",
|
498
|
+
new_password="O2eA4GIr38KGGlS",
|
499
|
+
)
|
500
|
+
```
|
501
|
+
|
354
502
|
## Contributing
|
355
503
|
|
356
504
|
1. Fork it (<https://github.com/kebasyaty/ramifice/fork>)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
ramifice/__init__.py,sha256=ISlaL2BprlJLE_N1fvtAqGrB3Dhniy9IZGoyWEYZhRU,678
|
2
2
|
ramifice/add_valid.py,sha256=nXIB9SSNtc3a6amCRM4k7P03zrcTKRz9kf-3MrdjZ-A,331
|
3
|
-
ramifice/decorators.py,sha256=
|
3
|
+
ramifice/decorators.py,sha256=SThYdnYwaxAgEZsJnR7M7C2XCmuDkXwA9gvR9umb2Rk,5055
|
4
4
|
ramifice/errors.py,sha256=iuhq7fzpUmsOyeXeg2fJjta8yAuqlXLKsZVMpfUhtHE,1901
|
5
5
|
ramifice/fixtures.py,sha256=NtxOnZslYJb4yvRpZbs3ckugmTwHQFS_9iCt2zddOC0,3102
|
6
6
|
ramifice/hooks.py,sha256=Ri-ISfMT-IHaLO2eAqg8CODCTs3HRTxSylqspUKnVf4,873
|
@@ -10,15 +10,15 @@ ramifice/mixins.py,sha256=gKLmWQ-QrGO3K5_k-h1tDa08lkCql_dte2Jy05q1wsM,1125
|
|
10
10
|
ramifice/model.py,sha256=7AWVvXdP2wBY2ffx1Kx_8Ao-8VD5WGo1eOEKkFrAP68,6533
|
11
11
|
ramifice/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
12
12
|
ramifice/store.py,sha256=Kqd2Zw7Wj2vnn_0eE6KmIOkWJRhDc6AZ0ZyqxENBetQ,2730
|
13
|
-
ramifice/
|
14
|
-
ramifice/
|
15
|
-
ramifice/
|
13
|
+
ramifice/translations.py,sha256=GNGE0ULAA0aOY5pTxUd3MQW-nVaKvp6BeXWEcsR0s0o,4048
|
14
|
+
ramifice/types.py,sha256=ljo6VscUGWEJ4Km4JYo8GEfxd1YK1CUbS8CfHp_MlEA,2390
|
15
|
+
ramifice/utilities.py,sha256=cX-qnNsGhP3ltA7wnhm5V8K5_FUKq2LpD-c5YdOHLJM,2527
|
16
16
|
ramifice/commons/__init__.py,sha256=LHCsdpl4z8W-xUvAlOr1ad0ras9hspvCpuce4SAdfP0,472
|
17
17
|
ramifice/commons/general.py,sha256=fTnIRA3IGbi0lMKBCgeqkI7BGJWTbZVu0nOJYoNRSRU,4571
|
18
18
|
ramifice/commons/indexes.py,sha256=ABNRXeWZSreAE4_EDlsTN9aS8QiZbzhsatUJFn2EuOo,3849
|
19
19
|
ramifice/commons/many.py,sha256=uxWgtXE40s0s6siLeqi55oAM3H9Z6S-1wKgNoJlVytg,8312
|
20
|
-
ramifice/commons/one.py,sha256=
|
21
|
-
ramifice/commons/tools.py,sha256=
|
20
|
+
ramifice/commons/one.py,sha256=XKWYYZn7SZ4At_yhjPSM1zXMoQzvt8wSM0F8kgWxhNU,5631
|
21
|
+
ramifice/commons/tools.py,sha256=1oJHgb_WpWwoGtMcGQ-ta1oUMJLyN8QxuMbY83Sk36o,2220
|
22
22
|
ramifice/commons/units.py,sha256=sYlRThKQPdBDJB3togJz8qyASpQ7gNZndbudUGDQEwE,4402
|
23
23
|
ramifice/fields/__init__.py,sha256=yRfX7Tvpuh27Ggcx5u9e1RRYK7Wu59EVJYxxetmuP1w,1290
|
24
24
|
ramifice/fields/bool_field.py,sha256=RUoHeU6L2wX5YfGyzsZBxg3OVlu3tP6eNhzWnHsU_G0,2041
|
@@ -56,25 +56,26 @@ ramifice/fields/general/field.py,sha256=Iwjx0idIUIFwcQw4TQEqejKct1EGaaJv1zZ1poSW
|
|
56
56
|
ramifice/fields/general/file_group.py,sha256=n45KfPzFI_l5hXoKkPDG0Q-0mdC2obExV-3PJ-MN5tA,1071
|
57
57
|
ramifice/fields/general/number_group.py,sha256=AqlCY-t6JHZ2QVBe7mk5nPt6z8M4VJ_RARRlSBoIxms,715
|
58
58
|
ramifice/fields/general/text_group.py,sha256=gP6mUGXr-LTI1UegqgEMQ-5vtUJuJs0wDYKVkknW_5E,873
|
59
|
-
ramifice/paladins/__init__.py,sha256=
|
60
|
-
ramifice/paladins/check.py,sha256=
|
61
|
-
ramifice/paladins/delete.py,sha256=
|
59
|
+
ramifice/paladins/__init__.py,sha256=PIP3AXI2KBRXNcLJUF0d7ygJ7VLOAxlhb4HRKQ9MGYY,516
|
60
|
+
ramifice/paladins/check.py,sha256=kseDMCMliRsYsldcRFo5itqroQQtHtcA_oNEd6wvfgU,6835
|
61
|
+
ramifice/paladins/delete.py,sha256=RfCyew5tbBCT8u1c8nMgC2vIQlIWzu9Tvh6TuO7VBmM,3584
|
62
62
|
ramifice/paladins/password.py,sha256=O1OvmeKuXbwtBX1sPTRVsoWYHPQn9isOe1rju6A3wbE,3264
|
63
|
-
ramifice/paladins/refrash.py,sha256=
|
64
|
-
ramifice/paladins/save.py,sha256=
|
65
|
-
ramifice/paladins/tools.py,sha256=
|
63
|
+
ramifice/paladins/refrash.py,sha256=DfBFGjPB6yYjga57GuCiWFBNHalmDO3EfMjkhgoWnVI,1207
|
64
|
+
ramifice/paladins/save.py,sha256=7KxocEisEVYWkZtBpHZyP6drGMyOVvF2vaymm4HNHmg,4276
|
65
|
+
ramifice/paladins/tools.py,sha256=LlrY3rtKmBBGXOd6Lf8w84jMULA5C6nwzpHIoN16ibc,2094
|
66
|
+
ramifice/paladins/validation.py,sha256=7lFDWjokUwMwVxWAVgd_GlWGpF4mukxIVpxvpdyA19A,1850
|
66
67
|
ramifice/paladins/groups/__init__.py,sha256=hpqmWLsYAMvZHAbmMXluQSqLhkHOSTUAgLHyTM1LTYI,472
|
67
|
-
ramifice/paladins/groups/bool_group.py,sha256=
|
68
|
-
ramifice/paladins/groups/choice_group.py,sha256=
|
69
|
-
ramifice/paladins/groups/date_group.py,sha256
|
70
|
-
ramifice/paladins/groups/file_group.py,sha256=
|
71
|
-
ramifice/paladins/groups/id_group.py,sha256=
|
72
|
-
ramifice/paladins/groups/img_group.py,sha256=
|
73
|
-
ramifice/paladins/groups/num_group.py,sha256=
|
74
|
-
ramifice/paladins/groups/pass_group.py,sha256=
|
75
|
-
ramifice/paladins/groups/slug_group.py,sha256=
|
76
|
-
ramifice/paladins/groups/text_group.py,sha256=
|
77
|
-
ramifice-0.3.
|
78
|
-
ramifice-0.3.
|
79
|
-
ramifice-0.3.
|
80
|
-
ramifice-0.3.
|
68
|
+
ramifice/paladins/groups/bool_group.py,sha256=fpxCySJ5JL7w1dLFPX1025zEU8CRsWSyQyXb3Hr3Z2s,819
|
69
|
+
ramifice/paladins/groups/choice_group.py,sha256=OJeZjpnqKnSF1_bechtNepCCPjmp54x1T0c28DAa11c,1777
|
70
|
+
ramifice/paladins/groups/date_group.py,sha256=-IS9pAHXdWK7tO9KQ2UyG1uf7TA43DKYCOCtOM2_tZ0,3844
|
71
|
+
ramifice/paladins/groups/file_group.py,sha256=oQ61TCGoRF8kArOY0F2IXXUTOiRyyu0oh3F1reV8rmU,3298
|
72
|
+
ramifice/paladins/groups/id_group.py,sha256=tK3OL1pr-V8PHPyS7vmjmJi_sRU669EvkY_Fc4sRHzg,1352
|
73
|
+
ramifice/paladins/groups/img_group.py,sha256=Yi2LG-RK_0NV5at8L8i_OYp--XDzqh66bw3xA9v2HeE,5779
|
74
|
+
ramifice/paladins/groups/num_group.py,sha256=Jvb-lwHxapQybbLerC4t-_yO8N7Coo1fIlZ9eAGHcGs,2508
|
75
|
+
ramifice/paladins/groups/pass_group.py,sha256=SEKpR2voNQtmywugDXJKY4XqPTL91CrJ87h0QNMqQqs,1952
|
76
|
+
ramifice/paladins/groups/slug_group.py,sha256=_IRil2PwpY7cH7WaExNksKz61kQjvc27blrEufgUB30,2323
|
77
|
+
ramifice/paladins/groups/text_group.py,sha256=nYZGwAIsJD-tX8RBtFlWvngO9RU4V0CnREUhxvV2UDo,3493
|
78
|
+
ramifice-0.3.15.dist-info/METADATA,sha256=JtyHFCf2oOu3Vbax_MgUcIRKnEKs768hiYrS9XSM6-Q,18876
|
79
|
+
ramifice-0.3.15.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
80
|
+
ramifice-0.3.15.dist-info/licenses/LICENSE,sha256=LrEL0aTZx90HDwFUQCJutORiDjJL9AnuVvCtspXIqt4,1095
|
81
|
+
ramifice-0.3.15.dist-info/RECORD,,
|
File without changes
|
File without changes
|