customfields.py 5.5 KB

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