diff --git a/.github/ISSUE_TEMPLATE/01-feature_request.yaml b/.github/ISSUE_TEMPLATE/01-feature_request.yaml index e4eb15d4f..7cf1ea56c 100644 --- a/.github/ISSUE_TEMPLATE/01-feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/01-feature_request.yaml @@ -15,7 +15,7 @@ body: attributes: label: NetBox version description: What version of NetBox are you currently running? - placeholder: v4.2.7 + placeholder: v4.2.8 validations: required: true - type: dropdown diff --git a/.github/ISSUE_TEMPLATE/02-bug_report.yaml b/.github/ISSUE_TEMPLATE/02-bug_report.yaml index f411aeaed..a39404a9d 100644 --- a/.github/ISSUE_TEMPLATE/02-bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/02-bug_report.yaml @@ -27,7 +27,7 @@ body: attributes: label: NetBox Version description: What version of NetBox are you currently running? - placeholder: v4.2.7 + placeholder: v4.2.8 validations: required: true - type: dropdown diff --git a/base_requirements.txt b/base_requirements.txt index be44944e3..9452cbd64 100644 --- a/base_requirements.txt +++ b/base_requirements.txt @@ -8,9 +8,6 @@ django-cors-headers # Runtime UI tool for debugging Django # https://github.com/jazzband/django-debug-toolbar/blob/main/docs/changes.rst -# See: https://django-debug-toolbar.readthedocs.io/en/latest/changes.html#id1 -# "Wrap SHOW_TOOLBAR_CALLBACK function with sync_to_async or async_to_sync to allow sync/async -# compatibility." breaks stawberry-graphql-django at version 0.52.0 (current) django-debug-toolbar # Library for writing reusable URL query filters @@ -135,8 +132,7 @@ strawberry-graphql # Strawberry GraphQL Django extension # https://github.com/strawberry-graphql/strawberry-django/releases -# Pinned to v0.52.0 for suspected upstream bug; see #18329 -strawberry-graphql-django==0.52.0 +strawberry-graphql-django # SVG image rendering (used for rack elevations) # https://github.com/mozman/svgwrite/blob/master/NEWS.rst diff --git a/docs/installation/3-netbox.md b/docs/installation/3-netbox.md index 60d60d4f0..0a5f51702 100644 --- a/docs/installation/3-netbox.md +++ b/docs/installation/3-netbox.md @@ -246,7 +246,7 @@ Once NetBox has been configured, we're ready to proceed with the actual installa * Create a Python virtual environment * Installs all required Python packages -* Run database schema migrations +* Run database schema migrations (skip with `--readonly`) * Builds the documentation locally (for offline use) * Aggregate static resource files on disk @@ -266,6 +266,9 @@ sudo PYTHON=/usr/bin/python3.10 /opt/netbox/upgrade.sh !!! note Upon completion, the upgrade script may warn that no existing virtual environment was detected. As this is a new installation, this warning can be safely ignored. +!!! note + To run the script on a node connected to a database in read-only mode, include the `--readonly` parameter. This will skip the application of any database migrations. + ## Create a Super User NetBox does not come with any predefined user accounts. You'll need to create a super user (administrative account) to be able to log into NetBox. First, enter the Python virtual environment created by the upgrade script: diff --git a/docs/installation/upgrading.md b/docs/installation/upgrading.md index 993d3987d..42b8c0187 100644 --- a/docs/installation/upgrading.md +++ b/docs/installation/upgrading.md @@ -124,17 +124,19 @@ sudo cp /opt/netbox-$OLDVER/gunicorn.py /opt/netbox/ ### Option B: Check Out a Git Release -This guide assumes that NetBox is installed at `/opt/netbox`. First, determine the latest release either by visiting our [releases page](https://github.com/netbox-community/netbox/releases) or by running the following `git` commands: +This guide assumes that NetBox is installed at `/opt/netbox`. First, determine the latest release either by visiting our [releases page](https://github.com/netbox-community/netbox/releases) or by running the following command: ``` -sudo git fetch --tags -git describe --tags $(git rev-list --tags --max-count=1) +git ls-remote --tags https://github.com/netbox-community/netbox.git \ + | grep -o 'refs/tags/v[0-9]*\.[0-9]*\.[0-9]*$' \ + | tail -n 1 \ + | sed 's|refs/tags/||' ``` -Check out the desired release by specifying its tag: +Check out the desired release by specifying its tag. For example: ``` -sudo git checkout v4.2.0 +sudo git checkout v4.2.7 ``` ## 4. Run the Upgrade Script @@ -152,6 +154,9 @@ sudo ./upgrade.sh sudo PYTHON=/usr/bin/python3.10 ./upgrade.sh ``` +!!! note + To run the script on a node connected to a database in read-only mode, include the `--readonly` parameter. This will skip the application of any database migrations. + This script performs the following actions: * Destroys and rebuilds the Python virtual environment diff --git a/docs/release-notes/version-4.2.md b/docs/release-notes/version-4.2.md index 4d291ee0a..612fb9b02 100644 --- a/docs/release-notes/version-4.2.md +++ b/docs/release-notes/version-4.2.md @@ -1,5 +1,35 @@ # NetBox v4.2 +## v4.2.8 (2025-04-22) + +### Enhancements + +* [#17136](https://github.com/netbox-community/netbox/issues/17136) - Introduce the `--readonly` flag on upgrade script +* [#17908](https://github.com/netbox-community/netbox/issues/17908) - Add trace buttons to terminations under cable view +* [#18879](https://github.com/netbox-community/netbox/issues/18879) - Enable filtering prefixes by group of assigned VLAN +* [#18976](https://github.com/netbox-community/netbox/issues/18976) - Include FHRP group name on interface lists +* [#18978](https://github.com/netbox-community/netbox/issues/18978) - Add 802.1Q mode to interface filter form +* [#19038](https://github.com/netbox-community/netbox/issues/19038) - Show count of related VLAN groups under cluster view +* [#19040](https://github.com/netbox-community/netbox/issues/19040) - Add "copy to clipboard" button for rendered config +* [#19056](https://github.com/netbox-community/netbox/issues/19056) - Enable filtering devices by location slug +* [#19196](https://github.com/netbox-community/netbox/issues/19196) - Add filtering by VLAN translation policy to interface filter forms + +### Bug Fixes + +* [#18500](https://github.com/netbox-community/netbox/issues/18500) - `prepare_cloned_fields()` should validate cloning support on model +* [#18669](https://github.com/netbox-community/netbox/issues/18669) - Ensure default custom field values are respected when creating objects via the REST API +* [#18881](https://github.com/netbox-community/netbox/issues/18881) - Include missing related object counts under certain views +* [#18955](https://github.com/netbox-community/netbox/issues/18955) - Omit "clear" button on required choice fields +* [#18959](https://github.com/netbox-community/netbox/issues/18959) - Preserve ordering of terminations in cable traces +* [#18961](https://github.com/netbox-community/netbox/issues/18961) - Virtual chassis form should exclude members of other VCs when adding members +* [#19166](https://github.com/netbox-community/netbox/issues/19166) - Fix custom field choices bulk import support for `base_choices` +* [#19189](https://github.com/netbox-community/netbox/issues/19189) - The `load_yaml()` convenience method on BaseScript should use SafeLoader +* [#19195](https://github.com/netbox-community/netbox/issues/19195) - Language cookie should respect `SESSION_COOKIE_SECURE` value +* [#19230](https://github.com/netbox-community/netbox/issues/19230) - Allow label reuse when creating multiple components from a pattern +* [#19268](https://github.com/netbox-community/netbox/issues/19268) - Restore editing conflict protection for several object forms + +--- + ## v4.2.7 (2025-04-10) ### Enhancements diff --git a/netbox/account/views.py b/netbox/account/views.py index 43df5436a..835ae81c2 100644 --- a/netbox/account/views.py +++ b/netbox/account/views.py @@ -28,6 +28,7 @@ from netbox.config import get_config from netbox.views import generic from users import forms, tables from users.models import UserConfig +from utilities.string import remove_linebreaks from utilities.views import register_model_view @@ -133,7 +134,8 @@ class LoginView(View): return response else: - logger.debug(f"Login form validation failed for username: {form['username'].value()}") + username = form['username'].value() + logger.debug(f"Login form validation failed for username: {remove_linebreaks(username)}") return render(request, self.template_name, { 'form': form, @@ -145,10 +147,10 @@ class LoginView(View): redirect_url = data.get('next', settings.LOGIN_REDIRECT_URL) if redirect_url and url_has_allowed_host_and_scheme(redirect_url, allowed_hosts=None): - logger.debug(f"Redirecting user to {redirect_url}") + logger.debug(f"Redirecting user to {remove_linebreaks(redirect_url)}") else: if redirect_url: - logger.warning(f"Ignoring unsafe 'next' URL passed to login form: {redirect_url}") + logger.warning(f"Ignoring unsafe 'next' URL passed to login form: {remove_linebreaks(redirect_url)}") redirect_url = reverse('home') return HttpResponseRedirect(redirect_url) diff --git a/netbox/dcim/forms/object_create.py b/netbox/dcim/forms/object_create.py index b279aec9b..1e898a386 100644 --- a/netbox/dcim/forms/object_create.py +++ b/netbox/dcim/forms/object_create.py @@ -55,19 +55,23 @@ class ComponentCreateForm(forms.Form): def clean(self): super().clean() - # Validate that all replication fields generate an equal number of values + # Validate that all replication fields generate an equal number of values (or a single value) if not (patterns := self.cleaned_data.get(self.replication_fields[0])): return - pattern_count = len(patterns) for field_name in self.replication_fields: value_count = len(self.cleaned_data[field_name]) - if self.cleaned_data[field_name] and value_count != pattern_count: - raise forms.ValidationError({ - field_name: _( - "The provided pattern specifies {value_count} values, but {pattern_count} are expected." - ).format(value_count=value_count, pattern_count=pattern_count) - }, code='label_pattern_mismatch') + if self.cleaned_data[field_name]: + if value_count == 1: + # If the field resolves to a single value (because no pattern was used), multiply it by the number + # of expected values. This allows us to reuse the same label when creating multiple components. + self.cleaned_data[field_name] = self.cleaned_data[field_name] * pattern_count + elif value_count != pattern_count: + raise forms.ValidationError({ + field_name: _( + "The provided pattern specifies {value_count} values, but {pattern_count} are expected." + ).format(value_count=value_count, pattern_count=pattern_count) + }, code='label_pattern_mismatch') # @@ -404,6 +408,7 @@ class VirtualChassisCreateForm(NetBoxModelForm): queryset=Device.objects.all(), required=False, query_params={ + 'virtual_chassis_id': 'null', 'site_id': '$site', 'rack_id': '$rack', } diff --git a/netbox/dcim/svg/cables.py b/netbox/dcim/svg/cables.py index 58fa27c6b..31ec06100 100644 --- a/netbox/dcim/svg/cables.py +++ b/netbox/dcim/svg/cables.py @@ -225,8 +225,7 @@ class CableTraceSVG: """ nodes_height = 0 nodes = [] - # Sort them by name to make renders more readable - for i, term in enumerate(sorted(terminations, key=lambda x: str(x))): + for i, term in enumerate(terminations): node = Node( position=(offset_x + i * width, self.cursor), width=width, diff --git a/netbox/dcim/tables/template_code.py b/netbox/dcim/tables/template_code.py index aa5978d93..3b2a9b4c3 100644 --- a/netbox/dcim/tables/template_code.py +++ b/netbox/dcim/tables/template_code.py @@ -64,7 +64,7 @@ INTERFACE_IPADDRESSES = """ INTERFACE_FHRPGROUPS = """ {% for assignment in value.all %} - {{ assignment.group.get_protocol_display }}: {{ assignment.group.group_id }} + {{ assignment.group }} {% endfor %} """ diff --git a/netbox/extras/forms/bulk_import.py b/netbox/extras/forms/bulk_import.py index 655a5d6ca..35c1cbc22 100644 --- a/netbox/extras/forms/bulk_import.py +++ b/netbox/extras/forms/bulk_import.py @@ -96,7 +96,7 @@ class CustomFieldChoiceSetImportForm(CSVModelForm): class Meta: model = CustomFieldChoiceSet fields = ( - 'name', 'description', 'extra_choices', 'order_alphabetically', + 'name', 'description', 'base_choices', 'extra_choices', 'order_alphabetically', ) def clean_extra_choices(self): diff --git a/netbox/project-static/dist/netbox.css b/netbox/project-static/dist/netbox.css index 9405ecda0..08fdc7147 100644 Binary files a/netbox/project-static/dist/netbox.css and b/netbox/project-static/dist/netbox.css differ diff --git a/netbox/project-static/dist/netbox.js b/netbox/project-static/dist/netbox.js index c37c70722..814f9f568 100644 Binary files a/netbox/project-static/dist/netbox.js and b/netbox/project-static/dist/netbox.js differ diff --git a/netbox/project-static/dist/netbox.js.map b/netbox/project-static/dist/netbox.js.map index 81041d18e..88c6c8050 100644 Binary files a/netbox/project-static/dist/netbox.js.map and b/netbox/project-static/dist/netbox.js.map differ diff --git a/netbox/project-static/package.json b/netbox/project-static/package.json index 6ca59318d..935c82d90 100644 --- a/netbox/project-static/package.json +++ b/netbox/project-static/package.json @@ -1,6 +1,6 @@ { "name": "netbox", - "version": "4.1.0", + "version": "4.2.8", "main": "dist/netbox.js", "license": "Apache-2.0", "private": true, @@ -24,13 +24,13 @@ "dependencies": { "@mdi/font": "7.4.47", "@tabler/core": "1.0.0-beta21", - "bootstrap": "5.3.3", + "bootstrap": "5.3.5", "clipboard": "2.0.11", "flatpickr": "4.6.13", "gridstack": "11.5.0", "htmx.org": "1.9.12", "query-string": "9.1.1", - "sass": "1.86.0", + "sass": "1.87.0", "tom-select": "2.4.3", "typeface-inter": "3.18.1", "typeface-roboto-mono": "1.1.13" diff --git a/netbox/project-static/yarn.lock b/netbox/project-static/yarn.lock index 8cc19d255..2379d44c2 100644 --- a/netbox/project-static/yarn.lock +++ b/netbox/project-static/yarn.lock @@ -1066,6 +1066,11 @@ bootstrap@5.3.3: resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.3.3.tgz#de35e1a765c897ac940021900fcbb831602bac38" integrity sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg== +bootstrap@5.3.5: + version "5.3.5" + resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.3.5.tgz#be42cfe0d580e97ee1abb7d38ce94f5c393c9bb6" + integrity sha512-ct1CHKtiobRimyGzmsSldEtM03E8fcEX4Tb3dGXz1V8faRwM50+vfHwTzOxB3IlKO7m+9vTH3s/3C6T2EAPeTA== + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -2673,10 +2678,10 @@ safe-regex-test@^1.0.3: es-errors "^1.3.0" is-regex "^1.1.4" -sass@1.86.0: - version "1.86.0" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.86.0.tgz#f49464fb6237a903a93f4e8760ef6e37a5030114" - integrity sha512-zV8vGUld/+mP4KbMLJMX7TyGCuUp7hnkOScgCMsWuHtns8CWBoz+vmEhoGMXsaJrbUP8gj+F1dLvVe79sK8UdA== +sass@1.87.0: + version "1.87.0" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.87.0.tgz#8cceb36fa63fb48a8d5d7f2f4c13b49c524b723e" + integrity sha512-d0NoFH4v6SjEK7BoX810Jsrhj7IQSYHAHLi/iSpgqKc7LaIDshFRlSg5LOymf9FqQhxEHs2W5ZQXlvy0KD45Uw== dependencies: chokidar "^4.0.0" immutable "^5.0.2" diff --git a/netbox/release.yaml b/netbox/release.yaml index 315055fff..903c5ef1b 100644 --- a/netbox/release.yaml +++ b/netbox/release.yaml @@ -1,3 +1,3 @@ -version: "4.2.7" +version: "4.2.8" edition: "Community" -published: "2025-04-10" +published: "2025-04-22" diff --git a/netbox/templates/dcim/cable_edit.html b/netbox/templates/dcim/cable_edit.html index fbe877d87..dd0d2f9c5 100644 --- a/netbox/templates/dcim/cable_edit.html +++ b/netbox/templates/dcim/cable_edit.html @@ -1,5 +1,5 @@ {% extends 'generic/object_edit.html' %} {% block form %} - {% include 'dcim/htmx/cable_edit.html' %} + {% include 'dcim/htmx/cable_edit.html' %} {% endblock %} diff --git a/netbox/templates/dcim/device_edit.html b/netbox/templates/dcim/device_edit.html index fcf1494b7..8ae98c201 100644 --- a/netbox/templates/dcim/device_edit.html +++ b/netbox/templates/dcim/device_edit.html @@ -3,7 +3,9 @@ {% load i18n %} {% block form %} - {% render_errors form %} + {% for field in form.hidden_fields %} + {{ field }} + {% endfor %}