serializers.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. from rest_framework import serializers
  2. from rest_framework.exceptions import ValidationError
  3. from desecapi.models import Domain, Donation, User, RR, RRset
  4. from djoser import serializers as djoserSerializers
  5. from django.db import models, transaction
  6. import django.core.exceptions
  7. from rest_framework_bulk import BulkListSerializer, BulkSerializerMixin
  8. class RRSerializer(serializers.ModelSerializer):
  9. class Meta:
  10. model = RR
  11. fields = ('content',)
  12. def to_representation(self, instance):
  13. return instance.content
  14. def to_internal_value(self, data):
  15. if not isinstance(data, dict):
  16. data = {'content': data}
  17. return self.Meta.model(**data)
  18. class RRsetBulkListSerializer(BulkListSerializer):
  19. @transaction.atomic
  20. def update(self, queryset, validated_data):
  21. q = models.Q(pk__isnull=True)
  22. for data in validated_data:
  23. q |= models.Q(subname=data.get('subname', ''), type=data['type'])
  24. rrsets = {(obj.subname, obj.type): obj for obj in queryset.filter(q)}
  25. instance = [rrsets.get((data.get('subname', ''), data['type']), None)
  26. for data in validated_data]
  27. return self.child._save(instance, validated_data)
  28. @transaction.atomic
  29. def create(self, validated_data):
  30. return self.child._save([None] * len(validated_data), validated_data)
  31. class SlugRRField(serializers.SlugRelatedField):
  32. def __init__(self, *args, **kwargs):
  33. kwargs['slug_field'] = 'content'
  34. kwargs['queryset'] = RR.objects.all()
  35. super().__init__(*args, **kwargs)
  36. def to_internal_value(self, data):
  37. return RR(**{self.slug_field: data})
  38. class RRsetSerializer(BulkSerializerMixin, serializers.ModelSerializer):
  39. domain = serializers.StringRelatedField()
  40. subname = serializers.CharField(allow_blank=True, required=False)
  41. records = SlugRRField(many=True)
  42. class Meta:
  43. model = RRset
  44. fields = ('id', 'domain', 'subname', 'name', 'records', 'ttl', 'type',)
  45. list_serializer_class = RRsetBulkListSerializer
  46. def _save(self, instance, validated_data):
  47. bulk = isinstance(instance, list)
  48. if not bulk:
  49. instance = [instance]
  50. validated_data = [validated_data]
  51. name = self.context['view'].kwargs['name']
  52. domain = self.context['request'].user.domains.get(name=name)
  53. method = self.context['request'].method
  54. errors = []
  55. rrsets = {}
  56. rrsets_seen = set()
  57. for rrset, data in zip(instance, validated_data):
  58. # Construct RRset
  59. records = data.pop('records', None)
  60. if rrset:
  61. # We have a known instance (update). Update fields if given.
  62. rrset.subname = data.get('subname', rrset.subname)
  63. rrset.type = data.get('type', rrset.type)
  64. rrset.ttl = data.get('ttl', rrset.ttl)
  65. else:
  66. # No known instance (creation or meaningless request)
  67. if not 'ttl' in data:
  68. if records:
  69. # If we have records, this is a creation request, so we
  70. # need a TTL.
  71. errors.append({'ttl': ['This field is required for new RRsets.']})
  72. continue
  73. else:
  74. # If this request is meaningless, we still want it to
  75. # be processed by pdns for type validation. In this
  76. # case, we need some dummy TTL.
  77. data['ttl'] = data.get('ttl', 1)
  78. data.pop('id', None)
  79. data['domain'] = domain
  80. rrset = RRset(**data)
  81. # Verify that we have not seen this RRset before
  82. if (rrset.subname, rrset.type) in rrsets_seen:
  83. errors.append({'__all__': ['RRset repeated with same subname and type.']})
  84. continue
  85. rrsets_seen.add((rrset.subname, rrset.type))
  86. # Validate RRset. Raises error if type or subname have been changed
  87. # or if new RRset is not unique.
  88. validate_unique = (method == 'POST')
  89. try:
  90. rrset.full_clean(exclude=['updated'],
  91. validate_unique=validate_unique)
  92. except django.core.exceptions.ValidationError as e:
  93. errors.append(e.message_dict)
  94. continue
  95. # Construct dictionary of RR lists to write, indexed by their RRset
  96. if records is None:
  97. rrsets[rrset] = None
  98. else:
  99. rr_data = [{'content': x.content, 'rrset': rrset} for x in records]
  100. # Use RRSerializer to validate records inputs
  101. allow_empty = (method in ('PATCH', 'PUT'))
  102. rr_serializer = RRSerializer(data=rr_data, many=True,
  103. allow_empty=allow_empty)
  104. if not rr_serializer.is_valid():
  105. error = rr_serializer.errors
  106. if 'non_field_errors' in error:
  107. error['records'] = error.pop('non_field_errors')
  108. errors.append(error)
  109. continue
  110. # Blessings have been given, so add RRset to the to-write dict
  111. rrsets[rrset] = [rr for rr in rr_serializer.validated_data]
  112. errors.append({})
  113. if any(errors):
  114. raise ValidationError(errors if bulk else errors[0])
  115. # Now try to save RRsets
  116. try:
  117. rrsets = domain.write_rrsets(rrsets)
  118. except django.core.exceptions.ValidationError as e:
  119. for attr in ['errors', 'error_dict', 'message']:
  120. detail = getattr(e, attr, None)
  121. if detail:
  122. raise ValidationError(detail)
  123. raise ValidationError(str(e))
  124. except ValueError as e:
  125. raise ValidationError({'__all__': str(e)})
  126. return rrsets if bulk else rrsets[0]
  127. @transaction.atomic
  128. def update(self, instance, validated_data):
  129. return self._save(instance, validated_data)
  130. @transaction.atomic
  131. def create(self, validated_data):
  132. return self._save(None, validated_data)
  133. def validate_type(self, value):
  134. if value in RRset.RESTRICTED_TYPES:
  135. raise serializers.ValidationError(
  136. "You cannot tinker with the %s RRset." % value)
  137. return value
  138. def to_representation(self, instance):
  139. data = super().to_representation(instance)
  140. data.pop('id')
  141. return data
  142. class DomainSerializer(serializers.ModelSerializer):
  143. owner = serializers.ReadOnlyField(source='owner.email')
  144. name = serializers.RegexField(regex=r'^[A-Za-z0-9_.-]+$', trim_whitespace=False)
  145. class Meta:
  146. model = Domain
  147. fields = ('name', 'owner', 'keys')
  148. class DonationSerializer(serializers.ModelSerializer):
  149. class Meta:
  150. model = Donation
  151. fields = ('name', 'iban', 'bic', 'amount', 'message', 'email')
  152. class UserSerializer(djoserSerializers.UserSerializer):
  153. class Meta(djoserSerializers.UserSerializer.Meta):
  154. fields = tuple(User.REQUIRED_FIELDS) + (
  155. User.USERNAME_FIELD,
  156. )
  157. class UserCreateSerializer(djoserSerializers.UserCreateSerializer):
  158. class Meta(djoserSerializers.UserCreateSerializer.Meta):
  159. fields = tuple(User.REQUIRED_FIELDS) + (
  160. User.USERNAME_FIELD,
  161. 'password',
  162. 'dyn',
  163. )