diff --git a/netbox/circuits/migrations/0046_charfield_null_choices.py b/netbox/circuits/migrations/0046_charfield_null_choices.py new file mode 100644 index 000000000..4ec21b750 --- /dev/null +++ b/netbox/circuits/migrations/0046_charfield_null_choices.py @@ -0,0 +1,43 @@ +from django.db import migrations, models + + +def set_null_values(apps, schema_editor): + """ + Replace empty strings with null values. + """ + Circuit = apps.get_model('circuits', 'Circuit') + CircuitGroupAssignment = apps.get_model('circuits', 'CircuitGroupAssignment') + CircuitTermination = apps.get_model('circuits', 'CircuitTermination') + + Circuit.objects.filter(distance_unit='').update(distance_unit=None) + CircuitGroupAssignment.objects.filter(priority='').update(priority=None) + CircuitTermination.objects.filter(cable_end='').update(cable_end=None) + + +class Migration(migrations.Migration): + + dependencies = [ + ('circuits', '0045_circuit_distance'), + ] + + operations = [ + migrations.AlterField( + model_name='circuit', + name='distance_unit', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AlterField( + model_name='circuitgroupassignment', + name='priority', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AlterField( + model_name='circuittermination', + name='cable_end', + field=models.CharField(blank=True, max_length=1, null=True), + ), + migrations.RunPython( + code=set_null_values, + reverse_code=migrations.RunPython.noop + ), + ] diff --git a/netbox/circuits/models/circuits.py b/netbox/circuits/models/circuits.py index 2df83e97e..5f749550c 100644 --- a/netbox/circuits/models/circuits.py +++ b/netbox/circuits/models/circuits.py @@ -187,7 +187,8 @@ class CircuitGroupAssignment(CustomFieldsMixin, ExportTemplatesMixin, TagsMixin, verbose_name=_('priority'), max_length=50, choices=CircuitPriorityChoices, - blank=True + blank=True, + null=True ) prerequisite_models = ( 'circuits.Circuit', diff --git a/netbox/dcim/migrations/0194_charfield_null_choices.py b/netbox/dcim/migrations/0194_charfield_null_choices.py new file mode 100644 index 000000000..8e507c050 --- /dev/null +++ b/netbox/dcim/migrations/0194_charfield_null_choices.py @@ -0,0 +1,287 @@ +from django.db import migrations, models + + +def set_null_values(apps, schema_editor): + """ + Replace empty strings with null values. + """ + Cable = apps.get_model('dcim', 'Cable') + ConsolePort = apps.get_model('dcim', 'ConsolePort') + ConsolePortTemplate = apps.get_model('dcim', 'ConsolePortTemplate') + ConsoleServerPort = apps.get_model('dcim', 'ConsoleServerPort') + ConsoleServerPortTemplate = apps.get_model('dcim', 'ConsoleServerPortTemplate') + Device = apps.get_model('dcim', 'Device') + DeviceType = apps.get_model('dcim', 'DeviceType') + FrontPort = apps.get_model('dcim', 'FrontPort') + Interface = apps.get_model('dcim', 'Interface') + InterfaceTemplate = apps.get_model('dcim', 'InterfaceTemplate') + ModuleType = apps.get_model('dcim', 'ModuleType') + PowerFeed = apps.get_model('dcim', 'PowerFeed') + PowerOutlet = apps.get_model('dcim', 'PowerOutlet') + PowerOutletTemplate = apps.get_model('dcim', 'PowerOutletTemplate') + PowerPort = apps.get_model('dcim', 'PowerPort') + PowerPortTemplate = apps.get_model('dcim', 'PowerPortTemplate') + Rack = apps.get_model('dcim', 'Rack') + RackType = apps.get_model('dcim', 'RackType') + RearPort = apps.get_model('dcim', 'RearPort') + + Cable.objects.filter(length_unit='').update(length_unit=None) + Cable.objects.filter(type='').update(type=None) + ConsolePort.objects.filter(cable_end='').update(cable_end=None) + ConsolePort.objects.filter(type='').update(type=None) + ConsolePortTemplate.objects.filter(type='').update(type=None) + ConsoleServerPort.objects.filter(cable_end='').update(cable_end=None) + ConsoleServerPort.objects.filter(type='').update(type=None) + ConsoleServerPortTemplate.objects.filter(type='').update(type=None) + Device.objects.filter(airflow='').update(airflow=None) + Device.objects.filter(face='').update(face=None) + DeviceType.objects.filter(airflow='').update(airflow=None) + DeviceType.objects.filter(subdevice_role='').update(subdevice_role=None) + DeviceType.objects.filter(weight_unit='').update(weight_unit=None) + FrontPort.objects.filter(cable_end='').update(cable_end=None) + Interface.objects.filter(cable_end='').update(cable_end=None) + Interface.objects.filter(mode='').update(mode=None) + Interface.objects.filter(poe_mode='').update(poe_mode=None) + Interface.objects.filter(poe_type='').update(poe_type=None) + Interface.objects.filter(rf_channel='').update(rf_channel=None) + Interface.objects.filter(rf_role='').update(rf_role=None) + InterfaceTemplate.objects.filter(poe_mode='').update(poe_mode=None) + InterfaceTemplate.objects.filter(poe_type='').update(poe_type=None) + InterfaceTemplate.objects.filter(rf_role='').update(rf_role=None) + ModuleType.objects.filter(airflow='').update(airflow=None) + ModuleType.objects.filter(weight_unit='').update(weight_unit=None) + PowerFeed.objects.filter(cable_end='').update(cable_end=None) + PowerOutlet.objects.filter(cable_end='').update(cable_end=None) + PowerOutlet.objects.filter(feed_leg='').update(feed_leg=None) + PowerOutlet.objects.filter(type='').update(type=None) + PowerOutletTemplate.objects.filter(feed_leg='').update(feed_leg=None) + PowerOutletTemplate.objects.filter(type='').update(type=None) + PowerPort.objects.filter(cable_end='').update(cable_end=None) + PowerPort.objects.filter(type='').update(type=None) + PowerPortTemplate.objects.filter(type='').update(type=None) + Rack.objects.filter(airflow='').update(airflow=None) + Rack.objects.filter(form_factor='').update(form_factor=None) + Rack.objects.filter(outer_unit='').update(outer_unit=None) + Rack.objects.filter(weight_unit='').update(weight_unit=None) + RackType.objects.filter(outer_unit='').update(outer_unit=None) + RackType.objects.filter(weight_unit='').update(weight_unit=None) + RearPort.objects.filter(cable_end='').update(cable_end=None) + + +class Migration(migrations.Migration): + + dependencies = [ + ('dcim', '0193_poweroutlet_color'), + ] + + operations = [ + migrations.AlterField( + model_name='cable', + name='length_unit', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AlterField( + model_name='cable', + name='type', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AlterField( + model_name='consoleport', + name='cable_end', + field=models.CharField(blank=True, max_length=1, null=True), + ), + migrations.AlterField( + model_name='consoleport', + name='type', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AlterField( + model_name='consoleporttemplate', + name='type', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AlterField( + model_name='consoleserverport', + name='cable_end', + field=models.CharField(blank=True, max_length=1, null=True), + ), + migrations.AlterField( + model_name='consoleserverport', + name='type', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AlterField( + model_name='consoleserverporttemplate', + name='type', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AlterField( + model_name='device', + name='airflow', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AlterField( + model_name='device', + name='face', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AlterField( + model_name='devicetype', + name='airflow', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AlterField( + model_name='devicetype', + name='subdevice_role', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AlterField( + model_name='devicetype', + name='weight_unit', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AlterField( + model_name='frontport', + name='cable_end', + field=models.CharField(blank=True, max_length=1, null=True), + ), + migrations.AlterField( + model_name='interface', + name='cable_end', + field=models.CharField(blank=True, max_length=1, null=True), + ), + migrations.AlterField( + model_name='interface', + name='mode', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AlterField( + model_name='interface', + name='poe_mode', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AlterField( + model_name='interface', + name='poe_type', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AlterField( + model_name='interface', + name='rf_channel', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AlterField( + model_name='interface', + name='rf_role', + field=models.CharField(blank=True, max_length=30, null=True), + ), + migrations.AlterField( + model_name='interfacetemplate', + name='poe_mode', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AlterField( + model_name='interfacetemplate', + name='poe_type', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AlterField( + model_name='interfacetemplate', + name='rf_role', + field=models.CharField(blank=True, max_length=30, null=True), + ), + migrations.AlterField( + model_name='moduletype', + name='airflow', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AlterField( + model_name='moduletype', + name='weight_unit', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AlterField( + model_name='powerfeed', + name='cable_end', + field=models.CharField(blank=True, max_length=1, null=True), + ), + migrations.AlterField( + model_name='poweroutlet', + name='cable_end', + field=models.CharField(blank=True, max_length=1, null=True), + ), + migrations.AlterField( + model_name='poweroutlet', + name='feed_leg', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AlterField( + model_name='poweroutlet', + name='type', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AlterField( + model_name='poweroutlettemplate', + name='feed_leg', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AlterField( + model_name='poweroutlettemplate', + name='type', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AlterField( + model_name='powerport', + name='cable_end', + field=models.CharField(blank=True, max_length=1, null=True), + ), + migrations.AlterField( + model_name='powerport', + name='type', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AlterField( + model_name='powerporttemplate', + name='type', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AlterField( + model_name='rack', + name='airflow', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AlterField( + model_name='rack', + name='form_factor', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AlterField( + model_name='rack', + name='outer_unit', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AlterField( + model_name='rack', + name='weight_unit', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AlterField( + model_name='racktype', + name='outer_unit', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AlterField( + model_name='racktype', + name='weight_unit', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AlterField( + model_name='rearport', + name='cable_end', + field=models.CharField(blank=True, max_length=1, null=True), + ), + migrations.RunPython( + code=set_null_values, + reverse_code=migrations.RunPython.noop + ), + ] diff --git a/netbox/dcim/models/cables.py b/netbox/dcim/models/cables.py index 96861a13e..a6293875e 100644 --- a/netbox/dcim/models/cables.py +++ b/netbox/dcim/models/cables.py @@ -42,7 +42,8 @@ class Cable(PrimaryModel): verbose_name=_('type'), max_length=50, choices=CableTypeChoices, - blank=True + blank=True, + null=True ) status = models.CharField( verbose_name=_('status'), @@ -78,6 +79,7 @@ class Cable(PrimaryModel): max_length=50, choices=CableLengthUnitChoices, blank=True, + null=True ) # Stores the normalized length (in meters) for database ordering _abs_length = models.DecimalField( @@ -206,7 +208,7 @@ class Cable(PrimaryModel): # Clear length_unit if no length is defined if self.length is None: - self.length_unit = '' + self.length_unit = None super().save(*args, **kwargs) @@ -365,7 +367,7 @@ class CableTermination(ChangeLoggedModel): termination = self.termination._meta.model.objects.get(pk=self.termination_id) termination.snapshot() termination.cable = None - termination.cable_end = '' + termination.cable_end = None termination.save() super().delete(*args, **kwargs) diff --git a/netbox/dcim/models/device_component_templates.py b/netbox/dcim/models/device_component_templates.py index 3a71c424d..00555d49e 100644 --- a/netbox/dcim/models/device_component_templates.py +++ b/netbox/dcim/models/device_component_templates.py @@ -203,7 +203,8 @@ class ConsolePortTemplate(ModularComponentTemplateModel): verbose_name=_('type'), max_length=50, choices=ConsolePortTypeChoices, - blank=True + blank=True, + null=True ) component_model = ConsolePort @@ -237,7 +238,8 @@ class ConsoleServerPortTemplate(ModularComponentTemplateModel): verbose_name=_('type'), max_length=50, choices=ConsolePortTypeChoices, - blank=True + blank=True, + null=True ) component_model = ConsoleServerPort @@ -272,7 +274,8 @@ class PowerPortTemplate(ModularComponentTemplateModel): verbose_name=_('type'), max_length=50, choices=PowerPortTypeChoices, - blank=True + blank=True, + null=True ) maximum_draw = models.PositiveIntegerField( verbose_name=_('maximum draw'), @@ -334,7 +337,8 @@ class PowerOutletTemplate(ModularComponentTemplateModel): verbose_name=_('type'), max_length=50, choices=PowerOutletTypeChoices, - blank=True + blank=True, + null=True ) power_port = models.ForeignKey( to='dcim.PowerPortTemplate', @@ -348,6 +352,7 @@ class PowerOutletTemplate(ModularComponentTemplateModel): max_length=50, choices=PowerOutletFeedLegChoices, blank=True, + null=True, help_text=_('Phase (for three-phase feeds)') ) @@ -434,18 +439,21 @@ class InterfaceTemplate(ModularComponentTemplateModel): max_length=50, choices=InterfacePoEModeChoices, blank=True, + null=True, verbose_name=_('PoE mode') ) poe_type = models.CharField( max_length=50, choices=InterfacePoETypeChoices, blank=True, + null=True, verbose_name=_('PoE type') ) rf_role = models.CharField( max_length=30, choices=WirelessRoleChoices, blank=True, + null=True, verbose_name=_('wireless role') ) diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index 73308c923..14f4120b5 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -142,8 +142,9 @@ class CabledObjectModel(models.Model): cable_end = models.CharField( verbose_name=_('cable end'), max_length=1, + choices=CableEndChoices, blank=True, - choices=CableEndChoices + null=True ) mark_connected = models.BooleanField( verbose_name=_('mark connected'), @@ -283,6 +284,7 @@ class ConsolePort(ModularComponentModel, CabledObjectModel, PathEndpoint, Tracki max_length=50, choices=ConsolePortTypeChoices, blank=True, + null=True, help_text=_('Physical port type') ) speed = models.PositiveIntegerField( @@ -309,6 +311,7 @@ class ConsoleServerPort(ModularComponentModel, CabledObjectModel, PathEndpoint, max_length=50, choices=ConsolePortTypeChoices, blank=True, + null=True, help_text=_('Physical port type') ) speed = models.PositiveIntegerField( @@ -339,6 +342,7 @@ class PowerPort(ModularComponentModel, CabledObjectModel, PathEndpoint, Tracking max_length=50, choices=PowerPortTypeChoices, blank=True, + null=True, help_text=_('Physical port type') ) maximum_draw = models.PositiveIntegerField( @@ -454,6 +458,7 @@ class PowerOutlet(ModularComponentModel, CabledObjectModel, PathEndpoint, Tracki max_length=50, choices=PowerOutletTypeChoices, blank=True, + null=True, help_text=_('Physical port type') ) power_port = models.ForeignKey( @@ -468,6 +473,7 @@ class PowerOutlet(ModularComponentModel, CabledObjectModel, PathEndpoint, Tracki max_length=50, choices=PowerOutletFeedLegChoices, blank=True, + null=True, help_text=_('Phase (for three-phase feeds)') ) color = ColorField( @@ -522,6 +528,7 @@ class BaseInterface(models.Model): max_length=50, choices=InterfaceModeChoices, blank=True, + null=True, help_text=_('IEEE 802.1Q tagging strategy') ) parent = models.ForeignKey( @@ -631,12 +638,14 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd max_length=30, choices=WirelessRoleChoices, blank=True, + null=True, verbose_name=_('wireless role') ) rf_channel = models.CharField( max_length=50, choices=WirelessChannelChoices, blank=True, + null=True, verbose_name=_('wireless channel') ) rf_channel_frequency = models.DecimalField( @@ -665,12 +674,14 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd max_length=50, choices=InterfacePoEModeChoices, blank=True, + null=True, verbose_name=_('PoE mode') ) poe_type = models.CharField( max_length=50, choices=InterfacePoETypeChoices, blank=True, + null=True, verbose_name=_('PoE type') ) wireless_link = models.ForeignKey( diff --git a/netbox/dcim/models/devices.py b/netbox/dcim/models/devices.py index e472303a6..b9ba2bb64 100644 --- a/netbox/dcim/models/devices.py +++ b/netbox/dcim/models/devices.py @@ -118,6 +118,7 @@ class DeviceType(ImageAttachmentsMixin, PrimaryModel, WeightMixin): max_length=50, choices=SubdeviceRoleChoices, blank=True, + null=True, verbose_name=_('parent/child status'), help_text=_('Parent devices house child devices in device bays. Leave blank ' 'if this device type is neither a parent nor a child.') @@ -126,7 +127,8 @@ class DeviceType(ImageAttachmentsMixin, PrimaryModel, WeightMixin): verbose_name=_('airflow'), max_length=50, choices=DeviceAirflowChoices, - blank=True + blank=True, + null=True ) front_image = models.ImageField( upload_to='devicetype-images', @@ -387,7 +389,8 @@ class ModuleType(ImageAttachmentsMixin, PrimaryModel, WeightMixin): verbose_name=_('airflow'), max_length=50, choices=ModuleAirflowChoices, - blank=True + blank=True, + null=True ) clone_fields = ('manufacturer', 'weight', 'weight_unit', 'airflow') @@ -632,6 +635,7 @@ class Device( face = models.CharField( max_length=50, blank=True, + null=True, choices=DeviceFaceChoices, verbose_name=_('rack face') ) @@ -645,7 +649,8 @@ class Device( verbose_name=_('airflow'), max_length=50, choices=DeviceAirflowChoices, - blank=True + blank=True, + null=True ) primary_ip4 = models.OneToOneField( to='ipam.IPAddress', diff --git a/netbox/dcim/models/racks.py b/netbox/dcim/models/racks.py index ae5513fea..013dfb619 100644 --- a/netbox/dcim/models/racks.py +++ b/netbox/dcim/models/racks.py @@ -83,7 +83,8 @@ class RackBase(WeightMixin, PrimaryModel): verbose_name=_('outer unit'), max_length=50, choices=RackDimensionUnitChoices, - blank=True + blank=True, + null=True ) mounting_depth = models.PositiveSmallIntegerField( verbose_name=_('mounting depth'), @@ -188,7 +189,7 @@ class RackType(RackBase): # Clear unit if outer width & depth are not set if self.outer_width is None and self.outer_depth is None: - self.outer_unit = '' + self.outer_unit = None super().save(*args, **kwargs) @@ -242,6 +243,7 @@ class Rack(ContactsMixin, ImageAttachmentsMixin, RackBase): choices=RackFormFactorChoices, max_length=50, blank=True, + null=True, verbose_name=_('form factor') ) rack_type = models.ForeignKey( @@ -317,7 +319,8 @@ class Rack(ContactsMixin, ImageAttachmentsMixin, RackBase): verbose_name=_('airflow'), max_length=50, choices=RackAirflowChoices, - blank=True + blank=True, + null=True ) # Generic relations @@ -409,7 +412,7 @@ class Rack(ContactsMixin, ImageAttachmentsMixin, RackBase): # Clear unit if outer width & depth are not set if self.outer_width is None and self.outer_depth is None: - self.outer_unit = '' + self.outer_unit = None super().save(*args, **kwargs) diff --git a/netbox/dcim/tests/test_filtersets.py b/netbox/dcim/tests/test_filtersets.py index 29fdf96e5..0a6417022 100644 --- a/netbox/dcim/tests/test_filtersets.py +++ b/netbox/dcim/tests/test_filtersets.py @@ -871,7 +871,6 @@ class RackTestCase(TestCase, ChangeLoggedFilterSetTests): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_outer_unit(self): - self.assertEqual(Rack.objects.filter(outer_unit__isnull=False).count(), 5) params = {'outer_unit': RackDimensionUnitChoices.UNIT_MILLIMETER} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) diff --git a/netbox/dcim/tests/test_views.py b/netbox/dcim/tests/test_views.py index ca0db5588..dc3e74ae1 100644 --- a/netbox/dcim/tests/test_views.py +++ b/netbox/dcim/tests/test_views.py @@ -592,7 +592,7 @@ class DeviceTypeTestCase( 'part_number': '123ABC', 'u_height': 2, 'is_full_depth': True, - 'subdevice_role': '', # CharField + 'subdevice_role': None, 'comments': 'Some comments', 'tags': [t.pk for t in tags], } diff --git a/netbox/extras/migrations/0122_charfield_null_choices.py b/netbox/extras/migrations/0122_charfield_null_choices.py new file mode 100644 index 000000000..9a1c7ff3f --- /dev/null +++ b/netbox/extras/migrations/0122_charfield_null_choices.py @@ -0,0 +1,29 @@ +from django.db import migrations, models + + +def set_null_values(apps, schema_editor): + """ + Replace empty strings with null values. + """ + CustomFieldChoiceSet = apps.get_model('extras', 'CustomFieldChoiceSet') + + CustomFieldChoiceSet.objects.filter(base_choices='').update(base_choices=None) + + +class Migration(migrations.Migration): + + dependencies = [ + ('extras', '0121_customfield_related_object_filter'), + ] + + operations = [ + migrations.AlterField( + model_name='customfieldchoiceset', + name='base_choices', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.RunPython( + code=set_null_values, + reverse_code=migrations.RunPython.noop + ), + ] diff --git a/netbox/extras/models/customfields.py b/netbox/extras/models/customfields.py index 8b7fc0cb6..e1ceaf7a6 100644 --- a/netbox/extras/models/customfields.py +++ b/netbox/extras/models/customfields.py @@ -760,6 +760,7 @@ class CustomFieldChoiceSet(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel max_length=50, choices=CustomFieldChoiceSetBaseChoices, blank=True, + null=True, help_text=_('Base set of predefined choices (optional)') ) extra_choices = ArrayField( diff --git a/netbox/ipam/migrations/0073_charfield_null_choices.py b/netbox/ipam/migrations/0073_charfield_null_choices.py new file mode 100644 index 000000000..9293728f5 --- /dev/null +++ b/netbox/ipam/migrations/0073_charfield_null_choices.py @@ -0,0 +1,36 @@ +from django.db import migrations, models + + +def set_null_values(apps, schema_editor): + """ + Replace empty strings with null values. + """ + FHRPGroup = apps.get_model('ipam', 'FHRPGroup') + IPAddress = apps.get_model('ipam', 'IPAddress') + + FHRPGroup.objects.filter(auth_type='').update(auth_type=None) + IPAddress.objects.filter(role='').update(role=None) + + +class Migration(migrations.Migration): + + dependencies = [ + ('ipam', '0072_prefix_cached_relations'), + ] + + operations = [ + migrations.AlterField( + model_name='fhrpgroup', + name='auth_type', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AlterField( + model_name='ipaddress', + name='role', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.RunPython( + code=set_null_values, + reverse_code=migrations.RunPython.noop + ), + ] diff --git a/netbox/ipam/models/fhrp.py b/netbox/ipam/models/fhrp.py index 28bb37ef3..f5982853e 100644 --- a/netbox/ipam/models/fhrp.py +++ b/netbox/ipam/models/fhrp.py @@ -34,6 +34,7 @@ class FHRPGroup(PrimaryModel): max_length=50, choices=FHRPGroupAuthTypeChoices, blank=True, + null=True, verbose_name=_('authentication type') ) auth_key = models.CharField( diff --git a/netbox/ipam/models/ip.py b/netbox/ipam/models/ip.py index 69da45fc6..f06b19237 100644 --- a/netbox/ipam/models/ip.py +++ b/netbox/ipam/models/ip.py @@ -784,6 +784,7 @@ class IPAddress(ContactsMixin, PrimaryModel): max_length=50, choices=IPAddressRoleChoices, blank=True, + null=True, help_text=_('The functional role of this IP') ) assigned_object_type = models.ForeignKey( diff --git a/netbox/netbox/models/mixins.py b/netbox/netbox/models/mixins.py index 804e0b71a..dc706c7c2 100644 --- a/netbox/netbox/models/mixins.py +++ b/netbox/netbox/models/mixins.py @@ -23,6 +23,7 @@ class WeightMixin(models.Model): max_length=50, choices=WeightUnitChoices, blank=True, + null=True, ) # Stores the normalized weight (in grams) for database ordering _abs_weight = models.PositiveBigIntegerField( @@ -64,6 +65,7 @@ class DistanceMixin(models.Model): max_length=50, choices=DistanceUnitChoices, blank=True, + null=True, ) # Stores the normalized distance (in meters) for database ordering _abs_distance = models.DecimalField( @@ -85,7 +87,7 @@ class DistanceMixin(models.Model): # Clear distance_unit if no distance is defined if self.distance is None: - self.distance_unit = '' + self.distance_unit = None super().save(*args, **kwargs) diff --git a/netbox/tenancy/migrations/0016_charfield_null_choices.py b/netbox/tenancy/migrations/0016_charfield_null_choices.py new file mode 100644 index 000000000..815a1bdf2 --- /dev/null +++ b/netbox/tenancy/migrations/0016_charfield_null_choices.py @@ -0,0 +1,29 @@ +from django.db import migrations, models + + +def set_null_values(apps, schema_editor): + """ + Replace empty strings with null values. + """ + ContactAssignment = apps.get_model('tenancy', 'ContactAssignment') + + ContactAssignment.objects.filter(priority='').update(priority=None) + + +class Migration(migrations.Migration): + + dependencies = [ + ('tenancy', '0015_contactassignment_rename_content_type'), + ] + + operations = [ + migrations.AlterField( + model_name='contactassignment', + name='priority', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.RunPython( + code=set_null_values, + reverse_code=migrations.RunPython.noop + ), + ] diff --git a/netbox/tenancy/models/contacts.py b/netbox/tenancy/models/contacts.py index 46ca97261..24ffef0cf 100644 --- a/netbox/tenancy/models/contacts.py +++ b/netbox/tenancy/models/contacts.py @@ -125,7 +125,8 @@ class ContactAssignment(CustomFieldsMixin, ExportTemplatesMixin, TagsMixin, Chan verbose_name=_('priority'), max_length=50, choices=ContactPriorityChoices, - blank=True + blank=True, + null=True ) clone_fields = ('object_type', 'object_id', 'role', 'priority') diff --git a/netbox/virtualization/migrations/0041_charfield_null_choices.py b/netbox/virtualization/migrations/0041_charfield_null_choices.py new file mode 100644 index 000000000..88b7f9b2c --- /dev/null +++ b/netbox/virtualization/migrations/0041_charfield_null_choices.py @@ -0,0 +1,29 @@ +from django.db import migrations, models + + +def set_null_values(apps, schema_editor): + """ + Replace empty strings with null values. + """ + VMInterface = apps.get_model('virtualization', 'VMInterface') + + VMInterface.objects.filter(mode='').update(mode=None) + + +class Migration(migrations.Migration): + + dependencies = [ + ('virtualization', '0040_convert_disk_size'), + ] + + operations = [ + migrations.AlterField( + model_name='vminterface', + name='mode', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.RunPython( + code=set_null_values, + reverse_code=migrations.RunPython.noop + ), + ] diff --git a/netbox/vpn/migrations/0006_charfield_null_choices.py b/netbox/vpn/migrations/0006_charfield_null_choices.py new file mode 100644 index 000000000..1943c466f --- /dev/null +++ b/netbox/vpn/migrations/0006_charfield_null_choices.py @@ -0,0 +1,49 @@ +from django.db import migrations, models + + +def set_null_values(apps, schema_editor): + """ + Replace empty strings with null values. + """ + IKEPolicy = apps.get_model('vpn', 'IKEPolicy') + IKEProposal = apps.get_model('vpn', 'IKEProposal') + IPSecProposal = apps.get_model('vpn', 'IPSecProposal') + + IKEPolicy.objects.filter(mode='').update(mode=None) + IKEProposal.objects.filter(authentication_algorithm='').update(authentication_algorithm=None) + IPSecProposal.objects.filter(authentication_algorithm='').update(authentication_algorithm=None) + IPSecProposal.objects.filter(encryption_algorithm='').update(encryption_algorithm=None) + + +class Migration(migrations.Migration): + + dependencies = [ + ('vpn', '0005_rename_indexes'), + ] + + operations = [ + migrations.AlterField( + model_name='ikepolicy', + name='mode', + field=models.CharField(blank=True, null=True), + ), + migrations.AlterField( + model_name='ikeproposal', + name='authentication_algorithm', + field=models.CharField(blank=True, null=True), + ), + migrations.AlterField( + model_name='ipsecproposal', + name='authentication_algorithm', + field=models.CharField(blank=True, null=True), + ), + migrations.AlterField( + model_name='ipsecproposal', + name='encryption_algorithm', + field=models.CharField(blank=True, null=True), + ), + migrations.RunPython( + code=set_null_values, + reverse_code=migrations.RunPython.noop + ), + ] diff --git a/netbox/vpn/models/crypto.py b/netbox/vpn/models/crypto.py index 618a1ec4a..2b721ec29 100644 --- a/netbox/vpn/models/crypto.py +++ b/netbox/vpn/models/crypto.py @@ -35,7 +35,8 @@ class IKEProposal(PrimaryModel): authentication_algorithm = models.CharField( verbose_name=_('authentication algorithm'), choices=AuthenticationAlgorithmChoices, - blank=True + blank=True, + null=True ) group = models.PositiveSmallIntegerField( verbose_name=_('group'), @@ -76,7 +77,8 @@ class IKEPolicy(PrimaryModel): mode = models.CharField( verbose_name=_('mode'), choices=IKEModeChoices, - blank=True + blank=True, + null=True ) proposals = models.ManyToManyField( to='vpn.IKEProposal', @@ -128,12 +130,14 @@ class IPSecProposal(PrimaryModel): encryption_algorithm = models.CharField( verbose_name=_('encryption'), choices=EncryptionAlgorithmChoices, - blank=True + blank=True, + null=True ) authentication_algorithm = models.CharField( verbose_name=_('authentication'), choices=AuthenticationAlgorithmChoices, - blank=True + blank=True, + null=True ) sa_lifetime_seconds = models.PositiveIntegerField( verbose_name=_('SA lifetime (seconds)'), diff --git a/netbox/wireless/migrations/0010_charfield_null_choices.py b/netbox/wireless/migrations/0010_charfield_null_choices.py new file mode 100644 index 000000000..9bfdc54ed --- /dev/null +++ b/netbox/wireless/migrations/0010_charfield_null_choices.py @@ -0,0 +1,54 @@ +from django.db import migrations, models + + +def set_null_values(apps, schema_editor): + """ + Replace empty strings with null values. + """ + WirelessLAN = apps.get_model('wireless', 'WirelessLAN') + WirelessLink = apps.get_model('wireless', 'WirelessLink') + + WirelessLAN.objects.filter(auth_cipher='').update(auth_cipher=None) + WirelessLAN.objects.filter(auth_type='').update(auth_type=None) + WirelessLink.objects.filter(auth_cipher='').update(auth_cipher=None) + WirelessLink.objects.filter(auth_type='').update(auth_type=None) + WirelessLink.objects.filter(distance_unit='').update(distance_unit=None) + + +class Migration(migrations.Migration): + + dependencies = [ + ('wireless', '0009_wirelesslink_distance'), + ] + + operations = [ + migrations.AlterField( + model_name='wirelesslan', + name='auth_cipher', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AlterField( + model_name='wirelesslan', + name='auth_type', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AlterField( + model_name='wirelesslink', + name='auth_cipher', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AlterField( + model_name='wirelesslink', + name='auth_type', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AlterField( + model_name='wirelesslink', + name='distance_unit', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.RunPython( + code=set_null_values, + reverse_code=migrations.RunPython.noop + ), + ] diff --git a/netbox/wireless/models.py b/netbox/wireless/models.py index 685d978a7..88c60d494 100644 --- a/netbox/wireless/models.py +++ b/netbox/wireless/models.py @@ -24,13 +24,15 @@ class WirelessAuthenticationBase(models.Model): max_length=50, choices=WirelessAuthTypeChoices, blank=True, + null=True, verbose_name=_("authentication type"), ) auth_cipher = models.CharField( verbose_name=_('authentication cipher'), max_length=50, choices=WirelessAuthCipherChoices, - blank=True + blank=True, + null=True ) auth_psk = models.CharField( max_length=PSK_MAX_LENGTH,