customfields.py 3.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. from django.utils.translation import gettext as _
  2. from drf_spectacular.types import OpenApiTypes
  3. from drf_spectacular.utils import extend_schema_field
  4. from rest_framework.fields import Field
  5. from rest_framework.serializers import ValidationError
  6. from core.models import ObjectType
  7. from extras.choices import CustomFieldTypeChoices
  8. from extras.models import CustomField
  9. from utilities.api import get_serializer_for_model
  10. #
  11. # Custom fields
  12. #
  13. class CustomFieldDefaultValues:
  14. """
  15. Return a dictionary of all CustomFields assigned to the parent model and their default values.
  16. """
  17. requires_context = True
  18. def __call__(self, serializer_field):
  19. self.model = serializer_field.parent.Meta.model
  20. # Retrieve the CustomFields for the parent model
  21. object_type = ObjectType.objects.get_for_model(self.model)
  22. fields = CustomField.objects.filter(object_types=object_type)
  23. # Populate the default value for each CustomField
  24. value = {}
  25. for field in fields:
  26. if field.default is not None:
  27. value[field.name] = field.default
  28. else:
  29. value[field.name] = None
  30. return value
  31. @extend_schema_field(OpenApiTypes.OBJECT)
  32. class CustomFieldsDataField(Field):
  33. def _get_custom_fields(self):
  34. """
  35. Cache CustomFields assigned to this model to avoid redundant database queries
  36. """
  37. if not hasattr(self, '_custom_fields'):
  38. object_type = ObjectType.objects.get_for_model(self.parent.Meta.model)
  39. self._custom_fields = CustomField.objects.filter(object_types=object_type)
  40. return self._custom_fields
  41. def to_representation(self, obj):
  42. # TODO: Fix circular import
  43. from utilities.api import get_serializer_for_model
  44. data = {}
  45. for cf in self._get_custom_fields():
  46. value = cf.deserialize(obj.get(cf.name))
  47. if value is not None and cf.type == CustomFieldTypeChoices.TYPE_OBJECT:
  48. serializer = get_serializer_for_model(cf.related_object_type.model_class())
  49. value = serializer(value, nested=True, context=self.parent.context).data
  50. elif value is not None and cf.type == CustomFieldTypeChoices.TYPE_MULTIOBJECT:
  51. serializer = get_serializer_for_model(cf.related_object_type.model_class())
  52. value = serializer(value, nested=True, many=True, context=self.parent.context).data
  53. data[cf.name] = value
  54. return data
  55. def to_internal_value(self, data):
  56. if type(data) is not dict:
  57. raise ValidationError(
  58. "Invalid data format. Custom field data must be passed as a dictionary mapping field names to their "
  59. "values."
  60. )
  61. # Serialize object and multi-object values
  62. for cf in self._get_custom_fields():
  63. if cf.name in data and data[cf.name] not in (None, []) and cf.type in (
  64. CustomFieldTypeChoices.TYPE_OBJECT,
  65. CustomFieldTypeChoices.TYPE_MULTIOBJECT
  66. ):
  67. serializer_class = get_serializer_for_model(cf.related_object_type.model_class())
  68. many = cf.type == CustomFieldTypeChoices.TYPE_MULTIOBJECT
  69. serializer = serializer_class(data=data[cf.name], nested=True, many=many, context=self.parent.context)
  70. if serializer.is_valid():
  71. data[cf.name] = [obj['id'] for obj in serializer.data] if many else serializer.data['id']
  72. else:
  73. raise ValidationError(_("Unknown related object(s): {name}").format(name=data[cf.name]))
  74. # If updating an existing instance, start with existing custom_field_data
  75. if self.parent.instance:
  76. data = {**self.parent.instance.custom_field_data, **data}
  77. return data