customfields.py 5.6 KB

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