From b7668fbfc3a81abf2c6a8ace98047647fd9244c9 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 4 Apr 2024 16:23:16 -0400 Subject: [PATCH 01/27] PRVB --- docs/release-notes/version-3.7.md | 4 ++++ netbox/netbox/settings.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-3.7.md b/docs/release-notes/version-3.7.md index ddbeb4bca38..cc20c9860fa 100644 --- a/docs/release-notes/version-3.7.md +++ b/docs/release-notes/version-3.7.md @@ -1,5 +1,9 @@ # NetBox v3.7 +## v3.7.6 (FUTURE) + +--- + ## v3.7.5 (2024-04-04) ### Enhancements diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 0ae43da668f..94303225346 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -28,7 +28,7 @@ # Environment setup # -VERSION = '3.7.5' +VERSION = '3.7.6-dev' # Hostname HOSTNAME = platform.node() From 54c6d95fbb45f39df4c55a6740e84880600afc06 Mon Sep 17 00:00:00 2001 From: Arthur Date: Thu, 11 Apr 2024 13:15:05 -0700 Subject: [PATCH 02/27] 15654 check for no termination in TunnelTerminationSerializer --- netbox/vpn/api/serializers.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/netbox/vpn/api/serializers.py b/netbox/vpn/api/serializers.py index 5f6fcd5f771..36ccf28de8b 100644 --- a/netbox/vpn/api/serializers.py +++ b/netbox/vpn/api/serializers.py @@ -98,6 +98,9 @@ class Meta: @extend_schema_field(serializers.JSONField(allow_null=True)) def get_termination(self, obj): + if not obj.termination: + return None + serializer = get_serializer_for_model(obj.termination, prefix=NESTED_SERIALIZER_PREFIX) context = {'request': self.context['request']} return serializer(obj.termination, context=context).data From d7922a68d88b79b32b710e3b92476e4500a4452f Mon Sep 17 00:00:00 2001 From: Julio-Oliveira-Encora Date: Thu, 11 Apr 2024 17:15:12 -0300 Subject: [PATCH 03/27] Fixed line 391 in netbox/virtualization/views.py. It was reeplaced "view_virtual_disk" with "view_virtualdisk" --- netbox/virtualization/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/virtualization/views.py b/netbox/virtualization/views.py index 6019fc22705..ec19a1d22f9 100644 --- a/netbox/virtualization/views.py +++ b/netbox/virtualization/views.py @@ -388,7 +388,7 @@ class VirtualMachineVirtualDisksView(generic.ObjectChildrenView): tab = ViewTab( label=_('Virtual Disks'), badge=lambda obj: obj.virtual_disk_count, - permission='virtualization.view_virtual_disk', + permission='virtualization.view_virtualdisk', weight=500 ) actions = { From 5098422f68fcb73e2fd64dbb55551c5069b0ed80 Mon Sep 17 00:00:00 2001 From: Julio Oliveira at Encora <149191228+Julio-Oliveira-Encora@users.noreply.github.com> Date: Mon, 15 Apr 2024 11:19:15 -0300 Subject: [PATCH 04/27] Fixes #15644 - Add the ability to configure HSTS in NetBox (#15683) * Added SECURE_HSTS_SECONDSm SECURE_HSTS_INCLUDE_SUBDOMAINS, and SECURE_HSTS_PRELOAD to settings.py * Addressed some PR comments. * Apply suggestions from code review --------- Co-authored-by: Jeremy Stretch --- docs/configuration/security.md | 24 ++++++++++++++++++++++++ netbox/netbox/settings.py | 3 +++ 2 files changed, 27 insertions(+) diff --git a/docs/configuration/security.md b/docs/configuration/security.md index 2ae92285f1d..9de09cedad0 100644 --- a/docs/configuration/security.md +++ b/docs/configuration/security.md @@ -183,6 +183,30 @@ The view name or URL to which a user is redirected after logging out. --- +## SECURE_HSTS_INCLUDE_SUBDOMAINS + +Default: False + +If true, the `includeSubDomains` directive will be included in the HTTP Strict Transport Security (HSTS) header. This directive instructs the browser to apply the HSTS policy to all subdomains of the current domain. + +--- + +## SECURE_HSTS_PRELOAD + +Default: False + +If true, the `preload` directive will be included in the HTTP Strict Transport Security (HSTS) header. This directive instructs the browser to preload the site in HTTPS. Browsers that use the HSTS preload list will force the site to be accessed via HTTPS even if the user types HTTP in the address bar. + +--- + +## SECURE_HSTS_SECONDS + +Default: 0 + +If set to a non-zero integer value, the SecurityMiddleware sets the HTTP Strict Transport Security (HSTS) header on all responses that do not already have it. This will instruct the browser that the website must be accessed via HTTPS, blocking any HTTP request. + +--- + ## SECURE_SSL_REDIRECT Default: False diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 94303225346..55002aa87f2 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -160,6 +160,9 @@ RQ_RETRY_MAX = getattr(configuration, 'RQ_RETRY_MAX', 0) SCRIPTS_ROOT = getattr(configuration, 'SCRIPTS_ROOT', os.path.join(BASE_DIR, 'scripts')).rstrip('/') SEARCH_BACKEND = getattr(configuration, 'SEARCH_BACKEND', 'netbox.search.backends.CachedValueSearchBackend') +SECURE_HSTS_INCLUDE_SUBDOMAINS = getattr(configuration, 'SECURE_HSTS_INCLUDE_SUBDOMAINS', False) +SECURE_HSTS_PRELOAD = getattr(configuration, 'SECURE_HSTS_PRELOAD', False) +SECURE_HSTS_SECONDS = getattr(configuration, 'SECURE_HSTS_SECONDS', 0) SECURE_SSL_REDIRECT = getattr(configuration, 'SECURE_SSL_REDIRECT', False) SENTRY_DSN = getattr(configuration, 'SENTRY_DSN', None) SENTRY_ENABLED = getattr(configuration, 'SENTRY_ENABLED', False) From f7e4fe2a9c4b8d4441cce342af616df6367041ab Mon Sep 17 00:00:00 2001 From: "Wrage, Florian" Date: Tue, 9 Apr 2024 15:48:49 +0200 Subject: [PATCH 05/27] Fixes #15640: add identifier field to search index of l2vpn --- netbox/vpn/search.py | 1 + 1 file changed, 1 insertion(+) diff --git a/netbox/vpn/search.py b/netbox/vpn/search.py index 066bc68bb12..c1914dc222d 100644 --- a/netbox/vpn/search.py +++ b/netbox/vpn/search.py @@ -75,6 +75,7 @@ class L2VPNIndex(SearchIndex): fields = ( ('name', 100), ('slug', 110), + ('identifier', 200), ('description', 500), ('comments', 5000), ) From f47b15886326e61cb2c4f39a4742185772bcbc4e Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Mon, 15 Apr 2024 08:24:32 -0700 Subject: [PATCH 06/27] 15685 Allow decimal for cable length filter form (#15703) * 15685 allow decimal for cable length filter * 15685 allow decimal for cable length filter * 15685 remove minlenth * 15685 remove minlenth --- netbox/dcim/forms/filtersets.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/netbox/dcim/forms/filtersets.py b/netbox/dcim/forms/filtersets.py index 95c44138135..d8d32627170 100644 --- a/netbox/dcim/forms/filtersets.py +++ b/netbox/dcim/forms/filtersets.py @@ -977,9 +977,9 @@ class CableFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): label=_('Color'), required=False ) - length = forms.IntegerField( + length = forms.DecimalField( label=_('Length'), - required=False + required=False, ) length_unit = forms.ChoiceField( label=_('Length unit'), From 17e8773c8cf13184f26c8a58b77407899adbc640 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 15 Apr 2024 12:10:33 -0400 Subject: [PATCH 07/27] Changelog for #15640, #15644, #15654, #15668, #15685 --- docs/release-notes/version-3.7.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/release-notes/version-3.7.md b/docs/release-notes/version-3.7.md index cc20c9860fa..48afc668887 100644 --- a/docs/release-notes/version-3.7.md +++ b/docs/release-notes/version-3.7.md @@ -2,6 +2,17 @@ ## v3.7.6 (FUTURE) +### Enhancements + +* [#15640](https://github.com/netbox-community/netbox/issues/15640) - Add global search support for L2VPN identifiers +* [#15644](https://github.com/netbox-community/netbox/issues/15644) - Introduce new configuration parameters for enabling HTTP Strict Transport Security (HSTS) + +### Bug Fixes + +* [#15654](https://github.com/netbox-community/netbox/issues/15654) - Fix `AttributeError` exception when attempting to save an incomplete tunnel termination +* [#15668](https://github.com/netbox-community/netbox/issues/15668) - Fix permission required to display virtual disks tab on virtual machine UI view +* [#15685](https://github.com/netbox-community/netbox/issues/15685) - Allow filtering cables by decimal values using UI filter form + --- ## v3.7.5 (2024-04-04) From 3c3943c809aa16499d603acdd160fe431bd8720c Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 15 Apr 2024 12:12:35 -0400 Subject: [PATCH 08/27] Convert "needs triage" label to a status indicator --- .github/ISSUE_TEMPLATE/bug_report.yaml | 2 +- .github/ISSUE_TEMPLATE/documentation_change.yaml | 2 +- .github/ISSUE_TEMPLATE/feature_request.yaml | 2 +- .github/workflows/auto-assign-issue.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index d4a9bab72ef..60d9a709181 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -1,7 +1,7 @@ --- name: 🐛 Bug Report description: Report a reproducible bug in the current release of NetBox -labels: ["type: bug", "needs triage"] +labels: ["type: bug", "status: needs triage"] body: - type: markdown attributes: diff --git a/.github/ISSUE_TEMPLATE/documentation_change.yaml b/.github/ISSUE_TEMPLATE/documentation_change.yaml index 0f80f1716d3..b5a97078219 100644 --- a/.github/ISSUE_TEMPLATE/documentation_change.yaml +++ b/.github/ISSUE_TEMPLATE/documentation_change.yaml @@ -1,7 +1,7 @@ --- name: 📖 Documentation Change description: Suggest an addition or modification to the NetBox documentation -labels: ["type: documentation", "needs triage"] +labels: ["type: documentation", "status: needs triage"] body: - type: dropdown attributes: diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index 2cee040f8b5..22c65f27647 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -1,7 +1,7 @@ --- name: ✨ Feature Request description: Propose a new NetBox feature or enhancement -labels: ["type: feature", "needs triage"] +labels: ["type: feature", "status: needs triage"] body: - type: markdown attributes: diff --git a/.github/workflows/auto-assign-issue.yml b/.github/workflows/auto-assign-issue.yml index 9abbc0cce3d..e32e23c84e6 100644 --- a/.github/workflows/auto-assign-issue.yml +++ b/.github/workflows/auto-assign-issue.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: pozil/auto-assign-issue@v1 - if: "contains(github.event.issue.labels.*.name, 'needs triage')" + if: "contains(github.event.issue.labels.*.name, 'status: needs triage')" with: # Weighted assignments assignees: arthanson:3, jeffgdotorg:3, jeremystretch:3, abhi1693, DanSheps From 4284028bb0621be47c1bb7e23a99ba58c2d498b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markku=20Leini=C3=B6?= Date: Tue, 16 Apr 2024 11:08:13 +0300 Subject: [PATCH 09/27] Closes #15727: Add tab template context variable in the plugin doc --- docs/plugins/development/views.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/plugins/development/views.md b/docs/plugins/development/views.md index 1730b0ebde3..3c13a6fcbf8 100644 --- a/docs/plugins/development/views.md +++ b/docs/plugins/development/views.md @@ -157,7 +157,7 @@ These views are provided to enable or enhance certain NetBox model features, suc ### Additional Tabs -Plugins can "attach" a custom view to a core NetBox model by registering it with `register_model_view()`. To include a tab for this view within the NetBox UI, declare a TabView instance named `tab`: +Plugins can "attach" a custom view to a core NetBox model by registering it with `register_model_view()`. To include a tab for this view within the NetBox UI, declare a TabView instance named `tab`, and add it to the template context dict: ```python from dcim.models import Site @@ -173,6 +173,16 @@ class MyView(generic.ObjectView): badge=lambda obj: Stuff.objects.filter(site=obj).count(), permission='myplugin.view_stuff' ) + + def get(self, request, pk): + ... + return render( + request, + "myplugin/mytabview.html", + context={ + "tab": self.tab, + }, + ) ``` ::: utilities.views.register_model_view From c5ae89ad0341a05560b8ab7daeb1656e24d7a8a4 Mon Sep 17 00:00:00 2001 From: Javier de la Puente Date: Wed, 17 Apr 2024 09:44:19 +0200 Subject: [PATCH 10/27] Use endpoint_url in S3Backend --- netbox/core/data_backends.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/netbox/core/data_backends.py b/netbox/core/data_backends.py index 15891a6f54e..2d3a7d8c8f2 100644 --- a/netbox/core/data_backends.py +++ b/netbox/core/data_backends.py @@ -149,7 +149,8 @@ def fetch(self): region_name=self._region_name, aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key, - config=self.config + config=self.config, + endpoint_url=self._endpoint_url ) bucket = s3.Bucket(self._bucket_name) @@ -176,6 +177,11 @@ def _bucket_name(self): url_path = urlparse(self.url).path.lstrip('/') return url_path.split('/')[0] + @property + def _endpoint_url(self): + url_path = urlparse(self.url) + return url_path._replace(params="", fragment="", query="", path="").geturl() + @property def _remote_path(self): url_path = urlparse(self.url).path.lstrip('/') From b8cedfcc083971f601177b465bf7d1aef58756d5 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Wed, 17 Apr 2024 07:09:50 -0700 Subject: [PATCH 11/27] 15582 check permissions on specific object when sync request (#15704) * 15582 check permissions on specific object when sync request * 15582 move permission check * Enable translation of error message --------- Co-authored-by: Jeremy Stretch --- netbox/core/api/views.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/netbox/core/api/views.py b/netbox/core/api/views.py index 7bf2f87a666..39c922eb641 100644 --- a/netbox/core/api/views.py +++ b/netbox/core/api/views.py @@ -1,5 +1,5 @@ from django.shortcuts import get_object_or_404 - +from django.utils.translation import gettext_lazy as _ from rest_framework.decorators import action from rest_framework.exceptions import PermissionDenied from rest_framework.response import Response @@ -33,10 +33,11 @@ def sync(self, request, pk): """ Enqueue a job to synchronize the DataSource. """ - if not request.user.has_perm('core.sync_datasource'): - raise PermissionDenied("Syncing data sources requires the core.sync_datasource permission.") - datasource = get_object_or_404(DataSource, pk=pk) + + if not request.user.has_perm('core.sync_datasource', obj=datasource): + raise PermissionDenied(_("This user does not have permission to synchronize this data source.")) + datasource.enqueue_sync_job(request) serializer = serializers.DataSourceSerializer(datasource, context={'request': request}) From b5bb732031c31b4409af45bbf5693d3e3f579131 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 17 Apr 2024 11:58:14 -0400 Subject: [PATCH 12/27] Closes #10696: Break out instructions for installing & removing plugins (#15757) * Closes #10696: Break out instructions for installing & rmeoving plugins * Misc cleanup --- docs/plugins/index.md | 121 +---------------------------------- docs/plugins/installation.md | 68 ++++++++++++++++++++ docs/plugins/removal.md | 72 +++++++++++++++++++++ mkdocs.yml | 4 +- 4 files changed, 145 insertions(+), 120 deletions(-) create mode 100644 docs/plugins/installation.md create mode 100644 docs/plugins/removal.md diff --git a/docs/plugins/index.md b/docs/plugins/index.md index 39314187b48..0f502c5d82f 100644 --- a/docs/plugins/index.md +++ b/docs/plugins/index.md @@ -2,6 +2,8 @@ Plugins are packaged [Django](https://docs.djangoproject.com/) apps that can be installed alongside NetBox to provide custom functionality not present in the core application. Plugins can introduce their own models and views, but cannot interfere with existing components. A NetBox user may opt to install plugins provided by the community or build his or her own. +Please see the documented instructions for [installing a plugin](./installation.md) to get started. + ## Capabilities The NetBox plugin architecture allows for the following: @@ -23,122 +25,3 @@ Either by policy or by technical limitation, the interaction of plugins with Net * **Override core templates.** Plugins can inject additional content where supported, but may not manipulate or remove core content. * **Modify core settings.** A configuration registry is provided for plugins, however they cannot alter or delete the core configuration. * **Disable core components.** Plugins are not permitted to disable or hide core NetBox components. - -## Installing Plugins - -The instructions below detail the process for installing and enabling a NetBox plugin. - -### Install Package - -Download and install the plugin package per its installation instructions. Plugins published via PyPI are typically installed using pip. Be sure to install the plugin within NetBox's virtual environment. - -```no-highlight -$ source /opt/netbox/venv/bin/activate -(venv) $ pip install -``` - -Alternatively, you may wish to install the plugin manually by running `python setup.py install`. If you are developing a plugin and want to install it only temporarily, run `python setup.py develop` instead. - -### Enable the Plugin - -In `configuration.py`, add the plugin's name to the `PLUGINS` list: - -```python -PLUGINS = [ - 'plugin_name', -] -``` - -### Configure Plugin - -If the plugin requires any configuration, define it in `configuration.py` under the `PLUGINS_CONFIG` parameter. The available configuration parameters should be detailed in the plugin's README file. - -```no-highlight -PLUGINS_CONFIG = { - 'plugin_name': { - 'foo': 'bar', - 'buzz': 'bazz' - } -} -``` - -### Run Database Migrations - -If the plugin introduces new database models, run the provided schema migrations: - -```no-highlight -(venv) $ cd /opt/netbox/netbox/ -(venv) $ python3 manage.py migrate -``` - -### Collect Static Files - -Plugins may package static files to be served directly by the HTTP front end. Ensure that these are copied to the static root directory with the `collectstatic` management command: - -```no-highlight -(venv) $ cd /opt/netbox/netbox/ -(venv) $ python3 manage.py collectstatic -``` - -### Restart WSGI Service - -Restart the WSGI service and RQ workers to load the new plugin: - -```no-highlight -# sudo systemctl restart netbox netbox-rq -``` - -## Removing Plugins - -Follow these steps to completely remove a plugin. - -### Update Configuration - -Remove the plugin from the `PLUGINS` list in `configuration.py`. Also remove any relevant configuration parameters from `PLUGINS_CONFIG`. - -### Remove the Python Package - -Use `pip` to remove the installed plugin: - -```no-highlight -$ source /opt/netbox/venv/bin/activate -(venv) $ pip uninstall -``` - -### Restart WSGI Service - -Restart the WSGI service: - -```no-highlight -# sudo systemctl restart netbox -``` - -### Drop Database Tables - -!!! note - This step is necessary only for plugin which have created one or more database tables (generally through the introduction of new models). Check your plugin's documentation if unsure. - -Enter the PostgreSQL database shell to determine if the plugin has created any SQL tables. Substitute `pluginname` in the example below for the name of the plugin being removed. (You can also run the `\dt` command without a pattern to list _all_ tables.) - -```no-highlight -netbox=> \dt pluginname_* - List of relations - List of relations - Schema | Name | Type | Owner ---------+----------------+-------+-------- - public | pluginname_foo | table | netbox - public | pluginname_bar | table | netbox -(2 rows) -``` - -!!! warning - Exercise extreme caution when removing tables. Users are strongly encouraged to perform a backup of their database immediately before taking these actions. - -Drop each of the listed tables to remove it from the database: - -```no-highlight -netbox=> DROP TABLE pluginname_foo; -DROP TABLE -netbox=> DROP TABLE pluginname_bar; -DROP TABLE -``` diff --git a/docs/plugins/installation.md b/docs/plugins/installation.md new file mode 100644 index 00000000000..ffea5d42deb --- /dev/null +++ b/docs/plugins/installation.md @@ -0,0 +1,68 @@ +# Installing a Plugin + +!!! warning + The instructions below detail the general process for installing and configuring a NetBox plugin. However, each plugin is different and may require additional tasks or modifications to the steps below. Always consult the documentation for a specific plugin **before** attempting to install it. + +## Install the Python Package + +Download and install the plugin's Python package per its installation instructions. Plugins published via PyPI are typically installed using the [`pip`](https://packaging.python.org/en/latest/tutorials/installing-packages/) command line utility. Be sure to install the plugin within NetBox's virtual environment. + +```no-highlight +$ source /opt/netbox/venv/bin/activate +(venv) $ pip install +``` + +Alternatively, you may wish to install the plugin manually by running `python setup.py install`. If you are developing a plugin and want to install it only temporarily, run `python setup.py develop` instead. + +## Enable the Plugin + +In `configuration.py`, add the plugin's name to the `PLUGINS` list: + +```python +PLUGINS = [ + # ... + 'plugin_name', +] +``` + +## Configure the Plugin + +If the plugin requires any configuration, define it in `configuration.py` under the `PLUGINS_CONFIG` parameter. The available configuration parameters should be detailed in the plugin's `README` file or other documentation. + +```no-highlight +PLUGINS_CONFIG = { + 'plugin_name': { + 'foo': 'bar', + 'buzz': 'bazz' + } +} +``` + +## Run Database Migrations + +If the plugin introduces new database models, run the provided schema migrations: + +```no-highlight +(venv) $ cd /opt/netbox/netbox/ +(venv) $ python3 manage.py migrate +``` + +!!! tip + It's okay to run the `migrate` management command even if the plugin does not include any migration files. + +## Collect Static Files + +Plugins may package static resources like images or scripts to be served directly by the HTTP front end. Ensure that these are copied to the static root directory with the `collectstatic` management command: + +```no-highlight +(venv) $ cd /opt/netbox/netbox/ +(venv) $ python3 manage.py collectstatic +``` + +### Restart WSGI Service + +Finally, restart the WSGI service and RQ workers to load the new plugin: + +```no-highlight +# sudo systemctl restart netbox netbox-rq +``` diff --git a/docs/plugins/removal.md b/docs/plugins/removal.md new file mode 100644 index 00000000000..f5e81bdc083 --- /dev/null +++ b/docs/plugins/removal.md @@ -0,0 +1,72 @@ +# Removing a Plugin + +!!! warning + The instructions below detail the general process for removing a NetBox plugin. However, each plugin is different and may require additional tasks or modifications to the steps below. Always consult the documentation for a specific plugin **before** attempting to remove it. + +## Disable the Plugin + +Disable the plugin by removing it from the `PLUGINS` list in `configuration.py`. + +## Remove its Configuration + +Delete the plugin's entry (if any) in the `PLUGINS_CONFIG` dictionary in `configuration.py`. + +!!! tip + If there's a chance you may reinstall the plugin, consider commenting out any configuration parameters instead of deleting them. + +## Re-index Search Entries + +Run the `reindex` management command to reindex the global search engine. This will remove any stale entries pertaining to objects provided by the plugin. + +```no-highlight +$ cd /opt/netbox/netbox/ +$ source /opt/netbox/venv/bin/activate +(venv) $ python3 manage.py reindex +``` + +## Uninstall its Python Package + +Use `pip` to remove the installed plugin: + +```no-highlight +$ source /opt/netbox/venv/bin/activate +(venv) $ pip uninstall +``` + +## Restart WSGI Service + +Restart the WSGI service: + +```no-highlight +# sudo systemctl restart netbox +``` + +## Drop Database Tables + +!!! note + This step is necessary only for plugins which have created one or more database tables (generally through the introduction of new models). Check your plugin's documentation if unsure. + +Enter the PostgreSQL database shell (`manage.py dbshell`) to determine if the plugin has created any SQL tables. Substitute `pluginname` in the example below for the name of the plugin being removed. (You can also run the `\dt` command without a pattern to list _all_ tables.) + +```no-highlight +netbox=> \dt pluginname_* + List of relations + List of relations + Schema | Name | Type | Owner +--------+----------------+-------+-------- + public | pluginname_foo | table | netbox + public | pluginname_bar | table | netbox +(2 rows) +``` + +!!! warning + Exercise extreme caution when removing tables. Users are strongly encouraged to perform a backup of their database immediately before taking these actions. + +Drop each of the listed tables to remove it from the database: + +```no-highlight +netbox=> DROP TABLE pluginname_foo; +DROP TABLE +netbox=> DROP TABLE pluginname_bar; +DROP TABLE +``` diff --git a/mkdocs.yml b/mkdocs.yml index e1128578acd..c04ef519f47 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -127,7 +127,9 @@ nav: - Synchronized Data: 'integrations/synchronized-data.md' - Prometheus Metrics: 'integrations/prometheus-metrics.md' - Plugins: - - Using Plugins: 'plugins/index.md' + - About Plugins: 'plugins/index.md' + - Installing a Plugin: 'plugins/installation.md' + - Removing a Plugin: 'plugins/removal.md' - Developing Plugins: - Getting Started: 'plugins/development/index.md' - Models: 'plugins/development/models.md' From 928014c766a2b41935d62c9eb861f370853ce035 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Wed, 17 Apr 2024 13:05:05 -0700 Subject: [PATCH 13/27] 5509 Add Test cases for Custom Fields (#12312) * 5509 add content type data to model tests create and update * 5509 update use cf form data * 5509 update tests to use CustomFieldTypeChoices * 5509 update tests to check custom fields * Simplify custom fields used for testing * Move custom field data functions to testing.utils * Move validate_custom_field_data() into assertInstanceEqual() --------- Co-authored-by: Jeremy Stretch --- netbox/utilities/testing/base.py | 11 +++++--- netbox/utilities/testing/utils.py | 44 ++++++++++++++++++++++++++++++- netbox/utilities/testing/views.py | 13 ++++++--- 3 files changed, 61 insertions(+), 7 deletions(-) diff --git a/netbox/utilities/testing/base.py b/netbox/utilities/testing/base.py index aa2093a9a54..52e5d66ca8b 100644 --- a/netbox/utilities/testing/base.py +++ b/netbox/utilities/testing/base.py @@ -10,10 +10,11 @@ from netaddr import IPNetwork from taggit.managers import TaggableManager +from netbox.models.features import CustomFieldsMixin from users.models import ObjectPermission from utilities.permissions import resolve_permission_ct from utilities.utils import content_type_identifier -from .utils import extract_form_failures +from .utils import DUMMY_CF_DATA, extract_form_failures __all__ = ( 'ModelTestCase', @@ -166,8 +167,12 @@ def assertInstanceEqual(self, instance, data, exclude=None, api=False): model_dict = self.model_to_dict(instance, fields=fields, api=api) # Omit any dictionary keys which are not instance attributes or have been excluded - relevant_data = { + model_data = { k: v for k, v in data.items() if hasattr(instance, k) and k not in exclude } - self.assertDictEqual(model_dict, relevant_data) + self.assertDictEqual(model_dict, model_data) + + # Validate any custom field data, if present + if getattr(instance, 'custom_field_data', None): + self.assertDictEqual(instance.custom_field_data, DUMMY_CF_DATA) diff --git a/netbox/utilities/testing/utils.py b/netbox/utilities/testing/utils.py index fd6d72e2718..238f8170e21 100644 --- a/netbox/utilities/testing/utils.py +++ b/netbox/utilities/testing/utils.py @@ -1,13 +1,16 @@ +import json import logging import re from contextlib import contextmanager from django.contrib.auth import get_user_model from django.contrib.auth.models import Permission +from django.contrib.contenttypes.models import ContentType from django.utils.text import slugify from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site -from extras.models import Tag +from extras.choices import CustomFieldTypeChoices +from extras.models import CustomField, Tag from virtualization.models import Cluster, ClusterType, VirtualMachine @@ -102,3 +105,42 @@ def disable_warnings(logger_name): logger.setLevel(logging.ERROR) yield logger.setLevel(current_level) + + +# +# Custom field testing +# + +DUMMY_CF_DATA = { + 'text_field': 'foo123', + 'integer_field': 456, + 'decimal_field': 456.12, + 'boolean_field': True, + 'json_field': {'abc': 123}, +} + + +def add_custom_field_data(form_data, model): + """ + Create some custom fields for the model and add a value for each to the form data. + + Args: + form_data: The dictionary of form data to be updated + model: The model of the object the form seeks to create or modify + """ + content_type = ContentType.objects.get_for_model(model) + custom_fields = ( + CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, name='text_field', default='foo'), + CustomField(type=CustomFieldTypeChoices.TYPE_INTEGER, name='integer_field', default=123), + CustomField(type=CustomFieldTypeChoices.TYPE_DECIMAL, name='decimal_field', default=123.45), + CustomField(type=CustomFieldTypeChoices.TYPE_BOOLEAN, name='boolean_field', default=False), + CustomField(type=CustomFieldTypeChoices.TYPE_JSON, name='json_field', default='{"x": "y"}'), + ) + CustomField.objects.bulk_create(custom_fields) + for cf in custom_fields: + cf.content_types.set([content_type]) + + form_data.update({ + f'cf_{k}': v if type(v) is str else json.dumps(v) + for k, v in DUMMY_CF_DATA.items() + }) diff --git a/netbox/utilities/testing/views.py b/netbox/utilities/testing/views.py index daa44b905af..22371db3cf6 100644 --- a/netbox/utilities/testing/views.py +++ b/netbox/utilities/testing/views.py @@ -10,11 +10,11 @@ from extras.choices import ObjectChangeActionChoices from extras.models import ObjectChange -from netbox.models.features import ChangeLoggingMixin +from netbox.models.features import ChangeLoggingMixin, CustomFieldsMixin from users.models import ObjectPermission from utilities.choices import CSVDelimiterChoices, ImportFormatChoices from .base import ModelTestCase -from .utils import disable_warnings, post_data +from .utils import add_custom_field_data, disable_warnings, post_data __all__ = ( 'ModelViewTestCase', @@ -26,7 +26,6 @@ # UI Tests # - class ModelViewTestCase(ModelTestCase): """ Base TestCase for model views. Subclass to test individual views. @@ -166,6 +165,10 @@ def test_create_object_with_permission(self): # Try GET with model-level permission self.assertHttpStatus(self.client.get(self._get_url('add')), 200) + # Add custom field data if the model supports it + if issubclass(self.model, CustomFieldsMixin): + add_custom_field_data(self.form_data, self.model) + # Try POST with model-level permission initial_count = self._get_queryset().count() request = { @@ -265,6 +268,10 @@ def test_edit_object_with_permission(self): # Try GET with model-level permission self.assertHttpStatus(self.client.get(self._get_url('edit', instance)), 200) + # Add custom field data if the model supports it + if issubclass(self.model, CustomFieldsMixin): + add_custom_field_data(self.form_data, self.model) + # Try POST with model-level permission request = { 'path': self._get_url('edit', instance), From 19fe5ef25ccfa05697cb49b302f4b7deb11b1ec3 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 17 Apr 2024 16:18:57 -0400 Subject: [PATCH 14/27] Changelog for #15427, #15582, #15635 --- docs/release-notes/version-3.7.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/release-notes/version-3.7.md b/docs/release-notes/version-3.7.md index 48afc668887..db54082ddf8 100644 --- a/docs/release-notes/version-3.7.md +++ b/docs/release-notes/version-3.7.md @@ -4,11 +4,14 @@ ### Enhancements +* [#15427](https://github.com/netbox-community/netbox/issues/15427) - Enable compatibility with non-Amazon S3 providers for remote data sources * [#15640](https://github.com/netbox-community/netbox/issues/15640) - Add global search support for L2VPN identifiers * [#15644](https://github.com/netbox-community/netbox/issues/15644) - Introduce new configuration parameters for enabling HTTP Strict Transport Security (HSTS) ### Bug Fixes +* [#15582](https://github.com/netbox-community/netbox/issues/15582) - Fix permission constraints for synchronization of remote data sources +* [#15635](https://github.com/netbox-community/netbox/issues/15635) - Extend plugin removal instruction to include reindexing the global search cache * [#15654](https://github.com/netbox-community/netbox/issues/15654) - Fix `AttributeError` exception when attempting to save an incomplete tunnel termination * [#15668](https://github.com/netbox-community/netbox/issues/15668) - Fix permission required to display virtual disks tab on virtual machine UI view * [#15685](https://github.com/netbox-community/netbox/issues/15685) - Allow filtering cables by decimal values using UI filter form From f4c8f5f5b64c2f8a6dcc7a475667531660cc4825 Mon Sep 17 00:00:00 2001 From: Jeff Gehlbach Date: Thu, 18 Apr 2024 18:29:14 -0400 Subject: [PATCH 15/27] Add link to plugin certification program details in Plugin module of docs. Fixes #15769 --- docs/plugins/development/index.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/plugins/development/index.md b/docs/plugins/development/index.md index 4db1d5ef6ad..4d026cacd4f 100644 --- a/docs/plugins/development/index.md +++ b/docs/plugins/development/index.md @@ -3,6 +3,9 @@ !!! tip "Plugins Development Tutorial" Just getting started with plugins? Check out our [**NetBox Plugin Tutorial**](https://github.com/netbox-community/netbox-plugin-tutorial) on GitHub! This in-depth guide will walk you through the process of creating an entire plugin from scratch. It even includes a companion [demo plugin repo](https://github.com/netbox-community/netbox-plugin-demo) to ensure you can jump in at any step along the way. This will get you up and running with plugins in no time! +!!! tip "Plugin Certification Program" + NetBox Labs offers a [**Plugin Certification Program**](https://github.com/netbox-community/netbox/wiki/Plugin-Certification-Program) for plugin developers interested in establishing a co-maintainer relationship. The program aims to assure ongoing compatibility, maintainability, and commercial supportability of key plugins. + NetBox can be extended to support additional data models and functionality through the use of plugins. A plugin is essentially a self-contained [Django app](https://docs.djangoproject.com/en/stable/) which gets installed alongside NetBox to provide custom functionality. Multiple plugins can be installed in a single NetBox instance, and each plugin can be enabled and configured independently. !!! info "Django Development" From 3d3c1c315b46a7926356301a7ad93fc0b5d69e95 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 19 Apr 2024 16:15:32 -0400 Subject: [PATCH 16/27] Update documentation for the DEFAULT_LANGUAGE configuration parameter --- docs/configuration/system.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/docs/configuration/system.md b/docs/configuration/system.md index 806839778f9..28c09444bd6 100644 --- a/docs/configuration/system.md +++ b/docs/configuration/system.md @@ -16,10 +16,7 @@ BASE_PATH = 'netbox/' Default: `en-us` (US English) -Defines the default preferred language/locale for requests that do not specify one. This is used to alter e.g. the display of dates and numbers to fit the user's locale. See [this list](http://www.i18nguy.com/unicode/language-identifiers.html) of standard language codes. (This parameter maps to Django's [`LANGUAGE_CODE`](https://docs.djangoproject.com/en/stable/ref/settings/#language-code) internal setting.) - -!!! note - Altering this parameter will *not* change the language used in NetBox. We hope to provide translation support in a future NetBox release. +Defines the default preferred language/locale for requests that do not specify one. (This parameter maps to Django's [`LANGUAGE_CODE`](https://docs.djangoproject.com/en/stable/ref/settings/#language-code) internal setting.) --- From 94c31622acf4978720e9dda9592094ad5d5161b9 Mon Sep 17 00:00:00 2001 From: Arthur Date: Thu, 18 Apr 2024 14:59:21 -0700 Subject: [PATCH 17/27] 15588 set readonly nullable fields as allow_null=True --- netbox/dcim/api/serializers.py | 8 ++++---- netbox/ipam/api/serializers.py | 10 +++++----- netbox/virtualization/api/serializers.py | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index 053b3e9eacf..ce3cf4d9c7a 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -612,7 +612,7 @@ class InventoryItemTemplateSerializer(ValidatedModelSerializer): required=False, allow_null=True ) - component = serializers.SerializerMethodField(read_only=True) + component = serializers.SerializerMethodField(read_only=True, allow_null=True) _depth = serializers.IntegerField(source='level', read_only=True) class Meta: @@ -685,7 +685,7 @@ class DeviceSerializer(NetBoxModelSerializer): ) status = ChoiceField(choices=DeviceStatusChoices, required=False) airflow = ChoiceField(choices=DeviceAirflowChoices, allow_blank=True, required=False) - primary_ip = NestedIPAddressSerializer(read_only=True) + primary_ip = NestedIPAddressSerializer(read_only=True, allow_null=True) primary_ip4 = NestedIPAddressSerializer(required=False, allow_null=True) primary_ip6 = NestedIPAddressSerializer(required=False, allow_null=True) oob_ip = NestedIPAddressSerializer(required=False, allow_null=True) @@ -735,7 +735,7 @@ def get_device_role(self, obj): class DeviceWithConfigContextSerializer(DeviceSerializer): - config_context = serializers.SerializerMethodField(read_only=True) + config_context = serializers.SerializerMethodField(read_only=True, allow_null=True) class Meta(DeviceSerializer.Meta): fields = [ @@ -1067,7 +1067,7 @@ class InventoryItemSerializer(NetBoxModelSerializer): required=False, allow_null=True ) - component = serializers.SerializerMethodField(read_only=True) + component = serializers.SerializerMethodField(read_only=True, allow_null=True) _depth = serializers.IntegerField(source='level', read_only=True) class Meta: diff --git a/netbox/ipam/api/serializers.py b/netbox/ipam/api/serializers.py index 33aa55a93ed..8dca73d9420 100644 --- a/netbox/ipam/api/serializers.py +++ b/netbox/ipam/api/serializers.py @@ -262,7 +262,7 @@ class AvailableVLANSerializer(serializers.Serializer): Representation of a VLAN which does not exist in the database. """ vid = serializers.IntegerField(read_only=True) - group = NestedVLANGroupSerializer(read_only=True) + group = NestedVLANGroupSerializer(read_only=True, allow_null=True) def to_representation(self, instance): return { @@ -348,9 +348,9 @@ class AvailablePrefixSerializer(serializers.Serializer): """ Representation of a prefix which does not exist in the database. """ - family = serializers.IntegerField(read_only=True) + family = serializers.IntegerField(read_only=True, allow_null=True) prefix = serializers.CharField(read_only=True) - vrf = NestedVRFSerializer(read_only=True) + vrf = NestedVRFSerializer(read_only=True, allow_null=True) def to_representation(self, instance): if self.context.get('vrf'): @@ -429,9 +429,9 @@ class AvailableIPSerializer(serializers.Serializer): """ Representation of an IP address which does not exist in the database. """ - family = serializers.IntegerField(read_only=True) + family = serializers.IntegerField(read_only=True, allow_null=True) address = serializers.CharField(read_only=True) - vrf = NestedVRFSerializer(read_only=True) + vrf = NestedVRFSerializer(read_only=True, allow_null=True) description = serializers.CharField(required=False) def to_representation(self, instance): diff --git a/netbox/virtualization/api/serializers.py b/netbox/virtualization/api/serializers.py index 34e4037e9fe..a54643e6215 100644 --- a/netbox/virtualization/api/serializers.py +++ b/netbox/virtualization/api/serializers.py @@ -76,7 +76,7 @@ class VirtualMachineSerializer(NetBoxModelSerializer): role = NestedDeviceRoleSerializer(required=False, allow_null=True) tenant = NestedTenantSerializer(required=False, allow_null=True) platform = NestedPlatformSerializer(required=False, allow_null=True) - primary_ip = NestedIPAddressSerializer(read_only=True) + primary_ip = NestedIPAddressSerializer(read_only=True, allow_null=True) primary_ip4 = NestedIPAddressSerializer(required=False, allow_null=True) primary_ip6 = NestedIPAddressSerializer(required=False, allow_null=True) config_template = NestedConfigTemplateSerializer(required=False, allow_null=True, default=None) From c9de3128ca156000d70a38ff948c78603c02ea9a Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 19 Apr 2024 16:10:06 -0400 Subject: [PATCH 18/27] Fixes #15790: Fix live preview support for EventRule comments --- netbox/extras/forms/model_forms.py | 1 + 1 file changed, 1 insertion(+) diff --git a/netbox/extras/forms/model_forms.py b/netbox/extras/forms/model_forms.py index 8f9face4128..70b7a78a4c6 100644 --- a/netbox/extras/forms/model_forms.py +++ b/netbox/extras/forms/model_forms.py @@ -265,6 +265,7 @@ class EventRuleForm(NetBoxModelForm): required=False, help_text=_('Enter parameters to pass to the action in JSON format.') ) + comments = CommentField() fieldsets = ( (_('Event Rule'), ('name', 'description', 'content_types', 'enabled', 'tags')), From 88facbafbb6a754cb5f48fb541005bdabda744b6 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Fri, 19 Apr 2024 14:09:55 -0700 Subject: [PATCH 19/27] 15761 filter IKE Proposals on IKE Policy detail view (#15766) * 15761 filter IKEAProposals on IKEAPolicy detail view * Add test for ike_policy filter --------- Co-authored-by: Jeremy Stretch --- netbox/vpn/filtersets.py | 11 +++++++++++ netbox/vpn/tests/test_filtersets.py | 17 +++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/netbox/vpn/filtersets.py b/netbox/vpn/filtersets.py index 0647838a8ec..10f0834fb78 100644 --- a/netbox/vpn/filtersets.py +++ b/netbox/vpn/filtersets.py @@ -136,6 +136,17 @@ class IKEProposalFilterSet(NetBoxModelFilterSet): group = django_filters.MultipleChoiceFilter( choices=DHGroupChoices ) + ike_policy_id = django_filters.ModelMultipleChoiceFilter( + field_name='ike_policies', + queryset=IKEPolicy.objects.all(), + label=_('IKE policy (ID)'), + ) + ike_policy = django_filters.ModelMultipleChoiceFilter( + field_name='ike_policies__name', + queryset=IKEPolicy.objects.all(), + to_field_name='name', + label=_('IKE policy (name)'), + ) class Meta: model = IKEProposal diff --git a/netbox/vpn/tests/test_filtersets.py b/netbox/vpn/tests/test_filtersets.py index d4e80750d02..f11e63f1045 100644 --- a/netbox/vpn/tests/test_filtersets.py +++ b/netbox/vpn/tests/test_filtersets.py @@ -331,6 +331,16 @@ def setUpTestData(cls): ) IKEProposal.objects.bulk_create(ike_proposals) + ike_policies = ( + IKEPolicy(name='IKE Policy 1'), + IKEPolicy(name='IKE Policy 2'), + IKEPolicy(name='IKE Policy 3'), + ) + IKEPolicy.objects.bulk_create(ike_policies) + ike_policies[0].proposals.add(ike_proposals[0]) + ike_policies[1].proposals.add(ike_proposals[1]) + ike_policies[2].proposals.add(ike_proposals[2]) + def test_q(self): params = {'q': 'foobar1'} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) @@ -369,6 +379,13 @@ def test_sa_lifetime(self): params = {'sa_lifetime': [1000, 2000]} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + def test_ike_policy(self): + ike_policies = IKEPolicy.objects.all()[:2] + params = {'ike_policy_id': [ike_policies[0].pk, ike_policies[1].pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + params = {'ike_policy': [ike_policies[0].name, ike_policies[1].name]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + class IKEPolicyTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = IKEPolicy.objects.all() From 90d0104359a07f25185fc238d820005780ace6c4 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Mon, 22 Apr 2024 05:22:53 -0700 Subject: [PATCH 20/27] 15541 Add component selector to InventoryItemTemplate (#15759) * 15541 make inventoryitemtemplateform match inventoryitemform * 15541 set tab active --- netbox/dcim/forms/model_forms.py | 105 ++++++++++++++++-- netbox/dcim/views.py | 2 + .../dcim/inventoryitemtemplate_edit.html | 104 +++++++++++++++++ 3 files changed, 203 insertions(+), 8 deletions(-) create mode 100644 netbox/templates/dcim/inventoryitemtemplate_edit.html diff --git a/netbox/dcim/forms/model_forms.py b/netbox/dcim/forms/model_forms.py index 6773bc55f81..cee8fcfba87 100644 --- a/netbox/dcim/forms/model_forms.py +++ b/netbox/dcim/forms/model_forms.py @@ -976,21 +976,67 @@ class InventoryItemTemplateForm(ComponentTemplateForm): queryset=Manufacturer.objects.all(), required=False ) - component_type = ContentTypeChoiceField( - queryset=ContentType.objects.all(), - limit_choices_to=MODULAR_COMPONENT_TEMPLATE_MODELS, + # Assigned component selectors + consoleporttemplate = DynamicModelChoiceField( + queryset=ConsolePortTemplate.objects.all(), + required=False, + query_params={ + 'device_type_id': '$device_type' + }, + label=_('Console port template') + ) + consoleserverporttemplate = DynamicModelChoiceField( + queryset=ConsoleServerPortTemplate.objects.all(), + required=False, + query_params={ + 'device_type_id': '$device_type' + }, + label=_('Console server port template') + ) + frontporttemplate = DynamicModelChoiceField( + queryset=FrontPortTemplate.objects.all(), + required=False, + query_params={ + 'device_type_id': '$device_type' + }, + label=_('Front port template') + ) + interfacetemplate = DynamicModelChoiceField( + queryset=InterfaceTemplate.objects.all(), + required=False, + query_params={ + 'device_type_id': '$device_type' + }, + label=_('Interface template') + ) + poweroutlettemplate = DynamicModelChoiceField( + queryset=PowerOutletTemplate.objects.all(), required=False, - widget=forms.HiddenInput + query_params={ + 'device_type_id': '$device_type' + }, + label=_('Power outlet template') ) - component_id = forms.IntegerField( + powerporttemplate = DynamicModelChoiceField( + queryset=PowerPortTemplate.objects.all(), required=False, - widget=forms.HiddenInput + query_params={ + 'device_type_id': '$device_type' + }, + label=_('Power port template') + ) + rearporttemplate = DynamicModelChoiceField( + queryset=RearPortTemplate.objects.all(), + required=False, + query_params={ + 'device_type_id': '$device_type' + }, + label=_('Rear port template') ) fieldsets = ( (None, ( 'device_type', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'description', - 'component_type', 'component_id', )), ) @@ -998,9 +1044,52 @@ class Meta: model = InventoryItemTemplate fields = [ 'device_type', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'description', - 'component_type', 'component_id', ] + def __init__(self, *args, **kwargs): + instance = kwargs.get('instance') + initial = kwargs.get('initial', {}).copy() + component_type = initial.get('component_type') + component_id = initial.get('component_id') + + # Used for picking the default active tab for component selection + self.no_component = True + + if instance: + # When editing set the initial value for component selection + for component_model in ContentType.objects.filter(MODULAR_COMPONENT_TEMPLATE_MODELS): + if type(instance.component) is component_model.model_class(): + initial[component_model.model] = instance.component + self.no_component = False + break + elif component_type and component_id: + # When adding the InventoryItem from a component page + if content_type := ContentType.objects.filter(MODULAR_COMPONENT_TEMPLATE_MODELS).filter(pk=component_type).first(): + if component := content_type.model_class().objects.filter(pk=component_id).first(): + initial[content_type.model] = component + self.no_component = False + + kwargs['initial'] = initial + + super().__init__(*args, **kwargs) + + def clean(self): + super().clean() + + # Handle object assignment + selected_objects = [ + field for field in ( + 'consoleporttemplate', 'consoleserverporttemplate', 'frontporttemplate', 'interfacetemplate', + 'poweroutlettemplate', 'powerporttemplate', 'rearporttemplate' + ) if self.cleaned_data[field] + ] + if len(selected_objects) > 1: + raise forms.ValidationError(_("An InventoryItem can only be assigned to a single component.")) + elif selected_objects: + self.instance.component = self.cleaned_data[selected_objects[0]] + else: + self.instance.component = None + # # Device components diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index d0e92ff56c4..ce4bb5750d8 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -1656,6 +1656,7 @@ class InventoryItemTemplateCreateView(generic.ComponentCreateView): queryset = InventoryItemTemplate.objects.all() form = forms.InventoryItemTemplateCreateForm model_form = forms.InventoryItemTemplateForm + template_name = 'dcim/inventoryitemtemplate_edit.html' def alter_object(self, instance, request): # Set component (if any) @@ -1673,6 +1674,7 @@ def alter_object(self, instance, request): class InventoryItemTemplateEditView(generic.ObjectEditView): queryset = InventoryItemTemplate.objects.all() form = forms.InventoryItemTemplateForm + template_name = 'dcim/inventoryitemtemplate_edit.html' @register_model_view(InventoryItemTemplate, 'delete') diff --git a/netbox/templates/dcim/inventoryitemtemplate_edit.html b/netbox/templates/dcim/inventoryitemtemplate_edit.html new file mode 100644 index 00000000000..d3ac58e255f --- /dev/null +++ b/netbox/templates/dcim/inventoryitemtemplate_edit.html @@ -0,0 +1,104 @@ +{% extends 'generic/object_edit.html' %} +{% load static %} +{% load form_helpers %} +{% load helpers %} +{% load i18n %} + +{% block form %} +
+
+
{% trans "Inventory Item" %}
+
+ {% render_field form.device_type %} + {% render_field form.parent %} + {% render_field form.name %} + {% render_field form.label %} + {% render_field form.role %} + {% render_field form.description %} +
+ +
+
+
{% trans "Hardware" %}
+
+ {% render_field form.manufacturer %} + {% render_field form.part_id %} +
+ +
+
+
{% trans "Component Assignment" %}
+
+
+ +
+
+
+ {% render_field form.consoleporttemplate %} +
+
+ {% render_field form.consoleserverporttemplate %} +
+
+ {% render_field form.frontporttemplate %} +
+
+ {% render_field form.interfacetemplate %} +
+
+ {% render_field form.poweroutlettemplate %} +
+
+ {% render_field form.powerporttemplate %} +
+
+ {% render_field form.rearporttemplate %} +
+
+
+ + {% if form.custom_fields %} +
+
+
{% trans "Custom Fields" %}
+
+ {% render_custom_fields form %} +
+ {% endif %} +{% endblock %} From b6e38b2ebe0717b3a7606d36f5bc9b40444fb42c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markku=20Leini=C3=B6?= Date: Mon, 22 Apr 2024 16:25:16 +0300 Subject: [PATCH 21/27] Closes #14690: Pretty-format JSON fields in the config form (#15623) * Closes #14690: Pretty-format JSON fields in the config form * Revert changes * Use our own JSONField for config parameters for pretty editor outputs * Compare identity instead of equality --- netbox/core/forms/model_forms.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/netbox/core/forms/model_forms.py b/netbox/core/forms/model_forms.py index ae891dd59e1..0f4f971dca2 100644 --- a/netbox/core/forms/model_forms.py +++ b/netbox/core/forms/model_forms.py @@ -3,6 +3,7 @@ from django import forms from django.conf import settings +from django.forms.fields import JSONField as _JSONField from django.utils.translation import gettext_lazy as _ from core.forms.mixins import SyncedDataMixin @@ -12,7 +13,7 @@ from netbox.registry import registry from netbox.utils import get_data_backend_choices from utilities.forms import BootstrapMixin, get_field_value -from utilities.forms.fields import CommentField +from utilities.forms.fields import CommentField, JSONField from utilities.forms.widgets import HTMXSelect __all__ = ( @@ -132,6 +133,9 @@ def __new__(mcs, name, bases, attrs): 'help_text': param.description, } field_kwargs.update(**param.field_kwargs) + if param.field is _JSONField: + # Replace with our own JSONField to get pretty JSON in config editor + param.field = JSONField param_fields[param.name] = param.field(**field_kwargs) attrs.update(param_fields) From ebe504c8252ca29cbdbc0a0bbf2ee0e0167fffcd Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 22 Apr 2024 09:52:03 -0400 Subject: [PATCH 22/27] Closes #15664: Restore usage of READTHEDOCS env variable --- docs/_theme/main.html | 4 ++-- mkdocs.yml | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/_theme/main.html b/docs/_theme/main.html index 3ff44b9cbf9..4dfc4e14e8a 100644 --- a/docs/_theme/main.html +++ b/docs/_theme/main.html @@ -2,8 +2,8 @@ {% block site_meta %} {{ super() }} - {# Disable search indexing unless we're building for ReadTheDocs (see #10496) #} - {% if page.canonical_url != 'https://docs.netbox.dev/' %} + {# Disable search indexing unless we're building for ReadTheDocs #} + {% if not config.extra.readthedocs %} {% endif %} {% endblock %} diff --git a/mkdocs.yml b/mkdocs.yml index c04ef519f47..5aa6572302b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -42,6 +42,7 @@ plugins: show_root_toc_entry: false show_source: false extra: + readthedocs: !ENV READTHEDOCS social: - icon: fontawesome/brands/github link: https://github.com/netbox-community/netbox From e87877b6ea15231df1e7e10a74c69a4e1c6407aa Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 22 Apr 2024 09:38:40 -0400 Subject: [PATCH 23/27] Fixes #15771: Show id field as supported on all bulk import forms --- netbox/netbox/forms/base.py | 5 ----- netbox/utilities/forms/forms.py | 6 ++++++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/netbox/netbox/forms/base.py b/netbox/netbox/forms/base.py index 0b0e2036e1f..736a2fe9b4e 100644 --- a/netbox/netbox/forms/base.py +++ b/netbox/netbox/forms/base.py @@ -73,11 +73,6 @@ class NetBoxModelImportForm(CSVModelForm, NetBoxModelForm): """ Base form for creating a NetBox objects from CSV data. Used for bulk importing. """ - id = forms.IntegerField( - label=_('Id'), - required=False, - help_text='Numeric ID of an existing object to update (if not creating a new object)' - ) tags = CSVModelMultipleChoiceField( label=_('Tags'), queryset=Tag.objects.all(), diff --git a/netbox/utilities/forms/forms.py b/netbox/utilities/forms/forms.py index 54c9e41cbaa..93227b1d057 100644 --- a/netbox/utilities/forms/forms.py +++ b/netbox/utilities/forms/forms.py @@ -70,6 +70,12 @@ class CSVModelForm(forms.ModelForm): """ ModelForm used for the import of objects in CSV format. """ + id = forms.IntegerField( + label=_('ID'), + required=False, + help_text=_('Numeric ID of an existing object to update (if not creating a new object)') + ) + def __init__(self, *args, headers=None, **kwargs): self.headers = headers or {} super().__init__(*args, **kwargs) From 6b8bfe9947db387ef00aeb026c5357400e0d23ea Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 22 Apr 2024 11:25:21 -0400 Subject: [PATCH 24/27] Changelog for #14690, #15541, #15588, #15761, #15771, #15790 --- docs/release-notes/version-3.7.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/release-notes/version-3.7.md b/docs/release-notes/version-3.7.md index db54082ddf8..e42720eaf07 100644 --- a/docs/release-notes/version-3.7.md +++ b/docs/release-notes/version-3.7.md @@ -4,17 +4,23 @@ ### Enhancements +* [#14690](https://github.com/netbox-community/netbox/issues/14690) - Improve rendering of JSON data in configuration form * [#15427](https://github.com/netbox-community/netbox/issues/15427) - Enable compatibility with non-Amazon S3 providers for remote data sources * [#15640](https://github.com/netbox-community/netbox/issues/15640) - Add global search support for L2VPN identifiers * [#15644](https://github.com/netbox-community/netbox/issues/15644) - Introduce new configuration parameters for enabling HTTP Strict Transport Security (HSTS) ### Bug Fixes +* [#15541](https://github.com/netbox-community/netbox/issues/15541) - Restore ability to modify assigned component template when adding/modifying an inventory item template * [#15582](https://github.com/netbox-community/netbox/issues/15582) - Fix permission constraints for synchronization of remote data sources +* [#15588](https://github.com/netbox-community/netbox/issues/15588) - Correct OpenAPI schema definitions for read-only fields which may return null values * [#15635](https://github.com/netbox-community/netbox/issues/15635) - Extend plugin removal instruction to include reindexing the global search cache * [#15654](https://github.com/netbox-community/netbox/issues/15654) - Fix `AttributeError` exception when attempting to save an incomplete tunnel termination * [#15668](https://github.com/netbox-community/netbox/issues/15668) - Fix permission required to display virtual disks tab on virtual machine UI view * [#15685](https://github.com/netbox-community/netbox/issues/15685) - Allow filtering cables by decimal values using UI filter form +* [#15761](https://github.com/netbox-community/netbox/issues/15761) - Add missing `ike_policy` & `ike_policy_id` filters for IKE proposals +* [#15771](https://github.com/netbox-community/netbox/issues/15771) - Include `id` in list of supported fields for all bulk import forms +* [#15790](https://github.com/netbox-community/netbox/issues/15790) - Fix live preview support for EventRule comments --- From 5d95d49268c2b19ad6f4850b0207897d8aa9e604 Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Mon, 22 Apr 2024 11:28:04 -0400 Subject: [PATCH 25/27] Update translations --- netbox/translations/fr/LC_MESSAGES/django.po | 9 +++++---- netbox/translations/ja/LC_MESSAGES/django.po | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/netbox/translations/fr/LC_MESSAGES/django.po b/netbox/translations/fr/LC_MESSAGES/django.po index 8c8a362ff3a..cc489f360ff 100644 --- a/netbox/translations/fr/LC_MESSAGES/django.po +++ b/netbox/translations/fr/LC_MESSAGES/django.po @@ -6,6 +6,7 @@ # Translators: # Jonathan Senecal, 2024 # Jeremy Stretch, 2024 +# Quentin Laurent, 2024 # #, fuzzy msgid "" @@ -14,7 +15,7 @@ msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-04-04 19:11+0000\n" "PO-Revision-Date: 2023-10-30 17:48+0000\n" -"Last-Translator: Jeremy Stretch, 2024\n" +"Last-Translator: Quentin Laurent, 2024\n" "Language-Team: French (https://app.transifex.com/netbox-community/teams/178115/fr/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -3716,7 +3717,7 @@ msgstr "Réservation" #: dcim/forms/model_forms.py:301 dcim/forms/model_forms.py:384 #: utilities/forms/fields/fields.py:47 msgid "Slug" -msgstr "limace" +msgstr "Identifiant" #: dcim/forms/model_forms.py:308 templates/dcim/devicetype.html:12 msgid "Chassis" @@ -5813,7 +5814,7 @@ msgstr "Poids maximum" #: ipam/tables/asn.py:66 netbox/navigation/menu.py:16 #: netbox/navigation/menu.py:18 msgid "Sites" -msgstr "Des sites" +msgstr "Sites" #: dcim/tests/test_api.py:49 msgid "Test case must set peer_termination_type" @@ -13355,7 +13356,7 @@ msgstr "" #: utilities/forms/fields/fields.py:48 msgid "URL-friendly unique shorthand" -msgstr "Raccourci unique et convivial pour les URL" +msgstr "Identifiant unique utilisable dans les URL" #: utilities/forms/fields/fields.py:101 msgid "Enter context data in JSON format." diff --git a/netbox/translations/ja/LC_MESSAGES/django.po b/netbox/translations/ja/LC_MESSAGES/django.po index 5b79cf0dd6e..34193822c20 100644 --- a/netbox/translations/ja/LC_MESSAGES/django.po +++ b/netbox/translations/ja/LC_MESSAGES/django.po @@ -5,8 +5,8 @@ # # Translators: # Tatsuya Ueda , 2024 -# teapot, 2024 # Jeremy Stretch, 2024 +# teapot, 2024 # #, fuzzy msgid "" @@ -15,7 +15,7 @@ msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-04-04 19:11+0000\n" "PO-Revision-Date: 2023-10-30 17:48+0000\n" -"Last-Translator: Jeremy Stretch, 2024\n" +"Last-Translator: teapot, 2024\n" "Language-Team: Japanese (https://app.transifex.com/netbox-community/teams/178115/ja/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -7681,7 +7681,7 @@ msgstr "プレフィックス内およびプレフィックスを含む" #: ipam/filtersets.py:259 msgid "Prefixes which contain this prefix or IP" -msgstr "このプレフィックスまたは IP を含むプレフィックス" +msgstr "このプレフィックス / IP を含むプレフィックス" #: ipam/filtersets.py:270 ipam/filtersets.py:538 ipam/forms/bulk_edit.py:326 #: ipam/forms/filtersets.py:191 ipam/forms/filtersets.py:317 @@ -7700,11 +7700,11 @@ msgstr "VLAN 番号 (1-4094)" #: ipam/forms/model_forms.py:430 templates/tenancy/contact.html:54 #: tenancy/forms/bulk_edit.py:112 msgid "Address" -msgstr "住所" +msgstr "アドレス" #: ipam/filtersets.py:445 msgid "Ranges which contain this prefix or IP" -msgstr "このプレフィックスまたは IP を含む範囲" +msgstr "このプレフィックス / IP を含む範囲" #: ipam/filtersets.py:473 ipam/filtersets.py:529 msgid "Parent prefix" @@ -7743,11 +7743,11 @@ msgstr "FHRP グループ (ID)" #: ipam/filtersets.py:618 msgid "Is assigned to an interface" -msgstr "インタフェースに割り当てられている" +msgstr "インタフェースに割り当てられているか" #: ipam/filtersets.py:622 msgid "Is assigned" -msgstr "割り当てられている" +msgstr "割当済みか" #: ipam/filtersets.py:1047 msgid "IP address (ID)" @@ -7881,7 +7881,7 @@ msgstr "子 VLAN VID の最小値" #: ipam/forms/bulk_edit.py:420 msgid "Maximum child VLAN VID" -msgstr "子 VLAN VID の最大数" +msgstr "子 VLAN VID の最大値" #: ipam/forms/bulk_edit.py:428 ipam/forms/model_forms.py:531 msgid "Scope type" @@ -7905,11 +7905,11 @@ msgstr "ポート" #: ipam/forms/bulk_import.py:47 msgid "Import route targets" -msgstr "ルートターゲットをインポート" +msgstr "インポートルートターゲット" #: ipam/forms/bulk_import.py:53 msgid "Export route targets" -msgstr "ルートターゲットをエクスポートする" +msgstr "エクスポートルートターゲット" #: ipam/forms/bulk_import.py:91 ipam/forms/bulk_import.py:111 #: ipam/forms/bulk_import.py:131 From 1eca1c3d1764ff32a67283f94402b752c01be34d Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Mon, 22 Apr 2024 08:42:20 -0700 Subject: [PATCH 26/27] 15803 localize help_text (#15804) --- docs/plugins/development/forms.md | 2 +- netbox/dcim/api/serializers.py | 2 +- netbox/dcim/forms/bulk_import.py | 4 ++-- netbox/netbox/forms/base.py | 2 +- netbox/wireless/forms/bulk_import.py | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/plugins/development/forms.md b/docs/plugins/development/forms.md index 31751855e50..1f844dc1b12 100644 --- a/docs/plugins/development/forms.md +++ b/docs/plugins/development/forms.md @@ -62,7 +62,7 @@ class MyModelImportForm(NetBoxModelImportForm): site = CSVModelChoiceField( queryset=Site.objects.all(), to_field_name='name', - help_text='Assigned site' + help_text=_('Assigned site') ) class Meta: diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index ce3cf4d9c7a..e07d17acaf9 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -668,7 +668,7 @@ class DeviceSerializer(NetBoxModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:device-detail') device_type = NestedDeviceTypeSerializer() role = NestedDeviceRoleSerializer() - device_role = NestedDeviceRoleSerializer(read_only=True, help_text='Deprecated in v3.6 in favor of `role`.') + device_role = NestedDeviceRoleSerializer(read_only=True, help_text=_('Deprecated in v3.6 in favor of `role`.')) tenant = NestedTenantSerializer(required=False, allow_null=True, default=None) platform = NestedPlatformSerializer(required=False, allow_null=True) site = NestedSiteSerializer() diff --git a/netbox/dcim/forms/bulk_import.py b/netbox/dcim/forms/bulk_import.py index 47974096fad..95e6c409849 100644 --- a/netbox/dcim/forms/bulk_import.py +++ b/netbox/dcim/forms/bulk_import.py @@ -1373,14 +1373,14 @@ class VirtualDeviceContextImportForm(NetBoxModelImportForm): label=_('Device'), queryset=Device.objects.all(), to_field_name='name', - help_text='Assigned role' + help_text=_('Assigned role') ) tenant = CSVModelChoiceField( label=_('Tenant'), queryset=Tenant.objects.all(), required=False, to_field_name='name', - help_text='Assigned tenant' + help_text=_('Assigned tenant') ) status = CSVChoiceField( label=_('Status'), diff --git a/netbox/netbox/forms/base.py b/netbox/netbox/forms/base.py index 736a2fe9b4e..1a4155aaba8 100644 --- a/netbox/netbox/forms/base.py +++ b/netbox/netbox/forms/base.py @@ -78,7 +78,7 @@ class NetBoxModelImportForm(CSVModelForm, NetBoxModelForm): queryset=Tag.objects.all(), required=False, to_field_name='slug', - help_text='Tag slugs separated by commas, encased with double quotes (e.g. "tag1,tag2,tag3")' + help_text=_('Tag slugs separated by commas, encased with double quotes (e.g. "tag1,tag2,tag3")') ) def _get_custom_fields(self, content_type): diff --git a/netbox/wireless/forms/bulk_import.py b/netbox/wireless/forms/bulk_import.py index c0e2dfb54cb..38bc37360af 100644 --- a/netbox/wireless/forms/bulk_import.py +++ b/netbox/wireless/forms/bulk_import.py @@ -42,7 +42,7 @@ class WirelessLANImportForm(NetBoxModelImportForm): status = CSVChoiceField( label=_('Status'), choices=WirelessLANStatusChoices, - help_text='Operational status' + help_text=_('Operational status') ) vlan = CSVModelChoiceField( label=_('VLAN'), From a61e20849b5ec9493e33e64560db5588ef9690ae Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 22 Apr 2024 11:46:03 -0400 Subject: [PATCH 27/27] Release v3.7.6 --- .github/ISSUE_TEMPLATE/bug_report.yaml | 2 +- .github/ISSUE_TEMPLATE/feature_request.yaml | 2 +- base_requirements.txt | 3 ++- docs/release-notes/version-3.7.md | 2 +- netbox/netbox/settings.py | 2 +- netbox/translations/fr/LC_MESSAGES/django.mo | Bin 220300 -> 220302 bytes netbox/translations/ja/LC_MESSAGES/django.mo | Bin 233614 -> 233577 bytes requirements.txt | 6 +++--- 8 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 60d9a709181..8fc9bc205b9 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -26,7 +26,7 @@ body: attributes: label: NetBox Version description: What version of NetBox are you currently running? - placeholder: v3.7.5 + placeholder: v3.7.6 validations: required: true - type: dropdown diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index 22c65f27647..3e7372484d1 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.7.5 + placeholder: v3.7.6 validations: required: true - type: dropdown diff --git a/base_requirements.txt b/base_requirements.txt index 642450cf85c..8971ebe1e1b 100644 --- a/base_requirements.txt +++ b/base_requirements.txt @@ -61,7 +61,8 @@ django-timezone-field # A REST API framework for Django projects # https://www.django-rest-framework.org/community/release-notes/ -djangorestframework +# Pinned to 3.14 for NetBox v3.7 +djangorestframework<3.15 # Sane and flexible OpenAPI 3 schema generation for Django REST framework. # https://github.com/tfranzel/drf-spectacular/blob/master/CHANGELOG.rst diff --git a/docs/release-notes/version-3.7.md b/docs/release-notes/version-3.7.md index e42720eaf07..062dc3fe734 100644 --- a/docs/release-notes/version-3.7.md +++ b/docs/release-notes/version-3.7.md @@ -1,6 +1,6 @@ # NetBox v3.7 -## v3.7.6 (FUTURE) +## v3.7.6 (2024-04-22) ### Enhancements diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 55002aa87f2..764aa049a67 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -28,7 +28,7 @@ # Environment setup # -VERSION = '3.7.6-dev' +VERSION = '3.7.6' # Hostname HOSTNAME = platform.node() diff --git a/netbox/translations/fr/LC_MESSAGES/django.mo b/netbox/translations/fr/LC_MESSAGES/django.mo index b5dac2ef5cd89f65ae7147f48d886b2a5d232d7f..4bb44f73d388f5c190a23b3bb4df7429987373e2 100644 GIT binary patch delta 23075 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 HtI7WXtFt|y diff --git a/netbox/translations/ja/LC_MESSAGES/django.mo b/netbox/translations/ja/LC_MESSAGES/django.mo index 2962616319d987b8cfaa1e2dd83024464a28c60c..da77c423a93b827c487bdcf2cb38945c431e816c 100644 GIT binary patch delta 23136 zcmXxrb%0gH`^WLay-T_XOLuo84bt7+T}yXL9bggZmZfD`loU}KNoi?B`4SQ?B_fDO zC?dc2=g#x{>oqfH&df7)&bf=uiFyCm)$ec3)z7#3oo9 zXW|l!!dza!`vlixI2MZ+@G4?OOod}HG0wxxxC~R`Zp?*eFgZTM^cV;PeD5WROo4#6 z7E=ZT-ay=qLojbxz-xd@F$vzsw)hN(V*U65Zx9~9c332V3vfCvBp;D5;C+Ul;|Q#o z$d!MKb;vhP><7GBB$gx&cpu^|GgFd)H;;TgV(7A%a3le;M_g9*vk zK}~rROor_+h`Kn7vVYzqRLaE z_CjjZ-pPUKu#}ZILIu{|uJ=RjwF#J-c)TyH!annic>}Xk@iD5w_fxor@}g#|2jTBDA33TqDC~uj74p(@61!E8!w=q zgmI|*`(<<^8ex8n+B=_O5Bw5!Uy4jFpxmgLuIiIeMBP!3#=+PXC!qp~L){q29Po}} zYCMbAP@8pq7PmA%pz?p=UCf#_;Pu9^Y=O|DdjM)6&rtonMlG2iKYPIINFp^h!(kYS z$FT(#&f%U^lTrE8sD`rT40xL`FKXs~KsEd;YDQjKxtA;8jUk@~JL7z8jL(oqyzf=b z?ViosP-~n&kFzFfnPkTh`PTX>iN(P6~Ii?z*b>t+>6@u&#=AD zf7bjiqOqtoUy15yALhbK<|`}DRKRugfmsdJQ8QG-JyA=v(DKXd`bkuP*Rc`)hW=6# z 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 P3KUJO#MbA5n=Aeg7)`GJ delta 23150 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 diff --git a/requirements.txt b/requirements.txt index 2fbc25b219c..78b423692ce 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,11 +18,11 @@ drf-spectacular==0.27.2 drf-spectacular-sidecar==2024.4.1 feedparser==6.0.11 graphene-django==3.0.0 -gunicorn==21.2.0 +gunicorn==22.0.0 Jinja2==3.1.3 Markdown==3.6 -mkdocs-material==9.5.17 -mkdocstrings[python-legacy]==0.24.2 +mkdocs-material==9.5.18 +mkdocstrings[python-legacy]==0.24.3 netaddr==1.2.1 Pillow==10.3.0 psycopg[binary,pool]==3.1.18