GeneralManager 0.10.2__py3-none-any.whl → 0.10.4__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.
@@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, Type, Callable, Union, Any, TypeVar, Literal
3
3
  from django.db import models
4
4
  from factory.django import DjangoModelFactory
5
5
  from general_manager.factory.factories import getFieldValue, getManyToManyFieldValue
6
-
6
+ from django.contrib.contenttypes.fields import GenericForeignKey
7
7
 
8
8
  if TYPE_CHECKING:
9
9
  from general_manager.interface.databaseInterface import (
@@ -29,16 +29,16 @@ class AutoFactory(DjangoModelFactory[modelsModel]):
29
29
  cls, strategy: Literal["build", "create"], params: dict[str, Any]
30
30
  ) -> models.Model | list[models.Model]:
31
31
  """
32
- Generates and populates a Django model instance or list of instances with automatic field value assignment.
32
+ Generates and populates one or more Django model instances with automatic field value assignment.
33
33
 
34
- Automatically assigns values to unset model fields, including handling custom and special fields, and processes many-to-many relationships after instance creation or building. Raises a ValueError if the model is not a subclass of Django's Model.
34
+ Automatically fills unset model fields, excluding generic foreign keys and auto-created fields, and handles custom and special fields as defined by the interface. After instance creation or building, processes many-to-many relationships. Raises a ValueError if the model is not a subclass of Django's Model.
35
35
 
36
- Args:
37
- strategy: Specifies whether to build (unsaved) or create (saved) the instance(s).
38
- params: Field values to use for instance generation; missing fields are auto-filled.
36
+ Parameters:
37
+ strategy (Literal["build", "create"]): Determines whether to build (unsaved) or create (saved) the instance(s).
38
+ params (dict[str, Any]): Field values to use for instance generation; missing fields are auto-filled.
39
39
 
40
40
  Returns:
41
- A single model instance or a list of model instances, depending on the input parameters and strategy.
41
+ models.Model or list[models.Model]: The generated model instance or list of instances.
42
42
  """
43
43
  cls._original_params = params
44
44
  model = cls._meta.model
@@ -50,6 +50,7 @@ class AutoFactory(DjangoModelFactory[modelsModel]):
50
50
  field
51
51
  for field in model._meta.get_fields()
52
52
  if field.name not in to_ignore_list
53
+ and not isinstance(field, GenericForeignKey)
53
54
  ]
54
55
  special_fields: list[models.Field[Any, Any]] = [
55
56
  getattr(model, field_name) for field_name in field_name_list
@@ -72,7 +73,7 @@ class AutoFactory(DjangoModelFactory[modelsModel]):
72
73
 
73
74
  obj: list[models.Model] | models.Model = super()._generate(strategy, params)
74
75
  if isinstance(obj, list):
75
- for item in obj: # type: ignore
76
+ for item in obj:
76
77
  if not isinstance(item, models.Model):
77
78
  raise ValueError("Model must be a type")
78
79
  cls._handleManyToManyFieldsAfterCreation(item, params)
@@ -85,9 +86,9 @@ class AutoFactory(DjangoModelFactory[modelsModel]):
85
86
  cls, obj: models.Model, attrs: dict[str, Any]
86
87
  ) -> None:
87
88
  """
88
- Sets many-to-many field values on a Django model instance after creation.
89
+ Assigns related objects to all many-to-many fields of a Django model instance after it has been created.
89
90
 
90
- For each many-to-many field, assigns related objects from the provided attributes if available; otherwise, generates values using `getManyToManyFieldValue`. The related objects are set using the Django ORM's `set` method.
91
+ For each many-to-many field, sets the related objects from the provided attributes if available; otherwise, generates related objects automatically. Uses the Django ORM's `set` method to establish the relationships.
91
92
  """
92
93
  for field in obj._meta.many_to_many:
93
94
  if field.name in attrs:
@@ -101,10 +102,10 @@ class AutoFactory(DjangoModelFactory[modelsModel]):
101
102
  def _adjust_kwargs(cls, **kwargs: dict[str, Any]) -> dict[str, Any]:
102
103
  # Remove ManyToMany fields from kwargs before object creation
103
104
  """
104
- Removes many-to-many field entries from keyword arguments before model instance creation.
105
+ Removes many-to-many fields from the provided keyword arguments before creating or building a model instance.
105
106
 
106
107
  Returns:
107
- The keyword arguments dictionary with many-to-many fields removed.
108
+ dict[str, Any]: The keyword arguments with any many-to-many fields excluded.
108
109
  """
109
110
  model: Type[models.Model] = cls._meta.model
110
111
  m2m_fields = {field.name for field in model._meta.many_to_many}
@@ -117,15 +118,12 @@ class AutoFactory(DjangoModelFactory[modelsModel]):
117
118
  cls, model_class: Type[models.Model], *args: list[Any], **kwargs: dict[str, Any]
118
119
  ) -> models.Model | list[models.Model]:
119
120
  """
120
- Creates and saves a Django model instance or instances, applying optional adjustment logic.
121
-
122
- If an adjustment method is defined, it is used to generate or modify field values before creation. Otherwise, the model is instantiated and saved with the provided attributes.
121
+ Create and save a Django model instance or multiple instances, optionally applying custom adjustment logic to field values.
123
122
 
124
- Args:
125
- model_class: The Django model class to instantiate.
123
+ If an adjustment method is defined, it is used to generate or modify field values before instance creation. Otherwise, the model is instantiated and saved with the provided attributes.
126
124
 
127
125
  Returns:
128
- A saved model instance or a list of instances.
126
+ A saved model instance or a list of saved instances.
129
127
  """
130
128
  kwargs = cls._adjust_kwargs(**kwargs)
131
129
  if cls._adjustmentMethod is not None:
@@ -137,9 +135,12 @@ class AutoFactory(DjangoModelFactory[modelsModel]):
137
135
  cls, model_class: Type[models.Model], *args: list[Any], **kwargs: dict[str, Any]
138
136
  ) -> models.Model | list[models.Model]:
139
137
  """
140
- Constructs an unsaved instance or list of instances of the given Django model class.
138
+ Builds an unsaved instance or list of instances of the specified Django model class.
141
139
 
142
- If an adjustment method is defined, it is used to generate or modify field values before building the instance(s). Many-to-many fields are removed from the keyword arguments prior to instantiation.
140
+ If an adjustment method is defined, it is used to generate or modify field values before building. Many-to-many fields are excluded from the keyword arguments prior to instantiation.
141
+
142
+ Returns:
143
+ models.Model or list[models.Model]: The unsaved model instance or list of instances.
143
144
  """
144
145
  kwargs = cls._adjust_kwargs(**kwargs)
145
146
  if cls._adjustmentMethod is not None:
@@ -153,9 +154,9 @@ class AutoFactory(DjangoModelFactory[modelsModel]):
153
154
  cls, model_class: Type[models.Model], **kwargs: dict[str, Any]
154
155
  ) -> models.Model:
155
156
  """
156
- Creates and saves a Django model instance with the provided field values.
157
+ Create, validate, and save a Django model instance with the specified field values.
157
158
 
158
- Initializes the model, assigns attributes from keyword arguments, validates the instance using `full_clean()`, and saves it to the database.
159
+ Initializes the model, assigns attributes from keyword arguments, performs validation with `full_clean()`, and saves the instance to the database.
159
160
 
160
161
  Returns:
161
162
  The saved Django model instance.
@@ -172,14 +173,14 @@ class AutoFactory(DjangoModelFactory[modelsModel]):
172
173
  cls, model_class: Type[models.Model], **kwargs: dict[str, Any]
173
174
  ) -> models.Model:
174
175
  """
175
- Constructs an unsaved instance of the specified Django model with provided field values.
176
+ Constructs an unsaved Django model instance with the specified field values.
176
177
 
177
- Args:
178
- model_class: The Django model class to instantiate.
179
- **kwargs: Field values to set on the model instance.
178
+ Parameters:
179
+ model_class (Type[models.Model]): The Django model class to instantiate.
180
+ **kwargs: Field values to assign to the model instance.
180
181
 
181
182
  Returns:
182
- An unsaved Django model instance with attributes set from kwargs.
183
+ models.Model: An unsaved instance of the specified model with attributes set from kwargs.
183
184
  """
184
185
  obj = model_class()
185
186
  for field, value in kwargs.items():
@@ -191,17 +192,16 @@ class AutoFactory(DjangoModelFactory[modelsModel]):
191
192
  cls, use_creation_method: bool, params: dict[str, Any]
192
193
  ) -> models.Model | list[models.Model]:
193
194
  """
194
- Generates one or more model instances using the adjustment method for field values.
195
+ Creates or builds one or more model instances using the adjustment method to generate field values.
195
196
 
196
- If the adjustment method returns a single dictionary, creates or builds a single model instance.
197
- If it returns a list of dictionaries, creates or builds multiple instances accordingly.
197
+ If the adjustment method returns a single dictionary, a single instance is created or built. If it returns a list of dictionaries, multiple instances are created or built accordingly.
198
198
 
199
- Args:
200
- use_creation_method: If True, saves instances to the database; otherwise, builds unsaved instances.
201
- params: Parameters to pass to the adjustment method for generating field values.
199
+ Parameters:
200
+ use_creation_method (bool): If True, instances are saved to the database; if False, instances are built but not saved.
201
+ params (dict[str, Any]): Arguments passed to the adjustment method for generating field values.
202
202
 
203
203
  Returns:
204
- A single model instance or a list of model instances, depending on the adjustment method's output.
204
+ models.Model or list[models.Model]: The created or built model instance(s).
205
205
 
206
206
  Raises:
207
207
  ValueError: If the adjustment method is not defined.
@@ -1,8 +1,8 @@
1
1
  from __future__ import annotations
2
- from typing import Any, cast
2
+ from typing import Any, cast, TYPE_CHECKING
3
3
  from factory.declarations import LazyFunction
4
4
  from factory.faker import Faker
5
- import exrex # type: ignore
5
+ import exrex
6
6
  from django.db import models
7
7
  from django.core.validators import RegexValidator
8
8
  import random
@@ -11,12 +11,17 @@ from general_manager.measurement.measurement import Measurement
11
11
  from general_manager.measurement.measurementField import MeasurementField
12
12
  from datetime import date, datetime, time, timezone
13
13
 
14
+ if TYPE_CHECKING:
15
+ from general_manager.factory.autoFactory import AutoFactory
14
16
 
15
- def getFieldValue(field: models.Field[Any, Any] | models.ForeignObjectRel) -> object:
17
+
18
+ def getFieldValue(
19
+ field: models.Field[Any, Any] | models.ForeignObjectRel,
20
+ ) -> object:
16
21
  """
17
- Generates an appropriate value for a given Django model field for use in testing or data factories.
22
+ Generate a suitable fake or factory value for a given Django model field, for use in tests or data factories.
18
23
 
19
- If the field allows null values, there is a 10% chance of returning None. Handles a wide range of Django field types, including measurement, text, numeric, date/time, boolean, relational, and specialized fields, returning a suitable fake or factory-generated value for each. For relational fields (OneToOneField and ForeignKey), attempts to use a factory if available or selects a random existing instance; raises ValueError if neither is possible. Returns None for unsupported field types.
24
+ Returns a value appropriate for the field type, including support for measurement, text, numeric, date/time, boolean, email, URL, IP address, UUID, duration, and character fields (with regex support). For relational fields (OneToOneField and ForeignKey), attempts to use a related model factory or select a random existing instance; raises ValueError if neither is available. Returns None for unsupported field types or with a 10% chance if the field allows null values.
20
25
  """
21
26
  if field.null:
22
27
  if random.choice([True] + 9 * [False]):
@@ -62,78 +67,103 @@ def getFieldValue(field: models.Field[Any, Any] | models.ForeignObjectRel) -> ob
62
67
  return cast(date, Faker("date_between", start_date="-1y", end_date="today"))
63
68
  elif isinstance(field, models.BooleanField):
64
69
  return cast(bool, Faker("pybool"))
70
+ elif isinstance(field, models.EmailField):
71
+ return cast(str, Faker("email"))
72
+ elif isinstance(field, models.URLField):
73
+ return cast(str, Faker("url"))
74
+ elif isinstance(field, models.GenericIPAddressField):
75
+ return cast(str, Faker("ipv4"))
76
+ elif isinstance(field, models.UUIDField):
77
+ return cast(str, Faker("uuid4"))
78
+ elif isinstance(field, models.DurationField):
79
+ return cast(time, Faker("time_delta"))
80
+ elif isinstance(field, models.CharField):
81
+ max_length = field.max_length or 100
82
+ # Check for RegexValidator
83
+ regex = None
84
+ for validator in field.validators:
85
+ if isinstance(validator, RegexValidator):
86
+ regex = getattr(validator.regex, "pattern", None)
87
+ break
88
+ if regex:
89
+ # Use exrex to generate a string matching the regex
90
+ return LazyFunction(lambda: exrex.getone(regex))
91
+ else:
92
+ return cast(str, Faker("text", max_nb_chars=max_length))
65
93
  elif isinstance(field, models.OneToOneField):
66
- if hasattr(field.related_model, "_general_manager_class"):
67
- related_factory = field.related_model._general_manager_class.Factory
94
+ related_model = getRelatedModel(field)
95
+ if hasattr(related_model, "_general_manager_class"):
96
+ related_factory = related_model._general_manager_class.Factory # type: ignore
68
97
  return related_factory()
69
98
  else:
70
99
  # If no factory exists, pick a random existing instance
71
- related_instances = list(field.related_model.objects.all())
100
+ related_instances = list(related_model.objects.all())
72
101
  if related_instances:
73
102
  return LazyFunction(lambda: random.choice(related_instances))
74
103
  else:
75
104
  raise ValueError(
76
- f"No factory found for {field.related_model.__name__} and no instances found"
105
+ f"No factory found for {related_model.__name__} and no instances found"
77
106
  )
78
107
  elif isinstance(field, models.ForeignKey):
108
+ related_model = getRelatedModel(field)
79
109
  # Create or get an instance of the related model
80
- if hasattr(field.related_model, "_general_manager_class"):
110
+ if hasattr(related_model, "_general_manager_class"):
81
111
  create_a_new_instance = random.choice([True, True, False])
82
112
  if not create_a_new_instance:
83
- existing_instances = list(field.related_model.objects.all())
113
+ existing_instances = list(related_model.objects.all())
84
114
  if existing_instances:
85
115
  # Pick a random existing instance
86
116
  return LazyFunction(lambda: random.choice(existing_instances))
87
117
 
88
- related_factory = field.related_model._general_manager_class.Factory
118
+ related_factory = related_model._general_manager_class.Factory # type: ignore
89
119
  return related_factory()
90
120
 
91
121
  else:
92
122
  # If no factory exists, pick a random existing instance
93
- related_instances = list(field.related_model.objects.all())
123
+ related_instances = list(related_model.objects.all())
94
124
  if related_instances:
95
125
  return LazyFunction(lambda: random.choice(related_instances))
96
126
  else:
97
127
  raise ValueError(
98
- f"No factory found for {field.related_model.__name__} and no instances found"
128
+ f"No factory found for {related_model.__name__} and no instances found"
99
129
  )
100
- elif isinstance(field, models.EmailField):
101
- return cast(str, Faker("email"))
102
- elif isinstance(field, models.URLField):
103
- return cast(str, Faker("url"))
104
- elif isinstance(field, models.GenericIPAddressField):
105
- return cast(str, Faker("ipv4"))
106
- elif isinstance(field, models.UUIDField):
107
- return cast(str, Faker("uuid4"))
108
- elif isinstance(field, models.DurationField):
109
- return cast(time, Faker("time_delta"))
110
- elif isinstance(field, models.CharField):
111
- max_length = field.max_length or 100
112
- # Check for RegexValidator
113
- regex = None
114
- for validator in field.validators:
115
- if isinstance(validator, RegexValidator):
116
- regex = getattr(validator.regex, "pattern", None)
117
- break
118
- if regex:
119
- # Use exrex to generate a string matching the regex
120
- return LazyFunction(lambda: exrex.getone(regex)) # type: ignore
121
- else:
122
- return cast(str, Faker("text", max_nb_chars=max_length))
123
130
  else:
124
- return None # For unsupported field types
131
+ return None
132
+
133
+
134
+ def getRelatedModel(
135
+ field: models.ForeignObjectRel | models.Field[Any, Any],
136
+ ) -> type[models.Model]:
137
+ """
138
+ Return the related model class for a given Django relational field.
139
+
140
+ Raises:
141
+ ValueError: If the field does not have a related model defined.
142
+ """
143
+ related_model = field.related_model
144
+ if related_model is None:
145
+ raise ValueError(f"Field {field.name} does not have a related model defined.")
146
+ if related_model == "self":
147
+ related_model = field.model
148
+ return related_model # For unsupported field types
125
149
 
126
150
 
127
151
  def getManyToManyFieldValue(
128
152
  field: models.ManyToManyField,
129
153
  ) -> list[models.Model]:
130
154
  """
131
- Returns a list of instances for a ManyToMany field.
155
+ Generate a list of model instances to associate with a ManyToMany field for testing or factory purposes.
156
+
157
+ If a related model factory is available, creates new instances as needed. Otherwise, selects from existing instances. Raises a ValueError if neither a factory nor existing instances are available.
158
+
159
+ Returns:
160
+ list[models.Model]: A list of related model instances for the ManyToMany field.
132
161
  """
133
162
  related_factory = None
134
- related_instances = list(field.related_model.objects.all())
135
- if hasattr(field.related_model, "_general_manager_class"):
136
- related_factory = field.related_model._general_manager_class.Factory
163
+ related_model = getRelatedModel(field)
164
+ related_instances = list(related_model.objects.all())
165
+ if hasattr(related_model, "_general_manager_class"):
166
+ related_factory = related_model._general_manager_class.Factory # type: ignore
137
167
 
138
168
  min_required = 0 if field.blank else 1
139
169
  number_of_instances = random.randint(min_required, 10)
@@ -158,5 +188,5 @@ def getManyToManyFieldValue(
158
188
  return existing_instances
159
189
  else:
160
190
  raise ValueError(
161
- f"No factory found for {field.related_model.__name__} and no instances found"
191
+ f"No factory found for {related_model.__name__} and no instances found"
162
192
  )
@@ -69,12 +69,12 @@ class InterfaceBase(ABC):
69
69
  **kwargs: dict[str, Any],
70
70
  ) -> dict[str, Any]:
71
71
  """
72
- Parses and validates input arguments into a structured identification dictionary.
72
+ Parse and validate input arguments into a dictionary of input field values.
73
73
 
74
- Converts positional and keyword arguments into a dictionary keyed by input field names, normalizing argument names and ensuring all required fields are present. Processes input fields in dependency order, casting and validating each value. Raises a `TypeError` for unexpected or missing arguments and a `ValueError` if circular dependencies among input fields are detected.
74
+ Positional and keyword arguments are mapped to input field names, with normalization of argument names (e.g., removing trailing `_id`). Ensures all required fields are present and no unexpected arguments are provided. Processes input fields in dependency order, casting and validating each value. Raises a `TypeError` for missing or unexpected arguments and a `ValueError` if circular dependencies are detected.
75
75
 
76
76
  Returns:
77
- A dictionary mapping input field names to their validated and cast values.
77
+ dict[str, Any]: A dictionary mapping input field names to their validated and cast values.
78
78
  """
79
79
  identification = {}
80
80
  kwargs = args_to_kwargs(args, self.input_fields.keys(), kwargs)
@@ -1,5 +1,5 @@
1
1
  from __future__ import annotations
2
- from typing import Type, Any, Callable, TYPE_CHECKING, TypeVar, Generic
2
+ from typing import Type, Any, Callable, TYPE_CHECKING, TypeVar, Generic, cast
3
3
  from django.db import models
4
4
  from datetime import datetime, timedelta
5
5
  from general_manager.measurement.measurement import Measurement
@@ -25,13 +25,13 @@ from general_manager.interface.models import (
25
25
  GeneralManagerModel,
26
26
  getFullCleanMethode,
27
27
  )
28
+ from django.contrib.contenttypes.fields import GenericForeignKey
28
29
 
29
30
  if TYPE_CHECKING:
30
31
  from general_manager.rule.rule import Rule
31
32
 
32
33
  modelsModel = TypeVar("modelsModel", bound=models.Model)
33
34
 
34
-
35
35
  MODEL_TYPE = TypeVar("MODEL_TYPE", bound=GeneralManagerBasisModel)
36
36
 
37
37
 
@@ -43,12 +43,12 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
43
43
  self,
44
44
  *args: list[Any],
45
45
  search_date: datetime | None = None,
46
- **kwargs: dict[str, Any],
46
+ **kwargs: Any,
47
47
  ):
48
48
  """
49
- Initialize the interface and load the associated model instance.
50
-
51
- If `search_date` is provided, loads the historical record as of that date; otherwise, loads the current record.
49
+ Initialize the interface and load the associated model instance by primary key.
50
+
51
+ If a `search_date` is provided, retrieves the historical record as of that date; otherwise, loads the current record.
52
52
  """
53
53
  super().__init__(*args, **kwargs)
54
54
  self.pk = self.identification["id"]
@@ -135,10 +135,10 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
135
135
  @classmethod
136
136
  def getAttributeTypes(cls) -> dict[str, AttributeTypedDict]:
137
137
  """
138
- Return a dictionary mapping attribute names to metadata describing their types and properties.
139
-
140
- 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.
141
-
138
+ Return a dictionary mapping each attribute name of the model to its type information and metadata.
139
+
140
+ The returned dictionary includes all standard model fields, custom fields, foreign keys, many-to-many, and reverse relation fields, excluding any GenericForeignKey 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.
141
+
142
142
  Returns:
143
143
  dict[str, AttributeTypedDict]: Mapping of attribute names to their type information and metadata.
144
144
  """
@@ -163,7 +163,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
163
163
  fields: dict[str, AttributeTypedDict] = {}
164
164
  field_name_list, to_ignore_list = cls.handleCustomFields(cls._model)
165
165
  for field_name in field_name_list:
166
- field: models.Field = getattr(cls._model, field_name)
166
+ field = cast(models.Field, getattr(cls._model, field_name))
167
167
  fields[field_name] = {
168
168
  "type": type(field),
169
169
  "is_derived": False,
@@ -174,7 +174,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
174
174
 
175
175
  for field_name in cls.__getModelFields():
176
176
  if field_name not in to_ignore_list:
177
- field: models.Field = getattr(cls._model, field_name).field
177
+ field = cast(models.Field, getattr(cls._model, field_name).field)
178
178
  fields[field_name] = {
179
179
  "type": type(field),
180
180
  "is_derived": False,
@@ -185,20 +185,27 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
185
185
 
186
186
  for field_name in cls.__getForeignKeyFields():
187
187
  field = cls._model._meta.get_field(field_name)
188
+ if isinstance(field, GenericForeignKey):
189
+ continue
188
190
  related_model = field.related_model
191
+ if related_model == "self":
192
+ related_model = cls._model
189
193
  if related_model and hasattr(
190
194
  related_model,
191
195
  "_general_manager_class",
192
196
  ):
193
- related_model = related_model._general_manager_class
197
+ related_model = related_model._general_manager_class # type: ignore
194
198
 
195
199
  elif related_model is not None:
200
+ default = None
201
+ if hasattr(field, "default"):
202
+ default = field.default # type: ignore
196
203
  fields[field_name] = {
197
204
  "type": related_model,
198
205
  "is_derived": False,
199
206
  "is_required": not field.null,
200
207
  "is_editable": field.editable,
201
- "default": field.default,
208
+ "default": default,
202
209
  }
203
210
 
204
211
  for field_name, field_call in [
@@ -212,11 +219,17 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
212
219
  raise ValueError("Field name already exists.")
213
220
  field = cls._model._meta.get_field(field_name)
214
221
  related_model = cls._model._meta.get_field(field_name).related_model
222
+ if related_model == "self":
223
+ related_model = cls._model
224
+ if isinstance(field, GenericForeignKey):
225
+ continue
226
+
215
227
  if related_model and hasattr(
216
228
  related_model,
217
229
  "_general_manager_class",
218
230
  ):
219
- related_model = related_model._general_manager_class
231
+ related_model = related_model._general_manager_class # type: ignore
232
+
220
233
  if related_model is not None:
221
234
  fields[f"{field_name}_list"] = {
222
235
  "type": related_model,
@@ -234,9 +247,12 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
234
247
  @classmethod
235
248
  def getAttributes(cls) -> dict[str, Callable[[DBBasedInterface], Any]]:
236
249
  """
237
- Returns a mapping of attribute names to callables that retrieve their values from a DBBasedInterface instance.
238
-
239
- 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.
250
+ Return a dictionary mapping attribute names to callables that retrieve values from a DBBasedInterface instance.
251
+
252
+ The mapping includes accessors for custom fields, standard model fields, foreign keys, many-to-many relations, and reverse relations. For related models with a general manager class, the accessor returns an instance of that class; otherwise, it returns the related object or queryset. Raises a ValueError if a field name conflict is detected.
253
+
254
+ Returns:
255
+ dict: A dictionary where keys are attribute names and values are callables that extract the corresponding value from a DBBasedInterface instance.
240
256
  """
241
257
  field_values: dict[str, Any] = {}
242
258
 
@@ -258,7 +274,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
258
274
  related_model,
259
275
  "_general_manager_class",
260
276
  ):
261
- generalManagerClass = related_model._general_manager_class
277
+ generalManagerClass = related_model._general_manager_class # type: ignore
262
278
  field_values[f"{field_name}"] = (
263
279
  lambda self, field_name=field_name, manager_class=generalManagerClass: manager_class(
264
280
  getattr(self._instance, field_name).pk
@@ -322,13 +338,13 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
322
338
  @staticmethod
323
339
  def _getCustomFields(model: Type[models.Model] | models.Model) -> list[str]:
324
340
  """
325
- Returns a list of custom field names defined directly on the model class.
326
-
327
- Args:
341
+ Return a list of custom field names defined directly as class attributes on the given Django model.
342
+
343
+ Parameters:
328
344
  model: The Django model class or instance to inspect.
329
-
345
+
330
346
  Returns:
331
- A list of field names that are defined as class attributes on the model, not via Django's meta system.
347
+ A list of field names for fields declared directly on the model class, excluding those defined via Django's meta system.
332
348
  """
333
349
  return [
334
350
  field.name
@@ -337,11 +353,11 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
337
353
  ]
338
354
 
339
355
  @classmethod
340
- def __getModelFields(cls):
356
+ def __getModelFields(cls) -> list[str]:
341
357
  """
342
- Returns a list of model field names that are not many-to-many or related fields.
343
-
344
- Excludes fields representing many-to-many relationships and related models.
358
+ Return a list of field names for the model that are neither many-to-many nor related fields.
359
+
360
+ Fields representing many-to-many relationships or relations to other models are excluded from the result.
345
361
  """
346
362
  return [
347
363
  field.name
@@ -350,9 +366,9 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
350
366
  ]
351
367
 
352
368
  @classmethod
353
- def __getForeignKeyFields(cls):
369
+ def __getForeignKeyFields(cls) -> list[str]:
354
370
  """
355
- Returns a list of field names for all foreign key and one-to-one relations on the model.
371
+ Return a list of field names for all foreign key and one-to-one relations on the model, excluding generic foreign keys.
356
372
  """
357
373
  return [
358
374
  field.name
@@ -361,11 +377,11 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
361
377
  ]
362
378
 
363
379
  @classmethod
364
- def __getManyToManyFields(cls):
380
+ def __getManyToManyFields(cls) -> list[tuple[str, str]]:
365
381
  """
366
- Returns a list of tuples containing the names of all many-to-many fields on the model.
367
-
368
- Each tuple consists of the field name repeated twice.
382
+ Return a list of tuples representing all many-to-many fields on the model.
383
+
384
+ Each tuple contains the field name twice. Fields that are generic foreign keys are excluded.
369
385
  """
370
386
  return [
371
387
  (field.name, field.name)
@@ -374,11 +390,11 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
374
390
  ]
375
391
 
376
392
  @classmethod
377
- def __getReverseRelations(cls):
393
+ def __getReverseRelations(cls) -> list[tuple[str, str]]:
378
394
  """
379
- Returns a list of tuples representing reverse one-to-many relations for the model.
380
-
381
- Each tuple contains the related field's name and its default related accessor name.
395
+ Return a list of reverse one-to-many relations for the model, excluding generic foreign keys.
396
+
397
+ Each tuple contains the related field's name and its default related accessor name (e.g., `fieldname_set`).
382
398
  """
383
399
  return [
384
400
  (field.name, f"{field.name}_set")
@@ -485,11 +501,15 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
485
501
  @classmethod
486
502
  def getFieldType(cls, field_name: str) -> type:
487
503
  """
488
- Returns the type associated with the specified field name.
489
-
490
- 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.
504
+ Return the type associated with a given model field name.
505
+
506
+ 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.
491
507
  """
492
508
  field = cls._model._meta.get_field(field_name)
493
- if field.is_relation and field.related_model:
494
- return field.related_model._general_manager_class
509
+ if (
510
+ field.is_relation
511
+ and field.related_model
512
+ and hasattr(field.related_model, "_general_manager_class")
513
+ ):
514
+ return field.related_model._general_manager_class # type: ignore
495
515
  return type(field)