GeneralManager 0.8.0__tar.gz → 0.9.0__tar.gz
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.
- {generalmanager-0.8.0 → generalmanager-0.9.0}/GeneralManager.egg-info/PKG-INFO +1 -1
- {generalmanager-0.8.0 → generalmanager-0.9.0}/GeneralManager.egg-info/SOURCES.txt +3 -33
- {generalmanager-0.8.0 → generalmanager-0.9.0}/PKG-INFO +1 -1
- {generalmanager-0.8.0 → generalmanager-0.9.0}/pyproject.toml +1 -1
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/apps.py +42 -27
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/interface/databaseBasedInterface.py +12 -73
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/interface/databaseInterface.py +6 -1
- generalmanager-0.9.0/src/general_manager/interface/models.py +88 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/manager/meta.py +16 -8
- generalmanager-0.9.0/src/general_manager/utils/testing.py +124 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/tests/test_settings.py +10 -1
- generalmanager-0.9.0/tests/test_urls.py +1 -0
- generalmanager-0.8.0/tests/test_argsToKwargs.py +0 -45
- generalmanager-0.8.0/tests/test_autoFactory.py +0 -247
- generalmanager-0.8.0/tests/test_baseBucket.py +0 -325
- generalmanager-0.8.0/tests/test_baseInterface.py +0 -273
- generalmanager-0.8.0/tests/test_basePermission.py +0 -146
- generalmanager-0.8.0/tests/test_cacheDecorator.py +0 -396
- generalmanager-0.8.0/tests/test_cacheTracker.py +0 -76
- generalmanager-0.8.0/tests/test_calculationBucket.py +0 -373
- generalmanager-0.8.0/tests/test_calculationInterface.py +0 -124
- generalmanager-0.8.0/tests/test_databaseBasedInterface.py +0 -274
- generalmanager-0.8.0/tests/test_databaseBucket.py +0 -333
- generalmanager-0.8.0/tests/test_databaseInterface.py +0 -181
- generalmanager-0.8.0/tests/test_dependencyIndex.py +0 -967
- generalmanager-0.8.0/tests/test_factories.py +0 -351
- generalmanager-0.8.0/tests/test_factoryMethods.py +0 -228
- generalmanager-0.8.0/tests/test_filterParser.py +0 -196
- generalmanager-0.8.0/tests/test_formatString.py +0 -61
- generalmanager-0.8.0/tests/test_generalManager.py +0 -278
- generalmanager-0.8.0/tests/test_generalManagerMeta.py +0 -582
- generalmanager-0.8.0/tests/test_graph_ql.py +0 -641
- generalmanager-0.8.0/tests/test_groupManager.py +0 -322
- generalmanager-0.8.0/tests/test_input.py +0 -176
- generalmanager-0.8.0/tests/test_jsonEncoder.py +0 -49
- generalmanager-0.8.0/tests/test_makeCacheKey.py +0 -372
- generalmanager-0.8.0/tests/test_managerBasedPermission.py +0 -293
- generalmanager-0.8.0/tests/test_measurement.py +0 -252
- generalmanager-0.8.0/tests/test_measurement_field.py +0 -122
- generalmanager-0.8.0/tests/test_modelDependencyCollector.py +0 -119
- generalmanager-0.8.0/tests/test_noneToZero.py +0 -17
- generalmanager-0.8.0/tests/test_readOnlyInterface.py +0 -370
- generalmanager-0.8.0/tests/test_rule_handler.py +0 -435
- generalmanager-0.8.0/tests/test_rules.py +0 -189
- generalmanager-0.8.0/tests/test_signals.py +0 -109
- {generalmanager-0.8.0 → generalmanager-0.9.0}/GeneralManager.egg-info/dependency_links.txt +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/GeneralManager.egg-info/requires.txt +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/GeneralManager.egg-info/top_level.txt +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/LICENSE +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/README.md +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/setup.cfg +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/__init__.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/api/graphql.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/api/mutation.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/api/property.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/auxiliary/__init__.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/auxiliary/argsToKwargs.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/auxiliary/filterParser.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/auxiliary/formatString.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/auxiliary/jsonEncoder.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/auxiliary/makeCacheKey.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/auxiliary/noneToZero.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/auxiliary/pathMapping.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/bucket/baseBucket.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/bucket/calculationBucket.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/bucket/databaseBucket.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/bucket/groupBucket.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/cache/cacheDecorator.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/cache/cacheTracker.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/cache/dependencyIndex.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/cache/modelDependencyCollector.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/cache/signals.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/factory/__init__.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/factory/autoFactory.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/factory/factories.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/factory/factoryMethods.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/interface/__init__.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/interface/baseInterface.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/interface/calculationInterface.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/interface/readOnlyInterface.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/manager/__init__.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/manager/generalManager.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/manager/groupManager.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/manager/input.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/measurement/__init__.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/measurement/measurement.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/measurement/measurementField.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/permission/__init__.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/permission/basePermission.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/permission/fileBasedPermission.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/permission/managerBasedPermission.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/permission/permissionChecks.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/permission/permissionDataManager.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/rule/__init__.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/rule/handler.py +0 -0
- {generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/rule/rule.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: GeneralManager
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.9.0
|
4
4
|
Summary: Modular Django-based data management framework with ORM, GraphQL, fine-grained permissions, rule validation, calculations and caching.
|
5
5
|
Author-email: Tim Kleindick <tkleindick@yahoo.de>
|
6
6
|
License-Expression: MIT
|
@@ -37,6 +37,7 @@ src/general_manager/interface/baseInterface.py
|
|
37
37
|
src/general_manager/interface/calculationInterface.py
|
38
38
|
src/general_manager/interface/databaseBasedInterface.py
|
39
39
|
src/general_manager/interface/databaseInterface.py
|
40
|
+
src/general_manager/interface/models.py
|
40
41
|
src/general_manager/interface/readOnlyInterface.py
|
41
42
|
src/general_manager/manager/__init__.py
|
42
43
|
src/general_manager/manager/generalManager.py
|
@@ -55,37 +56,6 @@ src/general_manager/permission/permissionDataManager.py
|
|
55
56
|
src/general_manager/rule/__init__.py
|
56
57
|
src/general_manager/rule/handler.py
|
57
58
|
src/general_manager/rule/rule.py
|
58
|
-
|
59
|
-
tests/test_autoFactory.py
|
60
|
-
tests/test_baseBucket.py
|
61
|
-
tests/test_baseInterface.py
|
62
|
-
tests/test_basePermission.py
|
63
|
-
tests/test_cacheDecorator.py
|
64
|
-
tests/test_cacheTracker.py
|
65
|
-
tests/test_calculationBucket.py
|
66
|
-
tests/test_calculationInterface.py
|
67
|
-
tests/test_databaseBasedInterface.py
|
68
|
-
tests/test_databaseBucket.py
|
69
|
-
tests/test_databaseInterface.py
|
70
|
-
tests/test_dependencyIndex.py
|
71
|
-
tests/test_factories.py
|
72
|
-
tests/test_factoryMethods.py
|
73
|
-
tests/test_filterParser.py
|
74
|
-
tests/test_formatString.py
|
75
|
-
tests/test_generalManager.py
|
76
|
-
tests/test_generalManagerMeta.py
|
77
|
-
tests/test_graph_ql.py
|
78
|
-
tests/test_groupManager.py
|
79
|
-
tests/test_input.py
|
80
|
-
tests/test_jsonEncoder.py
|
81
|
-
tests/test_makeCacheKey.py
|
82
|
-
tests/test_managerBasedPermission.py
|
83
|
-
tests/test_measurement.py
|
84
|
-
tests/test_measurement_field.py
|
85
|
-
tests/test_modelDependencyCollector.py
|
86
|
-
tests/test_noneToZero.py
|
87
|
-
tests/test_readOnlyInterface.py
|
88
|
-
tests/test_rule_handler.py
|
89
|
-
tests/test_rules.py
|
59
|
+
src/general_manager/utils/testing.py
|
90
60
|
tests/test_settings.py
|
91
|
-
tests/
|
61
|
+
tests/test_urls.py
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: GeneralManager
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.9.0
|
4
4
|
Summary: Modular Django-based data management framework with ORM, GraphQL, fine-grained permissions, rule validation, calculations and caching.
|
5
5
|
Author-email: Tim Kleindick <tkleindick@yahoo.de>
|
6
6
|
License-Expression: MIT
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "GeneralManager"
|
7
|
-
version = "0.
|
7
|
+
version = "0.9.0"
|
8
8
|
description = "Modular Django-based data management framework with ORM, GraphQL, fine-grained permissions, rule validation, calculations and caching."
|
9
9
|
readme = "README.md"
|
10
10
|
authors = [{ name = "Tim Kleindick", email = "tkleindick@yahoo.de" }]
|
@@ -29,26 +29,32 @@ class GeneralmanagerConfig(AppConfig):
|
|
29
29
|
|
30
30
|
def ready(self):
|
31
31
|
"""
|
32
|
-
|
32
|
+
Performs initialization tasks for the general_manager app when Django starts.
|
33
33
|
|
34
|
-
Sets up
|
34
|
+
Sets up synchronization and schema validation for read-only interfaces, initializes attributes and property accessors for general manager classes, and configures the GraphQL schema and endpoint if enabled in settings.
|
35
35
|
"""
|
36
|
-
self.handleReadOnlyInterface()
|
37
|
-
self.initializeGeneralManagerClasses(
|
36
|
+
self.handleReadOnlyInterface(GeneralManagerMeta.read_only_classes)
|
37
|
+
self.initializeGeneralManagerClasses(
|
38
|
+
GeneralManagerMeta.pending_attribute_initialization,
|
39
|
+
GeneralManagerMeta.all_classes,
|
40
|
+
)
|
38
41
|
if getattr(settings, "AUTOCREATE_GRAPHQL", False):
|
39
|
-
self.handleGraphQL()
|
42
|
+
self.handleGraphQL(GeneralManagerMeta.pending_graphql_interfaces)
|
40
43
|
|
41
|
-
|
44
|
+
@staticmethod
|
45
|
+
def handleReadOnlyInterface(
|
46
|
+
read_only_classes: list[Type[GeneralManager[Any, ReadOnlyInterface]]],
|
47
|
+
):
|
42
48
|
"""
|
43
|
-
Configures synchronization and schema validation for
|
49
|
+
Configures synchronization and schema validation for the provided read-only interface classes.
|
44
50
|
|
45
|
-
|
51
|
+
Ensures that each read-only interface is synchronized before Django management commands run, and registers system checks to validate that their schemas are up to date.
|
46
52
|
"""
|
47
|
-
|
53
|
+
GeneralmanagerConfig.patchReadOnlyInterfaceSync(read_only_classes)
|
48
54
|
from general_manager.interface.readOnlyInterface import ReadOnlyInterface
|
49
55
|
|
50
56
|
logger.debug("starting to register ReadOnlyInterface schema warnings...")
|
51
|
-
for general_manager_class in
|
57
|
+
for general_manager_class in read_only_classes:
|
52
58
|
read_only_interface = cast(
|
53
59
|
Type[ReadOnlyInterface], general_manager_class.Interface
|
54
60
|
)
|
@@ -65,9 +71,9 @@ class GeneralmanagerConfig(AppConfig):
|
|
65
71
|
general_manager_classes: list[Type[GeneralManager[Any, ReadOnlyInterface]]],
|
66
72
|
):
|
67
73
|
"""
|
68
|
-
Monkey-patches Django's management command runner to synchronize
|
74
|
+
Monkey-patches Django's management command runner to synchronize all provided read-only interfaces before executing any management command, except during autoreload subprocesses of 'runserver'.
|
69
75
|
|
70
|
-
For each
|
76
|
+
For each class in `general_manager_classes`, the associated read-only interface's `syncData` method is called prior to command execution, ensuring data consistency before management operations.
|
71
77
|
"""
|
72
78
|
from general_manager.interface.readOnlyInterface import ReadOnlyInterface
|
73
79
|
|
@@ -76,7 +82,7 @@ class GeneralmanagerConfig(AppConfig):
|
|
76
82
|
def run_from_argv_with_sync(self, argv):
|
77
83
|
# Ensure syncData is only called at real run of runserver
|
78
84
|
"""
|
79
|
-
Executes a Django management command, synchronizing all registered read-only interfaces before execution unless running an autoreload subprocess of 'runserver'.
|
85
|
+
Executes a Django management command, synchronizing all registered read-only interfaces before execution unless running in an autoreload subprocess of 'runserver'.
|
80
86
|
|
81
87
|
Parameters:
|
82
88
|
argv (list): Command-line arguments for the management command.
|
@@ -100,18 +106,20 @@ class GeneralmanagerConfig(AppConfig):
|
|
100
106
|
|
101
107
|
BaseCommand.run_from_argv = run_from_argv_with_sync
|
102
108
|
|
103
|
-
|
109
|
+
@staticmethod
|
110
|
+
def initializeGeneralManagerClasses(
|
111
|
+
pending_attribute_initialization: list[Type[GeneralManager]],
|
112
|
+
all_classes: list[Type[GeneralManager]],
|
113
|
+
):
|
104
114
|
"""
|
105
|
-
Initializes attributes and
|
115
|
+
Initializes attributes and establishes dynamic relationships for GeneralManager classes.
|
106
116
|
|
107
|
-
For each pending
|
117
|
+
For each class pending attribute initialization, assigns interface attributes and creates property accessors. Then, for all registered GeneralManager classes, connects input fields referencing other GeneralManager subclasses by adding GraphQL properties to enable filtered access to related objects.
|
108
118
|
"""
|
109
119
|
logger.debug("Initializing GeneralManager classes...")
|
110
120
|
|
111
121
|
logger.debug("starting to create attributes for GeneralManager classes...")
|
112
|
-
for
|
113
|
-
general_manager_class
|
114
|
-
) in GeneralManagerMeta.pending_attribute_initialization:
|
122
|
+
for general_manager_class in pending_attribute_initialization:
|
115
123
|
attributes = general_manager_class.Interface.getAttributes()
|
116
124
|
setattr(general_manager_class, "_attributes", attributes)
|
117
125
|
GeneralManagerMeta.createAtPropertiesForAttributes(
|
@@ -119,7 +127,7 @@ class GeneralmanagerConfig(AppConfig):
|
|
119
127
|
)
|
120
128
|
|
121
129
|
logger.debug("starting to connect inputs to other general manager classes...")
|
122
|
-
for general_manager_class in
|
130
|
+
for general_manager_class in all_classes:
|
123
131
|
attributes = getattr(general_manager_class.Interface, "input_fields", {})
|
124
132
|
for attribute_name, attribute in attributes.items():
|
125
133
|
if isinstance(attribute, Input) and issubclass(
|
@@ -137,12 +145,15 @@ class GeneralmanagerConfig(AppConfig):
|
|
137
145
|
graphQlProperty(func),
|
138
146
|
)
|
139
147
|
|
140
|
-
|
148
|
+
@staticmethod
|
149
|
+
def handleGraphQL(
|
150
|
+
pending_graphql_interfaces: list[Type[GeneralManager]],
|
151
|
+
):
|
141
152
|
"""
|
142
|
-
|
153
|
+
Creates GraphQL interfaces and mutations for the provided general manager classes, builds the GraphQL schema, and registers the GraphQL endpoint in the Django URL configuration.
|
143
154
|
"""
|
144
155
|
logger.debug("Starting to create GraphQL interfaces and mutations...")
|
145
|
-
for general_manager_class in
|
156
|
+
for general_manager_class in pending_graphql_interfaces:
|
146
157
|
GraphQL.createGraphqlInterface(general_manager_class)
|
147
158
|
GraphQL.createGraphqlMutation(general_manager_class)
|
148
159
|
|
@@ -160,18 +171,22 @@ class GeneralmanagerConfig(AppConfig):
|
|
160
171
|
query=GraphQL._query_class,
|
161
172
|
mutation=GraphQL._mutation_class,
|
162
173
|
)
|
163
|
-
|
174
|
+
GeneralmanagerConfig.addGraphqlUrl(schema)
|
164
175
|
|
165
|
-
|
176
|
+
@staticmethod
|
177
|
+
def addGraphqlUrl(schema):
|
166
178
|
"""
|
167
|
-
|
179
|
+
Adds a GraphQL endpoint to the Django URL configuration using the provided schema.
|
180
|
+
|
181
|
+
Parameters:
|
182
|
+
schema: The GraphQL schema to use for the endpoint.
|
168
183
|
|
169
184
|
Raises:
|
170
185
|
Exception: If the ROOT_URLCONF setting is not defined in Django settings.
|
171
186
|
"""
|
172
187
|
logging.debug("Adding GraphQL URL to Django settings...")
|
173
188
|
root_url_conf_path = getattr(settings, "ROOT_URLCONF", None)
|
174
|
-
graph_ql_url = getattr(settings, "GRAPHQL_URL", "graphql
|
189
|
+
graph_ql_url = getattr(settings, "GRAPHQL_URL", "graphql")
|
175
190
|
if not root_url_conf_path:
|
176
191
|
raise Exception("ROOT_URLCONF not found in settings")
|
177
192
|
urlconf = import_module(root_url_conf_path)
|
@@ -23,6 +23,11 @@ from general_manager.interface.baseInterface import (
|
|
23
23
|
)
|
24
24
|
from general_manager.manager.input import Input
|
25
25
|
from general_manager.bucket.databaseBucket import DatabaseBucket
|
26
|
+
from general_manager.interface.models import (
|
27
|
+
GeneralManagerBasisModel,
|
28
|
+
GeneralManagerModel,
|
29
|
+
getFullCleanMethode,
|
30
|
+
)
|
26
31
|
|
27
32
|
if TYPE_CHECKING:
|
28
33
|
from general_manager.manager.generalManager import GeneralManager
|
@@ -32,72 +37,6 @@ if TYPE_CHECKING:
|
|
32
37
|
modelsModel = TypeVar("modelsModel", bound=models.Model)
|
33
38
|
|
34
39
|
|
35
|
-
def getFullCleanMethode(model: Type[models.Model]) -> Callable[..., None]:
|
36
|
-
"""
|
37
|
-
Generates a custom `full_clean` method for a Django model that combines standard validation with additional rule-based checks.
|
38
|
-
|
39
|
-
The returned method first performs Django's built-in model validation, then evaluates any custom rules defined in the model's `_meta.rules` attribute. If any validation or rule fails, a `ValidationError` is raised containing all collected errors.
|
40
|
-
"""
|
41
|
-
|
42
|
-
def full_clean(self: models.Model, *args: Any, **kwargs: Any):
|
43
|
-
errors: dict[str, Any] = {}
|
44
|
-
try:
|
45
|
-
super(model, self).full_clean(*args, **kwargs) # type: ignore
|
46
|
-
except ValidationError as e:
|
47
|
-
errors.update(e.message_dict)
|
48
|
-
|
49
|
-
rules: list[Rule] = getattr(self._meta, "rules")
|
50
|
-
for rule in rules:
|
51
|
-
if not rule.evaluate(self):
|
52
|
-
error_message = rule.getErrorMessage()
|
53
|
-
if error_message:
|
54
|
-
errors.update(error_message)
|
55
|
-
|
56
|
-
if errors:
|
57
|
-
raise ValidationError(errors)
|
58
|
-
|
59
|
-
return full_clean
|
60
|
-
|
61
|
-
|
62
|
-
class GeneralManagerBasisModel(models.Model):
|
63
|
-
_general_manager_class: ClassVar[Type[GeneralManager]]
|
64
|
-
is_active = models.BooleanField(default=True)
|
65
|
-
history = HistoricalRecords(inherit=True)
|
66
|
-
|
67
|
-
class Meta:
|
68
|
-
abstract = True
|
69
|
-
|
70
|
-
|
71
|
-
class GeneralManagerModel(GeneralManagerBasisModel):
|
72
|
-
changed_by = models.ForeignKey(
|
73
|
-
settings.AUTH_USER_MODEL, on_delete=models.PROTECT, null=True, blank=True
|
74
|
-
)
|
75
|
-
changed_by_id: int | None
|
76
|
-
|
77
|
-
@property
|
78
|
-
def _history_user(self) -> AbstractUser | None:
|
79
|
-
"""
|
80
|
-
Gets the user who last modified this model instance, or None if not set.
|
81
|
-
|
82
|
-
Returns:
|
83
|
-
AbstractUser | None: The user who last changed the instance, or None if unavailable.
|
84
|
-
"""
|
85
|
-
return self.changed_by
|
86
|
-
|
87
|
-
@_history_user.setter
|
88
|
-
def _history_user(self, value: AbstractUser) -> None:
|
89
|
-
"""
|
90
|
-
Sets the user responsible for the latest change to the model instance.
|
91
|
-
|
92
|
-
Args:
|
93
|
-
value: The user to associate with the change.
|
94
|
-
"""
|
95
|
-
self.changed_by = value
|
96
|
-
|
97
|
-
class Meta: # type: ignore
|
98
|
-
abstract = True
|
99
|
-
|
100
|
-
|
101
40
|
MODEL_TYPE = TypeVar("MODEL_TYPE", bound=GeneralManagerBasisModel)
|
102
41
|
|
103
42
|
|
@@ -112,9 +51,9 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
112
51
|
**kwargs: dict[str, Any],
|
113
52
|
):
|
114
53
|
"""
|
115
|
-
Initialize the interface
|
54
|
+
Initialize the interface and load the associated model instance.
|
116
55
|
|
117
|
-
If `search_date` is provided,
|
56
|
+
If `search_date` is provided, loads the historical record as of that date; otherwise, loads the current record.
|
118
57
|
"""
|
119
58
|
super().__init__(*args, **kwargs)
|
120
59
|
self.pk = self.identification["id"]
|
@@ -201,10 +140,10 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
201
140
|
@classmethod
|
202
141
|
def getAttributeTypes(cls) -> dict[str, AttributeTypedDict]:
|
203
142
|
"""
|
204
|
-
Return a dictionary mapping attribute names to metadata describing their
|
143
|
+
Return a dictionary mapping attribute names to metadata describing their types and properties.
|
144
|
+
|
145
|
+
The dictionary includes all model fields, custom fields, foreign keys, many-to-many, and reverse relation fields. For each attribute, the metadata specifies its Python type (translated from Django field types when possible), whether it is required, editable, derived, and its default value. For related models with a general manager class, the type is set to that class.
|
205
146
|
|
206
|
-
The returned dictionary includes all model fields, custom fields, foreign keys, many-to-many, and reverse relation fields. For each attribute, the metadata includes its Python type (translated from Django field types when possible), whether it is required, editable, derived, and its default value. For related models with a general manager class, the type is set to that class.
|
207
|
-
|
208
147
|
Returns:
|
209
148
|
dict[str, AttributeTypedDict]: Mapping of attribute names to their type information and metadata.
|
210
149
|
"""
|
@@ -461,9 +400,9 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
|
|
461
400
|
) -> tuple[attributes, interfaceBaseClass, relatedClass]:
|
462
401
|
# Felder aus der Interface-Klasse sammeln
|
463
402
|
"""
|
464
|
-
Dynamically
|
403
|
+
Dynamically generates a Django model class, its associated interface class, and a factory class from an interface definition.
|
465
404
|
|
466
|
-
This method
|
405
|
+
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.
|
467
406
|
|
468
407
|
Parameters:
|
469
408
|
name: The name for the dynamically created model class.
|
{generalmanager-0.8.0 → generalmanager-0.9.0}/src/general_manager/interface/databaseInterface.py
RENAMED
@@ -111,7 +111,12 @@ class DatabaseInterface(DBBasedInterface[GeneralManagerModel]):
|
|
111
111
|
if isinstance(value, GeneralManager):
|
112
112
|
value = value.identification["id"]
|
113
113
|
key = f"{key}_id"
|
114
|
-
|
114
|
+
try:
|
115
|
+
setattr(instance, key, value)
|
116
|
+
except ValueError as e:
|
117
|
+
raise ValueError(f"Invalid value for {key}: {value}") from e
|
118
|
+
except TypeError as e:
|
119
|
+
raise TypeError(f"Type error for {key}: {e}") from e
|
115
120
|
return instance
|
116
121
|
|
117
122
|
@staticmethod
|
@@ -0,0 +1,88 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from typing import Type, ClassVar, Any, Callable, TYPE_CHECKING, TypeVar
|
3
|
+
from django.db import models
|
4
|
+
from django.conf import settings
|
5
|
+
from simple_history.models import HistoricalRecords # type: ignore
|
6
|
+
from django.core.exceptions import ValidationError
|
7
|
+
|
8
|
+
|
9
|
+
if TYPE_CHECKING:
|
10
|
+
from general_manager.manager.generalManager import GeneralManager
|
11
|
+
from django.contrib.auth.models import AbstractUser
|
12
|
+
from general_manager.rule.rule import Rule
|
13
|
+
|
14
|
+
modelsModel = TypeVar("modelsModel", bound=models.Model)
|
15
|
+
|
16
|
+
|
17
|
+
def getFullCleanMethode(model: Type[models.Model]) -> Callable[..., None]:
|
18
|
+
"""
|
19
|
+
Return a custom `full_clean` method for a Django model that performs both standard validation and additional rule-based checks.
|
20
|
+
|
21
|
+
The generated method first applies Django's built-in model validation, then evaluates custom rules defined in the model's `_meta.rules` attribute. If any validation or rule fails, it raises a `ValidationError` containing all collected errors.
|
22
|
+
|
23
|
+
Parameters:
|
24
|
+
model (Type[models.Model]): The Django model class for which to generate the custom `full_clean` method.
|
25
|
+
|
26
|
+
Returns:
|
27
|
+
Callable[..., None]: A `full_clean` method that can be assigned to the model class.
|
28
|
+
"""
|
29
|
+
|
30
|
+
def full_clean(self: models.Model, *args: Any, **kwargs: Any):
|
31
|
+
"""
|
32
|
+
Performs full validation on the model instance, including both standard Django validation and custom rule-based checks.
|
33
|
+
|
34
|
+
Aggregates errors from Django's built-in validation and any additional rules defined in the model's `_meta.rules` attribute. Raises a `ValidationError` containing all collected errors if any validation or rule check fails.
|
35
|
+
"""
|
36
|
+
errors: dict[str, Any] = {}
|
37
|
+
try:
|
38
|
+
super(model, self).full_clean(*args, **kwargs) # type: ignore
|
39
|
+
except ValidationError as e:
|
40
|
+
errors.update(e.message_dict)
|
41
|
+
|
42
|
+
rules: list[Rule] = getattr(self._meta, "rules")
|
43
|
+
for rule in rules:
|
44
|
+
if not rule.evaluate(self):
|
45
|
+
error_message = rule.getErrorMessage()
|
46
|
+
if error_message:
|
47
|
+
errors.update(error_message)
|
48
|
+
|
49
|
+
if errors:
|
50
|
+
raise ValidationError(errors)
|
51
|
+
|
52
|
+
return full_clean
|
53
|
+
|
54
|
+
|
55
|
+
class GeneralManagerBasisModel(models.Model):
|
56
|
+
_general_manager_class: ClassVar[Type[GeneralManager]]
|
57
|
+
is_active = models.BooleanField(default=True)
|
58
|
+
history = HistoricalRecords(inherit=True)
|
59
|
+
|
60
|
+
class Meta:
|
61
|
+
abstract = True
|
62
|
+
|
63
|
+
|
64
|
+
class GeneralManagerModel(GeneralManagerBasisModel):
|
65
|
+
changed_by = models.ForeignKey(
|
66
|
+
settings.AUTH_USER_MODEL, on_delete=models.PROTECT, null=True, blank=True
|
67
|
+
)
|
68
|
+
changed_by_id: int | None
|
69
|
+
|
70
|
+
@property
|
71
|
+
def _history_user(self) -> AbstractUser | None:
|
72
|
+
"""
|
73
|
+
Returns the user who last modified this model instance, or None if no user is set.
|
74
|
+
"""
|
75
|
+
return self.changed_by
|
76
|
+
|
77
|
+
@_history_user.setter
|
78
|
+
def _history_user(self, value: AbstractUser) -> None:
|
79
|
+
"""
|
80
|
+
Set the user responsible for the most recent change to the model instance.
|
81
|
+
|
82
|
+
Parameters:
|
83
|
+
value (AbstractUser): The user to associate with the latest modification.
|
84
|
+
"""
|
85
|
+
self.changed_by = value
|
86
|
+
|
87
|
+
class Meta: # type: ignore
|
88
|
+
abstract = True
|
@@ -25,9 +25,9 @@ class GeneralManagerMeta(type):
|
|
25
25
|
|
26
26
|
def __new__(mcs, name: str, bases: tuple[type, ...], attrs: dict[str, Any]) -> type:
|
27
27
|
"""
|
28
|
-
|
28
|
+
Creates a new class using the metaclass, integrating interface hooks and registering the class for attribute initialization and tracking.
|
29
29
|
|
30
|
-
If the class definition includes an
|
30
|
+
If the class definition includes an `Interface` attribute, validates it as a subclass of `InterfaceBase`, applies pre- and post-creation hooks from the interface, and registers the resulting class for attribute initialization and management. Regardless of interface presence, the new class is tracked for pending GraphQL interface creation.
|
31
31
|
|
32
32
|
Returns:
|
33
33
|
The newly created class, potentially augmented with interface integration and registration logic.
|
@@ -37,10 +37,10 @@ class GeneralManagerMeta(type):
|
|
37
37
|
mcs, name: str, bases: tuple[type, ...], attrs: dict[str, Any]
|
38
38
|
) -> Type[GeneralManager]:
|
39
39
|
"""
|
40
|
-
Create a new
|
40
|
+
Create a new GeneralManager class using the standard metaclass instantiation process.
|
41
41
|
|
42
42
|
Returns:
|
43
|
-
The newly created
|
43
|
+
The newly created GeneralManager subclass.
|
44
44
|
"""
|
45
45
|
return super().__new__(mcs, name, bases, attrs)
|
46
46
|
|
@@ -59,7 +59,6 @@ class GeneralManagerMeta(type):
|
|
59
59
|
|
60
60
|
else:
|
61
61
|
new_class = createNewGeneralManagerClass(mcs, name, bases, attrs)
|
62
|
-
|
63
62
|
if getattr(settings, "AUTOCREATE_GRAPHQL", False):
|
64
63
|
mcs.pending_graphql_interfaces.append(new_class)
|
65
64
|
|
@@ -69,13 +68,22 @@ class GeneralManagerMeta(type):
|
|
69
68
|
def createAtPropertiesForAttributes(
|
70
69
|
attributes: Iterable[str], new_class: Type[GeneralManager]
|
71
70
|
):
|
72
|
-
|
73
71
|
"""
|
74
|
-
Dynamically assigns property descriptors to a class for
|
72
|
+
Dynamically assigns property descriptors to a class for each specified attribute name.
|
75
73
|
|
76
|
-
For each attribute
|
74
|
+
For each attribute, creates a descriptor that:
|
75
|
+
- Returns the field type from the class's interface when accessed on the class.
|
76
|
+
- Retrieves the value from the instance's `_attributes` dictionary when accessed on an instance.
|
77
|
+
- Invokes the attribute with the instance's interface if it is callable.
|
78
|
+
- Raises `AttributeError` if the attribute is missing or if an error occurs during callable invocation.
|
77
79
|
"""
|
80
|
+
|
78
81
|
def desciptorMethod(attr_name: str, new_class: type):
|
82
|
+
"""
|
83
|
+
Creates a property descriptor for an attribute, enabling dynamic access and callable resolution.
|
84
|
+
|
85
|
+
When accessed on the class, returns the field type from the associated interface. When accessed on an instance, retrieves the attribute value from the instance's `_attributes` dictionary, invoking it with the instance's interface if the value is callable. Raises `AttributeError` if the attribute is missing or if a callable attribute raises an exception.
|
86
|
+
"""
|
79
87
|
class Descriptor(Generic[GeneralManagerType]):
|
80
88
|
def __init__(self, attr_name: str, new_class: Type[GeneralManager]):
|
81
89
|
self.attr_name = attr_name
|
@@ -0,0 +1,124 @@
|
|
1
|
+
from graphene_django.utils.testing import GraphQLTransactionTestCase
|
2
|
+
from general_manager.apps import GeneralmanagerConfig
|
3
|
+
from importlib import import_module
|
4
|
+
from django.db import connection
|
5
|
+
from django.conf import settings
|
6
|
+
from typing import cast
|
7
|
+
from django.db import models
|
8
|
+
from general_manager.manager.generalManager import GeneralManager
|
9
|
+
from general_manager.api.graphql import GraphQL
|
10
|
+
from django.apps import apps as global_apps
|
11
|
+
|
12
|
+
|
13
|
+
_original_get_app = global_apps.get_containing_app_config
|
14
|
+
|
15
|
+
|
16
|
+
def createFallbackGetApp(fallback_app: str):
|
17
|
+
"""
|
18
|
+
Creates a fallback function for getting the app config, which returns the specified fallback app if the original lookup fails.
|
19
|
+
|
20
|
+
Parameters:
|
21
|
+
fallback_app (str): The name of the app to return if the original lookup fails.
|
22
|
+
|
23
|
+
Returns:
|
24
|
+
function: A function that attempts to get the app config for a given object name, falling back to the specified app if not found.
|
25
|
+
"""
|
26
|
+
|
27
|
+
def _fallback_get_app(object_name: str):
|
28
|
+
cfg = _original_get_app(object_name)
|
29
|
+
if cfg is not None:
|
30
|
+
return cfg
|
31
|
+
try:
|
32
|
+
return global_apps.get_app_config(fallback_app)
|
33
|
+
except LookupError:
|
34
|
+
return None
|
35
|
+
|
36
|
+
return _fallback_get_app
|
37
|
+
|
38
|
+
|
39
|
+
def _default_graphql_url_clear():
|
40
|
+
"""
|
41
|
+
Removes the first URL pattern for the GraphQL view from the project's root URL configuration.
|
42
|
+
|
43
|
+
This function searches the root URL patterns for a pattern whose callback is a `GraphQLView` and removes it, effectively clearing the default GraphQL endpoint from the URL configuration.
|
44
|
+
"""
|
45
|
+
urlconf = import_module(settings.ROOT_URLCONF)
|
46
|
+
for pattern in urlconf.urlpatterns:
|
47
|
+
if (
|
48
|
+
hasattr(pattern, "callback")
|
49
|
+
and hasattr(pattern.callback, "view_class")
|
50
|
+
and pattern.callback.view_class.__name__ == "GraphQLView"
|
51
|
+
):
|
52
|
+
urlconf.urlpatterns.remove(pattern)
|
53
|
+
break
|
54
|
+
|
55
|
+
|
56
|
+
class GMTestCaseMeta(type):
|
57
|
+
"""
|
58
|
+
Metaclass that wraps setUpClass: first calls user-defined setup,
|
59
|
+
then performs GM environment initialization, then super().setUpClass().
|
60
|
+
"""
|
61
|
+
|
62
|
+
def __new__(mcs, name, bases, attrs):
|
63
|
+
"""
|
64
|
+
Creates a new test case class with a customized setUpClass that prepares the database schema and GraphQL environment for GeneralManager integration tests.
|
65
|
+
|
66
|
+
The generated setUpClass method resets GraphQL class registries, invokes any user-defined setUpClass, clears default GraphQL URL patterns, creates missing database tables for specified GeneralManager classes and their history models, initializes GeneralManager and GraphQL configurations, and finally calls the original GraphQLTransactionTestCase setUpClass.
|
67
|
+
"""
|
68
|
+
user_setup = attrs.get("setUpClass")
|
69
|
+
fallback_app = attrs.get("fallback_app", "general_manager")
|
70
|
+
# MERKE dir das echte GraphQLTransactionTestCase.setUpClass
|
71
|
+
base_setup = GraphQLTransactionTestCase.setUpClass
|
72
|
+
|
73
|
+
def wrapped_setUpClass(cls):
|
74
|
+
"""
|
75
|
+
Performs comprehensive setup for a test case class, initializing GraphQL and GeneralManager environments and ensuring required database tables exist.
|
76
|
+
|
77
|
+
This method resets internal GraphQL registries, invokes any user-defined setup, removes default GraphQL URL patterns, creates missing database tables for models and their history associated with specified GeneralManager classes, initializes GeneralManager and GraphQL configurations, and finally calls the base test case setup.
|
78
|
+
"""
|
79
|
+
GraphQL._query_class = None
|
80
|
+
GraphQL._mutation_class = None
|
81
|
+
GraphQL._mutations = {}
|
82
|
+
GraphQL._query_fields = {}
|
83
|
+
GraphQL.graphql_type_registry = {}
|
84
|
+
GraphQL.graphql_filter_type_registry = {}
|
85
|
+
|
86
|
+
if fallback_app is not None:
|
87
|
+
global_apps.get_containing_app_config = createFallbackGetApp(
|
88
|
+
fallback_app
|
89
|
+
)
|
90
|
+
|
91
|
+
# 1) user-defined setUpClass (if any)
|
92
|
+
if user_setup:
|
93
|
+
user_setup.__func__(cls)
|
94
|
+
# 2) clear URL patterns
|
95
|
+
_default_graphql_url_clear()
|
96
|
+
# 3) register models & create tables
|
97
|
+
existing = connection.introspection.table_names()
|
98
|
+
with connection.schema_editor() as editor:
|
99
|
+
for manager_class in cls.general_manager_classes:
|
100
|
+
model_class = cast(
|
101
|
+
type[models.Model], manager_class.Interface._model # type: ignore
|
102
|
+
)
|
103
|
+
if model_class._meta.db_table not in existing:
|
104
|
+
editor.create_model(model_class)
|
105
|
+
editor.create_model(model_class.history.model) # type: ignore
|
106
|
+
# 4) GM & GraphQL initialization
|
107
|
+
GeneralmanagerConfig.initializeGeneralManagerClasses(
|
108
|
+
cls.general_manager_classes, cls.general_manager_classes
|
109
|
+
)
|
110
|
+
GeneralmanagerConfig.handleReadOnlyInterface(cls.read_only_classes)
|
111
|
+
GeneralmanagerConfig.handleGraphQL(cls.general_manager_classes)
|
112
|
+
# 5) GraphQLTransactionTestCase.setUpClass
|
113
|
+
base_setup.__func__(cls)
|
114
|
+
|
115
|
+
attrs["setUpClass"] = classmethod(wrapped_setUpClass)
|
116
|
+
return super().__new__(mcs, name, bases, attrs)
|
117
|
+
|
118
|
+
|
119
|
+
class GeneralManagerTransactionTestCase(
|
120
|
+
GraphQLTransactionTestCase, metaclass=GMTestCaseMeta
|
121
|
+
):
|
122
|
+
general_manager_classes: list[type[GeneralManager]] = []
|
123
|
+
read_only_classes: list[type[GeneralManager]] = []
|
124
|
+
fallback_app: str | None = "general_manager"
|
@@ -4,6 +4,7 @@ DEBUG = True
|
|
4
4
|
INSTALLED_APPS = [
|
5
5
|
"django.contrib.auth",
|
6
6
|
"django.contrib.contenttypes",
|
7
|
+
"django.contrib.sessions",
|
7
8
|
# deine App-Package(s):
|
8
9
|
"general_manager", # falls du pip install -e . genutzt hast
|
9
10
|
]
|
@@ -16,4 +17,12 @@ DATABASES = {
|
|
16
17
|
}
|
17
18
|
|
18
19
|
# Alle weiteren von deinem Code abgefragten Settings
|
19
|
-
AUTOCREATE_GRAPHQL =
|
20
|
+
AUTOCREATE_GRAPHQL = True
|
21
|
+
SESSION_ENGINE = "django.contrib.sessions.backends.signed_cookies"
|
22
|
+
ROOT_URLCONF = "tests.test_urls"
|
23
|
+
|
24
|
+
MIDDLEWARE = [
|
25
|
+
# ggf. noch andere Middleware …
|
26
|
+
"django.contrib.sessions.middleware.SessionMiddleware",
|
27
|
+
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
28
|
+
]
|
@@ -0,0 +1 @@
|
|
1
|
+
urlpatterns = []
|