From fb407e90768bfb5c224af0e72a1985198e65a57f Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Wed, 30 Nov 2022 16:18:03 -0500 Subject: [PATCH 01/24] PRVB --- docs/release-notes/version-3.3.md | 4 ++++ netbox/netbox/settings.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-3.3.md b/docs/release-notes/version-3.3.md index 6de56fd31..2d62fb5bd 100644 --- a/docs/release-notes/version-3.3.md +++ b/docs/release-notes/version-3.3.md @@ -1,5 +1,9 @@ # NetBox v3.3 +## v3.3.10 (FUTURE) + +--- + ## v3.3.9 (2022-11-30) ### Enhancements diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 6ce4148a2..773c8753f 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -29,7 +29,7 @@ django.utils.encoding.force_text = force_str # Environment setup # -VERSION = '3.3.9' +VERSION = '3.3.10-dev' # Hostname HOSTNAME = platform.node() From 826a1714c37b94ca26f23936f120cb19981815b7 Mon Sep 17 00:00:00 2001 From: Arthur Date: Thu, 1 Dec 2022 08:52:16 -0800 Subject: [PATCH 02/24] 11041 return power percentage with 1 decimal place --- netbox/utilities/templatetags/helpers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/netbox/utilities/templatetags/helpers.py b/netbox/utilities/templatetags/helpers.py index 139562355..18abe86b1 100644 --- a/netbox/utilities/templatetags/helpers.py +++ b/netbox/utilities/templatetags/helpers.py @@ -138,7 +138,8 @@ def percentage(x, y): """ if x is None or y is None: return None - return round(x / y * 100) + + return round(x / y * 100, 1) @register.filter() From ee03f3d5846f9342782553245a6750e50eab33e4 Mon Sep 17 00:00:00 2001 From: PieterL75 <74899468+PieterL75@users.noreply.github.com> Date: Fri, 2 Dec 2022 15:27:47 +0100 Subject: [PATCH 03/24] 10748 Add 'Provider' to the circuit termination edit/view (#10939) * Show the Provider of the NetworkProvider * Clean up form fields Co-authored-by: Pieter Lambrecht Co-authored-by: jeremystretch --- netbox/circuits/forms/models.py | 16 ++++++++++++++-- .../circuits/circuittermination_edit.html | 1 + .../circuits/inc/circuit_termination.html | 2 +- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/netbox/circuits/forms/models.py b/netbox/circuits/forms/models.py index aebdce10e..7dd85aef9 100644 --- a/netbox/circuits/forms/models.py +++ b/netbox/circuits/forms/models.py @@ -158,16 +158,28 @@ class CircuitTerminationForm(NetBoxModelForm): }, required=False ) + provider_network_provider = DynamicModelChoiceField( + queryset=Provider.objects.all(), + required=False, + label='Provider', + initial_params={ + 'networks': 'provider_network' + } + ) provider_network = DynamicModelChoiceField( queryset=ProviderNetwork.objects.all(), + query_params={ + 'provider_id': '$provider_network_provider', + }, required=False ) class Meta: model = CircuitTermination fields = [ - 'provider', 'circuit', 'term_side', 'region', 'site_group', 'site', 'provider_network', 'mark_connected', - 'port_speed', 'upstream_speed', 'xconnect_id', 'pp_info', 'description', 'tags', + 'provider', 'circuit', 'term_side', 'region', 'site_group', 'site', 'provider_network_provider', + 'provider_network', 'mark_connected', 'port_speed', 'upstream_speed', 'xconnect_id', 'pp_info', + 'description', 'tags', ] help_texts = { 'port_speed': "Physical circuit speed", diff --git a/netbox/templates/circuits/circuittermination_edit.html b/netbox/templates/circuits/circuittermination_edit.html index 5196eddf2..f171ecc1b 100644 --- a/netbox/templates/circuits/circuittermination_edit.html +++ b/netbox/templates/circuits/circuittermination_edit.html @@ -32,6 +32,7 @@ {% render_field form.site %}
+ {% render_field form.provider_network_provider %} {% render_field form.provider_network %}
diff --git a/netbox/templates/circuits/inc/circuit_termination.html b/netbox/templates/circuits/inc/circuit_termination.html index f4e0ea6ca..160ff940f 100644 --- a/netbox/templates/circuits/inc/circuit_termination.html +++ b/netbox/templates/circuits/inc/circuit_termination.html @@ -81,7 +81,7 @@ {% else %} Provider Network - {{ termination.provider_network|linkify }} + {{ termination.provider_network.provider|linkify }} / {{ termination.provider_network|linkify }} {% endif %} From db7590df1a797de143e9128d880df1a3553c255d Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Fri, 2 Dec 2022 09:30:44 -0500 Subject: [PATCH 04/24] Changelog for #10748, #11041 --- docs/release-notes/version-3.3.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/release-notes/version-3.3.md b/docs/release-notes/version-3.3.md index 2d62fb5bd..c35ac2a64 100644 --- a/docs/release-notes/version-3.3.md +++ b/docs/release-notes/version-3.3.md @@ -2,6 +2,14 @@ ## v3.3.10 (FUTURE) +### Enhancements + +* [#10748](https://github.com/netbox-community/netbox/issues/10748) - Add provider selection field for provider networks to circuit termination edit view + +### Bug Fixes + +* [#11041](https://github.com/netbox-community/netbox/issues/11041) - Correct power utilization percentage precision + --- ## v3.3.9 (2022-11-30) From d4d8d00d01d422adaa13cd1fbd0384ee39ea1d9f Mon Sep 17 00:00:00 2001 From: Renato Almeida de Oliveira Date: Sat, 3 Dec 2022 20:02:51 +0000 Subject: [PATCH 05/24] add distinct method to circuit_count --- netbox/dcim/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index e8a7c66bd..edc9a5ce8 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -335,7 +335,7 @@ class SiteView(generic.ObjectView): scope_id=instance.pk ).count(), 'vlan_count': VLAN.objects.restrict(request.user, 'view').filter(site=instance).count(), - 'circuit_count': Circuit.objects.restrict(request.user, 'view').filter(terminations__site=instance).count(), + 'circuit_count': Circuit.objects.restrict(request.user, 'view').filter(terminations__site=instance).distinct().count(), 'vm_count': VirtualMachine.objects.restrict(request.user, 'view').filter(cluster__site=instance).count(), } locations = Location.objects.add_related_count( From d2d60c06075bae3188bd6a6a28cc1aa15ff18c55 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 6 Dec 2022 15:40:59 -0500 Subject: [PATCH 06/24] Fixes #11087: Fix background color of bottom banner content --- docs/release-notes/version-3.3.md | 2 ++ netbox/templates/base/layout.html | 12 ++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/release-notes/version-3.3.md b/docs/release-notes/version-3.3.md index c35ac2a64..321cb28b9 100644 --- a/docs/release-notes/version-3.3.md +++ b/docs/release-notes/version-3.3.md @@ -9,6 +9,8 @@ ### Bug Fixes * [#11041](https://github.com/netbox-community/netbox/issues/11041) - Correct power utilization percentage precision +* [#11087](https://github.com/netbox-community/netbox/issues/11087) - Fix background color of bottom banner content +* [#11101](https://github.com/netbox-community/netbox/issues/11101) - Correct circuits count under site view --- diff --git a/netbox/templates/base/layout.html b/netbox/templates/base/layout.html index dd0412eac..e4db1a1f5 100644 --- a/netbox/templates/base/layout.html +++ b/netbox/templates/base/layout.html @@ -103,14 +103,14 @@ Blocks: {% block content %}{% endblock %} {% endblock %} + {# Bottom banner #} + {% if config.BANNER_BOTTOM %} +
+ {{ config.BANNER_BOTTOM|safe }} +
+ {% endif %} - {% if config.BANNER_BOTTOM %} -
- {{ config.BANNER_BOTTOM|safe }} -
- {% endif %} - {# BS5 pop-up modals #} {% block modals %}{% endblock %} From 780997a568e79a3b86aa0ad19ef40bde370ee42f Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 6 Dec 2022 15:48:22 -0500 Subject: [PATCH 07/24] Closes #11119: Enable filtering L2VPNs by slug --- docs/release-notes/version-3.3.md | 1 + netbox/ipam/filtersets.py | 2 +- netbox/ipam/tests/test_filtersets.py | 4 ++++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-3.3.md b/docs/release-notes/version-3.3.md index 321cb28b9..f5bd53b80 100644 --- a/docs/release-notes/version-3.3.md +++ b/docs/release-notes/version-3.3.md @@ -5,6 +5,7 @@ ### Enhancements * [#10748](https://github.com/netbox-community/netbox/issues/10748) - Add provider selection field for provider networks to circuit termination edit view +* [#11119](https://github.com/netbox-community/netbox/issues/11119) - Enable filtering L2VPNs by slug ### Bug Fixes diff --git a/netbox/ipam/filtersets.py b/netbox/ipam/filtersets.py index 3c0ab1ac8..2634dd725 100644 --- a/netbox/ipam/filtersets.py +++ b/netbox/ipam/filtersets.py @@ -960,7 +960,7 @@ class L2VPNFilterSet(NetBoxModelFilterSet, TenancyFilterSet): class Meta: model = L2VPN - fields = ['id', 'identifier', 'name', 'type', 'description'] + fields = ['id', 'identifier', 'name', 'slug', 'type', 'description'] def search(self, queryset, name, value): if not value.strip(): diff --git a/netbox/ipam/tests/test_filtersets.py b/netbox/ipam/tests/test_filtersets.py index 5c4113786..d67994fc1 100644 --- a/netbox/ipam/tests/test_filtersets.py +++ b/netbox/ipam/tests/test_filtersets.py @@ -1501,6 +1501,10 @@ class L2VPNTestCase(TestCase, ChangeLoggedFilterSetTests): params = {'name': ['L2VPN 1', 'L2VPN 2']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + def test_slug(self): + params = {'slug': ['l2vpn-1', 'l2vpn-2']} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + def test_identifier(self): params = {'identifier': ['65001', '65002']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) From 0cacac82ee4cb9fb51eb52c931f77b16efc5a7ef Mon Sep 17 00:00:00 2001 From: kkthxbye-code Date: Thu, 8 Dec 2022 12:02:10 +0100 Subject: [PATCH 08/24] Disable sorting by object_repr on ObjectChangeTable --- netbox/extras/tables/tables.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/netbox/extras/tables/tables.py b/netbox/extras/tables/tables.py index 1df5c9487..22da5f52a 100644 --- a/netbox/extras/tables/tables.py +++ b/netbox/extras/tables/tables.py @@ -195,7 +195,8 @@ class ObjectChangeTable(NetBoxTable): object_repr = tables.TemplateColumn( accessor=tables.A('changed_object'), template_code=OBJECTCHANGE_OBJECT, - verbose_name='Object' + verbose_name='Object', + orderable=False ) request_id = tables.TemplateColumn( template_code=OBJECTCHANGE_REQUEST_ID, From 35596ddcbcce6b7287114b6e326d4c50be90151b Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Thu, 8 Dec 2022 08:56:57 -0500 Subject: [PATCH 09/24] Closes #10806: Add warning to run deactivate prior to upgrade script --- docs/installation/3-netbox.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/installation/3-netbox.md b/docs/installation/3-netbox.md index 72c054e5b..a2a2d9df3 100644 --- a/docs/installation/3-netbox.md +++ b/docs/installation/3-netbox.md @@ -225,6 +225,9 @@ Once NetBox has been configured, we're ready to proceed with the actual installa * Builds the documentation locally (for offline use) * Aggregate static resource files on disk +!!! warning + If you still have a Python virtual environment active from a previous installation step, disable it now by running the `deactivate` command. This will avoid errors on systems where `sudo` has been configured to preserve the user's current environment. + ```no-highlight sudo /opt/netbox/upgrade.sh ``` From ab9c253310dc36dca2aa685dc97cf9d43a518d5e Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Thu, 8 Dec 2022 08:59:26 -0500 Subject: [PATCH 10/24] Fixes #11128: Disable ordering changelog table by object to avoid exception --- docs/release-notes/version-3.3.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes/version-3.3.md b/docs/release-notes/version-3.3.md index f5bd53b80..4dd717f9b 100644 --- a/docs/release-notes/version-3.3.md +++ b/docs/release-notes/version-3.3.md @@ -12,6 +12,7 @@ * [#11041](https://github.com/netbox-community/netbox/issues/11041) - Correct power utilization percentage precision * [#11087](https://github.com/netbox-community/netbox/issues/11087) - Fix background color of bottom banner content * [#11101](https://github.com/netbox-community/netbox/issues/11101) - Correct circuits count under site view +* [#11128](https://github.com/netbox-community/netbox/issues/11128) - Disable ordering changelog table by object to avoid exception --- From 407365888a975f29c64a46dcf31cd387c3f2153f Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Fri, 9 Dec 2022 16:00:11 -0500 Subject: [PATCH 11/24] Closes #11089: Permit whitespace in MAC addresses --- docs/release-notes/version-3.3.md | 1 + netbox/dcim/fields.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/docs/release-notes/version-3.3.md b/docs/release-notes/version-3.3.md index 4dd717f9b..0572b7454 100644 --- a/docs/release-notes/version-3.3.md +++ b/docs/release-notes/version-3.3.md @@ -5,6 +5,7 @@ ### Enhancements * [#10748](https://github.com/netbox-community/netbox/issues/10748) - Add provider selection field for provider networks to circuit termination edit view +* [#11089](https://github.com/netbox-community/netbox/issues/11089) - Permit whitespace in MAC addresses * [#11119](https://github.com/netbox-community/netbox/issues/11119) - Enable filtering L2VPNs by slug ### Bug Fixes diff --git a/netbox/dcim/fields.py b/netbox/dcim/fields.py index d3afe5c08..4a2755be9 100644 --- a/netbox/dcim/fields.py +++ b/netbox/dcim/fields.py @@ -55,6 +55,8 @@ class MACAddressField(models.Field): def to_python(self, value): if value is None: return value + if type(value) is str: + value = value.replace(' ', '') try: return EUI(value, version=48, dialect=mac_unix_expanded_uppercase) except AddrFormatError: From 8486d47d1796c4c1dcfcf3e64390ccde9efd6e8a Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Fri, 9 Dec 2022 16:04:46 -0500 Subject: [PATCH 12/24] Fixes #11142: Correct available choices for status under IP range filter form --- docs/release-notes/version-3.3.md | 1 + netbox/ipam/forms/filtersets.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-3.3.md b/docs/release-notes/version-3.3.md index 0572b7454..75710ae8f 100644 --- a/docs/release-notes/version-3.3.md +++ b/docs/release-notes/version-3.3.md @@ -14,6 +14,7 @@ * [#11087](https://github.com/netbox-community/netbox/issues/11087) - Fix background color of bottom banner content * [#11101](https://github.com/netbox-community/netbox/issues/11101) - Correct circuits count under site view * [#11128](https://github.com/netbox-community/netbox/issues/11128) - Disable ordering changelog table by object to avoid exception +* [#11142](https://github.com/netbox-community/netbox/issues/11142) - Correct available choices for status under IP range filter form --- diff --git a/netbox/ipam/forms/filtersets.py b/netbox/ipam/forms/filtersets.py index 9566e69a9..34b811e94 100644 --- a/netbox/ipam/forms/filtersets.py +++ b/netbox/ipam/forms/filtersets.py @@ -250,7 +250,7 @@ class IPRangeFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): null_option='Global' ) status = MultipleChoiceField( - choices=PrefixStatusChoices, + choices=IPRangeStatusChoices, required=False ) role_id = DynamicModelMultipleChoiceField( From 1e0b0246094cfc0901ee96f3a21670b299a6c356 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Fri, 9 Dec 2022 16:35:37 -0500 Subject: [PATCH 13/24] Closes #10516: Add vertical frame & cabinet rack types --- docs/release-notes/version-3.3.md | 1 + netbox/dcim/choices.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/docs/release-notes/version-3.3.md b/docs/release-notes/version-3.3.md index 75710ae8f..3f05c7977 100644 --- a/docs/release-notes/version-3.3.md +++ b/docs/release-notes/version-3.3.md @@ -4,6 +4,7 @@ ### Enhancements +* [#10516](https://github.com/netbox-community/netbox/issues/10516) - Add vertical frame & cabinet rack types * [#10748](https://github.com/netbox-community/netbox/issues/10748) - Add provider selection field for provider networks to circuit termination edit view * [#11089](https://github.com/netbox-community/netbox/issues/11089) - Permit whitespace in MAC addresses * [#11119](https://github.com/netbox-community/netbox/issues/11119) - Enable filtering L2VPNs by slug diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py index 14ddc9930..fdd0b025f 100644 --- a/netbox/dcim/choices.py +++ b/netbox/dcim/choices.py @@ -55,14 +55,18 @@ class RackTypeChoices(ChoiceSet): TYPE_4POST = '4-post-frame' TYPE_CABINET = '4-post-cabinet' TYPE_WALLFRAME = 'wall-frame' + TYPE_WALLFRAME_VERTICAL = 'wall-frame-vertical' TYPE_WALLCABINET = 'wall-cabinet' + TYPE_WALLCABINET_VERTICAL = 'wall-cabinet-vertical' CHOICES = ( (TYPE_2POST, '2-post frame'), (TYPE_4POST, '4-post frame'), (TYPE_CABINET, '4-post cabinet'), (TYPE_WALLFRAME, 'Wall-mounted frame'), + (TYPE_WALLFRAME_VERTICAL, 'Wall-mounted frame (vertical)'), (TYPE_WALLCABINET, 'Wall-mounted cabinet'), + (TYPE_WALLCABINET_VERTICAL, 'Wall-mounted cabinet (vertical)'), ) From 860805ba8245f5d5a4401dca041b21678b55f060 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Fri, 9 Dec 2022 17:08:07 -0500 Subject: [PATCH 14/24] Closes #10255: Introduce LOGOUT_REDIRECT_URL config parameter to control redirection of user after logout --- docs/configuration/security.md | 8 ++++++++ docs/release-notes/version-3.3.md | 1 + netbox/netbox/configuration_example.py | 3 +++ netbox/netbox/settings.py | 1 + netbox/users/views.py | 4 ++-- 5 files changed, 15 insertions(+), 2 deletions(-) diff --git a/docs/configuration/security.md b/docs/configuration/security.md index 6aa363b1a..41f7cc71a 100644 --- a/docs/configuration/security.md +++ b/docs/configuration/security.md @@ -129,6 +129,14 @@ The lifetime (in seconds) of the authentication cookie issued to a NetBox user u --- +## LOGOUT_REDIRECT_URL + +Default: `'home'` + +The view name or URL to which a user is redirected after logging out. + +--- + ## SESSION_COOKIE_NAME Default: `sessionid` diff --git a/docs/release-notes/version-3.3.md b/docs/release-notes/version-3.3.md index 3f05c7977..c86317d26 100644 --- a/docs/release-notes/version-3.3.md +++ b/docs/release-notes/version-3.3.md @@ -4,6 +4,7 @@ ### Enhancements +* [#10255](https://github.com/netbox-community/netbox/issues/10255) - Introduce `LOGOUT_REDIRECT_URL` config parameter to control redirection of user after logout * [#10516](https://github.com/netbox-community/netbox/issues/10516) - Add vertical frame & cabinet rack types * [#10748](https://github.com/netbox-community/netbox/issues/10748) - Add provider selection field for provider networks to circuit termination edit view * [#11089](https://github.com/netbox-community/netbox/issues/11089) - Permit whitespace in MAC addresses diff --git a/netbox/netbox/configuration_example.py b/netbox/netbox/configuration_example.py index ad0dcc7c3..2601775e6 100644 --- a/netbox/netbox/configuration_example.py +++ b/netbox/netbox/configuration_example.py @@ -149,6 +149,9 @@ LOGIN_REQUIRED = False # re-authenticate. (Default: 1209600 [14 days]) LOGIN_TIMEOUT = None +# The view name or URL to which users are redirected after logging out. +LOGOUT_REDIRECT_URL = 'home' + # The file path where uploaded media such as image attachments are stored. A trailing slash is not needed. Note that # the default value of this setting is derived from the installed location. # MEDIA_ROOT = '/opt/netbox/netbox/media' diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 773c8753f..663da3b32 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -102,6 +102,7 @@ LOGGING = getattr(configuration, 'LOGGING', {}) LOGIN_PERSISTENCE = getattr(configuration, 'LOGIN_PERSISTENCE', False) LOGIN_REQUIRED = getattr(configuration, 'LOGIN_REQUIRED', False) LOGIN_TIMEOUT = getattr(configuration, 'LOGIN_TIMEOUT', None) +LOGOUT_REDIRECT_URL = getattr(configuration, 'LOGOUT_REDIRECT_URL', 'home') MEDIA_ROOT = getattr(configuration, 'MEDIA_ROOT', os.path.join(BASE_DIR, 'media')).rstrip('/') METRICS_ENABLED = getattr(configuration, 'METRICS_ENABLED', False) PLUGINS = getattr(configuration, 'PLUGINS', []) diff --git a/netbox/users/views.py b/netbox/users/views.py index c688d6b4f..a02f1ae2c 100644 --- a/netbox/users/views.py +++ b/netbox/users/views.py @@ -7,7 +7,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.models import update_last_login from django.contrib.auth.signals import user_logged_in from django.http import HttpResponseRedirect -from django.shortcuts import get_object_or_404, redirect, render +from django.shortcuts import get_object_or_404, redirect, render, resolve_url from django.urls import reverse from django.utils.decorators import method_decorator from django.utils.http import url_has_allowed_host_and_scheme, urlencode @@ -142,7 +142,7 @@ class LogoutView(View): messages.info(request, "You have logged out.") # Delete session key cookie (if set) upon logout - response = HttpResponseRedirect(reverse('home')) + response = HttpResponseRedirect(resolve_url(settings.LOGOUT_REDIRECT_URL)) response.delete_cookie('session_key') return response From 41f631b65b6c9df3630a3937fdaea1447eafce81 Mon Sep 17 00:00:00 2001 From: kkthxbye-code Date: Sun, 11 Dec 2022 13:53:04 +0100 Subject: [PATCH 15/24] Allow re-assigning inventoryitems to other devices --- netbox/dcim/forms/models.py | 7 +++++++ netbox/dcim/models/device_components.py | 17 +++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/netbox/dcim/forms/models.py b/netbox/dcim/forms/models.py index da50b8f2a..579851651 100644 --- a/netbox/dcim/forms/models.py +++ b/netbox/dcim/forms/models.py @@ -1610,6 +1610,13 @@ class InventoryItemForm(DeviceComponentForm): ('Hardware', ('manufacturer', 'part_id', 'serial', 'asset_tag')), ) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + # Specifically allow editing the device of IntentoryItems + if self.instance.pk: + self.fields['device'].disabled = False + class Meta: model = InventoryItem fields = [ diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index ce768e439..b425d8dbd 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -1153,3 +1153,20 @@ class InventoryItem(MPTTModel, ComponentModel): raise ValidationError({ "parent": "Cannot assign self as parent." }) + + # Validation for moving InventoryItems + if self.pk: + # Cannot move an InventoryItem to another device if it has a parent + if self.parent and self.parent.device != self.device: + raise ValidationError({ + "parent": "Parent inventory item does not belong to the same device." + }) + + # Prevent moving InventoryItems with children + first_child = self.get_children().first() + if first_child and first_child.device != self.device: + raise ValidationError("Cannot move an InventoryItem with dependent children") + + # When moving an InventoryItem to another device, remove any associated component + if self.component and self.component.device != self.device: + self.component = None \ No newline at end of file From a57378e780910b6aa30a900cdd9f7f80e7599ba6 Mon Sep 17 00:00:00 2001 From: kkthxbye-code Date: Sun, 11 Dec 2022 14:00:21 +0100 Subject: [PATCH 16/24] Add missing newline and change wording of InventoryItem validation --- netbox/dcim/models/device_components.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index b425d8dbd..dc1ae6e35 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -1165,8 +1165,8 @@ class InventoryItem(MPTTModel, ComponentModel): # Prevent moving InventoryItems with children first_child = self.get_children().first() if first_child and first_child.device != self.device: - raise ValidationError("Cannot move an InventoryItem with dependent children") + raise ValidationError("Cannot move an inventory item with dependent children") # When moving an InventoryItem to another device, remove any associated component if self.component and self.component.device != self.device: - self.component = None \ No newline at end of file + self.component = None From 9bb9ac3dec72e3d95127c52e6e04459786b7bda4 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Tue, 13 Dec 2022 06:22:57 -0800 Subject: [PATCH 17/24] 11077 use formatting for custom field date (#11143) * 11077 use formatting for custom field date * Apply configured date format to column render() method Co-authored-by: jeremystretch --- netbox/netbox/tables/columns.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/netbox/netbox/tables/columns.py b/netbox/netbox/tables/columns.py index 81fdaa20f..0987b8821 100644 --- a/netbox/netbox/tables/columns.py +++ b/netbox/netbox/tables/columns.py @@ -7,6 +7,7 @@ from django.contrib.auth.models import AnonymousUser from django.db.models import DateField, DateTimeField from django.template import Context, Template from django.urls import reverse +from django.utils.dateparse import parse_date from django.utils.encoding import escape_uri_path from django.utils.html import escape from django.utils.formats import date_format @@ -50,6 +51,10 @@ class DateColumn(tables.DateColumn): tables and null when exporting data. It is registered in the tables library to use this class instead of the default, making this behavior consistent in all fields of type DateField. """ + def render(self, value): + if value: + return date_format(value, format="SHORT_DATE_FORMAT") + def value(self, value): return value @@ -455,6 +460,8 @@ class CustomFieldColumn(tables.Column): )) if self.customfield.type == CustomFieldTypeChoices.TYPE_LONGTEXT and value: return render_markdown(value) + if self.customfield.type == CustomFieldTypeChoices.TYPE_DATE and value: + return date_format(parse_date(value), format="SHORT_DATE_FORMAT") if value is not None: obj = self.customfield.deserialize(value) return mark_safe(self._linkify_item(obj)) From b3693099dc106502d0a020b640eb58295e2fc806 Mon Sep 17 00:00:00 2001 From: sleepinggenius2 Date: Tue, 13 Dec 2022 11:33:09 -0500 Subject: [PATCH 18/24] Adds replication and adoption for module import (#9498) * Adds replication and adoption for module import * Moves common Module form clean logic to new class * Adds tests for replication and adoption for module import * Fix test Co-authored-by: jeremystretch --- netbox/dcim/forms/bulk_import.py | 21 +++++++- netbox/dcim/forms/common.py | 58 ++++++++++++++++++++ netbox/dcim/forms/models.py | 66 +---------------------- netbox/dcim/tests/test_views.py | 90 ++++++++++++++++++++++++++++++++ 4 files changed, 169 insertions(+), 66 deletions(-) diff --git a/netbox/dcim/forms/bulk_import.py b/netbox/dcim/forms/bulk_import.py index f0fd9bf86..5c6cbb0b0 100644 --- a/netbox/dcim/forms/bulk_import.py +++ b/netbox/dcim/forms/bulk_import.py @@ -13,6 +13,7 @@ from tenancy.models import Tenant from utilities.forms import CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, CSVTypedChoiceField, SlugField from virtualization.models import Cluster from wireless.choices import WirelessRoleChoices +from .common import ModuleCommonForm __all__ = ( 'CableCSVForm', @@ -407,7 +408,7 @@ class DeviceCSVForm(BaseDeviceCSVForm): self.fields['rack'].queryset = self.fields['rack'].queryset.filter(**params) -class ModuleCSVForm(NetBoxModelCSVForm): +class ModuleCSVForm(ModuleCommonForm, NetBoxModelCSVForm): device = CSVModelChoiceField( queryset=Device.objects.all(), to_field_name='name' @@ -420,11 +421,20 @@ class ModuleCSVForm(NetBoxModelCSVForm): queryset=ModuleType.objects.all(), to_field_name='model' ) + replicate_components = forms.BooleanField( + required=False, + help_text="Automatically populate components associated with this module type (default: true)" + ) + adopt_components = forms.BooleanField( + required=False, + help_text="Adopt already existing components" + ) class Meta: model = Module fields = ( - 'device', 'module_bay', 'module_type', 'serial', 'asset_tag', 'comments', + 'device', 'module_bay', 'module_type', 'serial', 'asset_tag', 'replicate_components', + 'adopt_components', 'comments', ) def __init__(self, data=None, *args, **kwargs): @@ -435,6 +445,13 @@ class ModuleCSVForm(NetBoxModelCSVForm): params = {f"device__{self.fields['device'].to_field_name}": data.get('device')} self.fields['module_bay'].queryset = self.fields['module_bay'].queryset.filter(**params) + def clean_replicate_components(self): + # Make sure replicate_components is True when it's not included in the uploaded data + if 'replicate_components' not in self.data: + return True + else: + return self.cleaned_data['replicate_components'] + class ChildDeviceCSVForm(BaseDeviceCSVForm): parent = CSVModelChoiceField( diff --git a/netbox/dcim/forms/common.py b/netbox/dcim/forms/common.py index f484b48e1..bfe09ab71 100644 --- a/netbox/dcim/forms/common.py +++ b/netbox/dcim/forms/common.py @@ -5,6 +5,7 @@ from dcim.constants import * __all__ = ( 'InterfaceCommonForm', + 'ModuleCommonForm' ) @@ -47,3 +48,60 @@ class InterfaceCommonForm(forms.Form): 'tagged_vlans': f"The tagged VLANs ({', '.join(invalid_vlans)}) must belong to the same site as " f"the interface's parent device/VM, or they must be global" }) + + +class ModuleCommonForm(forms.Form): + def clean(self): + super().clean() + + replicate_components = self.cleaned_data.get("replicate_components") + adopt_components = self.cleaned_data.get("adopt_components") + device = self.cleaned_data['device'] + module_type = self.cleaned_data['module_type'] + module_bay = self.cleaned_data['module_bay'] + + if adopt_components: + self.instance._adopt_components = True + + # Bail out if we are not installing a new module or if we are not replicating components + if self.instance.pk or not replicate_components: + self.instance._disable_replication = True + return + + for templates, component_attribute in [ + ("consoleporttemplates", "consoleports"), + ("consoleserverporttemplates", "consoleserverports"), + ("interfacetemplates", "interfaces"), + ("powerporttemplates", "powerports"), + ("poweroutlettemplates", "poweroutlets"), + ("rearporttemplates", "rearports"), + ("frontporttemplates", "frontports") + ]: + # Prefetch installed components + installed_components = { + component.name: component for component in getattr(device, component_attribute).all() + } + + # Get the templates for the module type. + for template in getattr(module_type, templates).all(): + # Installing modules with placeholders require that the bay has a position value + if MODULE_TOKEN in template.name and not module_bay.position: + raise forms.ValidationError( + "Cannot install module with placeholder values in a module bay with no position defined" + ) + + resolved_name = template.name.replace(MODULE_TOKEN, module_bay.position) + existing_item = installed_components.get(resolved_name) + + # It is not possible to adopt components already belonging to a module + if adopt_components and existing_item and existing_item.module: + raise forms.ValidationError( + f"Cannot adopt {template.component_model.__name__} '{resolved_name}' as it already belongs " + f"to a module" + ) + + # If we are not adopting components we error if the component exists + if not adopt_components and resolved_name in installed_components: + raise forms.ValidationError( + f"{template.component_model.__name__} - {resolved_name} already exists" + ) diff --git a/netbox/dcim/forms/models.py b/netbox/dcim/forms/models.py index 579851651..620806397 100644 --- a/netbox/dcim/forms/models.py +++ b/netbox/dcim/forms/models.py @@ -17,7 +17,7 @@ from utilities.forms import ( ) from virtualization.models import Cluster, ClusterGroup from wireless.models import WirelessLAN, WirelessLANGroup -from .common import InterfaceCommonForm +from .common import InterfaceCommonForm, ModuleCommonForm __all__ = ( 'CableForm', @@ -657,7 +657,7 @@ class DeviceForm(TenancyForm, NetBoxModelForm): self.fields['position'].widget.choices = [(position, f'U{position}')] -class ModuleForm(NetBoxModelForm): +class ModuleForm(ModuleCommonForm, NetBoxModelForm): device = DynamicModelChoiceField( queryset=Device.objects.all(), initial_params={ @@ -722,68 +722,6 @@ class ModuleForm(NetBoxModelForm): self.fields['adopt_components'].initial = False self.fields['adopt_components'].disabled = True - def save(self, *args, **kwargs): - - # If replicate_components is False, disable automatic component replication on the instance - if self.instance.pk or not self.cleaned_data['replicate_components']: - self.instance._disable_replication = True - - if self.cleaned_data['adopt_components']: - self.instance._adopt_components = True - - return super().save(*args, **kwargs) - - def clean(self): - super().clean() - - replicate_components = self.cleaned_data.get("replicate_components") - adopt_components = self.cleaned_data.get("adopt_components") - device = self.cleaned_data['device'] - module_type = self.cleaned_data['module_type'] - module_bay = self.cleaned_data['module_bay'] - - # Bail out if we are not installing a new module or if we are not replicating components - if self.instance.pk or not replicate_components: - return - - for templates, component_attribute in [ - ("consoleporttemplates", "consoleports"), - ("consoleserverporttemplates", "consoleserverports"), - ("interfacetemplates", "interfaces"), - ("powerporttemplates", "powerports"), - ("poweroutlettemplates", "poweroutlets"), - ("rearporttemplates", "rearports"), - ("frontporttemplates", "frontports") - ]: - # Prefetch installed components - installed_components = { - component.name: component for component in getattr(device, component_attribute).all() - } - - # Get the templates for the module type. - for template in getattr(module_type, templates).all(): - # Installing modules with placeholders require that the bay has a position value - if MODULE_TOKEN in template.name and not module_bay.position: - raise forms.ValidationError( - "Cannot install module with placeholder values in a module bay with no position defined" - ) - - resolved_name = template.name.replace(MODULE_TOKEN, module_bay.position) - existing_item = installed_components.get(resolved_name) - - # It is not possible to adopt components already belonging to a module - if adopt_components and existing_item and existing_item.module: - raise forms.ValidationError( - f"Cannot adopt {template.component_model.__name__} '{resolved_name}' as it already belongs " - f"to a module" - ) - - # If we are not adopting components we error if the component exists - if not adopt_components and resolved_name in installed_components: - raise forms.ValidationError( - f"{template.component_model.__name__} - {resolved_name} already exists" - ) - class CableForm(TenancyForm, NetBoxModelForm): diff --git a/netbox/dcim/tests/test_views.py b/netbox/dcim/tests/test_views.py index db3495521..16540af87 100644 --- a/netbox/dcim/tests/test_views.py +++ b/netbox/dcim/tests/test_views.py @@ -1848,6 +1848,53 @@ class ModuleTestCase( self.assertHttpStatus(self.client.post(**request), 302) self.assertEqual(Interface.objects.filter(device=device).count(), 5) + @override_settings(EXEMPT_VIEW_PERMISSIONS=['*']) + def test_module_bulk_replication(self): + self.add_permissions('dcim.add_module') + + # Add 5 InterfaceTemplates to a ModuleType + module_type = ModuleType.objects.first() + interface_templates = [ + InterfaceTemplate(module_type=module_type, name=f'Interface {i}') for i in range(1, 6) + ] + InterfaceTemplate.objects.bulk_create(interface_templates) + + form_data = self.form_data.copy() + device = Device.objects.get(pk=form_data['device']) + + # Create a module *without* replicating components + module_bay = ModuleBay.objects.get(pk=form_data['module_bay']) + csv_data = [ + "device,module_bay,module_type,replicate_components", + f"{device.name},{module_bay.name},{module_type.model},false" + ] + request = { + 'path': self._get_url('import'), + 'data': { + 'csv': '\n'.join(csv_data), + } + } + + initial_count = self._get_queryset().count() + self.assertHttpStatus(self.client.post(**request), 200) + self.assertEqual(self._get_queryset().count(), initial_count + len(csv_data) - 1) + self.assertEqual(Interface.objects.filter(device=device).count(), 0) + + # Create a second module (in the next bay) with replicated components + module_bay = ModuleBay.objects.get(pk=(form_data['module_bay'] + 1)) + csv_data[1] = f"{device.name},{module_bay.name},{module_type.model},true" + request = { + 'path': self._get_url('import'), + 'data': { + 'csv': '\n'.join(csv_data), + } + } + + initial_count = self._get_queryset().count() + self.assertHttpStatus(self.client.post(**request), 200) + self.assertEqual(self._get_queryset().count(), initial_count + len(csv_data) - 1) + self.assertEqual(Interface.objects.filter(device=device).count(), 5) + @override_settings(EXEMPT_VIEW_PERMISSIONS=['*']) def test_module_component_adoption(self): self.add_permissions('dcim.add_module') @@ -1885,6 +1932,49 @@ class ModuleTestCase( # Check that the Interface now has a module self.assertIsNotNone(interface.module) + @override_settings(EXEMPT_VIEW_PERMISSIONS=['*']) + def test_module_bulk_adoption(self): + self.add_permissions('dcim.add_module') + + interface_name = "Interface-1" + + # Add an interface to the ModuleType + module_type = ModuleType.objects.first() + InterfaceTemplate(module_type=module_type, name=interface_name).save() + + form_data = self.form_data.copy() + device = Device.objects.get(pk=form_data['device']) + + # Create an interface to be adopted + interface = Interface(device=device, name=interface_name, type=InterfaceTypeChoices.TYPE_10GE_FIXED) + interface.save() + + # Ensure that interface is created with no module + self.assertIsNone(interface.module) + + # Create a module with adopted components + module_bay = ModuleBay.objects.get(device=device, name='Module Bay 4') + csv_data = [ + "device,module_bay,module_type,replicate_components,adopt_components", + f"{device.name},{module_bay.name},{module_type.model},false,true" + ] + request = { + 'path': self._get_url('import'), + 'data': { + 'csv': '\n'.join(csv_data), + } + } + + initial_count = self._get_queryset().count() + self.assertHttpStatus(self.client.post(**request), 200) + self.assertEqual(self._get_queryset().count(), initial_count + len(csv_data) - 1) + + # Re-retrieve interface to get new module id + interface.refresh_from_db() + + # Check that the Interface now has a module + self.assertIsNotNone(interface.module) + class ConsolePortTestCase(ViewTestCases.DeviceComponentViewTestCase): model = ConsolePort From 68faab81964eb56c15229d3dd7f9b3aeee209b54 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Tue, 13 Dec 2022 13:22:28 -0500 Subject: [PATCH 19/24] Fixes #11168: Honor RQ_DEFAULT_TIMEOUT config parameter when using Redis Sentinel --- docs/release-notes/version-3.3.md | 1 + netbox/netbox/settings.py | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/release-notes/version-3.3.md b/docs/release-notes/version-3.3.md index c86317d26..ee3e7cddf 100644 --- a/docs/release-notes/version-3.3.md +++ b/docs/release-notes/version-3.3.md @@ -17,6 +17,7 @@ * [#11101](https://github.com/netbox-community/netbox/issues/11101) - Correct circuits count under site view * [#11128](https://github.com/netbox-community/netbox/issues/11128) - Disable ordering changelog table by object to avoid exception * [#11142](https://github.com/netbox-community/netbox/issues/11142) - Correct available choices for status under IP range filter form +* [#11168](https://github.com/netbox-community/netbox/issues/11168) - Honor `RQ_DEFAULT_TIMEOUT` config parameter when using Redis Sentinel --- diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 663da3b32..5c0f486f3 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -626,8 +626,6 @@ if TASKS_REDIS_USING_SENTINEL: RQ_PARAMS = { 'SENTINELS': TASKS_REDIS_SENTINELS, 'MASTER_NAME': TASKS_REDIS_SENTINEL_SERVICE, - 'DB': TASKS_REDIS_DATABASE, - 'PASSWORD': TASKS_REDIS_PASSWORD, 'SOCKET_TIMEOUT': None, 'CONNECTION_KWARGS': { 'socket_connect_timeout': TASKS_REDIS_SENTINEL_TIMEOUT @@ -637,12 +635,14 @@ else: RQ_PARAMS = { 'HOST': TASKS_REDIS_HOST, 'PORT': TASKS_REDIS_PORT, - 'DB': TASKS_REDIS_DATABASE, - 'PASSWORD': TASKS_REDIS_PASSWORD, 'SSL': TASKS_REDIS_SSL, 'SSL_CERT_REQS': None if TASKS_REDIS_SKIP_TLS_VERIFY else 'required', - 'DEFAULT_TIMEOUT': RQ_DEFAULT_TIMEOUT, } +RQ_PARAMS.update({ + 'DB': TASKS_REDIS_DATABASE, + 'PASSWORD': TASKS_REDIS_PASSWORD, + 'DEFAULT_TIMEOUT': RQ_DEFAULT_TIMEOUT, +}) RQ_QUEUES = { 'high': RQ_PARAMS, From 5c969a8caf081a9dc66d3b702ab7d0b73c625e32 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Tue, 13 Dec 2022 13:24:07 -0500 Subject: [PATCH 20/24] Changelog for #9361, #10447, #11077 --- docs/release-notes/version-3.3.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/release-notes/version-3.3.md b/docs/release-notes/version-3.3.md index ee3e7cddf..953e2d1d3 100644 --- a/docs/release-notes/version-3.3.md +++ b/docs/release-notes/version-3.3.md @@ -4,7 +4,9 @@ ### Enhancements +* [#9361](https://github.com/netbox-community/netbox/issues/9361) - Add replication controls for module bulk import * [#10255](https://github.com/netbox-community/netbox/issues/10255) - Introduce `LOGOUT_REDIRECT_URL` config parameter to control redirection of user after logout +* [#10447](https://github.com/netbox-community/netbox/issues/10447) - Enable reassigning an inventory item from one device to another * [#10516](https://github.com/netbox-community/netbox/issues/10516) - Add vertical frame & cabinet rack types * [#10748](https://github.com/netbox-community/netbox/issues/10748) - Add provider selection field for provider networks to circuit termination edit view * [#11089](https://github.com/netbox-community/netbox/issues/11089) - Permit whitespace in MAC addresses @@ -13,6 +15,7 @@ ### Bug Fixes * [#11041](https://github.com/netbox-community/netbox/issues/11041) - Correct power utilization percentage precision +* [#11077](https://github.com/netbox-community/netbox/issues/11077) - Honor configured date format when displaying date custom field values in tables * [#11087](https://github.com/netbox-community/netbox/issues/11087) - Fix background color of bottom banner content * [#11101](https://github.com/netbox-community/netbox/issues/11101) - Correct circuits count under site view * [#11128](https://github.com/netbox-community/netbox/issues/11128) - Disable ordering changelog table by object to avoid exception From 996e73d5d8f9d8367b3e51bc42e57a515c8619e3 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Tue, 13 Dec 2022 13:26:41 -0500 Subject: [PATCH 21/24] Fixes #10981: Fix release notes formatting --- docs/release-notes/version-3.3.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/release-notes/version-3.3.md b/docs/release-notes/version-3.3.md index 953e2d1d3..b4ab9c751 100644 --- a/docs/release-notes/version-3.3.md +++ b/docs/release-notes/version-3.3.md @@ -476,7 +476,7 @@ Custom field UI visibility has no impact on API operation. * The `cluster` field is now optional. A virtual machine must have a site and/or cluster assigned. * Added the optional `device` field * Added the `l2vpn_termination` read-only field -wireless.WirelessLAN +* wireless.WirelessLAN * Added `tenant` field -wireless.WirelessLink +* wireless.WirelessLink * Added `tenant` field From 96a796ebde6339eb78d6573d89efa4243be2c72f Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Tue, 13 Dec 2022 14:04:50 -0500 Subject: [PATCH 22/24] Fixes #11173: Enable missing tags columns for contact, L2VPN lists --- docs/release-notes/version-3.3.md | 1 + netbox/dcim/tables/devicetypes.py | 2 +- netbox/ipam/tables/l2vpn.py | 9 ++++++--- netbox/tenancy/tables/contacts.py | 5 ++++- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/docs/release-notes/version-3.3.md b/docs/release-notes/version-3.3.md index b4ab9c751..aa99e739f 100644 --- a/docs/release-notes/version-3.3.md +++ b/docs/release-notes/version-3.3.md @@ -21,6 +21,7 @@ * [#11128](https://github.com/netbox-community/netbox/issues/11128) - Disable ordering changelog table by object to avoid exception * [#11142](https://github.com/netbox-community/netbox/issues/11142) - Correct available choices for status under IP range filter form * [#11168](https://github.com/netbox-community/netbox/issues/11168) - Honor `RQ_DEFAULT_TIMEOUT` config parameter when using Redis Sentinel +* [#11173](https://github.com/netbox-community/netbox/issues/11173) - Enable missing tags columns for contact, L2VPN lists --- diff --git a/netbox/dcim/tables/devicetypes.py b/netbox/dcim/tables/devicetypes.py index 566c56a90..728fa3dd0 100644 --- a/netbox/dcim/tables/devicetypes.py +++ b/netbox/dcim/tables/devicetypes.py @@ -63,7 +63,7 @@ class ManufacturerTable(ContactsColumnMixin, NetBoxTable): model = Manufacturer fields = ( 'pk', 'id', 'name', 'devicetype_count', 'inventoryitem_count', 'platform_count', 'description', 'slug', - 'contacts', 'actions', 'created', 'last_updated', + 'tags', 'contacts', 'actions', 'created', 'last_updated', ) default_columns = ( 'pk', 'name', 'devicetype_count', 'inventoryitem_count', 'platform_count', 'description', 'slug', diff --git a/netbox/ipam/tables/l2vpn.py b/netbox/ipam/tables/l2vpn.py index 4a6af7c9b..628f9b8f0 100644 --- a/netbox/ipam/tables/l2vpn.py +++ b/netbox/ipam/tables/l2vpn.py @@ -29,14 +29,17 @@ class L2VPNTable(TenancyColumnsMixin, NetBoxTable): template_code=L2VPN_TARGETS, orderable=False ) + tags = columns.TagColumn( + url_name='ipam:l2vpn_list' + ) class Meta(NetBoxTable.Meta): model = L2VPN fields = ( - 'pk', 'name', 'slug', 'identifier', 'type', 'description', 'import_targets', 'export_targets', 'tenant', 'tenant_group', - 'actions', + 'pk', 'name', 'slug', 'identifier', 'type', 'description', 'import_targets', 'export_targets', 'tenant', + 'tenant_group', 'tags', 'created', 'last_updated', ) - default_columns = ('pk', 'name', 'identifier', 'type', 'description', 'actions') + default_columns = ('pk', 'name', 'identifier', 'type', 'description') class L2VPNTerminationTable(NetBoxTable): diff --git a/netbox/tenancy/tables/contacts.py b/netbox/tenancy/tables/contacts.py index 234dc2ad7..8cb6c0700 100644 --- a/netbox/tenancy/tables/contacts.py +++ b/netbox/tenancy/tables/contacts.py @@ -37,10 +37,13 @@ class ContactRoleTable(NetBoxTable): name = tables.Column( linkify=True ) + tags = columns.TagColumn( + url_name='tenancy:contactrole_list' + ) class Meta(NetBoxTable.Meta): model = ContactRole - fields = ('pk', 'name', 'description', 'slug', 'created', 'last_updated', 'actions') + fields = ('pk', 'name', 'description', 'slug', 'tags', 'created', 'last_updated', 'actions') default_columns = ('pk', 'name', 'description') From b9888d6f8652ec199ac7c2374e49012bf37a9189 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Tue, 13 Dec 2022 14:48:40 -0500 Subject: [PATCH 23/24] Fixes #11109: Fix nullification of custom object & multi-object fields via REST API --- docs/release-notes/version-3.3.md | 1 + netbox/extras/api/customfields.py | 2 +- netbox/extras/tests/test_customfields.py | 12 ++++++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-3.3.md b/docs/release-notes/version-3.3.md index aa99e739f..29593c9cb 100644 --- a/docs/release-notes/version-3.3.md +++ b/docs/release-notes/version-3.3.md @@ -18,6 +18,7 @@ * [#11077](https://github.com/netbox-community/netbox/issues/11077) - Honor configured date format when displaying date custom field values in tables * [#11087](https://github.com/netbox-community/netbox/issues/11087) - Fix background color of bottom banner content * [#11101](https://github.com/netbox-community/netbox/issues/11101) - Correct circuits count under site view +* [#11109](https://github.com/netbox-community/netbox/issues/11109) - Fix nullification of custom object & multi-object fields via REST API * [#11128](https://github.com/netbox-community/netbox/issues/11128) - Disable ordering changelog table by object to avoid exception * [#11142](https://github.com/netbox-community/netbox/issues/11142) - Correct available choices for status under IP range filter form * [#11168](https://github.com/netbox-community/netbox/issues/11168) - Honor `RQ_DEFAULT_TIMEOUT` config parameter when using Redis Sentinel diff --git a/netbox/extras/api/customfields.py b/netbox/extras/api/customfields.py index 17e6f77c5..d16fc0daf 100644 --- a/netbox/extras/api/customfields.py +++ b/netbox/extras/api/customfields.py @@ -72,7 +72,7 @@ class CustomFieldsDataField(Field): # Serialize object and multi-object values for cf in self._get_custom_fields(): - if cf.name in data and cf.type in ( + if cf.name in data and data[cf.name] not in (None, []) and cf.type in ( CustomFieldTypeChoices.TYPE_OBJECT, CustomFieldTypeChoices.TYPE_MULTIOBJECT ): diff --git a/netbox/extras/tests/test_customfields.py b/netbox/extras/tests/test_customfields.py index a023dd7fb..2a03e05d6 100644 --- a/netbox/extras/tests/test_customfields.py +++ b/netbox/extras/tests/test_customfields.py @@ -854,6 +854,18 @@ class CustomFieldAPITest(APITestCase): [vlans[1].pk, vlans[2].pk] ) + # Clear related objects + data = { + 'custom_fields': { + 'object_field': None, + 'multiobject_field': [], + }, + } + response = self.client.patch(url, data, format='json', **self.header) + self.assertHttpStatus(response, status.HTTP_200_OK) + self.assertIsNone(response.data['custom_fields']['object_field']) + self.assertListEqual(response.data['custom_fields']['multiobject_field'], []) + def test_minimum_maximum_values_validation(self): site2 = Site.objects.get(name='Site 2') url = reverse('dcim-api:site-detail', kwargs={'pk': site2.pk}) From 5e32b39f25f49102a09c01585d0426cd17be98de Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Tue, 13 Dec 2022 15:29:07 -0500 Subject: [PATCH 24/24] Release v3.3.10 --- .github/ISSUE_TEMPLATE/bug_report.yaml | 2 +- .github/ISSUE_TEMPLATE/feature_request.yaml | 2 +- docs/release-notes/version-3.3.md | 2 +- netbox/netbox/settings.py | 2 +- requirements.txt | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 03a58a2d7..45587b461 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -14,7 +14,7 @@ body: attributes: label: NetBox version description: What version of NetBox are you currently running? - placeholder: v3.3.9 + placeholder: v3.3.10 validations: required: true - type: dropdown diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index 94c879aed..de69bc9e0 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -14,7 +14,7 @@ body: attributes: label: NetBox version description: What version of NetBox are you currently running? - placeholder: v3.3.9 + placeholder: v3.3.10 validations: required: true - type: dropdown diff --git a/docs/release-notes/version-3.3.md b/docs/release-notes/version-3.3.md index 29593c9cb..94dd261a2 100644 --- a/docs/release-notes/version-3.3.md +++ b/docs/release-notes/version-3.3.md @@ -1,6 +1,6 @@ # NetBox v3.3 -## v3.3.10 (FUTURE) +## v3.3.10 (2022-12-13) ### Enhancements diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 5c0f486f3..b9ad5c1d0 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -29,7 +29,7 @@ django.utils.encoding.force_text = force_str # Environment setup # -VERSION = '3.3.10-dev' +VERSION = '3.3.10' # Hostname HOSTNAME = platform.node() diff --git a/requirements.txt b/requirements.txt index e920fcb0c..e078e4489 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ bleach==5.0.1 Django==4.0.8 django-cors-headers==3.13.0 -django-debug-toolbar==3.7.0 +django-debug-toolbar==3.8.1 django-filter==22.1 django-graphiql-debug-toolbar==0.2.0 django-mptt==0.14 @@ -29,7 +29,7 @@ sentry-sdk==1.11.1 social-auth-app-django==5.0.0 social-auth-core[openidconnect]==4.3.0 svgwrite==1.4.3 -tablib==3.2.1 +tablib==3.3.0 tzdata==2022.7 # Workaround for #7401