mirror of
https://github.com/inventree/InvenTree.git
synced 2025-12-16 17:28:11 +00:00
Add custom serializer field for ContentType with choices
This commit is contained in:
@@ -281,6 +281,25 @@ def getModelsWithMixin(mixin_class) -> list:
|
|||||||
return [x for x in db_models if x is not None and issubclass(x, mixin_class)]
|
return [x for x in db_models if x is not None and issubclass(x, mixin_class)]
|
||||||
|
|
||||||
|
|
||||||
|
def getModelChoicesWithMixin(
|
||||||
|
mixin_class, allow_null: bool = False
|
||||||
|
) -> list[tuple[str, str]]:
|
||||||
|
"""Return a list of model choices (app_label.model_name, verbose_name) for models that inherit from the given mixin class."""
|
||||||
|
choices = []
|
||||||
|
|
||||||
|
if allow_null:
|
||||||
|
choices.append((None, _('None')))
|
||||||
|
|
||||||
|
models = getModelsWithMixin(mixin_class)
|
||||||
|
|
||||||
|
for model in models:
|
||||||
|
label = f'{model._meta.app_label}.{model._meta.model_name}'
|
||||||
|
name = model._meta.verbose_name
|
||||||
|
choices.append((label, name))
|
||||||
|
|
||||||
|
return choices
|
||||||
|
|
||||||
|
|
||||||
def notify_responsible(
|
def notify_responsible(
|
||||||
instance,
|
instance,
|
||||||
sender,
|
sender,
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import InvenTree.ready
|
|||||||
from common.currency import currency_code_default, currency_code_mappings
|
from common.currency import currency_code_default, currency_code_mappings
|
||||||
from InvenTree.fields import InvenTreeRestURLField, InvenTreeURLField
|
from InvenTree.fields import InvenTreeRestURLField, InvenTreeURLField
|
||||||
from InvenTree.helpers import str2bool
|
from InvenTree.helpers import str2bool
|
||||||
|
from InvenTree.helpers_model import getModelChoicesWithMixin
|
||||||
|
|
||||||
from .setting.storages import StorageBackends
|
from .setting.storages import StorageBackends
|
||||||
|
|
||||||
@@ -716,3 +717,67 @@ class RemoteImageMixin(metaclass=serializers.SerializerMetaclass):
|
|||||||
raise ValidationError(_('Failed to download image from remote URL'))
|
raise ValidationError(_('Failed to download image from remote URL'))
|
||||||
|
|
||||||
return url
|
return url
|
||||||
|
|
||||||
|
|
||||||
|
class ContentTypeField(serializers.Field):
|
||||||
|
"""Serializer field which represents a ContentType as 'app_label.model_name'.
|
||||||
|
|
||||||
|
This field converts a ContentType instance to a string representation in the format 'app_label.model_name' during serialization, and vice versa during deserialization.
|
||||||
|
|
||||||
|
Additionally, a "mixin_class" can be supplied to the field, which will restrict the valid content types to only those models which inherit from the specified mixin.
|
||||||
|
"""
|
||||||
|
|
||||||
|
mixin_class = None
|
||||||
|
|
||||||
|
def __init__(self, *args, mixin_class=None, **kwargs):
|
||||||
|
"""Initialize the ContentTypeField.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
mixin_class: Optional mixin class to restrict valid content types.
|
||||||
|
"""
|
||||||
|
self.mixin_class = mixin_class
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
# Override the 'choices' field, to limit to the appropriate models
|
||||||
|
if self.mixin_class is not None:
|
||||||
|
self.choices = getModelChoicesWithMixin(
|
||||||
|
self.mixin_class, allow_null=kwargs.get('allow_null', False)
|
||||||
|
)
|
||||||
|
|
||||||
|
def to_representation(self, value):
|
||||||
|
"""Convert ContentType instance to string representation."""
|
||||||
|
return f'{value.app_label}.{value.model}'
|
||||||
|
|
||||||
|
def to_internal_value(self, data):
|
||||||
|
"""Convert string representation back to ContentType instance."""
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
|
content_type = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
app_label, model = data.split('.')
|
||||||
|
content_types = ContentType.objects.filter(app_label=app_label, model=model)
|
||||||
|
|
||||||
|
if content_types.exists() and content_types.count() == 1:
|
||||||
|
# Try exact match first
|
||||||
|
content_type = content_types.first()
|
||||||
|
else:
|
||||||
|
# Try lookup just on model name
|
||||||
|
content_types = ContentType.objects.filter(model=model)
|
||||||
|
if content_types.exists() and content_types.count() == 1:
|
||||||
|
content_type = content_types.first()
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
raise ValidationError(_('Invalid content type format'))
|
||||||
|
|
||||||
|
if content_type is None:
|
||||||
|
raise ValidationError(_('Content type not found'))
|
||||||
|
|
||||||
|
if self.mixin_class is not None:
|
||||||
|
model_class = content_type.model_class()
|
||||||
|
if not issubclass(model_class, self.mixin_class):
|
||||||
|
raise ValidationError(
|
||||||
|
_('Content type does not match required mixin class')
|
||||||
|
)
|
||||||
|
|
||||||
|
return content_type
|
||||||
|
|||||||
Reference in New Issue
Block a user