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 v3.4.8 #12234

Merged
merged 61 commits into from
Apr 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
016eff5
PRVB
jeremystretch Mar 13, 2023
4286d74
Add fieldsets functionality to scripts to allow for form field groupi…
ryanmerolle Mar 14, 2023
d93a24d
Removed type2-ieee802.3at as per described in #11984
danner26 Mar 15, 2023
3d14a79
Fixes #11979: Correct URL for tags in route targets list
jeremystretch Mar 16, 2023
e7ed280
Add parameters to the SavedFilterTable
kkthxbye-code Mar 10, 2023
85f40bc
Render the parameters column as JSON in SavedFiltersTable
kkthxbye-code Mar 15, 2023
fc482ed
Use ssid for the string representation of WirelessLinks if available
kkthxbye-code Mar 16, 2023
a864e81
Improve error reporting for duplicate CSV column headings
candlerb Mar 16, 2023
8d25d78
Closes #11682: Remove lateral padding from highlighted text
jeremystretch Mar 17, 2023
96eb89a
12049 fix passsword typo
arthanson Mar 24, 2023
571d33e
Fixes #11977: Multiple remote authentication backends (#12012)
decoupca Mar 28, 2023
835af32
12008 make export templates cloneable
arthanson Mar 21, 2023
879aabe
12038 fix clone tag
arthanson Mar 24, 2023
654e32c
11933 saved filters clone of content-types and add m2m field cloning…
arthanson Mar 28, 2023
a2c7452
12058 add clone to config context
arthanson Mar 27, 2023
7d64e5b
#12058: Fix initial JSON population
jeremystretch Mar 28, 2023
19787dd
Update changelog
jeremystretch Mar 28, 2023
87eabdb
12029 add description to virtual description add
arthanson Mar 22, 2023
ad03061
12038 show vc priority with placeholder
arthanson Mar 24, 2023
1d2335d
Updated _schedule_at to use local time when _interval is set (#12006)
abhi1693 Mar 28, 2023
9995fad
Update changelog for #11645, #12029, #12038
jeremystretch Mar 28, 2023
b12551c
Fixes #11991 - Add vdcs to InterfaceImportForm and InterfaceBulkEditF…
kkthxbye-code Mar 28, 2023
d3c5f1e
Release v3.4.7
jeremystretch Mar 28, 2023
5a6005c
Merge branch 'master' into develop
jeremystretch Mar 28, 2023
0330c65
PRVB
jeremystretch Mar 28, 2023
e467589
12084 saved filters (#12090)
arthanson Mar 30, 2023
0ac8419
Fixes #12104: Restore copy-to-clipboard & footer navigation in docs
jeremystretch Mar 30, 2023
6f08c4a
Fixes #11846: Update database creation instructions for PostgreSQL 14+
jeremystretch Mar 30, 2023
fbc2342
Update models.py
gdprdatasubect Mar 30, 2023
3264636
Changelog for #12084, #12095
jeremystretch Mar 30, 2023
2883fa1
Fixes #12074: Move automatic location assignment out of clean()
arthanson Apr 3, 2023
bca00cd
12117 remove clone from cable (#12130)
arthanson Apr 3, 2023
8a684ad
Changelog for #12074, #12117
jeremystretch Apr 3, 2023
b032742
Closes #12133: Move any instance mutations inside clean() to save()
jeremystretch Apr 3, 2023
0a2ae90
12011 fix module bay bulk create
arthanson Apr 4, 2023
94c2a2e
11746 fix delete custom field (#12092)
arthanson Apr 4, 2023
41c9248
#12087 - Fix Bulk Edit update when M2M operations are present. (#12169)
DanSheps Apr 5, 2023
f8d40ae
Changelog for #11746, #12011, #12087
jeremystretch Apr 5, 2023
74d8bae
Remove NS1 from sponsors list
jeremystretch Apr 6, 2023
2bf9acf
Closes #12193: Clean up tests (#12197)
jeremystretch Apr 6, 2023
ccfdc21
Fixes #12118: Refactor bulk creation logic under _instantiate_compone…
jeremystretch Apr 6, 2023
63a0ec7
Fixes #12190: Fix form layout for plugin textarea fields
jeremystretch Apr 7, 2023
085cfc5
Fixes #12184: Fix filtered bulk deletion for various models
jeremystretch Apr 7, 2023
c26fe26
Moved interface filterset under common class (#12200)
abhi1693 Apr 7, 2023
9f71cf7
Changelog for #12007, #12118
jeremystretch Apr 7, 2023
a6fd0ab
#12007: Move vlan & vlan_id filter methods to CommonInterfaceFilterSet
jeremystretch Apr 7, 2023
5a4feb7
10615 filter cable termination_id with cable_end (#12182)
arthanson Apr 7, 2023
1146aaf
Closes #11453: Display a warning banner when DEBUG is enabled
jeremystretch Apr 10, 2023
768d6f6
Fixes #12191: Change absolute image path to relative
jeremystretch Apr 10, 2023
278f2b1
Fixes #11431 - Disallow changing customfield type after creation (#11…
kkthxbye-code Apr 10, 2023
2c07762
Added optional user and group on custom field (#12206)
abhi1693 Apr 10, 2023
b41f875
Fixes GenericForeignKey validation (#11550)
abhi1693 Apr 10, 2023
ada01b3
#10221: Tweak variable names & error message
jeremystretch Apr 10, 2023
4c9cf90
Changelog for #10221, #10600, #11431, #11454
jeremystretch Apr 10, 2023
4a331b5
Closes #11015: Remove unit from commit rate column header in circuits…
jeremystretch Apr 10, 2023
6820796
Closes #10414: Enable general purpose image attachments for device types
jeremystretch Apr 10, 2023
97ed643
Fixes #12227: L2VPN Bulk import not setting Tenant field
pobradovic08 Apr 12, 2023
9e305c6
Closes #12207: Establish a permission for creating API tokens on beha…
arthanson Apr 12, 2023
8de252e
11432 device field (#11567)
arthanson Apr 12, 2023
bb9a125
Closes #12040: fix bulk import tab selection
decoupca Apr 12, 2023
eef3825
Release v3.4.8
jeremystretch Apr 12, 2023
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/bug_report.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.4.7
placeholder: v3.4.8
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.4.7
placeholder: v3.4.8
validations:
required: true
- type: dropdown
Expand Down
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,6 @@ as the cornerstone for network automation in thousands of organizations.
[![NetBox Labs](https://raw.githubusercontent.com/wiki/netbox-community/netbox/images/sponsors/netbox_labs.png)](https://netboxlabs.com)
          
[![DigitalOcean](https://raw.githubusercontent.com/wiki/netbox-community/netbox/images/sponsors/digitalocean.png)](https://try.digitalocean.com/developer-cloud)
          
[![NS1](https://raw.githubusercontent.com/wiki/netbox-community/netbox/images/sponsors/ns1.png)](https://ns1.com)
<br />
[![Sentry](https://raw.githubusercontent.com/wiki/netbox-community/netbox/images/sponsors/sentry.png)](https://sentry.io)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
Expand Down
2 changes: 1 addition & 1 deletion docs/configuration/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ interface.

Default: False

This parameter serves as a safeguard to prevent some potentially dangerous behavior, such as generating new database schema migrations. Set this to `True` **only** if you are actively developing the NetBox code base.
This parameter serves as a safeguard to prevent some potentially dangerous behavior, such as generating new database schema migrations. Additionally, enabling this setting disables the debug warning banner in the UI. Set this to `True` **only** if you are actively developing the NetBox code base.
2 changes: 1 addition & 1 deletion docs/customization/reports.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ Once you have created a report, it will appear in the reports list. Initially, r
!!! note
To run a report, a user must be assigned the `extras.run_report` permission. This is achieved by assigning the user (or group) a permission on the Report object and specifying the `run` action in the admin UI as shown below.

![Adding the run action to a permission](/media/admin_ui_run_permission.png)
![Adding the run action to a permission](../media/admin_ui_run_permission.png)

### Via the Web UI

Expand Down
2 changes: 1 addition & 1 deletion docs/installation/1-postgresql.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Within the shell, enter the following commands to create the database and user (
```postgresql
CREATE DATABASE netbox;
CREATE USER netbox WITH PASSWORD 'J5brHrAXFLQSif0K';
GRANT ALL PRIVILEGES ON DATABASE netbox TO netbox;
ALTER DATABASE netbox OWNER TO netbox;
```

!!! danger "Use a strong password"
Expand Down
11 changes: 8 additions & 3 deletions docs/integrations/rest-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -584,11 +584,16 @@ Additionally, a token can be set to expire at a specific time. This can be usefu

#### Client IP Restriction

!!! note
This feature was introduced in NetBox v3.3.

Each API token can optionally be restricted by client IP address. If one or more allowed IP prefixes/addresses is defined for a token, authentication will fail for any client connecting from an IP address outside the defined range(s). This enables restricting the use a token to a specific client. (By default, any client IP address is permitted.)

#### Creating Tokens for Other Users

It is possible to provision authentication tokens for other users via the REST API. To do, so the requesting user must have the `users.grant_token` permission assigned. While all users have inherent permission to create their own tokens, this permission is required to enable the creation of tokens for other users.

![Adding the grant action to a permission](../media/admin_ui_grant_permission.png)

!!! warning "Exercise Caution"
The ability to create tokens on behalf of other users enables the requestor to access the created token. This ability is intended e.g. for the provisioning of tokens by automated services, and should be used with extreme caution to avoid a security compromise.

### Authenticating to the API

Expand Down
Binary file added docs/media/admin_ui_grant_permission.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
32 changes: 32 additions & 0 deletions docs/release-notes/version-3.4.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,37 @@
# NetBox v3.4

## v3.4.8 (2023-04-12)

### Enhancements

* [#10414](https://github.com/netbox-community/netbox/issues/10414) - Enable general purpose image attachments for device types
* [#10600](https://github.com/netbox-community/netbox/issues/10600) - Allow custom object fields to reference a user or group
* [#11015](https://github.com/netbox-community/netbox/issues/11015) - Remove unit from commit rate column header in circuits table
* [#11431](https://github.com/netbox-community/netbox/issues/11431) - Disallow changing custom field type after creation
* [#11453](https://github.com/netbox-community/netbox/issues/11453) - Display a warning banner when `DEBUG` is enabled
* [#12007](https://github.com/netbox-community/netbox/issues/12007) - Enable filtering of VM Interfaces by assigned VLAN
* [#12095](https://github.com/netbox-community/netbox/issues/12095) - Specify UTF-8 encoding for default export template MIME type
* [#12207](https://github.com/netbox-community/netbox/issues/12207) - Introduce the `grant_token` permission for controlling the creation of API tokens on behalf of other users

### Bug Fixes

* [#10221](https://github.com/netbox-community/netbox/issues/10221) - Validate generic foreign key relations assigned via REST API requests
* [#11432](https://github.com/netbox-community/netbox/issues/11432) - Prevent existing components & component templates from being reassigned to different devices/device types via the REST API
* [#11454](https://github.com/netbox-community/netbox/issues/11454) - Raise validation error if generic foreign key assignment does not specify both object type and ID
* [#11746](https://github.com/netbox-community/netbox/issues/11746) - Fix cleanup of object data when deleting a custom field
* [#12011](https://github.com/netbox-community/netbox/issues/12011) - Fix KeyError exception when attempting to add module bays in bulk
* [#12040](https://github.com/netbox-community/netbox/issues/12040) - Display relevant UI tab upon bulk import validation failure
* [#12074](https://github.com/netbox-community/netbox/issues/12074) - Fix the automatic assignment of racks to devices via the REST API
* [#12084](https://github.com/netbox-community/netbox/issues/12084) - Fix exception when attempting to create a saved filter for applied filters
* [#12087](https://github.com/netbox-community/netbox/issues/12087) - Fix bulk editing of many-to-many relationships
* [#12117](https://github.com/netbox-community/netbox/issues/12117) - Hide clone button for objects with no clonable attributes
* [#12118](https://github.com/netbox-community/netbox/issues/12118) - Fix instantiation of nested inventory item templates when creating a device
* [#12184](https://github.com/netbox-community/netbox/issues/12184) - Fix filtered bulk deletion for various models
* [#12190](https://github.com/netbox-community/netbox/issues/12190) - Fix form layout for plugin textarea fields
* [#12227](https://github.com/netbox-community/netbox/issues/12227) - Fix tenant assignment on bulk import of L2VPNs

---

## v3.4.7 (2023-03-28)

### Enhancements
Expand Down
6 changes: 5 additions & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ theme:
custom_dir: docs/_theme/
icon:
repo: fontawesome/brands/github
features:
- content.code.copy
- navigation.footer
palette:
- media: "(prefers-color-scheme: light)"
scheme: default
Expand All @@ -20,7 +23,8 @@ theme:
icon: material/lightbulb
name: Switch to Light Mode
plugins:
- search
- search:
lang: en
- mkdocstrings:
handlers:
python:
Expand Down
4 changes: 3 additions & 1 deletion netbox/circuits/tables/circuits.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ class CircuitTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
template_code=CIRCUITTERMINATION_LINK,
verbose_name='Side Z'
)
commit_rate = CommitRateColumn()
commit_rate = CommitRateColumn(
verbose_name='Commit Rate'
)
comments = columns.MarkdownColumn()
tags = columns.TagColumn(
url_name='circuits:circuit_list'
Expand Down
1 change: 1 addition & 0 deletions netbox/circuits/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ class CircuitTypeBulkDeleteView(generic.BulkDeleteView):
queryset = CircuitType.objects.annotate(
circuit_count=count_related(Circuit, 'type')
)
filterset = filtersets.CircuitTypeFilterSet
table = tables.CircuitTypeTable


Expand Down
117 changes: 68 additions & 49 deletions netbox/dcim/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
'CableFilterSet',
'CabledObjectFilterSet',
'CableTerminationFilterSet',
'CommonInterfaceFilterSet',
'ConsoleConnectionFilterSet',
'ConsolePortFilterSet',
'ConsolePortTemplateFilterSet',
Expand Down Expand Up @@ -1321,11 +1322,63 @@ class Meta:
fields = ['id', 'name', 'label', 'feed_leg', 'description', 'cable_end']


class CommonInterfaceFilterSet(django_filters.FilterSet):
vlan_id = django_filters.CharFilter(
method='filter_vlan_id',
label=_('Assigned VLAN')
)
vlan = django_filters.CharFilter(
method='filter_vlan',
label=_('Assigned VID')
)
vrf_id = django_filters.ModelMultipleChoiceFilter(
field_name='vrf',
queryset=VRF.objects.all(),
label=_('VRF'),
)
vrf = django_filters.ModelMultipleChoiceFilter(
field_name='vrf__rd',
queryset=VRF.objects.all(),
to_field_name='rd',
label=_('VRF (RD)'),
)
l2vpn_id = django_filters.ModelMultipleChoiceFilter(
field_name='l2vpn_terminations__l2vpn',
queryset=L2VPN.objects.all(),
label=_('L2VPN (ID)'),
)
l2vpn = django_filters.ModelMultipleChoiceFilter(
field_name='l2vpn_terminations__l2vpn__identifier',
queryset=L2VPN.objects.all(),
to_field_name='identifier',
label=_('L2VPN'),
)

def filter_vlan_id(self, queryset, name, value):
value = value.strip()
if not value:
return queryset
return queryset.filter(
Q(untagged_vlan_id=value) |
Q(tagged_vlans=value)
)

def filter_vlan(self, queryset, name, value):
value = value.strip()
if not value:
return queryset
return queryset.filter(
Q(untagged_vlan_id__vid=value) |
Q(tagged_vlans__vid=value)
)


class InterfaceFilterSet(
ModularDeviceComponentFilterSet,
NetBoxModelFilterSet,
CabledObjectFilterSet,
PathEndpointFilterSet
PathEndpointFilterSet,
CommonInterfaceFilterSet
):
# Override device and device_id filters from DeviceComponentFilterSet to match against any peer virtual chassis
# members
Expand Down Expand Up @@ -1370,14 +1423,6 @@ class InterfaceFilterSet(
poe_type = django_filters.MultipleChoiceFilter(
choices=InterfacePoETypeChoices
)
vlan_id = django_filters.CharFilter(
method='filter_vlan_id',
label=_('Assigned VLAN')
)
vlan = django_filters.CharFilter(
method='filter_vlan',
label=_('Assigned VID')
)
type = django_filters.MultipleChoiceFilter(
choices=InterfaceTypeChoices,
null_value=None
Expand All @@ -1388,17 +1433,6 @@ class InterfaceFilterSet(
rf_channel = django_filters.MultipleChoiceFilter(
choices=WirelessChannelChoices
)
vrf_id = django_filters.ModelMultipleChoiceFilter(
field_name='vrf',
queryset=VRF.objects.all(),
label=_('VRF'),
)
vrf = django_filters.ModelMultipleChoiceFilter(
field_name='vrf__rd',
queryset=VRF.objects.all(),
to_field_name='rd',
label=_('VRF (RD)'),
)
vdc_id = django_filters.ModelMultipleChoiceFilter(
field_name='vdcs',
queryset=VirtualDeviceContext.objects.all(),
Expand All @@ -1416,17 +1450,6 @@ class InterfaceFilterSet(
to_field_name='name',
label='Virtual Device Context',
)
l2vpn_id = django_filters.ModelMultipleChoiceFilter(
field_name='l2vpn_terminations__l2vpn',
queryset=L2VPN.objects.all(),
label=_('L2VPN (ID)'),
)
l2vpn = django_filters.ModelMultipleChoiceFilter(
field_name='l2vpn_terminations__l2vpn__identifier',
queryset=L2VPN.objects.all(),
to_field_name='identifier',
label=_('L2VPN'),
)

class Meta:
model = Interface
Expand Down Expand Up @@ -1456,24 +1479,6 @@ def filter_device_id(self, queryset, name, id_list):
except Device.DoesNotExist:
return queryset.none()

def filter_vlan_id(self, queryset, name, value):
value = value.strip()
if not value:
return queryset
return queryset.filter(
Q(untagged_vlan_id=value) |
Q(tagged_vlans=value)
)

def filter_vlan(self, queryset, name, value):
value = value.strip()
if not value:
return queryset
return queryset.filter(
Q(untagged_vlan_id__vid=value) |
Q(tagged_vlans__vid=value)
)

def filter_kind(self, queryset, name, value):
value = value.strip().lower()
return {
Expand Down Expand Up @@ -1662,12 +1667,14 @@ class CableFilterSet(TenancyFilterSet, NetBoxModelFilterSet):
field_name='terminations__termination_type'
)
termination_a_id = MultiValueNumberFilter(
method='filter_by_cable_end_a',
field_name='terminations__termination_id'
)
termination_b_type = ContentTypeFilter(
field_name='terminations__termination_type'
)
termination_b_id = MultiValueNumberFilter(
method='filter_by_cable_end_b',
field_name='terminations__termination_id'
)
type = django_filters.MultipleChoiceFilter(
Expand Down Expand Up @@ -1725,6 +1732,18 @@ def filter_by_termination(self, queryset, name, value):
# Supported objects: device, rack, location, site
return queryset.filter(**{f'terminations___{name}__in': value}).distinct()

def filter_by_cable_end(self, queryset, name, value, side):
# Filter by termination id and cable_end type
return queryset.filter(**{f'{name}__in': value, 'terminations__cable_end': side}).distinct()

def filter_by_cable_end_a(self, queryset, name, value):
# Filter by termination id and cable_end type
return self.filter_by_cable_end(queryset, name, value, CableEndChoices.SIDE_A)

def filter_by_cable_end_b(self, queryset, name, value):
# Filter by termination id and cable_end type
return self.filter_by_cable_end(queryset, name, value, CableEndChoices.SIDE_B)


class CableTerminationFilterSet(BaseFilterSet):
termination_type = ContentTypeFilter()
Expand Down
4 changes: 2 additions & 2 deletions netbox/dcim/forms/bulk_create.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,9 @@ class RearPortBulkCreateForm(

class ModuleBayBulkCreateForm(DeviceBulkAddComponentForm):
model = ModuleBay
field_order = ('name', 'label', 'position_pattern', 'description', 'tags')
field_order = ('name', 'label', 'position', 'description', 'tags')
replication_fields = ('name', 'label', 'position')
position_pattern = ExpandableNameField(
position = ExpandableNameField(
label=_('Position'),
required=False,
help_text=_('Alphanumeric ranges are supported. (Must match the number of names being created.)')
Expand Down
6 changes: 4 additions & 2 deletions netbox/dcim/models/cables.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,6 @@ def clean(self):
# Validate length and length_unit
if self.length is not None and not self.length_unit:
raise ValidationError("Must specify a unit when setting a cable length")
elif self.length is None:
self.length_unit = ''

if self.pk is None and (not self.a_terminations or not self.b_terminations):
raise ValidationError("Must define A and B terminations when creating a new cable.")
Expand Down Expand Up @@ -187,6 +185,10 @@ def save(self, *args, **kwargs):
else:
self._abs_length = None

# Clear length_unit if no length is defined
if self.length is None:
self.length_unit = ''

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

# Update the private pk used in __str__ in case this is a new object (i.e. just got its pk)
Expand Down
11 changes: 11 additions & 0 deletions netbox/dcim/models/device_component_templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,12 @@ class Meta:
),
)

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

# Cache the original DeviceType ID for reference under clean()
self._original_device_type = self.device_type_id

def to_objectchange(self, action):
objectchange = super().to_objectchange(action)
if self.device_type is not None:
Expand All @@ -131,6 +137,11 @@ 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:
raise ValidationError({
"device_type": "Component templates cannot be moved to a different device type."
})

# A component template must belong to a DeviceType *or* to a ModuleType
if self.device_type and self.module_type:
raise ValidationError(
Expand Down
Loading