From 786f0cc7f355101241cebe034ff06d8fbf20b0d2 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 26 Sep 2023 16:31:33 -0400 Subject: [PATCH 01/30] PRVB --- docs/release-notes/version-3.6.md | 4 ++++ netbox/netbox/settings.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-3.6.md b/docs/release-notes/version-3.6.md index db0e3d3ea..0a7787e43 100644 --- a/docs/release-notes/version-3.6.md +++ b/docs/release-notes/version-3.6.md @@ -1,5 +1,9 @@ # NetBox v3.6 +## v3.6.4 (FUTURE) + +--- + ## v3.6.3 (2023-09-26) ### Enhancements diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index e483488fc..8be2800fb 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -25,7 +25,7 @@ from netbox.constants import RQ_QUEUE_DEFAULT, RQ_QUEUE_HIGH, RQ_QUEUE_LOW # Environment setup # -VERSION = '3.6.3' +VERSION = '3.6.4-dev' # Hostname HOSTNAME = platform.node() From ef460a38edd2c02d2fd887eb00f8599345550ea2 Mon Sep 17 00:00:00 2001 From: Arthur Date: Tue, 3 Oct 2023 10:22:17 -0700 Subject: [PATCH 02/30] 13944 fix report detail api --- netbox/extras/reports.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/extras/reports.py b/netbox/extras/reports.py index 9b12065ca..cc279a49a 100644 --- a/netbox/extras/reports.py +++ b/netbox/extras/reports.py @@ -23,7 +23,7 @@ logger = logging.getLogger(__name__) def get_module_and_report(module_name, report_name): module = ReportModule.objects.get(file_path=f'{module_name}.py') - report = module.reports.get(report_name) + report = module.reports.get(report_name)() return module, report From 5cb1a6b79050afc0ec8fbd5b05d19aa0851d12fb Mon Sep 17 00:00:00 2001 From: Arthur Date: Mon, 2 Oct 2023 12:07:28 -0700 Subject: [PATCH 03/30] 13872 fix bulk import --- netbox/templates/generic/bulk_import.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/netbox/templates/generic/bulk_import.html b/netbox/templates/generic/bulk_import.html index b9cb0d4cb..f78cb0bf5 100644 --- a/netbox/templates/generic/bulk_import.html +++ b/netbox/templates/generic/bulk_import.html @@ -67,6 +67,7 @@ Context: {% render_field form.upload_file %} {% render_field form.format %} + {% render_field form.csv_delimiter %}
@@ -88,6 +89,7 @@ Context: {% render_field form.data_source %} {% render_field form.data_file %} {% render_field form.format %} + {% render_field form.csv_delimiter %}
From 6dc560596d54274a0450fc771ecf544c1cb6b518 Mon Sep 17 00:00:00 2001 From: yash-pal1 Date: Wed, 27 Sep 2023 15:45:46 +0530 Subject: [PATCH 04/30] added device button under platform view pre-populated role field instead of platform field --- netbox/templates/dcim/platform.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/templates/dcim/platform.html b/netbox/templates/dcim/platform.html index 29f405b6e..9448ad3e5 100644 --- a/netbox/templates/dcim/platform.html +++ b/netbox/templates/dcim/platform.html @@ -13,7 +13,7 @@ {% block extra_controls %} {% if perms.dcim.add_device %} - + {% trans "Add Device" %} {% endif %} From 6093debb7192c76c5a395eb21c278168aefe88c9 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Tue, 3 Oct 2023 12:41:40 -0700 Subject: [PATCH 05/30] 12328 update GFK object in clean (#13946) * 12328 update GFK object in clean * Add missing import statement --------- Co-authored-by: Jeremy Stretch --- netbox/netbox/models/__init__.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/netbox/netbox/models/__init__.py b/netbox/netbox/models/__init__.py index 596357ea4..9d7696696 100644 --- a/netbox/netbox/models/__init__.py +++ b/netbox/netbox/models/__init__.py @@ -1,5 +1,6 @@ from django.conf import settings from django.contrib.contenttypes.fields import GenericForeignKey +from django.core.exceptions import ObjectDoesNotExist from django.core.validators import ValidationError from django.db import models from django.utils.translation import gettext_lazy as _ @@ -85,11 +86,16 @@ class NetBoxModel(NetBoxFeatureSet, models.Model): if ct_value and fk_value: klass = getattr(self, field.ct_field).model_class() - if not klass.objects.filter(pk=fk_value).exists(): + try: + obj = klass.objects.get(pk=fk_value) + except ObjectDoesNotExist: raise ValidationError({ field.fk_field: f"Related object not found using the provided value: {fk_value}." }) + # update the GFK field value + setattr(self, field.name, obj) + # # NetBox internal base models From a46255ddda73e1e29c610d40aeb7054214014935 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 3 Oct 2023 16:29:55 -0400 Subject: [PATCH 06/30] Fixes #13064: Ensure unchecked checkboxes do not revert to original values upon HTMX form refresh --- netbox/netbox/settings.py | 4 ++++ netbox/templates/django/forms/widgets/checkbox.html | 6 ++++++ 2 files changed, 10 insertions(+) create mode 100644 netbox/templates/django/forms/widgets/checkbox.html diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 8be2800fb..975e86858 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -355,6 +355,7 @@ INSTALLED_APPS = [ 'django.contrib.messages', 'django.contrib.staticfiles', 'django.contrib.humanize', + 'django.forms', 'corsheaders', 'debug_toolbar', 'graphiql_debug_toolbar', @@ -430,6 +431,9 @@ TEMPLATES = [ }, ] +# This allows us to override Django's stock form widget templates +FORM_RENDERER = 'django.forms.renderers.TemplatesSetting' + # Set up authentication backends if type(REMOTE_AUTH_BACKEND) not in (list, tuple): REMOTE_AUTH_BACKEND = [REMOTE_AUTH_BACKEND] diff --git a/netbox/templates/django/forms/widgets/checkbox.html b/netbox/templates/django/forms/widgets/checkbox.html new file mode 100644 index 000000000..bbe201a29 --- /dev/null +++ b/netbox/templates/django/forms/widgets/checkbox.html @@ -0,0 +1,6 @@ +{% comment %} + Include a hidden field of the same name to ensure that unchecked checkboxes + are always included in the submitted form data. +{% endcomment %} + +{% include "django/forms/widgets/input.html" %} From b93b331d8614a40c8c4d9095b7e5f684122a8d28 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 4 Oct 2023 14:09:29 -0400 Subject: [PATCH 07/30] Fixes #13966: Restore 'last login' column on users table --- docs/release-notes/version-3.6.md | 4 ++++ netbox/users/tables.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-3.6.md b/docs/release-notes/version-3.6.md index 0a7787e43..137825b89 100644 --- a/docs/release-notes/version-3.6.md +++ b/docs/release-notes/version-3.6.md @@ -2,6 +2,10 @@ ## v3.6.4 (FUTURE) +### Bug Fixes + +* [#13966](https://github.com/netbox-community/netbox/issues/13966) - Restore "last login" column on users table + --- ## v3.6.3 (2023-09-26) diff --git a/netbox/users/tables.py b/netbox/users/tables.py index 3b418715a..afb270568 100644 --- a/netbox/users/tables.py +++ b/netbox/users/tables.py @@ -52,7 +52,7 @@ class UserTable(NetBoxTable): model = NetBoxUser fields = ( 'pk', 'id', 'username', 'first_name', 'last_name', 'email', 'groups', 'is_active', 'is_staff', - 'is_superuser', + 'is_superuser', 'last_login', ) default_columns = ('pk', 'username', 'first_name', 'last_name', 'email', 'is_active') From e4a9cad75644d0489b9f87271d9f7baeac1e28ae Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 4 Oct 2023 14:11:28 -0400 Subject: [PATCH 08/30] Changelog for #12328, #13064, #13872, #13910, #13944 --- docs/release-notes/version-3.6.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/release-notes/version-3.6.md b/docs/release-notes/version-3.6.md index 137825b89..69a8ee62e 100644 --- a/docs/release-notes/version-3.6.md +++ b/docs/release-notes/version-3.6.md @@ -4,6 +4,11 @@ ### Bug Fixes +* [#12328](https://github.com/netbox-community/netbox/issues/12328) - Ensure generic foreign key relationships are populated in REST API serializations of objects +* [#13064](https://github.com/netbox-community/netbox/issues/13064) - Fix resetting of checkbox fields triggered by HTMX form re-rendering +* [#13872](https://github.com/netbox-community/netbox/issues/13872) - Add CSV delimiter field to file upload tab under bulk object upload views +* [#13910](https://github.com/netbox-community/netbox/issues/13910) - Correct "add device" button link under platform view +* [#13944](https://github.com/netbox-community/netbox/issues/13944) - Correct serialization of several report attributes in the REST API * [#13966](https://github.com/netbox-community/netbox/issues/13966) - Restore "last login" column on users table --- From 4d13f4d25294e53bc7691b7f0399d5d10c6fc96c Mon Sep 17 00:00:00 2001 From: Arthur Date: Thu, 5 Oct 2023 08:37:26 -0700 Subject: [PATCH 09/30] 13850 add requests to requirements --- base_requirements.txt | 4 ++++ requirements.txt | 1 + 2 files changed, 5 insertions(+) diff --git a/base_requirements.txt b/base_requirements.txt index 4b75b1313..423a9754b 100644 --- a/base_requirements.txt +++ b/base_requirements.txt @@ -120,6 +120,10 @@ psycopg[binary,pool] # https://github.com/yaml/pyyaml/blob/master/CHANGES PyYAML +# Requests +# https://github.com/psf/requests/blob/main/HISTORY.md +requests + # Sentry SDK # https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md sentry-sdk diff --git a/requirements.txt b/requirements.txt index 8c676df81..b81042acd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -27,6 +27,7 @@ netaddr==0.9.0 Pillow==10.0.1 psycopg[binary,pool]==3.1.11 PyYAML==6.0.1 +requests==2.28.1 sentry-sdk==1.31.0 social-auth-app-django==5.3.0 social-auth-core[openidconnect]==4.4.2 From 285187542d31fa4f722e564b7a8e0840e6687704 Mon Sep 17 00:00:00 2001 From: sleepinggenius2 Date: Wed, 4 Oct 2023 11:52:21 -0400 Subject: [PATCH 10/30] Adds selection custom field labels to UI --- netbox/extras/models/customfields.py | 5 +++++ netbox/netbox/tables/columns.py | 4 +++- netbox/utilities/templatetags/builtins/tags.py | 6 ++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/netbox/extras/models/customfields.py b/netbox/extras/models/customfields.py index e6f339e5a..2bed464bb 100644 --- a/netbox/extras/models/customfields.py +++ b/netbox/extras/models/customfields.py @@ -232,6 +232,11 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel): return self.choice_set.choices return [] + def get_choice_label(self, value): + if not hasattr(self, '_choice_map'): + self._choice_map = dict(self.choices) + return self._choice_map.get(value, value) + def populate_initial_data(self, content_types): """ Populate initial custom field data upon either a) the creation of a new CustomField, or diff --git a/netbox/netbox/tables/columns.py b/netbox/netbox/tables/columns.py index 9e348fb23..d2cd0a0d4 100644 --- a/netbox/netbox/tables/columns.py +++ b/netbox/netbox/tables/columns.py @@ -483,8 +483,10 @@ class CustomFieldColumn(tables.Column): return mark_safe('') if self.customfield.type == CustomFieldTypeChoices.TYPE_URL: return mark_safe(f'{escape(value)}') + if self.customfield.type == CustomFieldTypeChoices.TYPE_SELECT: + return self.customfield.get_choice_label(value) if self.customfield.type == CustomFieldTypeChoices.TYPE_MULTISELECT: - return ', '.join(v for v in value) + return ', '.join(self.customfield.get_choice_label(v) for v in value) if self.customfield.type == CustomFieldTypeChoices.TYPE_MULTIOBJECT: return mark_safe(', '.join( self._linkify_item(obj) for obj in self.customfield.deserialize(value) diff --git a/netbox/utilities/templatetags/builtins/tags.py b/netbox/utilities/templatetags/builtins/tags.py index 35aec1000..68541ae5a 100644 --- a/netbox/utilities/templatetags/builtins/tags.py +++ b/netbox/utilities/templatetags/builtins/tags.py @@ -1,6 +1,7 @@ from django import template from django.http import QueryDict +from extras.choices import CustomFieldTypeChoices from utilities.utils import dict_to_querydict __all__ = ( @@ -38,6 +39,11 @@ def customfield_value(customfield, value): customfield: A CustomField instance value: The custom field value applied to an object """ + if value: + if customfield.type == CustomFieldTypeChoices.TYPE_SELECT: + value = customfield.get_choice_label(value) + elif customfield.type == CustomFieldTypeChoices.TYPE_MULTISELECT: + value = [customfield.get_choice_label(v) for v in value] return { 'customfield': customfield, 'value': value, From bbb133019d708abd93efe790e338bb07e2155f4e Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Thu, 5 Oct 2023 12:15:50 -0700 Subject: [PATCH 11/30] 13815 document view permissions for scripts (#13943) * 13815 document view permissions for scripts * Replicate permissions note for reports * Remove duplicated text --------- Co-authored-by: Jeremy Stretch --- docs/customization/custom-scripts.md | 2 +- docs/customization/reports.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/customization/custom-scripts.md b/docs/customization/custom-scripts.md index 3811474d2..0b1ed11df 100644 --- a/docs/customization/custom-scripts.md +++ b/docs/customization/custom-scripts.md @@ -288,7 +288,7 @@ An IPv4 or IPv6 network with a mask. Returns a `netaddr.IPNetwork` object. Two a ## Running Custom Scripts !!! note - To run a custom script, a user must be assigned the `extras.run_script` permission. This is achieved by assigning the user (or group) a permission on the Script object and specifying the `run` action in the admin UI as shown below. + To run a custom script, a user must be assigned via permissions for `Extras > Script`, `Extras > ScriptModule`, and `Core > ManagedFile` objects. They must also be assigned the `extras.run_script` permission. This is achieved by assigning the user (or group) a permission on the Script object and specifying the `run` action in the admin UI as shown below. ![Adding the run action to a permission](../media/admin_ui_run_permission.png) diff --git a/docs/customization/reports.md b/docs/customization/reports.md index 7e3681304..a821c5da7 100644 --- a/docs/customization/reports.md +++ b/docs/customization/reports.md @@ -132,7 +132,7 @@ Once you have created a report, it will appear in the reports list. Initially, r ## Running Reports !!! note - To run a report, a user must be assigned the `extras.run_report` permission. This is achieved by assigning the user (or group) a permission on the Report object and specifying the `run` action in the admin UI as shown below. + To run a report, a user must be assigned via permissions for `Extras > Report`, `Extras > ReportModule`, and `Core > ManagedFile` objects. They must also be assigned the `extras.run_report` permission. This is achieved by assigning the user (or group) a permission on the Report object and specifying the `run` action in the admin UI as shown below. ![Adding the run action to a permission](../media/admin_ui_run_permission.png) From d03859b27b9bbc264f138d93d17773726949a1ab Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Thu, 5 Oct 2023 12:53:57 -0700 Subject: [PATCH 12/30] 13746 fix available ips API for posting custom-fields (#13889) --- netbox/ipam/api/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/ipam/api/views.py b/netbox/ipam/api/views.py index da6463e23..643e6a8e0 100644 --- a/netbox/ipam/api/views.py +++ b/netbox/ipam/api/views.py @@ -289,7 +289,7 @@ class AvailableObjectsView(ObjectValidationMixin, APIView): ) # Prepare object data for deserialization - requested_objects = self.prep_object_data(serializer.validated_data, available_objects, parent) + requested_objects = self.prep_object_data(requested_objects, available_objects, parent) # Initialize the serializer with a list or a single object depending on what was requested serializer_class = get_serializer_for_model(self.queryset.model) From a543bd469ad7dfee989f4edab6604134271095b2 Mon Sep 17 00:00:00 2001 From: Arthur Date: Thu, 5 Oct 2023 13:17:18 -0700 Subject: [PATCH 13/30] 11987 change cable bulk import to check if same cable --- netbox/dcim/forms/bulk_import.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/dcim/forms/bulk_import.py b/netbox/dcim/forms/bulk_import.py index 70aceaa49..e41e875e4 100644 --- a/netbox/dcim/forms/bulk_import.py +++ b/netbox/dcim/forms/bulk_import.py @@ -1192,7 +1192,7 @@ class CableImportForm(NetBoxModelImportForm): termination_object = model.objects.get(device__in=device.virtual_chassis.members.all(), name=name) else: termination_object = model.objects.get(device=device, name=name) - if termination_object.cable is not None: + if termination_object.cable is not None and termination_object.cable != self.instance: raise forms.ValidationError(f"Side {side.upper()}: {device} {termination_object} is already connected") except ObjectDoesNotExist: raise forms.ValidationError(f"{side.upper()} side termination not found: {device} {name}") From e23b246d46c348d7af8f67735f8e3414105219da Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 5 Oct 2023 16:55:15 -0400 Subject: [PATCH 14/30] Changelog for #11987, #13440, #13746, #13876, #13950 --- docs/release-notes/version-3.6.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/release-notes/version-3.6.md b/docs/release-notes/version-3.6.md index 69a8ee62e..cb09ee463 100644 --- a/docs/release-notes/version-3.6.md +++ b/docs/release-notes/version-3.6.md @@ -2,11 +2,19 @@ ## v3.6.4 (FUTURE) +### Enhancements + +* [#13950](https://github.com/netbox-community/netbox/issues/13950) - Display custom choice field labels rather than values in UI + ### Bug Fixes +* [#11987](https://github.com/netbox-community/netbox/issues/11987) - Fix validation of bulk cable updates via bulk import form * [#12328](https://github.com/netbox-community/netbox/issues/12328) - Ensure generic foreign key relationships are populated in REST API serializations of objects * [#13064](https://github.com/netbox-community/netbox/issues/13064) - Fix resetting of checkbox fields triggered by HTMX form re-rendering +* [#13440](https://github.com/netbox-community/netbox/issues/13440) - Fix support for assigning a tenant when creating "next available" VLANs via the REST API +* [#13746](https://github.com/netbox-community/netbox/issues/13746) - Fix support for setting custom field values when creating "next available" IP addresses via the REST API * [#13872](https://github.com/netbox-community/netbox/issues/13872) - Add CSV delimiter field to file upload tab under bulk object upload views +* [#13876](https://github.com/netbox-community/netbox/issues/13876) - Fix support for assigning an interface when creating "next available" IP addresses via the REST API * [#13910](https://github.com/netbox-community/netbox/issues/13910) - Correct "add device" button link under platform view * [#13944](https://github.com/netbox-community/netbox/issues/13944) - Correct serialization of several report attributes in the REST API * [#13966](https://github.com/netbox-community/netbox/issues/13966) - Restore "last login" column on users table From 383285fb943bf3e511a04e11510c6a04ed966ff7 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 6 Oct 2023 13:34:25 -0400 Subject: [PATCH 15/30] Closes #13997: Update runner versions (#13998) * Update runner versions * Update stale & lock runners --- .github/workflows/ci.yml | 8 ++++---- .github/workflows/lock.yml | 2 +- .github/workflows/stale.yml | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1d9692194..9d580baa4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,15 +31,15 @@ jobs: steps: - name: Check out repo - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v2 + uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} @@ -47,7 +47,7 @@ jobs: run: npm install -g yarn - name: Setup Node.js with Yarn Caching - uses: actions/setup-node@v2 + uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} cache: yarn diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml index 6019cef5d..a3e66a429 100644 --- a/.github/workflows/lock.yml +++ b/.github/workflows/lock.yml @@ -14,7 +14,7 @@ jobs: lock: runs-on: ubuntu-latest steps: - - uses: dessant/lock-threads@v3 + - uses: dessant/lock-threads@v4 with: issue-inactive-days: 90 pr-inactive-days: 30 diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 3b37aae56..22de146a2 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/stale@v6 + - uses: actions/stale@v8 with: close-issue-message: > This issue has been automatically closed due to lack of activity. In an From 4286c1cde255a9bf146b3b192e1ac17566af0094 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 6 Oct 2023 15:14:33 -0400 Subject: [PATCH 16/30] Closes #12831: Include circuit description in cable trace SVG image --- docs/release-notes/version-3.6.md | 1 + netbox/dcim/svg/cables.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/docs/release-notes/version-3.6.md b/docs/release-notes/version-3.6.md index cb09ee463..6cbcf3e19 100644 --- a/docs/release-notes/version-3.6.md +++ b/docs/release-notes/version-3.6.md @@ -4,6 +4,7 @@ ### Enhancements +* [#12831](https://github.com/netbox-community/netbox/issues/12831) - Include circuit description in cable trace SVG image * [#13950](https://github.com/netbox-community/netbox/issues/13950) - Display custom choice field labels rather than values in UI ### Bug Fixes diff --git a/netbox/dcim/svg/cables.py b/netbox/dcim/svg/cables.py index c01e656fd..acc4fcad9 100644 --- a/netbox/dcim/svg/cables.py +++ b/netbox/dcim/svg/cables.py @@ -160,6 +160,8 @@ class CableTraceSVG: elif instance._meta.model_name == 'circuit': labels[0] = f'Circuit {instance}' labels.append(instance.provider) + if instance.description: + labels.append(instance.description) elif instance._meta.model_name == 'circuittermination': if instance.xconnect_id: labels.append(f'{instance.xconnect_id}') From 01c894e6257b9a4c746bab4d0ca9d0b888730a23 Mon Sep 17 00:00:00 2001 From: Arthur Date: Thu, 12 Oct 2023 09:45:16 -0700 Subject: [PATCH 17/30] 14013 fix device role filter --- netbox/dcim/forms/filtersets.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/netbox/dcim/forms/filtersets.py b/netbox/dcim/forms/filtersets.py index 43e5f4481..7f99d1ca4 100644 --- a/netbox/dcim/forms/filtersets.py +++ b/netbox/dcim/forms/filtersets.py @@ -109,7 +109,7 @@ class DeviceComponentFilterForm(NetBoxModelFilterSetForm): required=False, label=_('Device type') ) - role_id = DynamicModelMultipleChoiceField( + device_role_id = DynamicModelMultipleChoiceField( queryset=DeviceRole.objects.all(), required=False, label=_('Device role') @@ -1136,7 +1136,7 @@ class ConsolePortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm): (None, ('q', 'filter_id', 'tag')), (_('Attributes'), ('name', 'label', 'type', 'speed')), (_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')), - (_('Device'), ('device_type_id', 'role_id', 'device_id', 'virtual_chassis_id')), + (_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')), (_('Connection'), ('cabled', 'connected', 'occupied')), ) type = forms.MultipleChoiceField( @@ -1158,7 +1158,7 @@ class ConsoleServerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterF (None, ('q', 'filter_id', 'tag')), (_('Attributes'), ('name', 'label', 'type', 'speed')), (_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')), - (_('Device'), ('device_type_id', 'role_id', 'device_id', 'virtual_chassis_id')), + (_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')), (_('Connection'), ('cabled', 'connected', 'occupied')), ) type = forms.MultipleChoiceField( @@ -1180,7 +1180,7 @@ class PowerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm): (None, ('q', 'filter_id', 'tag')), (_('Attributes'), ('name', 'label', 'type')), (_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')), - (_('Device'), ('device_type_id', 'role_id', 'device_id', 'virtual_chassis_id')), + (_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')), (_('Connection'), ('cabled', 'connected', 'occupied')), ) type = forms.MultipleChoiceField( @@ -1197,7 +1197,7 @@ class PowerOutletFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm): (None, ('q', 'filter_id', 'tag')), (_('Attributes'), ('name', 'label', 'type')), (_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')), - (_('Device'), ('device_type_id', 'role_id', 'device_id', 'virtual_chassis_id')), + (_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')), (_('Connection'), ('cabled', 'connected', 'occupied')), ) type = forms.MultipleChoiceField( @@ -1217,7 +1217,7 @@ class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm): (_('PoE'), ('poe_mode', 'poe_type')), (_('Wireless'), ('rf_role', 'rf_channel', 'rf_channel_width', 'tx_power')), (_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')), - (_('Device'), ('device_type_id', 'role_id', 'device_id', 'virtual_chassis_id', 'vdc_id')), + (_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', 'vdc_id')), (_('Connection'), ('cabled', 'connected', 'occupied')), ) vdc_id = DynamicModelMultipleChoiceField( @@ -1324,7 +1324,7 @@ class FrontPortFilterForm(CabledFilterForm, DeviceComponentFilterForm): (None, ('q', 'filter_id', 'tag')), (_('Attributes'), ('name', 'label', 'type', 'color')), (_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')), - (_('Device'), ('device_type_id', 'role_id', 'device_id', 'virtual_chassis_id')), + (_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')), (_('Cable'), ('cabled', 'occupied')), ) model = FrontPort @@ -1346,7 +1346,7 @@ class RearPortFilterForm(CabledFilterForm, DeviceComponentFilterForm): (None, ('q', 'filter_id', 'tag')), (_('Attributes'), ('name', 'label', 'type', 'color')), (_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')), - (_('Device'), ('device_type_id', 'role_id', 'device_id', 'virtual_chassis_id')), + (_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')), (_('Cable'), ('cabled', 'occupied')), ) type = forms.MultipleChoiceField( @@ -1367,7 +1367,7 @@ class ModuleBayFilterForm(DeviceComponentFilterForm): (None, ('q', 'filter_id', 'tag')), (_('Attributes'), ('name', 'label', 'position')), (_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')), - (_('Device'), ('device_type_id', 'role_id', 'device_id', 'virtual_chassis_id')), + (_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')), ) tag = TagFilterField(model) position = forms.CharField( @@ -1382,7 +1382,7 @@ class DeviceBayFilterForm(DeviceComponentFilterForm): (None, ('q', 'filter_id', 'tag')), (_('Attributes'), ('name', 'label')), (_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')), - (_('Device'), ('device_type_id', 'role_id', 'device_id', 'virtual_chassis_id')), + (_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')), ) tag = TagFilterField(model) @@ -1393,7 +1393,7 @@ class InventoryItemFilterForm(DeviceComponentFilterForm): (None, ('q', 'filter_id', 'tag')), (_('Attributes'), ('name', 'label', 'role_id', 'manufacturer_id', 'serial', 'asset_tag', 'discovered')), (_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')), - (_('Device'), ('device_type_id', 'role_id', 'device_id', 'virtual_chassis_id')), + (_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')), ) role_id = DynamicModelMultipleChoiceField( queryset=InventoryItemRole.objects.all(), From 2522056bd1a3d4c403ea4d9406c1c093a5b62cb1 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 6 Oct 2023 15:25:47 -0400 Subject: [PATCH 18/30] Closes #12872: Introduce DATA_UPLOAD_MAX_MEMORY_SIZE config parameter --- docs/configuration/miscellaneous.md | 12 ++++++++++-- netbox/netbox/settings.py | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/configuration/miscellaneous.md b/docs/configuration/miscellaneous.md index fd410a9d4..f143be139 100644 --- a/docs/configuration/miscellaneous.md +++ b/docs/configuration/miscellaneous.md @@ -80,6 +80,14 @@ changes in the database indefinitely. --- +## DATA_UPLOAD_MAX_MEMORY_SIZE + +Default: `2621440` (2.5 MB) + +The maximum size (in bytes) of an incoming HTTP request (i.e. `GET` or `POST` data). Requests which exceed this size will raise a `RequestDataTooBig` exception. + +--- + ## ENFORCE_GLOBAL_UNIQUE !!! tip "Dynamic Configuration Parameter" @@ -90,9 +98,9 @@ By default, NetBox will permit users to create duplicate prefixes and IP address --- -## `FILE_UPLOAD_MAX_MEMORY_SIZE` +## FILE_UPLOAD_MAX_MEMORY_SIZE -Default: `2621440` (2.5 MB). +Default: `2621440` (2.5 MB) The maximum amount (in bytes) of uploaded data that will be held in memory before being written to the filesystem. Changing this setting can be useful for example to be able to upload files bigger than 2.5MB to custom scripts for processing. diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 975e86858..15c456584 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -95,6 +95,7 @@ CORS_ORIGIN_WHITELIST = getattr(configuration, 'CORS_ORIGIN_WHITELIST', []) CSRF_COOKIE_NAME = getattr(configuration, 'CSRF_COOKIE_NAME', 'csrftoken') CSRF_COOKIE_SECURE = getattr(configuration, 'CSRF_COOKIE_SECURE', False) CSRF_TRUSTED_ORIGINS = getattr(configuration, 'CSRF_TRUSTED_ORIGINS', []) +DATA_UPLOAD_MAX_MEMORY_SIZE = getattr(configuration, 'DATA_UPLOAD_MAX_MEMORY_SIZE', 2621440) DATE_FORMAT = getattr(configuration, 'DATE_FORMAT', 'N j, Y') DATETIME_FORMAT = getattr(configuration, 'DATETIME_FORMAT', 'N j, Y g:i a') DEBUG = getattr(configuration, 'DEBUG', False) From 72f01b3e8931386a37a1225f7b3c28e54d88fc53 Mon Sep 17 00:00:00 2001 From: Tobias Genannt Date: Thu, 12 Oct 2023 16:50:20 +0200 Subject: [PATCH 19/30] Fix #14026: Only get the needed amount of objects --- netbox/ipam/api/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/netbox/ipam/api/views.py b/netbox/ipam/api/views.py index 643e6a8e0..662b393de 100644 --- a/netbox/ipam/api/views.py +++ b/netbox/ipam/api/views.py @@ -266,6 +266,7 @@ class AvailableObjectsView(ObjectValidationMixin, APIView): # Normalize request data to a list of objects requested_objects = request.data if isinstance(request.data, list) else [request.data] + limit = len(requested_objects) # Serialize and validate the request data serializer = self.write_serializer_class(data=requested_objects, many=True, context={ @@ -279,7 +280,7 @@ class AvailableObjectsView(ObjectValidationMixin, APIView): ) with advisory_lock(ADVISORY_LOCK_KEYS[self.advisory_lock_key]): - available_objects = self.get_available_objects(parent) + available_objects = self.get_available_objects(parent, limit) # Determine if the requested number of objects is available if not self.check_sufficient_available(serializer.validated_data, available_objects): From 06ed7ac8a5b827c137f25627377200d738eeffe5 Mon Sep 17 00:00:00 2001 From: Daniel Sheppard Date: Fri, 13 Oct 2023 12:57:58 -0500 Subject: [PATCH 20/30] Fixes: #14023 - Fixes bulk disconnecting with multiple components attached to the same cable (#14029) * Fixes: #14023 - Fixes bulk disconnecting with multiple components attached to the same cable * Update netbox/dcim/views.py Co-authored-by: Jeremy Stretch * Update netbox/dcim/views.py Co-authored-by: Jeremy Stretch * Update netbox/dcim/views.py Co-authored-by: Daniel Sheppard * Code cleanup & i18n fix * Restore original termination count logic --------- Co-authored-by: Jeremy Stretch --- netbox/dcim/views.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 2f661e613..7c75dd26e 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -122,16 +122,18 @@ class BulkDisconnectView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View) if form.is_valid(): with transaction.atomic(): - count = 0 + cable_ids = set() for obj in self.queryset.filter(pk__in=form.cleaned_data['pk']): - if obj.cable is None: - continue - obj.cable.delete() - count += 1 + if obj.cable: + cable_ids.add(obj.cable.pk) + count += 1 + for cable in Cable.objects.filter(pk__in=cable_ids): + cable.delete() - messages.success(request, "Disconnected {} {}".format( - count, self.queryset.model._meta.verbose_name_plural + messages.success(request, _("Disconnected {count} {type}").format( + count=count, + type=self.queryset.model._meta.verbose_name_plural )) return redirect(return_url) From 14447befb9dd2479ef265ef1a52b9430ffa10ed5 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 13 Oct 2023 14:01:08 -0400 Subject: [PATCH 21/30] Changelog for #12872, #14013, #14023, #14026 --- docs/release-notes/version-3.6.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/release-notes/version-3.6.md b/docs/release-notes/version-3.6.md index 6cbcf3e19..762302ddc 100644 --- a/docs/release-notes/version-3.6.md +++ b/docs/release-notes/version-3.6.md @@ -5,6 +5,7 @@ ### Enhancements * [#12831](https://github.com/netbox-community/netbox/issues/12831) - Include circuit description in cable trace SVG image +* [#12872](https://github.com/netbox-community/netbox/issues/12872) - Introduce the `DATA_UPLOAD_MAX_MEMORY_SIZE` configuration parameter * [#13950](https://github.com/netbox-community/netbox/issues/13950) - Display custom choice field labels rather than values in UI ### Bug Fixes @@ -19,6 +20,9 @@ * [#13910](https://github.com/netbox-community/netbox/issues/13910) - Correct "add device" button link under platform view * [#13944](https://github.com/netbox-community/netbox/issues/13944) - Correct serialization of several report attributes in the REST API * [#13966](https://github.com/netbox-community/netbox/issues/13966) - Restore "last login" column on users table +* [#14013](https://github.com/netbox-community/netbox/issues/14013) - Fix device role filter choices under inventory items list filters +* [#14023](https://github.com/netbox-community/netbox/issues/14023) - Fix exception when bulk disconnecting interfaces connected to the same cable +* [#14026](https://github.com/netbox-community/netbox/issues/14026) - Optimize the automatic creation of available IP addresses for large prefixes --- From 18a813aa39760636d80ad486a237bf2ec35713ad Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Tue, 17 Oct 2023 06:32:42 -0700 Subject: [PATCH 22/30] 13972 allow filtering of cables if have terminations (#13949) * 10769 allow filtering of cables if have terminations * 10769 change to termianted * 10769 add test case * 10769 review cleanup --- netbox/dcim/filtersets.py | 17 +++++++++++++++++ netbox/dcim/forms/filtersets.py | 9 ++++++++- netbox/dcim/tests/test_filtersets.py | 10 ++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/netbox/dcim/filtersets.py b/netbox/dcim/filtersets.py index 0261998db..d600667d7 100644 --- a/netbox/dcim/filtersets.py +++ b/netbox/dcim/filtersets.py @@ -1745,6 +1745,10 @@ class CableFilterSet(TenancyFilterSet, NetBoxModelFilterSet): method='filter_by_cable_end_b', field_name='terminations__termination_id' ) + unterminated = django_filters.BooleanFilter( + method='_unterminated', + label=_('Unterminated'), + ) type = django_filters.MultipleChoiceFilter( choices=CableTypeChoices ) @@ -1812,6 +1816,19 @@ class CableFilterSet(TenancyFilterSet, NetBoxModelFilterSet): # Filter by termination id and cable_end type return self.filter_by_cable_end(queryset, name, value, CableEndChoices.SIDE_B) + def _unterminated(self, queryset, name, value): + if value: + terminated_ids = ( + queryset.filter(terminations__cable_end=CableEndChoices.SIDE_A) + .filter(terminations__cable_end=CableEndChoices.SIDE_B) + .values("id") + ) + return queryset.exclude(id__in=terminated_ids) + else: + return queryset.filter(terminations__cable_end=CableEndChoices.SIDE_A).filter( + terminations__cable_end=CableEndChoices.SIDE_B + ) + class CableTerminationFilterSet(BaseFilterSet): termination_type = ContentTypeFilter() diff --git a/netbox/dcim/forms/filtersets.py b/netbox/dcim/forms/filtersets.py index 7f99d1ca4..d0d321187 100644 --- a/netbox/dcim/forms/filtersets.py +++ b/netbox/dcim/forms/filtersets.py @@ -910,7 +910,7 @@ class CableFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): fieldsets = ( (None, ('q', 'filter_id', 'tag')), (_('Location'), ('site_id', 'location_id', 'rack_id', 'device_id')), - (_('Attributes'), ('type', 'status', 'color', 'length', 'length_unit')), + (_('Attributes'), ('type', 'status', 'color', 'length', 'length_unit', 'unterminated')), (_('Tenant'), ('tenant_group_id', 'tenant_id')), ) region_id = DynamicModelMultipleChoiceField( @@ -979,6 +979,13 @@ class CableFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): choices=add_blank_choice(CableLengthUnitChoices), required=False ) + unterminated = forms.NullBooleanField( + label=_('Unterminated'), + required=False, + widget=forms.Select( + choices=BOOLEAN_WITH_BLANK_CHOICES + ) + ) tag = TagFilterField(model) diff --git a/netbox/dcim/tests/test_filtersets.py b/netbox/dcim/tests/test_filtersets.py index dd5ff7bc2..1f3b557b5 100644 --- a/netbox/dcim/tests/test_filtersets.py +++ b/netbox/dcim/tests/test_filtersets.py @@ -4275,6 +4275,7 @@ class CableTestCase(TestCase, ChangeLoggedFilterSetTests): Interface(device=devices[4], name='Interface 10', type=InterfaceTypeChoices.TYPE_1GE_FIXED), Interface(device=devices[5], name='Interface 11', type=InterfaceTypeChoices.TYPE_1GE_FIXED), Interface(device=devices[5], name='Interface 12', type=InterfaceTypeChoices.TYPE_1GE_FIXED), + Interface(device=devices[5], name='Interface 13', type=InterfaceTypeChoices.TYPE_1GE_FIXED), ) Interface.objects.bulk_create(interfaces) @@ -4290,6 +4291,9 @@ class CableTestCase(TestCase, ChangeLoggedFilterSetTests): Cable(a_terminations=[interfaces[11]], b_terminations=[interfaces[0]], label='Cable 6', type=CableTypeChoices.TYPE_CAT6, tenant=tenants[2], status=LinkStatusChoices.STATUS_PLANNED, color='e91e63', length=20, length_unit=CableLengthUnitChoices.UNIT_METER).save() Cable(a_terminations=[console_port], b_terminations=[console_server_port], label='Cable 7').save() + # Cable for unterminated test + Cable(a_terminations=[interfaces[12]], label='Cable 8', type=CableTypeChoices.TYPE_CAT6, status=LinkStatusChoices.STATUS_DECOMMISSIONING).save() + def test_label(self): params = {'label': ['Cable 1', 'Cable 2']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) @@ -4368,6 +4372,12 @@ class CableTestCase(TestCase, ChangeLoggedFilterSetTests): } self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) + def test_unterminated(self): + params = {'unterminated': True} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) + params = {'unterminated': False} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 7) + class PowerPanelTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = PowerPanel.objects.all() From c671ac2f28daad8a4761929b86a7d91e56ca3da1 Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Tue, 17 Oct 2023 19:36:33 +0530 Subject: [PATCH 23/30] Add dns_name filter on the IP Address page (#14046) * Add dns_name filter on the IP Address page #13957 * add dns_name field in the filterset field and remove extra method --- netbox/ipam/forms/filtersets.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/netbox/ipam/forms/filtersets.py b/netbox/ipam/forms/filtersets.py index e4e967f81..aae62ca75 100644 --- a/netbox/ipam/forms/filtersets.py +++ b/netbox/ipam/forms/filtersets.py @@ -295,7 +295,7 @@ class IPAddressFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): model = IPAddress fieldsets = ( (None, ('q', 'filter_id', 'tag')), - (_('Attributes'), ('parent', 'family', 'status', 'role', 'mask_length', 'assigned_to_interface')), + (_('Attributes'), ('parent', 'family', 'status', 'role', 'mask_length', 'assigned_to_interface', 'dns_name')), (_('VRF'), ('vrf_id', 'present_in_vrf_id')), (_('Tenant'), ('tenant_group_id', 'tenant_id')), (_('Device/VM'), ('device_id', 'virtual_machine_id')), @@ -357,6 +357,10 @@ class IPAddressFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): choices=BOOLEAN_WITH_BLANK_CHOICES ) ) + dns_name = forms.CharField( + required=False, + label=_('DNS Name') + ) tag = TagFilterField(model) From a24864bc6dffc070bfcaeb43158c42dbccefa57f Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Tue, 17 Oct 2023 07:31:58 -0700 Subject: [PATCH 24/30] 14042 mptt cache count (#14048) * 14042 fix cache count for mptt child delete * 14042 add test * Misc cleanup --------- Co-authored-by: Jeremy Stretch --- netbox/utilities/counters.py | 6 +++-- netbox/utilities/tests/test_counters.py | 31 ++++++++++++++++++++++--- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/netbox/utilities/counters.py b/netbox/utilities/counters.py index 6c597b943..0ee2606db 100644 --- a/netbox/utilities/counters.py +++ b/netbox/utilities/counters.py @@ -62,7 +62,7 @@ def post_save_receiver(sender, instance, created, **kwargs): update_counter(parent_model, new_pk, counter_name, 1) -def post_delete_receiver(sender, instance, **kwargs): +def post_delete_receiver(sender, instance, origin, **kwargs): """ Update counter fields on related objects when a TrackingModelMixin subclass is deleted. """ @@ -72,7 +72,9 @@ def post_delete_receiver(sender, instance, **kwargs): # Decrement the parent's counter by one if parent_pk is not None: - update_counter(parent_model, parent_pk, counter_name, -1) + # MPTT sends two delete signals for child elements so guard against multiple decrements + if not origin or origin == instance: + update_counter(parent_model, parent_pk, counter_name, -1) # diff --git a/netbox/utilities/tests/test_counters.py b/netbox/utilities/tests/test_counters.py index cf8850c52..014c758e9 100644 --- a/netbox/utilities/tests/test_counters.py +++ b/netbox/utilities/tests/test_counters.py @@ -1,7 +1,11 @@ -from django.test import TestCase +from django.contrib.contenttypes.models import ContentType +from django.test import override_settings +from django.urls import reverse from dcim.models import * -from utilities.testing.utils import create_test_device +from users.models import ObjectPermission +from utilities.testing.base import TestCase +from utilities.testing.utils import create_test_device, create_test_user class CountersTest(TestCase): @@ -10,7 +14,6 @@ class CountersTest(TestCase): """ @classmethod def setUpTestData(cls): - # Create devices device1 = create_test_device('Device 1') device2 = create_test_device('Device 2') @@ -79,3 +82,25 @@ class CountersTest(TestCase): device2.refresh_from_db() self.assertEqual(device1.interface_count, 1) self.assertEqual(device2.interface_count, 3) + + @override_settings(EXEMPT_VIEW_PERMISSIONS=['*']) + def test_mptt_child_delete(self): + device1, device2 = Device.objects.all() + inventory_item1 = InventoryItem.objects.create(device=device1, name='Inventory Item 1') + inventory_item2 = InventoryItem.objects.create(device=device1, name='Inventory Item 2', parent=inventory_item1) + device1.refresh_from_db() + self.assertEqual(device1.inventory_item_count, 2) + + # Setup bulk_delete for the inventory items + self.add_permissions('dcim.delete_inventoryitem') + pk_list = device1.inventoryitems.values_list('pk', flat=True) + data = { + 'pk': pk_list, + 'confirm': True, + '_confirm': True, # Form button + } + + # Try POST with model-level permission + self.client.post(reverse("dcim:inventoryitem_bulk_delete"), data) + device1.refresh_from_db() + self.assertEqual(device1.inventory_item_count, 0) From d77d45e795e8341ea95fbb1897daee10f170e429 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Tue, 17 Oct 2023 07:35:01 -0700 Subject: [PATCH 25/30] 12336 make region API calls atomic (#13942) * 12336 make region API calls atomic * 12336 switch to pg locks * 12336 add locks to all views using mptt models * 12336 fix ADVISORY_LOCK_KEYS reference * 12336 review changes * Tweak advisory lock numbering --------- Co-authored-by: Jeremy Stretch --- netbox/dcim/api/views.py | 12 ++++++------ netbox/netbox/api/viewsets/__init__.py | 21 +++++++++++++++++++++ netbox/netbox/constants.py | 11 +++++++++++ netbox/tenancy/api/views.py | 6 +++--- netbox/wireless/api/views.py | 4 ++-- netbox/wireless/models.py | 1 - 6 files changed, 43 insertions(+), 12 deletions(-) diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index f045f1bb4..80a991736 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -20,7 +20,7 @@ from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired from netbox.api.metadata import ContentTypeMetadata from netbox.api.pagination import StripCountAnnotationsPaginator from netbox.api.renderers import TextRenderer -from netbox.api.viewsets import NetBoxModelViewSet +from netbox.api.viewsets import NetBoxModelViewSet, MPTTLockedMixin from netbox.api.viewsets.mixins import SequentialBulkCreatesMixin from netbox.constants import NESTED_SERIALIZER_PREFIX from utilities.api import get_serializer_for_model @@ -98,7 +98,7 @@ class PassThroughPortMixin(object): # Regions # -class RegionViewSet(NetBoxModelViewSet): +class RegionViewSet(MPTTLockedMixin, NetBoxModelViewSet): queryset = Region.objects.add_related_count( Region.objects.all(), Site, @@ -114,7 +114,7 @@ class RegionViewSet(NetBoxModelViewSet): # Site groups # -class SiteGroupViewSet(NetBoxModelViewSet): +class SiteGroupViewSet(MPTTLockedMixin, NetBoxModelViewSet): queryset = SiteGroup.objects.add_related_count( SiteGroup.objects.all(), Site, @@ -149,7 +149,7 @@ class SiteViewSet(NetBoxModelViewSet): # Locations # -class LocationViewSet(NetBoxModelViewSet): +class LocationViewSet(MPTTLockedMixin, NetBoxModelViewSet): queryset = Location.objects.add_related_count( Location.objects.add_related_count( Location.objects.all(), @@ -350,7 +350,7 @@ class DeviceBayTemplateViewSet(NetBoxModelViewSet): filterset_class = filtersets.DeviceBayTemplateFilterSet -class InventoryItemTemplateViewSet(NetBoxModelViewSet): +class InventoryItemTemplateViewSet(MPTTLockedMixin, NetBoxModelViewSet): queryset = InventoryItemTemplate.objects.prefetch_related('device_type__manufacturer', 'role') serializer_class = serializers.InventoryItemTemplateSerializer filterset_class = filtersets.InventoryItemTemplateFilterSet @@ -538,7 +538,7 @@ class DeviceBayViewSet(NetBoxModelViewSet): brief_prefetch_fields = ['device'] -class InventoryItemViewSet(NetBoxModelViewSet): +class InventoryItemViewSet(MPTTLockedMixin, NetBoxModelViewSet): queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer', 'tags') serializer_class = serializers.InventoryItemSerializer filterset_class = filtersets.InventoryItemFilterSet diff --git a/netbox/netbox/api/viewsets/__init__.py b/netbox/netbox/api/viewsets/__init__.py index 5fe81b1f5..c6794bb61 100644 --- a/netbox/netbox/api/viewsets/__init__.py +++ b/netbox/netbox/api/viewsets/__init__.py @@ -3,6 +3,8 @@ import logging from django.core.exceptions import ObjectDoesNotExist, PermissionDenied from django.db import transaction from django.db.models import ProtectedError +from django_pglocks import advisory_lock +from netbox.constants import ADVISORY_LOCK_KEYS from rest_framework import mixins as drf_mixins from rest_framework.response import Response from rest_framework.viewsets import GenericViewSet @@ -157,3 +159,22 @@ class NetBoxModelViewSet( logger.info(f"Deleting {model._meta.verbose_name} {instance} (PK: {instance.pk})") return super().perform_destroy(instance) + + +class MPTTLockedMixin: + """ + Puts pglock on objects that derive from MPTTModel for parallel API calling. + Note: If adding this to a view, must add the model name to ADVISORY_LOCK_KEYS + """ + + def create(self, request, *args, **kwargs): + with advisory_lock(ADVISORY_LOCK_KEYS[self.queryset.model._meta.model_name]): + return super().create(request, *args, **kwargs) + + def update(self, request, *args, **kwargs): + with advisory_lock(ADVISORY_LOCK_KEYS[self.queryset.model._meta.model_name]): + return super().update(request, *args, **kwargs) + + def destroy(self, request, *args, **kwargs): + with advisory_lock(ADVISORY_LOCK_KEYS[self.queryset.model._meta.model_name]): + return super().destroy(request, *args, **kwargs) diff --git a/netbox/netbox/constants.py b/netbox/netbox/constants.py index d69edc69c..2f4ee8e6b 100644 --- a/netbox/netbox/constants.py +++ b/netbox/netbox/constants.py @@ -11,8 +11,19 @@ RQ_QUEUE_LOW = 'low' # When adding a new key, pick something arbitrary and unique so that it is easily searchable in # query logs. ADVISORY_LOCK_KEYS = { + # Available object locks 'available-prefixes': 100100, 'available-ips': 100200, 'available-vlans': 100300, 'available-asns': 100400, + + # MPTT locks + 'region': 105100, + 'sitegroup': 105200, + 'location': 105300, + 'tenantgroup': 105400, + 'contactgroup': 105500, + 'wirelesslangroup': 105600, + 'inventoryitem': 105700, + 'inventoryitemtemplate': 105800, } diff --git a/netbox/tenancy/api/views.py b/netbox/tenancy/api/views.py index 39c86d80e..71a4961c3 100644 --- a/netbox/tenancy/api/views.py +++ b/netbox/tenancy/api/views.py @@ -3,7 +3,7 @@ from rest_framework.routers import APIRootView from circuits.models import Circuit from dcim.models import Device, Rack, Site from ipam.models import IPAddress, Prefix, VLAN, VRF -from netbox.api.viewsets import NetBoxModelViewSet +from netbox.api.viewsets import NetBoxModelViewSet, MPTTLockedMixin from tenancy import filtersets from tenancy.models import * from utilities.utils import count_related @@ -23,7 +23,7 @@ class TenancyRootView(APIRootView): # Tenants # -class TenantGroupViewSet(NetBoxModelViewSet): +class TenantGroupViewSet(MPTTLockedMixin, NetBoxModelViewSet): queryset = TenantGroup.objects.add_related_count( TenantGroup.objects.all(), Tenant, @@ -58,7 +58,7 @@ class TenantViewSet(NetBoxModelViewSet): # Contacts # -class ContactGroupViewSet(NetBoxModelViewSet): +class ContactGroupViewSet(MPTTLockedMixin, NetBoxModelViewSet): queryset = ContactGroup.objects.add_related_count( ContactGroup.objects.all(), Contact, diff --git a/netbox/wireless/api/views.py b/netbox/wireless/api/views.py index 1103cec37..a6cc9f535 100644 --- a/netbox/wireless/api/views.py +++ b/netbox/wireless/api/views.py @@ -1,6 +1,6 @@ from rest_framework.routers import APIRootView -from netbox.api.viewsets import NetBoxModelViewSet +from netbox.api.viewsets import NetBoxModelViewSet, MPTTLockedMixin from wireless import filtersets from wireless.models import * from . import serializers @@ -14,7 +14,7 @@ class WirelessRootView(APIRootView): return 'Wireless' -class WirelessLANGroupViewSet(NetBoxModelViewSet): +class WirelessLANGroupViewSet(MPTTLockedMixin, NetBoxModelViewSet): queryset = WirelessLANGroup.objects.add_related_count( WirelessLANGroup.objects.all(), WirelessLAN, diff --git a/netbox/wireless/models.py b/netbox/wireless/models.py index 046918535..e8e48eef8 100644 --- a/netbox/wireless/models.py +++ b/netbox/wireless/models.py @@ -2,7 +2,6 @@ from django.core.exceptions import ValidationError from django.db import models from django.urls import reverse from django.utils.translation import gettext_lazy as _ -from mptt.models import MPTTModel from dcim.choices import LinkStatusChoices from dcim.constants import WIRELESS_IFACE_TYPES From 7983c2590e74b36682c4b76e8a543c709fad0891 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Tue, 17 Oct 2023 07:57:50 -0700 Subject: [PATCH 26/30] 14025 fix script name checking (#14030) * 14025 fix script name checking * 14025 fix script name checking * 14025 add file extension validation and simplify get logic * 14025 match start of string with regex * 14025 backout changes to model_forms * 14025 add filepatch checking to reports --- netbox/extras/views.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/netbox/extras/views.py b/netbox/extras/views.py index 9efcc02dc..55b73d29d 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -978,6 +978,10 @@ class ReportListView(ContentTypePermissionRequiredMixin, View): }) +def get_report_module(module, request): + return get_object_or_404(ReportModule.objects.restrict(request.user), file_path__regex=f"^{module}\\.") + + class ReportView(ContentTypePermissionRequiredMixin, View): """ Display a single Report and its associated Job (if any). @@ -986,7 +990,7 @@ class ReportView(ContentTypePermissionRequiredMixin, View): return 'extras.view_report' def get(self, request, module, name): - module = get_object_or_404(ReportModule.objects.restrict(request.user), file_path__startswith=module) + module = get_report_module(module, request) report = module.reports[name]() object_type = ContentType.objects.get(app_label='extras', model='reportmodule') @@ -1007,7 +1011,7 @@ class ReportView(ContentTypePermissionRequiredMixin, View): if not request.user.has_perm('extras.run_report'): return HttpResponseForbidden() - module = get_object_or_404(ReportModule.objects.restrict(request.user), file_path__startswith=module) + module = get_report_module(module, request) report = module.reports[name]() form = ReportForm(request.POST, scheduling_enabled=report.scheduling_enabled) @@ -1046,7 +1050,7 @@ class ReportSourceView(ContentTypePermissionRequiredMixin, View): return 'extras.view_report' def get(self, request, module, name): - module = get_object_or_404(ReportModule.objects.restrict(request.user), file_path__startswith=module) + module = get_report_module(module, request) report = module.reports[name]() return render(request, 'extras/report/source.html', { @@ -1062,7 +1066,7 @@ class ReportJobsView(ContentTypePermissionRequiredMixin, View): return 'extras.view_report' def get(self, request, module, name): - module = get_object_or_404(ReportModule.objects.restrict(request.user), file_path__startswith=module) + module = get_report_module(module, request) report = module.reports[name]() object_type = ContentType.objects.get(app_label='extras', model='reportmodule') @@ -1151,13 +1155,17 @@ class ScriptListView(ContentTypePermissionRequiredMixin, View): }) +def get_script_module(module, request): + return get_object_or_404(ScriptModule.objects.restrict(request.user), file_path__regex=f"^{module}\\.") + + class ScriptView(ContentTypePermissionRequiredMixin, View): def get_required_permission(self): return 'extras.view_script' def get(self, request, module, name): - module = get_object_or_404(ScriptModule.objects.restrict(request.user), file_path__startswith=module) + module = get_script_module(module, request) script = module.scripts[name]() form = script.as_form(initial=normalize_querydict(request.GET)) @@ -1181,7 +1189,7 @@ class ScriptView(ContentTypePermissionRequiredMixin, View): if not request.user.has_perm('extras.run_script'): return HttpResponseForbidden() - module = get_object_or_404(ScriptModule.objects.restrict(request.user), file_path__startswith=module) + module = get_script_module(module, request) script = module.scripts[name]() form = script.as_form(request.POST, request.FILES) @@ -1218,7 +1226,7 @@ class ScriptSourceView(ContentTypePermissionRequiredMixin, View): return 'extras.view_script' def get(self, request, module, name): - module = get_object_or_404(ScriptModule.objects.restrict(request.user), file_path__startswith=module) + module = get_script_module(module, request) script = module.scripts[name]() return render(request, 'extras/script/source.html', { @@ -1234,7 +1242,7 @@ class ScriptJobsView(ContentTypePermissionRequiredMixin, View): return 'extras.view_script' def get(self, request, module, name): - module = get_object_or_404(ScriptModule.objects.restrict(request.user), file_path__startswith=module) + module = get_script_module(module, request) script = module.scripts[name]() object_type = ContentType.objects.get(app_label='extras', model='scriptmodule') From 51ef4fb920acd689755af6ea9f7765b341ebfcec Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 13 Oct 2023 16:39:19 -0400 Subject: [PATCH 27/30] Closes #13962: Add a copy-to-clipboard button to the key field of the API token creation form --- netbox/project-static/dist/netbox.js | Bin 529868 -> 529867 bytes netbox/project-static/dist/netbox.js.map | Bin 450255 -> 450255 bytes netbox/project-static/src/clipboard.ts | 2 +- netbox/templates/account/token.html | 5 ----- netbox/users/forms/model_forms.py | 3 +++ .../templates/form_helpers/render_field.html | 8 ++++++++ 6 files changed, 12 insertions(+), 6 deletions(-) diff --git a/netbox/project-static/dist/netbox.js b/netbox/project-static/dist/netbox.js index 7d16f9916551ad8f5deea05dcc5db33d79d507df..426302ea83c97ad0ae968919b4ede00f323f214e 100644 GIT binary patch delta 33 ocmX@JTH*9+g@zW!7N!>F7M2#)7Pc+yPQvZZ!tC3fg*iHz0MF(MZvX%Q delta 35 qcmX@TTH(xUg@zW!7N!>F7M2#)7Pc+yPQr|d?M}k%+nt0tI++0D;tJ0I diff --git a/netbox/project-static/dist/netbox.js.map b/netbox/project-static/dist/netbox.js.map index 1797f57ceb117de051fd4be372f96b16e715f26f..077c4bcc0b0e1e6c7dbb1eb1722ea94132e260b2 100644 GIT binary patch delta 29 lcmX@VSNi;3>4p}@7N!>F7M2#)Eo@1gjG5b$IoWcq0syPU3TprW delta 29 lcmX@VSNi;3>4p}@7N!>F7M2#)Eo@1gjM>|hIoWcq0syPi3T*%Y diff --git a/netbox/project-static/src/clipboard.ts b/netbox/project-static/src/clipboard.ts index 46ca5e36c..ddcb7b96e 100644 --- a/netbox/project-static/src/clipboard.ts +++ b/netbox/project-static/src/clipboard.ts @@ -2,7 +2,7 @@ import Clipboard from 'clipboard'; import { getElements } from './util'; export function initClipboard(): void { - for (const element of getElements('a.copy-content')) { + for (const element of getElements('.copy-content')) { new Clipboard(element); } } diff --git a/netbox/templates/account/token.html b/netbox/templates/account/token.html index d83e13ff5..57d1de3f4 100644 --- a/netbox/templates/account/token.html +++ b/netbox/templates/account/token.html @@ -15,11 +15,6 @@ {% block content %}
- {% if key and not settings.ALLOW_TOKEN_RETRIEVAL %} - - {% endif %}
{% trans "Token" %}
diff --git a/netbox/users/forms/model_forms.py b/netbox/users/forms/model_forms.py index 5fe84ad5f..1c3233f87 100644 --- a/netbox/users/forms/model_forms.py +++ b/netbox/users/forms/model_forms.py @@ -114,6 +114,9 @@ class UserTokenForm(BootstrapMixin, forms.ModelForm): help_text=_( 'Keys must be at least 40 characters in length. Be sure to record your key prior to ' 'submitting this form, as it may no longer be accessible once the token has been created.' + ), + widget=forms.TextInput( + attrs={'data-clipboard': 'true'} ) ) allowed_ips = SimpleArrayField( diff --git a/netbox/utilities/templates/form_helpers/render_field.html b/netbox/utilities/templates/form_helpers/render_field.html index 379dcc021..e5a564a3d 100644 --- a/netbox/utilities/templates/form_helpers/render_field.html +++ b/netbox/utilities/templates/form_helpers/render_field.html @@ -29,6 +29,14 @@ {{ label }}
+ {# Include a copy-to-clipboard button #} + {% elif 'data-clipboard' in field.field.widget.attrs %} +
+ {{ field }} + +
{# Default field rendering #} {% else %} {{ field }} From ab9de4344785f39a34dfd79ecc1721f22ee69e74 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 17 Oct 2023 12:25:49 -0400 Subject: [PATCH 28/30] Changelog for #12336, #13957, #13962, #13972, #14025, #14042 --- docs/release-notes/version-3.6.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/release-notes/version-3.6.md b/docs/release-notes/version-3.6.md index 762302ddc..230c7a652 100644 --- a/docs/release-notes/version-3.6.md +++ b/docs/release-notes/version-3.6.md @@ -7,11 +7,15 @@ * [#12831](https://github.com/netbox-community/netbox/issues/12831) - Include circuit description in cable trace SVG image * [#12872](https://github.com/netbox-community/netbox/issues/12872) - Introduce the `DATA_UPLOAD_MAX_MEMORY_SIZE` configuration parameter * [#13950](https://github.com/netbox-community/netbox/issues/13950) - Display custom choice field labels rather than values in UI +* [#13957](https://github.com/netbox-community/netbox/issues/13957) - Add DNS name filter on IP addresses list +* [#13962](https://github.com/netbox-community/netbox/issues/13962) - Add a copy-to-clipboard button for API tokens +* [#13972](https://github.com/netbox-community/netbox/issues/13972) - Introduce a filter to find unterminated cables ### Bug Fixes * [#11987](https://github.com/netbox-community/netbox/issues/11987) - Fix validation of bulk cable updates via bulk import form * [#12328](https://github.com/netbox-community/netbox/issues/12328) - Ensure generic foreign key relationships are populated in REST API serializations of objects +* [#12336](https://github.com/netbox-community/netbox/issues/12336) - Employ PostgreSQL advisory locks to avoid duplicate MPTT tree IDs when bulk creating objects * [#13064](https://github.com/netbox-community/netbox/issues/13064) - Fix resetting of checkbox fields triggered by HTMX form re-rendering * [#13440](https://github.com/netbox-community/netbox/issues/13440) - Fix support for assigning a tenant when creating "next available" VLANs via the REST API * [#13746](https://github.com/netbox-community/netbox/issues/13746) - Fix support for setting custom field values when creating "next available" IP addresses via the REST API @@ -22,7 +26,9 @@ * [#13966](https://github.com/netbox-community/netbox/issues/13966) - Restore "last login" column on users table * [#14013](https://github.com/netbox-community/netbox/issues/14013) - Fix device role filter choices under inventory items list filters * [#14023](https://github.com/netbox-community/netbox/issues/14023) - Fix exception when bulk disconnecting interfaces connected to the same cable +* [#14025](https://github.com/netbox-community/netbox/issues/14025) - Fix exception when viewing a script that begins with the same name as another * [#14026](https://github.com/netbox-community/netbox/issues/14026) - Optimize the automatic creation of available IP addresses for large prefixes +* [#14042](https://github.com/netbox-community/netbox/issues/14042) - Fix duplicated child object count decrements when removing objects in bulk --- From 3fd8e48fac47d22e3202c101921d6c57cddd0428 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 17 Oct 2023 12:37:14 -0400 Subject: [PATCH 29/30] Release v3.6.4 --- .github/ISSUE_TEMPLATE/bug_report.yaml | 2 +- .github/ISSUE_TEMPLATE/feature_request.yaml | 2 +- docs/release-notes/version-3.6.md | 2 +- netbox/netbox/settings.py | 2 +- requirements.txt | 28 ++++++++++----------- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index a587b36e2..f4afe3f98 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.6.3 + placeholder: v3.6.4 validations: required: true - type: dropdown diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index 71f1f2d97..9bf991e6e 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.6.3 + placeholder: v3.6.4 validations: required: true - type: dropdown diff --git a/docs/release-notes/version-3.6.md b/docs/release-notes/version-3.6.md index 230c7a652..a03dc548f 100644 --- a/docs/release-notes/version-3.6.md +++ b/docs/release-notes/version-3.6.md @@ -1,6 +1,6 @@ # NetBox v3.6 -## v3.6.4 (FUTURE) +## v3.6.4 (2023-10-17) ### Enhancements diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 15c456584..a5bbad85e 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -25,7 +25,7 @@ from netbox.constants import RQ_QUEUE_DEFAULT, RQ_QUEUE_HIGH, RQ_QUEUE_LOW # Environment setup # -VERSION = '3.6.4-dev' +VERSION = '3.6.4' # Hostname HOSTNAME = platform.node() diff --git a/requirements.txt b/requirements.txt index b81042acd..38585c433 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,35 +1,35 @@ -bleach==6.0.0 -Django==4.2.5 -django-cors-headers==4.2.0 +bleach==6.1.0 +Django==4.2.6 +django-cors-headers==4.3.0 django-debug-toolbar==4.2.0 django-filter==23.3 django-graphiql-debug-toolbar==0.2.0 -django-mptt==0.14 +django-mptt==0.15 django-pglocks==1.0.4 django-prometheus==2.3.1 -django-redis==5.3.0 -django-rich==1.7.0 +django-redis==5.4.0 +django-rich==1.8.0 django-rq==2.8.1 django-tables2==2.6.0 django-taggit==4.0.0 django-timezone-field==6.0.1 djangorestframework==3.14.0 -drf-spectacular==0.26.4 -drf-spectacular-sidecar==2023.9.1 +drf-spectacular==0.26.5 +drf-spectacular-sidecar==2023.10.1 feedparser==6.0.10 graphene-django==3.0.0 gunicorn==21.2.0 Jinja2==3.1.2 Markdown==3.3.7 -mkdocs-material==9.4.2 +mkdocs-material==9.4.6 mkdocstrings[python-legacy]==0.23.0 netaddr==0.9.0 -Pillow==10.0.1 -psycopg[binary,pool]==3.1.11 +Pillow==10.1.0 +psycopg[binary,pool]==3.1.12 PyYAML==6.0.1 -requests==2.28.1 -sentry-sdk==1.31.0 -social-auth-app-django==5.3.0 +requests==2.31.0 +sentry-sdk==1.32.0 +social-auth-app-django==5.4.0 social-auth-core[openidconnect]==4.4.2 svgwrite==1.4.3 tablib==3.5.0 From de298224f13df3a2f9871a27d451a33abd6fbee2 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 17 Oct 2023 12:48:42 -0400 Subject: [PATCH 30/30] Pin django-mptt to v0.14.0, for Python 3.8 --- base_requirements.txt | 3 ++- requirements.txt | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/base_requirements.txt b/base_requirements.txt index 423a9754b..9863984ca 100644 --- a/base_requirements.txt +++ b/base_requirements.txt @@ -23,8 +23,9 @@ django-filter django-graphiql-debug-toolbar # Modified Preorder Tree Traversal (recursive nesting of objects) +# Pinned to 0.14.0; 0.15.0 requires Python 3.9+ # https://github.com/django-mptt/django-mptt/blob/main/CHANGELOG.rst -django-mptt +django-mptt==0.14.0 # Context managers for PostgreSQL advisory locks # https://github.com/Xof/django-pglocks/blob/master/CHANGES.txt diff --git a/requirements.txt b/requirements.txt index 38585c433..9f9176ea2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ django-cors-headers==4.3.0 django-debug-toolbar==4.2.0 django-filter==23.3 django-graphiql-debug-toolbar==0.2.0 -django-mptt==0.15 +django-mptt==0.14.0 django-pglocks==1.0.4 django-prometheus==2.3.1 django-redis==5.4.0