Skip to content

Commit

Permalink
Merge pull request #15222 from netbox-community/develop
Browse files Browse the repository at this point in the history
Release v3.7.3
  • Loading branch information
jeremystretch authored Feb 21, 2024
2 parents 426805c + 503c78b commit b7f6b72
Show file tree
Hide file tree
Showing 99 changed files with 9,460 additions and 4,740 deletions.
6 changes: 4 additions & 2 deletions .github/ISSUE_TEMPLATE/bug_report.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ body:
- type: dropdown
attributes:
label: Deployment Type
description: How are you running NetBox?
description: >
How are you running NetBox? (For issues with the Docker image, please go to the
[netbox-docker](https://github.com/netbox-community/netbox-docker) repo.)
options:
- Self-hosted
- NetBox Cloud
Expand All @@ -23,7 +25,7 @@ body:
attributes:
label: NetBox Version
description: What version of NetBox are you currently running?
placeholder: v3.7.2
placeholder: v3.7.3
validations:
required: true
- type: dropdown
Expand Down
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/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: v3.7.2
placeholder: v3.7.3
validations:
required: true
- type: dropdown
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<a href="https://github.com/netbox-community/netbox/blob/master/LICENSE.txt"><img src="https://img.shields.io/badge/license-Apache_2.0-blue.svg" alt="License" /></a>
<a href="https://github.com/netbox-community/netbox/graphs/contributors"><img src="https://img.shields.io/github/contributors/netbox-community/netbox?color=blue" alt="Contributors" /></a>
<a href="https://github.com/netbox-community/netbox/stargazers"><img src="https://img.shields.io/github/stars/netbox-community/netbox?style=flat" alt="GitHub stars" /></a>
<a href="https://explore.transifex.com/netbox-community/netbox/"><img src="https://img.shields.io/badge/languages-6-blue" alt="Languages supported" /></a>
<a href="https://explore.transifex.com/netbox-community/netbox/"><img src="https://img.shields.io/badge/languages-7-blue" alt="Languages supported" /></a>
<a href="https://github.com/netbox-community/netbox/actions/workflows/ci.yml"><img src="https://github.com/netbox-community/netbox/workflows/CI/badge.svg?branch=master" alt="CI status" /></a>
<p></p>
</div>
Expand Down
2 changes: 1 addition & 1 deletion base_requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ mkdocs-material
mkdocstrings[python-legacy]

# Library for manipulating IP prefixes and addresses
# https://github.com/netaddr/netaddr/blob/master/CHANGELOG
# https://github.com/netaddr/netaddr/blob/master/CHANGELOG.rst
netaddr

# Fork of PIL (Python Imaging Library) for image processing
Expand Down
2 changes: 1 addition & 1 deletion docs/configuration/remote-authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ When remote user authentication is in use, this is the name of the HTTP header w

Default: `|` (Pipe)

The Seperator upon which `REMOTE_AUTH_GROUP_HEADER` gets split into individual Groups. This needs to be coordinated with your authentication Proxy. (Requires `REMOTE_AUTH_ENABLED` and `REMOTE_AUTH_GROUP_SYNC_ENABLED` )
The Separator upon which `REMOTE_AUTH_GROUP_HEADER` gets split into individual Groups. This needs to be coordinated with your authentication Proxy. (Requires `REMOTE_AUTH_ENABLED` and `REMOTE_AUTH_GROUP_SYNC_ENABLED` )

---

Expand Down
2 changes: 1 addition & 1 deletion docs/customization/custom-scripts.md
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ class NewBranchScript(Script):
name=f'{site.slug}-switch{i}',
site=site,
status=DeviceStatusChoices.STATUS_PLANNED,
role=switch_role
device_role=switch_role
)
switch.full_clean()
switch.save()
Expand Down
35 changes: 35 additions & 0 deletions docs/release-notes/version-3.7.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,40 @@
# NetBox v3.7

## v3.7.3 (2024-02-21)

### Enhancements

* [#14587](https://github.com/netbox-community/netbox/issues/14587) - Display a human-friendly name for the OpenID Connect remote auth backend
* [#14946](https://github.com/netbox-community/netbox/issues/14946) - Remove `associate_by_email()` from default social auth pipeline
* [#14966](https://github.com/netbox-community/netbox/issues/14966) - Add PostgreSQL index for object type & ID on CachedValue table to improve performance
* [#15177](https://github.com/netbox-community/netbox/issues/15177) - Add "last login" time to user display & REST API serializer

### Bug Fixes

* [#14058](https://github.com/netbox-community/netbox/issues/14058) - Limit platform options by manufacturer when editing a device or device type
* [#14064](https://github.com/netbox-community/netbox/issues/14064) - Resolving parent location should consider assigned site when bulk importing locations
* [#14079](https://github.com/netbox-community/netbox/issues/14079) - Ensure changes are logged on related objects when deleting an object referenced via a many-to-many relationship (e.g. tags)
* [#14405](https://github.com/netbox-community/netbox/issues/14405) - Clean up formatting of link peers in bulk CSV export of cable termination objects
* [#14689](https://github.com/netbox-community/netbox/issues/14689) - Preserve "empty" default values for JSON custom fields
* [#14952](https://github.com/netbox-community/netbox/issues/14952) - Update existing AutoSyncRecord when changing the data file of an auto-synced object
* [#15059](https://github.com/netbox-community/netbox/issues/15059) - Correct IP address count link in VM interfaces table
* [#15067](https://github.com/netbox-community/netbox/issues/15067) - Fix uncaught exception when attempting invalid device bay import
* [#15070](https://github.com/netbox-community/netbox/issues/15070) - Fix inclusion of `config_template` field on REST API serializer for virtual machines
* [#15084](https://github.com/netbox-community/netbox/issues/15084) - Fix "add export template" link under "export" button on object list views
* [#15090](https://github.com/netbox-community/netbox/issues/15090) - Ensure protection rules are evaluated prior to enqueueing events when deleting an object
* [#15091](https://github.com/netbox-community/netbox/issues/15091) - Fix designation of the active tab for assigned object when modifying an L2VPN termination
* [#15101](https://github.com/netbox-community/netbox/issues/15101) - Correct OpenAPI schema for rack elevation REST API endpoint
* [#15115](https://github.com/netbox-community/netbox/issues/15115) - Fix unhandled exception with invalid permission constraints
* [#15126](https://github.com/netbox-community/netbox/issues/15126) - `group` field should be optional when creating VPN tunnel via REST API
* [#15127](https://github.com/netbox-community/netbox/issues/15127) - Add missing group column to VPN tunnels table
* [#15133](https://github.com/netbox-community/netbox/issues/15133) - Fix FHRP group representation on assignments REST API endpoint using brief mode
* [#15174](https://github.com/netbox-community/netbox/issues/15174) - Warn that permission constraints are not supported for reports or scripts
* [#15184](https://github.com/netbox-community/netbox/issues/15184) - Correct REST API schema definition for `front_image` & `rear_image` on DeviceType
* [#15185](https://github.com/netbox-community/netbox/issues/15185) - Ensure error messages pertaining to related objects are displayed on the bulk import form
* [#15192](https://github.com/netbox-community/netbox/issues/15192) - Fix exception when viewing current config when no history is present

---

## v3.7.2 (2024-02-05)

### Enhancements
Expand Down
4 changes: 2 additions & 2 deletions netbox/circuits/models/circuits.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,9 +234,9 @@ def clean(self):

# Must define either site *or* provider network
if self.site is None and self.provider_network is None:
raise ValidationError("A circuit termination must attach to either a site or a provider network.")
raise ValidationError(_("A circuit termination must attach to either a site or a provider network."))
if self.site and self.provider_network:
raise ValidationError("A circuit termination cannot attach to both a site and a provider network.")
raise ValidationError(_("A circuit termination cannot attach to both a site and a provider network."))

def to_objectchange(self, action):
objectchange = super().to_objectchange(action)
Expand Down
1 change: 1 addition & 0 deletions netbox/core/api/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
build_basic_type, build_choice_field, build_media_type_object, build_object_type, get_doc,
)
from drf_spectacular.types import OpenApiTypes
from rest_framework import serializers
from rest_framework.relations import ManyRelatedField

from netbox.api.fields import ChoiceField, SerializedPKRelatedField
Expand Down
2 changes: 1 addition & 1 deletion netbox/core/data_backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def fetch(self):
try:
porcelain.clone(self.url, local_path.name, **clone_args)
except BaseException as e:
raise SyncError(f"Fetching remote data failed ({type(e).__name__}): {e}")
raise SyncError(_("Fetching remote data failed ({name}): {error}").format(name=type(e).__name__, error=e))

yield local_path.name

Expand Down
4 changes: 2 additions & 2 deletions netbox/core/forms/model_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,9 @@ def clean(self):
super().clean()

if self.cleaned_data.get('upload_file') and self.cleaned_data.get('data_file'):
raise forms.ValidationError("Cannot upload a file and sync from an existing file")
raise forms.ValidationError(_("Cannot upload a file and sync from an existing file"))
if not self.cleaned_data.get('upload_file') and not self.cleaned_data.get('data_file'):
raise forms.ValidationError("Must upload a file or select a data file to sync")
raise forms.ValidationError(_("Must upload a file or select a data file to sync"))

return self.cleaned_data

Expand Down
2 changes: 1 addition & 1 deletion netbox/core/models/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def __str__(self):
return gettext('Config revision #{id}').format(id=self.pk)

def __getattr__(self, item):
if item in self.data:
if self.data and item in self.data:
return self.data[item]
return super().__getattribute__(item)

Expand Down
4 changes: 2 additions & 2 deletions netbox/core/models/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ def sync(self):
Create/update/delete child DataFiles as necessary to synchronize with the remote source.
"""
if self.status == DataSourceStatusChoices.SYNCING:
raise SyncError("Cannot initiate sync; syncing already in progress.")
raise SyncError(_("Cannot initiate sync; syncing already in progress."))

# Emit the pre_sync signal
pre_sync.send(sender=self.__class__, instance=self)
Expand All @@ -190,7 +190,7 @@ def sync(self):
backend = self.get_backend()
except ModuleNotFoundError as e:
raise SyncError(
f"There was an error initializing the backend. A dependency needs to be installed: {e}"
_("There was an error initializing the backend. A dependency needs to be installed: ") + str(e)
)
with backend.fetch() as local_path:

Expand Down
6 changes: 5 additions & 1 deletion netbox/core/models/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,11 @@ def terminate(self, status=JobStatusChoices.STATUS_COMPLETED, error=None):
"""
valid_statuses = JobStatusChoices.TERMINAL_STATE_CHOICES
if status not in valid_statuses:
raise ValueError(f"Invalid status for job termination. Choices are: {', '.join(valid_statuses)}")
raise ValueError(
_("Invalid status for job termination. Choices are: {choices}").format(
choices=', '.join(valid_statuses)
)
)

# Mark the job as completed
self.status = status
Expand Down
2 changes: 1 addition & 1 deletion netbox/core/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ def get_object(self, **kwargs):
except ConfigRevision.DoesNotExist:
# Fall back to using the active config data if no record is found
return ConfigRevision(
data=get_config()
data=get_config().defaults
)


Expand Down
2 changes: 2 additions & 0 deletions netbox/dcim/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,8 @@ class DeviceTypeSerializer(NetBoxModelSerializer):
airflow = ChoiceField(choices=DeviceAirflowChoices, allow_blank=True, required=False, allow_null=True)
weight_unit = ChoiceField(choices=WeightUnitChoices, allow_blank=True, required=False, allow_null=True)
device_count = serializers.IntegerField(read_only=True)
front_image = serializers.URLField(allow_null=True, required=False)
rear_image = serializers.URLField(allow_null=True, required=False)

# Counter fields
console_port_template_count = serializers.IntegerField(read_only=True)
Expand Down
6 changes: 6 additions & 0 deletions netbox/dcim/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,12 @@ class RackViewSet(NetBoxModelViewSet):
serializer_class = serializers.RackSerializer
filterset_class = filtersets.RackFilterSet

@extend_schema(
operation_id='dcim_racks_elevation_retrieve',
filters=False,
parameters=[serializers.RackElevationDetailFilterSerializer],
responses={200: serializers.RackUnitSerializer(many=True)}
)
@action(detail=True)
def elevation(self, request, pk=None):
"""
Expand Down
5 changes: 3 additions & 2 deletions netbox/dcim/fields.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from django.contrib.postgres.fields import ArrayField
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import gettext as _
from netaddr import AddrFormatError, EUI, eui64_unix_expanded, mac_unix_expanded

from .lookups import PathContains
Expand Down Expand Up @@ -41,7 +42,7 @@ def to_python(self, value):
try:
return EUI(value, version=48, dialect=mac_unix_expanded_uppercase)
except AddrFormatError:
raise ValidationError(f"Invalid MAC address format: {value}")
raise ValidationError(_("Invalid MAC address format: {value}").format(value=value))

def db_type(self, connection):
return 'macaddr'
Expand All @@ -67,7 +68,7 @@ def to_python(self, value):
try:
return EUI(value, version=64, dialect=eui64_unix_expanded_uppercase)
except AddrFormatError:
raise ValidationError(f"Invalid WWN format: {value}")
raise ValidationError(_("Invalid WWN format: {value}").format(value=value))

def db_type(self, connection):
return 'macaddr8'
Expand Down
14 changes: 14 additions & 0 deletions netbox/dcim/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.utils.translation import gettext as _
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import extend_schema_field

from circuits.models import CircuitTermination
from extras.filtersets import LocalConfigContextFilterSet
Expand Down Expand Up @@ -818,6 +820,10 @@ class PlatformFilterSet(OrganizationalModelFilterSet):
to_field_name='slug',
label=_('Manufacturer (slug)'),
)
available_for_device_type = django_filters.ModelChoiceFilter(
queryset=DeviceType.objects.all(),
method='get_for_device_type'
)
config_template_id = django_filters.ModelMultipleChoiceFilter(
queryset=ConfigTemplate.objects.all(),
label=_('Config template (ID)'),
Expand All @@ -827,6 +833,14 @@ class Meta:
model = Platform
fields = ['id', 'name', 'slug', 'description']

@extend_schema_field(OpenApiTypes.STR)
def get_for_device_type(self, queryset, name, value):
"""
Return all Platforms available for a specific manufacturer based on device type and Platforms not assigned any
manufacturer
"""
return queryset.filter(Q(manufacturer=None) | Q(manufacturer__device_types=value))


class DeviceFilterSet(
NetBoxModelFilterSet,
Expand Down
35 changes: 29 additions & 6 deletions netbox/dcim/forms/bulk_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,14 @@ class Meta:
model = Location
fields = ('site', 'parent', 'name', 'slug', 'status', 'tenant', 'description', 'tags')

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

if data:
# Limit location queryset by assigned site
params = {f"site__{self.fields['site'].to_field_name}": data.get('site')}
self.fields['parent'].queryset = self.fields['parent'].queryset.filter(**params)


class RackRoleImportForm(NetBoxModelImportForm):
slug = SlugField()
Expand Down Expand Up @@ -870,7 +878,11 @@ def clean_enabled(self):
def clean_vdcs(self):
for vdc in self.cleaned_data['vdcs']:
if vdc.device != self.cleaned_data['device']:
raise forms.ValidationError(f"VDC {vdc} is not assigned to device {self.cleaned_data['device']}")
raise forms.ValidationError(
_("VDC {vdc} is not assigned to device {device}").format(
vdc=vdc, device=self.cleaned_data['device']
)
)
return self.cleaned_data['vdcs']


Expand Down Expand Up @@ -996,7 +1008,7 @@ def __init__(self, *args, **kwargs):
device_type__subdevice_role=SubdeviceRoleChoices.ROLE_CHILD
).exclude(pk=device.pk)
else:
self.fields['installed_device'].queryset = Interface.objects.none()
self.fields['installed_device'].queryset = Device.objects.none()


class InventoryItemImportForm(NetBoxModelImportForm):
Expand Down Expand Up @@ -1075,7 +1087,11 @@ def clean_component_name(self):
component = model.objects.get(device=device, name=component_name)
self.instance.component = component
except ObjectDoesNotExist:
raise forms.ValidationError(f"Component not found: {device} - {component_name}")
raise forms.ValidationError(
_("Component not found: {device} - {component_name}").format(
device=device, component_name=component_name
)
)


#
Expand Down Expand Up @@ -1193,10 +1209,17 @@ def _clean_side(self, side):
else:
termination_object = model.objects.get(device=device, name=name)
if termination_object.cable is not None and termination_object.cable != self.instance:
raise forms.ValidationError(f"Side {side.upper()}: {device} {termination_object} is already connected")
raise forms.ValidationError(
_("Side {side_upper}: {device} {termination_object} is already connected").format(
side_upper=side.upper(), device=device, termination_object=termination_object
)
)
except ObjectDoesNotExist:
raise forms.ValidationError(f"{side.upper()} side termination not found: {device} {name}")

raise forms.ValidationError(
_("{side_upper} side termination not found: {device} {name}").format(
side_upper=side.upper(), device=device, name=name
)
)
setattr(self.instance, f'{side}_terminations', [termination_object])
return termination_object

Expand Down
11 changes: 9 additions & 2 deletions netbox/dcim/forms/model_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,11 @@ class DeviceTypeForm(NetBoxModelForm):
default_platform = DynamicModelChoiceField(
label=_('Default platform'),
queryset=Platform.objects.all(),
required=False
required=False,
selector=True,
query_params={
'manufacturer_id': ['$manufacturer', 'null'],
}
)
slug = SlugField(
label=_('Slug'),
Expand Down Expand Up @@ -444,7 +448,10 @@ class DeviceForm(TenancyForm, NetBoxModelForm):
label=_('Platform'),
queryset=Platform.objects.all(),
required=False,
selector=True
selector=True,
query_params={
'available_for_device_type': '$device_type',
}
)
cluster = DynamicModelChoiceField(
label=_('Cluster'),
Expand Down
Loading

0 comments on commit b7f6b72

Please sign in to comment.