customfields.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. from datetime import datetime
  2. from django.contrib.contenttypes.models import ContentType
  3. from django.db import transaction
  4. from rest_framework import serializers
  5. from rest_framework.exceptions import ValidationError
  6. from extras.constants import *
  7. from extras.models import CustomField, CustomFieldChoice, CustomFieldValue
  8. from utilities.api import ValidatedModelSerializer
  9. #
  10. # Custom fields
  11. #
  12. class CustomFieldsSerializer(serializers.BaseSerializer):
  13. def to_representation(self, obj):
  14. return obj
  15. def to_internal_value(self, data):
  16. content_type = ContentType.objects.get_for_model(self.parent.Meta.model)
  17. custom_fields = {field.name: field for field in CustomField.objects.filter(obj_type=content_type)}
  18. for field_name, value in data.items():
  19. try:
  20. cf = custom_fields[field_name]
  21. except KeyError:
  22. raise ValidationError(
  23. "Invalid custom field for {} objects: {}".format(content_type, field_name)
  24. )
  25. # Data validation
  26. if value not in [None, '']:
  27. # Validate integer
  28. if cf.type == CF_TYPE_INTEGER:
  29. try:
  30. int(value)
  31. except ValueError:
  32. raise ValidationError(
  33. "Invalid value for integer field {}: {}".format(field_name, value)
  34. )
  35. # Validate boolean
  36. if cf.type == CF_TYPE_BOOLEAN and value not in [True, False, 1, 0]:
  37. raise ValidationError(
  38. "Invalid value for boolean field {}: {}".format(field_name, value)
  39. )
  40. # Validate date
  41. if cf.type == CF_TYPE_DATE:
  42. try:
  43. datetime.strptime(value, '%Y-%m-%d')
  44. except ValueError:
  45. raise ValidationError(
  46. "Invalid date for field {}: {}. (Required format is YYYY-MM-DD.)".format(field_name, value)
  47. )
  48. # Validate selected choice
  49. if cf.type == CF_TYPE_SELECT:
  50. try:
  51. value = int(value)
  52. except ValueError:
  53. raise ValidationError(
  54. "{}: Choice selections must be passed as integers.".format(field_name)
  55. )
  56. valid_choices = [c.pk for c in cf.choices.all()]
  57. if value not in valid_choices:
  58. raise ValidationError(
  59. "Invalid choice for field {}: {}".format(field_name, value)
  60. )
  61. elif cf.required:
  62. raise ValidationError("Required field {} cannot be empty.".format(field_name))
  63. # Check for missing required fields
  64. missing_fields = []
  65. for field_name, field in custom_fields.items():
  66. if field.required and field_name not in data:
  67. missing_fields.append(field_name)
  68. if missing_fields:
  69. raise ValidationError("Missing required fields: {}".format(u", ".join(missing_fields)))
  70. return data
  71. class CustomFieldModelSerializer(ValidatedModelSerializer):
  72. """
  73. Extends ModelSerializer to render any CustomFields and their values associated with an object.
  74. """
  75. custom_fields = CustomFieldsSerializer(required=False)
  76. def __init__(self, *args, **kwargs):
  77. def _populate_custom_fields(instance, fields):
  78. custom_fields = {f.name: None for f in fields}
  79. for cfv in instance.custom_field_values.all():
  80. if cfv.field.type == CF_TYPE_SELECT:
  81. custom_fields[cfv.field.name] = CustomFieldChoiceSerializer(cfv.value).data
  82. else:
  83. custom_fields[cfv.field.name] = cfv.value
  84. instance.custom_fields = custom_fields
  85. super().__init__(*args, **kwargs)
  86. if self.instance is not None:
  87. # Retrieve the set of CustomFields which apply to this type of object
  88. content_type = ContentType.objects.get_for_model(self.Meta.model)
  89. fields = CustomField.objects.filter(obj_type=content_type)
  90. # Populate CustomFieldValues for each instance from database
  91. try:
  92. for obj in self.instance:
  93. _populate_custom_fields(obj, fields)
  94. except TypeError:
  95. _populate_custom_fields(self.instance, fields)
  96. def _save_custom_fields(self, instance, custom_fields):
  97. content_type = ContentType.objects.get_for_model(self.Meta.model)
  98. for field_name, value in custom_fields.items():
  99. custom_field = CustomField.objects.get(name=field_name)
  100. CustomFieldValue.objects.update_or_create(
  101. field=custom_field,
  102. obj_type=content_type,
  103. obj_id=instance.pk,
  104. defaults={'serialized_value': custom_field.serialize_value(value)},
  105. )
  106. def create(self, validated_data):
  107. custom_fields = validated_data.pop('custom_fields', None)
  108. with transaction.atomic():
  109. instance = super().create(validated_data)
  110. # Save custom fields
  111. if custom_fields is not None:
  112. self._save_custom_fields(instance, custom_fields)
  113. instance.custom_fields = custom_fields
  114. return instance
  115. def update(self, instance, validated_data):
  116. custom_fields = validated_data.pop('custom_fields', None)
  117. with transaction.atomic():
  118. instance = super().update(instance, validated_data)
  119. # Save custom fields
  120. if custom_fields is not None:
  121. self._save_custom_fields(instance, custom_fields)
  122. instance.custom_fields = custom_fields
  123. return instance
  124. class CustomFieldChoiceSerializer(serializers.ModelSerializer):
  125. """
  126. Imitate utilities.api.ChoiceFieldSerializer
  127. """
  128. value = serializers.IntegerField(source='pk')
  129. label = serializers.CharField(source='value')
  130. class Meta:
  131. model = CustomFieldChoice
  132. fields = ['value', 'label']