mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-09 00:58:16 -06:00
9627 bulk import / edit
This commit is contained in:
parent
35a5165c87
commit
7628145982
@ -10,6 +10,7 @@ from netbox.api.fields import ChoiceField, ContentTypeField, RelatedObjectCountF
|
|||||||
from netbox.api.serializers import NetBoxModelSerializer
|
from netbox.api.serializers import NetBoxModelSerializer
|
||||||
from tenancy.api.serializers_.tenants import TenantSerializer
|
from tenancy.api.serializers_.tenants import TenantSerializer
|
||||||
from utilities.api import get_serializer_for_model
|
from utilities.api import get_serializer_for_model
|
||||||
|
from utilities.data import ranges_to_string, string_to_range_array
|
||||||
from vpn.api.serializers_.l2vpn import L2VPNTerminationSerializer
|
from vpn.api.serializers_.l2vpn import L2VPNTerminationSerializer
|
||||||
from .roles import RoleSerializer
|
from .roles import RoleSerializer
|
||||||
|
|
||||||
@ -21,6 +22,14 @@ __all__ = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class NumericRangeArraySerializer(serializers.BaseSerializer):
|
||||||
|
def to_internal_value(self, data):
|
||||||
|
return string_to_range_array(data)
|
||||||
|
|
||||||
|
def to_representation(self, instance):
|
||||||
|
return ranges_to_string(data)
|
||||||
|
|
||||||
|
|
||||||
class VLANGroupSerializer(NetBoxModelSerializer):
|
class VLANGroupSerializer(NetBoxModelSerializer):
|
||||||
url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vlangroup-detail')
|
url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vlangroup-detail')
|
||||||
scope_type = ContentTypeField(
|
scope_type = ContentTypeField(
|
||||||
@ -37,11 +46,12 @@ class VLANGroupSerializer(NetBoxModelSerializer):
|
|||||||
|
|
||||||
# Related object counts
|
# Related object counts
|
||||||
vlan_count = RelatedObjectCountField('vlans')
|
vlan_count = RelatedObjectCountField('vlans')
|
||||||
|
vlan_id_ranges = NumericRangeArraySerializer()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = VLANGroup
|
model = VLANGroup
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display', 'name', 'slug', 'scope_type', 'scope_id', 'scope',
|
'id', 'url', 'display', 'name', 'slug', 'scope_type', 'scope_id', 'scope', 'vlan_id_ranges',
|
||||||
'description', 'tags', 'custom_fields', 'created', 'last_updated', 'vlan_count', 'utilization'
|
'description', 'tags', 'custom_fields', 'created', 'last_updated', 'vlan_count', 'utilization'
|
||||||
]
|
]
|
||||||
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'vlan_count')
|
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'vlan_count')
|
||||||
|
@ -12,6 +12,7 @@ from tenancy.models import Tenant
|
|||||||
from utilities.forms import add_blank_choice
|
from utilities.forms import add_blank_choice
|
||||||
from utilities.forms.fields import (
|
from utilities.forms.fields import (
|
||||||
CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, NumericArrayField,
|
CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, NumericArrayField,
|
||||||
|
NumericRangeArrayField,
|
||||||
)
|
)
|
||||||
from utilities.forms.rendering import FieldSet
|
from utilities.forms.rendering import FieldSet
|
||||||
from utilities.forms.widgets import BulkEditNullBooleanSelect
|
from utilities.forms.widgets import BulkEditNullBooleanSelect
|
||||||
@ -471,10 +472,11 @@ class VLANGroupBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
'group_id': '$clustergroup',
|
'group_id': '$clustergroup',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
vlan_id_ranges = NumericRangeArrayField()
|
||||||
|
|
||||||
model = VLANGroup
|
model = VLANGroup
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet('site', 'description'),
|
FieldSet('site', 'vlan_id_ranges', 'description'),
|
||||||
FieldSet(
|
FieldSet(
|
||||||
'scope_type', 'region', 'sitegroup', 'site', 'location', 'rack', 'clustergroup', 'cluster', name=_('Scope')
|
'scope_type', 'region', 'sitegroup', 'site', 'location', 'rack', 'clustergroup', 'cluster', name=_('Scope')
|
||||||
),
|
),
|
||||||
|
@ -9,7 +9,8 @@ from ipam.models 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 (
|
||||||
CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, CSVModelMultipleChoiceField, SlugField
|
CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, CSVModelMultipleChoiceField, SlugField,
|
||||||
|
NumericRangeArrayField,
|
||||||
)
|
)
|
||||||
from virtualization.models import VirtualMachine, VMInterface
|
from virtualization.models import VirtualMachine, VMInterface
|
||||||
|
|
||||||
@ -411,10 +412,11 @@ class VLANGroupImportForm(NetBoxModelImportForm):
|
|||||||
required=False,
|
required=False,
|
||||||
label=_('Scope type (app & model)')
|
label=_('Scope type (app & model)')
|
||||||
)
|
)
|
||||||
|
vlan_id_ranges = NumericRangeArrayField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = VLANGroup
|
model = VLANGroup
|
||||||
fields = ('name', 'slug', 'scope_type', 'scope_id', 'description', 'tags')
|
fields = ('name', 'slug', 'scope_type', 'scope_id', 'vlan_id_ranges', 'description', 'tags')
|
||||||
labels = {
|
labels = {
|
||||||
'scope_id': 'Scope ID',
|
'scope_id': 'Scope ID',
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ from ipam.choices import *
|
|||||||
from ipam.constants import *
|
from ipam.constants import *
|
||||||
from ipam.querysets import VLANQuerySet, VLANGroupQuerySet
|
from ipam.querysets import VLANQuerySet, VLANGroupQuerySet
|
||||||
from netbox.models import OrganizationalModel, PrimaryModel
|
from netbox.models import OrganizationalModel, PrimaryModel
|
||||||
from utilities.data import check_ranges_overlap
|
from utilities.data import check_ranges_overlap, ranges_to_string
|
||||||
from virtualization.models import VMInterface
|
from virtualization.models import VMInterface
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
@ -129,7 +129,7 @@ class VLANGroup(OrganizationalModel):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def vlan_ranges(self):
|
def vlan_ranges(self):
|
||||||
return ','.join([f"{vlan_range.lower}-{vlan_range.upper}" for vlan_range in self.vlan_id_ranges])
|
return ranges_to_string(self.vlan_id_ranges)
|
||||||
|
|
||||||
|
|
||||||
class VLAN(PrimaryModel):
|
class VLAN(PrimaryModel):
|
||||||
|
@ -8,7 +8,9 @@ __all__ = (
|
|||||||
'deepmerge',
|
'deepmerge',
|
||||||
'drange',
|
'drange',
|
||||||
'flatten_dict',
|
'flatten_dict',
|
||||||
|
'ranges_to_string',
|
||||||
'shallow_compare_dict',
|
'shallow_compare_dict',
|
||||||
|
'string_to_range_array',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -129,3 +131,31 @@ def check_ranges_overlap(ranges):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def ranges_to_string(ranges):
|
||||||
|
"""
|
||||||
|
Generate a human-friendly string from a set of ranges. Intended for use with ArrayField.
|
||||||
|
For example:
|
||||||
|
[1-100, 200-300] => "1-100, 200-300"
|
||||||
|
"""
|
||||||
|
return ', '.join([f"{val.lower}-{val.upper}" for val in value])
|
||||||
|
|
||||||
|
|
||||||
|
def string_to_range_array(value):
|
||||||
|
"""
|
||||||
|
Given a string in the format "1-100, 200-300" create an array of ranges. Intended for use with ArrayField.
|
||||||
|
For example:
|
||||||
|
"1-100, 200-300" => [1-100, 200-300]
|
||||||
|
"""
|
||||||
|
if not value:
|
||||||
|
return None
|
||||||
|
ranges = value.split(",")
|
||||||
|
values = []
|
||||||
|
for dash_range in value.split(','):
|
||||||
|
if '-' not in dash_range:
|
||||||
|
return None
|
||||||
|
|
||||||
|
lower, upper = dash_range.split('-')
|
||||||
|
values.append(NumericRange(int(lower), int(upper)))
|
||||||
|
return values
|
||||||
|
@ -2,6 +2,7 @@ from django import forms
|
|||||||
from django.contrib.postgres.forms import SimpleArrayField
|
from django.contrib.postgres.forms import SimpleArrayField
|
||||||
from django.db.backends.postgresql.psycopg_any import NumericRange
|
from django.db.backends.postgresql.psycopg_any import NumericRange
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from utilities.data import ranges_to_string, string_to_range_array
|
||||||
|
|
||||||
from ..utils import parse_numeric_range
|
from ..utils import parse_numeric_range
|
||||||
|
|
||||||
@ -42,17 +43,17 @@ class NumericRangeArrayField(forms.CharField):
|
|||||||
"Example: <code>1-5,20-30</code>"
|
"Example: <code>1-5,20-30</code>"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def clean(self, value):
|
||||||
|
if value and not self.to_python(value):
|
||||||
|
raise forms.ValidationError(
|
||||||
|
_("Invalid ranges ({value}). Must be range of number '100-200' and ranges must be in ascending order.").format(value=value)
|
||||||
|
)
|
||||||
|
return super().clean(value)
|
||||||
|
|
||||||
def prepare_value(self, value):
|
def prepare_value(self, value):
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
return value
|
return value
|
||||||
return ','.join([f"{val.lower}-{val.upper}" for val in value])
|
return ranges_to_string(value)
|
||||||
|
|
||||||
def to_python(self, value):
|
def to_python(self, value):
|
||||||
if not value:
|
return string_to_range_array(value)
|
||||||
return None
|
|
||||||
ranges = value.split(",")
|
|
||||||
values = []
|
|
||||||
for dash_range in value.split(','):
|
|
||||||
lower, upper = dash_range.split('-')
|
|
||||||
values.append(NumericRange(int(lower), int(upper)))
|
|
||||||
return values
|
|
||||||
|
Loading…
Reference in New Issue
Block a user