From 79e51084bf565cb0ee89a4c09eb4dd6cf6c61260 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Thu, 26 Sep 2024 15:14:45 -0700 Subject: [PATCH] 16547 Add distance to Circuit --- ..._abs_distance_circuit_distance_and_more.py | 28 ++++++ netbox/circuits/models/circuits.py | 3 +- netbox/dcim/api/serializers_/devicetypes.py | 1 + netbox/dcim/api/serializers_/racks.py | 1 + netbox/dcim/choices.py | 18 ---- netbox/dcim/forms/bulk_edit.py | 1 + netbox/dcim/forms/bulk_import.py | 1 + netbox/dcim/forms/filtersets.py | 1 + netbox/dcim/models/devices.py | 3 +- netbox/dcim/models/mixins.py | 44 --------- netbox/dcim/models/racks.py | 2 +- netbox/netbox/choices.py | 38 ++++++++ netbox/netbox/models/mixins.py | 97 +++++++++++++++++++ netbox/utilities/conversion.py | 3 +- .../api/serializers_/wirelesslinks.py | 3 +- netbox/wireless/choices.py | 18 ---- netbox/wireless/forms/bulk_edit.py | 3 +- netbox/wireless/forms/bulk_import.py | 3 +- netbox/wireless/forms/filtersets.py | 3 +- netbox/wireless/models.py | 37 +------ 20 files changed, 185 insertions(+), 123 deletions(-) create mode 100644 netbox/circuits/migrations/0045_circuit__abs_distance_circuit_distance_and_more.py create mode 100644 netbox/netbox/models/mixins.py diff --git a/netbox/circuits/migrations/0045_circuit__abs_distance_circuit_distance_and_more.py b/netbox/circuits/migrations/0045_circuit__abs_distance_circuit_distance_and_more.py new file mode 100644 index 000000000..6c970339d --- /dev/null +++ b/netbox/circuits/migrations/0045_circuit__abs_distance_circuit_distance_and_more.py @@ -0,0 +1,28 @@ +# Generated by Django 5.0.9 on 2024-09-26 22:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('circuits', '0044_circuit_groups'), + ] + + operations = [ + migrations.AddField( + model_name='circuit', + name='_abs_distance', + field=models.DecimalField(blank=True, decimal_places=4, max_digits=10, null=True), + ), + migrations.AddField( + model_name='circuit', + name='distance', + field=models.DecimalField(blank=True, decimal_places=2, max_digits=8, null=True), + ), + migrations.AddField( + model_name='circuit', + name='distance_unit', + field=models.CharField(blank=True, max_length=50), + ), + ] diff --git a/netbox/circuits/models/circuits.py b/netbox/circuits/models/circuits.py index 68c938aa9..7df7543b3 100644 --- a/netbox/circuits/models/circuits.py +++ b/netbox/circuits/models/circuits.py @@ -6,6 +6,7 @@ from django.utils.translation import gettext_lazy as _ from circuits.choices import * from dcim.models import CabledObjectModel from netbox.models import ChangeLoggedModel, OrganizationalModel, PrimaryModel +from netbox.models.mixins import DistanceMixin from netbox.models.features import ContactsMixin, CustomFieldsMixin, CustomLinksMixin, ExportTemplatesMixin, ImageAttachmentsMixin, TagsMixin from utilities.fields import ColorField @@ -37,7 +38,7 @@ class CircuitType(OrganizationalModel): verbose_name_plural = _('circuit types') -class Circuit(ContactsMixin, ImageAttachmentsMixin, PrimaryModel): +class Circuit(ContactsMixin, ImageAttachmentsMixin, DistanceMixin, PrimaryModel): """ A communications circuit connects two points. Each Circuit belongs to a Provider; Providers may have multiple circuits. Each circuit is also assigned a CircuitType and a Site, and may optionally be assigned to a particular diff --git a/netbox/dcim/api/serializers_/devicetypes.py b/netbox/dcim/api/serializers_/devicetypes.py index cda738862..0ce2af2f8 100644 --- a/netbox/dcim/api/serializers_/devicetypes.py +++ b/netbox/dcim/api/serializers_/devicetypes.py @@ -7,6 +7,7 @@ from dcim.choices import * from dcim.models import DeviceType, ModuleType from netbox.api.fields import ChoiceField, RelatedObjectCountField from netbox.api.serializers import NetBoxModelSerializer +from netbox.choices import * from .manufacturers import ManufacturerSerializer from .platforms import PlatformSerializer diff --git a/netbox/dcim/api/serializers_/racks.py b/netbox/dcim/api/serializers_/racks.py index ee76b09ce..1378c265a 100644 --- a/netbox/dcim/api/serializers_/racks.py +++ b/netbox/dcim/api/serializers_/racks.py @@ -6,6 +6,7 @@ from dcim.constants import * from dcim.models import Rack, RackReservation, RackRole, RackType from netbox.api.fields import ChoiceField, RelatedObjectCountField from netbox.api.serializers import NetBoxModelSerializer +from netbox.choices import * from netbox.config import ConfigItem from tenancy.api.serializers_.tenants import TenantSerializer from users.api.serializers_.users import UserSerializer diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py index 848f57d7e..d530db42d 100644 --- a/netbox/dcim/choices.py +++ b/netbox/dcim/choices.py @@ -1546,24 +1546,6 @@ class CableLengthUnitChoices(ChoiceSet): ) -class WeightUnitChoices(ChoiceSet): - - # Metric - UNIT_KILOGRAM = 'kg' - UNIT_GRAM = 'g' - - # Imperial - UNIT_POUND = 'lb' - UNIT_OUNCE = 'oz' - - CHOICES = ( - (UNIT_KILOGRAM, _('Kilograms')), - (UNIT_GRAM, _('Grams')), - (UNIT_POUND, _('Pounds')), - (UNIT_OUNCE, _('Ounces')), - ) - - # # CableTerminations # diff --git a/netbox/dcim/forms/bulk_edit.py b/netbox/dcim/forms/bulk_edit.py index 96036f4da..4d3a7acb1 100644 --- a/netbox/dcim/forms/bulk_edit.py +++ b/netbox/dcim/forms/bulk_edit.py @@ -8,6 +8,7 @@ from dcim.constants import * from dcim.models import * from extras.models import ConfigTemplate from ipam.models import ASN, VLAN, VLANGroup, VRF +from netbox.choices import * from netbox.forms import NetBoxModelBulkEditForm from tenancy.models import Tenant from users.models import User diff --git a/netbox/dcim/forms/bulk_import.py b/netbox/dcim/forms/bulk_import.py index e9c8b362e..551ec73a2 100644 --- a/netbox/dcim/forms/bulk_import.py +++ b/netbox/dcim/forms/bulk_import.py @@ -10,6 +10,7 @@ from dcim.constants import * from dcim.models import * from extras.models import ConfigTemplate from ipam.models import VRF, IPAddress +from netbox.choices import * from netbox.forms import NetBoxModelImportForm from tenancy.models import Tenant from utilities.forms.fields import ( diff --git a/netbox/dcim/forms/filtersets.py b/netbox/dcim/forms/filtersets.py index e2b6fda07..5a1a43a79 100644 --- a/netbox/dcim/forms/filtersets.py +++ b/netbox/dcim/forms/filtersets.py @@ -7,6 +7,7 @@ from dcim.models import * from extras.forms import LocalConfigContextFilterForm from extras.models import ConfigTemplate from ipam.models import ASN, VRF +from netbox.choices import * from netbox.forms import NetBoxModelFilterSetForm from tenancy.forms import ContactModelFilterForm, TenancyFilterForm from users.models import User diff --git a/netbox/dcim/models/devices.py b/netbox/dcim/models/devices.py index a2b78957e..8af43e2a5 100644 --- a/netbox/dcim/models/devices.py +++ b/netbox/dcim/models/devices.py @@ -21,11 +21,12 @@ from extras.querysets import ConfigContextModelQuerySet from netbox.choices import ColorChoices from netbox.config import ConfigItem from netbox.models import OrganizationalModel, PrimaryModel +from netbox.models.mixins import WeightMixin from netbox.models.features import ContactsMixin, ImageAttachmentsMixin from utilities.fields import ColorField, CounterCacheField, NaturalOrderingField from utilities.tracking import TrackingModelMixin from .device_components import * -from .mixins import RenderConfigMixin, WeightMixin +from .mixins import RenderConfigMixin __all__ = ( diff --git a/netbox/dcim/models/mixins.py b/netbox/dcim/models/mixins.py index d4a05699c..8dbcb35b7 100644 --- a/netbox/dcim/models/mixins.py +++ b/netbox/dcim/models/mixins.py @@ -1,56 +1,12 @@ from django.core.exceptions import ValidationError from django.db import models from django.utils.translation import gettext_lazy as _ -from dcim.choices import * -from utilities.conversion import to_grams __all__ = ( 'RenderConfigMixin', - 'WeightMixin', ) -class WeightMixin(models.Model): - weight = models.DecimalField( - verbose_name=_('weight'), - max_digits=8, - decimal_places=2, - blank=True, - null=True - ) - weight_unit = models.CharField( - verbose_name=_('weight unit'), - max_length=50, - choices=WeightUnitChoices, - blank=True, - ) - # Stores the normalized weight (in grams) for database ordering - _abs_weight = models.PositiveBigIntegerField( - blank=True, - null=True - ) - - class Meta: - abstract = True - - def save(self, *args, **kwargs): - - # Store the given weight (if any) in grams for use in database ordering - if self.weight and self.weight_unit: - self._abs_weight = to_grams(self.weight, self.weight_unit) - else: - self._abs_weight = None - - super().save(*args, **kwargs) - - def clean(self): - super().clean() - - # Validate weight and weight_unit - if self.weight and not self.weight_unit: - raise ValidationError(_("Must specify a unit when setting a weight")) - - class RenderConfigMixin(models.Model): config_template = models.ForeignKey( to='extras.ConfigTemplate', diff --git a/netbox/dcim/models/racks.py b/netbox/dcim/models/racks.py index 3aead09ca..fe2e9560d 100644 --- a/netbox/dcim/models/racks.py +++ b/netbox/dcim/models/racks.py @@ -16,13 +16,13 @@ from dcim.constants import * from dcim.svg import RackElevationSVG from netbox.choices import ColorChoices from netbox.models import OrganizationalModel, PrimaryModel +from netbox.models.mixins import WeightMixin from netbox.models.features import ContactsMixin, ImageAttachmentsMixin from utilities.conversion import to_grams from utilities.data import array_to_string, drange from utilities.fields import ColorField, NaturalOrderingField from .device_components import PowerPort from .devices import Device, Module -from .mixins import WeightMixin from .power import PowerFeed __all__ = ( diff --git a/netbox/netbox/choices.py b/netbox/netbox/choices.py index 4fd730255..5c3110745 100644 --- a/netbox/netbox/choices.py +++ b/netbox/netbox/choices.py @@ -7,8 +7,10 @@ __all__ = ( 'ButtonColorChoices', 'ColorChoices', 'CSVDelimiterChoices', + 'DistanceUnitChoices', 'ImportFormatChoices', 'ImportMethodChoices', + 'WeightUnitChoices', ) @@ -157,3 +159,39 @@ class CSVDelimiterChoices(ChoiceSet): (SEMICOLON, _('Semicolon')), (TAB, _('Tab')), ] + + +class DistanceUnitChoices(ChoiceSet): + + # Metric + UNIT_KILOMETER = 'km' + UNIT_METER = 'm' + + # Imperial + UNIT_MILE = 'mi' + UNIT_FOOT = 'ft' + + CHOICES = ( + (UNIT_KILOMETER, _('Kilometers')), + (UNIT_METER, _('Meters')), + (UNIT_MILE, _('Miles')), + (UNIT_FOOT, _('Feet')), + ) + + +class WeightUnitChoices(ChoiceSet): + + # Metric + UNIT_KILOGRAM = 'kg' + UNIT_GRAM = 'g' + + # Imperial + UNIT_POUND = 'lb' + UNIT_OUNCE = 'oz' + + CHOICES = ( + (UNIT_KILOGRAM, _('Kilograms')), + (UNIT_GRAM, _('Grams')), + (UNIT_POUND, _('Pounds')), + (UNIT_OUNCE, _('Ounces')), + ) diff --git a/netbox/netbox/models/mixins.py b/netbox/netbox/models/mixins.py new file mode 100644 index 000000000..dd28126b7 --- /dev/null +++ b/netbox/netbox/models/mixins.py @@ -0,0 +1,97 @@ +from django.core.exceptions import ValidationError +from django.db import models +from django.utils.translation import gettext_lazy as _ +from netbox.choices import * +from utilities.conversion import to_grams + +__all__ = ( + 'DistanceMixin', + 'WeightMixin', +) + + +class WeightMixin(models.Model): + weight = models.DecimalField( + verbose_name=_('weight'), + max_digits=8, + decimal_places=2, + blank=True, + null=True + ) + weight_unit = models.CharField( + verbose_name=_('weight unit'), + max_length=50, + choices=WeightUnitChoices, + blank=True, + ) + # Stores the normalized weight (in grams) for database ordering + _abs_weight = models.PositiveBigIntegerField( + blank=True, + null=True + ) + + class Meta: + abstract = True + + def save(self, *args, **kwargs): + + # Store the given weight (if any) in grams for use in database ordering + if self.weight and self.weight_unit: + self._abs_weight = to_grams(self.weight, self.weight_unit) + else: + self._abs_weight = None + + super().save(*args, **kwargs) + + def clean(self): + super().clean() + + # Validate weight and weight_unit + if self.weight and not self.weight_unit: + raise ValidationError(_("Must specify a unit when setting a weight")) + + +class DistanceMixin(models.Model): + distance = models.DecimalField( + verbose_name=_('distance'), + max_digits=8, + decimal_places=2, + blank=True, + null=True + ) + distance_unit = models.CharField( + verbose_name=_('distance unit'), + max_length=50, + choices=DistanceUnitChoices, + blank=True, + ) + # Stores the normalized distance (in meters) for database ordering + _abs_distance = models.DecimalField( + max_digits=10, + decimal_places=4, + blank=True, + null=True + ) + + class Meta: + abstract = True + + def save(self, *args, **kwargs): + # Store the given distance (if any) in meters for use in database ordering + if self.distance is not None and self.distance_unit: + self._abs_distance = to_meters(self.distance, self.distance_unit) + else: + self._abs_distance = None + + # Clear distance_unit if no distance is defined + if self.distance is None: + self.distance_unit = '' + + super().save(*args, **kwargs) + + def clean(self): + super().clean() + + # Validate distance and distance_unit + if self.distance and not self.distance_unit: + raise ValidationError(_("Must specify a unit when setting a distance")) diff --git a/netbox/utilities/conversion.py b/netbox/utilities/conversion.py index cd75c3c17..ee0175e7c 100644 --- a/netbox/utilities/conversion.py +++ b/netbox/utilities/conversion.py @@ -2,7 +2,8 @@ from decimal import Decimal from django.utils.translation import gettext as _ -from dcim.choices import CableLengthUnitChoices, WeightUnitChoices +from dcim.choices import CableLengthUnitChoices +from netbox.choices import WeightUnitChoices __all__ = ( 'to_grams', diff --git a/netbox/wireless/api/serializers_/wirelesslinks.py b/netbox/wireless/api/serializers_/wirelesslinks.py index 1f3bcfdea..494c3514b 100644 --- a/netbox/wireless/api/serializers_/wirelesslinks.py +++ b/netbox/wireless/api/serializers_/wirelesslinks.py @@ -4,6 +4,7 @@ from dcim.api.serializers_.device_components import InterfaceSerializer from dcim.choices import LinkStatusChoices from netbox.api.fields import ChoiceField from netbox.api.serializers import NetBoxModelSerializer +from netbox.choices import * from tenancy.api.serializers_.tenants import TenantSerializer from wireless.choices import * from wireless.models import WirelessLink @@ -20,7 +21,7 @@ class WirelessLinkSerializer(NetBoxModelSerializer): tenant = TenantSerializer(nested=True, required=False, allow_null=True) auth_type = ChoiceField(choices=WirelessAuthTypeChoices, required=False, allow_blank=True) auth_cipher = ChoiceField(choices=WirelessAuthCipherChoices, required=False, allow_blank=True) - distance_unit = ChoiceField(choices=WirelessLinkDistanceUnitChoices, allow_blank=True, required=False, allow_null=True) + distance_unit = ChoiceField(choices=DistanceUnitChoices, allow_blank=True, required=False, allow_null=True) class Meta: model = WirelessLink diff --git a/netbox/wireless/choices.py b/netbox/wireless/choices.py index f17ea584d..710cd3a8d 100644 --- a/netbox/wireless/choices.py +++ b/netbox/wireless/choices.py @@ -481,21 +481,3 @@ class WirelessAuthCipherChoices(ChoiceSet): (CIPHER_TKIP, 'TKIP'), (CIPHER_AES, 'AES'), ) - - -class WirelessLinkDistanceUnitChoices(ChoiceSet): - - # Metric - UNIT_KILOMETER = 'km' - UNIT_METER = 'm' - - # Imperial - UNIT_MILE = 'mi' - UNIT_FOOT = 'ft' - - CHOICES = ( - (UNIT_KILOMETER, _('Kilometers')), - (UNIT_METER, _('Meters')), - (UNIT_MILE, _('Miles')), - (UNIT_FOOT, _('Feet')), - ) diff --git a/netbox/wireless/forms/bulk_edit.py b/netbox/wireless/forms/bulk_edit.py index 64a9bfa98..c8b378104 100644 --- a/netbox/wireless/forms/bulk_edit.py +++ b/netbox/wireless/forms/bulk_edit.py @@ -3,6 +3,7 @@ from django.utils.translation import gettext_lazy as _ from dcim.choices import LinkStatusChoices from ipam.models import VLAN +from netbox.choices import * from netbox.forms import NetBoxModelBulkEditForm from tenancy.models import Tenant from utilities.forms import add_blank_choice @@ -132,7 +133,7 @@ class WirelessLinkBulkEditForm(NetBoxModelBulkEditForm): ) distance_unit = forms.ChoiceField( label=_('Distance unit'), - choices=add_blank_choice(WirelessLinkDistanceUnitChoices), + choices=add_blank_choice(DistanceUnitChoices), required=False, initial='' ) diff --git a/netbox/wireless/forms/bulk_import.py b/netbox/wireless/forms/bulk_import.py index 878afd5c8..cff3e49af 100644 --- a/netbox/wireless/forms/bulk_import.py +++ b/netbox/wireless/forms/bulk_import.py @@ -3,6 +3,7 @@ from django.utils.translation import gettext_lazy as _ from dcim.choices import LinkStatusChoices from dcim.models import Interface from ipam.models import VLAN +from netbox.choices import * from netbox.forms import NetBoxModelImportForm from tenancy.models import Tenant from utilities.forms.fields import CSVChoiceField, CSVModelChoiceField, SlugField @@ -114,7 +115,7 @@ class WirelessLinkImportForm(NetBoxModelImportForm): ) distance_unit = CSVChoiceField( label=_('Distance unit'), - choices=WirelessLinkDistanceUnitChoices, + choices=DistanceUnitChoices, required=False, help_text=_('Distance unit') ) diff --git a/netbox/wireless/forms/filtersets.py b/netbox/wireless/forms/filtersets.py index f87cadfb9..6439a2516 100644 --- a/netbox/wireless/forms/filtersets.py +++ b/netbox/wireless/forms/filtersets.py @@ -2,6 +2,7 @@ from django import forms from django.utils.translation import gettext_lazy as _ from dcim.choices import LinkStatusChoices +from netbox.choices import * from netbox.forms import NetBoxModelFilterSetForm from tenancy.forms import TenancyFilterForm from utilities.forms import add_blank_choice @@ -104,7 +105,7 @@ class WirelessLinkFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): ) distance_unit = forms.ChoiceField( label=_('Distance unit'), - choices=add_blank_choice(WirelessLinkDistanceUnitChoices), + choices=add_blank_choice(DistanceUnitChoices), required=False ) tag = TagFilterField(model) diff --git a/netbox/wireless/models.py b/netbox/wireless/models.py index 4214ac29d..2acb9c66e 100644 --- a/netbox/wireless/models.py +++ b/netbox/wireless/models.py @@ -6,6 +6,7 @@ from django.utils.translation import gettext_lazy as _ from dcim.choices import LinkStatusChoices from dcim.constants import WIRELESS_IFACE_TYPES from netbox.models import NestedGroupModel, PrimaryModel +from netbox.models.mixins import DistanceMixin from utilities.conversion import to_meters from .choices import * from .constants import * @@ -132,7 +133,7 @@ def get_wireless_interface_types(): return {'type__in': WIRELESS_IFACE_TYPES} -class WirelessLink(WirelessAuthenticationBase, PrimaryModel): +class WirelessLink(WirelessAuthenticationBase, DistanceMixin, PrimaryModel): """ A point-to-point connection between two wireless Interfaces. """ @@ -161,26 +162,6 @@ class WirelessLink(WirelessAuthenticationBase, PrimaryModel): choices=LinkStatusChoices, default=LinkStatusChoices.STATUS_CONNECTED ) - distance = models.DecimalField( - verbose_name=_('distance'), - max_digits=8, - decimal_places=2, - blank=True, - null=True - ) - distance_unit = models.CharField( - verbose_name=_('distance unit'), - max_length=50, - choices=WirelessLinkDistanceUnitChoices, - blank=True, - ) - # Stores the normalized distance (in meters) for database ordering - _abs_distance = models.DecimalField( - max_digits=10, - decimal_places=4, - blank=True, - null=True - ) tenant = models.ForeignKey( to='tenancy.Tenant', on_delete=models.PROTECT, @@ -231,10 +212,6 @@ class WirelessLink(WirelessAuthenticationBase, PrimaryModel): def clean(self): super().clean() - # Validate distance and distance_unit - if self.distance is not None and not self.distance_unit: - raise ValidationError(_("Must specify a unit when setting a wireless distance")) - # Validate interface types if self.interface_a.type not in WIRELESS_IFACE_TYPES: raise ValidationError({ @@ -250,16 +227,6 @@ class WirelessLink(WirelessAuthenticationBase, PrimaryModel): }) def save(self, *args, **kwargs): - # Store the given distance (if any) in meters for use in database ordering - if self.distance is not None and self.distance_unit: - self._abs_distance = to_meters(self.distance, self.distance_unit) - else: - self._abs_distance = None - - # Clear distance_unit if no distance is defined - if self.distance is None: - self.distance_unit = '' - # Store the parent Device for the A and B interfaces self._interface_a_device = self.interface_a.device self._interface_b_device = self.interface_b.device