16547 Add distance to Circuit

This commit is contained in:
Arthur Hanson 2024-09-26 15:14:45 -07:00
parent 85396866bc
commit 79e51084bf
20 changed files with 185 additions and 123 deletions

View File

@ -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),
),
]

View File

@ -6,6 +6,7 @@ from django.utils.translation import gettext_lazy as _
from circuits.choices import * from circuits.choices import *
from dcim.models import CabledObjectModel from dcim.models import CabledObjectModel
from netbox.models import ChangeLoggedModel, OrganizationalModel, PrimaryModel 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 netbox.models.features import ContactsMixin, CustomFieldsMixin, CustomLinksMixin, ExportTemplatesMixin, ImageAttachmentsMixin, TagsMixin
from utilities.fields import ColorField from utilities.fields import ColorField
@ -37,7 +38,7 @@ class CircuitType(OrganizationalModel):
verbose_name_plural = _('circuit types') 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 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 circuits. Each circuit is also assigned a CircuitType and a Site, and may optionally be assigned to a particular

View File

@ -7,6 +7,7 @@ from dcim.choices import *
from dcim.models import DeviceType, ModuleType from dcim.models import DeviceType, ModuleType
from netbox.api.fields import ChoiceField, RelatedObjectCountField from netbox.api.fields import ChoiceField, RelatedObjectCountField
from netbox.api.serializers import NetBoxModelSerializer from netbox.api.serializers import NetBoxModelSerializer
from netbox.choices import *
from .manufacturers import ManufacturerSerializer from .manufacturers import ManufacturerSerializer
from .platforms import PlatformSerializer from .platforms import PlatformSerializer

View File

@ -6,6 +6,7 @@ from dcim.constants import *
from dcim.models import Rack, RackReservation, RackRole, RackType from dcim.models import Rack, RackReservation, RackRole, RackType
from netbox.api.fields import ChoiceField, RelatedObjectCountField from netbox.api.fields import ChoiceField, RelatedObjectCountField
from netbox.api.serializers import NetBoxModelSerializer from netbox.api.serializers import NetBoxModelSerializer
from netbox.choices import *
from netbox.config import ConfigItem from netbox.config import ConfigItem
from tenancy.api.serializers_.tenants import TenantSerializer from tenancy.api.serializers_.tenants import TenantSerializer
from users.api.serializers_.users import UserSerializer from users.api.serializers_.users import UserSerializer

View File

@ -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 # CableTerminations
# #

View File

@ -8,6 +8,7 @@ from dcim.constants import *
from dcim.models import * from dcim.models import *
from extras.models import ConfigTemplate from extras.models import ConfigTemplate
from ipam.models import ASN, VLAN, VLANGroup, VRF from ipam.models import ASN, VLAN, VLANGroup, VRF
from netbox.choices import *
from netbox.forms import NetBoxModelBulkEditForm from netbox.forms import NetBoxModelBulkEditForm
from tenancy.models import Tenant from tenancy.models import Tenant
from users.models import User from users.models import User

View File

@ -10,6 +10,7 @@ from dcim.constants import *
from dcim.models import * from dcim.models import *
from extras.models import ConfigTemplate from extras.models import ConfigTemplate
from ipam.models import VRF, IPAddress from ipam.models import VRF, IPAddress
from netbox.choices import *
from netbox.forms import NetBoxModelImportForm from netbox.forms import NetBoxModelImportForm
from tenancy.models import Tenant from tenancy.models import Tenant
from utilities.forms.fields import ( from utilities.forms.fields import (

View File

@ -7,6 +7,7 @@ from dcim.models import *
from extras.forms import LocalConfigContextFilterForm from extras.forms import LocalConfigContextFilterForm
from extras.models import ConfigTemplate from extras.models import ConfigTemplate
from ipam.models import ASN, VRF from ipam.models import ASN, VRF
from netbox.choices import *
from netbox.forms import NetBoxModelFilterSetForm from netbox.forms import NetBoxModelFilterSetForm
from tenancy.forms import ContactModelFilterForm, TenancyFilterForm from tenancy.forms import ContactModelFilterForm, TenancyFilterForm
from users.models import User from users.models import User

View File

@ -21,11 +21,12 @@ from extras.querysets import ConfigContextModelQuerySet
from netbox.choices import ColorChoices from netbox.choices import ColorChoices
from netbox.config import ConfigItem from netbox.config import ConfigItem
from netbox.models import OrganizationalModel, PrimaryModel from netbox.models import OrganizationalModel, PrimaryModel
from netbox.models.mixins import WeightMixin
from netbox.models.features import ContactsMixin, ImageAttachmentsMixin from netbox.models.features import ContactsMixin, ImageAttachmentsMixin
from utilities.fields import ColorField, CounterCacheField, NaturalOrderingField from utilities.fields import ColorField, CounterCacheField, NaturalOrderingField
from utilities.tracking import TrackingModelMixin from utilities.tracking import TrackingModelMixin
from .device_components import * from .device_components import *
from .mixins import RenderConfigMixin, WeightMixin from .mixins import RenderConfigMixin
__all__ = ( __all__ = (

View File

@ -1,56 +1,12 @@
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from dcim.choices import *
from utilities.conversion import to_grams
__all__ = ( __all__ = (
'RenderConfigMixin', '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): class RenderConfigMixin(models.Model):
config_template = models.ForeignKey( config_template = models.ForeignKey(
to='extras.ConfigTemplate', to='extras.ConfigTemplate',

View File

@ -16,13 +16,13 @@ from dcim.constants import *
from dcim.svg import RackElevationSVG from dcim.svg import RackElevationSVG
from netbox.choices import ColorChoices from netbox.choices import ColorChoices
from netbox.models import OrganizationalModel, PrimaryModel from netbox.models import OrganizationalModel, PrimaryModel
from netbox.models.mixins import WeightMixin
from netbox.models.features import ContactsMixin, ImageAttachmentsMixin from netbox.models.features import ContactsMixin, ImageAttachmentsMixin
from utilities.conversion import to_grams from utilities.conversion import to_grams
from utilities.data import array_to_string, drange from utilities.data import array_to_string, drange
from utilities.fields import ColorField, NaturalOrderingField from utilities.fields import ColorField, NaturalOrderingField
from .device_components import PowerPort from .device_components import PowerPort
from .devices import Device, Module from .devices import Device, Module
from .mixins import WeightMixin
from .power import PowerFeed from .power import PowerFeed
__all__ = ( __all__ = (

View File

@ -7,8 +7,10 @@ __all__ = (
'ButtonColorChoices', 'ButtonColorChoices',
'ColorChoices', 'ColorChoices',
'CSVDelimiterChoices', 'CSVDelimiterChoices',
'DistanceUnitChoices',
'ImportFormatChoices', 'ImportFormatChoices',
'ImportMethodChoices', 'ImportMethodChoices',
'WeightUnitChoices',
) )
@ -157,3 +159,39 @@ class CSVDelimiterChoices(ChoiceSet):
(SEMICOLON, _('Semicolon')), (SEMICOLON, _('Semicolon')),
(TAB, _('Tab')), (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')),
)

View File

@ -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"))

View File

@ -2,7 +2,8 @@ from decimal import Decimal
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from dcim.choices import CableLengthUnitChoices, WeightUnitChoices from dcim.choices import CableLengthUnitChoices
from netbox.choices import WeightUnitChoices
__all__ = ( __all__ = (
'to_grams', 'to_grams',

View File

@ -4,6 +4,7 @@ from dcim.api.serializers_.device_components import InterfaceSerializer
from dcim.choices import LinkStatusChoices from dcim.choices import LinkStatusChoices
from netbox.api.fields import ChoiceField from netbox.api.fields import ChoiceField
from netbox.api.serializers import NetBoxModelSerializer from netbox.api.serializers import NetBoxModelSerializer
from netbox.choices import *
from tenancy.api.serializers_.tenants import TenantSerializer from tenancy.api.serializers_.tenants import TenantSerializer
from wireless.choices import * from wireless.choices import *
from wireless.models import WirelessLink from wireless.models import WirelessLink
@ -20,7 +21,7 @@ class WirelessLinkSerializer(NetBoxModelSerializer):
tenant = TenantSerializer(nested=True, required=False, allow_null=True) tenant = TenantSerializer(nested=True, required=False, allow_null=True)
auth_type = ChoiceField(choices=WirelessAuthTypeChoices, required=False, allow_blank=True) auth_type = ChoiceField(choices=WirelessAuthTypeChoices, required=False, allow_blank=True)
auth_cipher = ChoiceField(choices=WirelessAuthCipherChoices, 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: class Meta:
model = WirelessLink model = WirelessLink

View File

@ -481,21 +481,3 @@ class WirelessAuthCipherChoices(ChoiceSet):
(CIPHER_TKIP, 'TKIP'), (CIPHER_TKIP, 'TKIP'),
(CIPHER_AES, 'AES'), (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')),
)

View File

@ -3,6 +3,7 @@ from django.utils.translation import gettext_lazy as _
from dcim.choices import LinkStatusChoices from dcim.choices import LinkStatusChoices
from ipam.models import VLAN from ipam.models import VLAN
from netbox.choices import *
from netbox.forms import NetBoxModelBulkEditForm from netbox.forms import NetBoxModelBulkEditForm
from tenancy.models import Tenant from tenancy.models import Tenant
from utilities.forms import add_blank_choice from utilities.forms import add_blank_choice
@ -132,7 +133,7 @@ class WirelessLinkBulkEditForm(NetBoxModelBulkEditForm):
) )
distance_unit = forms.ChoiceField( distance_unit = forms.ChoiceField(
label=_('Distance unit'), label=_('Distance unit'),
choices=add_blank_choice(WirelessLinkDistanceUnitChoices), choices=add_blank_choice(DistanceUnitChoices),
required=False, required=False,
initial='' initial=''
) )

View File

@ -3,6 +3,7 @@ from django.utils.translation import gettext_lazy as _
from dcim.choices import LinkStatusChoices from dcim.choices import LinkStatusChoices
from dcim.models import Interface from dcim.models import Interface
from ipam.models import VLAN from ipam.models import VLAN
from netbox.choices import *
from netbox.forms import NetBoxModelImportForm from netbox.forms import NetBoxModelImportForm
from tenancy.models import Tenant from tenancy.models import Tenant
from utilities.forms.fields import CSVChoiceField, CSVModelChoiceField, SlugField from utilities.forms.fields import CSVChoiceField, CSVModelChoiceField, SlugField
@ -114,7 +115,7 @@ class WirelessLinkImportForm(NetBoxModelImportForm):
) )
distance_unit = CSVChoiceField( distance_unit = CSVChoiceField(
label=_('Distance unit'), label=_('Distance unit'),
choices=WirelessLinkDistanceUnitChoices, choices=DistanceUnitChoices,
required=False, required=False,
help_text=_('Distance unit') help_text=_('Distance unit')
) )

View File

@ -2,6 +2,7 @@ from django import forms
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from dcim.choices import LinkStatusChoices from dcim.choices import LinkStatusChoices
from netbox.choices import *
from netbox.forms import NetBoxModelFilterSetForm from netbox.forms import NetBoxModelFilterSetForm
from tenancy.forms import TenancyFilterForm from tenancy.forms import TenancyFilterForm
from utilities.forms import add_blank_choice from utilities.forms import add_blank_choice
@ -104,7 +105,7 @@ class WirelessLinkFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
) )
distance_unit = forms.ChoiceField( distance_unit = forms.ChoiceField(
label=_('Distance unit'), label=_('Distance unit'),
choices=add_blank_choice(WirelessLinkDistanceUnitChoices), choices=add_blank_choice(DistanceUnitChoices),
required=False required=False
) )
tag = TagFilterField(model) tag = TagFilterField(model)

View File

@ -6,6 +6,7 @@ from django.utils.translation import gettext_lazy as _
from dcim.choices import LinkStatusChoices from dcim.choices import LinkStatusChoices
from dcim.constants import WIRELESS_IFACE_TYPES from dcim.constants import WIRELESS_IFACE_TYPES
from netbox.models import NestedGroupModel, PrimaryModel from netbox.models import NestedGroupModel, PrimaryModel
from netbox.models.mixins import DistanceMixin
from utilities.conversion import to_meters from utilities.conversion import to_meters
from .choices import * from .choices import *
from .constants import * from .constants import *
@ -132,7 +133,7 @@ def get_wireless_interface_types():
return {'type__in': WIRELESS_IFACE_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. A point-to-point connection between two wireless Interfaces.
""" """
@ -161,26 +162,6 @@ class WirelessLink(WirelessAuthenticationBase, PrimaryModel):
choices=LinkStatusChoices, choices=LinkStatusChoices,
default=LinkStatusChoices.STATUS_CONNECTED 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( tenant = models.ForeignKey(
to='tenancy.Tenant', to='tenancy.Tenant',
on_delete=models.PROTECT, on_delete=models.PROTECT,
@ -231,10 +212,6 @@ class WirelessLink(WirelessAuthenticationBase, PrimaryModel):
def clean(self): def clean(self):
super().clean() 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 # Validate interface types
if self.interface_a.type not in WIRELESS_IFACE_TYPES: if self.interface_a.type not in WIRELESS_IFACE_TYPES:
raise ValidationError({ raise ValidationError({
@ -250,16 +227,6 @@ class WirelessLink(WirelessAuthenticationBase, PrimaryModel):
}) })
def save(self, *args, **kwargs): 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 # Store the parent Device for the A and B interfaces
self._interface_a_device = self.interface_a.device self._interface_a_device = self.interface_a.device
self._interface_b_device = self.interface_b.device self._interface_b_device = self.interface_b.device