diff --git a/netbox/dcim/constants.py b/netbox/dcim/constants.py index c6e18ca82..387b4d6a7 100644 --- a/netbox/dcim/constants.py +++ b/netbox/dcim/constants.py @@ -53,6 +53,11 @@ WIRELESS_IFACE_TYPES = [ InterfaceTypeChoices.TYPE_802151, InterfaceTypeChoices.TYPE_802154, InterfaceTypeChoices.TYPE_OTHER_WIRELESS, + InterfaceTypeChoices.TYPE_GSM, + InterfaceTypeChoices.TYPE_CDMA, + InterfaceTypeChoices.TYPE_LTE, + InterfaceTypeChoices.TYPE_4G, + InterfaceTypeChoices.TYPE_5G, ] NONCONNECTABLE_IFACE_TYPES = VIRTUAL_IFACE_TYPES + WIRELESS_IFACE_TYPES diff --git a/netbox/dcim/tests/test_models.py b/netbox/dcim/tests/test_models.py index 281071ed9..be9f067d4 100644 --- a/netbox/dcim/tests/test_models.py +++ b/netbox/dcim/tests/test_models.py @@ -954,6 +954,19 @@ class CableTestCase(TestCase): with self.assertRaises(ValidationError): cable.clean() + @tag('regression') + def test_cable_cannot_terminate_to_a_cellular_interface(self): + """ + A cable cannot terminate to a cellular interface + """ + device1 = Device.objects.get(name='TestDevice1') + interface2 = Interface.objects.get(device__name='TestDevice2', name='eth0') + + cellular_interface = Interface(device=device1, name="W1", type=InterfaceTypeChoices.TYPE_LTE) + cable = Cable(a_terminations=[interface2], b_terminations=[cellular_interface]) + with self.assertRaises(ValidationError): + cable.clean() + class VirtualDeviceContextTestCase(TestCase): diff --git a/netbox/extras/api/serializers_/scripts.py b/netbox/extras/api/serializers_/scripts.py index 897ccf966..aa0268ecf 100644 --- a/netbox/extras/api/serializers_/scripts.py +++ b/netbox/extras/api/serializers_/scripts.py @@ -66,11 +66,11 @@ class ScriptInputSerializer(serializers.Serializer): interval = serializers.IntegerField(required=False, allow_null=True) def validate_schedule_at(self, value): - if value and not self.context['script'].scheduling_enabled: + if value and not self.context['script'].python_class.scheduling_enabled: raise serializers.ValidationError(_("Scheduling is not enabled for this script.")) return value def validate_interval(self, value): - if value and not self.context['script'].scheduling_enabled: + if value and not self.context['script'].python_class.scheduling_enabled: raise serializers.ValidationError(_("Scheduling is not enabled for this script.")) return value diff --git a/netbox/extras/api/views.py b/netbox/extras/api/views.py index 6e9225f73..3f5bb172a 100644 --- a/netbox/extras/api/views.py +++ b/netbox/extras/api/views.py @@ -270,6 +270,7 @@ class ScriptViewSet(ModelViewSet): module_name, script_name = pk.split('.', maxsplit=1) except ValueError: raise Http404 + return get_object_or_404(self.queryset, module__file_path=f'{module_name}.py', name=script_name) def retrieve(self, request, pk): diff --git a/netbox/extras/forms/bulk_import.py b/netbox/extras/forms/bulk_import.py index 5c62932e5..cf15495ca 100644 --- a/netbox/extras/forms/bulk_import.py +++ b/netbox/extras/forms/bulk_import.py @@ -238,10 +238,18 @@ class TagImportForm(CSVModelForm): label=_('Weight'), required=False ) + object_types = CSVMultipleContentTypeField( + label=_('Object types'), + queryset=ObjectType.objects.with_feature('tags'), + help_text=_("One or more assigned object types"), + required=False, + ) class Meta: model = Tag - fields = ('name', 'slug', 'color', 'weight', 'description') + fields = ( + 'name', 'slug', 'color', 'weight', 'description', 'object_types', + ) class JournalEntryImportForm(NetBoxModelImportForm): diff --git a/netbox/extras/tests/test_views.py b/netbox/extras/tests/test_views.py index 6378b29b8..fd3ce5453 100644 --- a/netbox/extras/tests/test_views.py +++ b/netbox/extras/tests/test_views.py @@ -444,6 +444,8 @@ class TagTestCase(ViewTestCases.OrganizationalObjectViewTestCase): @classmethod def setUpTestData(cls): + site_ct = ContentType.objects.get_for_model(Site) + tags = ( Tag(name='Tag 1', slug='tag-1'), Tag(name='Tag 2', slug='tag-2', weight=1), @@ -456,14 +458,15 @@ class TagTestCase(ViewTestCases.OrganizationalObjectViewTestCase): 'slug': 'tag-x', 'color': 'c0c0c0', 'comments': 'Some comments', + 'object_types': [site_ct.pk], 'weight': 11, } cls.csv_data = ( - "name,slug,color,description,weight", - "Tag 4,tag-4,ff0000,Fourth tag,0", - "Tag 5,tag-5,00ff00,Fifth tag,1111", - "Tag 6,tag-6,0000ff,Sixth tag,0", + "name,slug,color,description,object_types,weight", + "Tag 4,tag-4,ff0000,Fourth tag,dcim.interface,0", + "Tag 5,tag-5,00ff00,Fifth tag,'dcim.device,dcim.site',1111", + "Tag 6,tag-6,0000ff,Sixth tag,dcim.site,0", ) cls.csv_update_data = ( diff --git a/netbox/translations/en/LC_MESSAGES/django.po b/netbox/translations/en/LC_MESSAGES/django.po index dd5374cb5..77bef53c6 100644 --- a/netbox/translations/en/LC_MESSAGES/django.po +++ b/netbox/translations/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-06-10 05:02+0000\n" +"POT-Creation-Date: 2025-06-11 05:02+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -5278,7 +5278,7 @@ msgid "Connection" msgstr "" #: netbox/dcim/forms/filtersets.py:1426 netbox/extras/forms/bulk_edit.py:382 -#: netbox/extras/forms/bulk_import.py:253 netbox/extras/forms/filtersets.py:527 +#: netbox/extras/forms/bulk_import.py:261 netbox/extras/forms/filtersets.py:527 #: netbox/extras/forms/model_forms.py:759 netbox/extras/tables/tables.py:640 #: netbox/templates/extras/journalentry.html:30 msgid "Kind" @@ -8192,7 +8192,8 @@ msgstr "" #: netbox/extras/forms/bulk_import.py:37 netbox/extras/forms/bulk_import.py:118 #: netbox/extras/forms/bulk_import.py:139 #: netbox/extras/forms/bulk_import.py:164 -#: netbox/extras/forms/bulk_import.py:188 netbox/extras/forms/filtersets.py:141 +#: netbox/extras/forms/bulk_import.py:188 +#: netbox/extras/forms/bulk_import.py:242 netbox/extras/forms/filtersets.py:141 #: netbox/extras/forms/filtersets.py:235 netbox/extras/forms/filtersets.py:265 #: netbox/extras/forms/model_forms.py:50 netbox/extras/forms/model_forms.py:222 #: netbox/extras/forms/model_forms.py:254 @@ -8206,6 +8207,7 @@ msgstr "" #: netbox/extras/forms/bulk_import.py:141 #: netbox/extras/forms/bulk_import.py:166 #: netbox/extras/forms/bulk_import.py:190 +#: netbox/extras/forms/bulk_import.py:244 #: netbox/tenancy/forms/bulk_import.py:95 msgid "One or more assigned object types" msgstr "" @@ -8282,15 +8284,15 @@ msgstr "" msgid "Script {name} not found" msgstr "" -#: netbox/extras/forms/bulk_import.py:250 +#: netbox/extras/forms/bulk_import.py:258 msgid "Assigned object type" msgstr "" -#: netbox/extras/forms/bulk_import.py:255 +#: netbox/extras/forms/bulk_import.py:263 msgid "The classification of entry" msgstr "" -#: netbox/extras/forms/bulk_import.py:267 +#: netbox/extras/forms/bulk_import.py:275 #: netbox/extras/forms/model_forms.py:398 netbox/netbox/navigation/menu.py:413 #: netbox/templates/extras/notificationgroup.html:41 #: netbox/templates/users/group.html:29 netbox/users/forms/model_forms.py:236 @@ -8299,11 +8301,11 @@ msgstr "" msgid "Users" msgstr "" -#: netbox/extras/forms/bulk_import.py:271 +#: netbox/extras/forms/bulk_import.py:279 msgid "User names separated by commas, encased with double quotes" msgstr "" -#: netbox/extras/forms/bulk_import.py:274 +#: netbox/extras/forms/bulk_import.py:282 #: netbox/extras/forms/model_forms.py:393 netbox/netbox/navigation/menu.py:295 #: netbox/netbox/navigation/menu.py:433 #: netbox/templates/extras/notificationgroup.html:31 @@ -8316,7 +8318,7 @@ msgstr "" msgid "Groups" msgstr "" -#: netbox/extras/forms/bulk_import.py:278 +#: netbox/extras/forms/bulk_import.py:286 msgid "Group names separated by commas, encased with double quotes" msgstr "" diff --git a/pyproject.toml b/pyproject.toml index 3664e4805..9cd50e650 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ [project] name = "netbox" -version = "4.3.1" +version = "4.3.2" requires-python = ">=3.10" authors = [ { name = "NetBox Community" }