GeneralManager 0.5.2__py3-none-any.whl → 0.6.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/api/graphql.py +2 -1
- general_manager/bucket/baseBucket.py +240 -0
- general_manager/bucket/calculationBucket.py +477 -0
- general_manager/bucket/databaseBucket.py +235 -0
- general_manager/bucket/groupBucket.py +296 -0
- general_manager/cache/modelDependencyCollector.py +5 -8
- general_manager/interface/__init__.py +0 -3
- general_manager/interface/baseInterface.py +13 -103
- general_manager/interface/calculationInterface.py +12 -299
- general_manager/interface/databaseBasedInterface.py +538 -0
- general_manager/interface/databaseInterface.py +16 -655
- general_manager/interface/readOnlyInterface.py +107 -0
- general_manager/manager/generalManager.py +11 -10
- general_manager/manager/groupManager.py +12 -190
- general_manager/manager/meta.py +19 -5
- general_manager/permission/basePermission.py +4 -6
- {generalmanager-0.5.2.dist-info → generalmanager-0.6.0.dist-info}/METADATA +2 -2
- {generalmanager-0.5.2.dist-info → generalmanager-0.6.0.dist-info}/RECORD +21 -15
- {generalmanager-0.5.2.dist-info → generalmanager-0.6.0.dist-info}/WHEEL +0 -0
- {generalmanager-0.5.2.dist-info → generalmanager-0.6.0.dist-info}/licenses/LICENSE +0 -0
- {generalmanager-0.5.2.dist-info → generalmanager-0.6.0.dist-info}/top_level.txt +0 -0
@@ -1,521 +1,14 @@
|
|
1
1
|
from __future__ import annotations
|
2
|
-
import json
|
3
2
|
from typing import (
|
4
3
|
Type,
|
5
|
-
ClassVar,
|
6
4
|
Any,
|
7
|
-
Callable,
|
8
|
-
TYPE_CHECKING,
|
9
|
-
Generator,
|
10
|
-
TypeVar,
|
11
5
|
)
|
12
6
|
from django.db import models, transaction
|
13
|
-
from django.contrib.auth import get_user_model
|
14
7
|
from simple_history.utils import update_change_reason # type: ignore
|
15
|
-
from
|
16
|
-
|
17
|
-
|
18
|
-
from general_manager.measurement.measurementField import MeasurementField
|
19
|
-
from decimal import Decimal
|
20
|
-
from general_manager.factory.autoFactory import AutoFactory
|
21
|
-
from django.core.exceptions import ValidationError
|
22
|
-
from general_manager.interface.baseInterface import (
|
23
|
-
InterfaceBase,
|
24
|
-
Bucket,
|
25
|
-
classPostCreationMethod,
|
26
|
-
classPreCreationMethod,
|
27
|
-
generalManagerClassName,
|
28
|
-
attributes,
|
29
|
-
interfaceBaseClass,
|
30
|
-
newlyCreatedGeneralManagerClass,
|
31
|
-
newlyCreatedInterfaceClass,
|
32
|
-
relatedClass,
|
33
|
-
GeneralManagerType,
|
34
|
-
AttributeTypedDict,
|
8
|
+
from general_manager.interface.databaseBasedInterface import (
|
9
|
+
DBBasedInterface,
|
10
|
+
GeneralManagerModel,
|
35
11
|
)
|
36
|
-
from general_manager.manager.input import Input
|
37
|
-
|
38
|
-
if TYPE_CHECKING:
|
39
|
-
from general_manager.manager.generalManager import GeneralManager
|
40
|
-
from general_manager.manager.meta import GeneralManagerMeta
|
41
|
-
from django.contrib.auth.models import AbstractUser
|
42
|
-
from general_manager.rule.rule import Rule
|
43
|
-
|
44
|
-
modelsModel = TypeVar("modelsModel", bound=models.Model)
|
45
|
-
|
46
|
-
|
47
|
-
def getFullCleanMethode(model: Type[models.Model]) -> Callable[..., None]:
|
48
|
-
def full_clean(self: models.Model, *args: Any, **kwargs: Any):
|
49
|
-
errors: dict[str, Any] = {}
|
50
|
-
try:
|
51
|
-
super(model, self).full_clean(*args, **kwargs) # type: ignore
|
52
|
-
except ValidationError as e:
|
53
|
-
errors.update(e.message_dict)
|
54
|
-
|
55
|
-
rules: list[Rule] = getattr(self._meta, "rules")
|
56
|
-
for rule in rules:
|
57
|
-
if not rule.evaluate(self):
|
58
|
-
error_message = rule.getErrorMessage()
|
59
|
-
if error_message:
|
60
|
-
errors.update(error_message)
|
61
|
-
|
62
|
-
if errors:
|
63
|
-
raise ValidationError(errors)
|
64
|
-
|
65
|
-
return full_clean
|
66
|
-
|
67
|
-
|
68
|
-
class GeneralManagerModel(models.Model):
|
69
|
-
_general_manager_class: ClassVar[Type[GeneralManager]]
|
70
|
-
is_active = models.BooleanField(default=True)
|
71
|
-
changed_by = models.ForeignKey(get_user_model(), on_delete=models.PROTECT)
|
72
|
-
changed_by_id: int
|
73
|
-
history = HistoricalRecords(inherit=True)
|
74
|
-
|
75
|
-
@property
|
76
|
-
def _history_user(self) -> AbstractUser:
|
77
|
-
return self.changed_by
|
78
|
-
|
79
|
-
@_history_user.setter
|
80
|
-
def _history_user(self, value: AbstractUser) -> None:
|
81
|
-
self.changed_by = value
|
82
|
-
|
83
|
-
class Meta:
|
84
|
-
abstract = True
|
85
|
-
|
86
|
-
|
87
|
-
class DBBasedInterface(InterfaceBase):
|
88
|
-
_model: ClassVar[Type[GeneralManagerModel]]
|
89
|
-
input_fields: dict[str, Input] = {"id": Input(int)}
|
90
|
-
|
91
|
-
def __init__(
|
92
|
-
self,
|
93
|
-
*args: list[Any],
|
94
|
-
search_date: datetime | None = None,
|
95
|
-
**kwargs: dict[str, Any],
|
96
|
-
):
|
97
|
-
super().__init__(*args, **kwargs)
|
98
|
-
self.pk = self.identification["id"]
|
99
|
-
self._instance = self.getData(search_date)
|
100
|
-
|
101
|
-
def getData(self, search_date: datetime | None = None) -> GeneralManagerModel:
|
102
|
-
model = self._model
|
103
|
-
instance = model.objects.get(pk=self.pk)
|
104
|
-
if search_date and not search_date > datetime.now() - timedelta(seconds=5):
|
105
|
-
instance = self.getHistoricalRecord(instance, search_date)
|
106
|
-
return instance
|
107
|
-
|
108
|
-
@classmethod
|
109
|
-
def filter(cls, **kwargs: Any) -> DatabaseBucket:
|
110
|
-
return DatabaseBucket(
|
111
|
-
cls._model.objects.filter(**kwargs),
|
112
|
-
cls._parent_class,
|
113
|
-
cls.__createFilterDefinitions(**kwargs),
|
114
|
-
)
|
115
|
-
|
116
|
-
@classmethod
|
117
|
-
def exclude(cls, **kwargs: Any) -> DatabaseBucket:
|
118
|
-
return DatabaseBucket(
|
119
|
-
cls._model.objects.exclude(**kwargs),
|
120
|
-
cls._parent_class,
|
121
|
-
cls.__createFilterDefinitions(**kwargs),
|
122
|
-
)
|
123
|
-
|
124
|
-
@staticmethod
|
125
|
-
def __createFilterDefinitions(**kwargs: Any) -> dict[str, Any]:
|
126
|
-
filter_definitions: dict[str, Any] = {}
|
127
|
-
for key, value in kwargs.items():
|
128
|
-
filter_definitions[key] = value
|
129
|
-
return filter_definitions
|
130
|
-
|
131
|
-
@classmethod
|
132
|
-
def getHistoricalRecord(
|
133
|
-
cls, instance: GeneralManagerModel, search_date: datetime | None = None
|
134
|
-
) -> GeneralManagerModel:
|
135
|
-
return instance.history.filter(history_date__lte=search_date).last() # type: ignore
|
136
|
-
|
137
|
-
@classmethod
|
138
|
-
def getAttributeTypes(cls) -> dict[str, AttributeTypedDict]:
|
139
|
-
TRANSLATION: dict[Type[models.Field[Any, Any]], type] = {
|
140
|
-
models.fields.BigAutoField: int,
|
141
|
-
models.AutoField: int,
|
142
|
-
models.CharField: str,
|
143
|
-
models.TextField: str,
|
144
|
-
models.BooleanField: bool,
|
145
|
-
models.IntegerField: int,
|
146
|
-
models.FloatField: float,
|
147
|
-
models.DateField: datetime,
|
148
|
-
models.DateTimeField: datetime,
|
149
|
-
MeasurementField: Measurement,
|
150
|
-
models.DecimalField: Decimal,
|
151
|
-
models.EmailField: str,
|
152
|
-
models.FileField: str,
|
153
|
-
models.ImageField: str,
|
154
|
-
models.URLField: str,
|
155
|
-
models.TimeField: datetime,
|
156
|
-
}
|
157
|
-
fields: dict[str, AttributeTypedDict] = {}
|
158
|
-
field_name_list, to_ignore_list = cls.handleCustomFields(cls._model)
|
159
|
-
for field_name in field_name_list:
|
160
|
-
field: models.Field = getattr(cls._model, field_name)
|
161
|
-
fields[field_name] = {
|
162
|
-
"type": type(field),
|
163
|
-
"is_required": not field.null,
|
164
|
-
"is_editable": field.editable,
|
165
|
-
"default": field.default,
|
166
|
-
}
|
167
|
-
|
168
|
-
for field_name in cls.__getModelFields():
|
169
|
-
if field_name not in to_ignore_list:
|
170
|
-
field: models.Field = getattr(cls._model, field_name).field
|
171
|
-
fields[field_name] = {
|
172
|
-
"type": type(field),
|
173
|
-
"is_required": not field.null,
|
174
|
-
"is_editable": field.editable,
|
175
|
-
"default": field.default,
|
176
|
-
}
|
177
|
-
|
178
|
-
for field_name in cls.__getForeignKeyFields():
|
179
|
-
field = cls._model._meta.get_field(field_name)
|
180
|
-
related_model = field.related_model
|
181
|
-
if related_model and hasattr(
|
182
|
-
related_model,
|
183
|
-
"_general_manager_class",
|
184
|
-
):
|
185
|
-
fields[field_name] = {
|
186
|
-
"type": related_model._general_manager_class,
|
187
|
-
"is_required": not field.null,
|
188
|
-
"is_editable": field.editable,
|
189
|
-
"default": field.default,
|
190
|
-
}
|
191
|
-
elif related_model is not None:
|
192
|
-
fields[field_name] = {
|
193
|
-
"type": related_model,
|
194
|
-
"is_required": not field.null,
|
195
|
-
"is_editable": field.editable,
|
196
|
-
"default": field.default,
|
197
|
-
}
|
198
|
-
|
199
|
-
for field_name, field_call in [
|
200
|
-
*cls.__getManyToManyFields(),
|
201
|
-
*cls.__getReverseRelations(),
|
202
|
-
]:
|
203
|
-
if field_name in fields.keys():
|
204
|
-
if field_call not in fields.keys():
|
205
|
-
field_name = field_call
|
206
|
-
else:
|
207
|
-
raise ValueError("Field name already exists.")
|
208
|
-
related_model = cls._model._meta.get_field(field_name).related_model
|
209
|
-
if related_model and hasattr(
|
210
|
-
related_model,
|
211
|
-
"_general_manager_class",
|
212
|
-
):
|
213
|
-
fields[f"{field_name}_list"] = {
|
214
|
-
"type": related_model._general_manager_class,
|
215
|
-
"is_required": False,
|
216
|
-
"is_editable": False,
|
217
|
-
"default": None,
|
218
|
-
}
|
219
|
-
elif related_model is not None:
|
220
|
-
fields[f"{field_name}_list"] = {
|
221
|
-
"type": related_model,
|
222
|
-
"is_required": False,
|
223
|
-
"is_editable": False,
|
224
|
-
"default": None,
|
225
|
-
}
|
226
|
-
|
227
|
-
return {
|
228
|
-
field_name: {**field, "type": TRANSLATION.get(field["type"], field["type"])}
|
229
|
-
for field_name, field in fields.items()
|
230
|
-
}
|
231
|
-
|
232
|
-
@classmethod
|
233
|
-
def getAttributes(cls) -> dict[str, Any]:
|
234
|
-
field_values: dict[str, Any] = {}
|
235
|
-
|
236
|
-
field_name_list, to_ignore_list = cls.handleCustomFields(cls._model)
|
237
|
-
for field_name in field_name_list:
|
238
|
-
field_values[field_name] = lambda self, field_name=field_name: getattr(
|
239
|
-
self._instance, field_name
|
240
|
-
)
|
241
|
-
|
242
|
-
for field_name in cls.__getModelFields():
|
243
|
-
if field_name not in to_ignore_list:
|
244
|
-
field_values[field_name] = lambda self, field_name=field_name: getattr(
|
245
|
-
self._instance, field_name
|
246
|
-
)
|
247
|
-
|
248
|
-
for field_name in cls.__getForeignKeyFields():
|
249
|
-
related_model = cls._model._meta.get_field(field_name).related_model
|
250
|
-
if related_model and hasattr(
|
251
|
-
related_model,
|
252
|
-
"_general_manager_class",
|
253
|
-
):
|
254
|
-
generalManagerClass = related_model._general_manager_class
|
255
|
-
field_values[f"{field_name}"] = (
|
256
|
-
lambda self, field_name=field_name: generalManagerClass(
|
257
|
-
getattr(self._instance, field_name).pk
|
258
|
-
)
|
259
|
-
)
|
260
|
-
else:
|
261
|
-
field_values[f"{field_name}"] = (
|
262
|
-
lambda self, field_name=field_name: getattr(
|
263
|
-
self._instance, field_name
|
264
|
-
)
|
265
|
-
)
|
266
|
-
|
267
|
-
for field_name, field_call in [
|
268
|
-
*cls.__getManyToManyFields(),
|
269
|
-
*cls.__getReverseRelations(),
|
270
|
-
]:
|
271
|
-
if field_name in field_values.keys():
|
272
|
-
if field_call not in field_values.keys():
|
273
|
-
field_name = field_call
|
274
|
-
else:
|
275
|
-
raise ValueError("Field name already exists.")
|
276
|
-
if hasattr(
|
277
|
-
cls._model._meta.get_field(field_name).related_model,
|
278
|
-
"_general_manager_class",
|
279
|
-
):
|
280
|
-
field_values[
|
281
|
-
f"{field_name}_list"
|
282
|
-
] = lambda self, field_name=field_name: self._instance._meta.get_field(
|
283
|
-
field_name
|
284
|
-
).related_model._general_manager_class.filter(
|
285
|
-
**{self._instance.__class__.__name__.lower(): self.pk}
|
286
|
-
)
|
287
|
-
else:
|
288
|
-
field_values[f"{field_name}_list"] = (
|
289
|
-
lambda self, field_call=field_call: getattr(
|
290
|
-
self._instance, field_call
|
291
|
-
).all()
|
292
|
-
)
|
293
|
-
return field_values
|
294
|
-
|
295
|
-
@staticmethod
|
296
|
-
def handleCustomFields(
|
297
|
-
model: Type[models.Model] | models.Model,
|
298
|
-
) -> tuple[list[str], list[str]]:
|
299
|
-
field_name_list: list[str] = []
|
300
|
-
to_ignore_list: list[str] = []
|
301
|
-
for field_name in DBBasedInterface._getCustomFields(model):
|
302
|
-
to_ignore_list.append(f"{field_name}_value")
|
303
|
-
to_ignore_list.append(f"{field_name}_unit")
|
304
|
-
field_name_list.append(field_name)
|
305
|
-
|
306
|
-
return field_name_list, to_ignore_list
|
307
|
-
|
308
|
-
@staticmethod
|
309
|
-
def _getCustomFields(model: Type[models.Model] | models.Model) -> list[str]:
|
310
|
-
return [
|
311
|
-
field.name
|
312
|
-
for field in model.__dict__.values()
|
313
|
-
if isinstance(field, models.Field)
|
314
|
-
]
|
315
|
-
|
316
|
-
@classmethod
|
317
|
-
def __getModelFields(cls):
|
318
|
-
return [
|
319
|
-
field.name
|
320
|
-
for field in cls._model._meta.get_fields()
|
321
|
-
if not field.many_to_many and not field.related_model
|
322
|
-
]
|
323
|
-
|
324
|
-
@classmethod
|
325
|
-
def __getForeignKeyFields(cls):
|
326
|
-
return [
|
327
|
-
field.name
|
328
|
-
for field in cls._model._meta.get_fields()
|
329
|
-
if field.is_relation and (field.many_to_one or field.one_to_one)
|
330
|
-
]
|
331
|
-
|
332
|
-
@classmethod
|
333
|
-
def __getManyToManyFields(cls):
|
334
|
-
return [
|
335
|
-
(field.name, field.name)
|
336
|
-
for field in cls._model._meta.get_fields()
|
337
|
-
if field.is_relation and field.many_to_many
|
338
|
-
]
|
339
|
-
|
340
|
-
@classmethod
|
341
|
-
def __getReverseRelations(cls):
|
342
|
-
return [
|
343
|
-
(field.name, f"{field.name}_set")
|
344
|
-
for field in cls._model._meta.get_fields()
|
345
|
-
if field.is_relation and field.one_to_many
|
346
|
-
]
|
347
|
-
|
348
|
-
@staticmethod
|
349
|
-
def _preCreate(
|
350
|
-
name: generalManagerClassName, attrs: attributes, interface: interfaceBaseClass
|
351
|
-
) -> tuple[attributes, interfaceBaseClass, relatedClass]:
|
352
|
-
# Felder aus der Interface-Klasse sammeln
|
353
|
-
model_fields: dict[str, Any] = {}
|
354
|
-
meta_class = None
|
355
|
-
for attr_name, attr_value in interface.__dict__.items():
|
356
|
-
if not attr_name.startswith("__"):
|
357
|
-
if attr_name == "Meta" and isinstance(attr_value, type):
|
358
|
-
# Meta-Klasse speichern
|
359
|
-
meta_class = attr_value
|
360
|
-
elif attr_name == "Factory":
|
361
|
-
# Factory nicht in model_fields speichern
|
362
|
-
pass
|
363
|
-
else:
|
364
|
-
model_fields[attr_name] = attr_value
|
365
|
-
model_fields["__module__"] = attrs.get("__module__")
|
366
|
-
# Meta-Klasse hinzufügen oder erstellen
|
367
|
-
rules: list[Rule] | None = None
|
368
|
-
if meta_class:
|
369
|
-
model_fields["Meta"] = meta_class
|
370
|
-
|
371
|
-
if hasattr(meta_class, "rules"):
|
372
|
-
rules = getattr(meta_class, "rules")
|
373
|
-
delattr(meta_class, "rules")
|
374
|
-
|
375
|
-
# Modell erstellen
|
376
|
-
model = type(name, (GeneralManagerModel,), model_fields)
|
377
|
-
if meta_class and rules:
|
378
|
-
setattr(model._meta, "rules", rules)
|
379
|
-
# full_clean Methode hinzufügen
|
380
|
-
model.full_clean = getFullCleanMethode(model)
|
381
|
-
# Interface-Typ bestimmen
|
382
|
-
attrs["_interface_type"] = interface._interface_type
|
383
|
-
interface_cls = type(interface.__name__, (interface,), {})
|
384
|
-
setattr(interface_cls, "_model", model)
|
385
|
-
attrs["Interface"] = interface_cls
|
386
|
-
|
387
|
-
# add factory class
|
388
|
-
factory_definition = getattr(interface, "Factory", None)
|
389
|
-
factory_attributes: dict[str, Any] = {}
|
390
|
-
if factory_definition:
|
391
|
-
for attr_name, attr_value in factory_definition.__dict__.items():
|
392
|
-
if not attr_name.startswith("__"):
|
393
|
-
factory_attributes[attr_name] = attr_value
|
394
|
-
factory_attributes["interface"] = interface_cls
|
395
|
-
factory_attributes["Meta"] = type("Meta", (), {"model": model})
|
396
|
-
factory_class = type(f"{name}Factory", (AutoFactory,), factory_attributes)
|
397
|
-
# factory_class._meta.model = model
|
398
|
-
attrs["Factory"] = factory_class
|
399
|
-
|
400
|
-
return attrs, interface_cls, model
|
401
|
-
|
402
|
-
@staticmethod
|
403
|
-
def _postCreate(
|
404
|
-
new_class: newlyCreatedGeneralManagerClass,
|
405
|
-
interface_class: newlyCreatedInterfaceClass,
|
406
|
-
model: relatedClass,
|
407
|
-
) -> None:
|
408
|
-
interface_class._parent_class = new_class
|
409
|
-
setattr(model, "_general_manager_class", new_class)
|
410
|
-
|
411
|
-
@classmethod
|
412
|
-
def handleInterface(
|
413
|
-
cls,
|
414
|
-
) -> tuple[classPreCreationMethod, classPostCreationMethod]:
|
415
|
-
"""
|
416
|
-
This method returns a pre and a post GeneralManager creation method
|
417
|
-
and is called inside the GeneralManagerMeta class to initialize the
|
418
|
-
Interface.
|
419
|
-
The pre creation method is called before the GeneralManager instance
|
420
|
-
is created to modify the kwargs.
|
421
|
-
The post creation method is called after the GeneralManager instance
|
422
|
-
is created to modify the instance and add additional data.
|
423
|
-
"""
|
424
|
-
return cls._preCreate, cls._postCreate
|
425
|
-
|
426
|
-
@classmethod
|
427
|
-
def getFieldType(cls, field_name: str) -> type:
|
428
|
-
"""
|
429
|
-
This method returns the field type for the given field name.
|
430
|
-
"""
|
431
|
-
field = cls._model._meta.get_field(field_name)
|
432
|
-
if field.is_relation and field.related_model:
|
433
|
-
return field.related_model._general_manager_class
|
434
|
-
return type(field)
|
435
|
-
|
436
|
-
|
437
|
-
class ReadOnlyInterface(DBBasedInterface):
|
438
|
-
_interface_type = "readonly"
|
439
|
-
|
440
|
-
@classmethod
|
441
|
-
def sync_data(cls) -> None:
|
442
|
-
model: Type[models.Model] | None = getattr(cls, "_model", None)
|
443
|
-
parent_class = getattr(cls, "_parent_class", None)
|
444
|
-
if model is None or parent_class is None:
|
445
|
-
raise ValueError("Attribute '_model' and '_parent_class' must be set.")
|
446
|
-
json_data = getattr(parent_class, "_json_data", None)
|
447
|
-
if not json_data:
|
448
|
-
raise ValueError(
|
449
|
-
f"For ReadOnlyInterface '{parent_class.__name__}' must be set '_json_data'"
|
450
|
-
)
|
451
|
-
|
452
|
-
# JSON-Daten parsen
|
453
|
-
if isinstance(json_data, str):
|
454
|
-
data_list = json.loads(json_data)
|
455
|
-
if isinstance(json_data, list):
|
456
|
-
data_list: list[Any] = json_data
|
457
|
-
else:
|
458
|
-
raise ValueError(
|
459
|
-
"_json_data must be a JSON string or a list of dictionaries"
|
460
|
-
)
|
461
|
-
|
462
|
-
unique_fields = getattr(parent_class, "_unique_fields", [])
|
463
|
-
if not unique_fields:
|
464
|
-
raise ValueError(
|
465
|
-
f"For ReadOnlyInterface '{parent_class.__name__}' must be defined '_unique_fields'"
|
466
|
-
)
|
467
|
-
|
468
|
-
with transaction.atomic():
|
469
|
-
json_unique_values: set[Any] = set()
|
470
|
-
|
471
|
-
# Daten synchronisieren
|
472
|
-
for data in data_list:
|
473
|
-
lookup = {field: data[field] for field in unique_fields}
|
474
|
-
unique_identifier = tuple(lookup[field] for field in unique_fields)
|
475
|
-
json_unique_values.add(unique_identifier)
|
476
|
-
|
477
|
-
instance, _ = model.objects.get_or_create(**lookup)
|
478
|
-
updated = False
|
479
|
-
for field_name, value in data.items():
|
480
|
-
if getattr(instance, field_name, None) != value:
|
481
|
-
setattr(instance, field_name, value)
|
482
|
-
updated = True
|
483
|
-
if updated:
|
484
|
-
instance.save()
|
485
|
-
|
486
|
-
# Existierende Einträge abrufen und löschen, wenn nicht im JSON vorhanden
|
487
|
-
existing_instances = model.objects.all()
|
488
|
-
for instance in existing_instances:
|
489
|
-
lookup = {field: getattr(instance, field) for field in unique_fields}
|
490
|
-
unique_identifier = tuple(lookup[field] for field in unique_fields)
|
491
|
-
if unique_identifier not in json_unique_values:
|
492
|
-
instance.delete()
|
493
|
-
|
494
|
-
@staticmethod
|
495
|
-
def readOnlyPostCreate(func: Callable[..., Any]) -> Callable[..., Any]:
|
496
|
-
def wrapper(
|
497
|
-
mcs: Type[GeneralManagerMeta],
|
498
|
-
new_class: Type[GeneralManager],
|
499
|
-
interface_cls: Type[ReadOnlyInterface],
|
500
|
-
model: Type[GeneralManagerModel],
|
501
|
-
):
|
502
|
-
func(mcs, new_class, interface_cls, model)
|
503
|
-
mcs.read_only_classes.append(interface_cls)
|
504
|
-
|
505
|
-
return wrapper
|
506
|
-
|
507
|
-
@classmethod
|
508
|
-
def handleInterface(cls) -> tuple[classPreCreationMethod, classPostCreationMethod]:
|
509
|
-
"""
|
510
|
-
This method returns a pre and a post GeneralManager creation method
|
511
|
-
and is called inside the GeneralManagerMeta class to initialize the
|
512
|
-
Interface.
|
513
|
-
The pre creation method is called before the GeneralManager instance
|
514
|
-
is created to modify the kwargs.
|
515
|
-
The post creation method is called after the GeneralManager instance
|
516
|
-
is created to modify the instance and add additional data.
|
517
|
-
"""
|
518
|
-
return cls._preCreate, cls.readOnlyPostCreate(cls._postCreate)
|
519
12
|
|
520
13
|
|
521
14
|
class DatabaseInterface(DBBasedInterface):
|
@@ -591,6 +84,19 @@ class DatabaseInterface(DBBasedInterface):
|
|
591
84
|
def __save_with_history(
|
592
85
|
cls, instance: GeneralManagerModel, creator_id: int, history_comment: str | None
|
593
86
|
) -> int:
|
87
|
+
"""
|
88
|
+
Saves a model instance with validation and optional history tracking.
|
89
|
+
|
90
|
+
Sets the `changed_by_id` field, validates the instance, applies a history comment if provided, and saves the instance within an atomic transaction.
|
91
|
+
|
92
|
+
Args:
|
93
|
+
instance: The model instance to save.
|
94
|
+
creator_id: The ID of the user making the change.
|
95
|
+
history_comment: Optional comment describing the reason for the change.
|
96
|
+
|
97
|
+
Returns:
|
98
|
+
The primary key of the saved instance.
|
99
|
+
"""
|
594
100
|
instance.changed_by_id = creator_id
|
595
101
|
instance.full_clean()
|
596
102
|
if history_comment:
|
@@ -598,148 +104,3 @@ class DatabaseInterface(DBBasedInterface):
|
|
598
104
|
instance.save()
|
599
105
|
|
600
106
|
return instance.pk
|
601
|
-
|
602
|
-
|
603
|
-
class DatabaseBucket(Bucket[GeneralManagerType]):
|
604
|
-
|
605
|
-
def __init__(
|
606
|
-
self,
|
607
|
-
data: models.QuerySet[modelsModel],
|
608
|
-
manager_class: Type[GeneralManagerType],
|
609
|
-
filter_definitions: dict[str, list[Any]] = {},
|
610
|
-
exclude_definitions: dict[str, list[Any]] = {},
|
611
|
-
):
|
612
|
-
if data is None:
|
613
|
-
data = manager_class.filter(**filter_definitions).exclude(
|
614
|
-
**exclude_definitions
|
615
|
-
)
|
616
|
-
self._data = data
|
617
|
-
|
618
|
-
self._manager_class = manager_class
|
619
|
-
self.filters = {**filter_definitions}
|
620
|
-
self.excludes = {**exclude_definitions}
|
621
|
-
|
622
|
-
def __iter__(self) -> Generator[GeneralManagerType]:
|
623
|
-
for item in self._data:
|
624
|
-
yield self._manager_class(item.pk)
|
625
|
-
|
626
|
-
def __or__(
|
627
|
-
self,
|
628
|
-
other: Bucket[GeneralManagerType] | GeneralManager[GeneralManagerType],
|
629
|
-
) -> DatabaseBucket[GeneralManagerType]:
|
630
|
-
from general_manager.manager.generalManager import GeneralManager
|
631
|
-
|
632
|
-
if isinstance(other, GeneralManager) and other.__class__ == self._manager_class:
|
633
|
-
return self.__or__(self.filter(id__in=[getattr(other, "id")]))
|
634
|
-
if not isinstance(other, self.__class__):
|
635
|
-
raise ValueError("Cannot combine different bucket types")
|
636
|
-
if self._manager_class != other._manager_class:
|
637
|
-
raise ValueError("Cannot combine different bucket managers")
|
638
|
-
return self.__class__(
|
639
|
-
self._data | other._data,
|
640
|
-
self._manager_class,
|
641
|
-
{},
|
642
|
-
)
|
643
|
-
|
644
|
-
def __mergeFilterDefinitions(
|
645
|
-
self, basis: dict[str, list[Any]], **kwargs: Any
|
646
|
-
) -> dict[str, list[Any]]:
|
647
|
-
"""
|
648
|
-
Merges filter definitions by appending values from keyword arguments to the corresponding lists in the basis dictionary.
|
649
|
-
|
650
|
-
Args:
|
651
|
-
basis: A dictionary mapping filter keys to lists of values. Existing filter criteria.
|
652
|
-
**kwargs: Additional filter criteria to be merged, where each value is appended to the corresponding key's list.
|
653
|
-
|
654
|
-
Returns:
|
655
|
-
A dictionary with keys mapping to lists containing all values from both the original basis and the new keyword arguments.
|
656
|
-
"""
|
657
|
-
kwarg_filter: dict[str, list[Any]] = {}
|
658
|
-
for key, value in basis.items():
|
659
|
-
kwarg_filter[key] = value
|
660
|
-
for key, value in kwargs.items():
|
661
|
-
if key not in kwarg_filter:
|
662
|
-
kwarg_filter[key] = []
|
663
|
-
kwarg_filter[key].append(value)
|
664
|
-
return kwarg_filter
|
665
|
-
|
666
|
-
def filter(self, **kwargs: Any) -> DatabaseBucket[GeneralManagerType]:
|
667
|
-
"""
|
668
|
-
Returns a new bucket containing manager instances matching the given filter criteria.
|
669
|
-
|
670
|
-
Additional filter keyword arguments are merged with existing filters to further restrict the queryset.
|
671
|
-
"""
|
672
|
-
merged_filter = self.__mergeFilterDefinitions(self.filters, **kwargs)
|
673
|
-
return self.__class__(
|
674
|
-
self._data.filter(**kwargs),
|
675
|
-
self._manager_class,
|
676
|
-
merged_filter,
|
677
|
-
self.excludes,
|
678
|
-
)
|
679
|
-
|
680
|
-
def exclude(self, **kwargs: Any) -> DatabaseBucket[GeneralManagerType]:
|
681
|
-
"""
|
682
|
-
Returns a new DatabaseBucket excluding items matching the given criteria.
|
683
|
-
|
684
|
-
Keyword arguments define field lookups to exclude from the queryset. The returned bucket contains only items that do not match these filters.
|
685
|
-
"""
|
686
|
-
merged_exclude = self.__mergeFilterDefinitions(self.excludes, **kwargs)
|
687
|
-
return self.__class__(
|
688
|
-
self._data.exclude(**kwargs),
|
689
|
-
self._manager_class,
|
690
|
-
self.filters,
|
691
|
-
merged_exclude,
|
692
|
-
)
|
693
|
-
|
694
|
-
def first(self) -> GeneralManagerType | None:
|
695
|
-
first_element = self._data.first()
|
696
|
-
if first_element is None:
|
697
|
-
return None
|
698
|
-
return self._manager_class(first_element.pk)
|
699
|
-
|
700
|
-
def last(self) -> GeneralManagerType | None:
|
701
|
-
first_element = self._data.last()
|
702
|
-
if first_element is None:
|
703
|
-
return None
|
704
|
-
return self._manager_class(first_element.pk)
|
705
|
-
|
706
|
-
def count(self) -> int:
|
707
|
-
return self._data.count()
|
708
|
-
|
709
|
-
def all(self) -> DatabaseBucket:
|
710
|
-
return self.__class__(self._data.all(), self._manager_class)
|
711
|
-
|
712
|
-
def get(self, **kwargs: Any) -> GeneralManagerType:
|
713
|
-
element = self._data.get(**kwargs)
|
714
|
-
return self._manager_class(element.pk)
|
715
|
-
|
716
|
-
def __getitem__(self, item: int | slice) -> GeneralManagerType | DatabaseBucket:
|
717
|
-
if isinstance(item, slice):
|
718
|
-
return self.__class__(self._data[item], self._manager_class)
|
719
|
-
return self._manager_class(self._data[item].pk)
|
720
|
-
|
721
|
-
def __len__(self) -> int:
|
722
|
-
return self._data.count()
|
723
|
-
|
724
|
-
def __repr__(self) -> str:
|
725
|
-
return f"{self._manager_class.__name__}Bucket ({self._data})"
|
726
|
-
|
727
|
-
def __contains__(self, item: GeneralManagerType | models.Model) -> bool:
|
728
|
-
from general_manager.manager.generalManager import GeneralManager
|
729
|
-
|
730
|
-
if isinstance(item, GeneralManager):
|
731
|
-
return getattr(item, "id") in self._data.values_list("pk", flat=True)
|
732
|
-
return item in self._data
|
733
|
-
|
734
|
-
def sort(
|
735
|
-
self,
|
736
|
-
key: tuple[str] | str,
|
737
|
-
reverse: bool = False,
|
738
|
-
) -> DatabaseBucket:
|
739
|
-
if isinstance(key, str):
|
740
|
-
key = (key,)
|
741
|
-
if reverse:
|
742
|
-
sorted_data = self._data.order_by(*[f"-{k}" for k in key])
|
743
|
-
else:
|
744
|
-
sorted_data = self._data.order_by(*key)
|
745
|
-
return self.__class__(sorted_data, self._manager_class)
|