utils.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. from collections import defaultdict
  2. from django.apps import apps
  3. from django.contrib.contenttypes.models import ContentType
  4. from django.db import router, transaction
  5. from django.utils.translation import gettext as _
  6. from dcim.constants import MODULE_TOKEN
  7. def get_module_bay_positions(module_bay):
  8. """
  9. Given a module bay, traverse up the module hierarchy and return
  10. a list of bay position strings from root to leaf, resolving any
  11. {module} tokens in each position using the parent position
  12. (position inheritance).
  13. """
  14. positions = []
  15. while module_bay:
  16. pos = module_bay.position or ''
  17. if positions and MODULE_TOKEN in pos:
  18. pos = pos.replace(MODULE_TOKEN, positions[-1])
  19. positions.append(pos)
  20. if module_bay.module:
  21. module_bay = module_bay.module.module_bay
  22. else:
  23. module_bay = None
  24. positions.reverse()
  25. return positions
  26. def resolve_module_placeholder(value, positions):
  27. """
  28. Resolve {module} placeholder tokens in a string using the given
  29. list of module bay positions (ordered root to leaf).
  30. A single {module} token resolves to the leaf (immediate parent) bay's position.
  31. Multiple tokens must match the tree depth and resolve level-by-level.
  32. Returns the resolved string.
  33. Raises ValueError if token count is greater than 1 and doesn't match tree depth.
  34. """
  35. if MODULE_TOKEN not in value:
  36. return value
  37. token_count = value.count(MODULE_TOKEN)
  38. if token_count == 1:
  39. return value.replace(MODULE_TOKEN, positions[-1])
  40. if token_count == len(positions):
  41. for pos in positions:
  42. value = value.replace(MODULE_TOKEN, pos, 1)
  43. return value
  44. raise ValueError(
  45. _("Cannot install module with placeholder values in a module bay tree "
  46. "{level} levels deep but {tokens} placeholders given.").format(
  47. level=len(positions), tokens=token_count
  48. )
  49. )
  50. def compile_path_node(ct_id, object_id):
  51. return f'{ct_id}:{object_id}'
  52. def decompile_path_node(repr):
  53. ct_id, object_id = repr.split(':')
  54. return int(ct_id), int(object_id)
  55. def object_to_path_node(obj):
  56. """
  57. Return a representation of an object suitable for inclusion in a CablePath path. Node representation is in the
  58. form <ContentType ID>:<Object ID>.
  59. """
  60. ct = ContentType.objects.get_for_model(obj)
  61. return compile_path_node(ct.pk, obj.pk)
  62. def path_node_to_object(repr):
  63. """
  64. Given the string representation of a path node, return the corresponding instance. If the object no longer
  65. exists, return None.
  66. """
  67. ct_id, object_id = decompile_path_node(repr)
  68. ct = ContentType.objects.get_for_id(ct_id)
  69. return ct.model_class().objects.filter(pk=object_id).first()
  70. def create_cablepaths(objects):
  71. """
  72. Create CablePaths for all paths originating from the specified set of nodes.
  73. :param objects: Iterable of cabled objects (e.g. Interfaces)
  74. """
  75. from dcim.models import CablePath
  76. # Arrange objects by cable connector. All objects with a null connector are grouped together.
  77. origins = defaultdict(list)
  78. for obj in objects:
  79. origins[obj.cable_connector].append(obj)
  80. for connector, objects in origins.items():
  81. if cp := CablePath.from_origin(objects):
  82. cp.save()
  83. def rebuild_paths(terminations):
  84. """
  85. Rebuild all CablePaths which traverse the specified nodes.
  86. """
  87. from dcim.models import CablePath
  88. for obj in terminations:
  89. cable_paths = CablePath.objects.filter(_nodes__contains=obj)
  90. with transaction.atomic(using=router.db_for_write(CablePath)):
  91. for cp in cable_paths:
  92. cp.delete()
  93. create_cablepaths(cp.origins)
  94. def update_interface_bridges(device, interface_templates, module=None):
  95. """
  96. Used for device and module instantiation. Iterates all InterfaceTemplates with a bridge assigned
  97. and applies it to the actual interfaces.
  98. """
  99. Interface = apps.get_model('dcim', 'Interface')
  100. for interface_template in interface_templates.exclude(bridge=None):
  101. interface = Interface.objects.get(device=device, name=interface_template.resolve_name(module=module))
  102. if interface_template.bridge:
  103. interface.bridge = Interface.objects.get(
  104. device=device,
  105. name=interface_template.bridge.resolve_name(module=module)
  106. )
  107. interface.full_clean()
  108. interface.save()
  109. def create_port_mappings(device, device_or_module_type, module=None):
  110. """
  111. Replicate all front/rear port mappings from a DeviceType or ModuleType to the given device.
  112. """
  113. from dcim.models import FrontPort, PortMapping, RearPort
  114. templates = device_or_module_type.port_mappings.prefetch_related('front_port', 'rear_port')
  115. # Cache front & rear ports for efficient lookups by name
  116. front_ports = {
  117. fp.name: fp for fp in FrontPort.objects.filter(device=device)
  118. }
  119. rear_ports = {
  120. rp.name: rp for rp in RearPort.objects.filter(device=device)
  121. }
  122. # Replicate PortMappings
  123. mappings = []
  124. for template in templates:
  125. front_port = front_ports.get(template.front_port.resolve_name(module=module))
  126. rear_port = rear_ports.get(template.rear_port.resolve_name(module=module))
  127. mappings.append(
  128. PortMapping(
  129. device_id=front_port.device_id,
  130. front_port=front_port,
  131. front_port_position=template.front_port_position,
  132. rear_port=rear_port,
  133. rear_port_position=template.rear_port_position,
  134. )
  135. )
  136. PortMapping.objects.bulk_create(mappings)