GeneralManager 0.14.0__py3-none-any.whl → 0.15.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- general_manager/__init__.py +49 -0
- general_manager/api/__init__.py +36 -0
- general_manager/api/graphql.py +92 -43
- general_manager/api/mutation.py +35 -10
- general_manager/api/property.py +26 -3
- general_manager/apps.py +23 -16
- general_manager/bucket/__init__.py +32 -0
- general_manager/bucket/baseBucket.py +76 -64
- general_manager/bucket/calculationBucket.py +188 -108
- general_manager/bucket/databaseBucket.py +130 -49
- general_manager/bucket/groupBucket.py +113 -60
- general_manager/cache/__init__.py +38 -0
- general_manager/cache/cacheDecorator.py +29 -17
- general_manager/cache/cacheTracker.py +34 -15
- general_manager/cache/dependencyIndex.py +117 -33
- general_manager/cache/modelDependencyCollector.py +17 -8
- general_manager/cache/signals.py +17 -6
- general_manager/factory/__init__.py +34 -5
- general_manager/factory/autoFactory.py +57 -60
- general_manager/factory/factories.py +39 -14
- general_manager/factory/factoryMethods.py +38 -1
- general_manager/interface/__init__.py +36 -0
- general_manager/interface/baseInterface.py +71 -27
- general_manager/interface/calculationInterface.py +18 -10
- general_manager/interface/databaseBasedInterface.py +102 -71
- general_manager/interface/databaseInterface.py +66 -20
- general_manager/interface/models.py +10 -4
- general_manager/interface/readOnlyInterface.py +44 -30
- general_manager/manager/__init__.py +36 -3
- general_manager/manager/generalManager.py +73 -47
- general_manager/manager/groupManager.py +72 -17
- general_manager/manager/input.py +23 -15
- general_manager/manager/meta.py +53 -53
- general_manager/measurement/__init__.py +37 -2
- general_manager/measurement/measurement.py +135 -58
- general_manager/measurement/measurementField.py +161 -61
- general_manager/permission/__init__.py +32 -1
- general_manager/permission/basePermission.py +29 -12
- general_manager/permission/managerBasedPermission.py +32 -26
- general_manager/permission/mutationPermission.py +32 -3
- general_manager/permission/permissionChecks.py +9 -1
- general_manager/permission/permissionDataManager.py +49 -15
- general_manager/permission/utils.py +14 -3
- general_manager/rule/__init__.py +27 -1
- general_manager/rule/handler.py +90 -5
- general_manager/rule/rule.py +40 -27
- general_manager/utils/__init__.py +44 -2
- general_manager/utils/argsToKwargs.py +17 -9
- general_manager/utils/filterParser.py +29 -30
- general_manager/utils/formatString.py +2 -0
- general_manager/utils/jsonEncoder.py +14 -1
- general_manager/utils/makeCacheKey.py +18 -12
- general_manager/utils/noneToZero.py +8 -6
- general_manager/utils/pathMapping.py +92 -29
- general_manager/utils/public_api.py +49 -0
- general_manager/utils/testing.py +135 -69
- {generalmanager-0.14.0.dist-info → generalmanager-0.15.0.dist-info}/METADATA +38 -4
- generalmanager-0.15.0.dist-info/RECORD +62 -0
- generalmanager-0.15.0.dist-info/licenses/LICENSE +21 -0
- generalmanager-0.14.0.dist-info/RECORD +0 -58
- generalmanager-0.14.0.dist-info/licenses/LICENSE +0 -29
- {generalmanager-0.14.0.dist-info → generalmanager-0.15.0.dist-info}/WHEEL +0 -0
- {generalmanager-0.14.0.dist-info → generalmanager-0.15.0.dist-info}/top_level.txt +0 -0
@@ -1,8 +1,11 @@
|
|
1
|
+
"""Database-backed interface implementation for GeneralManager classes."""
|
2
|
+
|
1
3
|
from __future__ import annotations
|
2
4
|
from typing import Type, Any, Callable, TYPE_CHECKING, TypeVar, Generic, cast
|
3
5
|
from django.db import models
|
4
6
|
|
5
|
-
from datetime import datetime, timedelta
|
7
|
+
from datetime import datetime, date, time, timedelta
|
8
|
+
from django.utils import timezone
|
6
9
|
from general_manager.measurement.measurement import Measurement
|
7
10
|
from general_manager.measurement.measurementField import MeasurementField
|
8
11
|
from decimal import Decimal
|
@@ -37,42 +40,64 @@ MODEL_TYPE = TypeVar("MODEL_TYPE", bound=GeneralManagerBasisModel)
|
|
37
40
|
|
38
41
|
|
39
42
|
class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
43
|
+
"""Interface implementation that persists data using Django ORM models."""
|
44
|
+
|
40
45
|
_model: Type[MODEL_TYPE]
|
41
46
|
input_fields: dict[str, Input] = {"id": Input(int)}
|
42
47
|
|
43
48
|
def __init__(
|
44
49
|
self,
|
45
|
-
*args:
|
50
|
+
*args: Any,
|
46
51
|
search_date: datetime | None = None,
|
47
52
|
**kwargs: Any,
|
48
|
-
):
|
53
|
+
) -> None:
|
49
54
|
"""
|
50
|
-
|
55
|
+
Build the interface and hydrate the underlying model instance.
|
56
|
+
|
57
|
+
Parameters:
|
58
|
+
*args (list[Any]): Positional identification arguments forwarded to the parent interface.
|
59
|
+
search_date (datetime | None): When provided, load historical data for the given timestamp.
|
60
|
+
**kwargs (Any): Keyword identification arguments forwarded to the parent interface.
|
51
61
|
|
52
|
-
|
62
|
+
Returns:
|
63
|
+
None
|
53
64
|
"""
|
54
65
|
super().__init__(*args, **kwargs)
|
55
66
|
self.pk = self.identification["id"]
|
56
|
-
self._instance = self.getData(search_date)
|
67
|
+
self._instance: MODEL_TYPE = self.getData(search_date)
|
57
68
|
|
58
|
-
def getData(self, search_date: datetime | None = None) ->
|
69
|
+
def getData(self, search_date: datetime | None = None) -> MODEL_TYPE:
|
59
70
|
"""
|
60
|
-
|
71
|
+
Fetch the underlying model instance, optionally as of a historical date.
|
72
|
+
|
73
|
+
Parameters:
|
74
|
+
search_date (datetime | None): When provided, retrieve the state closest to this timestamp.
|
61
75
|
|
62
|
-
|
76
|
+
Returns:
|
77
|
+
MODEL_TYPE: Current or historical instance matching the primary key.
|
63
78
|
"""
|
64
79
|
model = self._model
|
65
|
-
instance = model.objects.get(pk=self.pk)
|
66
|
-
if search_date
|
67
|
-
|
80
|
+
instance = cast(MODEL_TYPE, model.objects.get(pk=self.pk))
|
81
|
+
if search_date is not None:
|
82
|
+
# Normalize to aware datetime if needed
|
83
|
+
if timezone.is_naive(search_date):
|
84
|
+
search_date = timezone.make_aware(search_date)
|
85
|
+
if search_date <= timezone.now() - timedelta(seconds=5):
|
86
|
+
historical = self.getHistoricalRecord(instance, search_date)
|
87
|
+
if historical is not None:
|
88
|
+
instance = historical
|
68
89
|
return instance
|
69
90
|
|
70
91
|
@staticmethod
|
71
92
|
def __parseKwargs(**kwargs: Any) -> dict[str, Any]:
|
72
93
|
"""
|
73
|
-
|
94
|
+
Convert keyword arguments into ORM-friendly values.
|
74
95
|
|
75
|
-
|
96
|
+
Parameters:
|
97
|
+
**kwargs (Any): Filter or update arguments potentially containing manager instances.
|
98
|
+
|
99
|
+
Returns:
|
100
|
+
dict[str, Any]: Arguments ready to be passed to Django ORM methods.
|
76
101
|
"""
|
77
102
|
from general_manager.manager.generalManager import GeneralManager
|
78
103
|
|
@@ -89,13 +114,13 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
89
114
|
@classmethod
|
90
115
|
def filter(cls, **kwargs: Any) -> DatabaseBucket:
|
91
116
|
"""
|
92
|
-
|
117
|
+
Return a bucket of model instances filtered by the provided lookups.
|
93
118
|
|
94
|
-
|
95
|
-
**kwargs:
|
119
|
+
Parameters:
|
120
|
+
**kwargs (Any): Django-style filter expressions.
|
96
121
|
|
97
122
|
Returns:
|
98
|
-
|
123
|
+
DatabaseBucket: Bucket wrapping the filtered queryset.
|
99
124
|
"""
|
100
125
|
|
101
126
|
kwargs = cls.__parseKwargs(**kwargs)
|
@@ -109,13 +134,13 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
109
134
|
@classmethod
|
110
135
|
def exclude(cls, **kwargs: Any) -> DatabaseBucket:
|
111
136
|
"""
|
112
|
-
|
137
|
+
Return a bucket excluding model instances that match the provided lookups.
|
113
138
|
|
114
|
-
|
115
|
-
**kwargs:
|
139
|
+
Parameters:
|
140
|
+
**kwargs (Any): Django-style exclusion expressions.
|
116
141
|
|
117
142
|
Returns:
|
118
|
-
|
143
|
+
DatabaseBucket: Bucket wrapping the excluded queryset.
|
119
144
|
"""
|
120
145
|
kwargs = cls.__parseKwargs(**kwargs)
|
121
146
|
|
@@ -128,13 +153,13 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
128
153
|
@staticmethod
|
129
154
|
def __createFilterDefinitions(**kwargs: Any) -> dict[str, Any]:
|
130
155
|
"""
|
131
|
-
|
156
|
+
Build a filter-definition mapping from Django-style kwargs.
|
132
157
|
|
133
|
-
|
134
|
-
**kwargs:
|
158
|
+
Parameters:
|
159
|
+
**kwargs (Any): Filter expressions provided by the caller.
|
135
160
|
|
136
161
|
Returns:
|
137
|
-
|
162
|
+
dict[str, Any]: Mapping of filter names to their values.
|
138
163
|
"""
|
139
164
|
filter_definitions: dict[str, Any] = {}
|
140
165
|
for key, value in kwargs.items():
|
@@ -143,19 +168,20 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
143
168
|
|
144
169
|
@classmethod
|
145
170
|
def getHistoricalRecord(
|
146
|
-
cls, instance:
|
147
|
-
) ->
|
171
|
+
cls, instance: MODEL_TYPE, search_date: datetime | None = None
|
172
|
+
) -> MODEL_TYPE | None:
|
148
173
|
"""
|
149
174
|
Retrieves the most recent historical record of a model instance at or before a specified date.
|
150
175
|
|
151
|
-
|
152
|
-
instance:
|
153
|
-
search_date
|
176
|
+
Parameters:
|
177
|
+
instance (MODEL_TYPE): Model instance whose history is queried.
|
178
|
+
search_date (datetime | None): Cutoff datetime used to select the historical record.
|
154
179
|
|
155
180
|
Returns:
|
156
|
-
|
181
|
+
MODEL_TYPE | None: Historical instance as of the specified date, if available.
|
157
182
|
"""
|
158
|
-
|
183
|
+
historical = instance.history.filter(history_date__lte=search_date).last() # type: ignore[attr-defined]
|
184
|
+
return cast(MODEL_TYPE | None, historical)
|
159
185
|
|
160
186
|
@classmethod
|
161
187
|
def getAttributeTypes(cls) -> dict[str, AttributeTypedDict]:
|
@@ -363,11 +389,13 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
363
389
|
model: Type[models.Model] | models.Model,
|
364
390
|
) -> tuple[list[str], list[str]]:
|
365
391
|
"""
|
366
|
-
|
392
|
+
Identify custom fields on a model and related helper fields to ignore.
|
393
|
+
|
394
|
+
Parameters:
|
395
|
+
model (type[models.Model] | models.Model): Model class or instance to inspect.
|
367
396
|
|
368
397
|
Returns:
|
369
|
-
|
370
|
-
(typically suffixed with '_value' and '_unit') that should be ignored.
|
398
|
+
tuple[list[str], list[str]]: Names of custom fields and associated helper fields to ignore.
|
371
399
|
"""
|
372
400
|
field_name_list: list[str] = []
|
373
401
|
to_ignore_list: list[str] = []
|
@@ -381,13 +409,13 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
381
409
|
@staticmethod
|
382
410
|
def _getCustomFields(model: Type[models.Model] | models.Model) -> list[str]:
|
383
411
|
"""
|
384
|
-
Return
|
412
|
+
Return names of fields declared directly on the model class.
|
385
413
|
|
386
414
|
Parameters:
|
387
|
-
model
|
415
|
+
model (type[models.Model] | models.Model): Model class or instance to inspect.
|
388
416
|
|
389
417
|
Returns:
|
390
|
-
|
418
|
+
list[str]: Field names declared as class attributes.
|
391
419
|
"""
|
392
420
|
return [
|
393
421
|
field.name
|
@@ -397,11 +425,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
397
425
|
|
398
426
|
@classmethod
|
399
427
|
def __getModelFields(cls) -> list[str]:
|
400
|
-
"""
|
401
|
-
Return a list of field names for the model that are neither many-to-many nor related fields.
|
402
|
-
|
403
|
-
Fields representing many-to-many relationships or relations to other models are excluded from the result.
|
404
|
-
"""
|
428
|
+
"""Return names of non-relational fields defined on the model."""
|
405
429
|
return [
|
406
430
|
field.name
|
407
431
|
for field in cls._model._meta.get_fields()
|
@@ -410,9 +434,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
410
434
|
|
411
435
|
@classmethod
|
412
436
|
def __getForeignKeyFields(cls) -> list[str]:
|
413
|
-
"""
|
414
|
-
Return a list of field names for all foreign key and one-to-one relations on the model, excluding generic foreign keys.
|
415
|
-
"""
|
437
|
+
"""Return names of foreign-key and one-to-one relations on the model."""
|
416
438
|
return [
|
417
439
|
field.name
|
418
440
|
for field in cls._model._meta.get_fields()
|
@@ -421,11 +443,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
421
443
|
|
422
444
|
@classmethod
|
423
445
|
def __getManyToManyFields(cls) -> list[tuple[str, str]]:
|
424
|
-
"""
|
425
|
-
Return a list of tuples representing all many-to-many fields on the model.
|
426
|
-
|
427
|
-
Each tuple contains the field name twice. Fields that are generic foreign keys are excluded.
|
428
|
-
"""
|
446
|
+
"""Return (field_name, accessor_name) tuples for many-to-many fields."""
|
429
447
|
return [
|
430
448
|
(field.name, field.name)
|
431
449
|
for field in cls._model._meta.get_fields()
|
@@ -434,11 +452,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
434
452
|
|
435
453
|
@classmethod
|
436
454
|
def __getReverseRelations(cls) -> list[tuple[str, str]]:
|
437
|
-
"""
|
438
|
-
Return a list of reverse one-to-many relations for the model, excluding generic foreign keys.
|
439
|
-
|
440
|
-
Each tuple contains the related field's name and its default related accessor name (e.g., `fieldname_set`).
|
441
|
-
"""
|
455
|
+
"""Return (field_name, accessor_name) tuples for reverse one-to-many relations."""
|
442
456
|
return [
|
443
457
|
(field.name, f"{field.name}_set")
|
444
458
|
for field in cls._model._meta.get_fields()
|
@@ -450,37 +464,37 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
450
464
|
name: generalManagerClassName,
|
451
465
|
attrs: attributes,
|
452
466
|
interface: interfaceBaseClass,
|
453
|
-
base_model_class=GeneralManagerModel,
|
467
|
+
base_model_class: type[GeneralManagerBasisModel] = GeneralManagerModel,
|
454
468
|
) -> tuple[attributes, interfaceBaseClass, relatedClass]:
|
455
|
-
#
|
469
|
+
# Collect fields defined directly on the interface class
|
456
470
|
"""
|
457
471
|
Dynamically generates a Django model class, its associated interface class, and a factory class from an interface definition.
|
458
472
|
|
459
473
|
This method collects fields and metadata from the provided interface class, creates a new Django model inheriting from the specified base model class, attaches custom validation rules if present, and constructs corresponding interface and factory classes. The updated attributes dictionary, the new interface class, and the newly created model class are returned for integration into the general manager framework.
|
460
474
|
|
461
475
|
Parameters:
|
462
|
-
name:
|
463
|
-
attrs:
|
464
|
-
interface:
|
465
|
-
base_model_class:
|
476
|
+
name (generalManagerClassName): Name for the dynamically created model class.
|
477
|
+
attrs (attributes): Attribute dictionary updated with interface and factory definitions.
|
478
|
+
interface (interfaceBaseClass): Interface definition used to derive the model.
|
479
|
+
base_model_class (type[GeneralManagerBasisModel]): Base Django model class (defaults to GeneralManagerModel).
|
466
480
|
|
467
481
|
Returns:
|
468
|
-
tuple
|
482
|
+
tuple[attributes, interfaceBaseClass, relatedClass]: Updated attributes, interface class, and the generated model.
|
469
483
|
"""
|
470
484
|
model_fields: dict[str, Any] = {}
|
471
485
|
meta_class = None
|
472
486
|
for attr_name, attr_value in interface.__dict__.items():
|
473
487
|
if not attr_name.startswith("__"):
|
474
488
|
if attr_name == "Meta" and isinstance(attr_value, type):
|
475
|
-
# Meta
|
489
|
+
# Store the Meta class definition for later use
|
476
490
|
meta_class = attr_value
|
477
491
|
elif attr_name == "Factory":
|
478
|
-
#
|
492
|
+
# Do not register the factory on the model
|
479
493
|
pass
|
480
494
|
else:
|
481
495
|
model_fields[attr_name] = attr_value
|
482
496
|
model_fields["__module__"] = attrs.get("__module__")
|
483
|
-
# Meta
|
497
|
+
# Attach the Meta class or create a default one
|
484
498
|
rules: list[Rule] | None = None
|
485
499
|
if meta_class:
|
486
500
|
model_fields["Meta"] = meta_class
|
@@ -489,19 +503,22 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
489
503
|
rules = getattr(meta_class, "rules")
|
490
504
|
delattr(meta_class, "rules")
|
491
505
|
|
492
|
-
#
|
493
|
-
model =
|
506
|
+
# Create the concrete Django model dynamically
|
507
|
+
model = cast(
|
508
|
+
type[GeneralManagerBasisModel],
|
509
|
+
type(name, (base_model_class,), model_fields),
|
510
|
+
)
|
494
511
|
if meta_class and rules:
|
495
512
|
setattr(model._meta, "rules", rules)
|
496
513
|
# full_clean Methode hinzufügen
|
497
|
-
model
|
514
|
+
setattr(model, "full_clean", getFullCleanMethode(model))
|
498
515
|
# Interface-Typ bestimmen
|
499
516
|
attrs["_interface_type"] = interface._interface_type
|
500
517
|
interface_cls = type(interface.__name__, (interface,), {})
|
501
518
|
setattr(interface_cls, "_model", model)
|
502
519
|
attrs["Interface"] = interface_cls
|
503
520
|
|
504
|
-
#
|
521
|
+
# Build the associated factory class
|
505
522
|
factory_definition = getattr(interface, "Factory", None)
|
506
523
|
factory_attributes: dict[str, Any] = {}
|
507
524
|
if factory_definition:
|
@@ -526,6 +543,11 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
526
543
|
Finalizes the setup of dynamically created classes by linking the interface and model to the new general manager class.
|
527
544
|
|
528
545
|
This method sets the `_parent_class` attribute on the interface class and attaches the new general manager class to the model via the `_general_manager_class` attribute.
|
546
|
+
|
547
|
+
Parameters:
|
548
|
+
new_class (newlyCreatedGeneralManagerClass): Generated GeneralManager subclass.
|
549
|
+
interface_class (newlyCreatedInterfaceClass): Concrete interface class created for the model.
|
550
|
+
model (relatedClass): Django model linked to the manager.
|
529
551
|
"""
|
530
552
|
interface_class._parent_class = new_class
|
531
553
|
setattr(model, "_general_manager_class", new_class)
|
@@ -538,6 +560,9 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
538
560
|
Returns the pre- and post-creation hooks for initializing the interface.
|
539
561
|
|
540
562
|
The pre-creation method is called before the GeneralManager class is created to allow customization, while the post-creation method is called after creation to finalize setup.
|
563
|
+
|
564
|
+
Returns:
|
565
|
+
tuple[classPreCreationMethod, classPostCreationMethod]: Hooks used during manager class creation.
|
541
566
|
"""
|
542
567
|
return cls._preCreate, cls._postCreate
|
543
568
|
|
@@ -547,6 +572,12 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
547
572
|
Return the type associated with a given model field name.
|
548
573
|
|
549
574
|
If the field is a relation and its related model has a `_general_manager_class` attribute, that class is returned; otherwise, returns the Django field type.
|
575
|
+
|
576
|
+
Parameters:
|
577
|
+
field_name (str): Name of the model field.
|
578
|
+
|
579
|
+
Returns:
|
580
|
+
type: Type or GeneralManager class representing the field.
|
550
581
|
"""
|
551
582
|
field = cls._model._meta.get_field(field_name)
|
552
583
|
if (
|
@@ -1,3 +1,5 @@
|
|
1
|
+
"""Concrete interface providing CRUD operations via Django ORM."""
|
2
|
+
|
1
3
|
from __future__ import annotations
|
2
4
|
from typing import (
|
3
5
|
Type,
|
@@ -13,6 +15,8 @@ from django.db.models import NOT_PROVIDED
|
|
13
15
|
|
14
16
|
|
15
17
|
class DatabaseInterface(DBBasedInterface[GeneralManagerModel]):
|
18
|
+
"""CRUD-capable interface backed by a concrete Django model."""
|
19
|
+
|
16
20
|
_interface_type = "database"
|
17
21
|
|
18
22
|
@classmethod
|
@@ -20,16 +24,19 @@ class DatabaseInterface(DBBasedInterface[GeneralManagerModel]):
|
|
20
24
|
cls, creator_id: int | None, history_comment: str | None = None, **kwargs: Any
|
21
25
|
) -> int:
|
22
26
|
"""
|
23
|
-
Create a new model instance
|
24
|
-
|
25
|
-
Validates input attributes, separates and sets many-to-many relationships, saves the instance with optional creator and history comment, and returns the primary key of the created instance.
|
27
|
+
Create a new model instance and return its primary key.
|
26
28
|
|
27
29
|
Parameters:
|
28
|
-
creator_id (int | None):
|
29
|
-
history_comment (str | None): Optional comment
|
30
|
+
creator_id (int | None): Identifier of the user creating the instance.
|
31
|
+
history_comment (str | None): Optional comment stored in the history log.
|
32
|
+
**kwargs (Any): Field values used to populate the model.
|
30
33
|
|
31
34
|
Returns:
|
32
|
-
int:
|
35
|
+
int: Primary key of the newly created instance.
|
36
|
+
|
37
|
+
Raises:
|
38
|
+
ValueError: If unknown fields are supplied.
|
39
|
+
ValidationError: Propagated when model validation fails.
|
33
40
|
"""
|
34
41
|
cls._checkForInvalidKwargs(cls._model, kwargs=kwargs)
|
35
42
|
kwargs, many_to_many_kwargs = cls._sortKwargs(cls._model, kwargs)
|
@@ -42,14 +49,19 @@ class DatabaseInterface(DBBasedInterface[GeneralManagerModel]):
|
|
42
49
|
self, creator_id: int | None, history_comment: str | None = None, **kwargs: Any
|
43
50
|
) -> int:
|
44
51
|
"""
|
45
|
-
Update the current model instance
|
52
|
+
Update the current model instance and return its primary key.
|
46
53
|
|
47
54
|
Parameters:
|
48
|
-
creator_id (int | None):
|
49
|
-
history_comment (str | None):
|
55
|
+
creator_id (int | None): Identifier of the user performing the update.
|
56
|
+
history_comment (str | None): Optional comment stored in the history log.
|
57
|
+
**kwargs (Any): Field updates applied to the model.
|
50
58
|
|
51
59
|
Returns:
|
52
|
-
int:
|
60
|
+
int: Primary key of the updated instance.
|
61
|
+
|
62
|
+
Raises:
|
63
|
+
ValueError: If unknown fields are supplied.
|
64
|
+
ValidationError: Propagated when model validation fails.
|
53
65
|
"""
|
54
66
|
self._checkForInvalidKwargs(self._model, kwargs=kwargs)
|
55
67
|
kwargs, many_to_many_kwargs = self._sortKwargs(self._model, kwargs)
|
@@ -62,14 +74,14 @@ class DatabaseInterface(DBBasedInterface[GeneralManagerModel]):
|
|
62
74
|
self, creator_id: int | None, history_comment: str | None = None
|
63
75
|
) -> int:
|
64
76
|
"""
|
65
|
-
|
77
|
+
Mark the current model instance as inactive and record the change.
|
66
78
|
|
67
79
|
Parameters:
|
68
|
-
creator_id (int | None):
|
69
|
-
history_comment (str | None):
|
80
|
+
creator_id (int | None): Identifier of the user performing the action.
|
81
|
+
history_comment (str | None): Optional comment stored in the history log.
|
70
82
|
|
71
83
|
Returns:
|
72
|
-
int:
|
84
|
+
int: Primary key of the deactivated instance.
|
73
85
|
"""
|
74
86
|
instance = self._model.objects.get(pk=self.pk)
|
75
87
|
instance.is_active = False
|
@@ -84,12 +96,14 @@ class DatabaseInterface(DBBasedInterface[GeneralManagerModel]):
|
|
84
96
|
instance: GeneralManagerModel, many_to_many_kwargs: dict[str, list[Any]]
|
85
97
|
) -> GeneralManagerModel:
|
86
98
|
"""
|
87
|
-
Set many-to-many relationship
|
88
|
-
|
89
|
-
|
90
|
-
|
99
|
+
Set many-to-many relationship values on the provided instance.
|
100
|
+
|
101
|
+
Parameters:
|
102
|
+
instance (GeneralManagerModel): Model instance whose relations are updated.
|
103
|
+
many_to_many_kwargs (dict[str, list[Any]]): Mapping of relation names to values.
|
104
|
+
|
91
105
|
Returns:
|
92
|
-
GeneralManagerModel:
|
106
|
+
GeneralManagerModel: Updated instance.
|
93
107
|
"""
|
94
108
|
from general_manager.manager.generalManager import GeneralManager
|
95
109
|
|
@@ -113,6 +127,16 @@ class DatabaseInterface(DBBasedInterface[GeneralManagerModel]):
|
|
113
127
|
instance: GeneralManagerModel,
|
114
128
|
kwargs: dict[str, Any],
|
115
129
|
) -> GeneralManagerModel:
|
130
|
+
"""
|
131
|
+
Populate non-relational fields on the instance before saving.
|
132
|
+
|
133
|
+
Parameters:
|
134
|
+
instance (GeneralManagerModel): Model instance that will receive the values.
|
135
|
+
kwargs (dict[str, Any]): Key-value pairs to assign to the instance.
|
136
|
+
|
137
|
+
Returns:
|
138
|
+
GeneralManagerModel: Instance with updated attributes.
|
139
|
+
"""
|
116
140
|
from general_manager.manager.generalManager import GeneralManager
|
117
141
|
|
118
142
|
for key, value in kwargs.items():
|
@@ -130,7 +154,19 @@ class DatabaseInterface(DBBasedInterface[GeneralManagerModel]):
|
|
130
154
|
return instance
|
131
155
|
|
132
156
|
@staticmethod
|
133
|
-
def _checkForInvalidKwargs(
|
157
|
+
def _checkForInvalidKwargs(
|
158
|
+
model: Type[models.Model], kwargs: dict[str, Any]
|
159
|
+
) -> None:
|
160
|
+
"""
|
161
|
+
Ensure provided keyword arguments map to known fields or attributes.
|
162
|
+
|
163
|
+
Parameters:
|
164
|
+
model (type[models.Model]): Django model being validated.
|
165
|
+
kwargs (dict[str, Any]): Keyword arguments supplied by the caller.
|
166
|
+
|
167
|
+
Raises:
|
168
|
+
ValueError: If an unknown field name is encountered.
|
169
|
+
"""
|
134
170
|
attributes = vars(model)
|
135
171
|
field_names = {f.name for f in model._meta.get_fields()}
|
136
172
|
for key in kwargs:
|
@@ -142,6 +178,16 @@ class DatabaseInterface(DBBasedInterface[GeneralManagerModel]):
|
|
142
178
|
def _sortKwargs(
|
143
179
|
model: Type[models.Model], kwargs: dict[Any, Any]
|
144
180
|
) -> tuple[dict[str, Any], dict[str, list[Any]]]:
|
181
|
+
"""
|
182
|
+
Split keyword arguments into simple fields and many-to-many relations.
|
183
|
+
|
184
|
+
Parameters:
|
185
|
+
model (type[models.Model]): Model whose relation metadata is inspected.
|
186
|
+
kwargs (dict[Any, Any]): Keyword arguments supplied by the caller.
|
187
|
+
|
188
|
+
Returns:
|
189
|
+
tuple[dict[str, Any], dict[str, list[Any]]]: Tuple containing simple-field kwargs and many-to-many kwargs.
|
190
|
+
"""
|
145
191
|
many_to_many_fields = [field.name for field in model._meta.many_to_many]
|
146
192
|
many_to_many_kwargs: dict[Any, Any] = {}
|
147
193
|
for key, value in list(kwargs.items()):
|
@@ -1,6 +1,9 @@
|
|
1
|
+
"""Django model mixins and helpers backing GeneralManager interfaces."""
|
2
|
+
|
1
3
|
from __future__ import annotations
|
2
4
|
from typing import Type, ClassVar, Any, Callable, TYPE_CHECKING, TypeVar
|
3
5
|
from django.db import models
|
6
|
+
from django.contrib.auth.models import AbstractUser
|
4
7
|
from django.conf import settings
|
5
8
|
from simple_history.models import HistoricalRecords # type: ignore
|
6
9
|
from django.core.exceptions import ValidationError
|
@@ -8,7 +11,6 @@ from django.core.exceptions import ValidationError
|
|
8
11
|
|
9
12
|
if TYPE_CHECKING:
|
10
13
|
from general_manager.manager.generalManager import GeneralManager
|
11
|
-
from django.contrib.auth.models import AbstractUser
|
12
14
|
from general_manager.rule.rule import Rule
|
13
15
|
|
14
16
|
modelsModel = TypeVar("modelsModel", bound=models.Model)
|
@@ -27,7 +29,7 @@ def getFullCleanMethode(model: Type[models.Model]) -> Callable[..., None]:
|
|
27
29
|
Callable[..., None]: A `full_clean` method that can be assigned to the model class.
|
28
30
|
"""
|
29
31
|
|
30
|
-
def full_clean(self: models.Model, *args: Any, **kwargs: Any):
|
32
|
+
def full_clean(self: models.Model, *args: Any, **kwargs: Any) -> None:
|
31
33
|
"""
|
32
34
|
Performs full validation on the model instance, including both standard Django validation and custom rule-based checks.
|
33
35
|
|
@@ -53,6 +55,8 @@ def getFullCleanMethode(model: Type[models.Model]) -> Callable[..., None]:
|
|
53
55
|
|
54
56
|
|
55
57
|
class GeneralManagerBasisModel(models.Model):
|
58
|
+
"""Abstract base model providing shared fields for GeneralManager storage."""
|
59
|
+
|
56
60
|
_general_manager_class: ClassVar[Type[GeneralManager]]
|
57
61
|
is_active = models.BooleanField(default=True)
|
58
62
|
history = HistoricalRecords(inherit=True)
|
@@ -62,6 +66,8 @@ class GeneralManagerBasisModel(models.Model):
|
|
62
66
|
|
63
67
|
|
64
68
|
class GeneralManagerModel(GeneralManagerBasisModel):
|
69
|
+
"""Abstract model adding change-tracking metadata for writeable managers."""
|
70
|
+
|
65
71
|
changed_by = models.ForeignKey(
|
66
72
|
settings.AUTH_USER_MODEL, on_delete=models.PROTECT, null=True, blank=True
|
67
73
|
)
|
@@ -75,14 +81,14 @@ class GeneralManagerModel(GeneralManagerBasisModel):
|
|
75
81
|
return self.changed_by
|
76
82
|
|
77
83
|
@_history_user.setter
|
78
|
-
def _history_user(self, value: AbstractUser) -> None:
|
84
|
+
def _history_user(self, value: AbstractUser | None) -> None:
|
79
85
|
"""
|
80
86
|
Set the user responsible for the most recent change to the model instance.
|
81
87
|
|
82
88
|
Parameters:
|
83
89
|
value (AbstractUser): The user to associate with the latest modification.
|
84
90
|
"""
|
85
|
-
self
|
91
|
+
setattr(self, "changed_by", value)
|
86
92
|
|
87
93
|
class Meta: # type: ignore
|
88
94
|
abstract = True
|