diff --git a/netbox/dcim/migrations/0201_alter_platform_name_and_more.py b/netbox/dcim/migrations/0201_alter_platform_name_and_more.py new file mode 100644 index 00000000000..29705bf451c --- /dev/null +++ b/netbox/dcim/migrations/0201_alter_platform_name_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 5.1.5 on 2025-02-19 09:24 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dcim', '0200_populate_mac_addresses'), + ('extras', '0122_charfield_null_choices'), + ] + + operations = [ + migrations.AlterField( + model_name='platform', + name='name', + field=models.CharField(max_length=100), + ), + migrations.AddConstraint( + model_name='platform', + constraint=models.UniqueConstraint(fields=('name', 'manufacturer'), name='unique_platform_per_manufacturer'), + ), + ] diff --git a/netbox/dcim/models/devices.py b/netbox/dcim/models/devices.py index 12b0dae18f1..1c83cdc7781 100644 --- a/netbox/dcim/models/devices.py +++ b/netbox/dcim/models/devices.py @@ -502,6 +502,10 @@ class Platform(OrganizationalModel): Platform refers to the software or firmware running on a Device. For example, "Cisco IOS-XR" or "Juniper Junos". A Platform may optionally be associated with a particular Manufacturer. """ + name = models.CharField( + verbose_name=_('name'), + max_length=100 + ) manufacturer = models.ForeignKey( to='dcim.Manufacturer', on_delete=models.PROTECT, @@ -522,7 +526,12 @@ class Meta: ordering = ('name',) verbose_name = _('platform') verbose_name_plural = _('platforms') - + constraints = [ + models.UniqueConstraint( + fields=['name', 'manufacturer'], + name='unique_platform_per_manufacturer' + ) + ] def update_interface_bridges(device, interface_templates, module=None): """ diff --git a/netbox/virtualization/forms/bulk_import.py b/netbox/virtualization/forms/bulk_import.py index 6b5b62d1103..8613784755a 100644 --- a/netbox/virtualization/forms/bulk_import.py +++ b/netbox/virtualization/forms/bulk_import.py @@ -2,7 +2,7 @@ from dcim.choices import InterfaceModeChoices from dcim.forms.mixins import ScopedImportForm -from dcim.models import Device, DeviceRole, Platform, Site +from dcim.models import Device, DeviceRole, Platform, Site, Manufacturer from extras.models import ConfigTemplate from ipam.models import VRF from netbox.forms import NetBoxModelImportForm @@ -124,6 +124,13 @@ class VirtualMachineImportForm(NetBoxModelImportForm): to_field_name='name', help_text=_('Assigned tenant') ) + manufacturer = CSVModelChoiceField( + label=_('Manufacturer'), + queryset=Manufacturer.objects.all(), + required=False, + to_field_name='name', + help_text=_('Device type manufacturer'), + ) platform = CSVModelChoiceField( label=_('Platform'), queryset=Platform.objects.all(), @@ -138,12 +145,25 @@ class VirtualMachineImportForm(NetBoxModelImportForm): label=_('Config template'), help_text=_('Config template') ) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + manufacturer_name = self.data.get('manufacturer') or self.initial.get('manufacturer') + platform_name = self.data.get('platform') or self.initial.get('platform') + + if manufacturer_name: + try: + manufacturer = Manufacturer.objects.get(name=manufacturer_name) + self.fields['platform'].queryset = Platform.objects.filter(manufacturer=manufacturer) + except Manufacturer.DoesNotExist: + self.fields['platform'].queryset = Platform.objects.none() + elif platform_name: + self.add_error('manufacturer', _("Manufacturer is required if Platform is provided")) class Meta: model = VirtualMachine fields = ( 'name', 'status', 'role', 'site', 'cluster', 'device', 'tenant', 'platform', 'vcpus', 'memory', 'disk', - 'description', 'serial', 'config_template', 'comments', 'tags', + 'description', 'serial', 'config_template', 'comments', 'tags','manufacturer' )