GeneralManager 0.19.0__py3-none-any.whl → 0.19.2__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.
Potentially problematic release.
This version of GeneralManager might be problematic. Click here for more details.
- general_manager/_types/api.py +4 -4
- general_manager/_types/bucket.py +4 -4
- general_manager/_types/cache.py +6 -6
- general_manager/_types/factory.py +35 -35
- general_manager/_types/general_manager.py +9 -9
- general_manager/_types/interface.py +5 -5
- general_manager/_types/manager.py +4 -4
- general_manager/_types/measurement.py +1 -1
- general_manager/_types/permission.py +3 -3
- general_manager/_types/utils.py +12 -12
- general_manager/api/graphql.py +118 -95
- general_manager/api/mutation.py +9 -9
- general_manager/api/property.py +4 -4
- general_manager/apps.py +31 -31
- general_manager/bucket/{baseBucket.py → base_bucket.py} +5 -5
- general_manager/bucket/{calculationBucket.py → calculation_bucket.py} +10 -10
- general_manager/bucket/{databaseBucket.py → database_bucket.py} +16 -19
- general_manager/bucket/{groupBucket.py → group_bucket.py} +8 -8
- general_manager/cache/{cacheDecorator.py → cache_decorator.py} +5 -5
- general_manager/cache/{cacheTracker.py → cache_tracker.py} +1 -1
- general_manager/cache/{dependencyIndex.py → dependency_index.py} +1 -1
- general_manager/cache/{modelDependencyCollector.py → model_dependency_collector.py} +4 -4
- general_manager/cache/signals.py +1 -1
- general_manager/factory/{autoFactory.py → auto_factory.py} +24 -19
- general_manager/factory/factories.py +10 -13
- general_manager/factory/{factoryMethods.py → factory_methods.py} +19 -17
- general_manager/interface/{baseInterface.py → base_interface.py} +29 -21
- general_manager/interface/{calculationInterface.py → calculation_interface.py} +10 -10
- general_manager/interface/{databaseBasedInterface.py → database_based_interface.py} +42 -42
- general_manager/interface/{databaseInterface.py → database_interface.py} +15 -15
- general_manager/interface/models.py +3 -3
- general_manager/interface/{readOnlyInterface.py → read_only_interface.py} +12 -12
- general_manager/manager/{generalManager.py → general_manager.py} +11 -11
- general_manager/manager/{groupManager.py → group_manager.py} +6 -6
- general_manager/manager/input.py +1 -1
- general_manager/manager/meta.py +15 -15
- general_manager/measurement/measurement.py +3 -3
- general_manager/permission/{basePermission.py → base_permission.py} +21 -21
- general_manager/permission/{managerBasedPermission.py → manager_based_permission.py} +21 -21
- general_manager/permission/{mutationPermission.py → mutation_permission.py} +12 -12
- general_manager/permission/{permissionChecks.py → permission_checks.py} +2 -2
- general_manager/permission/{permissionDataManager.py → permission_data_manager.py} +6 -6
- general_manager/permission/utils.py +6 -6
- general_manager/public_api_registry.py +75 -66
- general_manager/rule/handler.py +2 -2
- general_manager/rule/rule.py +17 -11
- general_manager/utils/{filterParser.py → filter_parser.py} +3 -3
- general_manager/utils/{jsonEncoder.py → json_encoder.py} +1 -1
- general_manager/utils/{makeCacheKey.py → make_cache_key.py} +1 -1
- general_manager/utils/{noneToZero.py → none_to_zero.py} +1 -1
- general_manager/utils/{pathMapping.py → path_mapping.py} +14 -14
- general_manager/utils/testing.py +14 -14
- {generalmanager-0.19.0.dist-info → generalmanager-0.19.2.dist-info}/METADATA +1 -1
- generalmanager-0.19.2.dist-info/RECORD +77 -0
- generalmanager-0.19.0.dist-info/RECORD +0 -77
- /general_manager/measurement/{measurementField.py → measurement_field.py} +0 -0
- /general_manager/permission/{fileBasedPermission.py → file_based_permission.py} +0 -0
- /general_manager/utils/{argsToKwargs.py → args_to_kwargs.py} +0 -0
- /general_manager/utils/{formatString.py → format_string.py} +0 -0
- {generalmanager-0.19.0.dist-info → generalmanager-0.19.2.dist-info}/WHEEL +0 -0
- {generalmanager-0.19.0.dist-info → generalmanager-0.19.2.dist-info}/licenses/LICENSE +0 -0
- {generalmanager-0.19.0.dist-info → generalmanager-0.19.2.dist-info}/top_level.txt +0 -0
|
@@ -7,10 +7,10 @@ from django.db import models
|
|
|
7
7
|
from datetime import datetime, date, time, timedelta
|
|
8
8
|
from django.utils import timezone
|
|
9
9
|
from general_manager.measurement.measurement import Measurement
|
|
10
|
-
from general_manager.measurement.
|
|
10
|
+
from general_manager.measurement.measurement_field import MeasurementField
|
|
11
11
|
from decimal import Decimal
|
|
12
|
-
from general_manager.factory.
|
|
13
|
-
from general_manager.interface.
|
|
12
|
+
from general_manager.factory.auto_factory import AutoFactory
|
|
13
|
+
from general_manager.interface.base_interface import (
|
|
14
14
|
InterfaceBase,
|
|
15
15
|
classPostCreationMethod,
|
|
16
16
|
classPreCreationMethod,
|
|
@@ -23,11 +23,11 @@ from general_manager.interface.baseInterface import (
|
|
|
23
23
|
AttributeTypedDict,
|
|
24
24
|
)
|
|
25
25
|
from general_manager.manager.input import Input
|
|
26
|
-
from general_manager.bucket.
|
|
26
|
+
from general_manager.bucket.database_bucket import DatabaseBucket
|
|
27
27
|
from general_manager.interface.models import (
|
|
28
28
|
GeneralManagerBasisModel,
|
|
29
29
|
GeneralManagerModel,
|
|
30
|
-
|
|
30
|
+
get_full_clean_methode,
|
|
31
31
|
)
|
|
32
32
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
|
33
33
|
|
|
@@ -76,9 +76,9 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
76
76
|
"""
|
|
77
77
|
super().__init__(*args, **kwargs)
|
|
78
78
|
self.pk = self.identification["id"]
|
|
79
|
-
self._instance: MODEL_TYPE = self.
|
|
79
|
+
self._instance: MODEL_TYPE = self.get_data(search_date)
|
|
80
80
|
|
|
81
|
-
def
|
|
81
|
+
def get_data(self, search_date: datetime | None = None) -> MODEL_TYPE:
|
|
82
82
|
"""
|
|
83
83
|
Fetch the underlying model instance, optionally as of a historical date.
|
|
84
84
|
|
|
@@ -95,13 +95,13 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
95
95
|
if timezone.is_naive(search_date):
|
|
96
96
|
search_date = timezone.make_aware(search_date)
|
|
97
97
|
if search_date <= timezone.now() - timedelta(seconds=5):
|
|
98
|
-
historical = self.
|
|
98
|
+
historical = self.get_historical_record(instance, search_date)
|
|
99
99
|
if historical is not None:
|
|
100
100
|
instance = historical
|
|
101
101
|
return instance
|
|
102
102
|
|
|
103
103
|
@staticmethod
|
|
104
|
-
def
|
|
104
|
+
def __parse_kwargs(**kwargs: Any) -> dict[str, Any]:
|
|
105
105
|
"""
|
|
106
106
|
Convert keyword arguments into ORM-friendly values.
|
|
107
107
|
|
|
@@ -111,7 +111,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
111
111
|
Returns:
|
|
112
112
|
dict[str, Any]: Arguments ready to be passed to Django ORM methods.
|
|
113
113
|
"""
|
|
114
|
-
from general_manager.manager.
|
|
114
|
+
from general_manager.manager.general_manager import GeneralManager
|
|
115
115
|
|
|
116
116
|
parsed_kwargs: dict[str, Any] = {}
|
|
117
117
|
for key, value in kwargs.items():
|
|
@@ -135,12 +135,12 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
135
135
|
DatabaseBucket: Bucket wrapping the filtered queryset.
|
|
136
136
|
"""
|
|
137
137
|
|
|
138
|
-
kwargs = cls.
|
|
138
|
+
kwargs = cls.__parse_kwargs(**kwargs)
|
|
139
139
|
|
|
140
140
|
return DatabaseBucket(
|
|
141
141
|
cls._model.objects.filter(**kwargs),
|
|
142
142
|
cls._parent_class,
|
|
143
|
-
cls.
|
|
143
|
+
cls.__create_filter_definitions(**kwargs),
|
|
144
144
|
)
|
|
145
145
|
|
|
146
146
|
@classmethod
|
|
@@ -154,16 +154,16 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
154
154
|
Returns:
|
|
155
155
|
DatabaseBucket: Bucket wrapping the excluded queryset.
|
|
156
156
|
"""
|
|
157
|
-
kwargs = cls.
|
|
157
|
+
kwargs = cls.__parse_kwargs(**kwargs)
|
|
158
158
|
|
|
159
159
|
return DatabaseBucket(
|
|
160
160
|
cls._model.objects.exclude(**kwargs),
|
|
161
161
|
cls._parent_class,
|
|
162
|
-
cls.
|
|
162
|
+
cls.__create_filter_definitions(**kwargs),
|
|
163
163
|
)
|
|
164
164
|
|
|
165
165
|
@staticmethod
|
|
166
|
-
def
|
|
166
|
+
def __create_filter_definitions(**kwargs: Any) -> dict[str, Any]:
|
|
167
167
|
"""
|
|
168
168
|
Build a filter-definition mapping from Django-style kwargs.
|
|
169
169
|
|
|
@@ -179,7 +179,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
179
179
|
return filter_definitions
|
|
180
180
|
|
|
181
181
|
@classmethod
|
|
182
|
-
def
|
|
182
|
+
def get_historical_record(
|
|
183
183
|
cls, instance: MODEL_TYPE, search_date: datetime | None = None
|
|
184
184
|
) -> MODEL_TYPE | None:
|
|
185
185
|
"""
|
|
@@ -196,7 +196,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
196
196
|
return cast(MODEL_TYPE | None, historical)
|
|
197
197
|
|
|
198
198
|
@classmethod
|
|
199
|
-
def
|
|
199
|
+
def get_attribute_types(cls) -> dict[str, AttributeTypedDict]:
|
|
200
200
|
"""
|
|
201
201
|
Builds a mapping of model attribute names to their type metadata for the interface.
|
|
202
202
|
|
|
@@ -232,7 +232,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
232
232
|
models.TimeField: datetime,
|
|
233
233
|
}
|
|
234
234
|
fields: dict[str, AttributeTypedDict] = {}
|
|
235
|
-
field_name_list, to_ignore_list = cls.
|
|
235
|
+
field_name_list, to_ignore_list = cls.handle_custom_fields(cls._model)
|
|
236
236
|
for field_name in field_name_list:
|
|
237
237
|
field = cast(models.Field, getattr(cls._model, field_name))
|
|
238
238
|
fields[field_name] = {
|
|
@@ -243,7 +243,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
243
243
|
"default": field.default,
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
-
for field_name in cls.
|
|
246
|
+
for field_name in cls.__get_model_fields():
|
|
247
247
|
if field_name not in to_ignore_list:
|
|
248
248
|
field = cast(models.Field, getattr(cls._model, field_name).field)
|
|
249
249
|
fields[field_name] = {
|
|
@@ -255,7 +255,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
255
255
|
"default": field.default,
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
-
for field_name in cls.
|
|
258
|
+
for field_name in cls.__get_foreign_key_fields():
|
|
259
259
|
field = cls._model._meta.get_field(field_name)
|
|
260
260
|
if isinstance(field, GenericForeignKey):
|
|
261
261
|
continue
|
|
@@ -281,8 +281,8 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
281
281
|
}
|
|
282
282
|
|
|
283
283
|
for field_name, field_call in [
|
|
284
|
-
*cls.
|
|
285
|
-
*cls.
|
|
284
|
+
*cls.__get_many_to_many_fields(),
|
|
285
|
+
*cls.__get_reverse_relations(),
|
|
286
286
|
]:
|
|
287
287
|
if field_name in fields:
|
|
288
288
|
if field_call not in fields:
|
|
@@ -317,7 +317,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
317
317
|
}
|
|
318
318
|
|
|
319
319
|
@classmethod
|
|
320
|
-
def
|
|
320
|
+
def get_attributes(cls) -> dict[str, Callable[[DBBasedInterface], Any]]:
|
|
321
321
|
"""
|
|
322
322
|
Builds a mapping of attribute names to accessor callables for a DBBasedInterface instance.
|
|
323
323
|
|
|
@@ -329,23 +329,23 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
329
329
|
Raises:
|
|
330
330
|
DuplicateFieldNameError: If a generated attribute name conflicts with an existing attribute name.
|
|
331
331
|
"""
|
|
332
|
-
from general_manager.manager.
|
|
332
|
+
from general_manager.manager.general_manager import GeneralManager
|
|
333
333
|
|
|
334
334
|
field_values: dict[str, Any] = {}
|
|
335
335
|
|
|
336
|
-
field_name_list, to_ignore_list = cls.
|
|
336
|
+
field_name_list, to_ignore_list = cls.handle_custom_fields(cls._model)
|
|
337
337
|
for field_name in field_name_list:
|
|
338
338
|
field_values[field_name] = lambda self, field_name=field_name: getattr(
|
|
339
339
|
self._instance, field_name
|
|
340
340
|
)
|
|
341
341
|
|
|
342
|
-
for field_name in cls.
|
|
342
|
+
for field_name in cls.__get_model_fields():
|
|
343
343
|
if field_name not in to_ignore_list:
|
|
344
344
|
field_values[field_name] = lambda self, field_name=field_name: getattr(
|
|
345
345
|
self._instance, field_name
|
|
346
346
|
)
|
|
347
347
|
|
|
348
|
-
for field_name in cls.
|
|
348
|
+
for field_name in cls.__get_foreign_key_fields():
|
|
349
349
|
related_model = cls._model._meta.get_field(field_name).related_model
|
|
350
350
|
if related_model and hasattr(
|
|
351
351
|
related_model,
|
|
@@ -371,8 +371,8 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
371
371
|
)
|
|
372
372
|
|
|
373
373
|
for field_name, field_call in [
|
|
374
|
-
*cls.
|
|
375
|
-
*cls.
|
|
374
|
+
*cls.__get_many_to_many_fields(),
|
|
375
|
+
*cls.__get_reverse_relations(),
|
|
376
376
|
]:
|
|
377
377
|
if field_name in field_values:
|
|
378
378
|
if field_call not in field_values:
|
|
@@ -415,7 +415,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
415
415
|
return field_values
|
|
416
416
|
|
|
417
417
|
@staticmethod
|
|
418
|
-
def
|
|
418
|
+
def handle_custom_fields(
|
|
419
419
|
model: Type[models.Model] | models.Model,
|
|
420
420
|
) -> tuple[list[str], list[str]]:
|
|
421
421
|
"""
|
|
@@ -429,7 +429,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
429
429
|
"""
|
|
430
430
|
field_name_list: list[str] = []
|
|
431
431
|
to_ignore_list: list[str] = []
|
|
432
|
-
for field_name in DBBasedInterface.
|
|
432
|
+
for field_name in DBBasedInterface._get_custom_fields(model):
|
|
433
433
|
to_ignore_list.append(f"{field_name}_value")
|
|
434
434
|
to_ignore_list.append(f"{field_name}_unit")
|
|
435
435
|
field_name_list.append(field_name)
|
|
@@ -437,7 +437,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
437
437
|
return field_name_list, to_ignore_list
|
|
438
438
|
|
|
439
439
|
@staticmethod
|
|
440
|
-
def
|
|
440
|
+
def _get_custom_fields(model: Type[models.Model] | models.Model) -> list[str]:
|
|
441
441
|
"""
|
|
442
442
|
Return names of fields declared directly on the model class.
|
|
443
443
|
|
|
@@ -454,7 +454,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
454
454
|
]
|
|
455
455
|
|
|
456
456
|
@classmethod
|
|
457
|
-
def
|
|
457
|
+
def __get_model_fields(cls) -> list[str]:
|
|
458
458
|
"""Return names of non-relational fields defined on the model."""
|
|
459
459
|
return [
|
|
460
460
|
field.name
|
|
@@ -463,7 +463,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
463
463
|
]
|
|
464
464
|
|
|
465
465
|
@classmethod
|
|
466
|
-
def
|
|
466
|
+
def __get_foreign_key_fields(cls) -> list[str]:
|
|
467
467
|
"""Return names of foreign-key and one-to-one relations on the model."""
|
|
468
468
|
return [
|
|
469
469
|
field.name
|
|
@@ -472,7 +472,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
472
472
|
]
|
|
473
473
|
|
|
474
474
|
@classmethod
|
|
475
|
-
def
|
|
475
|
+
def __get_many_to_many_fields(cls) -> list[tuple[str, str]]:
|
|
476
476
|
"""Return (field_name, accessor_name) tuples for many-to-many fields."""
|
|
477
477
|
return [
|
|
478
478
|
(field.name, field.name)
|
|
@@ -481,7 +481,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
481
481
|
]
|
|
482
482
|
|
|
483
483
|
@classmethod
|
|
484
|
-
def
|
|
484
|
+
def __get_reverse_relations(cls) -> list[tuple[str, str]]:
|
|
485
485
|
"""Return (field_name, accessor_name) tuples for reverse one-to-many relations."""
|
|
486
486
|
return [
|
|
487
487
|
(field.name, f"{field.name}_set")
|
|
@@ -490,7 +490,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
490
490
|
]
|
|
491
491
|
|
|
492
492
|
@staticmethod
|
|
493
|
-
def
|
|
493
|
+
def _pre_create(
|
|
494
494
|
name: generalManagerClassName,
|
|
495
495
|
attrs: attributes,
|
|
496
496
|
interface: interfaceBaseClass,
|
|
@@ -539,7 +539,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
539
539
|
if meta_class and rules:
|
|
540
540
|
model._meta.rules = rules # type: ignore[attr-defined]
|
|
541
541
|
# add full_clean method
|
|
542
|
-
model.full_clean =
|
|
542
|
+
model.full_clean = get_full_clean_methode(model) # type: ignore[assignment]
|
|
543
543
|
# Determine interface type
|
|
544
544
|
attrs["_interface_type"] = interface._interface_type
|
|
545
545
|
interface_cls = type(interface.__name__, (interface,), {})
|
|
@@ -562,7 +562,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
562
562
|
return attrs, interface_cls, model
|
|
563
563
|
|
|
564
564
|
@staticmethod
|
|
565
|
-
def
|
|
565
|
+
def _post_create(
|
|
566
566
|
new_class: newlyCreatedGeneralManagerClass,
|
|
567
567
|
interface_class: newlyCreatedInterfaceClass,
|
|
568
568
|
model: relatedClass,
|
|
@@ -581,7 +581,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
581
581
|
model._general_manager_class = new_class # type: ignore
|
|
582
582
|
|
|
583
583
|
@classmethod
|
|
584
|
-
def
|
|
584
|
+
def handle_interface(
|
|
585
585
|
cls,
|
|
586
586
|
) -> tuple[classPreCreationMethod, classPostCreationMethod]:
|
|
587
587
|
"""
|
|
@@ -590,10 +590,10 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
|
590
590
|
Returns:
|
|
591
591
|
tuple[classPreCreationMethod, classPostCreationMethod]: A pair (pre_create, post_create) where `pre_create` is invoked before the manager class is created to allow customization, and `post_create` is invoked after creation to finalize setup.
|
|
592
592
|
"""
|
|
593
|
-
return cls.
|
|
593
|
+
return cls._pre_create, cls._post_create
|
|
594
594
|
|
|
595
595
|
@classmethod
|
|
596
|
-
def
|
|
596
|
+
def get_field_type(cls, field_name: str) -> type:
|
|
597
597
|
"""
|
|
598
598
|
Return the type associated with a given model field name.
|
|
599
599
|
|
|
@@ -8,7 +8,7 @@ from typing import (
|
|
|
8
8
|
)
|
|
9
9
|
from django.db import models, transaction
|
|
10
10
|
from simple_history.utils import update_change_reason # type: ignore
|
|
11
|
-
from general_manager.interface.
|
|
11
|
+
from general_manager.interface.database_based_interface import (
|
|
12
12
|
DBBasedInterface,
|
|
13
13
|
GeneralManagerModel,
|
|
14
14
|
)
|
|
@@ -86,11 +86,11 @@ class DatabaseInterface(DBBasedInterface[GeneralManagerModel]):
|
|
|
86
86
|
ValidationError: If model validation fails during save.
|
|
87
87
|
"""
|
|
88
88
|
model_cls = cast(type[GeneralManagerModel], cls._model)
|
|
89
|
-
cls.
|
|
90
|
-
kwargs, many_to_many_kwargs = cls.
|
|
91
|
-
instance = cls.
|
|
89
|
+
cls._check_for_invalid_kwargs(model_cls, kwargs=kwargs)
|
|
90
|
+
kwargs, many_to_many_kwargs = cls._sort_kwargs(model_cls, kwargs)
|
|
91
|
+
instance = cls.__set_attr_for_write(model_cls(), kwargs)
|
|
92
92
|
pk = cls._save_with_history(instance, creator_id, history_comment)
|
|
93
|
-
cls.
|
|
93
|
+
cls.__set_many_to_many_attributes(instance, many_to_many_kwargs)
|
|
94
94
|
return pk
|
|
95
95
|
|
|
96
96
|
def update(
|
|
@@ -112,11 +112,11 @@ class DatabaseInterface(DBBasedInterface[GeneralManagerModel]):
|
|
|
112
112
|
ValidationError: If model validation fails during save.
|
|
113
113
|
"""
|
|
114
114
|
model_cls = cast(type[GeneralManagerModel], self._model)
|
|
115
|
-
self.
|
|
116
|
-
kwargs, many_to_many_kwargs = self.
|
|
117
|
-
instance = self.
|
|
115
|
+
self._check_for_invalid_kwargs(model_cls, kwargs=kwargs)
|
|
116
|
+
kwargs, many_to_many_kwargs = self._sort_kwargs(model_cls, kwargs)
|
|
117
|
+
instance = self.__set_attr_for_write(model_cls.objects.get(pk=self.pk), kwargs)
|
|
118
118
|
pk = self._save_with_history(instance, creator_id, history_comment)
|
|
119
|
-
self.
|
|
119
|
+
self.__set_many_to_many_attributes(instance, many_to_many_kwargs)
|
|
120
120
|
return pk
|
|
121
121
|
|
|
122
122
|
def deactivate(
|
|
@@ -142,7 +142,7 @@ class DatabaseInterface(DBBasedInterface[GeneralManagerModel]):
|
|
|
142
142
|
return self._save_with_history(instance, creator_id, history_comment)
|
|
143
143
|
|
|
144
144
|
@staticmethod
|
|
145
|
-
def
|
|
145
|
+
def __set_many_to_many_attributes(
|
|
146
146
|
instance: GeneralManagerModel, many_to_many_kwargs: dict[str, list[Any]]
|
|
147
147
|
) -> GeneralManagerModel:
|
|
148
148
|
"""
|
|
@@ -155,7 +155,7 @@ class DatabaseInterface(DBBasedInterface[GeneralManagerModel]):
|
|
|
155
155
|
Returns:
|
|
156
156
|
GeneralManagerModel: Updated instance.
|
|
157
157
|
"""
|
|
158
|
-
from general_manager.manager.
|
|
158
|
+
from general_manager.manager.general_manager import GeneralManager
|
|
159
159
|
|
|
160
160
|
for key, value in many_to_many_kwargs.items():
|
|
161
161
|
if value is None or value is NOT_PROVIDED:
|
|
@@ -173,7 +173,7 @@ class DatabaseInterface(DBBasedInterface[GeneralManagerModel]):
|
|
|
173
173
|
return instance
|
|
174
174
|
|
|
175
175
|
@staticmethod
|
|
176
|
-
def
|
|
176
|
+
def __set_attr_for_write(
|
|
177
177
|
instance: GeneralManagerModel,
|
|
178
178
|
kwargs: dict[str, Any],
|
|
179
179
|
) -> GeneralManagerModel:
|
|
@@ -193,7 +193,7 @@ class DatabaseInterface(DBBasedInterface[GeneralManagerModel]):
|
|
|
193
193
|
InvalidFieldValueError: If setting an attribute raises a `ValueError`.
|
|
194
194
|
InvalidFieldTypeError: If setting an attribute raises a `TypeError`.
|
|
195
195
|
"""
|
|
196
|
-
from general_manager.manager.
|
|
196
|
+
from general_manager.manager.general_manager import GeneralManager
|
|
197
197
|
|
|
198
198
|
for key, value in kwargs.items():
|
|
199
199
|
if isinstance(value, GeneralManager):
|
|
@@ -210,7 +210,7 @@ class DatabaseInterface(DBBasedInterface[GeneralManagerModel]):
|
|
|
210
210
|
return instance
|
|
211
211
|
|
|
212
212
|
@staticmethod
|
|
213
|
-
def
|
|
213
|
+
def _check_for_invalid_kwargs(
|
|
214
214
|
model: Type[models.Model], kwargs: dict[str, Any]
|
|
215
215
|
) -> None:
|
|
216
216
|
"""
|
|
@@ -231,7 +231,7 @@ class DatabaseInterface(DBBasedInterface[GeneralManagerModel]):
|
|
|
231
231
|
raise UnknownFieldError(key, model.__name__)
|
|
232
232
|
|
|
233
233
|
@staticmethod
|
|
234
|
-
def
|
|
234
|
+
def _sort_kwargs(
|
|
235
235
|
model: Type[models.Model], kwargs: dict[Any, Any]
|
|
236
236
|
) -> tuple[dict[str, Any], dict[str, list[Any]]]:
|
|
237
237
|
"""
|
|
@@ -10,13 +10,13 @@ from django.core.exceptions import ValidationError
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
if TYPE_CHECKING:
|
|
13
|
-
from general_manager.manager.
|
|
13
|
+
from general_manager.manager.general_manager import GeneralManager
|
|
14
14
|
from general_manager.rule.rule import Rule
|
|
15
15
|
|
|
16
16
|
modelsModel = TypeVar("modelsModel", bound=models.Model)
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
def
|
|
19
|
+
def get_full_clean_methode(model: Type[models.Model]) -> Callable[..., None]:
|
|
20
20
|
"""
|
|
21
21
|
Return a custom `full_clean` method for a Django model that performs both standard validation and additional rule-based checks.
|
|
22
22
|
|
|
@@ -44,7 +44,7 @@ def getFullCleanMethode(model: Type[models.Model]) -> Callable[..., None]:
|
|
|
44
44
|
rules: list[Rule] = getattr(self._meta, "rules", [])
|
|
45
45
|
for rule in rules:
|
|
46
46
|
if rule.evaluate(self) is False:
|
|
47
|
-
error_message = rule.
|
|
47
|
+
error_message = rule.get_error_message()
|
|
48
48
|
if error_message:
|
|
49
49
|
errors.update(error_message)
|
|
50
50
|
|
|
@@ -5,7 +5,7 @@ import json
|
|
|
5
5
|
|
|
6
6
|
from typing import Type, Any, Callable, TYPE_CHECKING, cast, ClassVar
|
|
7
7
|
from django.db import models, transaction
|
|
8
|
-
from general_manager.interface.
|
|
8
|
+
from general_manager.interface.database_based_interface import (
|
|
9
9
|
DBBasedInterface,
|
|
10
10
|
GeneralManagerBasisModel,
|
|
11
11
|
classPreCreationMethod,
|
|
@@ -19,7 +19,7 @@ from django.core.checks import Warning
|
|
|
19
19
|
import logging
|
|
20
20
|
|
|
21
21
|
if TYPE_CHECKING:
|
|
22
|
-
from general_manager.manager.
|
|
22
|
+
from general_manager.manager.general_manager import GeneralManager
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
logger = logging.getLogger(__name__)
|
|
@@ -86,7 +86,7 @@ class ReadOnlyInterface(DBBasedInterface[GeneralManagerBasisModel]):
|
|
|
86
86
|
_parent_class: ClassVar[Type["GeneralManager"]]
|
|
87
87
|
|
|
88
88
|
@staticmethod
|
|
89
|
-
def
|
|
89
|
+
def get_unique_fields(model: Type[models.Model]) -> set[str]:
|
|
90
90
|
"""
|
|
91
91
|
Determine which fields on the given Django model uniquely identify its instances.
|
|
92
92
|
|
|
@@ -117,7 +117,7 @@ class ReadOnlyInterface(DBBasedInterface[GeneralManagerBasisModel]):
|
|
|
117
117
|
return unique_fields
|
|
118
118
|
|
|
119
119
|
@classmethod
|
|
120
|
-
def
|
|
120
|
+
def sync_data(cls) -> None:
|
|
121
121
|
"""
|
|
122
122
|
Synchronize the Django model with the parent manager's class-level `_data` JSON.
|
|
123
123
|
|
|
@@ -129,7 +129,7 @@ class ReadOnlyInterface(DBBasedInterface[GeneralManagerBasisModel]):
|
|
|
129
129
|
InvalidReadOnlyDataTypeError: If `_data` is neither a string nor a list.
|
|
130
130
|
MissingUniqueFieldError: If the model exposes no unique fields to identify records.
|
|
131
131
|
"""
|
|
132
|
-
if cls.
|
|
132
|
+
if cls.ensure_schema_is_up_to_date(cls._parent_class, cls._model):
|
|
133
133
|
logger.warning(
|
|
134
134
|
f"Schema for ReadOnlyInterface '{cls._parent_class.__name__}' is not up to date."
|
|
135
135
|
)
|
|
@@ -153,7 +153,7 @@ class ReadOnlyInterface(DBBasedInterface[GeneralManagerBasisModel]):
|
|
|
153
153
|
|
|
154
154
|
data_list = cast(list[dict[str, Any]], parsed_data)
|
|
155
155
|
|
|
156
|
-
unique_fields = cls.
|
|
156
|
+
unique_fields = cls.get_unique_fields(model)
|
|
157
157
|
if not unique_fields:
|
|
158
158
|
raise MissingUniqueFieldError(parent_class.__name__)
|
|
159
159
|
|
|
@@ -215,7 +215,7 @@ class ReadOnlyInterface(DBBasedInterface[GeneralManagerBasisModel]):
|
|
|
215
215
|
)
|
|
216
216
|
|
|
217
217
|
@staticmethod
|
|
218
|
-
def
|
|
218
|
+
def ensure_schema_is_up_to_date(
|
|
219
219
|
new_manager_class: Type[GeneralManager], model: Type[models.Model]
|
|
220
220
|
) -> list[Warning]:
|
|
221
221
|
"""
|
|
@@ -286,7 +286,7 @@ class ReadOnlyInterface(DBBasedInterface[GeneralManagerBasisModel]):
|
|
|
286
286
|
return []
|
|
287
287
|
|
|
288
288
|
@staticmethod
|
|
289
|
-
def
|
|
289
|
+
def read_only_post_create(func: Callable[..., Any]) -> Callable[..., Any]:
|
|
290
290
|
"""
|
|
291
291
|
Decorator for post-creation hooks that registers a new manager class as read-only.
|
|
292
292
|
|
|
@@ -311,7 +311,7 @@ class ReadOnlyInterface(DBBasedInterface[GeneralManagerBasisModel]):
|
|
|
311
311
|
return wrapper
|
|
312
312
|
|
|
313
313
|
@staticmethod
|
|
314
|
-
def
|
|
314
|
+
def read_only_pre_create(func: Callable[..., Any]) -> Callable[..., Any]:
|
|
315
315
|
"""
|
|
316
316
|
Decorator for pre-creation hook functions that ensures the base model class is set to `GeneralManagerBasisModel`.
|
|
317
317
|
|
|
@@ -344,7 +344,7 @@ class ReadOnlyInterface(DBBasedInterface[GeneralManagerBasisModel]):
|
|
|
344
344
|
return wrapper
|
|
345
345
|
|
|
346
346
|
@classmethod
|
|
347
|
-
def
|
|
347
|
+
def handle_interface(cls) -> tuple[classPreCreationMethod, classPostCreationMethod]:
|
|
348
348
|
"""
|
|
349
349
|
Return the pre- and post-creation hook methods for integrating the interface with a manager meta-class system.
|
|
350
350
|
|
|
@@ -355,6 +355,6 @@ class ReadOnlyInterface(DBBasedInterface[GeneralManagerBasisModel]):
|
|
|
355
355
|
Returns:
|
|
356
356
|
tuple: The pre-creation and post-creation hook methods for manager class lifecycle integration.
|
|
357
357
|
"""
|
|
358
|
-
return cls.
|
|
359
|
-
cls.
|
|
358
|
+
return cls.read_only_pre_create(cls._pre_create), cls.read_only_post_create(
|
|
359
|
+
cls._post_create
|
|
360
360
|
)
|
|
@@ -3,9 +3,9 @@ from typing import TYPE_CHECKING, Any, Iterator, Self, Type
|
|
|
3
3
|
from general_manager.manager.meta import GeneralManagerMeta
|
|
4
4
|
|
|
5
5
|
from general_manager.api.property import GraphQLProperty
|
|
6
|
-
from general_manager.cache.
|
|
7
|
-
from general_manager.cache.signals import
|
|
8
|
-
from general_manager.bucket.
|
|
6
|
+
from general_manager.cache.cache_tracker import DependencyTracker
|
|
7
|
+
from general_manager.cache.signals import data_change
|
|
8
|
+
from general_manager.bucket.base_bucket import Bucket
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class UnsupportedUnionOperandError(TypeError):
|
|
@@ -22,8 +22,8 @@ class UnsupportedUnionOperandError(TypeError):
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
if TYPE_CHECKING:
|
|
25
|
-
from general_manager.permission.
|
|
26
|
-
from general_manager.interface.
|
|
25
|
+
from general_manager.permission.base_permission import BasePermission
|
|
26
|
+
from general_manager.interface.base_interface import InterfaceBase
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
class GeneralManager(metaclass=GeneralManagerMeta):
|
|
@@ -117,7 +117,7 @@ class GeneralManager(metaclass=GeneralManagerMeta):
|
|
|
117
117
|
yield name, getattr(self, name)
|
|
118
118
|
|
|
119
119
|
@classmethod
|
|
120
|
-
@
|
|
120
|
+
@data_change
|
|
121
121
|
def create(
|
|
122
122
|
cls,
|
|
123
123
|
creator_id: int | None = None,
|
|
@@ -141,13 +141,13 @@ class GeneralManager(metaclass=GeneralManagerMeta):
|
|
|
141
141
|
PermissionError: Propagated if the permission check fails.
|
|
142
142
|
"""
|
|
143
143
|
if not ignore_permission:
|
|
144
|
-
cls.Permission.
|
|
144
|
+
cls.Permission.check_create_permission(kwargs, cls, creator_id)
|
|
145
145
|
identification = cls.Interface.create(
|
|
146
146
|
creator_id=creator_id, history_comment=history_comment, **kwargs
|
|
147
147
|
)
|
|
148
148
|
return cls(identification)
|
|
149
149
|
|
|
150
|
-
@
|
|
150
|
+
@data_change
|
|
151
151
|
def update(
|
|
152
152
|
self,
|
|
153
153
|
creator_id: int | None = None,
|
|
@@ -171,7 +171,7 @@ class GeneralManager(metaclass=GeneralManagerMeta):
|
|
|
171
171
|
PermissionError: Propagated if the permission check fails.
|
|
172
172
|
"""
|
|
173
173
|
if not ignore_permission:
|
|
174
|
-
self.Permission.
|
|
174
|
+
self.Permission.check_update_permission(kwargs, self, creator_id)
|
|
175
175
|
self._interface.update(
|
|
176
176
|
creator_id=creator_id,
|
|
177
177
|
history_comment=history_comment,
|
|
@@ -179,7 +179,7 @@ class GeneralManager(metaclass=GeneralManagerMeta):
|
|
|
179
179
|
)
|
|
180
180
|
return self.__class__(**self.identification)
|
|
181
181
|
|
|
182
|
-
@
|
|
182
|
+
@data_change
|
|
183
183
|
def deactivate(
|
|
184
184
|
self,
|
|
185
185
|
creator_id: int | None = None,
|
|
@@ -201,7 +201,7 @@ class GeneralManager(metaclass=GeneralManagerMeta):
|
|
|
201
201
|
PermissionError: Propagated if the permission check fails.
|
|
202
202
|
"""
|
|
203
203
|
if not ignore_permission:
|
|
204
|
-
self.Permission.
|
|
204
|
+
self.Permission.check_delete_permission(self, creator_id)
|
|
205
205
|
self._interface.deactivate(
|
|
206
206
|
creator_id=creator_id, history_comment=history_comment
|
|
207
207
|
)
|
|
@@ -5,8 +5,8 @@ from typing import Any, Generic, Iterator, Type, cast, get_args
|
|
|
5
5
|
from datetime import datetime, date, time
|
|
6
6
|
from general_manager.api.property import GraphQLProperty
|
|
7
7
|
from general_manager.measurement import Measurement
|
|
8
|
-
from general_manager.manager.
|
|
9
|
-
from general_manager.bucket.
|
|
8
|
+
from general_manager.manager.general_manager import GeneralManager
|
|
9
|
+
from general_manager.bucket.base_bucket import (
|
|
10
10
|
Bucket,
|
|
11
11
|
GeneralManagerType,
|
|
12
12
|
)
|
|
@@ -99,7 +99,7 @@ class GroupManager(Generic[GeneralManagerType]):
|
|
|
99
99
|
Yields:
|
|
100
100
|
tuple[str, Any]: Attribute name and aggregated value pairs.
|
|
101
101
|
"""
|
|
102
|
-
for attribute in self._manager_class.Interface.
|
|
102
|
+
for attribute in self._manager_class.Interface.get_attributes().keys():
|
|
103
103
|
yield attribute, getattr(self, attribute)
|
|
104
104
|
for attribute, attr_value in self._manager_class.__dict__.items():
|
|
105
105
|
if isinstance(attr_value, GraphQLProperty):
|
|
@@ -121,10 +121,10 @@ class GroupManager(Generic[GeneralManagerType]):
|
|
|
121
121
|
if item in self._group_by_value:
|
|
122
122
|
return self._group_by_value[item]
|
|
123
123
|
if item not in self._grouped_data.keys():
|
|
124
|
-
self._grouped_data[item] = self.
|
|
124
|
+
self._grouped_data[item] = self.combine_value(item)
|
|
125
125
|
return self._grouped_data[item]
|
|
126
126
|
|
|
127
|
-
def
|
|
127
|
+
def combine_value(self, item: str) -> Any:
|
|
128
128
|
"""
|
|
129
129
|
Aggregate the values of a named attribute across all records in the group.
|
|
130
130
|
|
|
@@ -140,7 +140,7 @@ class GroupManager(Generic[GeneralManagerType]):
|
|
|
140
140
|
if item == "id":
|
|
141
141
|
return None
|
|
142
142
|
|
|
143
|
-
attribute_types = self._manager_class.Interface.
|
|
143
|
+
attribute_types = self._manager_class.Interface.get_attribute_types()
|
|
144
144
|
attr_info = attribute_types.get(item)
|
|
145
145
|
data_type = attr_info["type"] if attr_info else None
|
|
146
146
|
if data_type is None and item in self._manager_class.__dict__:
|
general_manager/manager/input.py
CHANGED
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
from typing import Iterable, Optional, Callable, List, TypeVar, Generic, Any, Type, cast
|
|
5
5
|
import inspect
|
|
6
6
|
|
|
7
|
-
from general_manager.manager.
|
|
7
|
+
from general_manager.manager.general_manager import GeneralManager
|
|
8
8
|
from datetime import date, datetime
|
|
9
9
|
from general_manager.measurement import Measurement
|
|
10
10
|
|