connections.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. from django import forms
  2. from django.contrib.contenttypes.models import ContentType
  3. from django.utils.translation import gettext_lazy as _
  4. from circuits.models import Circuit, CircuitTermination
  5. from dcim.models import *
  6. from utilities.forms.fields import DynamicModelMultipleChoiceField
  7. from .model_forms import CableForm
  8. def get_cable_form(a_type, b_type):
  9. class FormMetaclass(forms.models.ModelFormMetaclass):
  10. def __new__(mcs, name, bases, attrs):
  11. # NOTE: Cable.clone() mirrors the parent selector mapping below:
  12. # termination_{end}_device / termination_{end}_powerpanel / termination_{end}_circuit
  13. # This supports both the "Clone" and "Create & Add Another" workflows.
  14. # If you change the mapping here, update Cable.clone() accordingly.
  15. for cable_end, term_cls in (('a', a_type), ('b', b_type)):
  16. # Device component
  17. if hasattr(term_cls, 'device'):
  18. # Dynamically change the param field for interfaces to use virtual_chassis filter
  19. query_param_device_field = 'device_id'
  20. if term_cls == Interface:
  21. query_param_device_field = 'virtual_chassis_member_or_master_id'
  22. attrs[f'termination_{cable_end}_device'] = DynamicModelMultipleChoiceField(
  23. queryset=Device.objects.all(),
  24. label=_('Device'),
  25. required=False,
  26. selector=True,
  27. initial_params={
  28. f'{term_cls._meta.model_name}s__in': f'${cable_end}_terminations'
  29. }
  30. )
  31. attrs[f'{cable_end}_terminations'] = DynamicModelMultipleChoiceField(
  32. queryset=term_cls.objects.all(),
  33. label=term_cls._meta.verbose_name.title(),
  34. context={
  35. 'disabled': '_occupied',
  36. 'parent': 'device',
  37. },
  38. query_params={
  39. query_param_device_field: f'$termination_{cable_end}_device',
  40. 'kind': 'physical', # Exclude virtual interfaces
  41. }
  42. )
  43. # PowerFeed
  44. elif term_cls == PowerFeed:
  45. attrs[f'termination_{cable_end}_powerpanel'] = DynamicModelMultipleChoiceField(
  46. queryset=PowerPanel.objects.all(),
  47. label=_('Power Panel'),
  48. required=False,
  49. selector=True,
  50. initial_params={
  51. 'powerfeeds__in': f'${cable_end}_terminations'
  52. }
  53. )
  54. attrs[f'{cable_end}_terminations'] = DynamicModelMultipleChoiceField(
  55. queryset=term_cls.objects.all(),
  56. label=_('Power Feed'),
  57. context={
  58. 'disabled': '_occupied',
  59. 'parent': 'powerpanel',
  60. },
  61. query_params={
  62. 'power_panel_id': f'$termination_{cable_end}_powerpanel',
  63. }
  64. )
  65. # CircuitTermination
  66. elif term_cls == CircuitTermination:
  67. attrs[f'termination_{cable_end}_circuit'] = DynamicModelMultipleChoiceField(
  68. queryset=Circuit.objects.all(),
  69. label=_('Circuit'),
  70. selector=True,
  71. initial_params={
  72. 'terminations__in': f'${cable_end}_terminations'
  73. }
  74. )
  75. attrs[f'{cable_end}_terminations'] = DynamicModelMultipleChoiceField(
  76. queryset=term_cls.objects.all(),
  77. label=_('Side'),
  78. context={
  79. 'disabled': '_occupied',
  80. 'parent': 'circuit',
  81. },
  82. query_params={
  83. 'circuit_id': f'$termination_{cable_end}_circuit',
  84. }
  85. )
  86. return super().__new__(mcs, name, bases, attrs)
  87. class _CableForm(CableForm, metaclass=FormMetaclass):
  88. def __init__(self, *args, initial=None, **kwargs):
  89. initial = initial or {}
  90. if a_type:
  91. a_ct = ContentType.objects.get_for_model(a_type)
  92. initial['a_terminations_type'] = f'{a_ct.app_label}.{a_ct.model}'
  93. if b_type:
  94. b_ct = ContentType.objects.get_for_model(b_type)
  95. initial['b_terminations_type'] = f'{b_ct.app_label}.{b_ct.model}'
  96. # TODO: Temporary hack to work around list handling limitations with utils.normalize_querydict()
  97. for field_name in ('a_terminations', 'b_terminations'):
  98. if field_name in initial and type(initial[field_name]) is not list:
  99. initial[field_name] = [initial[field_name]]
  100. super().__init__(*args, initial=initial, **kwargs)
  101. if self.instance and self.instance.pk:
  102. # Initialize A/B terminations when modifying an existing Cable instance
  103. if (
  104. a_type and self.instance.a_terminations and
  105. a_ct == ContentType.objects.get_for_model(self.instance.a_terminations[0])
  106. ):
  107. self.initial['a_terminations'] = self.instance.a_terminations
  108. if (
  109. b_type and self.instance.b_terminations and
  110. b_ct == ContentType.objects.get_for_model(self.instance.b_terminations[0])
  111. ):
  112. self.initial['b_terminations'] = self.instance.b_terminations
  113. else:
  114. # Need to clear terminations if swapped type - but need to do it only
  115. # if not from instance
  116. if a_type:
  117. initial.pop('a_terminations', None)
  118. if b_type:
  119. initial.pop('b_terminations', None)
  120. def clean(self):
  121. super().clean()
  122. # Set the A/B terminations on the Cable instance
  123. self.instance.a_terminations = self.cleaned_data.get('a_terminations', [])
  124. self.instance.b_terminations = self.cleaned_data.get('b_terminations', [])
  125. return _CableForm