mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-08 16:48: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 tenancy.api.serializers_.tenants import TenantSerializer
|
||||
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 .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):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vlangroup-detail')
|
||||
scope_type = ContentTypeField(
|
||||
@ -37,11 +46,12 @@ class VLANGroupSerializer(NetBoxModelSerializer):
|
||||
|
||||
# Related object counts
|
||||
vlan_count = RelatedObjectCountField('vlans')
|
||||
vlan_id_ranges = NumericRangeArraySerializer()
|
||||
|
||||
class Meta:
|
||||
model = VLANGroup
|
||||
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'
|
||||
]
|
||||
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.fields import (
|
||||
CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, NumericArrayField,
|
||||
NumericRangeArrayField,
|
||||
)
|
||||
from utilities.forms.rendering import FieldSet
|
||||
from utilities.forms.widgets import BulkEditNullBooleanSelect
|
||||
@ -471,10 +472,11 @@ class VLANGroupBulkEditForm(NetBoxModelBulkEditForm):
|
||||
'group_id': '$clustergroup',
|
||||
}
|
||||
)
|
||||
vlan_id_ranges = NumericRangeArrayField()
|
||||
|
||||
model = VLANGroup
|
||||
fieldsets = (
|
||||
FieldSet('site', 'description'),
|
||||
FieldSet('site', 'vlan_id_ranges', 'description'),
|
||||
FieldSet(
|
||||
'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 tenancy.models import Tenant
|
||||
from utilities.forms.fields import (
|
||||
CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, CSVModelMultipleChoiceField, SlugField
|
||||
CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, CSVModelMultipleChoiceField, SlugField,
|
||||
NumericRangeArrayField,
|
||||
)
|
||||
from virtualization.models import VirtualMachine, VMInterface
|
||||
|
||||
@ -411,10 +412,11 @@ class VLANGroupImportForm(NetBoxModelImportForm):
|
||||
required=False,
|
||||
label=_('Scope type (app & model)')
|
||||
)
|
||||
vlan_id_ranges = NumericRangeArrayField()
|
||||
|
||||
class Meta:
|
||||
model = VLANGroup
|
||||
fields = ('name', 'slug', 'scope_type', 'scope_id', 'description', 'tags')
|
||||
fields = ('name', 'slug', 'scope_type', 'scope_id', 'vlan_id_ranges', 'description', 'tags')
|
||||
labels = {
|
||||
'scope_id': 'Scope ID',
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ from ipam.choices import *
|
||||
from ipam.constants import *
|
||||
from ipam.querysets import VLANQuerySet, VLANGroupQuerySet
|
||||
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
|
||||
|
||||
__all__ = (
|
||||
@ -129,7 +129,7 @@ class VLANGroup(OrganizationalModel):
|
||||
|
||||
@property
|
||||
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):
|
||||
|
@ -8,7 +8,9 @@ __all__ = (
|
||||
'deepmerge',
|
||||
'drange',
|
||||
'flatten_dict',
|
||||
'ranges_to_string',
|
||||
'shallow_compare_dict',
|
||||
'string_to_range_array',
|
||||
)
|
||||
|
||||
|
||||
@ -129,3 +131,31 @@ def check_ranges_overlap(ranges):
|
||||
return True
|
||||
|
||||
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.db.backends.postgresql.psycopg_any import NumericRange
|
||||
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
|
||||
|
||||
@ -42,17 +43,17 @@ class NumericRangeArrayField(forms.CharField):
|
||||
"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):
|
||||
if isinstance(value, str):
|
||||
return value
|
||||
return ','.join([f"{val.lower}-{val.upper}" for val in value])
|
||||
return ranges_to_string(value)
|
||||
|
||||
def to_python(self, value):
|
||||
if not 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
|
||||
return string_to_range_array(value)
|
||||
|
Loading…
Reference in New Issue
Block a user