ramifice 0.4.10__py3-none-any.whl → 0.4.12__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.
@@ -7,6 +7,7 @@ from pymongo.asynchronous.command_cursor import AsyncCommandCursor
7
7
  from pymongo.asynchronous.database import AsyncDatabase
8
8
 
9
9
  from ..utils import globals, translations
10
+ from .tools import correct_mongo_filter
10
11
 
11
12
 
12
13
  class GeneralMixin:
@@ -58,7 +59,10 @@ class GeneralMixin:
58
59
  """Gets an estimate of the count of documents in a collection using collection metadata."""
59
60
  # Get collection for current model.
60
61
  collection: AsyncCollection = globals.MONGO_DATABASE[cls.META["collection_name"]]
61
- #
62
+ # Correcting filter.
63
+ if filter is not None:
64
+ filter = correct_mongo_filter(cls, filter)
65
+
62
66
  return await collection.count_documents(
63
67
  filter=filter,
64
68
  session=session,
@@ -78,7 +82,10 @@ class GeneralMixin:
78
82
  """Runs an aggregation framework pipeline."""
79
83
  # Get collection for current model.
80
84
  collection: AsyncCollection = globals.MONGO_DATABASE[cls.META["collection_name"]]
81
- #
85
+ # Correcting filter.
86
+ if pipeline is not None:
87
+ pipeline = correct_mongo_filter(cls, pipeline)
88
+
82
89
  return await collection.aggregate(
83
90
  pipeline=pipeline,
84
91
  session=session,
@@ -103,7 +110,10 @@ class GeneralMixin:
103
110
  """
104
111
  # Get collection for current model.
105
112
  collection: AsyncCollection = globals.MONGO_DATABASE[cls.META["collection_name"]]
106
- #
113
+ # Correcting filter.
114
+ if filter is not None:
115
+ filter = correct_mongo_filter(cls, filter)
116
+
107
117
  return await collection.distinct(
108
118
  key=key,
109
119
  filter=filter,
ramifice/commons/many.py CHANGED
@@ -7,9 +7,9 @@ from pymongo.asynchronous.collection import AsyncCollection
7
7
  from pymongo.asynchronous.cursor import AsyncCursor, CursorType
8
8
  from pymongo.results import DeleteResult
9
9
 
10
- from ..utils import globals
10
+ from ..utils import globals, translations
11
11
  from ..utils.errors import PanicError
12
- from .tools import mongo_doc_to_raw_doc, password_to_none
12
+ from .tools import correct_mongo_filter, mongo_doc_to_raw_doc, password_to_none
13
13
 
14
14
 
15
15
  class ManyMixin:
@@ -43,6 +43,9 @@ class ManyMixin:
43
43
  """Find documents."""
44
44
  # Get collection for current model.
45
45
  collection: AsyncCollection = globals.MONGO_DATABASE[cls.META["collection_name"]]
46
+ # Correcting filter.
47
+ if filter is not None:
48
+ filter = correct_mongo_filter(cls, filter)
46
49
  # Get documents.
47
50
  doc_list: list[dict[str, Any]] = []
48
51
  cursor: AsyncCursor = collection.find(
@@ -108,6 +111,9 @@ class ManyMixin:
108
111
  """
109
112
  # Get collection for current model.
110
113
  collection: AsyncCollection = globals.MONGO_DATABASE[cls.META["collection_name"]]
114
+ # Correcting filter.
115
+ if filter is not None:
116
+ filter = correct_mongo_filter(cls, filter)
111
117
  # Get documents.
112
118
  doc_list: list[dict[str, Any]] = []
113
119
  cursor: AsyncCursor = collection.find(
@@ -133,9 +139,18 @@ class ManyMixin:
133
139
  session=session,
134
140
  allow_disk_use=allow_disk_use,
135
141
  )
136
- field_name_and_type = cls.META["field_name_and_type"]
142
+ inst_model_dict = {
143
+ key: val for key, val in cls().__dict__.items() if not callable(val) and not val.ignored
144
+ }
145
+ lang = translations.CURRENT_LOCALE
137
146
  async for mongo_doc in cursor:
138
- doc_list.append(mongo_doc_to_raw_doc(field_name_and_type, mongo_doc))
147
+ doc_list.append(
148
+ mongo_doc_to_raw_doc(
149
+ inst_model_dict,
150
+ mongo_doc,
151
+ lang,
152
+ )
153
+ )
139
154
  return doc_list
140
155
 
141
156
  @classmethod
@@ -166,6 +181,9 @@ class ManyMixin:
166
181
  """Find documents and convert to a json string."""
167
182
  # Get collection for current model.
168
183
  collection: AsyncCollection = globals.MONGO_DATABASE[cls.META["collection_name"]]
184
+ # Correcting filter.
185
+ if filter is not None:
186
+ filter = correct_mongo_filter(cls, filter)
169
187
  # Get documents.
170
188
  doc_list: list[dict[str, Any]] = []
171
189
  cursor: AsyncCursor = collection.find(
@@ -191,9 +209,18 @@ class ManyMixin:
191
209
  session=session,
192
210
  allow_disk_use=allow_disk_use,
193
211
  )
194
- field_name_and_type = cls.META["field_name_and_type"]
212
+ inst_model_dict = {
213
+ key: val for key, val in cls().__dict__.items() if not callable(val) and not val.ignored
214
+ }
215
+ lang = translations.CURRENT_LOCALE
195
216
  async for mongo_doc in cursor:
196
- doc_list.append(mongo_doc_to_raw_doc(field_name_and_type, mongo_doc))
217
+ doc_list.append(
218
+ mongo_doc_to_raw_doc(
219
+ inst_model_dict,
220
+ mongo_doc,
221
+ lang,
222
+ )
223
+ )
197
224
  return json.dumps(doc_list) if len(doc_list) > 0 else None
198
225
 
199
226
  @classmethod
@@ -217,6 +244,9 @@ class ManyMixin:
217
244
  raise PanicError(msg)
218
245
  # Get collection for current model.
219
246
  collection: AsyncCollection = globals.MONGO_DATABASE[cls.META["collection_name"]]
247
+ # Correcting filter.
248
+ if filter is not None:
249
+ filter = correct_mongo_filter(cls, filter)
220
250
  # Delete documents.
221
251
  result: DeleteResult = await collection.delete_many(
222
252
  filter=filter,
ramifice/commons/one.py CHANGED
@@ -5,9 +5,9 @@ from typing import Any
5
5
  from pymongo.asynchronous.collection import AsyncCollection
6
6
  from pymongo.results import DeleteResult
7
7
 
8
- from ..utils import globals
8
+ from ..utils import globals, translations
9
9
  from ..utils.errors import PanicError
10
- from .tools import mongo_doc_to_raw_doc, password_to_none
10
+ from .tools import correct_mongo_filter, mongo_doc_to_raw_doc, password_to_none
11
11
 
12
12
 
13
13
  class OneMixin:
@@ -23,6 +23,9 @@ class OneMixin:
23
23
  """Find a single document."""
24
24
  # Get collection for current model.
25
25
  collection: AsyncCollection = globals.MONGO_DATABASE[cls.META["collection_name"]]
26
+ # Correcting filter.
27
+ if filter is not None:
28
+ filter = correct_mongo_filter(cls, filter)
26
29
  # Get document.
27
30
  mongo_doc = await collection.find_one(filter, *args, **kwargs)
28
31
  if mongo_doc is not None:
@@ -42,13 +45,20 @@ class OneMixin:
42
45
  """Find a single document and converting to raw document."""
43
46
  # Get collection for current model.
44
47
  collection: AsyncCollection = globals.MONGO_DATABASE[cls.META["collection_name"]]
48
+ # Correcting filter.
49
+ if filter is not None:
50
+ filter = correct_mongo_filter(cls, filter)
45
51
  # Get document.
46
52
  raw_doc = None
47
53
  mongo_doc = await collection.find_one(filter, *args, **kwargs)
54
+ inst_model_dict = {
55
+ key: val for key, val in cls().__dict__.items() if not callable(val) and not val.ignored
56
+ }
48
57
  if mongo_doc is not None:
49
58
  raw_doc = mongo_doc_to_raw_doc(
50
- cls.META["field_name_and_type"],
59
+ inst_model_dict,
51
60
  mongo_doc,
61
+ translations.CURRENT_LOCALE,
52
62
  )
53
63
  return raw_doc
54
64
 
@@ -62,6 +72,9 @@ class OneMixin:
62
72
  """Find a single document and convert it to a Model instance."""
63
73
  # Get collection for current model.
64
74
  collection: AsyncCollection = globals.MONGO_DATABASE[cls.META["collection_name"]]
75
+ # Correcting filter.
76
+ if filter is not None:
77
+ filter = correct_mongo_filter(cls, filter)
65
78
  # Get document.
66
79
  inst_model = None
67
80
  mongo_doc = await collection.find_one(filter, *args, **kwargs)
@@ -80,12 +93,15 @@ class OneMixin:
80
93
  """Find a single document and convert it to a JSON string."""
81
94
  # Get collection for current model.
82
95
  collection: AsyncCollection = globals.MONGO_DATABASE[cls.META["collection_name"]]
96
+ # Correcting filter.
97
+ if filter is not None:
98
+ filter = correct_mongo_filter(cls, filter)
83
99
  # Get document.
84
100
  json_str: str | None = None
85
101
  mongo_doc = await collection.find_one(filter, *args, **kwargs)
86
102
  if mongo_doc is not None:
87
103
  # Convert document to Model instance.
88
- inst_model = cls.from_mongo_doc( mongo_doc)
104
+ inst_model = cls.from_mongo_doc(mongo_doc)
89
105
  json_str = inst_model.to_json()
90
106
  return json_str
91
107
 
@@ -110,6 +126,9 @@ class OneMixin:
110
126
  raise PanicError(msg)
111
127
  # Get collection for current model.
112
128
  collection: AsyncCollection = globals.MONGO_DATABASE[cls.META["collection_name"]]
129
+ # Correcting filter.
130
+ if filter is not None:
131
+ filter = correct_mongo_filter(cls, filter)
113
132
  # Get document.
114
133
  result: DeleteResult = await collection.delete_one(
115
134
  filter=filter,
@@ -144,6 +163,9 @@ class OneMixin:
144
163
  raise PanicError(msg)
145
164
  # Get collection for current model.
146
165
  collection: AsyncCollection = globals.MONGO_DATABASE[cls.META["collection_name"]]
166
+ # Correcting filter.
167
+ if filter is not None:
168
+ filter = correct_mongo_filter(cls, filter)
147
169
  # Get document.
148
170
  mongo_doc: dict[str, Any] | None = await collection.find_one_and_delete(
149
171
  filter=filter,
ramifice/commons/tools.py CHANGED
@@ -1,12 +1,29 @@
1
1
  """Tool of Commons - A set of auxiliary methods."""
2
2
 
3
+ import json
3
4
  from typing import Any
4
5
 
5
6
  from babel.dates import format_date, format_datetime
7
+ from bson import json_util
6
8
 
7
9
  from ..utils import translations
8
10
 
9
11
 
12
+ def correct_mongo_filter(cls_model: Any, filter: Any) -> Any:
13
+ """Correcting filter of request.
14
+
15
+ Corrects `TextField` fields that require localization of translation.
16
+ """
17
+ lang: str = translations.CURRENT_LOCALE
18
+ filter_json: str = json_util.dumps(filter)
19
+ filter_json = (
20
+ cls_model.META["regex_mongo_filter"]
21
+ .sub(rf'\g<field>.{lang}":', filter_json)
22
+ .replace('":.', ".")
23
+ )
24
+ return json_util.loads(filter_json)
25
+
26
+
10
27
  def password_to_none(
11
28
  field_name_and_type: dict[str, str],
12
29
  mongo_doc: dict[str, Any],
@@ -19,8 +36,9 @@ def password_to_none(
19
36
 
20
37
 
21
38
  def mongo_doc_to_raw_doc(
22
- field_name_and_type: dict[str, str],
39
+ inst_model_dict: dict[str, Any],
23
40
  mongo_doc: dict[str, Any],
41
+ lang: str,
24
42
  ) -> dict[str, Any]:
25
43
  """Convert the Mongo document to the raw document.
26
44
 
@@ -31,14 +49,14 @@ def mongo_doc_to_raw_doc(
31
49
  datetime to str
32
50
  """
33
51
  doc: dict[str, Any] = {}
34
- lang: str = translations.CURRENT_LOCALE
35
- for f_name, t_name in field_name_and_type.items():
52
+ for f_name, f_data in inst_model_dict.items():
53
+ field_type = f_data.field_type
36
54
  value = mongo_doc[f_name]
37
55
  if value is not None:
38
- if t_name == "TextField":
56
+ if field_type == "TextField" and f_data.multi_language:
39
57
  value = value.get(lang, "") if value is not None else None
40
- elif "Date" in t_name:
41
- if "Time" in t_name:
58
+ elif "Date" in field_type:
59
+ if "Time" in field_type:
42
60
  value = format_datetime(
43
61
  datetime=value,
44
62
  format="short",
@@ -50,9 +68,9 @@ def mongo_doc_to_raw_doc(
50
68
  format="short",
51
69
  locale=lang,
52
70
  )
53
- elif t_name == "IDField":
71
+ elif field_type == "IDField":
54
72
  value = str(value)
55
- elif t_name == "PasswordField":
73
+ elif field_type == "PasswordField":
56
74
  value = None
57
75
  doc[f_name] = value
58
76
  return doc
@@ -69,9 +69,9 @@ class ChoiceFloatDynField(Field, ChoiceGroup, JsonMixin):
69
69
  self.value: float | None = None
70
70
  self.choices: dict[str, float] | None = None
71
71
 
72
- def has_value(self, is_migrat: bool = False) -> bool:
72
+ def has_value(self, is_migrate: bool = False) -> bool:
73
73
  """Does the field value match the possible options in choices."""
74
- if is_migrat:
74
+ if is_migrate:
75
75
  return True
76
76
  value = self.value
77
77
  if value is not None:
@@ -80,7 +80,7 @@ class ChoiceFloatField(Field, ChoiceGroup, JsonMixin):
80
80
  if not isinstance(readonly, bool):
81
81
  raise AssertionError("Parameter `readonly` - Not а `bool` type!")
82
82
 
83
- def has_value(self, is_migrat: bool = False) -> bool:
83
+ def has_value(self, is_migrate: bool = False) -> bool:
84
84
  """Does the field value match the possible options in choices."""
85
85
  value = self.value
86
86
  if value is None:
@@ -69,9 +69,9 @@ class ChoiceFloatMultDynField(Field, ChoiceGroup, JsonMixin):
69
69
  self.value: list[float] | None = None
70
70
  self.choices: dict[str, float] | None = None
71
71
 
72
- def has_value(self, is_migrat: bool = False) -> bool:
72
+ def has_value(self, is_migrate: bool = False) -> bool:
73
73
  """Does the field value match the possible options in choices."""
74
- if is_migrat:
74
+ if is_migrate:
75
75
  return True
76
76
  value = self.value
77
77
  if value is not None:
@@ -91,7 +91,7 @@ class ChoiceFloatMultField(Field, ChoiceGroup, JsonMixin):
91
91
  if not isinstance(readonly, bool):
92
92
  raise AssertionError("Parameter `readonly` - Not а `bool` type!")
93
93
 
94
- def has_value(self, is_migrat: bool = False) -> bool:
94
+ def has_value(self, is_migrate: bool = False) -> bool:
95
95
  """Does the field value match the possible options in choices."""
96
96
  value = self.value
97
97
  if value is None:
@@ -69,9 +69,9 @@ class ChoiceIntDynField(Field, ChoiceGroup, JsonMixin):
69
69
  self.value: int | None = None
70
70
  self.choices: dict[str, int] | None = None
71
71
 
72
- def has_value(self, is_migrat: bool = False) -> bool:
72
+ def has_value(self, is_migrate: bool = False) -> bool:
73
73
  """Does the field value match the possible options in choices."""
74
- if is_migrat:
74
+ if is_migrate:
75
75
  return True
76
76
  value = self.value
77
77
  if value is not None:
@@ -80,7 +80,7 @@ class ChoiceIntField(Field, ChoiceGroup, JsonMixin):
80
80
  if not isinstance(readonly, bool):
81
81
  raise AssertionError("Parameter `readonly` - Not а `bool` type!")
82
82
 
83
- def has_value(self, is_migrat: bool = False) -> bool:
83
+ def has_value(self, is_migrate: bool = False) -> bool:
84
84
  """Does the field value match the possible options in choices."""
85
85
  value = self.value
86
86
  if value is None:
@@ -69,9 +69,9 @@ class ChoiceIntMultDynField(Field, ChoiceGroup, JsonMixin):
69
69
  self.value: list[int] | None = None
70
70
  self.choices: dict[str, int] | None = None
71
71
 
72
- def has_value(self, is_migrat: bool = False) -> bool:
72
+ def has_value(self, is_migrate: bool = False) -> bool:
73
73
  """Does the field value match the possible options in choices."""
74
- if is_migrat:
74
+ if is_migrate:
75
75
  return True
76
76
  value = self.value
77
77
  if value is not None:
@@ -91,7 +91,7 @@ class ChoiceIntMultField(Field, ChoiceGroup, JsonMixin):
91
91
  if not isinstance(readonly, bool):
92
92
  raise AssertionError("Parameter `readonly` - Not а `bool` type!")
93
93
 
94
- def has_value(self, is_migrat: bool = False) -> bool:
94
+ def has_value(self, is_migrate: bool = False) -> bool:
95
95
  """Does the field value match the possible options in choices."""
96
96
  value = self.value
97
97
  if value is None:
@@ -69,9 +69,9 @@ class ChoiceTextDynField(Field, ChoiceGroup, JsonMixin):
69
69
  self.value: str | None = None
70
70
  self.choices: dict[str, str] | None = None
71
71
 
72
- def has_value(self, is_migrat: bool = False) -> bool:
72
+ def has_value(self, is_migrate: bool = False) -> bool:
73
73
  """Does the field value match the possible options in choices."""
74
- if is_migrat:
74
+ if is_migrate:
75
75
  return True
76
76
  value = self.value
77
77
  if value is not None:
@@ -85,7 +85,7 @@ class ChoiceTextField(Field, ChoiceGroup, JsonMixin):
85
85
  if not isinstance(readonly, bool):
86
86
  raise AssertionError("Parameter `readonly` - Not а `bool` type!")
87
87
 
88
- def has_value(self, is_migrat: bool = False) -> bool:
88
+ def has_value(self, is_migrate: bool = False) -> bool:
89
89
  """Does the field value match the possible options in choices."""
90
90
  value = self.value
91
91
  if value is None:
@@ -69,9 +69,9 @@ class ChoiceTextMultDynField(Field, ChoiceGroup, JsonMixin):
69
69
  self.value: list[str] | None = None
70
70
  self.choices: dict[str, str] | None = None
71
71
 
72
- def has_value(self, is_migrat: bool = False) -> bool:
72
+ def has_value(self, is_migrate: bool = False) -> bool:
73
73
  """Does the field value match the possible options in choices."""
74
- if is_migrat:
74
+ if is_migrate:
75
75
  return True
76
76
  value = self.value
77
77
  if value is not None:
@@ -91,7 +91,7 @@ class ChoiceTextMultField(Field, ChoiceGroup, JsonMixin):
91
91
  if not isinstance(readonly, bool):
92
92
  raise AssertionError("Parameter `readonly` - Not а `bool` type!")
93
93
 
94
- def has_value(self, is_migrat: bool = False) -> bool:
94
+ def has_value(self, is_migrate: bool = False) -> bool:
95
95
  """Does the field value match the possible options in choices."""
96
96
  value = self.value
97
97
  if value is None:
@@ -23,6 +23,8 @@ class TextField(Field, JsonMixin):
23
23
  readonly: bool = False,
24
24
  unique: bool = False,
25
25
  maxlength: int = 256,
26
+ # Support for several language.
27
+ multi_language: bool = False,
26
28
  ):
27
29
  if globals.DEBUG:
28
30
  if not isinstance(maxlength, int):
@@ -55,6 +57,8 @@ class TextField(Field, JsonMixin):
55
57
  raise AssertionError("Parameter `use_editor` - Not а `bool` type!")
56
58
  if not isinstance(maxlength, int):
57
59
  raise AssertionError("Parameter `maxlength` - Not а `int` type!")
60
+ if not isinstance(multi_language, bool):
61
+ raise AssertionError("Parameter `multi_language` - Not а `int` type!")
58
62
 
59
63
  Field.__init__(
60
64
  self,
@@ -78,6 +82,8 @@ class TextField(Field, JsonMixin):
78
82
  self.textarea = textarea
79
83
  self.use_editor = use_editor
80
84
  self.maxlength = maxlength
85
+ # Support for several language.
86
+ self.multi_language = multi_language
81
87
 
82
88
  def __len__(self) -> int:
83
89
  """Return length of field `value`."""
@@ -1,16 +1,17 @@
1
1
  """Decorator for converting Python classes into Ramifice models."""
2
2
 
3
3
  import os
4
+ import re
4
5
  from typing import Any
5
6
 
6
7
  from ..commons import QCommonsMixin
7
8
  from ..fields import DateTimeField, IDField
8
9
  from ..paladins import CheckMixin, QPaladinsMixin, ValidationMixin
9
10
  from ..utils.errors import DoesNotMatchRegexError, PanicError
11
+ from ..utils.globals import REGEX
10
12
  from ..utils.mixins.add_valid import AddValidMixin
11
13
  from ..utils.mixins.hooks import HooksMixin
12
14
  from ..utils.mixins.indexing import IndexMixin
13
- from ..utils.globals import REGEX
14
15
  from .model import Model
15
16
  from .pseudo import PseudoModel
16
17
 
@@ -103,7 +104,7 @@ def model(
103
104
  def caching(cls: Any, service_name: str, is_migrate_model: bool) -> dict[str, Any]:
104
105
  """Add additional metadata to `Model.META`."""
105
106
  metadata: dict[str, Any] = {}
106
- model_name = cls.__name__
107
+ model_name: str = cls.__name__
107
108
  if REGEX["model_name"].match(model_name) is None:
108
109
  raise DoesNotMatchRegexError("^[A-Z][a-zA-Z0-9]{0,24}$")
109
110
  #
@@ -118,9 +119,12 @@ def caching(cls: Any, service_name: str, is_migrate_model: bool) -> dict[str, An
118
119
  # Build data migration storage for dynamic fields.
119
120
  data_dynamic_fields: dict[str, dict[str, str | int | float] | None] = {}
120
121
  # Count all fields.
121
- count_all_fields = 0
122
+ count_all_fields: int = 0
122
123
  # Count fields for migrating.
123
- count_fields_no_ignored = 0
124
+ count_fields_no_ignored: int = 0
125
+ # List of fields that support localization of translates.
126
+ # Hint: `TextField`
127
+ supported_lang_fields: list[str] = []
124
128
 
125
129
  raw_model = cls()
126
130
  raw_model.fields()
@@ -130,9 +134,9 @@ def caching(cls: Any, service_name: str, is_migrate_model: bool) -> dict[str, An
130
134
  default_fields["created_at"] = DateTimeField()
131
135
  default_fields["updated_at"] = DateTimeField()
132
136
  fields = {**raw_model.__dict__, **default_fields}
133
- for f_name, f_type in fields.items():
134
- if not callable(f_type):
135
- f_type_str = f_type.__class__.__name__
137
+ for f_name, f_data in fields.items():
138
+ if not callable(f_data):
139
+ f_type_str = f_data.__class__.__name__
136
140
  # Count all fields.
137
141
  count_all_fields += 1
138
142
  # Get attributes value for fields of Model: id, name.
@@ -141,14 +145,14 @@ def caching(cls: Any, service_name: str, is_migrate_model: bool) -> dict[str, An
141
145
  "name": f_name,
142
146
  }
143
147
  #
144
- if not f_type.ignored:
148
+ if not f_data.ignored:
145
149
  # Count fields for migrating.
146
150
  if is_migrate_model:
147
151
  count_fields_no_ignored += 1
148
152
  # Get a dictionary of field names and types.
149
153
  field_name_and_type[f_name] = f_type_str
150
154
  # Build data migration storage for dynamic fields.
151
- if "Dyn" in f_type.field_type:
155
+ if "Dyn" in f_data.field_type:
152
156
  if not is_migrate_model:
153
157
  msg = (
154
158
  f"Model: `{cls.__module__}.{model_name}` > "
@@ -157,11 +161,16 @@ def caching(cls: Any, service_name: str, is_migrate_model: bool) -> dict[str, An
157
161
  )
158
162
  raise PanicError(msg)
159
163
  data_dynamic_fields[f_name] = None
164
+ if f_data.field_type == "TextField" and f_data.multi_language:
165
+ supported_lang_fields.append(f_name)
160
166
 
161
167
  metadata["field_name_and_type"] = field_name_and_type
162
168
  metadata["field_attrs"] = field_attrs
163
169
  metadata["data_dynamic_fields"] = data_dynamic_fields
164
170
  metadata["count_all_fields"] = count_all_fields
165
171
  metadata["count_fields_no_ignored"] = count_fields_no_ignored
172
+ metadata["regex_mongo_filter"] = re.compile(
173
+ rf'(?P<field>"(?:{"|".join(supported_lang_fields)})":)'
174
+ )
166
175
 
167
176
  return metadata
@@ -26,6 +26,7 @@ class ChoiceGroupMixin:
26
26
  def choice_group(self, params: dict[str, Any]) -> None:
27
27
  """Checking choice fields."""
28
28
  field = params["field_data"]
29
+ is_migrate = params["is_migration_process"]
29
30
  # Get current value.
30
31
  value = field.value or field.__dict__.get("default") or None
31
32
 
@@ -37,7 +38,7 @@ class ChoiceGroupMixin:
37
38
  params["result_map"][field.name] = None
38
39
  return
39
40
  # Does the field value match the possible options in choices.
40
- if not field.has_value(is_migrat=params["is_migration_process"]):
41
+ if not field.has_value(is_migrate):
41
42
  err_msg = translations._("Your choice does not match the options offered !")
42
43
  accumulate_error(err_msg, params)
43
44
  # Insert result.
@@ -27,11 +27,11 @@ class TextGroupMixin:
27
27
  field = params["field_data"]
28
28
  field_name = field.name
29
29
  field_type: str = field.field_type
30
- is_text_field: bool = "TextField" == field_type
30
+ is_multi_language: bool = (field_type == "TextField") and field.multi_language
31
31
  # Get current value.
32
32
  value = field.value or field.__dict__.get("default")
33
33
 
34
- if is_text_field:
34
+ if is_multi_language:
35
35
  if not isinstance(value, (str, dict, type(None))):
36
36
  panic_type_error("str | dict | None", params)
37
37
  else:
@@ -55,7 +55,7 @@ class TextGroupMixin:
55
55
  value,
56
56
  params,
57
57
  field_name,
58
- is_text_field,
58
+ is_multi_language,
59
59
  ):
60
60
  err_msg = translations._("Is not unique !")
61
61
  accumulate_error(err_msg, params)
@@ -85,7 +85,7 @@ class TextGroupMixin:
85
85
  accumulate_error(err_msg, params)
86
86
  # Insert result.
87
87
  if params["is_save"]:
88
- if is_text_field:
88
+ if is_multi_language:
89
89
  mult_lang_text: dict[str, str] = (
90
90
  params["curr_doc"][field_name]
91
91
  if params["is_update"]
@@ -15,9 +15,10 @@ def ignored_fields_to_none(inst_model: Any) -> None:
15
15
  def refresh_from_mongo_doc(inst_model: Any, mongo_doc: dict[str, Any]) -> None:
16
16
  """Update object instance from Mongo document."""
17
17
  lang: str = translations.CURRENT_LOCALE
18
+ model_dict = inst_model.__dict__
18
19
  for name, data in mongo_doc.items():
19
- field = inst_model.__dict__[name]
20
- if field.field_type == "TextField":
20
+ field = model_dict[name]
21
+ if field.field_type == "TextField" and field.multi_language:
21
22
  field.value = data.get(lang, "") if data is not None else None
22
23
  elif field.group == "pass":
23
24
  field.value = None
@@ -54,13 +55,13 @@ async def check_uniqueness(
54
55
  value: str | int | float,
55
56
  params: dict[str, Any],
56
57
  field_name: str | None = None,
57
- is_text_field: bool = False,
58
+ is_multi_language: bool = False,
58
59
  ) -> bool:
59
60
  """Check the uniqueness of the value in the collection."""
60
61
  if not params["is_migrate_model"]:
61
62
  return True
62
63
  q_filter = None
63
- if is_text_field:
64
+ if is_multi_language:
64
65
  lang_filter = [{f"{field_name}.{lang}": value} for lang in translations.LANGUAGES]
65
66
  q_filter = {
66
67
  "$and": [
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ramifice
3
- Version: 0.4.10
3
+ Version: 0.4.12
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/
@@ -12,7 +12,7 @@ Author-email: Gennady Kostyunin <kebasyaty@gmail.com>
12
12
  License-Expression: MIT
13
13
  License-File: LICENSE
14
14
  Keywords: mongo,mongodb,orm,pymongo,ramifice
15
- Classifier: Development Status :: 3 - Alpha
15
+ Classifier: Development Status :: 4 - Beta
16
16
  Classifier: Intended Audience :: Developers
17
17
  Classifier: License :: OSI Approved :: MIT License
18
18
  Classifier: Programming Language :: Python :: 3
@@ -82,7 +82,7 @@ _For more information see [PyMongo](https://pypi.org/project/pymongo/ "PyMongo")
82
82
 
83
83
  <p>
84
84
  <a href="https://github.com/kebasyaty/ramifice" alt="Project Status">
85
- <img src="https://raw.githubusercontent.com/kebasyaty/ramifice/v0/assets/project_status/project-status-alpha.svg"
85
+ <img src="https://raw.githubusercontent.com/kebasyaty/ramifice/v0/assets/project_status/project-status-beta.svg"
86
86
  alt="Project Status">
87
87
  </a>
88
88
  </p>
@@ -177,7 +177,10 @@ class User:
177
177
  required=True,
178
178
  unique=True,
179
179
  )
180
- self.first_name = TextField(label=gettext("First name"), required=True)
180
+ self.first_name = TextField(
181
+ label=gettext("First name"),
182
+ required=True,
183
+ )
181
184
  self.last_name = TextField(
182
185
  label=gettext("Last name"),
183
186
  required=True,
@@ -187,8 +190,17 @@ class User:
187
190
  required=True,
188
191
  unique=True,
189
192
  )
190
- self.birthday = DateField(label=gettext("Birthday"))
191
- self.password = PasswordField(label=gettext("Password"))
193
+ self.birthday = DateField(
194
+ label=gettext("Birthday"),
195
+ )
196
+ self.description = TextField(
197
+ label=gettext("About yourself"),
198
+ # Support for several language.
199
+ multi_language=True,
200
+ )
201
+ self.password = PasswordField(
202
+ label=gettext("Password"),
203
+ )
192
204
  self.сonfirm_password = PasswordField(
193
205
  label=gettext("Confirm password"),
194
206
  # If true, the value of this field is not saved in the database.
@@ -251,8 +263,7 @@ async def main():
251
263
  print("User details:")
252
264
  user_details = await User.find_one_to_raw_doc(
253
265
  # {"_id": user.id.value}
254
- # For `TextField`.
255
- {f"username.{translations.CURRENT_LOCALE}": user.username.value}
266
+ {f"username": user.username.value}
256
267
  )
257
268
  if user_details is not None:
258
269
  pprint.pprint(user_details)
@@ -1,26 +1,26 @@
1
1
  ramifice/__init__.py,sha256=IuI20h84QWyFftykvte_HLft2iVxwpu0K592NypMwfo,759
2
2
  ramifice/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  ramifice/commons/__init__.py,sha256=FTG78sTU0ay0rvmrq5fUmVjL-I-S7wgQq3BLcyqnjrQ,433
4
- ramifice/commons/general.py,sha256=yi52I9t8R1XePZdyrPmhXJxLc_YkY72Ea7_dBvsKfXI,4984
4
+ ramifice/commons/general.py,sha256=Vq0IMuaZj-BqwENTDMg_DouMjV8EjzDy181Ujtw3JQs,5358
5
5
  ramifice/commons/indexes.py,sha256=hAcWKZ9MMgLbRtoQ6Af0b8r-PY0dbEYmMBl8z_d1DRo,3646
6
- ramifice/commons/many.py,sha256=KGt7licVJmLADvyPtH7-GbXHig-EnGFbnk7QKu3mfvQ,8214
7
- ramifice/commons/one.py,sha256=MwUlU9getNtwTOL6-lROdzViBsChWgMIMppCWoeAnFU,5815
8
- ramifice/commons/tools.py,sha256=s4CdHb9h8aqR371py0KVsBnNBJSW8-7AVjbTmuFAeeg,1793
6
+ ramifice/commons/many.py,sha256=jkah1kO1CXWPB0xW0dbMelCFBSirRYkX9a333EcPE_M,9202
7
+ ramifice/commons/one.py,sha256=uZsgwg_GgA1VMZDPqhRFMmCdtvcv6xsL4DWbCOwGV3w,6730
8
+ ramifice/commons/tools.py,sha256=hCnB95nwSCUufig9cSA5Kj9jvqRIJJ2GUSzfUK4FNXM,2363
9
9
  ramifice/commons/unit_manager.py,sha256=IkWqXu1PHHal0aGfx6zme81iXPeygxPqEWO-u_8RXoI,4356
10
10
  ramifice/fields/__init__.py,sha256=yRfX7Tvpuh27Ggcx5u9e1RRYK7Wu59EVJYxxetmuP1w,1290
11
11
  ramifice/fields/bool_field.py,sha256=WWubSwsFJZu8b3MviDd2zXnNYOQaYw8toklFNSTVFLA,2072
12
- ramifice/fields/choice_float_dyn_field.py,sha256=FNOX8999vOz9S_0UK1Mn_K9s3Mmy7OeXioCM1bOwtD0,3091
13
- ramifice/fields/choice_float_field.py,sha256=rf93rsDkl4AImIIimkpFdMf3DzDq4j3yCzv_alu-R5U,3674
14
- ramifice/fields/choice_float_mult_dyn_field.py,sha256=RRSWEietLjR3e0R62oq7c8PIng0XmHoLLJj0k9veE60,3154
15
- ramifice/fields/choice_float_mult_field.py,sha256=a3n1YnBN7VBu45nLIl3eu3FcuEj86LebvT_iPzgLFnc,4231
16
- ramifice/fields/choice_int_dyn_field.py,sha256=QhP0mGHTj3V4KKPj6hsc05bOe4Hw2TnNHimQBixVYAs,3085
17
- ramifice/fields/choice_int_field.py,sha256=PyyFCkz-2lwKLJaKdR0DwYctMSk7n0iqxFbYeXUhVJQ,3662
18
- ramifice/fields/choice_int_mult_dyn_field.py,sha256=NMPDWcgR6doeCHPYIHFeD9w66FcE6uKIVEIW4Zmk3YA,3150
19
- ramifice/fields/choice_int_mult_field.py,sha256=gzmtf9segqAH7IuaZjR6l9cyaCNIrBdWKRuNPAU0Klk,4225
20
- ramifice/fields/choice_text_dyn_field.py,sha256=yfk9pa1_KEKFNL5PzvTkRuz_83q3Lf38sBdogBQpybI,3081
21
- ramifice/fields/choice_text_field.py,sha256=W4X5HkMVBrxu_6hWRfOffU6GAhgJEh9fJAb6cLIESPk,3867
22
- ramifice/fields/choice_text_mult_dyn_field.py,sha256=xU_8mGPHIsRFndziE5o2NhuxwSg_cJX1mSBYMQtB8Yw,3146
23
- ramifice/fields/choice_text_mult_field.py,sha256=0pNE2WWoyk_rX7-IMq3KUx6Yxl8OzSf0-_WGTRnxX1g,4221
12
+ ramifice/fields/choice_float_dyn_field.py,sha256=Yrd9cNKLm0mhU-P3IojwFRLSbuxdp7f2NMGJhrRWsf8,3093
13
+ ramifice/fields/choice_float_field.py,sha256=GX7ygsflgXFwwU5Gacq8EsXTdzdgNxWfT5ys8z4s2K8,3675
14
+ ramifice/fields/choice_float_mult_dyn_field.py,sha256=zDaJ_-qSytIXfY4EH43QwPB0dtkQfHPCGSZI7VMDnoo,3156
15
+ ramifice/fields/choice_float_mult_field.py,sha256=TArh0D13F9JuJjFn7WDrBzAqDCL03T35z1nZB4T55Rw,4232
16
+ ramifice/fields/choice_int_dyn_field.py,sha256=pryP7KDvRexKBo9mQTwN61Rnyuhiiv_ZHw56wWFe_bs,3087
17
+ ramifice/fields/choice_int_field.py,sha256=OyuADapAIwlutYvAtu-g7hwmq251e9cHMqKP4dm9Du8,3663
18
+ ramifice/fields/choice_int_mult_dyn_field.py,sha256=BZbH0SUcJji6daazj3ovBYCaY1o00rMBbEFzCzDu7b4,3152
19
+ ramifice/fields/choice_int_mult_field.py,sha256=4PZx0Wv1R2Wq5JZWQNqqYi_4OX13KNwdxlwlyKqndZM,4226
20
+ ramifice/fields/choice_text_dyn_field.py,sha256=HcJXq9HHgglz_BTOpGfvXmxH2BGhXze_-QAampmTGnI,3083
21
+ ramifice/fields/choice_text_field.py,sha256=JHauvxmYIbxUMjCgOHI0IcBNT2zv2gPH0njp6qXlKr4,3868
22
+ ramifice/fields/choice_text_mult_dyn_field.py,sha256=GmoRveIQjidAV-NOmfEO2irlG6agRHpMKNUFVzKSGiM,3148
23
+ ramifice/fields/choice_text_mult_field.py,sha256=RHB38fNL4ppasE-x2jJE73s221sDZ-YX3-WgpYsI_kA,4222
24
24
  ramifice/fields/color_field.py,sha256=NdYey3Og8hh7oK7yDljrG6K8frgqj4C2OTXFnJVAYFQ,3526
25
25
  ramifice/fields/date_field.py,sha256=w875p6JwssKhEZJLBHmC8sE5wArn161rlQi0Zt-qEZo,5214
26
26
  ramifice/fields/date_time_field.py,sha256=OGi-ED-Gm_AWGzczHA1qch59IuWy3KkVIJHQl3OlatU,5249
@@ -34,7 +34,7 @@ ramifice/fields/ip_field.py,sha256=tsxdwKd-MhkhnE1F2FmFg67jwh9Q-RvXECv1aG8PitE,3
34
34
  ramifice/fields/password_field.py,sha256=m3n_qJRRHCtgyBC7nDB0dYBO_82Lp1Ws48d1_jxHA8o,3216
35
35
  ramifice/fields/phone_field.py,sha256=5qZ_rR82M9cuW7Dv-D5d9jDaw7DJp9y60lRB4GEmi4w,3615
36
36
  ramifice/fields/slug_field.py,sha256=9rH1VdxYPoK60QCKHRB91bA3ZmIeIuFmf6WttzB1yTI,2685
37
- ramifice/fields/text_field.py,sha256=OrZJ337C9YUT7LOzIZR5xRBxL7dSb7ERddiJWQaT3zI,3797
37
+ ramifice/fields/text_field.py,sha256=pa5J1nTBC56JDU3mDyjy1Qofv8FLFUgACkNcZB6cUGQ,4107
38
38
  ramifice/fields/url_field.py,sha256=DTy4B22juav9dzIrqKy3NzM2vuICHvDzIDbzkkyF1C0,3993
39
39
  ramifice/fields/general/__init__.py,sha256=5OE0TwPQjvpB3lBPuEwKrhjR_1ehOWxB98OJP_n32MA,20
40
40
  ramifice/fields/general/choice_group.py,sha256=TBJblwH8mB71wd1z2jcSs28H-zx3JZVBfkk4YCE1-pI,916
@@ -44,7 +44,7 @@ ramifice/fields/general/file_group.py,sha256=n45KfPzFI_l5hXoKkPDG0Q-0mdC2obExV-3
44
44
  ramifice/fields/general/number_group.py,sha256=AqlCY-t6JHZ2QVBe7mk5nPt6z8M4VJ_RARRlSBoIxms,715
45
45
  ramifice/fields/general/text_group.py,sha256=6GD2Fe6Toz6zZjAAlcy5rPVCAGh6Yn1ltdIrFg9RF18,1057
46
46
  ramifice/models/__init__.py,sha256=h_QQ5rSJNZ-7kmx7wIFd8E8RmUS94b_x4jdwMbq8zm4,15
47
- ramifice/models/decorator.py,sha256=jOEn-0v0m_tM7mow45-tM-8LADUmwlG2DWZsEuC9S_w,6834
47
+ ramifice/models/decorator.py,sha256=U1ukWWq2voEwvKonQAzihqGYVsoyPrOQ2jDXJZ-UcMc,7251
48
48
  ramifice/models/model.py,sha256=ZvgIbcuSXuAkBl_FofOZXGwWMwlqKB-PwTMyXiqK-Fo,6610
49
49
  ramifice/models/pseudo.py,sha256=PhLQM4zXdaOtTSmNFzwN4twlUmdvA1D_-YOMJtaOIwM,6754
50
50
  ramifice/paladins/__init__.py,sha256=PIP3AXI2KBRXNcLJUF0d7ygJ7VLOAxlhb4HRKQ9MGYY,516
@@ -53,11 +53,11 @@ ramifice/paladins/delete.py,sha256=tw50E98D5eFZ7gHGnh_8ztUB1LeTeWWKZvIcQqlgbF8,3
53
53
  ramifice/paladins/password.py,sha256=w1XWh3bsncH1VTVjCLxyKI2waxMvltwcsPWW3V9Ib84,3027
54
54
  ramifice/paladins/refrash.py,sha256=fw-9x_NKGzreipBt_F9KF6FTsYm9hSzfq4ubi1FHYrQ,1065
55
55
  ramifice/paladins/save.py,sha256=EG0_v-imCQPax2pJIAXPoQcSL99g8abY2EeGJMvXBW4,3864
56
- ramifice/paladins/tools.py,sha256=QxxwZILyBLcAiDUN48KE99c_cHVjG-VYilEsIinRhEU,2641
56
+ ramifice/paladins/tools.py,sha256=fn1gve72oFRWDmFw9ESe5wd6bY5av5qyrrvWf0tw8Ns,2703
57
57
  ramifice/paladins/validation.py,sha256=gcEJXIEPu1g7Z54vl14YTs5rCmxOEYsgQH1usFfyy7k,1730
58
58
  ramifice/paladins/groups/__init__.py,sha256=hpqmWLsYAMvZHAbmMXluQSqLhkHOSTUAgLHyTM1LTYI,472
59
59
  ramifice/paladins/groups/bool_group.py,sha256=oJc9mw9KGrnK_Pj7uXixYYQAJphcXLr_xSQv3PMUlcU,792
60
- ramifice/paladins/groups/choice_group.py,sha256=96MOiOn-dD_GgYOCBjkG_Gct-mEF35z1TSCzEkyXK5o,1768
60
+ ramifice/paladins/groups/choice_group.py,sha256=NsOdPMHtnK-ITnSQxEteCeTFhk7MrJJ3wIcDEeOgodU,1791
61
61
  ramifice/paladins/groups/date_group.py,sha256=B9usKKrHX16F1ckik60Xkub1tawgNENSHTk5Rt-K96k,3741
62
62
  ramifice/paladins/groups/file_group.py,sha256=-xgtrLOTlKXc71Mnbu3I_LbYnTEd8OprnhtYcJbaDtg,2932
63
63
  ramifice/paladins/groups/id_group.py,sha256=q5BeoM1e7mZmX0zVgemva9K-9ihJKBrX8kxvMh-uhhQ,1268
@@ -65,7 +65,7 @@ ramifice/paladins/groups/img_group.py,sha256=RoD_QnW0F0DAzrGQmRi8jMZnWy2IomlFn6A
65
65
  ramifice/paladins/groups/num_group.py,sha256=6jT7nfIiVoQTlI2UdkcQOlHIDYjllFqSH7Nb87uJgzg,2274
66
66
  ramifice/paladins/groups/pass_group.py,sha256=YU5a-NwohEutoEx2N5JmGfg8uPiYiW0XJ8XYsOih6eA,1859
67
67
  ramifice/paladins/groups/slug_group.py,sha256=joBB5litljbv2h5JKEMzF71s_DKMWH6nzgThLiLZx2E,2307
68
- ramifice/paladins/groups/text_group.py,sha256=rUWNaAPkTMMlRf6A-6nQAa4HCj6NBTjPKIYaEKSOUL4,4305
68
+ ramifice/paladins/groups/text_group.py,sha256=iftXyyqloQr9yJki__d0LwCEeerkcFquDHyI5rB8fio,4348
69
69
  ramifice/utils/__init__.py,sha256=xixHoOX4ja5jIUZemem1qn4k4aonv3G3Q76azQK_pkU,43
70
70
  ramifice/utils/errors.py,sha256=iuhq7fzpUmsOyeXeg2fJjta8yAuqlXLKsZVMpfUhtHE,1901
71
71
  ramifice/utils/fixtures.py,sha256=qsv9cg06lc82XaRlhI1j5vftmOQTTwOcDXCg_lnpK04,3159
@@ -79,7 +79,7 @@ ramifice/utils/mixins/add_valid.py,sha256=TLOObedzXNA9eCylfAVbVCqIKE5sV-P5AdIN7a
79
79
  ramifice/utils/mixins/hooks.py,sha256=33jvJRhfnJeL2Hd_YFXk3M_7wjqHaByU2wRjKyboL6s,914
80
80
  ramifice/utils/mixins/indexing.py,sha256=Z0427HoaVRyNmSNN8Fx0mSICgAKV-gDdu3iR5qYUEbs,329
81
81
  ramifice/utils/mixins/json_converter.py,sha256=WhigXyDAV-FfILaZuwvRFRIk0D90Rv3dG5t-mv5fVyc,1107
82
- ramifice-0.4.10.dist-info/METADATA,sha256=KO9Jhk80L6Y9NajuZQ37nBSWxJt1XRHcyTGO2VFbuWY,21787
83
- ramifice-0.4.10.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
84
- ramifice-0.4.10.dist-info/licenses/LICENSE,sha256=LrEL0aTZx90HDwFUQCJutORiDjJL9AnuVvCtspXIqt4,1095
85
- ramifice-0.4.10.dist-info/RECORD,,
82
+ ramifice-0.4.12.dist-info/METADATA,sha256=u46bs5blVVIv_vh4jrW5ERLTFe7z8O_11-U-VK7UvLg,21965
83
+ ramifice-0.4.12.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
84
+ ramifice-0.4.12.dist-info/licenses/LICENSE,sha256=LrEL0aTZx90HDwFUQCJutORiDjJL9AnuVvCtspXIqt4,1095
85
+ ramifice-0.4.12.dist-info/RECORD,,