Skip to content

Commit

Permalink
Merge pull request #17626 from netbox-community/develop
Browse files Browse the repository at this point in the history
Release v4.1.2
  • Loading branch information
jeremystretch authored Sep 26, 2024
2 parents 0e34fba + b4e181c commit ead6e63
Show file tree
Hide file tree
Showing 120 changed files with 3,216 additions and 2,842 deletions.
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.1
placeholder: v4.1.2
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 @@ -26,7 +26,7 @@ body:
attributes:
label: NetBox Version
description: What version of NetBox are you currently running?
placeholder: v4.1.1
placeholder: v4.1.2
validations:
required: true
- type: dropdown
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/close-stale-issues.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
- uses: actions/stale@v9
with:
# General parameters
operations-per-run: 100
operations-per-run: 200
remove-stale-when-updated: false

# Issue parameters
Expand All @@ -43,7 +43,7 @@ jobs:
# Pull request parameters
close-pr-message: >
This PR has been automatically closed due to lack of activity.
days-before-pr-stale: 15
days-before-pr-stale: 30
days-before-pr-close: 15
exempt-pr-labels: 'status: blocked'
stale-pr-label: 'pending closure'
Expand Down
3 changes: 3 additions & 0 deletions contrib/generated_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@
"molex-micro-fit-2x2",
"molex-micro-fit-2x4",
"dc-terminal",
"eaton-c39",
"hdot-cx",
"saf-d-grid",
"neutrik-powercon-20a",
Expand Down Expand Up @@ -330,6 +331,7 @@
"5gbase-t",
"10gbase-t",
"10gbase-cx4",
"100base-x-sfp",
"1000base-x-gbic",
"1000base-x-sfp",
"10gbase-x-sfpp",
Expand Down Expand Up @@ -381,6 +383,7 @@
"ieee802.11ay",
"ieee802.11be",
"ieee802.15.1",
"ieee802.15.4",
"other-wireless",
"gsm",
"cdma",
Expand Down
18 changes: 18 additions & 0 deletions docs/_theme/partials/copyright.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<div class="md-copyright">
{% if config.copyright %}
<div class="md-copyright__highlight">
{{ config.copyright }}
</div>
{% endif %}
{% if not config.extra.generator == false %}
Made with
<a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener">
Material for MkDocs
</a>
{% endif %}
</div>
{% if not config.extra.build_public %}
<div class="md-copyright">
ℹ️ Documentation is being served locally
</div>
{% endif %}
36 changes: 36 additions & 0 deletions docs/release-notes/version-4.1.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,41 @@
# NetBox v4.1

## v4.1.2 (2024-09-26)

### Enhancements

* [#14201](https://github.com/netbox-community/netbox/issues/14201) - Enable global search for AS numbers using "AS" prefix
* [#15408](https://github.com/netbox-community/netbox/issues/15408) - Enable bulk import of primary IPv4 & IPv6 addresses for virtual device contexts (VDCs)
* [#16781](https://github.com/netbox-community/netbox/issues/16781) - Add 100Base-X SFP interface type
* [#17255](https://github.com/netbox-community/netbox/issues/17255) - Include return URL when creating new IP address from prefix IPs list
* [#17471](https://github.com/netbox-community/netbox/issues/17471) - Add Eaton C39 power outlet type
* [#17482](https://github.com/netbox-community/netbox/issues/17482) - Do not preload Branch & StagedChange models in `nbshell`
* [#17550](https://github.com/netbox-community/netbox/issues/17550) - Add IEEE 802.15.4 wireless interface type

### Bug Fixes

* [#16837](https://github.com/netbox-community/netbox/issues/16837) - Fix filtering of cables with no type assigned
* [#17083](https://github.com/netbox-community/netbox/issues/17083) - Trim clickable area of form field labels
* [#17126](https://github.com/netbox-community/netbox/issues/17126) - Show total device weight in both imperial & metric units
* [#17360](https://github.com/netbox-community/netbox/issues/17360) - Fix AttributeError under child object views when experimental HTMX navigation is enabled
* [#17406](https://github.com/netbox-community/netbox/issues/17406) - Fix the cleanup of stale custom field data after removing a plugin
* [#17419](https://github.com/netbox-community/netbox/issues/17419) - Rebuild MPTT for module bays on upgrade to v4.1
* [#17492](https://github.com/netbox-community/netbox/issues/17492) - Fix URL resolution in `NetBoxModelSerializer` for plugin models
* [#17497](https://github.com/netbox-community/netbox/issues/17497) - Fix uncaught FieldError exception when referencing an invalid field on a related object during bulk import
* [#17498](https://github.com/netbox-community/netbox/issues/17498) - Fix MultipleObjectsReturned exception when importing a device type without uniquely specifying a manufacturer
* [#17501](https://github.com/netbox-community/netbox/issues/17501) - Fix reporting of last run time & status for custom scripts under UI
* [#17511](https://github.com/netbox-community/netbox/issues/17511) - Restore consistent font support for non-Latin characters
* [#17517](https://github.com/netbox-community/netbox/issues/17517) - Fix cable termination selection after switching termination type
* [#17521](https://github.com/netbox-community/netbox/issues/17521) - Correct text color in notification pop-ups under dark mode
* [#17522](https://github.com/netbox-community/netbox/issues/17522) - Fix language translation of form field labels under user preferences
* [#17537](https://github.com/netbox-community/netbox/issues/17537) - Fix global search support for ASN range names
* [#17555](https://github.com/netbox-community/netbox/issues/17555) - Fix toggling disconnected interfaces under device view
* [#17601](https://github.com/netbox-community/netbox/issues/17601) - Record change to terminating object when disconnecting a cable
* [#17605](https://github.com/netbox-community/netbox/issues/17605) - Fix calculation of aggregate VM disk space under cluster view
* [#17611](https://github.com/netbox-community/netbox/issues/17611) - Correct custom field minimum value validation error message

---

## v4.1.1 (2024-09-12)

### Enhancements
Expand Down
14 changes: 11 additions & 3 deletions netbox/core/management/commands/nbshell.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
from users.models import User

APPS = ('circuits', 'core', 'dcim', 'extras', 'ipam', 'tenancy', 'users', 'virtualization', 'vpn', 'wireless')
EXCLUDE_MODELS = (
'extras.branch',
'extras.stagedchange',
)

BANNER_TEXT = """### NetBox interactive shell ({node})
### Python {python} | Django {django} | NetBox {netbox}
Expand Down Expand Up @@ -44,12 +48,16 @@ def get_namespace(self):

# Gather Django models and constants from each app
for app in APPS:
self.django_models[app] = []
models = []

# Load models from each app
for model in apps.get_app_config(app).get_models():
namespace[model.__name__] = model
self.django_models[app].append(model.__name__)
app_label = model._meta.app_label
model_name = model._meta.model_name
if f'{app_label}.{model_name}' not in EXCLUDE_MODELS:
namespace[model.__name__] = model
models.append(model.__name__)
self.django_models[app] = sorted(models)

# Constants
try:
Expand Down
6 changes: 6 additions & 0 deletions netbox/dcim/choices.py
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,7 @@ class PowerOutletTypeChoices(ChoiceSet):
# Direct current (DC)
TYPE_DC = 'dc-terminal'
# Proprietary
TYPE_EATON_C39 = 'eaton-c39'
TYPE_HDOT_CX = 'hdot-cx'
TYPE_SAF_D_GRID = 'saf-d-grid'
TYPE_NEUTRIK_POWERCON_20A = 'neutrik-powercon-20a'
Expand Down Expand Up @@ -805,6 +806,7 @@ class PowerOutletTypeChoices(ChoiceSet):
(TYPE_DC, 'DC Terminal'),
)),
(_('Proprietary'), (
(TYPE_EATON_C39, 'Eaton C39'),
(TYPE_HDOT_CX, 'HDOT Cx'),
(TYPE_SAF_D_GRID, 'Saf-D-Grid'),
(TYPE_NEUTRIK_POWERCON_20A, 'Neutrik powerCON (20A)'),
Expand Down Expand Up @@ -861,6 +863,7 @@ class InterfaceTypeChoices(ChoiceSet):
TYPE_100ME_LFX = '100base-lfx'
TYPE_100ME_FIXED = '100base-tx'
TYPE_100ME_T1 = '100base-t1'
TYPE_100ME_SFP = '100base-x-sfp'
TYPE_1GE_FIXED = '1000base-t'
TYPE_1GE_TX_FIXED = '1000base-tx'
TYPE_1GE_GBIC = '1000base-x-gbic'
Expand Down Expand Up @@ -922,6 +925,7 @@ class InterfaceTypeChoices(ChoiceSet):
TYPE_80211AY = 'ieee802.11ay'
TYPE_80211BE = 'ieee802.11be'
TYPE_802151 = 'ieee802.15.1'
TYPE_802154 = 'ieee802.15.4'
TYPE_OTHER_WIRELESS = 'other-wireless'

# Cellular
Expand Down Expand Up @@ -1033,6 +1037,7 @@ class InterfaceTypeChoices(ChoiceSet):
(
_('Ethernet (modular)'),
(
(TYPE_100ME_SFP, 'SFP (100ME)'),
(TYPE_1GE_GBIC, 'GBIC (1GE)'),
(TYPE_1GE_SFP, 'SFP (1GE)'),
(TYPE_10GE_SFP_PLUS, 'SFP+ (10GE)'),
Expand Down Expand Up @@ -1094,6 +1099,7 @@ class InterfaceTypeChoices(ChoiceSet):
(TYPE_80211AY, 'IEEE 802.11ay'),
(TYPE_80211BE, 'IEEE 802.11be'),
(TYPE_802151, 'IEEE 802.15.1 (Bluetooth)'),
(TYPE_802154, 'IEEE 802.15.4 (LR-WPAN)'),
(TYPE_OTHER_WIRELESS, 'Other (Wireless)'),
)
),
Expand Down
1 change: 1 addition & 0 deletions netbox/dcim/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
InterfaceTypeChoices.TYPE_80211AY,
InterfaceTypeChoices.TYPE_80211BE,
InterfaceTypeChoices.TYPE_802151,
InterfaceTypeChoices.TYPE_802154,
InterfaceTypeChoices.TYPE_OTHER_WIRELESS,
]

Expand Down
32 changes: 28 additions & 4 deletions netbox/dcim/forms/bulk_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from dcim.constants import *
from dcim.models import *
from extras.models import ConfigTemplate
from ipam.models import VRF
from ipam.models import VRF, IPAddress
from netbox.forms import NetBoxModelImportForm
from tenancy.models import Tenant
from utilities.forms.fields import (
Expand Down Expand Up @@ -367,13 +367,13 @@ class Meta:


class DeviceTypeImportForm(NetBoxModelImportForm):
manufacturer = forms.ModelChoiceField(
manufacturer = CSVModelChoiceField(
label=_('Manufacturer'),
queryset=Manufacturer.objects.all(),
to_field_name='name',
help_text=_('The manufacturer which produces this device type')
)
default_platform = forms.ModelChoiceField(
default_platform = CSVModelChoiceField(
label=_('Default platform'),
queryset=Platform.objects.all(),
to_field_name='name',
Expand Down Expand Up @@ -1435,9 +1435,33 @@ class VirtualDeviceContextImportForm(NetBoxModelImportForm):
label=_('Status'),
choices=VirtualDeviceContextStatusChoices,
)
primary_ip4 = CSVModelChoiceField(
label=_('Primary IPv4'),
queryset=IPAddress.objects.all(),
required=False,
to_field_name='address',
help_text=_('IPv4 address with mask, e.g. 1.2.3.4/24')
)
primary_ip6 = CSVModelChoiceField(
label=_('Primary IPv6'),
queryset=IPAddress.objects.all(),
required=False,
to_field_name='address',
help_text=_('IPv6 address with prefix length, e.g. 2001:db8::1/64')
)

class Meta:
fields = [
'name', 'device', 'status', 'tenant', 'identifier', 'comments',
'name', 'device', 'status', 'tenant', 'identifier', 'comments', 'primary_ip4', 'primary_ip6',
]
model = VirtualDeviceContext

def __init__(self, data=None, *args, **kwargs):
super().__init__(data, *args, **kwargs)

if data:

# Limit primary_ip4/ip6 querysets by assigned device
params = {f"interface__device__{self.fields['device'].to_field_name}": data.get('device')}
self.fields['primary_ip4'].queryset = self.fields['primary_ip4'].queryset.filter(**params)
self.fields['primary_ip6'].queryset = self.fields['primary_ip6'].queryset.filter(**params)
3 changes: 2 additions & 1 deletion netbox/dcim/forms/model_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -1351,7 +1351,8 @@ class InterfaceForm(InterfaceCommonForm, ModularDeviceComponentForm):
vlan_group = DynamicModelChoiceField(
queryset=VLANGroup.objects.all(),
required=False,
label=_('VLAN group')
label=_('VLAN group'),
help_text=_("Filter VLANs available for assignment by group.")
)
untagged_vlan = DynamicModelChoiceField(
queryset=VLAN.objects.all(),
Expand Down
26 changes: 26 additions & 0 deletions netbox/dcim/migrations/0191_module_bay_rebuild.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from django.db import migrations
import mptt
import mptt.managers


def rebuild_mptt(apps, schema_editor):
manager = mptt.managers.TreeManager()
ModuleBay = apps.get_model('dcim', 'ModuleBay')
manager.model = ModuleBay
mptt.register(ModuleBay)
manager.contribute_to_class(ModuleBay, 'objects')
manager.rebuild()


class Migration(migrations.Migration):

dependencies = [
('dcim', '0190_nested_modules'),
]

operations = [
migrations.RunPython(
code=rebuild_mptt,
reverse_code=migrations.RunPython.noop
),
]
12 changes: 6 additions & 6 deletions netbox/dcim/models/cables.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ def clean(self):
if self.length is not None and not self.length_unit:
raise ValidationError(_("Must specify a unit when setting a cable length"))

if self.pk is None and (not self.a_terminations or not self.b_terminations):
if self._state.adding and (not self.a_terminations or not self.b_terminations):
raise ValidationError(_("Must define A and B terminations when creating a new cable."))

if self._terminations_modified:
Expand Down Expand Up @@ -366,11 +366,11 @@ def save(self, *args, **kwargs):
def delete(self, *args, **kwargs):

# Delete the cable association on the terminating object
termination_model = self.termination._meta.model
termination_model.objects.filter(pk=self.termination_id).update(
cable=None,
cable_end=''
)
termination = self.termination._meta.model.objects.get(pk=self.termination_id)
termination.snapshot()
termination.cable = None
termination.cable_end = ''
termination.save()

super().delete(*args, **kwargs)

Expand Down
2 changes: 1 addition & 1 deletion netbox/dcim/models/device_component_templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ def to_objectchange(self, action):
def clean(self):
super().clean()

if self.pk is not None and self._original_device_type != self.device_type_id:
if not self._state.adding and self._original_device_type != self.device_type_id:
raise ValidationError({
"device_type": _("Component templates cannot be moved to a different device type.")
})
Expand Down
6 changes: 3 additions & 3 deletions netbox/dcim/models/device_components.py
Original file line number Diff line number Diff line change
Expand Up @@ -561,7 +561,7 @@ def save(self, *args, **kwargs):
self.untagged_vlan = None

# Only "tagged" interfaces may have tagged VLANs assigned. ("tagged all" implies all VLANs are assigned.)
if self.pk and self.mode != InterfaceModeChoices.MODE_TAGGED:
if not self._state.adding and self.mode != InterfaceModeChoices.MODE_TAGGED:
self.tagged_vlans.clear()

return super().save(*args, **kwargs)
Expand Down Expand Up @@ -1072,7 +1072,7 @@ def clean(self):
super().clean()

# Check that positions count is greater than or equal to the number of associated FrontPorts
if self.pk:
if not self._state.adding:
frontport_count = self.frontports.count()
if self.positions < frontport_count:
raise ValidationError({
Expand Down Expand Up @@ -1314,7 +1314,7 @@ def clean(self):
})

# Validation for moving InventoryItems
if self.pk:
if not self._state.adding:
# Cannot move an InventoryItem to another device if it has a parent
if self.parent and self.parent.device != self.device:
raise ValidationError({
Expand Down
6 changes: 3 additions & 3 deletions netbox/dcim/models/devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ def clean(self):
# If editing an existing DeviceType to have a larger u_height, first validate that *all* instances of it have
# room to expand within their racks. This validation will impose a very high performance penalty when there are
# many instances to check, but increasing the u_height of a DeviceType should be a very rare occurrence.
if self.pk and self.u_height > self._original_u_height:
if not self._state.adding and self.u_height > self._original_u_height:
for d in Device.objects.filter(device_type=self, position__isnull=False):
face_required = None if self.is_full_depth else d.face
u_available = d.rack.get_available_units(
Expand All @@ -310,7 +310,7 @@ def clean(self):
})

# If modifying the height of an existing DeviceType to 0U, check for any instances assigned to a rack position.
elif self.pk and self._original_u_height > 0 and self.u_height == 0:
elif not self._state.adding and self._original_u_height > 0 and self.u_height == 0:
racked_instance_count = Device.objects.filter(
device_type=self,
position__isnull=False
Expand Down Expand Up @@ -1351,7 +1351,7 @@ def clean(self):

# Verify that the selected master device has been assigned to this VirtualChassis. (Skip when creating a new
# VirtualChassis.)
if self.pk and self.master and self.master not in self.members.all():
if not self._state.adding and self.master and self.master not in self.members.all():
raise ValidationError({
'master': _("The selected master ({master}) is not assigned to this virtual chassis.").format(
master=self.master
Expand Down
Loading

0 comments on commit ead6e63

Please sign in to comment.