Coverage for ipam/api/serializers_/ip.py: 78%

120 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-10 18:35 +0000

1from django.contrib.contenttypes.models import ContentType 

2from rest_framework import serializers 

3 

4from dcim.constants import LOCATION_SCOPE_TYPES 

5from ipam.choices import * 

6from ipam.constants import IPADDRESS_ASSIGNMENT_MODELS 

7from ipam.models import Aggregate, IPAddress, IPRange, Prefix 

8from netbox.api.fields import ChoiceField, ContentTypeField 

9from netbox.api.gfk_fields import GFKSerializerField 

10from netbox.api.serializers import PrimaryModelSerializer 

11from tenancy.api.serializers_.tenants import TenantSerializer 

12 

13from ..field_serializers import IPAddressField, IPNetworkField 

14from .asns import RIRSerializer 

15from .nested import NestedIPAddressSerializer 

16from .roles import RoleSerializer 

17from .vlans import VLANSerializer 

18from .vrfs import VRFSerializer 

19 

20__all__ = ( 

21 'AggregateSerializer', 

22 'AvailableIPRequestSerializer', 

23 'AvailableIPSerializer', 

24 'AvailablePrefixSerializer', 

25 'IPAddressSerializer', 

26 'IPRangeSerializer', 

27 'PrefixLengthSerializer', 

28 'PrefixSerializer', 

29) 

30 

31 

32class AggregateSerializer(PrimaryModelSerializer): 

33 family = ChoiceField(choices=IPAddressFamilyChoices, read_only=True) 

34 rir = RIRSerializer(nested=True) 

35 tenant = TenantSerializer(nested=True, required=False, allow_null=True) 

36 prefix = IPNetworkField() 

37 

38 class Meta: 

39 model = Aggregate 

40 fields = [ 

41 'id', 'url', 'display_url', 'display', 'family', 'prefix', 'rir', 'tenant', 'date_added', 'description', 

42 'owner', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 

43 ] 

44 brief_fields = ('id', 'url', 'display', 'family', 'prefix', 'description') 

45 

46 

47class PrefixSerializer(PrimaryModelSerializer): 

48 family = ChoiceField(choices=IPAddressFamilyChoices, read_only=True) 

49 vrf = VRFSerializer(nested=True, required=False, allow_null=True) 

50 scope_type = ContentTypeField( 

51 queryset=ContentType.objects.filter( 

52 model__in=LOCATION_SCOPE_TYPES 

53 ), 

54 allow_null=True, 

55 required=False, 

56 default=None 

57 ) 

58 scope_id = serializers.IntegerField(allow_null=True, required=False, default=None) 

59 scope = GFKSerializerField(read_only=True) 

60 tenant = TenantSerializer(nested=True, required=False, allow_null=True) 

61 vlan = VLANSerializer(nested=True, required=False, allow_null=True) 

62 status = ChoiceField(choices=PrefixStatusChoices, required=False) 

63 role = RoleSerializer(nested=True, required=False, allow_null=True) 

64 children = serializers.IntegerField(read_only=True) 

65 _depth = serializers.IntegerField(read_only=True) 

66 prefix = IPNetworkField() 

67 

68 class Meta: 

69 model = Prefix 

70 fields = [ 

71 'id', 'url', 'display_url', 'display', 'family', 'prefix', 'vrf', 'scope_type', 'scope_id', 'scope', 

72 'tenant', 'vlan', 'status', 'role', 'is_pool', 'mark_utilized', 'description', 'owner', 'comments', 'tags', 

73 'custom_fields', 'created', 'last_updated', 'children', '_depth', 

74 ] 

75 brief_fields = ('id', 'url', 'display', 'family', 'prefix', 'description', '_depth') 

76 

77 

78class PrefixLengthSerializer(serializers.Serializer): 

79 

80 prefix_length = serializers.IntegerField() 

81 

82 def to_internal_value(self, data): 

83 requested_prefix = data.get('prefix_length') 

84 if requested_prefix is None: 

85 raise serializers.ValidationError({ 

86 'prefix_length': 'this field can not be missing' 

87 }) 

88 if not isinstance(requested_prefix, int): 

89 raise serializers.ValidationError({ 

90 'prefix_length': 'this field must be int type' 

91 }) 

92 

93 prefix = self.context.get('prefix') 

94 if prefix.family == 4 and requested_prefix > 32: 

95 raise serializers.ValidationError({ 

96 'prefix_length': 'Invalid prefix length ({}) for IPv4'.format(requested_prefix) 

97 }) 

98 if prefix.family == 6 and requested_prefix > 128: 

99 raise serializers.ValidationError({ 

100 'prefix_length': 'Invalid prefix length ({}) for IPv6'.format(requested_prefix) 

101 }) 

102 return data 

103 

104 

105class AvailablePrefixSerializer(serializers.Serializer): 

106 """ 

107 Representation of a prefix which does not exist in the database. 

108 """ 

109 family = serializers.IntegerField(read_only=True) 

110 prefix = serializers.CharField(read_only=True) 

111 vrf = VRFSerializer(nested=True, read_only=True, allow_null=True) 

112 

113 def to_representation(self, instance): 

114 if self.context.get('vrf'): 114 ↛ 115line 114 didn't jump to line 115 because the condition on line 114 was never true

115 vrf = VRFSerializer(self.context['vrf'], nested=True, context={'request': self.context['request']}).data 

116 else: 

117 vrf = None 

118 return { 

119 'family': instance.version, 

120 'prefix': str(instance), 

121 'vrf': vrf, 

122 } 

123 

124 

125# 

126# IP ranges 

127# 

128 

129class IPRangeSerializer(PrimaryModelSerializer): 

130 family = ChoiceField(choices=IPAddressFamilyChoices, read_only=True) 

131 start_address = IPAddressField() 

132 end_address = IPAddressField() 

133 vrf = VRFSerializer(nested=True, required=False, allow_null=True) 

134 tenant = TenantSerializer(nested=True, required=False, allow_null=True) 

135 status = ChoiceField(choices=IPRangeStatusChoices, required=False) 

136 role = RoleSerializer(nested=True, required=False, allow_null=True) 

137 

138 class Meta: 

139 model = IPRange 

140 fields = [ 

141 'id', 'url', 'display_url', 'display', 'family', 'start_address', 'end_address', 'size', 'vrf', 'tenant', 

142 'status', 'role', 'description', 'owner', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 

143 'mark_populated', 'mark_utilized', 

144 ] 

145 brief_fields = ('id', 'url', 'display', 'family', 'start_address', 'end_address', 'description') 

146 

147 

148# 

149# IP addresses 

150# 

151 

152class AvailableIPRequestSerializer(serializers.Serializer): 

153 """ 

154 Request payload for creating IP addresses from the available-ips endpoint. 

155 """ 

156 prefix_length = serializers.IntegerField(required=False) 

157 

158 def to_internal_value(self, data): 

159 data = super().to_internal_value(data) 

160 

161 prefix_length = data.get('prefix_length') 

162 if prefix_length is None: 

163 # No override requested; the parent prefix/range mask length will be used. 

164 return data 

165 

166 parent = self.context.get('parent') 

167 if parent is None: 167 ↛ 168line 167 didn't jump to line 168 because the condition on line 167 was never true

168 return data 

169 

170 # Validate the requested prefix length 

171 if prefix_length < parent.mask_length: 

172 raise serializers.ValidationError({ 

173 'prefix_length': 'Prefix length must be greater than or equal to the parent mask length ({})'.format( 

174 parent.mask_length 

175 ) 

176 }) 

177 if parent.family == 4 and prefix_length > 32: 177 ↛ 181line 177 didn't jump to line 181 because the condition on line 177 was always true

178 raise serializers.ValidationError({ 

179 'prefix_length': 'Invalid prefix length ({}) for IPv6'.format(prefix_length) 

180 }) 

181 if parent.family == 6 and prefix_length > 128: 

182 raise serializers.ValidationError({ 

183 'prefix_length': 'Invalid prefix length ({}) for IPv4'.format(prefix_length) 

184 }) 

185 

186 return data 

187 

188 

189class IPAddressSerializer(PrimaryModelSerializer): 

190 family = ChoiceField(choices=IPAddressFamilyChoices, read_only=True) 

191 address = IPAddressField() 

192 vrf = VRFSerializer(nested=True, required=False, allow_null=True) 

193 tenant = TenantSerializer(nested=True, required=False, allow_null=True) 

194 status = ChoiceField(choices=IPAddressStatusChoices, required=False) 

195 role = ChoiceField(choices=IPAddressRoleChoices, allow_blank=True, required=False) 

196 assigned_object_type = ContentTypeField( 

197 queryset=ContentType.objects.filter(IPADDRESS_ASSIGNMENT_MODELS), 

198 required=False, 

199 allow_null=True 

200 ) 

201 assigned_object = GFKSerializerField(read_only=True) 

202 nat_inside = NestedIPAddressSerializer(required=False, allow_null=True) 

203 nat_outside = NestedIPAddressSerializer(many=True, read_only=True) 

204 

205 class Meta: 

206 model = IPAddress 

207 fields = [ 

208 'id', 'url', 'display_url', 'display', 'family', 'address', 'vrf', 'tenant', 'status', 'role', 

209 'assigned_object_type', 'assigned_object_id', 'assigned_object', 'nat_inside', 'nat_outside', 

210 'dns_name', 'description', 'owner', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 

211 ] 

212 brief_fields = ('id', 'url', 'display', 'family', 'address', 'description') 

213 

214 

215class AvailableIPSerializer(serializers.Serializer): 

216 """ 

217 Representation of an IP address which does not exist in the database. 

218 """ 

219 family = serializers.IntegerField(read_only=True) 

220 address = serializers.CharField(read_only=True) 

221 vrf = VRFSerializer(nested=True, read_only=True, allow_null=True) 

222 description = serializers.CharField(required=False) 

223 

224 def to_representation(self, instance): 

225 if self.context.get('vrf'): 225 ↛ 226line 225 didn't jump to line 226 because the condition on line 225 was never true

226 vrf = VRFSerializer(self.context['vrf'], nested=True, context={'request': self.context['request']}).data 

227 else: 

228 vrf = None 

229 return { 

230 'family': self.context['parent'].family, 

231 'address': f"{instance}/{self.context['parent'].mask_length}", 

232 'vrf': vrf, 

233 }