From 0ac8419005ecc85522b742cca9229dd597e65e5d Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Thu, 30 Mar 2023 16:29:54 -0400 Subject: [PATCH 01/19] Fixes #12104: Restore copy-to-clipboard & footer navigation in docs --- mkdocs.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index 2317dad6d..57ffaf461 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -8,6 +8,9 @@ theme: custom_dir: docs/_theme/ icon: repo: fontawesome/brands/github + features: + - content.code.copy + - navigation.footer palette: - media: "(prefers-color-scheme: light)" scheme: default @@ -20,7 +23,8 @@ theme: icon: material/lightbulb name: Switch to Light Mode plugins: - - search + - search: + lang: en - mkdocstrings: handlers: python: From 6f08c4a4be9b6780d083fa85057aff97ffd1fd7e Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Thu, 30 Mar 2023 16:35:56 -0400 Subject: [PATCH 02/19] Fixes #11846: Update database creation instructions for PostgreSQL 14+ --- docs/installation/1-postgresql.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation/1-postgresql.md b/docs/installation/1-postgresql.md index 583a4f3e9..546bfb7b4 100644 --- a/docs/installation/1-postgresql.md +++ b/docs/installation/1-postgresql.md @@ -54,7 +54,7 @@ Within the shell, enter the following commands to create the database and user ( ```postgresql CREATE DATABASE netbox; CREATE USER netbox WITH PASSWORD 'J5brHrAXFLQSif0K'; -GRANT ALL PRIVILEGES ON DATABASE netbox TO netbox; +ALTER DATABASE netbox OWNER TO netbox; ``` !!! danger "Use a strong password" From fbc23424a6689d95b52e9514e95862daddb13863 Mon Sep 17 00:00:00 2001 From: gdprdatasubect <101808236+gdprdatasubect@users.noreply.github.com> Date: Thu, 30 Mar 2023 17:19:55 +0200 Subject: [PATCH 03/19] Update models.py Change default MIME-Type as discussed under #12095 --- netbox/extras/models/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/netbox/extras/models/models.py b/netbox/extras/models/models.py index bb22c9851..3cab6154d 100644 --- a/netbox/extras/models/models.py +++ b/netbox/extras/models/models.py @@ -301,7 +301,7 @@ class ExportTemplate(CloningMixin, ExportTemplatesMixin, WebhooksMixin, ChangeLo max_length=50, blank=True, verbose_name='MIME type', - help_text=_('Defaults to text/plain') + help_text=_('Defaults to text/plain; charset=utf-8') ) file_extension = models.CharField( max_length=15, @@ -357,7 +357,7 @@ class ExportTemplate(CloningMixin, ExportTemplatesMixin, WebhooksMixin, ChangeLo Render the template to an HTTP response, delivered as a named file attachment """ output = self.render(queryset) - mime_type = 'text/plain' if not self.mime_type else self.mime_type + mime_type = 'text/plain; charset=utf-8' if not self.mime_type else self.mime_type # Build the response response = HttpResponse(output, content_type=mime_type) From 3264636b7a3cec47646abda1e8d9fb5f3dbd6176 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Thu, 30 Mar 2023 17:02:23 -0400 Subject: [PATCH 04/19] Changelog for #12084, #12095 --- docs/release-notes/version-3.4.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/release-notes/version-3.4.md b/docs/release-notes/version-3.4.md index 47f590d59..c98e62b81 100644 --- a/docs/release-notes/version-3.4.md +++ b/docs/release-notes/version-3.4.md @@ -2,6 +2,14 @@ ## v3.4.8 (FUTURE) +### Enhancements + +* [#12095](https://github.com/netbox-community/netbox/issues/12095) - Specify UTF-8 encoding for default export template MIME type + +### Bug Fixes + +* [#12084](https://github.com/netbox-community/netbox/issues/12084) - Fix exception when attempting to create a saved filter for applied filters + --- ## v3.4.7 (2023-03-28) From 2883fa14de3f7cd089ff5c98cd598ebb3dbe7387 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Mon, 3 Apr 2023 12:22:36 -0700 Subject: [PATCH 05/19] Fixes #12074: Move automatic location assignment out of clean() * 12074 fix full clean * 12074 move device location setting to save from clean * 12074 fix set location only if present in rack * Update base.py --------- Co-authored-by: Jeremy Stretch --- netbox/dcim/models/devices.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/netbox/dcim/models/devices.py b/netbox/dcim/models/devices.py index 603129228..1429003c5 100644 --- a/netbox/dcim/models/devices.py +++ b/netbox/dcim/models/devices.py @@ -659,8 +659,6 @@ class Device(PrimaryModel, ConfigContextModel): raise ValidationError({ 'rack': f"Rack {self.rack} does not belong to location {self.location}.", }) - elif self.rack: - self.location = self.rack.location if self.rack is None: if self.face: @@ -801,6 +799,9 @@ class Device(PrimaryModel, ConfigContextModel): if is_new and not self.airflow: self.airflow = self.device_type.airflow + if self.rack and self.rack.location: + self.location = self.rack.location + super().save(*args, **kwargs) # If this is a new Device, instantiate all the related components per the DeviceType definition From bca00cd97a279e5437f2e2f8ff8ec89620f97125 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Mon, 3 Apr 2023 12:27:13 -0700 Subject: [PATCH 06/19] 12117 remove clone from cable (#12130) * 12117 remove clone from cable * 12117 remove clone button if no params * Update clone.html * Update clone.html * Update clone.html --------- Co-authored-by: Jeremy Stretch --- netbox/utilities/templates/buttons/clone.html | 8 +++++--- netbox/utilities/templatetags/buttons.py | 2 ++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/netbox/utilities/templates/buttons/clone.html b/netbox/utilities/templates/buttons/clone.html index 24e685c3d..8fe62a1c8 100644 --- a/netbox/utilities/templates/buttons/clone.html +++ b/netbox/utilities/templates/buttons/clone.html @@ -1,3 +1,5 @@ - -  Clone - +{% if url %} + + Clone + +{% endif %} diff --git a/netbox/utilities/templatetags/buttons.py b/netbox/utilities/templatetags/buttons.py index bcdb099d8..dbd0240b9 100644 --- a/netbox/utilities/templatetags/buttons.py +++ b/netbox/utilities/templatetags/buttons.py @@ -20,6 +20,8 @@ def clone_button(instance): param_string = prepare_cloned_fields(instance).urlencode() if param_string: url = f'{url}?{param_string}' + else: + url = None return { 'url': url, From 8a684adf66869d6123c55cb8ae67a917446aeefd Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 3 Apr 2023 15:38:05 -0400 Subject: [PATCH 07/19] Changelog for #12074, #12117 --- docs/release-notes/version-3.4.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/release-notes/version-3.4.md b/docs/release-notes/version-3.4.md index c98e62b81..39a2315a0 100644 --- a/docs/release-notes/version-3.4.md +++ b/docs/release-notes/version-3.4.md @@ -8,7 +8,9 @@ ### Bug Fixes +* [#12074](https://github.com/netbox-community/netbox/issues/12074) - Fix the automatic assignment of racks to devices via the REST API * [#12084](https://github.com/netbox-community/netbox/issues/12084) - Fix exception when attempting to create a saved filter for applied filters +* [#12117](https://github.com/netbox-community/netbox/issues/12117) - Hide clone button for objects with no clonable attributes --- From b032742418842cbaf74479c8179ba249ed6611d0 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 3 Apr 2023 16:26:07 -0400 Subject: [PATCH 08/19] Closes #12133: Move any instance mutations inside clean() to save() --- netbox/dcim/models/cables.py | 6 ++++-- netbox/dcim/models/device_components.py | 14 ++++++++++---- netbox/dcim/models/racks.py | 6 ++++-- netbox/ipam/models/ip.py | 3 --- netbox/virtualization/models/virtualmachines.py | 10 ++++++++-- netbox/virtualization/tests/test_models.py | 2 +- 6 files changed, 27 insertions(+), 14 deletions(-) diff --git a/netbox/dcim/models/cables.py b/netbox/dcim/models/cables.py index 062734355..cf5f30ee4 100644 --- a/netbox/dcim/models/cables.py +++ b/netbox/dcim/models/cables.py @@ -152,8 +152,6 @@ class Cable(PrimaryModel): # Validate length and length_unit if self.length is not None and not self.length_unit: raise ValidationError("Must specify a unit when setting a cable length") - elif self.length is None: - self.length_unit = '' if self.pk is None and (not self.a_terminations or not self.b_terminations): raise ValidationError("Must define A and B terminations when creating a new cable.") @@ -187,6 +185,10 @@ class Cable(PrimaryModel): else: self._abs_length = None + # Clear length_unit if no length is defined + if self.length is None: + self.length_unit = '' + super().save(*args, **kwargs) # Update the private pk used in __str__ in case this is a new object (i.e. just got its pk) diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index 26a6ade98..b879b77d3 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -794,8 +794,6 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd raise ValidationError({ 'rf_channel_frequency': "Cannot specify custom frequency with channel selected.", }) - elif self.rf_channel: - self.rf_channel_frequency = get_channel_attr(self.rf_channel, 'frequency') # Validate channel width against interface type and selected channel (if any) if self.rf_channel_width: @@ -803,8 +801,6 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd raise ValidationError({'rf_channel_width': "Channel width may be set only on wireless interfaces."}) if self.rf_channel and self.rf_channel_width != get_channel_attr(self.rf_channel, 'width'): raise ValidationError({'rf_channel_width': "Cannot specify custom width with channel selected."}) - elif self.rf_channel: - self.rf_channel_width = get_channel_attr(self.rf_channel, 'width') # VLAN validation @@ -815,6 +811,16 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd f"interface's parent device, or it must be global." }) + def save(self, *args, **kwargs): + + # Set absolute channel attributes from selected options + if self.rf_channel and not self.rf_channel_frequency: + self.rf_channel_frequency = get_channel_attr(self.rf_channel, 'frequency') + if self.rf_channel and not self.rf_channel_width: + self.rf_channel_width = get_channel_attr(self.rf_channel, 'width') + + super().save(*args, **kwargs) + @property def _occupied(self): return super()._occupied or bool(self.wireless_link_id) diff --git a/netbox/dcim/models/racks.py b/netbox/dcim/models/racks.py index 03be2fdb3..e61e0f2a3 100644 --- a/netbox/dcim/models/racks.py +++ b/netbox/dcim/models/racks.py @@ -222,8 +222,6 @@ class Rack(PrimaryModel, WeightMixin): # Validate outer dimensions and unit if (self.outer_width is not None or self.outer_depth is not None) and not self.outer_unit: raise ValidationError("Must specify a unit when setting an outer width/depth") - elif self.outer_width is None and self.outer_depth is None: - self.outer_unit = '' # Validate max_weight and weight_unit if self.max_weight and not self.weight_unit: @@ -259,6 +257,10 @@ class Rack(PrimaryModel, WeightMixin): else: self._abs_max_weight = None + # Clear unit if outer width & depth are not set + if self.outer_width is None and self.outer_depth is None: + self.outer_unit = '' + super().save(*args, **kwargs) @property diff --git a/netbox/ipam/models/ip.py b/netbox/ipam/models/ip.py index e8bf13375..9effc3add 100644 --- a/netbox/ipam/models/ip.py +++ b/netbox/ipam/models/ip.py @@ -178,9 +178,6 @@ class Aggregate(GetAvailablePrefixesMixin, PrimaryModel): if self.prefix: - # Clear host bits from prefix - self.prefix = self.prefix.cidr - # /0 masks are not acceptable if self.prefix.prefixlen == 0: raise ValidationError({ diff --git a/netbox/virtualization/models/virtualmachines.py b/netbox/virtualization/models/virtualmachines.py index cc39044f9..6e9cc5664 100644 --- a/netbox/virtualization/models/virtualmachines.py +++ b/netbox/virtualization/models/virtualmachines.py @@ -169,8 +169,6 @@ class VirtualMachine(PrimaryModel, ConfigContextModel): raise ValidationError({ 'cluster': f'The selected cluster ({self.cluster}) is not assigned to this site ({self.site}).' }) - elif self.cluster: - self.site = self.cluster.site # Validate assigned cluster device if self.device and not self.cluster: @@ -201,6 +199,14 @@ class VirtualMachine(PrimaryModel, ConfigContextModel): field: f"The specified IP address ({ip}) is not assigned to this VM.", }) + def save(self, *args, **kwargs): + + # Assign site from cluster if not set + if self.cluster and not self.site: + self.site = self.cluster.site + + super().save(*args, **kwargs) + def get_status_color(self): return VirtualMachineStatusChoices.colors.get(self.status) diff --git a/netbox/virtualization/tests/test_models.py b/netbox/virtualization/tests/test_models.py index f7fa4cb39..782b9f07f 100644 --- a/netbox/virtualization/tests/test_models.py +++ b/netbox/virtualization/tests/test_models.py @@ -72,7 +72,7 @@ class VirtualMachineTestCase(TestCase): # VM with cluster site but no direct site should have its site set automatically vm = VirtualMachine(name='vm1', site=None, cluster=clusters[0]) - vm.full_clean() + vm.save() self.assertEqual(vm.site, sites[0]) def test_vm_name_case_sensitivity(self): From 0a2ae9041166ec559d288313f6b16d607f6af8b3 Mon Sep 17 00:00:00 2001 From: Arthur Date: Tue, 4 Apr 2023 13:04:13 -0700 Subject: [PATCH 09/19] 12011 fix module bay bulk create --- netbox/dcim/forms/bulk_create.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/netbox/dcim/forms/bulk_create.py b/netbox/dcim/forms/bulk_create.py index 11fdfa6d2..4127aa3ea 100644 --- a/netbox/dcim/forms/bulk_create.py +++ b/netbox/dcim/forms/bulk_create.py @@ -103,9 +103,9 @@ class RearPortBulkCreateForm( class ModuleBayBulkCreateForm(DeviceBulkAddComponentForm): model = ModuleBay - field_order = ('name', 'label', 'position_pattern', 'description', 'tags') + field_order = ('name', 'label', 'position', 'description', 'tags') replication_fields = ('name', 'label', 'position') - position_pattern = ExpandableNameField( + position = ExpandableNameField( label=_('Position'), required=False, help_text=_('Alphanumeric ranges are supported. (Must match the number of names being created.)') From 94c2a2e56cc827f07aae57268e3c21835e92e0a8 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Tue, 4 Apr 2023 13:38:48 -0700 Subject: [PATCH 10/19] 11746 fix delete custom field (#12092) * 11746 delete custom field * 11746 use filter instead of exclude --- netbox/extras/models/customfields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/extras/models/customfields.py b/netbox/extras/models/customfields.py index fa16b8501..836562b95 100644 --- a/netbox/extras/models/customfields.py +++ b/netbox/extras/models/customfields.py @@ -215,7 +215,7 @@ class CustomField(CloningMixin, ExportTemplatesMixin, WebhooksMixin, ChangeLogge """ for ct in content_types: model = ct.model_class() - instances = model.objects.filter(**{f'custom_field_data__{self.name}__isnull': False}) + instances = model.objects.filter(custom_field_data__has_key=self.name) for instance in instances: del instance.custom_field_data[self.name] model.objects.bulk_update(instances, ['custom_field_data'], batch_size=100) From 41c92483a0b9b820dd0ca0c43c6c6d6e560043ed Mon Sep 17 00:00:00 2001 From: Daniel Sheppard Date: Wed, 5 Apr 2023 12:29:32 -0500 Subject: [PATCH 11/19] #12087 - Fix Bulk Edit update when M2M operations are present. (#12169) * #12087 - Fix Bulk Edit update when M2M operations are present. * #12087 - Minor tweaks * Change .set() to .clear() Co-authored-by: Jeremy Stretch * #12087 - Update comments --------- Co-authored-by: Jeremy Stretch --- netbox/netbox/views/generic/bulk_views.py | 41 +++++++++++++---------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/netbox/netbox/views/generic/bulk_views.py b/netbox/netbox/views/generic/bulk_views.py index 6060475d8..c0efe3014 100644 --- a/netbox/netbox/views/generic/bulk_views.py +++ b/netbox/netbox/views/generic/bulk_views.py @@ -500,6 +500,21 @@ class BulkEditView(GetReturnURLMixin, BaseMultiObjectView): ] nullified_fields = request.POST.getlist('_nullify') updated_objects = [] + model_fields = {} + m2m_fields = {} + + # Build list of model fields and m2m fields for later iteration + for name in standard_fields: + try: + model_field = self.queryset.model._meta.get_field(name) + if isinstance(model_field, (ManyToManyField, ManyToManyRel)): + m2m_fields[name] = model_field + else: + model_fields[name] = model_field + + except FieldDoesNotExist: + # This form field is used to modify a field rather than set its value directly + model_fields[name] = None for obj in self.queryset.filter(pk__in=form.cleaned_data['pk']): @@ -508,25 +523,10 @@ class BulkEditView(GetReturnURLMixin, BaseMultiObjectView): obj.snapshot() # Update standard fields. If a field is listed in _nullify, delete its value. - for name in standard_fields: - - try: - model_field = self.queryset.model._meta.get_field(name) - except FieldDoesNotExist: - # This form field is used to modify a field rather than set its value directly - model_field = None - + for name, model_field in model_fields.items(): # Handle nullification if name in form.nullable_fields and name in nullified_fields: - if isinstance(model_field, ManyToManyField): - getattr(obj, name).set([]) - else: - setattr(obj, name, None if model_field.null else '') - - # ManyToManyFields - elif isinstance(model_field, (ManyToManyField, ManyToManyRel)): - if form.cleaned_data[name]: - getattr(obj, name).set(form.cleaned_data[name]) + setattr(obj, name, None if model_field.null else '') # Normal fields elif name in form.changed_data: setattr(obj, name, form.cleaned_data[name]) @@ -544,6 +544,13 @@ class BulkEditView(GetReturnURLMixin, BaseMultiObjectView): obj.save() updated_objects.append(obj) + # Handle M2M fields after save + for name, m2m_field in m2m_fields.items(): + if name in form.nullable_fields and name in nullified_fields: + getattr(obj, name).clear() + else: + getattr(obj, name).set(form.cleaned_data[name]) + # Add/remove tags if form.cleaned_data.get('add_tags', None): obj.tags.add(*form.cleaned_data['add_tags']) From f8d40ae8249d46884c185c87471af61d894dda35 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Wed, 5 Apr 2023 13:32:18 -0400 Subject: [PATCH 12/19] Changelog for #11746, #12011, #12087 --- docs/release-notes/version-3.4.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/release-notes/version-3.4.md b/docs/release-notes/version-3.4.md index 39a2315a0..d682c9fca 100644 --- a/docs/release-notes/version-3.4.md +++ b/docs/release-notes/version-3.4.md @@ -8,8 +8,11 @@ ### Bug Fixes +* [#11746](https://github.com/netbox-community/netbox/issues/11746) - Fix cleanup of object data when deleting a custom field +* [#12011](https://github.com/netbox-community/netbox/issues/12011) - Fix KeyError exception when attempting to add module bays in bulk * [#12074](https://github.com/netbox-community/netbox/issues/12074) - Fix the automatic assignment of racks to devices via the REST API * [#12084](https://github.com/netbox-community/netbox/issues/12084) - Fix exception when attempting to create a saved filter for applied filters +* [#12087](https://github.com/netbox-community/netbox/issues/12087) - Fix bulk editing of many-to-many relationships * [#12117](https://github.com/netbox-community/netbox/issues/12117) - Hide clone button for objects with no clonable attributes --- From 74d8baea30d65e6a6bf5b765f686d051ee9405e2 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Thu, 6 Apr 2023 14:32:57 -0400 Subject: [PATCH 13/19] Remove NS1 from sponsors list --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index e3c9611c0..99ad9a597 100644 --- a/README.md +++ b/README.md @@ -58,8 +58,6 @@ as the cornerstone for network automation in thousands of organizations. [![NetBox Labs](https://raw.githubusercontent.com/wiki/netbox-community/netbox/images/sponsors/netbox_labs.png)](https://netboxlabs.com)            [![DigitalOcean](https://raw.githubusercontent.com/wiki/netbox-community/netbox/images/sponsors/digitalocean.png)](https://try.digitalocean.com/developer-cloud) -            - [![NS1](https://raw.githubusercontent.com/wiki/netbox-community/netbox/images/sponsors/ns1.png)](https://ns1.com)
[![Sentry](https://raw.githubusercontent.com/wiki/netbox-community/netbox/images/sponsors/sentry.png)](https://sentry.io)            From 2bf9acfb19dcec6290dc32ffcad77c7f3843d99f Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 6 Apr 2023 16:35:27 -0400 Subject: [PATCH 14/19] Closes #12193: Clean up tests (#12197) * Fix skipped API tests * Remove invalid tests * Correct logger name --- netbox/extras/tests/test_api.py | 36 --------------------------------- netbox/ipam/tests/test_api.py | 2 +- netbox/users/tests/test_api.py | 17 ++++++++++++---- 3 files changed, 14 insertions(+), 41 deletions(-) diff --git a/netbox/extras/tests/test_api.py b/netbox/extras/tests/test_api.py index 81a607eec..4b5efda3c 100644 --- a/netbox/extras/tests/test_api.py +++ b/netbox/extras/tests/test_api.py @@ -1,13 +1,10 @@ import datetime -from unittest import skipIf from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType from django.urls import reverse from django.utils.timezone import make_aware -from django_rq.queues import get_connection from rest_framework import status -from rq import Worker from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Rack, Location, RackRole, Site from extras.api.views import ReportViewSet, ScriptViewSet @@ -16,8 +13,6 @@ from extras.reports import Report from extras.scripts import BooleanVar, IntegerVar, Script, StringVar from utilities.testing import APITestCase, APIViewTestCases -rq_worker_running = Worker.count(get_connection('default')) - class AppTest(APITestCase): @@ -539,16 +534,6 @@ class ReportTest(APITestCase): self.assertEqual(response.data['name'], self.TestReport.__name__) - @skipIf(not rq_worker_running, "RQ worker not running") - def test_run_report(self): - self.add_permissions('extras.run_script') - - url = reverse('extras-api:report-run', kwargs={'pk': None}) - response = self.client.post(url, {}, format='json', **self.header) - self.assertHttpStatus(response, status.HTTP_200_OK) - - self.assertEqual(response.data['result']['status']['value'], 'pending') - class ScriptTest(APITestCase): @@ -589,27 +574,6 @@ class ScriptTest(APITestCase): self.assertEqual(response.data['vars']['var2'], 'IntegerVar') self.assertEqual(response.data['vars']['var3'], 'BooleanVar') - @skipIf(not rq_worker_running, "RQ worker not running") - def test_run_script(self): - self.add_permissions('extras.run_script') - - script_data = { - 'var1': 'FooBar', - 'var2': 123, - 'var3': False, - } - - data = { - 'data': script_data, - 'commit': True, - } - - url = reverse('extras-api:script-detail', kwargs={'pk': None}) - response = self.client.post(url, data, format='json', **self.header) - self.assertHttpStatus(response, status.HTTP_200_OK) - - self.assertEqual(response.data['result']['status']['value'], 'pending') - class CreatedUpdatedFilterTest(APITestCase): diff --git a/netbox/ipam/tests/test_api.py b/netbox/ipam/tests/test_api.py index ea6441650..5af1bc769 100644 --- a/netbox/ipam/tests/test_api.py +++ b/netbox/ipam/tests/test_api.py @@ -836,7 +836,7 @@ class VLANTest(APIViewTestCases.APIViewTestCase): self.add_permissions('ipam.delete_vlan') url = reverse('ipam-api:vlan-detail', kwargs={'pk': vlan.pk}) - with disable_warnings('django.request'): + with disable_warnings('netbox.api.views.ModelViewSet'): response = self.client.delete(url, **self.header) self.assertHttpStatus(response, status.HTTP_409_CONFLICT) diff --git a/netbox/users/tests/test_api.py b/netbox/users/tests/test_api.py index a0bf8a49e..7041eb812 100644 --- a/netbox/users/tests/test_api.py +++ b/netbox/users/tests/test_api.py @@ -12,7 +12,7 @@ class AppTest(APITestCase): def test_root(self): url = reverse('users-api:api-root') - response = self.client.get('{}?format=api'.format(url), **self.header) + response = self.client.get(f'{url}?format=api', **self.header) self.assertEqual(response.status_code, 200) @@ -36,14 +36,17 @@ class UserTest(APIViewTestCases.APIViewTestCase): 'password': 'password6', }, ] + bulk_update_data = { + 'email': 'test@example.com', + } @classmethod def setUpTestData(cls): users = ( - User(username='User_1'), - User(username='User_2'), - User(username='User_3'), + User(username='User_1', password='password1'), + User(username='User_2', password='password2'), + User(username='User_3', password='password3'), ) User.objects.bulk_create(users) @@ -74,6 +77,12 @@ class GroupTest(APIViewTestCases.APIViewTestCase): ) Group.objects.bulk_create(users) + def test_bulk_update_objects(self): + """ + Disabled test. There's no attribute we can set in bulk for Groups. + """ + return + class TokenTest( # No GraphQL support for Token From ccfdc216a5b82a301212452c8951b753f8330ce6 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Thu, 6 Apr 2023 16:48:12 -0400 Subject: [PATCH 15/19] Fixes #12118: Refactor bulk creation logic under _instantiate_components() --- netbox/dcim/models/devices.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/netbox/dcim/models/devices.py b/netbox/dcim/models/devices.py index 1429003c5..0526c49cb 100644 --- a/netbox/dcim/models/devices.py +++ b/netbox/dcim/models/devices.py @@ -774,8 +774,10 @@ class Device(PrimaryModel, ConfigContextModel): bulk_create: If True, bulk_create() will be called to create all components in a single query (default). Otherwise, save() will be called on each instance individually. """ - components = [obj.instantiate(device=self) for obj in queryset] - if components and bulk_create: + if bulk_create: + components = [obj.instantiate(device=self) for obj in queryset] + if not components: + return model = components[0]._meta.model model.objects.bulk_create(components) # Manually send the post_save signal for each of the newly created components @@ -788,8 +790,9 @@ class Device(PrimaryModel, ConfigContextModel): using='default', update_fields=None ) - elif components: - for component in components: + else: + for obj in queryset: + component = obj.instantiate(device=self) component.save() def save(self, *args, **kwargs): From 63a0ec7a7993ec31993ceee1900913f92feba980 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Fri, 7 Apr 2023 10:03:47 -0400 Subject: [PATCH 16/19] Fixes #12190: Fix form layout for plugin textarea fields --- docs/release-notes/version-3.4.md | 1 + netbox/templates/dcim/cable_edit.html | 2 +- netbox/templates/dcim/device_edit.html | 1 - netbox/templates/dcim/rack_edit.html | 1 - .../templates/dcim/virtualchassis_edit.html | 1 - netbox/templates/generic/object_edit.html | 1 - netbox/templates/ipam/fhrpgroup_edit.html | 3 -- netbox/templates/ipam/ipaddress_edit.html | 3 -- netbox/templates/ipam/service_create.html | 3 -- netbox/templates/ipam/service_edit.html | 3 -- netbox/templates/ipam/vlan_edit.html | 3 -- .../templates/wireless/wirelesslink_edit.html | 39 ------------------- netbox/utilities/forms/fields/fields.py | 4 +- .../templates/form_helpers/render_field.html | 7 +--- 14 files changed, 6 insertions(+), 66 deletions(-) delete mode 100644 netbox/templates/wireless/wirelesslink_edit.html diff --git a/docs/release-notes/version-3.4.md b/docs/release-notes/version-3.4.md index d682c9fca..b913a252a 100644 --- a/docs/release-notes/version-3.4.md +++ b/docs/release-notes/version-3.4.md @@ -14,6 +14,7 @@ * [#12084](https://github.com/netbox-community/netbox/issues/12084) - Fix exception when attempting to create a saved filter for applied filters * [#12087](https://github.com/netbox-community/netbox/issues/12087) - Fix bulk editing of many-to-many relationships * [#12117](https://github.com/netbox-community/netbox/issues/12117) - Hide clone button for objects with no clonable attributes +* [#12190](https://github.com/netbox-community/netbox/issues/12190) - Fix form layout for plugin textarea fields --- diff --git a/netbox/templates/dcim/cable_edit.html b/netbox/templates/dcim/cable_edit.html index 1c747b44b..0ad0637e3 100644 --- a/netbox/templates/dcim/cable_edit.html +++ b/netbox/templates/dcim/cable_edit.html @@ -98,7 +98,7 @@
Comments
- {% render_field form.comments %} + {% render_field form.comments %}
{% if form.custom_fields %} diff --git a/netbox/templates/dcim/device_edit.html b/netbox/templates/dcim/device_edit.html index b814e65ef..185482162 100644 --- a/netbox/templates/dcim/device_edit.html +++ b/netbox/templates/dcim/device_edit.html @@ -111,7 +111,6 @@
-
Comments
{% render_field form.comments %}
diff --git a/netbox/templates/dcim/rack_edit.html b/netbox/templates/dcim/rack_edit.html index cd9ed637a..03624df1f 100644 --- a/netbox/templates/dcim/rack_edit.html +++ b/netbox/templates/dcim/rack_edit.html @@ -85,7 +85,6 @@ {% endif %}
-
Comments
{% render_field form.comments %}
{% endblock %} diff --git a/netbox/templates/dcim/virtualchassis_edit.html b/netbox/templates/dcim/virtualchassis_edit.html index 433837cf5..82b4c5bcc 100644 --- a/netbox/templates/dcim/virtualchassis_edit.html +++ b/netbox/templates/dcim/virtualchassis_edit.html @@ -27,7 +27,6 @@
-
Comments
{% render_field vc_form.comments %}
diff --git a/netbox/templates/generic/object_edit.html b/netbox/templates/generic/object_edit.html index c61fb723f..ff2ca26ad 100644 --- a/netbox/templates/generic/object_edit.html +++ b/netbox/templates/generic/object_edit.html @@ -85,7 +85,6 @@ Context: {% if form.comments %}
-
Comments
{% render_field form.comments %}
{% endif %} diff --git a/netbox/templates/ipam/fhrpgroup_edit.html b/netbox/templates/ipam/fhrpgroup_edit.html index bf86e6c41..bc0a6797c 100644 --- a/netbox/templates/ipam/fhrpgroup_edit.html +++ b/netbox/templates/ipam/fhrpgroup_edit.html @@ -33,9 +33,6 @@ {% endif %}
-
-
Comments
-
{% render_field form.comments %}
diff --git a/netbox/templates/ipam/ipaddress_edit.html b/netbox/templates/ipam/ipaddress_edit.html index b9a988009..4aa1c610a 100644 --- a/netbox/templates/ipam/ipaddress_edit.html +++ b/netbox/templates/ipam/ipaddress_edit.html @@ -139,9 +139,6 @@
-
-
Comments
-
{% render_field form.comments %}
diff --git a/netbox/templates/ipam/service_create.html b/netbox/templates/ipam/service_create.html index 5c47dd2f8..2d8d183c5 100644 --- a/netbox/templates/ipam/service_create.html +++ b/netbox/templates/ipam/service_create.html @@ -66,9 +66,6 @@
-
-
Comments
-
{% render_field form.comments %}
diff --git a/netbox/templates/ipam/service_edit.html b/netbox/templates/ipam/service_edit.html index 709d816c1..f2a2f711d 100644 --- a/netbox/templates/ipam/service_edit.html +++ b/netbox/templates/ipam/service_edit.html @@ -53,9 +53,6 @@
-
-
Comments
-
{% render_field form.comments %}
diff --git a/netbox/templates/ipam/vlan_edit.html b/netbox/templates/ipam/vlan_edit.html index f4432efe3..0c4b68e7e 100644 --- a/netbox/templates/ipam/vlan_edit.html +++ b/netbox/templates/ipam/vlan_edit.html @@ -56,9 +56,6 @@
-
-
Comments
-
{% render_field form.comments %}
diff --git a/netbox/templates/wireless/wirelesslink_edit.html b/netbox/templates/wireless/wirelesslink_edit.html deleted file mode 100644 index 462ae5148..000000000 --- a/netbox/templates/wireless/wirelesslink_edit.html +++ /dev/null @@ -1,39 +0,0 @@ -{% extends 'generic/object_edit.html' %} -{% load form_helpers %} - -{% block form %} -
-
-
-
-
Side A
-
- {% render_field form.device_a %} - {% render_field form.interface_a %} -
-
-
-
-
-
Side B
-
- {% render_field form.device_b %} - {% render_field form.interface_b %} -
-
-
-
-
-
Comments
-
- {% render_field form.comments %} -
- {% if form.custom_fields %} -
-
-
Custom Fields
-
- {% render_custom_fields form %} -
- {% endif %} -{% endblock %} diff --git a/netbox/utilities/forms/fields/fields.py b/netbox/utilities/forms/fields/fields.py index ee9543452..bb8226e4d 100644 --- a/netbox/utilities/forms/fields/fields.py +++ b/netbox/utilities/forms/fields/fields.py @@ -34,8 +34,8 @@ class CommentField(forms.CharField): Markdown syntax is supported """ - def __init__(self, *, label='', help_text=help_text, required=False, **kwargs): - super().__init__(label=label, help_text=help_text, required=required, **kwargs) + def __init__(self, *, help_text=help_text, required=False, **kwargs): + super().__init__(help_text=help_text, required=required, **kwargs) class SlugField(forms.SlugField): diff --git a/netbox/utilities/templates/form_helpers/render_field.html b/netbox/utilities/templates/form_helpers/render_field.html index 85c04df92..85bd86bbc 100644 --- a/netbox/utilities/templates/form_helpers/render_field.html +++ b/netbox/utilities/templates/form_helpers/render_field.html @@ -3,11 +3,8 @@
- {# Render the field label, except for: #} - {# 1. Checkboxes (label appears to the right of the field #} - {# 2. Textareas with no label set (will expand across entire row) #} - {% if field|widget_type == 'checkboxinput' or field|widget_type == 'textarea' or field|widget_type == 'markdownwidget' and not label %} - {% else %} + {# Render the field label, except for checkboxes #} + {% if field|widget_type != 'checkboxinput' %} From 085cfc58f493cb314f2d714c00d7ae8e75a7136a Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Fri, 7 Apr 2023 10:25:36 -0400 Subject: [PATCH 17/19] Fixes #12184: Fix filtered bulk deletion for various models --- docs/release-notes/version-3.4.md | 1 + netbox/circuits/views.py | 1 + netbox/dcim/views.py | 6 ++++++ netbox/extras/views.py | 1 + netbox/ipam/views.py | 1 + netbox/netbox/views/generic/bulk_views.py | 1 - netbox/tenancy/views.py | 3 +++ netbox/virtualization/views.py | 2 ++ 8 files changed, 15 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-3.4.md b/docs/release-notes/version-3.4.md index b913a252a..eb25591db 100644 --- a/docs/release-notes/version-3.4.md +++ b/docs/release-notes/version-3.4.md @@ -14,6 +14,7 @@ * [#12084](https://github.com/netbox-community/netbox/issues/12084) - Fix exception when attempting to create a saved filter for applied filters * [#12087](https://github.com/netbox-community/netbox/issues/12087) - Fix bulk editing of many-to-many relationships * [#12117](https://github.com/netbox-community/netbox/issues/12117) - Hide clone button for objects with no clonable attributes +* [#12184](https://github.com/netbox-community/netbox/issues/12184) - Fix filtered bulk deletion for various models * [#12190](https://github.com/netbox-community/netbox/issues/12190) - Fix form layout for plugin textarea fields --- diff --git a/netbox/circuits/views.py b/netbox/circuits/views.py index 3168509ba..461113147 100644 --- a/netbox/circuits/views.py +++ b/netbox/circuits/views.py @@ -196,6 +196,7 @@ class CircuitTypeBulkDeleteView(generic.BulkDeleteView): queryset = CircuitType.objects.annotate( circuit_count=count_related(Circuit, 'type') ) + filterset = filtersets.CircuitTypeFilterSet table = tables.CircuitTypeTable diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 9b49e799c..5a6261eba 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -628,6 +628,7 @@ class RackRoleBulkDeleteView(generic.BulkDeleteView): queryset = RackRole.objects.annotate( rack_count=count_related(Rack, 'role') ) + filterset = filtersets.RackRoleFilterSet table = tables.RackRoleTable @@ -909,6 +910,7 @@ class ManufacturerBulkDeleteView(generic.BulkDeleteView): queryset = Manufacturer.objects.annotate( devicetype_count=count_related(DeviceType, 'manufacturer') ) + filterset = filtersets.ManufacturerFilterSet table = tables.ManufacturerTable @@ -1808,6 +1810,7 @@ class DeviceRoleBulkDeleteView(generic.BulkDeleteView): device_count=count_related(Device, 'device_role'), vm_count=count_related(VirtualMachine, 'role') ) + filterset = filtersets.DeviceRoleFilterSet table = tables.DeviceRoleTable @@ -1868,6 +1871,7 @@ class PlatformBulkEditView(generic.BulkEditView): class PlatformBulkDeleteView(generic.BulkDeleteView): queryset = Platform.objects.all() + filterset = filtersets.PlatformFilterSet table = tables.PlatformTable @@ -2981,6 +2985,7 @@ class InventoryItemBulkRenameView(generic.BulkRenameView): class InventoryItemBulkDeleteView(generic.BulkDeleteView): queryset = InventoryItem.objects.all() + filterset = filtersets.InventoryItemFilterSet table = tables.InventoryItemTable template_name = 'dcim/inventoryitem_bulk_delete.html' @@ -3038,6 +3043,7 @@ class InventoryItemRoleBulkDeleteView(generic.BulkDeleteView): queryset = InventoryItemRole.objects.annotate( inventoryitem_count=count_related(InventoryItem, 'role'), ) + filterset = filtersets.InventoryItemRoleFilterSet table = tables.InventoryItemRoleTable diff --git a/netbox/extras/views.py b/netbox/extras/views.py index 91d3b5c58..d8a3c377d 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -414,6 +414,7 @@ class ConfigContextDeleteView(generic.ObjectDeleteView): class ConfigContextBulkDeleteView(generic.BulkDeleteView): queryset = ConfigContext.objects.all() + filterset = filtersets.ConfigContextFilterSet table = tables.ConfigContextTable diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 130014f3f..5f10fb5a7 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -431,6 +431,7 @@ class RoleBulkEditView(generic.BulkEditView): class RoleBulkDeleteView(generic.BulkDeleteView): queryset = Role.objects.all() + filterset = filtersets.RoleFilterSet table = tables.RoleTable diff --git a/netbox/netbox/views/generic/bulk_views.py b/netbox/netbox/views/generic/bulk_views.py index c0efe3014..211b869e6 100644 --- a/netbox/netbox/views/generic/bulk_views.py +++ b/netbox/netbox/views/generic/bulk_views.py @@ -16,7 +16,6 @@ from django_tables2.export import TableExport from extras.models import ExportTemplate from extras.signals import clear_webhooks -from utilities.choices import ImportFormatChoices from utilities.error_handlers import handle_protectederror from utilities.exceptions import AbortRequest, AbortTransaction, PermissionsViolation from utilities.forms import BulkRenameForm, ConfirmationForm, ImportForm, restrict_form_fields diff --git a/netbox/tenancy/views.py b/netbox/tenancy/views.py index f4c9b6d04..3ee39f0a7 100644 --- a/netbox/tenancy/views.py +++ b/netbox/tenancy/views.py @@ -84,6 +84,7 @@ class TenantGroupBulkDeleteView(generic.BulkDeleteView): 'tenant_count', cumulative=True ) + filterset = filtersets.TenantGroupFilterSet table = tables.TenantGroupTable @@ -247,6 +248,7 @@ class ContactGroupBulkDeleteView(generic.BulkDeleteView): 'contact_count', cumulative=True ) + filterset = filtersets.ContactGroupFilterSet table = tables.ContactGroupTable @@ -305,6 +307,7 @@ class ContactRoleBulkEditView(generic.BulkEditView): class ContactRoleBulkDeleteView(generic.BulkDeleteView): queryset = ContactRole.objects.all() + filterset = filtersets.ContactRoleFilterSet table = tables.ContactRoleTable diff --git a/netbox/virtualization/views.py b/netbox/virtualization/views.py index af130fcce..1ff21f1e0 100644 --- a/netbox/virtualization/views.py +++ b/netbox/virtualization/views.py @@ -80,6 +80,7 @@ class ClusterTypeBulkDeleteView(generic.BulkDeleteView): queryset = ClusterType.objects.annotate( cluster_count=count_related(Cluster, 'type') ) + filterset = filtersets.ClusterTypeFilterSet table = tables.ClusterTypeTable @@ -147,6 +148,7 @@ class ClusterGroupBulkDeleteView(generic.BulkDeleteView): queryset = ClusterGroup.objects.annotate( cluster_count=count_related(Cluster, 'group') ) + filterset = filtersets.ClusterGroupFilterSet table = tables.ClusterGroupTable From c26fe266ccccb13e790783aa8a847b9018b1de60 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Fri, 7 Apr 2023 20:24:39 +0530 Subject: [PATCH 18/19] Moved interface filterset under common class (#12200) * moved interface filterset under common class #12007 * lint fix --- netbox/dcim/filtersets.py | 67 ++++++++++++++++------------- netbox/virtualization/filtersets.py | 26 +---------- 2 files changed, 38 insertions(+), 55 deletions(-) diff --git a/netbox/dcim/filtersets.py b/netbox/dcim/filtersets.py index 493ccbbea..fb59659d3 100644 --- a/netbox/dcim/filtersets.py +++ b/netbox/dcim/filtersets.py @@ -24,6 +24,7 @@ __all__ = ( 'CableFilterSet', 'CabledObjectFilterSet', 'CableTerminationFilterSet', + 'CommonInterfaceFilterSet', 'ConsoleConnectionFilterSet', 'ConsolePortFilterSet', 'ConsolePortTemplateFilterSet', @@ -1321,11 +1322,45 @@ class PowerOutletFilterSet( fields = ['id', 'name', 'label', 'feed_leg', 'description', 'cable_end'] +class CommonInterfaceFilterSet(django_filters.FilterSet): + vlan_id = django_filters.CharFilter( + method='filter_vlan_id', + label=_('Assigned VLAN') + ) + vlan = django_filters.CharFilter( + method='filter_vlan', + label=_('Assigned VID') + ) + vrf_id = django_filters.ModelMultipleChoiceFilter( + field_name='vrf', + queryset=VRF.objects.all(), + label=_('VRF'), + ) + vrf = django_filters.ModelMultipleChoiceFilter( + field_name='vrf__rd', + queryset=VRF.objects.all(), + to_field_name='rd', + label=_('VRF (RD)'), + ) + l2vpn_id = django_filters.ModelMultipleChoiceFilter( + field_name='l2vpn_terminations__l2vpn', + queryset=L2VPN.objects.all(), + label=_('L2VPN (ID)'), + ) + l2vpn = django_filters.ModelMultipleChoiceFilter( + field_name='l2vpn_terminations__l2vpn__identifier', + queryset=L2VPN.objects.all(), + to_field_name='identifier', + label=_('L2VPN'), + ) + + class InterfaceFilterSet( ModularDeviceComponentFilterSet, NetBoxModelFilterSet, CabledObjectFilterSet, - PathEndpointFilterSet + PathEndpointFilterSet, + CommonInterfaceFilterSet ): # Override device and device_id filters from DeviceComponentFilterSet to match against any peer virtual chassis # members @@ -1370,14 +1405,6 @@ class InterfaceFilterSet( poe_type = django_filters.MultipleChoiceFilter( choices=InterfacePoETypeChoices ) - vlan_id = django_filters.CharFilter( - method='filter_vlan_id', - label=_('Assigned VLAN') - ) - vlan = django_filters.CharFilter( - method='filter_vlan', - label=_('Assigned VID') - ) type = django_filters.MultipleChoiceFilter( choices=InterfaceTypeChoices, null_value=None @@ -1388,17 +1415,6 @@ class InterfaceFilterSet( rf_channel = django_filters.MultipleChoiceFilter( choices=WirelessChannelChoices ) - vrf_id = django_filters.ModelMultipleChoiceFilter( - field_name='vrf', - queryset=VRF.objects.all(), - label=_('VRF'), - ) - vrf = django_filters.ModelMultipleChoiceFilter( - field_name='vrf__rd', - queryset=VRF.objects.all(), - to_field_name='rd', - label=_('VRF (RD)'), - ) vdc_id = django_filters.ModelMultipleChoiceFilter( field_name='vdcs', queryset=VirtualDeviceContext.objects.all(), @@ -1416,17 +1432,6 @@ class InterfaceFilterSet( to_field_name='name', label='Virtual Device Context', ) - l2vpn_id = django_filters.ModelMultipleChoiceFilter( - field_name='l2vpn_terminations__l2vpn', - queryset=L2VPN.objects.all(), - label=_('L2VPN (ID)'), - ) - l2vpn = django_filters.ModelMultipleChoiceFilter( - field_name='l2vpn_terminations__l2vpn__identifier', - queryset=L2VPN.objects.all(), - to_field_name='identifier', - label=_('L2VPN'), - ) class Meta: model = Interface diff --git a/netbox/virtualization/filtersets.py b/netbox/virtualization/filtersets.py index 8f656811a..cf716ca32 100644 --- a/netbox/virtualization/filtersets.py +++ b/netbox/virtualization/filtersets.py @@ -2,9 +2,9 @@ import django_filters from django.db.models import Q from django.utils.translation import gettext as _ +from dcim.filtersets import CommonInterfaceFilterSet from dcim.models import Device, DeviceRole, Platform, Region, Site, SiteGroup from extras.filtersets import LocalConfigContextFilterSet -from ipam.models import L2VPN, VRF from netbox.filtersets import OrganizationalModelFilterSet, NetBoxModelFilterSet from tenancy.filtersets import TenancyFilterSet, ContactModelFilterSet from utilities.filters import MultiValueCharFilter, MultiValueMACAddressFilter, TreeNodeMultipleChoiceFilter @@ -250,7 +250,7 @@ class VirtualMachineFilterSet( return queryset.exclude(params) -class VMInterfaceFilterSet(NetBoxModelFilterSet): +class VMInterfaceFilterSet(NetBoxModelFilterSet, CommonInterfaceFilterSet): cluster_id = django_filters.ModelMultipleChoiceFilter( field_name='virtual_machine__cluster', queryset=Cluster.objects.all(), @@ -286,28 +286,6 @@ class VMInterfaceFilterSet(NetBoxModelFilterSet): mac_address = MultiValueMACAddressFilter( label=_('MAC address'), ) - vrf_id = django_filters.ModelMultipleChoiceFilter( - field_name='vrf', - queryset=VRF.objects.all(), - label=_('VRF'), - ) - vrf = django_filters.ModelMultipleChoiceFilter( - field_name='vrf__rd', - queryset=VRF.objects.all(), - to_field_name='rd', - label=_('VRF (RD)'), - ) - l2vpn_id = django_filters.ModelMultipleChoiceFilter( - field_name='l2vpn_terminations__l2vpn', - queryset=L2VPN.objects.all(), - label=_('L2VPN (ID)'), - ) - l2vpn = django_filters.ModelMultipleChoiceFilter( - field_name='l2vpn_terminations__l2vpn__identifier', - queryset=L2VPN.objects.all(), - to_field_name='identifier', - label=_('L2VPN'), - ) class Meta: model = VMInterface From 9f71cf79e697d56c567d9c9ccb777b8bae247fda Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Fri, 7 Apr 2023 11:54:43 -0400 Subject: [PATCH 19/19] Changelog for #12007, #12118 --- docs/release-notes/version-3.4.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/release-notes/version-3.4.md b/docs/release-notes/version-3.4.md index eb25591db..5f086bca4 100644 --- a/docs/release-notes/version-3.4.md +++ b/docs/release-notes/version-3.4.md @@ -4,6 +4,7 @@ ### Enhancements +* [#12007](https://github.com/netbox-community/netbox/issues/12007) - Enable filtering of VM Interfaces by assigned VLAN * [#12095](https://github.com/netbox-community/netbox/issues/12095) - Specify UTF-8 encoding for default export template MIME type ### Bug Fixes @@ -14,6 +15,7 @@ * [#12084](https://github.com/netbox-community/netbox/issues/12084) - Fix exception when attempting to create a saved filter for applied filters * [#12087](https://github.com/netbox-community/netbox/issues/12087) - Fix bulk editing of many-to-many relationships * [#12117](https://github.com/netbox-community/netbox/issues/12117) - Hide clone button for objects with no clonable attributes +* [#12118](https://github.com/netbox-community/netbox/issues/12118) - Fix instantiation of nested inventory item templates when creating a device * [#12184](https://github.com/netbox-community/netbox/issues/12184) - Fix filtered bulk deletion for various models * [#12190](https://github.com/netbox-community/netbox/issues/12190) - Fix form layout for plugin textarea fields