mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-27 10:58:37 -06:00
initial work on #5952
This commit is contained in:
parent
ae3527df16
commit
859bda079e
@ -59,6 +59,17 @@ POWERFEED_MAX_UTILIZATION_DEFAULT = 80 # Percentage
|
|||||||
# Cabling and connections
|
# Cabling and connections
|
||||||
#
|
#
|
||||||
|
|
||||||
|
# Cable endpoint parent types
|
||||||
|
CABLE_TERMINATION_PARENT_MODELS = Q(
|
||||||
|
Q(app_label='circuits', model__in=(
|
||||||
|
'circuit',
|
||||||
|
)) |
|
||||||
|
Q(app_label='dcim', model__in=(
|
||||||
|
'device',
|
||||||
|
'powerpanel',
|
||||||
|
))
|
||||||
|
)
|
||||||
|
|
||||||
# Cable endpoint types
|
# Cable endpoint types
|
||||||
CABLE_TERMINATION_MODELS = Q(
|
CABLE_TERMINATION_MODELS = Q(
|
||||||
Q(app_label='circuits', model__in=(
|
Q(app_label='circuits', model__in=(
|
||||||
|
@ -3819,10 +3819,13 @@ class CableForm(BootstrapMixin, CustomFieldModelForm):
|
|||||||
|
|
||||||
class CableCSVForm(CustomFieldModelCSVForm):
|
class CableCSVForm(CustomFieldModelCSVForm):
|
||||||
# Termination A
|
# Termination A
|
||||||
side_a_device = CSVModelChoiceField(
|
side_a_parent_type = CSVContentTypeField(
|
||||||
queryset=Device.objects.all(),
|
queryset=ContentType.objects.all(),
|
||||||
to_field_name='name',
|
limit_choices_to=CABLE_TERMINATION_PARENT_MODELS,
|
||||||
help_text='Side A device'
|
help_text='Side A parent type'
|
||||||
|
)
|
||||||
|
side_a_parent_id = forms.IntegerField(
|
||||||
|
help_text='Side A parent ID'
|
||||||
)
|
)
|
||||||
side_a_type = CSVContentTypeField(
|
side_a_type = CSVContentTypeField(
|
||||||
queryset=ContentType.objects.all(),
|
queryset=ContentType.objects.all(),
|
||||||
@ -3834,10 +3837,13 @@ class CableCSVForm(CustomFieldModelCSVForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Termination B
|
# Termination B
|
||||||
side_b_device = CSVModelChoiceField(
|
side_b_parent_type = CSVContentTypeField(
|
||||||
queryset=Device.objects.all(),
|
queryset=ContentType.objects.all(),
|
||||||
to_field_name='name',
|
limit_choices_to=CABLE_TERMINATION_PARENT_MODELS,
|
||||||
help_text='Side B device'
|
help_text='Side B parent type'
|
||||||
|
)
|
||||||
|
side_b_parent_id = forms.IntegerField(
|
||||||
|
help_text='Side B parent ID'
|
||||||
)
|
)
|
||||||
side_b_type = CSVContentTypeField(
|
side_b_type = CSVContentTypeField(
|
||||||
queryset=ContentType.objects.all(),
|
queryset=ContentType.objects.all(),
|
||||||
@ -3868,13 +3874,30 @@ class CableCSVForm(CustomFieldModelCSVForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Cable
|
model = Cable
|
||||||
fields = [
|
fields = [
|
||||||
'side_a_device', 'side_a_type', 'side_a_name', 'side_b_device', 'side_b_type', 'side_b_name', 'type',
|
'side_a_parent_type', 'side_a_parent_id', 'side_a_type', 'side_a_name',
|
||||||
'status', 'label', 'color', 'length', 'length_unit',
|
'side_b_parent_type', 'side_b_parent_id', 'side_b_type', 'side_b_name',
|
||||||
|
'type', 'status', 'label', 'color', 'length', 'length_unit',
|
||||||
]
|
]
|
||||||
help_texts = {
|
help_texts = {
|
||||||
'color': mark_safe('RGB color in hexadecimal (e.g. <code>00ff00</code>)'),
|
'color': mark_safe('RGB color in hexadecimal (e.g. <code>00ff00</code>)'),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def _get_parent(self, side):
|
||||||
|
parent_type = self.cleaned_data.get(f'side_{side}_parent_type')
|
||||||
|
parent_id = self.cleaned_data.get(f'side_{side}_parent_id')
|
||||||
|
if not parent_type or not parent_id:
|
||||||
|
return None
|
||||||
|
|
||||||
|
model = parent_type.model_class()
|
||||||
|
|
||||||
|
return model.objects.get(pk=parent_id)
|
||||||
|
|
||||||
|
def _translate_model(self, parent_type):
|
||||||
|
# TODO: maybe we went overboard with making this generic/extensible?
|
||||||
|
return {
|
||||||
|
"powerpanel": "power_panel",
|
||||||
|
}.get(parent_type.model, parent_type.model)
|
||||||
|
|
||||||
def _clean_side(self, side):
|
def _clean_side(self, side):
|
||||||
"""
|
"""
|
||||||
Derive a Cable's A/B termination objects.
|
Derive a Cable's A/B termination objects.
|
||||||
@ -3883,19 +3906,24 @@ class CableCSVForm(CustomFieldModelCSVForm):
|
|||||||
"""
|
"""
|
||||||
assert side in 'ab', f"Invalid side designation: {side}"
|
assert side in 'ab', f"Invalid side designation: {side}"
|
||||||
|
|
||||||
device = self.cleaned_data.get(f'side_{side}_device')
|
parent_type = self.cleaned_data.get(f'side_{side}_parent_type')
|
||||||
|
parent = self._get_parent(side)
|
||||||
content_type = self.cleaned_data.get(f'side_{side}_type')
|
content_type = self.cleaned_data.get(f'side_{side}_type')
|
||||||
name = self.cleaned_data.get(f'side_{side}_name')
|
name = self.cleaned_data.get(f'side_{side}_name')
|
||||||
if not device or not content_type or not name:
|
if not parent or not content_type or not name:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
model = content_type.model_class()
|
model = content_type.model_class()
|
||||||
try:
|
try:
|
||||||
termination_object = model.objects.get(device=device, name=name)
|
# the parent is named like the model, we utilize that fact for the query
|
||||||
|
termination_object = model.objects.get(**{
|
||||||
|
"name" if hasattr(model, "name") else "pk": name,
|
||||||
|
self._translate_model(parent_type): parent
|
||||||
|
})
|
||||||
if termination_object.cable is not None:
|
if termination_object.cable is not None:
|
||||||
raise forms.ValidationError(f"Side {side.upper()}: {device} {termination_object} is already connected")
|
raise forms.ValidationError(f"Side {side.upper()}: {parent} {termination_object} is already connected")
|
||||||
except ObjectDoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
raise forms.ValidationError(f"{side.upper()} side termination not found: {device} {name}")
|
raise forms.ValidationError(f"{side.upper()} side termination not found: {parent} {name}")
|
||||||
|
|
||||||
setattr(self.instance, f'termination_{side}', termination_object)
|
setattr(self.instance, f'termination_{side}', termination_object)
|
||||||
return termination_object
|
return termination_object
|
||||||
|
@ -112,8 +112,9 @@ class Cable(ChangeLoggedModel, CustomFieldModel):
|
|||||||
objects = RestrictedQuerySet.as_manager()
|
objects = RestrictedQuerySet.as_manager()
|
||||||
|
|
||||||
csv_headers = [
|
csv_headers = [
|
||||||
'termination_a_type', 'termination_a_id', 'termination_b_type', 'termination_b_id', 'type', 'status', 'label',
|
'side_a_parent_type', 'side_a_parent_id', 'side_a_type', 'side_a_name',
|
||||||
'color', 'length', 'length_unit',
|
'side_b_parent_type', 'side_b_parent_id', 'side_b_type', 'side_b_name',
|
||||||
|
'type', 'status', 'label', 'color', 'length', 'length_unit',
|
||||||
]
|
]
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -281,12 +282,16 @@ class Cable(ChangeLoggedModel, CustomFieldModel):
|
|||||||
|
|
||||||
def to_csv(self):
|
def to_csv(self):
|
||||||
return (
|
return (
|
||||||
|
'{}.{}'.format(self.termination_a.parent._meta.app_label, self.termination_a.parent._meta.model_name),
|
||||||
|
self.termination_a.parent.id,
|
||||||
'{}.{}'.format(self.termination_a_type.app_label, self.termination_a_type.model),
|
'{}.{}'.format(self.termination_a_type.app_label, self.termination_a_type.model),
|
||||||
self.termination_a_id,
|
self.termination_a.name if hasattr(self.termination_a, "name") else self.termination_a_id,
|
||||||
|
'{}.{}'.format(self.termination_b.parent._meta.app_label, self.termination_b.parent._meta.model_name),
|
||||||
|
self.termination_b.parent.id,
|
||||||
'{}.{}'.format(self.termination_b_type.app_label, self.termination_b_type.model),
|
'{}.{}'.format(self.termination_b_type.app_label, self.termination_b_type.model),
|
||||||
self.termination_b_id,
|
self.termination_b.name if hasattr(self.termination_b, "name") else self.termination_b_id,
|
||||||
self.get_type_display(),
|
self.type,
|
||||||
self.get_status_display(),
|
self.status,
|
||||||
self.label,
|
self.label,
|
||||||
self.color,
|
self.color,
|
||||||
self.length,
|
self.length,
|
||||||
|
Loading…
Reference in New Issue
Block a user