base.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. from drf_spectacular.types import OpenApiTypes
  2. from drf_spectacular.utils import extend_schema_field
  3. from rest_framework import serializers
  4. from dcim.models import FrontPort, FrontPortTemplate, PortMapping, PortTemplateMapping, RearPort, RearPortTemplate
  5. from utilities.api import get_serializer_for_model
  6. __all__ = (
  7. 'ConnectedEndpointsSerializer',
  8. 'PortSerializer',
  9. )
  10. class ConnectedEndpointsSerializer(serializers.ModelSerializer):
  11. """
  12. Legacy serializer for pre-v3.3 connections
  13. """
  14. connected_endpoints_type = serializers.SerializerMethodField(read_only=True, allow_null=True)
  15. connected_endpoints = serializers.SerializerMethodField(read_only=True)
  16. connected_endpoints_reachable = serializers.SerializerMethodField(read_only=True)
  17. @extend_schema_field(OpenApiTypes.STR)
  18. def get_connected_endpoints_type(self, obj):
  19. if endpoints := obj.connected_endpoints:
  20. return f'{endpoints[0]._meta.app_label}.{endpoints[0]._meta.model_name}'
  21. return None
  22. @extend_schema_field(serializers.ListField(allow_null=True))
  23. def get_connected_endpoints(self, obj):
  24. """
  25. Return the appropriate serializer for the type of connected object.
  26. """
  27. if endpoints := obj.connected_endpoints:
  28. serializer = get_serializer_for_model(endpoints[0])
  29. context = {'request': self.context['request']}
  30. return serializer(endpoints, nested=True, many=True, context=context).data
  31. return None
  32. @extend_schema_field(serializers.BooleanField)
  33. def get_connected_endpoints_reachable(self, obj):
  34. """
  35. Return whether the connected endpoints are reachable via a complete, active cable path.
  36. """
  37. # Use the public `path` accessor rather than dereferencing `_path`
  38. # directly. `path` already handles the stale in-memory relation case
  39. # that can occur while CablePath rows are rebuilt during cable edits.
  40. if path := obj.path:
  41. return path.is_complete and path.is_active
  42. return False
  43. class PortSerializer(serializers.ModelSerializer):
  44. """
  45. Base serializer for front & rear port and port templates.
  46. """
  47. @property
  48. def _mapper(self):
  49. """
  50. Return the model and ForeignKey field name used to track port mappings for this model.
  51. """
  52. if self.Meta.model is FrontPort:
  53. return PortMapping, 'front_port'
  54. if self.Meta.model is RearPort:
  55. return PortMapping, 'rear_port'
  56. if self.Meta.model is FrontPortTemplate:
  57. return PortTemplateMapping, 'front_port'
  58. if self.Meta.model is RearPortTemplate:
  59. return PortTemplateMapping, 'rear_port'
  60. raise ValueError(f"Could not determine mapping details for {self.__class__}")
  61. def create(self, validated_data):
  62. mappings = validated_data.pop('mappings', [])
  63. instance = super().create(validated_data)
  64. # Create port mappings
  65. mapping_model, fk_name = self._mapper
  66. for attrs in mappings:
  67. mapping_model.objects.create(**{
  68. fk_name: instance,
  69. **attrs,
  70. })
  71. return instance
  72. def update(self, instance, validated_data):
  73. mappings = validated_data.pop('mappings', None)
  74. instance = super().update(instance, validated_data)
  75. if mappings is not None:
  76. # Update port mappings
  77. mapping_model, fk_name = self._mapper
  78. mapping_model.objects.filter(**{fk_name: instance}).delete()
  79. for attrs in mappings:
  80. mapping_model.objects.create(**{
  81. fk_name: instance,
  82. **attrs,
  83. })
  84. return instance