44from typing import Union
55
66from django .core .exceptions import ValidationError
7+ from django .db .models import Model
78from django .utils .translation import gettext_lazy as _
89
910import common .icons
1011from common .settings import get_global_setting
1112
1213
13- def get_model_types (mixin_class ):
14- """Return a list of valid attachment model choices."""
14+ def get_mixin_models (mixin_class : type [Model ]):
15+ """Return a list of Django model classes that inherit from the given mixin.
16+
17+ This function lazily imports the helper to avoid circular imports.
18+
19+ Args:
20+ mixin_class: The mixin class to search for.
21+
22+ Returns:
23+ A list of Django model classes that subclass the provided mixin.
24+ """
25+ # Lazy import to prevent circular dependency issues
1526 import InvenTree .helpers_model
1627
1728 return list (InvenTree .helpers_model .getModelsWithMixin (mixin_class ))
1829
1930
20- def get_model_options (mixin_class ):
21- """Return a list of options for models."""
22- return [
23- (model .__name__ .lower (), model ._meta .verbose_name )
24- for model in get_model_types (mixin_class )
25- ]
31+ def get_model_options_for_mixin (mixin_class : type [Model ]):
32+ """Build choice options for all models that inherit from a given mixin.
33+
34+ Each option is a tuple of:
35+ - key: the lowercase model class name (e.g. 'part', 'stockitem')
36+ - label: the model's verbose name for display
37+
38+ Args:
39+ mixin_class: The mixin class used to filter models.
40+
41+ Returns:
42+ A list of (key, label) tuples suitable for form field choices.
43+ """
44+ options = []
45+ for model in get_mixin_models (mixin_class ):
46+ key = model .__name__ .lower ()
47+ label = str (model ._meta .verbose_name )
48+ options .append ((key , label ))
49+ return options
2650
2751
2852def attachment_model_options ():
2953 """Return a list of valid attachment model choices."""
3054 import InvenTree .models
3155
32- return get_model_options (InvenTree .models .InvenTreeAttachmentMixin )
56+ return get_model_options_for_mixin (InvenTree .models .InvenTreeAttachmentMixin )
57+
3358
59+ def resolve_model_from_label (label : str , mixin_class : type [Model ]):
60+ """Resolve a model class by a case-insensitive label among models inheriting a mixin.
3461
35- def get_model_class_from_label (label : str , mixin_class ):
36- """Returns the model class matching the given label from a set of models inheriting a specific mixin."""
62+ Args:
63+ label: The identifier string to match (typically the model class name in lowercase).
64+ mixin_class: The mixin class that candidate models must inherit from.
65+
66+ Returns:
67+ The Django model class that matches the provided label.
68+ """
3769 if not label :
3870 raise ValidationError (_ ('No attachment model type provided' ))
3971
40- for model in get_model_types (mixin_class ):
41- if model .__name__ .lower () == label .lower ():
72+ label_lc = label .lower ()
73+
74+ for model in get_mixin_models (mixin_class ):
75+ if model .__name__ .lower () == label_lc :
4276 return model
4377
4478 raise ValidationError (_ ('Invalid attachment model type' ) + f": '{ label } '" )
@@ -49,7 +83,8 @@ def validate_attachment_model_type(value):
4983 import InvenTree .models
5084
5185 model_names = [
52- el [0 ] for el in get_model_options (InvenTree .models .InvenTreeAttachmentMixin )
86+ el [0 ]
87+ for el in get_model_options_for_mixin (InvenTree .models .InvenTreeAttachmentMixin )
5388 ]
5489 if value not in model_names :
5590 raise ValidationError ('Model type does not support attachments' )
@@ -82,7 +117,7 @@ def limit_image_content_types():
82117 import InvenTree .models
83118
84119 allowed_models = [
85- m [0 ] for m in get_model_options (InvenTree .models .InvenTreeImageMixin )
120+ m [0 ] for m in get_model_options_for_mixin (InvenTree .models .InvenTreeImageMixin )
86121 ]
87122
88123 return {'model__in' : allowed_models }
0 commit comments