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
@@ -0,0 +1,538 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from typing import (
|
3
|
+
Type,
|
4
|
+
ClassVar,
|
5
|
+
Any,
|
6
|
+
Callable,
|
7
|
+
TYPE_CHECKING,
|
8
|
+
TypeVar,
|
9
|
+
)
|
10
|
+
from django.db import models
|
11
|
+
from django.conf import settings
|
12
|
+
from datetime import datetime, timedelta
|
13
|
+
from simple_history.models import HistoricalRecords # type: ignore
|
14
|
+
from general_manager.measurement.measurement import Measurement
|
15
|
+
from general_manager.measurement.measurementField import MeasurementField
|
16
|
+
from decimal import Decimal
|
17
|
+
from general_manager.factory.autoFactory import AutoFactory
|
18
|
+
from django.core.exceptions import ValidationError
|
19
|
+
from general_manager.interface.baseInterface import (
|
20
|
+
InterfaceBase,
|
21
|
+
classPostCreationMethod,
|
22
|
+
classPreCreationMethod,
|
23
|
+
generalManagerClassName,
|
24
|
+
attributes,
|
25
|
+
interfaceBaseClass,
|
26
|
+
newlyCreatedGeneralManagerClass,
|
27
|
+
newlyCreatedInterfaceClass,
|
28
|
+
relatedClass,
|
29
|
+
AttributeTypedDict,
|
30
|
+
)
|
31
|
+
from general_manager.manager.input import Input
|
32
|
+
from general_manager.bucket.databaseBucket import DatabaseBucket
|
33
|
+
|
34
|
+
if TYPE_CHECKING:
|
35
|
+
from general_manager.manager.generalManager import GeneralManager
|
36
|
+
from django.contrib.auth.models import AbstractUser
|
37
|
+
from general_manager.rule.rule import Rule
|
38
|
+
|
39
|
+
modelsModel = TypeVar("modelsModel", bound=models.Model)
|
40
|
+
|
41
|
+
|
42
|
+
def getFullCleanMethode(model: Type[models.Model]) -> Callable[..., None]:
|
43
|
+
"""
|
44
|
+
Generates a custom `full_clean` method for a Django model that combines standard validation with additional rule-based checks.
|
45
|
+
|
46
|
+
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.
|
47
|
+
"""
|
48
|
+
|
49
|
+
def full_clean(self: models.Model, *args: Any, **kwargs: Any):
|
50
|
+
errors: dict[str, Any] = {}
|
51
|
+
try:
|
52
|
+
super(model, self).full_clean(*args, **kwargs) # type: ignore
|
53
|
+
except ValidationError as e:
|
54
|
+
errors.update(e.message_dict)
|
55
|
+
|
56
|
+
rules: list[Rule] = getattr(self._meta, "rules")
|
57
|
+
for rule in rules:
|
58
|
+
if not rule.evaluate(self):
|
59
|
+
error_message = rule.getErrorMessage()
|
60
|
+
if error_message:
|
61
|
+
errors.update(error_message)
|
62
|
+
|
63
|
+
if errors:
|
64
|
+
raise ValidationError(errors)
|
65
|
+
|
66
|
+
return full_clean
|
67
|
+
|
68
|
+
|
69
|
+
class GeneralManagerModel(models.Model):
|
70
|
+
_general_manager_class: ClassVar[Type[GeneralManager]]
|
71
|
+
is_active = models.BooleanField(default=True)
|
72
|
+
changed_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
|
73
|
+
changed_by_id: int
|
74
|
+
history = HistoricalRecords(inherit=True)
|
75
|
+
|
76
|
+
@property
|
77
|
+
def _history_user(self) -> AbstractUser:
|
78
|
+
"""
|
79
|
+
Returns the user who last modified this model instance.
|
80
|
+
"""
|
81
|
+
return self.changed_by
|
82
|
+
|
83
|
+
@_history_user.setter
|
84
|
+
def _history_user(self, value: AbstractUser) -> None:
|
85
|
+
"""
|
86
|
+
Sets the user responsible for the latest change to the model instance.
|
87
|
+
|
88
|
+
Args:
|
89
|
+
value: The user to associate with the change.
|
90
|
+
"""
|
91
|
+
self.changed_by = value
|
92
|
+
|
93
|
+
class Meta:
|
94
|
+
abstract = True
|
95
|
+
|
96
|
+
|
97
|
+
class DBBasedInterface(InterfaceBase):
|
98
|
+
_model: ClassVar[Type[GeneralManagerModel]]
|
99
|
+
input_fields: dict[str, Input] = {"id": Input(int)}
|
100
|
+
|
101
|
+
def __init__(
|
102
|
+
self,
|
103
|
+
*args: list[Any],
|
104
|
+
search_date: datetime | None = None,
|
105
|
+
**kwargs: dict[str, Any],
|
106
|
+
):
|
107
|
+
"""
|
108
|
+
Initializes the interface instance and loads the corresponding model record.
|
109
|
+
|
110
|
+
If a `search_date` is provided, retrieves the historical record as of that date; otherwise, loads the current record.
|
111
|
+
"""
|
112
|
+
super().__init__(*args, **kwargs)
|
113
|
+
self.pk = self.identification["id"]
|
114
|
+
self._instance = self.getData(search_date)
|
115
|
+
|
116
|
+
def getData(self, search_date: datetime | None = None) -> GeneralManagerModel:
|
117
|
+
"""
|
118
|
+
Retrieves the model instance by primary key, optionally as of a specified historical date.
|
119
|
+
|
120
|
+
If a `search_date` is provided and is not within the last 5 seconds, returns the historical record of the instance as of that date; otherwise, returns the current instance.
|
121
|
+
"""
|
122
|
+
model = self._model
|
123
|
+
instance = model.objects.get(pk=self.pk)
|
124
|
+
if search_date and not search_date > datetime.now() - timedelta(seconds=5):
|
125
|
+
instance = self.getHistoricalRecord(instance, search_date)
|
126
|
+
return instance
|
127
|
+
|
128
|
+
@classmethod
|
129
|
+
def filter(cls, **kwargs: Any) -> DatabaseBucket:
|
130
|
+
"""
|
131
|
+
Returns a DatabaseBucket containing model instances filtered by the given criteria.
|
132
|
+
|
133
|
+
Args:
|
134
|
+
**kwargs: Field lookups to filter the queryset.
|
135
|
+
|
136
|
+
Returns:
|
137
|
+
A DatabaseBucket wrapping the filtered queryset and associated metadata.
|
138
|
+
"""
|
139
|
+
return DatabaseBucket(
|
140
|
+
cls._model.objects.filter(**kwargs),
|
141
|
+
cls._parent_class,
|
142
|
+
cls.__createFilterDefinitions(**kwargs),
|
143
|
+
)
|
144
|
+
|
145
|
+
@classmethod
|
146
|
+
def exclude(cls, **kwargs: Any) -> DatabaseBucket:
|
147
|
+
"""
|
148
|
+
Returns a DatabaseBucket containing model instances that do not match the given filter criteria.
|
149
|
+
|
150
|
+
Args:
|
151
|
+
**kwargs: Field lookups to exclude from the queryset.
|
152
|
+
|
153
|
+
Returns:
|
154
|
+
A DatabaseBucket wrapping the queryset of excluded model instances.
|
155
|
+
"""
|
156
|
+
return DatabaseBucket(
|
157
|
+
cls._model.objects.exclude(**kwargs),
|
158
|
+
cls._parent_class,
|
159
|
+
cls.__createFilterDefinitions(**kwargs),
|
160
|
+
)
|
161
|
+
|
162
|
+
@staticmethod
|
163
|
+
def __createFilterDefinitions(**kwargs: Any) -> dict[str, Any]:
|
164
|
+
"""
|
165
|
+
Creates a dictionary of filter definitions from the provided keyword arguments.
|
166
|
+
|
167
|
+
Args:
|
168
|
+
**kwargs: Key-value pairs representing filter criteria.
|
169
|
+
|
170
|
+
Returns:
|
171
|
+
A dictionary mapping filter keys to their corresponding values.
|
172
|
+
"""
|
173
|
+
filter_definitions: dict[str, Any] = {}
|
174
|
+
for key, value in kwargs.items():
|
175
|
+
filter_definitions[key] = value
|
176
|
+
return filter_definitions
|
177
|
+
|
178
|
+
@classmethod
|
179
|
+
def getHistoricalRecord(
|
180
|
+
cls, instance: GeneralManagerModel, search_date: datetime | None = None
|
181
|
+
) -> GeneralManagerModel:
|
182
|
+
"""
|
183
|
+
Retrieves the most recent historical record of a model instance at or before a specified date.
|
184
|
+
|
185
|
+
Args:
|
186
|
+
instance: The model instance whose history is queried.
|
187
|
+
search_date: The cutoff datetime; returns the last record at or before this date.
|
188
|
+
|
189
|
+
Returns:
|
190
|
+
The historical model instance as of the specified date, or None if no such record exists.
|
191
|
+
"""
|
192
|
+
return instance.history.filter(history_date__lte=search_date).last() # type: ignore
|
193
|
+
|
194
|
+
@classmethod
|
195
|
+
def getAttributeTypes(cls) -> dict[str, AttributeTypedDict]:
|
196
|
+
"""
|
197
|
+
Returns a dictionary mapping attribute names to their type information and metadata.
|
198
|
+
|
199
|
+
The returned dictionary includes all model fields, custom fields, foreign keys, many-to-many, and reverse relation fields. Each entry provides the Python type (translated from Django field types when possible), whether the field is required, editable, and its default value. For related models that have a general manager class, the type is set to that class.
|
200
|
+
"""
|
201
|
+
TRANSLATION: dict[Type[models.Field[Any, Any]], type] = {
|
202
|
+
models.fields.BigAutoField: int,
|
203
|
+
models.AutoField: int,
|
204
|
+
models.CharField: str,
|
205
|
+
models.TextField: str,
|
206
|
+
models.BooleanField: bool,
|
207
|
+
models.IntegerField: int,
|
208
|
+
models.FloatField: float,
|
209
|
+
models.DateField: datetime,
|
210
|
+
models.DateTimeField: datetime,
|
211
|
+
MeasurementField: Measurement,
|
212
|
+
models.DecimalField: Decimal,
|
213
|
+
models.EmailField: str,
|
214
|
+
models.FileField: str,
|
215
|
+
models.ImageField: str,
|
216
|
+
models.URLField: str,
|
217
|
+
models.TimeField: datetime,
|
218
|
+
}
|
219
|
+
fields: dict[str, AttributeTypedDict] = {}
|
220
|
+
field_name_list, to_ignore_list = cls.handleCustomFields(cls._model)
|
221
|
+
for field_name in field_name_list:
|
222
|
+
field: models.Field = getattr(cls._model, field_name)
|
223
|
+
fields[field_name] = {
|
224
|
+
"type": type(field),
|
225
|
+
"is_required": not field.null,
|
226
|
+
"is_editable": field.editable,
|
227
|
+
"default": field.default,
|
228
|
+
}
|
229
|
+
|
230
|
+
for field_name in cls.__getModelFields():
|
231
|
+
if field_name not in to_ignore_list:
|
232
|
+
field: models.Field = getattr(cls._model, field_name).field
|
233
|
+
fields[field_name] = {
|
234
|
+
"type": type(field),
|
235
|
+
"is_required": not field.null,
|
236
|
+
"is_editable": field.editable,
|
237
|
+
"default": field.default,
|
238
|
+
}
|
239
|
+
|
240
|
+
for field_name in cls.__getForeignKeyFields():
|
241
|
+
field = cls._model._meta.get_field(field_name)
|
242
|
+
related_model = field.related_model
|
243
|
+
if related_model and hasattr(
|
244
|
+
related_model,
|
245
|
+
"_general_manager_class",
|
246
|
+
):
|
247
|
+
related_model = related_model._general_manager_class
|
248
|
+
|
249
|
+
elif related_model is not None:
|
250
|
+
fields[field_name] = {
|
251
|
+
"type": related_model,
|
252
|
+
"is_required": not field.null,
|
253
|
+
"is_editable": field.editable,
|
254
|
+
"default": field.default,
|
255
|
+
}
|
256
|
+
|
257
|
+
for field_name, field_call in [
|
258
|
+
*cls.__getManyToManyFields(),
|
259
|
+
*cls.__getReverseRelations(),
|
260
|
+
]:
|
261
|
+
if field_name in fields:
|
262
|
+
if field_call not in fields:
|
263
|
+
field_name = field_call
|
264
|
+
else:
|
265
|
+
raise ValueError("Field name already exists.")
|
266
|
+
field = cls._model._meta.get_field(field_name)
|
267
|
+
related_model = cls._model._meta.get_field(field_name).related_model
|
268
|
+
if related_model and hasattr(
|
269
|
+
related_model,
|
270
|
+
"_general_manager_class",
|
271
|
+
):
|
272
|
+
related_model = related_model._general_manager_class
|
273
|
+
if related_model is not None:
|
274
|
+
fields[f"{field_name}_list"] = {
|
275
|
+
"type": related_model,
|
276
|
+
"is_required": False,
|
277
|
+
"is_editable": bool(field.many_to_many and field.editable),
|
278
|
+
"default": None,
|
279
|
+
}
|
280
|
+
|
281
|
+
return {
|
282
|
+
field_name: {**field, "type": TRANSLATION.get(field["type"], field["type"])}
|
283
|
+
for field_name, field in fields.items()
|
284
|
+
}
|
285
|
+
|
286
|
+
@classmethod
|
287
|
+
def getAttributes(cls) -> dict[str, Callable[[DBBasedInterface], Any]]:
|
288
|
+
"""
|
289
|
+
Returns a mapping of attribute names to callables that retrieve their values from a DBBasedInterface instance.
|
290
|
+
|
291
|
+
The returned dictionary includes accessors for custom fields, model fields, foreign keys (optionally returning related interface instances), many-to-many relations, and reverse relations. For related models that have a general manager class, the accessor returns an instance of that class; otherwise, it returns the related object or queryset directly. Raises a ValueError if a field name conflict occurs.
|
292
|
+
"""
|
293
|
+
field_values: dict[str, Any] = {}
|
294
|
+
|
295
|
+
field_name_list, to_ignore_list = cls.handleCustomFields(cls._model)
|
296
|
+
for field_name in field_name_list:
|
297
|
+
field_values[field_name] = lambda self, field_name=field_name: getattr(
|
298
|
+
self._instance, field_name
|
299
|
+
)
|
300
|
+
|
301
|
+
for field_name in cls.__getModelFields():
|
302
|
+
if field_name not in to_ignore_list:
|
303
|
+
field_values[field_name] = lambda self, field_name=field_name: getattr(
|
304
|
+
self._instance, field_name
|
305
|
+
)
|
306
|
+
|
307
|
+
for field_name in cls.__getForeignKeyFields():
|
308
|
+
related_model = cls._model._meta.get_field(field_name).related_model
|
309
|
+
if related_model and hasattr(
|
310
|
+
related_model,
|
311
|
+
"_general_manager_class",
|
312
|
+
):
|
313
|
+
generalManagerClass = related_model._general_manager_class
|
314
|
+
field_values[f"{field_name}"] = (
|
315
|
+
lambda self, field_name=field_name, manager_class=generalManagerClass: manager_class(
|
316
|
+
getattr(self._instance, field_name).pk
|
317
|
+
)
|
318
|
+
)
|
319
|
+
else:
|
320
|
+
field_values[f"{field_name}"] = (
|
321
|
+
lambda self, field_name=field_name: getattr(
|
322
|
+
self._instance, field_name
|
323
|
+
)
|
324
|
+
)
|
325
|
+
|
326
|
+
for field_name, field_call in [
|
327
|
+
*cls.__getManyToManyFields(),
|
328
|
+
*cls.__getReverseRelations(),
|
329
|
+
]:
|
330
|
+
if field_name in field_values:
|
331
|
+
if field_call not in field_values:
|
332
|
+
field_name = field_call
|
333
|
+
else:
|
334
|
+
raise ValueError("Field name already exists.")
|
335
|
+
if hasattr(
|
336
|
+
cls._model._meta.get_field(field_name).related_model,
|
337
|
+
"_general_manager_class",
|
338
|
+
):
|
339
|
+
field_values[
|
340
|
+
f"{field_name}_list"
|
341
|
+
] = lambda self, field_name=field_name: self._instance._meta.get_field(
|
342
|
+
field_name
|
343
|
+
).related_model._general_manager_class.filter(
|
344
|
+
**{self._instance.__class__.__name__.lower(): self.pk}
|
345
|
+
)
|
346
|
+
else:
|
347
|
+
field_values[f"{field_name}_list"] = (
|
348
|
+
lambda self, field_call=field_call: getattr(
|
349
|
+
self._instance, field_call
|
350
|
+
).all()
|
351
|
+
)
|
352
|
+
return field_values
|
353
|
+
|
354
|
+
@staticmethod
|
355
|
+
def handleCustomFields(
|
356
|
+
model: Type[models.Model] | models.Model,
|
357
|
+
) -> tuple[list[str], list[str]]:
|
358
|
+
"""
|
359
|
+
Identifies custom fields on a model and their associated auxiliary fields to ignore.
|
360
|
+
|
361
|
+
Returns:
|
362
|
+
A tuple containing a list of custom field names and a list of related field names
|
363
|
+
(typically suffixed with '_value' and '_unit') that should be ignored.
|
364
|
+
"""
|
365
|
+
field_name_list: list[str] = []
|
366
|
+
to_ignore_list: list[str] = []
|
367
|
+
for field_name in DBBasedInterface._getCustomFields(model):
|
368
|
+
to_ignore_list.append(f"{field_name}_value")
|
369
|
+
to_ignore_list.append(f"{field_name}_unit")
|
370
|
+
field_name_list.append(field_name)
|
371
|
+
|
372
|
+
return field_name_list, to_ignore_list
|
373
|
+
|
374
|
+
@staticmethod
|
375
|
+
def _getCustomFields(model: Type[models.Model] | models.Model) -> list[str]:
|
376
|
+
"""
|
377
|
+
Returns a list of custom field names defined directly on the model class.
|
378
|
+
|
379
|
+
Args:
|
380
|
+
model: The Django model class or instance to inspect.
|
381
|
+
|
382
|
+
Returns:
|
383
|
+
A list of field names that are defined as class attributes on the model, not via Django's meta system.
|
384
|
+
"""
|
385
|
+
return [
|
386
|
+
field.name
|
387
|
+
for field in model.__dict__.values()
|
388
|
+
if isinstance(field, models.Field)
|
389
|
+
]
|
390
|
+
|
391
|
+
@classmethod
|
392
|
+
def __getModelFields(cls):
|
393
|
+
"""
|
394
|
+
Returns a list of model field names that are not many-to-many or related fields.
|
395
|
+
|
396
|
+
Excludes fields representing many-to-many relationships and related models.
|
397
|
+
"""
|
398
|
+
return [
|
399
|
+
field.name
|
400
|
+
for field in cls._model._meta.get_fields()
|
401
|
+
if not field.many_to_many and not field.related_model
|
402
|
+
]
|
403
|
+
|
404
|
+
@classmethod
|
405
|
+
def __getForeignKeyFields(cls):
|
406
|
+
"""
|
407
|
+
Returns a list of field names for all foreign key and one-to-one relations on the model.
|
408
|
+
"""
|
409
|
+
return [
|
410
|
+
field.name
|
411
|
+
for field in cls._model._meta.get_fields()
|
412
|
+
if field.is_relation and (field.many_to_one or field.one_to_one)
|
413
|
+
]
|
414
|
+
|
415
|
+
@classmethod
|
416
|
+
def __getManyToManyFields(cls):
|
417
|
+
"""
|
418
|
+
Returns a list of tuples containing the names of all many-to-many fields on the model.
|
419
|
+
|
420
|
+
Each tuple consists of the field name repeated twice.
|
421
|
+
"""
|
422
|
+
return [
|
423
|
+
(field.name, field.name)
|
424
|
+
for field in cls._model._meta.get_fields()
|
425
|
+
if field.is_relation and field.many_to_many
|
426
|
+
]
|
427
|
+
|
428
|
+
@classmethod
|
429
|
+
def __getReverseRelations(cls):
|
430
|
+
"""
|
431
|
+
Returns a list of tuples representing reverse one-to-many relations for the model.
|
432
|
+
|
433
|
+
Each tuple contains the related field's name and its default related accessor name.
|
434
|
+
"""
|
435
|
+
return [
|
436
|
+
(field.name, f"{field.name}_set")
|
437
|
+
for field in cls._model._meta.get_fields()
|
438
|
+
if field.is_relation and field.one_to_many
|
439
|
+
]
|
440
|
+
|
441
|
+
@staticmethod
|
442
|
+
def _preCreate(
|
443
|
+
name: generalManagerClassName, attrs: attributes, interface: interfaceBaseClass
|
444
|
+
) -> tuple[attributes, interfaceBaseClass, relatedClass]:
|
445
|
+
# Felder aus der Interface-Klasse sammeln
|
446
|
+
"""
|
447
|
+
Dynamically creates a Django model, its associated interface class, and a factory class based on the provided interface definition.
|
448
|
+
|
449
|
+
This method extracts fields and meta information from the interface class, constructs a new Django model inheriting from `GeneralManagerModel`, attaches custom validation rules if present, and generates a corresponding interface and factory class. The resulting classes are returned for further use in the general manager framework.
|
450
|
+
|
451
|
+
Returns:
|
452
|
+
A tuple containing the updated attributes dictionary, the new interface class, and the newly created model class.
|
453
|
+
"""
|
454
|
+
model_fields: dict[str, Any] = {}
|
455
|
+
meta_class = None
|
456
|
+
for attr_name, attr_value in interface.__dict__.items():
|
457
|
+
if not attr_name.startswith("__"):
|
458
|
+
if attr_name == "Meta" and isinstance(attr_value, type):
|
459
|
+
# Meta-Klasse speichern
|
460
|
+
meta_class = attr_value
|
461
|
+
elif attr_name == "Factory":
|
462
|
+
# Factory nicht in model_fields speichern
|
463
|
+
pass
|
464
|
+
else:
|
465
|
+
model_fields[attr_name] = attr_value
|
466
|
+
model_fields["__module__"] = attrs.get("__module__")
|
467
|
+
# Meta-Klasse hinzufügen oder erstellen
|
468
|
+
rules: list[Rule] | None = None
|
469
|
+
if meta_class:
|
470
|
+
model_fields["Meta"] = meta_class
|
471
|
+
|
472
|
+
if hasattr(meta_class, "rules"):
|
473
|
+
rules = getattr(meta_class, "rules")
|
474
|
+
delattr(meta_class, "rules")
|
475
|
+
|
476
|
+
# Modell erstellen
|
477
|
+
model = type(name, (GeneralManagerModel,), model_fields)
|
478
|
+
if meta_class and rules:
|
479
|
+
setattr(model._meta, "rules", rules)
|
480
|
+
# full_clean Methode hinzufügen
|
481
|
+
model.full_clean = getFullCleanMethode(model)
|
482
|
+
# Interface-Typ bestimmen
|
483
|
+
attrs["_interface_type"] = interface._interface_type
|
484
|
+
interface_cls = type(interface.__name__, (interface,), {})
|
485
|
+
setattr(interface_cls, "_model", model)
|
486
|
+
attrs["Interface"] = interface_cls
|
487
|
+
|
488
|
+
# add factory class
|
489
|
+
factory_definition = getattr(interface, "Factory", None)
|
490
|
+
factory_attributes: dict[str, Any] = {}
|
491
|
+
if factory_definition:
|
492
|
+
for attr_name, attr_value in factory_definition.__dict__.items():
|
493
|
+
if not attr_name.startswith("__"):
|
494
|
+
factory_attributes[attr_name] = attr_value
|
495
|
+
factory_attributes["interface"] = interface_cls
|
496
|
+
factory_attributes["Meta"] = type("Meta", (), {"model": model})
|
497
|
+
factory_class = type(f"{name}Factory", (AutoFactory,), factory_attributes)
|
498
|
+
# factory_class._meta.model = model
|
499
|
+
attrs["Factory"] = factory_class
|
500
|
+
|
501
|
+
return attrs, interface_cls, model
|
502
|
+
|
503
|
+
@staticmethod
|
504
|
+
def _postCreate(
|
505
|
+
new_class: newlyCreatedGeneralManagerClass,
|
506
|
+
interface_class: newlyCreatedInterfaceClass,
|
507
|
+
model: relatedClass,
|
508
|
+
) -> None:
|
509
|
+
"""
|
510
|
+
Finalizes the setup of dynamically created classes by linking the interface and model to the new general manager class.
|
511
|
+
|
512
|
+
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.
|
513
|
+
"""
|
514
|
+
interface_class._parent_class = new_class
|
515
|
+
setattr(model, "_general_manager_class", new_class)
|
516
|
+
|
517
|
+
@classmethod
|
518
|
+
def handleInterface(
|
519
|
+
cls,
|
520
|
+
) -> tuple[classPreCreationMethod, classPostCreationMethod]:
|
521
|
+
"""
|
522
|
+
Returns the pre- and post-creation hooks for initializing the interface.
|
523
|
+
|
524
|
+
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.
|
525
|
+
"""
|
526
|
+
return cls._preCreate, cls._postCreate
|
527
|
+
|
528
|
+
@classmethod
|
529
|
+
def getFieldType(cls, field_name: str) -> type:
|
530
|
+
"""
|
531
|
+
Returns the type associated with the specified field name.
|
532
|
+
|
533
|
+
If the field is a relation and its related model defines a `_general_manager_class`, that class is returned; otherwise, returns the Django field type.
|
534
|
+
"""
|
535
|
+
field = cls._model._meta.get_field(field_name)
|
536
|
+
if field.is_relation and field.related_model:
|
537
|
+
return field.related_model._general_manager_class
|
538
|
+
return type(field)
|