mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-24 09:28:38 -06:00
7376 csv tags (#10802)
* 7376 add tags to CSV import * 7376 change help text * 7376 validate tags * 7376 fix tests * 7376 add tag validation tests * Introduce CSVModelMultipleChoiceField for CSV import tag assignment * Clean up CSVImportTestCase Co-authored-by: jeremystretch <jstretch@ns1.com>
This commit is contained in:
parent
bc6b5bc4be
commit
cdeb65e2fb
@ -18,7 +18,7 @@ class ProviderCSVForm(NetBoxModelCSVForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Provider
|
model = Provider
|
||||||
fields = (
|
fields = (
|
||||||
'name', 'slug', 'account', 'description', 'comments',
|
'name', 'slug', 'account', 'description', 'comments', 'tags',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ class ProviderNetworkCSVForm(NetBoxModelCSVForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = ProviderNetwork
|
model = ProviderNetwork
|
||||||
fields = [
|
fields = [
|
||||||
'provider', 'name', 'service_id', 'description', 'comments',
|
'provider', 'name', 'service_id', 'description', 'comments', 'tags'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ class CircuitTypeCSVForm(NetBoxModelCSVForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CircuitType
|
model = CircuitType
|
||||||
fields = ('name', 'slug', 'description')
|
fields = ('name', 'slug', 'description', 'tags')
|
||||||
help_texts = {
|
help_texts = {
|
||||||
'name': 'Name of circuit type',
|
'name': 'Name of circuit type',
|
||||||
}
|
}
|
||||||
@ -73,5 +73,5 @@ class CircuitCSVForm(NetBoxModelCSVForm):
|
|||||||
model = Circuit
|
model = Circuit
|
||||||
fields = [
|
fields = [
|
||||||
'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'termination_date', 'commit_rate',
|
'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'termination_date', 'commit_rate',
|
||||||
'description', 'comments',
|
'description', 'comments', 'tags'
|
||||||
]
|
]
|
||||||
|
@ -56,7 +56,7 @@ class RegionCSVForm(NetBoxModelCSVForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Region
|
model = Region
|
||||||
fields = ('name', 'slug', 'parent', 'description')
|
fields = ('name', 'slug', 'parent', 'description', 'tags')
|
||||||
|
|
||||||
|
|
||||||
class SiteGroupCSVForm(NetBoxModelCSVForm):
|
class SiteGroupCSVForm(NetBoxModelCSVForm):
|
||||||
@ -100,7 +100,7 @@ class SiteCSVForm(NetBoxModelCSVForm):
|
|||||||
model = Site
|
model = Site
|
||||||
fields = (
|
fields = (
|
||||||
'name', 'slug', 'status', 'region', 'group', 'tenant', 'facility', 'time_zone', 'description',
|
'name', 'slug', 'status', 'region', 'group', 'tenant', 'facility', 'time_zone', 'description',
|
||||||
'physical_address', 'shipping_address', 'latitude', 'longitude', 'comments',
|
'physical_address', 'shipping_address', 'latitude', 'longitude', 'comments', 'tags'
|
||||||
)
|
)
|
||||||
help_texts = {
|
help_texts = {
|
||||||
'time_zone': mark_safe(
|
'time_zone': mark_safe(
|
||||||
@ -137,7 +137,7 @@ class LocationCSVForm(NetBoxModelCSVForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Location
|
model = Location
|
||||||
fields = ('site', 'parent', 'name', 'slug', 'status', 'tenant', 'description')
|
fields = ('site', 'parent', 'name', 'slug', 'status', 'tenant', 'description', 'tags')
|
||||||
|
|
||||||
|
|
||||||
class RackRoleCSVForm(NetBoxModelCSVForm):
|
class RackRoleCSVForm(NetBoxModelCSVForm):
|
||||||
@ -145,7 +145,7 @@ class RackRoleCSVForm(NetBoxModelCSVForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = RackRole
|
model = RackRole
|
||||||
fields = ('name', 'slug', 'color', 'description')
|
fields = ('name', 'slug', 'color', 'description', 'tags')
|
||||||
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>)'),
|
||||||
}
|
}
|
||||||
@ -197,7 +197,7 @@ class RackCSVForm(NetBoxModelCSVForm):
|
|||||||
fields = (
|
fields = (
|
||||||
'site', 'location', 'name', 'facility_id', 'tenant', 'status', 'role', 'type', 'serial', 'asset_tag',
|
'site', 'location', 'name', 'facility_id', 'tenant', 'status', 'role', 'type', 'serial', 'asset_tag',
|
||||||
'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'mounting_depth',
|
'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'mounting_depth',
|
||||||
'description', 'comments',
|
'description', 'comments', 'tags',
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, data=None, *args, **kwargs):
|
def __init__(self, data=None, *args, **kwargs):
|
||||||
@ -241,7 +241,7 @@ class RackReservationCSVForm(NetBoxModelCSVForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = RackReservation
|
model = RackReservation
|
||||||
fields = ('site', 'location', 'rack', 'units', 'tenant', 'description', 'comments')
|
fields = ('site', 'location', 'rack', 'units', 'tenant', 'description', 'comments', 'tags')
|
||||||
|
|
||||||
def __init__(self, data=None, *args, **kwargs):
|
def __init__(self, data=None, *args, **kwargs):
|
||||||
super().__init__(data, *args, **kwargs)
|
super().__init__(data, *args, **kwargs)
|
||||||
@ -264,7 +264,7 @@ class ManufacturerCSVForm(NetBoxModelCSVForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Manufacturer
|
model = Manufacturer
|
||||||
fields = ('name', 'slug', 'description')
|
fields = ('name', 'slug', 'description', 'tags')
|
||||||
|
|
||||||
|
|
||||||
class DeviceRoleCSVForm(NetBoxModelCSVForm):
|
class DeviceRoleCSVForm(NetBoxModelCSVForm):
|
||||||
@ -272,7 +272,7 @@ class DeviceRoleCSVForm(NetBoxModelCSVForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = DeviceRole
|
model = DeviceRole
|
||||||
fields = ('name', 'slug', 'color', 'vm_role', 'description')
|
fields = ('name', 'slug', 'color', 'vm_role', 'description', 'tags')
|
||||||
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>)'),
|
||||||
}
|
}
|
||||||
@ -289,7 +289,7 @@ class PlatformCSVForm(NetBoxModelCSVForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Platform
|
model = Platform
|
||||||
fields = ('name', 'slug', 'manufacturer', 'napalm_driver', 'napalm_args', 'description')
|
fields = ('name', 'slug', 'manufacturer', 'napalm_driver', 'napalm_args', 'description', 'tags')
|
||||||
|
|
||||||
|
|
||||||
class BaseDeviceCSVForm(NetBoxModelCSVForm):
|
class BaseDeviceCSVForm(NetBoxModelCSVForm):
|
||||||
@ -388,7 +388,7 @@ class DeviceCSVForm(BaseDeviceCSVForm):
|
|||||||
fields = [
|
fields = [
|
||||||
'name', 'device_role', 'tenant', 'manufacturer', 'device_type', 'platform', 'serial', 'asset_tag', 'status',
|
'name', 'device_role', 'tenant', 'manufacturer', 'device_type', 'platform', 'serial', 'asset_tag', 'status',
|
||||||
'site', 'location', 'rack', 'position', 'face', 'airflow', 'virtual_chassis', 'vc_position', 'vc_priority',
|
'site', 'location', 'rack', 'position', 'face', 'airflow', 'virtual_chassis', 'vc_position', 'vc_priority',
|
||||||
'cluster', 'description', 'comments',
|
'cluster', 'description', 'comments', 'tags',
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, data=None, *args, **kwargs):
|
def __init__(self, data=None, *args, **kwargs):
|
||||||
@ -425,7 +425,7 @@ class ModuleCSVForm(NetBoxModelCSVForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Module
|
model = Module
|
||||||
fields = (
|
fields = (
|
||||||
'device', 'module_bay', 'module_type', 'serial', 'asset_tag', 'description', 'comments',
|
'device', 'module_bay', 'module_type', 'serial', 'asset_tag', 'description', 'comments', 'tags',
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, data=None, *args, **kwargs):
|
def __init__(self, data=None, *args, **kwargs):
|
||||||
@ -452,7 +452,7 @@ class ChildDeviceCSVForm(BaseDeviceCSVForm):
|
|||||||
class Meta(BaseDeviceCSVForm.Meta):
|
class Meta(BaseDeviceCSVForm.Meta):
|
||||||
fields = [
|
fields = [
|
||||||
'name', 'device_role', 'tenant', 'manufacturer', 'device_type', 'platform', 'serial', 'asset_tag', 'status',
|
'name', 'device_role', 'tenant', 'manufacturer', 'device_type', 'platform', 'serial', 'asset_tag', 'status',
|
||||||
'parent', 'device_bay', 'virtual_chassis', 'vc_position', 'vc_priority', 'cluster', 'comments',
|
'parent', 'device_bay', 'virtual_chassis', 'vc_position', 'vc_priority', 'cluster', 'comments', 'tags'
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, data=None, *args, **kwargs):
|
def __init__(self, data=None, *args, **kwargs):
|
||||||
@ -503,7 +503,7 @@ class ConsolePortCSVForm(NetBoxModelCSVForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ConsolePort
|
model = ConsolePort
|
||||||
fields = ('device', 'name', 'label', 'type', 'speed', 'mark_connected', 'description')
|
fields = ('device', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags')
|
||||||
|
|
||||||
|
|
||||||
class ConsoleServerPortCSVForm(NetBoxModelCSVForm):
|
class ConsoleServerPortCSVForm(NetBoxModelCSVForm):
|
||||||
@ -526,7 +526,7 @@ class ConsoleServerPortCSVForm(NetBoxModelCSVForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ConsoleServerPort
|
model = ConsoleServerPort
|
||||||
fields = ('device', 'name', 'label', 'type', 'speed', 'mark_connected', 'description')
|
fields = ('device', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags')
|
||||||
|
|
||||||
|
|
||||||
class PowerPortCSVForm(NetBoxModelCSVForm):
|
class PowerPortCSVForm(NetBoxModelCSVForm):
|
||||||
@ -543,7 +543,7 @@ class PowerPortCSVForm(NetBoxModelCSVForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = PowerPort
|
model = PowerPort
|
||||||
fields = (
|
fields = (
|
||||||
'device', 'name', 'label', 'type', 'mark_connected', 'maximum_draw', 'allocated_draw', 'description',
|
'device', 'name', 'label', 'type', 'mark_connected', 'maximum_draw', 'allocated_draw', 'description', 'tags'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -571,7 +571,7 @@ class PowerOutletCSVForm(NetBoxModelCSVForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PowerOutlet
|
model = PowerOutlet
|
||||||
fields = ('device', 'name', 'label', 'type', 'mark_connected', 'power_port', 'feed_leg', 'description')
|
fields = ('device', 'name', 'label', 'type', 'mark_connected', 'power_port', 'feed_leg', 'description', 'tags')
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
@ -659,7 +659,7 @@ class InterfaceCSVForm(NetBoxModelCSVForm):
|
|||||||
fields = (
|
fields = (
|
||||||
'device', 'name', 'label', 'parent', 'bridge', 'lag', 'type', 'speed', 'duplex', 'enabled',
|
'device', 'name', 'label', 'parent', 'bridge', 'lag', 'type', 'speed', 'duplex', 'enabled',
|
||||||
'mark_connected', 'mac_address', 'wwn', 'mtu', 'mgmt_only', 'description', 'poe_mode', 'poe_type', 'mode',
|
'mark_connected', 'mac_address', 'wwn', 'mtu', 'mgmt_only', 'description', 'poe_mode', 'poe_type', 'mode',
|
||||||
'vrf', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power',
|
'vrf', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'tags'
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, data=None, *args, **kwargs):
|
def __init__(self, data=None, *args, **kwargs):
|
||||||
@ -702,7 +702,7 @@ class FrontPortCSVForm(NetBoxModelCSVForm):
|
|||||||
model = FrontPort
|
model = FrontPort
|
||||||
fields = (
|
fields = (
|
||||||
'device', 'name', 'label', 'type', 'color', 'mark_connected', 'rear_port', 'rear_port_position',
|
'device', 'name', 'label', 'type', 'color', 'mark_connected', 'rear_port', 'rear_port_position',
|
||||||
'description',
|
'description', 'tags'
|
||||||
)
|
)
|
||||||
help_texts = {
|
help_texts = {
|
||||||
'rear_port_position': 'Mapped position on corresponding rear port',
|
'rear_port_position': 'Mapped position on corresponding rear port',
|
||||||
@ -743,7 +743,7 @@ class RearPortCSVForm(NetBoxModelCSVForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = RearPort
|
model = RearPort
|
||||||
fields = ('device', 'name', 'label', 'type', 'color', 'mark_connected', 'positions', 'description')
|
fields = ('device', 'name', 'label', 'type', 'color', 'mark_connected', 'positions', 'description', 'tags')
|
||||||
help_texts = {
|
help_texts = {
|
||||||
'positions': 'Number of front ports which may be mapped'
|
'positions': 'Number of front ports which may be mapped'
|
||||||
}
|
}
|
||||||
@ -757,7 +757,7 @@ class ModuleBayCSVForm(NetBoxModelCSVForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ModuleBay
|
model = ModuleBay
|
||||||
fields = ('device', 'name', 'label', 'position', 'description')
|
fields = ('device', 'name', 'label', 'position', 'description', 'tags')
|
||||||
|
|
||||||
|
|
||||||
class DeviceBayCSVForm(NetBoxModelCSVForm):
|
class DeviceBayCSVForm(NetBoxModelCSVForm):
|
||||||
@ -777,7 +777,7 @@ class DeviceBayCSVForm(NetBoxModelCSVForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = DeviceBay
|
model = DeviceBay
|
||||||
fields = ('device', 'name', 'label', 'installed_device', 'description')
|
fields = ('device', 'name', 'label', 'installed_device', 'description', 'tags')
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
@ -832,7 +832,7 @@ class InventoryItemCSVForm(NetBoxModelCSVForm):
|
|||||||
model = InventoryItem
|
model = InventoryItem
|
||||||
fields = (
|
fields = (
|
||||||
'device', 'name', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered',
|
'device', 'name', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered',
|
||||||
'description',
|
'description', 'tags'
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@ -928,7 +928,7 @@ class CableCSVForm(NetBoxModelCSVForm):
|
|||||||
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_device', 'side_a_type', 'side_a_name', 'side_b_device', 'side_b_type', 'side_b_name', 'type',
|
||||||
'status', 'tenant', 'label', 'color', 'length', 'length_unit', 'description', 'comments',
|
'status', 'tenant', 'label', 'color', 'length', 'length_unit', 'description', 'comments', 'tags',
|
||||||
]
|
]
|
||||||
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>)'),
|
||||||
@ -985,7 +985,7 @@ class VirtualChassisCSVForm(NetBoxModelCSVForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = VirtualChassis
|
model = VirtualChassis
|
||||||
fields = ('name', 'domain', 'master', 'description')
|
fields = ('name', 'domain', 'master', 'description', 'comments', 'tags')
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -1006,7 +1006,7 @@ class PowerPanelCSVForm(NetBoxModelCSVForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PowerPanel
|
model = PowerPanel
|
||||||
fields = ('site', 'location', 'name', 'description', 'comments')
|
fields = ('site', 'location', 'name', 'description', 'comments', 'tags')
|
||||||
|
|
||||||
def __init__(self, data=None, *args, **kwargs):
|
def __init__(self, data=None, *args, **kwargs):
|
||||||
super().__init__(data, *args, **kwargs)
|
super().__init__(data, *args, **kwargs)
|
||||||
@ -1062,7 +1062,7 @@ class PowerFeedCSVForm(NetBoxModelCSVForm):
|
|||||||
model = PowerFeed
|
model = PowerFeed
|
||||||
fields = (
|
fields = (
|
||||||
'site', 'power_panel', 'location', 'rack', 'name', 'status', 'type', 'mark_connected', 'supply', 'phase',
|
'site', 'power_panel', 'location', 'rack', 'name', 'status', 'type', 'mark_connected', 'supply', 'phase',
|
||||||
'voltage', 'amperage', 'max_utilization', 'description', 'comments',
|
'voltage', 'amperage', 'max_utilization', 'description', 'comments', 'tags',
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, data=None, *args, **kwargs):
|
def __init__(self, data=None, *args, **kwargs):
|
||||||
|
@ -41,7 +41,7 @@ class VRFCSVForm(NetBoxModelCSVForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = VRF
|
model = VRF
|
||||||
fields = ('name', 'rd', 'tenant', 'enforce_unique', 'description', 'comments')
|
fields = ('name', 'rd', 'tenant', 'enforce_unique', 'description', 'comments', 'tags')
|
||||||
|
|
||||||
|
|
||||||
class RouteTargetCSVForm(NetBoxModelCSVForm):
|
class RouteTargetCSVForm(NetBoxModelCSVForm):
|
||||||
@ -54,7 +54,7 @@ class RouteTargetCSVForm(NetBoxModelCSVForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = RouteTarget
|
model = RouteTarget
|
||||||
fields = ('name', 'tenant', 'description', 'comments')
|
fields = ('name', 'tenant', 'description', 'comments', 'tags')
|
||||||
|
|
||||||
|
|
||||||
class RIRCSVForm(NetBoxModelCSVForm):
|
class RIRCSVForm(NetBoxModelCSVForm):
|
||||||
@ -62,7 +62,7 @@ class RIRCSVForm(NetBoxModelCSVForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = RIR
|
model = RIR
|
||||||
fields = ('name', 'slug', 'is_private', 'description')
|
fields = ('name', 'slug', 'is_private', 'description', 'tags')
|
||||||
help_texts = {
|
help_texts = {
|
||||||
'name': 'RIR name',
|
'name': 'RIR name',
|
||||||
}
|
}
|
||||||
@ -83,7 +83,7 @@ class AggregateCSVForm(NetBoxModelCSVForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Aggregate
|
model = Aggregate
|
||||||
fields = ('prefix', 'rir', 'tenant', 'date_added', 'description', 'comments')
|
fields = ('prefix', 'rir', 'tenant', 'date_added', 'description', 'comments', 'tags')
|
||||||
|
|
||||||
|
|
||||||
class ASNCSVForm(NetBoxModelCSVForm):
|
class ASNCSVForm(NetBoxModelCSVForm):
|
||||||
@ -101,8 +101,7 @@ class ASNCSVForm(NetBoxModelCSVForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ASN
|
model = ASN
|
||||||
fields = ('asn', 'rir', 'tenant', 'description', 'comments')
|
fields = ('asn', 'rir', 'tenant', 'description', 'comments', 'tags')
|
||||||
help_texts = {}
|
|
||||||
|
|
||||||
|
|
||||||
class RoleCSVForm(NetBoxModelCSVForm):
|
class RoleCSVForm(NetBoxModelCSVForm):
|
||||||
@ -110,7 +109,7 @@ class RoleCSVForm(NetBoxModelCSVForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Role
|
model = Role
|
||||||
fields = ('name', 'slug', 'weight', 'description')
|
fields = ('name', 'slug', 'weight', 'description', 'tags')
|
||||||
|
|
||||||
|
|
||||||
class PrefixCSVForm(NetBoxModelCSVForm):
|
class PrefixCSVForm(NetBoxModelCSVForm):
|
||||||
@ -159,7 +158,7 @@ class PrefixCSVForm(NetBoxModelCSVForm):
|
|||||||
model = Prefix
|
model = Prefix
|
||||||
fields = (
|
fields = (
|
||||||
'prefix', 'vrf', 'tenant', 'site', 'vlan_group', 'vlan', 'status', 'role', 'is_pool', 'mark_utilized',
|
'prefix', 'vrf', 'tenant', 'site', 'vlan_group', 'vlan', 'status', 'role', 'is_pool', 'mark_utilized',
|
||||||
'description', 'comments',
|
'description', 'comments', 'tags',
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, data=None, *args, **kwargs):
|
def __init__(self, data=None, *args, **kwargs):
|
||||||
@ -204,7 +203,7 @@ class IPRangeCSVForm(NetBoxModelCSVForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = IPRange
|
model = IPRange
|
||||||
fields = (
|
fields = (
|
||||||
'start_address', 'end_address', 'vrf', 'tenant', 'status', 'role', 'description', 'comments',
|
'start_address', 'end_address', 'vrf', 'tenant', 'status', 'role', 'description', 'comments', 'tags',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -257,7 +256,7 @@ class IPAddressCSVForm(NetBoxModelCSVForm):
|
|||||||
model = IPAddress
|
model = IPAddress
|
||||||
fields = [
|
fields = [
|
||||||
'address', 'vrf', 'tenant', 'status', 'role', 'device', 'virtual_machine', 'interface', 'is_primary',
|
'address', 'vrf', 'tenant', 'status', 'role', 'device', 'virtual_machine', 'interface', 'is_primary',
|
||||||
'dns_name', 'description', 'comments',
|
'dns_name', 'description', 'comments', 'tags',
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, data=None, *args, **kwargs):
|
def __init__(self, data=None, *args, **kwargs):
|
||||||
@ -326,7 +325,7 @@ class FHRPGroupCSVForm(NetBoxModelCSVForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = FHRPGroup
|
model = FHRPGroup
|
||||||
fields = ('protocol', 'group_id', 'auth_type', 'auth_key', 'name', 'description', 'comments')
|
fields = ('protocol', 'group_id', 'auth_type', 'auth_key', 'name', 'description', 'comments', 'tags')
|
||||||
|
|
||||||
|
|
||||||
class VLANGroupCSVForm(NetBoxModelCSVForm):
|
class VLANGroupCSVForm(NetBoxModelCSVForm):
|
||||||
@ -351,7 +350,7 @@ class VLANGroupCSVForm(NetBoxModelCSVForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = VLANGroup
|
model = VLANGroup
|
||||||
fields = ('name', 'slug', 'scope_type', 'scope_id', 'min_vid', 'max_vid', 'description')
|
fields = ('name', 'slug', 'scope_type', 'scope_id', 'min_vid', 'max_vid', 'description', 'tags')
|
||||||
labels = {
|
labels = {
|
||||||
'scope_id': 'Scope ID',
|
'scope_id': 'Scope ID',
|
||||||
}
|
}
|
||||||
@ -389,7 +388,7 @@ class VLANCSVForm(NetBoxModelCSVForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = VLAN
|
model = VLAN
|
||||||
fields = ('site', 'group', 'vid', 'name', 'tenant', 'status', 'role', 'description', 'comments')
|
fields = ('site', 'group', 'vid', 'name', 'tenant', 'status', 'role', 'description', 'comments', 'tags')
|
||||||
help_texts = {
|
help_texts = {
|
||||||
'vid': 'Numeric VLAN ID (1-4094)',
|
'vid': 'Numeric VLAN ID (1-4094)',
|
||||||
'name': 'VLAN name',
|
'name': 'VLAN name',
|
||||||
@ -404,7 +403,7 @@ class ServiceTemplateCSVForm(NetBoxModelCSVForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ServiceTemplate
|
model = ServiceTemplate
|
||||||
fields = ('name', 'protocol', 'ports', 'description', 'comments')
|
fields = ('name', 'protocol', 'ports', 'description', 'comments', 'tags')
|
||||||
|
|
||||||
|
|
||||||
class ServiceCSVForm(NetBoxModelCSVForm):
|
class ServiceCSVForm(NetBoxModelCSVForm):
|
||||||
@ -427,7 +426,7 @@ class ServiceCSVForm(NetBoxModelCSVForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Service
|
model = Service
|
||||||
fields = ('device', 'virtual_machine', 'name', 'protocol', 'ports', 'description', 'comments')
|
fields = ('device', 'virtual_machine', 'name', 'protocol', 'ports', 'description', 'comments', 'tags')
|
||||||
|
|
||||||
|
|
||||||
class L2VPNCSVForm(NetBoxModelCSVForm):
|
class L2VPNCSVForm(NetBoxModelCSVForm):
|
||||||
@ -443,7 +442,7 @@ class L2VPNCSVForm(NetBoxModelCSVForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = L2VPN
|
model = L2VPN
|
||||||
fields = ('identifier', 'name', 'slug', 'type', 'description', 'comments')
|
fields = ('identifier', 'name', 'slug', 'type', 'description', 'comments', 'tags')
|
||||||
|
|
||||||
|
|
||||||
class L2VPNTerminationCSVForm(NetBoxModelCSVForm):
|
class L2VPNTerminationCSVForm(NetBoxModelCSVForm):
|
||||||
@ -480,7 +479,7 @@ class L2VPNTerminationCSVForm(NetBoxModelCSVForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = L2VPNTermination
|
model = L2VPNTermination
|
||||||
fields = ('l2vpn', 'device', 'virtual_machine', 'interface', 'vlan')
|
fields = ('l2vpn', 'device', 'virtual_machine', 'interface', 'vlan', 'tags')
|
||||||
|
|
||||||
def __init__(self, data=None, *args, **kwargs):
|
def __init__(self, data=None, *args, **kwargs):
|
||||||
super().__init__(data, *args, **kwargs)
|
super().__init__(data, *args, **kwargs)
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
from extras.choices import CustomFieldFilterLogicChoices, CustomFieldTypeChoices, CustomFieldVisibilityChoices
|
from extras.choices import CustomFieldFilterLogicChoices, CustomFieldTypeChoices, CustomFieldVisibilityChoices
|
||||||
from extras.forms.mixins import CustomFieldsMixin, SavedFiltersMixin
|
from extras.forms.mixins import CustomFieldsMixin, SavedFiltersMixin
|
||||||
from extras.models import CustomField, Tag
|
from extras.models import CustomField, Tag
|
||||||
from utilities.forms import BootstrapMixin, CSVModelForm
|
from utilities.forms import BootstrapMixin, CSVModelForm
|
||||||
from utilities.forms.fields import DynamicModelMultipleChoiceField
|
from utilities.forms.fields import CSVModelMultipleChoiceField, DynamicModelMultipleChoiceField
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'NetBoxModelForm',
|
'NetBoxModelForm',
|
||||||
@ -61,7 +62,12 @@ class NetBoxModelCSVForm(CSVModelForm, NetBoxModelForm):
|
|||||||
"""
|
"""
|
||||||
Base form for creating a NetBox objects from CSV data. Used for bulk importing.
|
Base form for creating a NetBox objects from CSV data. Used for bulk importing.
|
||||||
"""
|
"""
|
||||||
tags = None # Temporary fix in lieu of tag import support (see #9158)
|
tags = CSVModelMultipleChoiceField(
|
||||||
|
queryset=Tag.objects.all(),
|
||||||
|
required=False,
|
||||||
|
to_field_name='slug',
|
||||||
|
help_text='Tag slugs separated by commas, encased with double quotes (e.g. "tag1,tag2,tag3")'
|
||||||
|
)
|
||||||
|
|
||||||
def _get_custom_fields(self, content_type):
|
def _get_custom_fields(self, content_type):
|
||||||
return CustomField.objects.filter(content_types=content_type).filter(
|
return CustomField.objects.filter(content_types=content_type).filter(
|
||||||
|
84
netbox/netbox/tests/test_import.py
Normal file
84
netbox/netbox/tests/test_import.py
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.test import override_settings
|
||||||
|
|
||||||
|
from dcim.models import *
|
||||||
|
from users.models import ObjectPermission
|
||||||
|
from utilities.testing import ModelViewTestCase, create_tags
|
||||||
|
|
||||||
|
|
||||||
|
class CSVImportTestCase(ModelViewTestCase):
|
||||||
|
model = Region
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
create_tags('Alpha', 'Bravo', 'Charlie', 'Delta', 'Echo')
|
||||||
|
|
||||||
|
def _get_csv_data(self, csv_data):
|
||||||
|
return '\n'.join(csv_data)
|
||||||
|
|
||||||
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||||
|
def test_valid_tags(self):
|
||||||
|
csv_data = (
|
||||||
|
'name,slug,tags',
|
||||||
|
'Region 1,region-1,"alpha,bravo"',
|
||||||
|
'Region 2,region-2,"charlie,delta"',
|
||||||
|
'Region 3,region-3,echo',
|
||||||
|
'Region 4,region-4,',
|
||||||
|
)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'csv': self._get_csv_data(csv_data),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Assign model-level permission
|
||||||
|
obj_perm = ObjectPermission(name='Test permission', actions=['add'])
|
||||||
|
obj_perm.save()
|
||||||
|
obj_perm.users.add(self.user)
|
||||||
|
obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
|
||||||
|
|
||||||
|
# Try GET with model-level permission
|
||||||
|
self.assertHttpStatus(self.client.get(self._get_url('import')), 200)
|
||||||
|
|
||||||
|
# Test POST with permission
|
||||||
|
self.assertHttpStatus(self.client.post(self._get_url('import'), data), 200)
|
||||||
|
regions = Region.objects.all()
|
||||||
|
self.assertEqual(regions.count(), 4)
|
||||||
|
region = Region.objects.get(slug="region-4")
|
||||||
|
self.assertEqual(
|
||||||
|
list(regions[0].tags.values_list('name', flat=True)),
|
||||||
|
['Alpha', 'Bravo']
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
list(regions[1].tags.values_list('name', flat=True)),
|
||||||
|
['Charlie', 'Delta']
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
list(regions[2].tags.values_list('name', flat=True)),
|
||||||
|
['Echo']
|
||||||
|
)
|
||||||
|
self.assertEqual(regions[3].tags.count(), 0)
|
||||||
|
|
||||||
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||||
|
def test_invalid_tags(self):
|
||||||
|
csv_data = (
|
||||||
|
'name,slug,tags',
|
||||||
|
'Region 1,region-1,"Alpha,Bravo"', # Valid
|
||||||
|
'Region 2,region-2,"Alpha,Tango"', # Invalid
|
||||||
|
)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'csv': self._get_csv_data(csv_data),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Assign model-level permission
|
||||||
|
obj_perm = ObjectPermission(name='Test permission', actions=['add'])
|
||||||
|
obj_perm.save()
|
||||||
|
obj_perm.users.add(self.user)
|
||||||
|
obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
|
||||||
|
|
||||||
|
# Try GET with model-level permission
|
||||||
|
self.assertHttpStatus(self.client.get(self._get_url('import')), 200)
|
||||||
|
|
||||||
|
# Test POST with permission
|
||||||
|
self.assertHttpStatus(self.client.post(self._get_url('import'), data), 200)
|
||||||
|
self.assertEqual(Region.objects.count(), 0)
|
@ -26,7 +26,7 @@ class TenantGroupCSVForm(NetBoxModelCSVForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = TenantGroup
|
model = TenantGroup
|
||||||
fields = ('name', 'slug', 'parent', 'description')
|
fields = ('name', 'slug', 'parent', 'description', 'tags')
|
||||||
|
|
||||||
|
|
||||||
class TenantCSVForm(NetBoxModelCSVForm):
|
class TenantCSVForm(NetBoxModelCSVForm):
|
||||||
@ -40,7 +40,7 @@ class TenantCSVForm(NetBoxModelCSVForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Tenant
|
model = Tenant
|
||||||
fields = ('name', 'slug', 'group', 'description', 'comments')
|
fields = ('name', 'slug', 'group', 'description', 'comments', 'tags')
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -58,7 +58,7 @@ class ContactGroupCSVForm(NetBoxModelCSVForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ContactGroup
|
model = ContactGroup
|
||||||
fields = ('name', 'slug', 'parent', 'description')
|
fields = ('name', 'slug', 'parent', 'description', 'tags')
|
||||||
|
|
||||||
|
|
||||||
class ContactRoleCSVForm(NetBoxModelCSVForm):
|
class ContactRoleCSVForm(NetBoxModelCSVForm):
|
||||||
@ -79,4 +79,4 @@ class ContactCSVForm(NetBoxModelCSVForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Contact
|
model = Contact
|
||||||
fields = ('name', 'title', 'phone', 'email', 'address', 'link', 'group', 'description', 'comments')
|
fields = ('name', 'title', 'phone', 'email', 'address', 'link', 'group', 'description', 'comments', 'tags')
|
||||||
|
@ -16,6 +16,7 @@ __all__ = (
|
|||||||
'CSVDataField',
|
'CSVDataField',
|
||||||
'CSVFileField',
|
'CSVFileField',
|
||||||
'CSVModelChoiceField',
|
'CSVModelChoiceField',
|
||||||
|
'CSVModelMultipleChoiceField',
|
||||||
'CSVMultipleChoiceField',
|
'CSVMultipleChoiceField',
|
||||||
'CSVMultipleContentTypeField',
|
'CSVMultipleContentTypeField',
|
||||||
'CSVTypedChoiceField',
|
'CSVTypedChoiceField',
|
||||||
@ -142,7 +143,7 @@ class CSVModelChoiceField(forms.ModelChoiceField):
|
|||||||
Extends Django's `ModelChoiceField` to provide additional validation for CSV values.
|
Extends Django's `ModelChoiceField` to provide additional validation for CSV values.
|
||||||
"""
|
"""
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'invalid_choice': 'Object not found.',
|
'invalid_choice': 'Object not found: %(value)s',
|
||||||
}
|
}
|
||||||
|
|
||||||
def to_python(self, value):
|
def to_python(self, value):
|
||||||
@ -154,6 +155,19 @@ class CSVModelChoiceField(forms.ModelChoiceField):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CSVModelMultipleChoiceField(forms.ModelMultipleChoiceField):
|
||||||
|
"""
|
||||||
|
Extends Django's `ModelMultipleChoiceField` to support comma-separated values.
|
||||||
|
"""
|
||||||
|
default_error_messages = {
|
||||||
|
'invalid_choice': 'Object not found: %(value)s',
|
||||||
|
}
|
||||||
|
|
||||||
|
def clean(self, value):
|
||||||
|
value = value.split(',') if value else []
|
||||||
|
return super().clean(value)
|
||||||
|
|
||||||
|
|
||||||
class CSVContentTypeField(CSVModelChoiceField):
|
class CSVContentTypeField(CSVModelChoiceField):
|
||||||
"""
|
"""
|
||||||
CSV field for referencing a single content type, in the form `<app>.<model>`.
|
CSV field for referencing a single content type, in the form `<app>.<model>`.
|
||||||
|
@ -21,7 +21,7 @@ class ClusterTypeCSVForm(NetBoxModelCSVForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ClusterType
|
model = ClusterType
|
||||||
fields = ('name', 'slug', 'description')
|
fields = ('name', 'slug', 'description', 'tags')
|
||||||
|
|
||||||
|
|
||||||
class ClusterGroupCSVForm(NetBoxModelCSVForm):
|
class ClusterGroupCSVForm(NetBoxModelCSVForm):
|
||||||
@ -29,7 +29,7 @@ class ClusterGroupCSVForm(NetBoxModelCSVForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ClusterGroup
|
model = ClusterGroup
|
||||||
fields = ('name', 'slug', 'description')
|
fields = ('name', 'slug', 'description', 'tags')
|
||||||
|
|
||||||
|
|
||||||
class ClusterCSVForm(NetBoxModelCSVForm):
|
class ClusterCSVForm(NetBoxModelCSVForm):
|
||||||
@ -63,7 +63,7 @@ class ClusterCSVForm(NetBoxModelCSVForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Cluster
|
model = Cluster
|
||||||
fields = ('name', 'type', 'group', 'status', 'site', 'description', 'comments')
|
fields = ('name', 'type', 'group', 'status', 'site', 'description', 'comments', 'tags')
|
||||||
|
|
||||||
|
|
||||||
class VirtualMachineCSVForm(NetBoxModelCSVForm):
|
class VirtualMachineCSVForm(NetBoxModelCSVForm):
|
||||||
@ -114,7 +114,7 @@ class VirtualMachineCSVForm(NetBoxModelCSVForm):
|
|||||||
model = VirtualMachine
|
model = VirtualMachine
|
||||||
fields = (
|
fields = (
|
||||||
'name', 'status', 'role', 'site', 'cluster', 'device', 'tenant', 'platform', 'vcpus', 'memory', 'disk',
|
'name', 'status', 'role', 'site', 'cluster', 'device', 'tenant', 'platform', 'vcpus', 'memory', 'disk',
|
||||||
'description', 'comments',
|
'description', 'comments', 'tags',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -151,7 +151,7 @@ class VMInterfaceCSVForm(NetBoxModelCSVForm):
|
|||||||
model = VMInterface
|
model = VMInterface
|
||||||
fields = (
|
fields = (
|
||||||
'virtual_machine', 'name', 'parent', 'bridge', 'enabled', 'mac_address', 'mtu', 'description', 'mode',
|
'virtual_machine', 'name', 'parent', 'bridge', 'enabled', 'mac_address', 'mtu', 'description', 'mode',
|
||||||
'vrf',
|
'vrf', 'tags'
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, data=None, *args, **kwargs):
|
def __init__(self, data=None, *args, **kwargs):
|
||||||
|
@ -25,7 +25,7 @@ class WirelessLANGroupCSVForm(NetBoxModelCSVForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = WirelessLANGroup
|
model = WirelessLANGroup
|
||||||
fields = ('name', 'slug', 'parent', 'description')
|
fields = ('name', 'slug', 'parent', 'description', 'tags')
|
||||||
|
|
||||||
|
|
||||||
class WirelessLANCSVForm(NetBoxModelCSVForm):
|
class WirelessLANCSVForm(NetBoxModelCSVForm):
|
||||||
@ -62,6 +62,7 @@ class WirelessLANCSVForm(NetBoxModelCSVForm):
|
|||||||
model = WirelessLAN
|
model = WirelessLAN
|
||||||
fields = (
|
fields = (
|
||||||
'ssid', 'group', 'vlan', 'tenant', 'auth_type', 'auth_cipher', 'auth_psk', 'description', 'comments',
|
'ssid', 'group', 'vlan', 'tenant', 'auth_type', 'auth_cipher', 'auth_psk', 'description', 'comments',
|
||||||
|
'tags',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -97,5 +98,5 @@ class WirelessLinkCSVForm(NetBoxModelCSVForm):
|
|||||||
model = WirelessLink
|
model = WirelessLink
|
||||||
fields = (
|
fields = (
|
||||||
'interface_a', 'interface_b', 'ssid', 'tenant', 'auth_type', 'auth_cipher', 'auth_psk', 'description',
|
'interface_a', 'interface_b', 'ssid', 'tenant', 'auth_type', 'auth_cipher', 'auth_psk', 'description',
|
||||||
'comments',
|
'comments', 'tags',
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user