Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release v4.1.8 #18221

Merged
merged 22 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
24b7679
Closed #18091: Include summary for v4.1 release
jeremystretch Nov 25, 2024
d122c33
18044 enable alert for plugins in script
arthanson Nov 25, 2024
954b5e9
Use the housekeeping app to update translation sources
jeremystretch Dec 3, 2024
b4265b7
Update source translation strings
github-actions[bot] Dec 3, 2024
1e845e6
Add status to rack elevation device tooltip (#18083)
rodvand Dec 5, 2024
327ad8c
Fixes #17490: Config Template unable to dynamically include templates…
robduffy2010 Dec 5, 2024
8c9bb73
Fixes #17810: Disable DRF's native unique constraint checks
jeremystretch Dec 5, 2024
674af4d
Fixes: #14044 - Allow regex renaming of unnamed devices (#17212)
DanSheps Dec 9, 2024
3326a65
Closes #17071: Add is_oob parameter on bulk_import ipaddress (#17975)
pl0xym0r Dec 9, 2024
7a92c20
Fixes 17889: Add checkbox oob ip for ipaddress form (#18057)
pl0xym0r Dec 9, 2024
21962b3
fix #17960 by adding 6 more tunnel encap options (#18097)
jmcguir Dec 9, 2024
4017d0c
Update source translation strings
github-actions[bot] Dec 10, 2024
001f06c
Fixes 18183 - Hide Light/Dark Mode and Login Info from Printed Pages …
jchambers2012 Dec 10, 2024
cc51e70
Fixes: #17820 - Store default values from custom fields on newly crea…
bctiemann Dec 11, 2024
26f8c3a
Closes 18061: Hide traceback from rendered device config (#18127)
alehaa Dec 11, 2024
a15ff29
fixes 17465 : add racktype on bulkimport and bulkedit of racks (#18077)
pl0xym0r Dec 11, 2024
bd5e7a8
Update source translation strings
github-actions[bot] Dec 12, 2024
dbaa9c1
Fixes: #18021 - Clear Swagger/drf-spectacular API cache on startup (#…
bctiemann Dec 12, 2024
8e427e5
Closes #18211: Enable dynamic registration of request processors (#18…
jeremystretch Dec 12, 2024
abfa28d
Fixes: #18150 - Get pagination limit with default 0 (#18151)
bctiemann Dec 12, 2024
2da1a75
Fixes #18213: Enable searching for ASN ranges by name
jeremystretch Dec 12, 2024
e63fe23
Release v4.1.8
jeremystretch Dec 12, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/01-feature_request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ body:
attributes:
label: NetBox version
description: What version of NetBox are you currently running?
placeholder: v4.1.7
placeholder: v4.1.8
validations:
required: true
- type: dropdown
Expand Down
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/02-bug_report.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ body:
attributes:
label: NetBox Version
description: What version of NetBox are you currently running?
placeholder: v4.1.7
placeholder: v4.1.8
validations:
required: true
- type: dropdown
Expand Down
9 changes: 9 additions & 0 deletions .github/workflows/update-translation-strings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,17 @@ jobs:
NETBOX_CONFIGURATION: netbox.configuration_testing

steps:
- name: Create app token
uses: actions/create-github-app-token@v1
id: app-token
with:
app-id: 1076524
private-key: ${{ secrets.HOUSEKEEPING_SECRET_KEY }}

- name: Check out repo
uses: actions/checkout@v4
with:
token: ${{ steps.app-token.outputs.token }}

- name: Set up Python
uses: actions/setup-python@v5
Expand Down
4 changes: 4 additions & 0 deletions docs/development/application-registry.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ This key lists all models which have been registered in NetBox which are not des

This store maintains all registered items for plugins, such as navigation menus, template extensions, etc.

### `request_processors`

A list of context managers to invoke when processing a request e.g. in middleware or when executing a background job. Request processors can be registered with the `@register_request_processor` decorator.

### `search`

A dictionary mapping each model (identified by its app and label) to its search index class, if one has been registered for it.
Expand Down
9 changes: 9 additions & 0 deletions docs/release-notes/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ Minor releases are published in April, August, and December of each calendar yea

This page contains a history of all major and minor releases since NetBox v2.0. For more detail on a specific patch release, please see the release notes page for that specific minor release.

#### [Version 4.1](./version-4.1.md) (September 2024)

* Circuit Groups ([#7025](https://github.com/netbox-community/netbox/issues/7025))
* VLAN Group ID Ranges ([#9627](https://github.com/netbox-community/netbox/issues/9627))
* Nested Device Modules ([#10500](https://github.com/netbox-community/netbox/issues/10500))
* Rack Types ([#12826](https://github.com/netbox-community/netbox/issues/12826))
* Plugins Catalog Integration ([#14731](https://github.com/netbox-community/netbox/issues/14731))
* User Notifications ([#15621](https://github.com/netbox-community/netbox/issues/15621))

#### [Version 4.0](./version-4.0.md) (April 2024)

* Complete UI Refresh ([#12128](https://github.com/netbox-community/netbox/issues/12128))
Expand Down
28 changes: 27 additions & 1 deletion docs/release-notes/version-4.1.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,32 @@
# NetBox v4.1

## v4.1.7 (FUTURE)
## v4.1.8 (2024-12-12)

### Enhancements

* [#17071](https://github.com/netbox-community/netbox/issues/17071) - Enable OOB IP address designation during bulk import
* [#17465](https://github.com/netbox-community/netbox/issues/17465) - Enable designation of rack type during bulk import & bulk edit
* [#17889](https://github.com/netbox-community/netbox/issues/17889) - Enable designating an IP address as out-of-band for a device upon creation
* [#17960](https://github.com/netbox-community/netbox/issues/17960) - Add L2TP, PPTP, Wireguard, and OpenVPN tunnel types
* [#18021](https://github.com/netbox-community/netbox/issues/18021) - Automatically clear cache on restart when `DEBUG` is enabled
* [#18061](https://github.com/netbox-community/netbox/issues/18061) - Omit stack trace from rendered device/VM configuration when an exception is raised
* [#18065](https://github.com/netbox-community/netbox/issues/18065) - Include status in device details when hovering on rack elevation
* [#18211](https://github.com/netbox-community/netbox/issues/18211) - Enable the dynamic registration of context managers for request processing

### Bug Fixes

* [#14044](https://github.com/netbox-community/netbox/issues/14044) - Fix unhandled AttributeError exception when bulk renaming objects
* [#17490](https://github.com/netbox-community/netbox/issues/17490) - Fix dynamic inclusion support for config templates
* [#17810](https://github.com/netbox-community/netbox/issues/17810) - Fix validation of racked device fields when modifying via REST API
* [#17820](https://github.com/netbox-community/netbox/issues/17820) - Ensure default custom field values are populated when creating new modules
* [#18044](https://github.com/netbox-community/netbox/issues/18044) - Show plugin-generated alerts within UI views for custom scripts
* [#18150](https://github.com/netbox-community/netbox/issues/18150) - Fix REST API pagination for low `MAX_PAGE_SIZE` values
* [#18183](https://github.com/netbox-community/netbox/issues/18183) - Omit UI navigation bar when printing
* [#18213](https://github.com/netbox-community/netbox/issues/18213) - Fix searching for ASN ranges by name

---

## v4.1.7 (2024-11-21)

### Enhancements

Expand Down
6 changes: 6 additions & 0 deletions netbox/core/apps.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from django.apps import AppConfig
from django.conf import settings
from django.core.cache import cache
from django.db import models
from django.db.migrations.operations import AlterModelOptions

Expand All @@ -22,3 +24,7 @@ def ready(self):

# Register models
register_models(*self.get_models())

# Clear Redis cache on startup in development mode
if settings.DEBUG:
cache.clear()
7 changes: 6 additions & 1 deletion netbox/dcim/forms/bulk_edit.py
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,11 @@ class RackBulkEditForm(NetBoxModelBulkEditForm):
queryset=RackRole.objects.all(),
required=False
)
rack_type = DynamicModelChoiceField(
label=_('Rack type'),
queryset=RackType.objects.all(),
required=False,
)
serial = forms.CharField(
max_length=50,
required=False,
Expand Down Expand Up @@ -438,7 +443,7 @@ class RackBulkEditForm(NetBoxModelBulkEditForm):

model = Rack
fieldsets = (
FieldSet('status', 'role', 'tenant', 'serial', 'asset_tag', 'description', name=_('Rack')),
FieldSet('status', 'role', 'tenant', 'serial', 'asset_tag', 'rack_type', 'description', name=_('Rack')),
FieldSet('region', 'site_group', 'site', 'location', name=_('Location')),
FieldSet(
'form_factor', 'width', 'u_height', 'desc_units', 'airflow', 'outer_width', 'outer_depth', 'outer_unit',
Expand Down
28 changes: 25 additions & 3 deletions netbox/dcim/forms/bulk_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,13 @@ class RackImportForm(NetBoxModelImportForm):
to_field_name='name',
help_text=_('Name of assigned role')
)
rack_type = CSVModelChoiceField(
label=_('Rack type'),
queryset=RackType.objects.all(),
to_field_name='model',
required=False,
help_text=_('Rack type model')
)
form_factor = CSVChoiceField(
label=_('Type'),
choices=RackFormFactorChoices,
Expand All @@ -265,8 +272,13 @@ class RackImportForm(NetBoxModelImportForm):
width = forms.ChoiceField(
label=_('Width'),
choices=RackWidthChoices,
required=False,
help_text=_('Rail-to-rail width (in inches)')
)
u_height = forms.IntegerField(
required=False,
label=_('Height (U)')
)
outer_unit = CSVChoiceField(
label=_('Outer unit'),
choices=RackDimensionUnitChoices,
Expand All @@ -289,9 +301,9 @@ class RackImportForm(NetBoxModelImportForm):
class Meta:
model = Rack
fields = (
'site', 'location', 'name', 'facility_id', 'tenant', 'status', 'role', 'form_factor', 'serial', 'asset_tag',
'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'mounting_depth', 'airflow',
'weight', 'max_weight', 'weight_unit', 'description', 'comments', 'tags',
'site', 'location', 'name', 'facility_id', 'tenant', 'status', 'role', 'rack_type', 'form_factor', 'serial',
'asset_tag', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit',
'mounting_depth', 'airflow', 'weight', 'max_weight', 'weight_unit', 'description', 'comments', 'tags',
)

def __init__(self, data=None, *args, **kwargs):
Expand All @@ -303,6 +315,16 @@ def __init__(self, data=None, *args, **kwargs):
params = {f"site__{self.fields['site'].to_field_name}": data.get('site')}
self.fields['location'].queryset = self.fields['location'].queryset.filter(**params)

def clean(self):
super().clean()

# width & u_height must be set if not specifying a rack type on import
if not self.instance.pk:
if not self.cleaned_data.get('rack_type') and not self.cleaned_data.get('width'):
raise forms.ValidationError(_("Width must be set if not specifying a rack type."))
if not self.cleaned_data.get('rack_type') and not self.cleaned_data.get('u_height'):
raise forms.ValidationError(_("U height must be set if not specifying a rack type."))


class RackReservationImportForm(NetBoxModelImportForm):
site = CSVModelChoiceField(
Expand Down
5 changes: 5 additions & 0 deletions netbox/dcim/models/devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -1277,6 +1277,11 @@ def save(self, *args, **kwargs):
if not disable_replication:
create_instances.append(template_instance)

# Set default values for any applicable custom fields
if cf_defaults := CustomField.objects.get_defaults_for_model(component_model):
for component in create_instances:
component.custom_field_data = cf_defaults

if component_model is not ModuleBay:
component_model.objects.bulk_create(create_instances)
# Emit the post_save signal for each newly created object
Expand Down
2 changes: 2 additions & 0 deletions netbox/dcim/svg/racks.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,15 @@ def get_device_description(device):

Name: <name>
Role: <role>
Status: <status>
Device Type: <manufacturer> <model> (<u_height>)
Asset tag: <asset_tag> (if defined)
Serial: <serial> (if defined)
Description: <description> (if defined)
"""
description = f'Name: {device.name}'
description += f'\nRole: {device.role}'
description += f'\nStatus: {device.get_status_display()}'
u_height = f'{floatformat(device.device_type.u_height)}U'
description += f'\nDevice Type: {device.device_type.manufacturer.name} {device.device_type.model} ({u_height})'
if device.asset_tag:
Expand Down
10 changes: 5 additions & 5 deletions netbox/dcim/views.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import traceback

from django.contrib import messages
from django.contrib.contenttypes.models import ContentType
from django.core.paginator import EmptyPage, PageNotAnInteger
Expand Down Expand Up @@ -2106,7 +2104,8 @@ def get(self, request, **kwargs):
# If a direct export has been requested, return the rendered template content as a
# downloadable file.
if request.GET.get('export'):
response = HttpResponse(context['rendered_config'], content_type='text')
content = context['rendered_config'] or context['error_message']
response = HttpResponse(content, content_type='text')
filename = f"{instance.name or 'config'}.txt"
response['Content-Disposition'] = f'attachment; filename="{filename}"'
return response
Expand All @@ -2124,17 +2123,18 @@ def get_extra_context(self, request, instance):

# Render the config template
rendered_config = None
error_message = None
if config_template := instance.get_config_template():
try:
rendered_config = config_template.render(context=context_data)
except TemplateError as e:
messages.error(request, _("An error occurred while rendering the template: {error}").format(error=e))
rendered_config = traceback.format_exc()
error_message = _("An error occurred while rendering the template: {error}").format(error=e)

return {
'config_template': config_template,
'context_data': context_data,
'rendered_config': rendered_config,
'error_message': error_message,
}


Expand Down
4 changes: 4 additions & 0 deletions netbox/extras/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1141,12 +1141,14 @@ def get(self, request, **kwargs):
script_class = self._get_script_class(script)
if not script_class:
return render(request, 'extras/script.html', {
'object': script,
'script': script,
})

form = script_class.as_form(initial=normalize_querydict(request.GET))

return render(request, 'extras/script.html', {
'object': script,
'script': script,
'script_class': script_class,
'form': form,
Expand All @@ -1162,6 +1164,7 @@ def post(self, request, **kwargs):
script_class = self._get_script_class(script)
if not script_class:
return render(request, 'extras/script.html', {
'object': script,
'script': script,
})

Expand All @@ -1186,6 +1189,7 @@ def post(self, request, **kwargs):
return redirect('extras:script_result', job_pk=job.pk)

return render(request, 'extras/script.html', {
'object': script,
'script': script,
'script_class': script.python_class(),
'form': form,
Expand Down
6 changes: 4 additions & 2 deletions netbox/ipam/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,10 @@ class Meta:
def search(self, queryset, name, value):
if not value.strip():
return queryset
qs_filter = Q(description__icontains=value)
return queryset.filter(qs_filter)
return queryset.filter(
Q(name__icontains=value) |
Q(description__icontains=value)
)


class ASNFilterSet(OrganizationalModelFilterSet, TenancyFilterSet):
Expand Down
30 changes: 27 additions & 3 deletions netbox/ipam/forms/bulk_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,12 +326,17 @@ class IPAddressImportForm(NetBoxModelImportForm):
help_text=_('Make this the primary IP for the assigned device'),
required=False
)
is_oob = forms.BooleanField(
label=_('Is out-of-band'),
help_text=_('Designate this as the out-of-band IP address for the assigned device'),
required=False
)

class Meta:
model = IPAddress
fields = [
'address', 'vrf', 'tenant', 'status', 'role', 'device', 'virtual_machine', 'interface', 'is_primary',
'dns_name', 'description', 'comments', 'tags',
'is_oob', 'dns_name', 'description', 'comments', 'tags',
]

def __init__(self, data=None, *args, **kwargs):
Expand All @@ -345,7 +350,7 @@ def __init__(self, data=None, *args, **kwargs):
**{f"device__{self.fields['device'].to_field_name}": data['device']}
)

# Limit interface queryset by assigned device
# Limit interface queryset by assigned VM
elif data.get('virtual_machine'):
self.fields['interface'].queryset = VMInterface.objects.filter(
**{f"virtual_machine__{self.fields['virtual_machine'].to_field_name}": data['virtual_machine']}
Expand All @@ -358,16 +363,29 @@ def clean(self):
virtual_machine = self.cleaned_data.get('virtual_machine')
interface = self.cleaned_data.get('interface')
is_primary = self.cleaned_data.get('is_primary')
is_oob = self.cleaned_data.get('is_oob')

# Validate is_primary
# Validate is_primary and is_oob
if is_primary and not device and not virtual_machine:
raise forms.ValidationError({
"is_primary": _("No device or virtual machine specified; cannot set as primary IP")
})
if is_oob and not device:
raise forms.ValidationError({
"is_oob": _("No device specified; cannot set as out-of-band IP")
})
if is_oob and virtual_machine:
raise forms.ValidationError({
"is_oob": _("Cannot set out-of-band IP for virtual machines")
})
if is_primary and not interface:
raise forms.ValidationError({
"is_primary": _("No interface specified; cannot set as primary IP")
})
if is_oob and not interface:
raise forms.ValidationError({
"is_oob": _("No interface specified; cannot set as out-of-band IP")
})

def save(self, *args, **kwargs):

Expand All @@ -386,6 +404,12 @@ def save(self, *args, **kwargs):
parent.primary_ip6 = ipaddress
parent.save()

# Set as OOB for device
if self.cleaned_data.get('is_oob'):
parent = self.cleaned_data.get('device')
parent.oob_ip = ipaddress
parent.save()

return ipaddress


Expand Down
Loading
Loading