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 v2.10.3 #5581

Merged
merged 32 commits into from
Jan 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
5fe5fd7
PRVB
jeremystretch Dec 21, 2020
8f4197c
Fixes #5518: Fix persistent vertical scrollbar
jeremystretch Dec 22, 2020
dc6dbdf
Update documentation to reference the feature branch
jeremystretch Dec 22, 2020
09633ee
Move rack group field directly beneath site
jeremystretch Dec 22, 2020
e4f22bc
Employ signals to update child objects when RackGroup/Rack site assig…
jeremystretch Dec 22, 2020
ddd10ba
Clean up hierarchical table columns
jeremystretch Dec 22, 2020
8d9d4ce
Extend handle_rackgroup_site_change() receiver to update power panels
jeremystretch Dec 23, 2020
fe2e33a
Merge pull request #5522 from netbox-community/5311-site-rack-validation
jeremystretch Dec 23, 2020
396b0da
Changelog for #5311
jeremystretch Dec 23, 2020
fce6129
Fixes #5301: Fix misleading error when racking a device with invalid …
jeremystretch Dec 23, 2020
1fe5857
Fixes #5543: Fix rendering of config contexts with cluster assignment…
jeremystretch Dec 28, 2020
6f39e65
Fixes #5540: Fix exception when viewing a provider with one or more t…
jeremystretch Dec 28, 2020
cc1a43e
Fixes #5533: Fix bulk editing of objects with required custom fields
jeremystretch Dec 28, 2020
b2e05aa
Closes #5531: Ensure consistent calls to parent clean() methods for m…
jeremystretch Dec 28, 2020
8ae3331
Closes #5549: Eliminate extraneous database queries when using brief …
jeremystretch Dec 29, 2020
249948e
Fixes #5546: Add custom field bulk edit support for cables, power pan…
jeremystretch Dec 29, 2020
d989ce2
Fixes #5547: Add custom field bulk import support for cables, power p…
jeremystretch Dec 29, 2020
7873952
Avoid wrapping text in hierarchical table columns
jeremystretch Dec 30, 2020
af3c490
Fixes #5558: Fix regex validation support for custom URL fields
jeremystretch Dec 31, 2020
39e6872
Fixes #5557: Fix VRF route target assignment via REST API
jeremystretch Dec 31, 2020
e73c225
Fixes #5551: Restore missing import button on services list
jeremystretch Dec 31, 2020
a9a2509
Fixes: #5049 - Account for chassis neighbors in lldp_neighbors template
DanSheps Jan 5, 2021
359ae5d
Raise exceptions for other inconsistencies when migrating custom fiel…
candlerb Jan 5, 2021
d16a7e1
Fixes: #5563 - Fix power feed cable trace
DanSheps Jan 5, 2021
3441216
Fixes: #5564 - Raise validation error if a PowerPortTemplate's draw e…
DanSheps Jan 5, 2021
98983e7
Merge pull request #5578 from candlerb/candlerb-5573
jeremystretch Jan 5, 2021
aa10430
Changelog for #5573
jeremystretch Jan 5, 2021
0accaed
Fixes #5569: Ensure consistent labeling of interface mgmt_only field
jeremystretch Jan 5, 2021
4a2d288
Fixes #5579: mark `ie` and `nie` filter exprs as insensitive
Jan 5, 2021
a77658a
Merge pull request #5580 from nemith/apidocfix
jeremystretch Jan 6, 2021
601cbd2
Release v2.10.3
jeremystretch Jan 6, 2021
a6cb796
Merge branch 'master' into develop
jeremystretch Jan 6, 2021
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
6 changes: 3 additions & 3 deletions docs/development/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ base_requirements.txt contrib docs mkdocs.yml NOTICE requ
CHANGELOG.md CONTRIBUTING.md LICENSE.txt netbox README.md scripts
```

The NetBox project utilizes three long-term branches:
The NetBox project utilizes three persistent git branches to track work:

* `master` - Serves as a snapshot of the current stable release
* `develop` - All development on the upcoming stable release occurs here
* `develop-x.y` - Tracks work on an upcoming major release
* `feature` - Tracks work on an upcoming major release

Typically, you'll base pull requests off of the `develop` branch, or off of `develop-x.y` if you're working on a new major release. **Never** base pull requests off of the master branch, which receives merged only from the `develop` branch.
Typically, you'll base pull requests off of the `develop` branch, or off of `feature` if you're working on a new major release. **Never** merge pull requests into the `master` branch, which receives merged only from the `develop` branch.

### Enable Pre-Commit Hooks

Expand Down
5 changes: 1 addition & 4 deletions docs/development/release-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,7 @@ Close the release milestone on GitHub after ensuring there are no remaining open

### Merge the Release Branch

Submit a pull request to merge the release branch `develop-x.y` into the `develop` branch in preparation for its releases.

!!! warning
No further releases for the current major version can be published once this pull request is merged.
Submit a pull request to merge the `feature` branch into the `develop` branch in preparation for its release.

---

Expand Down
2 changes: 1 addition & 1 deletion docs/installation/3-netbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ Checking connectivity... done.
```

!!! note
Installation via git also allows you to easily try out development versions of NetBox. The `develop` branch contains all work underway for the next minor release, and the `develop-x.y` branch (if present) tracks progress on the next major release.
Installation via git also allows you to easily try out development versions of NetBox. The `develop` branch contains all work underway for the next minor release, and the `feature` branch tracks progress on the next major release.

## Create the NetBox System User

Expand Down
23 changes: 23 additions & 0 deletions docs/release-notes/version-2.10.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,28 @@
# NetBox v2.10

## v2.10.3 (2021-01-05)

### Bug Fixes

* [#5049](https://github.com/netbox-community/netbox/issues/5049) - Add check for LLDP neighbor chassis name to lldp_neighbors
* [#5301](https://github.com/netbox-community/netbox/issues/5301) - Fix misleading error when racking a device with invalid parameters
* [#5311](https://github.com/netbox-community/netbox/issues/5311) - Update child objects when a rack group is moved to a new site
* [#5518](https://github.com/netbox-community/netbox/issues/5518) - Fix persistent vertical scrollbar
* [#5533](https://github.com/netbox-community/netbox/issues/5533) - Fix bulk editing of objects with required custom fields
* [#5540](https://github.com/netbox-community/netbox/issues/5540) - Fix exception when viewing a provider with one or more tags assigned
* [#5543](https://github.com/netbox-community/netbox/issues/5543) - Fix rendering of config contexts with cluster assignment for devices
* [#5546](https://github.com/netbox-community/netbox/issues/5546) - Add custom field bulk edit support for cables, power panels, rack reservations, and virtual chassis
* [#5547](https://github.com/netbox-community/netbox/issues/5547) - Add custom field bulk import support for cables, power panels, rack reservations, and virtual chassis
* [#5551](https://github.com/netbox-community/netbox/issues/5551) - Restore missing import button on services list
* [#5557](https://github.com/netbox-community/netbox/issues/5557) - Fix VRF route target assignment via REST API
* [#5558](https://github.com/netbox-community/netbox/issues/5558) - Fix regex validation support for custom URL fields
* [#5563](https://github.com/netbox-community/netbox/issues/5563) - Fix power feed cable trace link
* [#5564](https://github.com/netbox-community/netbox/issues/5564) - Raise validation error if a power port template's `allocated_draw` exceeds its `maximum_draw`
* [#5569](https://github.com/netbox-community/netbox/issues/5569) - Ensure consistent labeling of interface `mgmt_only` field
* [#5573](https://github.com/netbox-community/netbox/issues/5573) - Report inconsistent values when migrating custom field data

---

## v2.10.2 (2020-12-21)

### Enhancements
Expand Down
4 changes: 2 additions & 2 deletions docs/rest-api/filtering.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ String based (char) fields (Name, Address, etc) support these lookup expressions
- `nisw` - negated case insensitive starts with
- `iew` - case insensitive ends with
- `niew` - negated case insensitive ends with
- `ie` - case sensitive exact match
- `nie` - negated case sensitive exact match
- `ie` - case insensitive exact match
- `nie` - negated case insensitive exact match

### Foreign Keys & Other Fields

Expand Down
1 change: 1 addition & 0 deletions netbox/circuits/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,4 @@ class CircuitTerminationViewSet(PathEndpointMixin, ModelViewSet):
)
serializer_class = serializers.CircuitTerminationSerializer
filterset_class = filters.CircuitTerminationFilterSet
brief_prefetch_fields = ['circuit']
11 changes: 11 additions & 0 deletions netbox/dcim/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ class DeviceTypeViewSet(CustomFieldModelViewSet):
)
serializer_class = serializers.DeviceTypeSerializer
filterset_class = filters.DeviceTypeFilterSet
brief_prefetch_fields = ['manufacturer']


#
Expand Down Expand Up @@ -493,6 +494,7 @@ class ConsolePortViewSet(PathEndpointMixin, ModelViewSet):
queryset = ConsolePort.objects.prefetch_related('device', '_path__destination', 'cable', '_cable_peer', 'tags')
serializer_class = serializers.ConsolePortSerializer
filterset_class = filters.ConsolePortFilterSet
brief_prefetch_fields = ['device']


class ConsoleServerPortViewSet(PathEndpointMixin, ModelViewSet):
Expand All @@ -501,18 +503,21 @@ class ConsoleServerPortViewSet(PathEndpointMixin, ModelViewSet):
)
serializer_class = serializers.ConsoleServerPortSerializer
filterset_class = filters.ConsoleServerPortFilterSet
brief_prefetch_fields = ['device']


class PowerPortViewSet(PathEndpointMixin, ModelViewSet):
queryset = PowerPort.objects.prefetch_related('device', '_path__destination', 'cable', '_cable_peer', 'tags')
serializer_class = serializers.PowerPortSerializer
filterset_class = filters.PowerPortFilterSet
brief_prefetch_fields = ['device']


class PowerOutletViewSet(PathEndpointMixin, ModelViewSet):
queryset = PowerOutlet.objects.prefetch_related('device', '_path__destination', 'cable', '_cable_peer', 'tags')
serializer_class = serializers.PowerOutletSerializer
filterset_class = filters.PowerOutletFilterSet
brief_prefetch_fields = ['device']


class InterfaceViewSet(PathEndpointMixin, ModelViewSet):
Expand All @@ -521,30 +526,35 @@ class InterfaceViewSet(PathEndpointMixin, ModelViewSet):
)
serializer_class = serializers.InterfaceSerializer
filterset_class = filters.InterfaceFilterSet
brief_prefetch_fields = ['device']


class FrontPortViewSet(PassThroughPortMixin, ModelViewSet):
queryset = FrontPort.objects.prefetch_related('device__device_type__manufacturer', 'rear_port', 'cable', 'tags')
serializer_class = serializers.FrontPortSerializer
filterset_class = filters.FrontPortFilterSet
brief_prefetch_fields = ['device']


class RearPortViewSet(PassThroughPortMixin, ModelViewSet):
queryset = RearPort.objects.prefetch_related('device__device_type__manufacturer', 'cable', 'tags')
serializer_class = serializers.RearPortSerializer
filterset_class = filters.RearPortFilterSet
brief_prefetch_fields = ['device']


class DeviceBayViewSet(ModelViewSet):
queryset = DeviceBay.objects.prefetch_related('installed_device').prefetch_related('tags')
serializer_class = serializers.DeviceBaySerializer
filterset_class = filters.DeviceBayFilterSet
brief_prefetch_fields = ['device']


class InventoryItemViewSet(ModelViewSet):
queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer').prefetch_related('tags')
serializer_class = serializers.InventoryItemSerializer
filterset_class = filters.InventoryItemFilterSet
brief_prefetch_fields = ['device']


#
Expand Down Expand Up @@ -600,6 +610,7 @@ class VirtualChassisViewSet(ModelViewSet):
)
serializer_class = serializers.VirtualChassisSerializer
filterset_class = filters.VirtualChassisFilterSet
brief_prefetch_fields = ['master']


#
Expand Down
64 changes: 26 additions & 38 deletions netbox/dcim/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ class ComponentForm(BootstrapMixin, forms.Form):
)

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

# Validate that the number of components being created from both the name_pattern and label_pattern are equal
if self.cleaned_data['label_pattern']:
Expand Down Expand Up @@ -783,7 +784,7 @@ class Meta:
]


class RackReservationCSVForm(CSVModelForm):
class RackReservationCSVForm(CustomFieldModelCSVForm):
site = CSVModelChoiceField(
queryset=Site.objects.all(),
to_field_name='name',
Expand Down Expand Up @@ -833,7 +834,7 @@ def __init__(self, data=None, *args, **kwargs):
self.fields['rack'].queryset = self.fields['rack'].queryset.filter(**params)


class RackReservationBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
class RackReservationBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=RackReservation.objects.all(),
widget=forms.MultipleHiddenInput()
Expand Down Expand Up @@ -1438,6 +1439,7 @@ def __init__(self, *args, **kwargs):
self.fields['rear_port_set'].choices = choices

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

# Validate that the number of ports being created equals the number of selected (rear port, position) tuples
front_port_count = len(self.cleaned_data['name_pattern'])
Expand Down Expand Up @@ -1781,9 +1783,8 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
'group_id': '$rack_group',
}
)
position = forms.TypedChoiceField(
position = forms.IntegerField(
required=False,
empty_value=None,
help_text="The lowest-numbered unit occupied by the device",
widget=APISelect(
api_url='/api/dcim/racks/{{rack}}/elevation/',
Expand Down Expand Up @@ -1856,6 +1857,7 @@ class Meta:
"config context",
}
widgets = {
'face': StaticSelect2(),
'status': StaticSelect2(),
'primary_ip4': StaticSelect2(),
'primary_ip6': StaticSelect2(),
Expand Down Expand Up @@ -1902,6 +1904,13 @@ def __init__(self, *args, **kwargs):
Q(manufacturer__isnull=True) | Q(manufacturer=self.instance.device_type.manufacturer)
)

# Disable rack assignment if this is a child device installed in a parent device
if self.instance.device_type.is_child_device and hasattr(self.instance, 'parent_bay'):
self.fields['site'].disabled = True
self.fields['rack'].disabled = True
self.initial['site'] = self.instance.parent_bay.device.site_id
self.initial['rack'] = self.instance.parent_bay.device.rack_id

else:

# An object that doesn't exist yet can't have any IPs assigned to it
Expand All @@ -1911,31 +1920,9 @@ def __init__(self, *args, **kwargs):
self.fields['primary_ip6'].widget.attrs['readonly'] = True

# Rack position
pk = self.instance.pk if self.instance.pk else None
try:
if self.is_bound and self.data.get('rack') and str(self.data.get('face')):
position_choices = Rack.objects.get(pk=self.data['rack']) \
.get_rack_units(face=self.data.get('face'), exclude=pk)
elif self.initial.get('rack') and str(self.initial.get('face')):
position_choices = Rack.objects.get(pk=self.initial['rack']) \
.get_rack_units(face=self.initial.get('face'), exclude=pk)
else:
position_choices = []
except Rack.DoesNotExist:
position_choices = []
self.fields['position'].choices = [('', '---------')] + [
(p['id'], {
'label': p['name'],
'disabled': bool(p['device'] and p['id'] != self.initial.get('position')),
}) for p in position_choices
]

# Disable rack assignment if this is a child device installed in a parent device
if pk and self.instance.device_type.is_child_device and hasattr(self.instance, 'parent_bay'):
self.fields['site'].disabled = True
self.fields['rack'].disabled = True
self.initial['site'] = self.instance.parent_bay.device.site_id
self.initial['rack'] = self.instance.parent_bay.device.rack_id
position = self.data.get('position') or self.initial.get('position')
if position:
self.fields['position'].widget.choices = [(position, f'U{position}')]


class BaseDeviceCSVForm(CustomFieldModelCSVForm):
Expand Down Expand Up @@ -2944,6 +2931,7 @@ def __init__(self, *args, **kwargs):
self.fields['lag'].widget.attrs['disabled'] = True

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

# Untagged interfaces cannot be assigned tagged VLANs
if self.cleaned_data['mode'] == InterfaceModeChoices.MODE_ACCESS and self.cleaned_data['tagged_vlans']:
Expand Down Expand Up @@ -3092,6 +3080,7 @@ def __init__(self, *args, **kwargs):
self.fields['rear_port_set'].choices = choices

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

# Validate that the number of ports being created equals the number of selected (rear port, position) tuples
front_port_count = len(self.cleaned_data['name_pattern'])
Expand Down Expand Up @@ -3786,7 +3775,7 @@ class Meta:
}


class CableCSVForm(CSVModelForm):
class CableCSVForm(CustomFieldModelCSVForm):
# Termination A
side_a_device = CSVModelChoiceField(
queryset=Device.objects.all(),
Expand Down Expand Up @@ -3881,7 +3870,7 @@ def clean_length_unit(self):
return length_unit if length_unit is not None else ''


class CableBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
class CableBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=Cable.objects.all(),
widget=forms.MultipleHiddenInput
Expand Down Expand Up @@ -3924,6 +3913,7 @@ class Meta:
]

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

# Validate length/unit
length = self.cleaned_data.get('length')
Expand Down Expand Up @@ -4267,7 +4257,7 @@ def clean_device(self):
return device


class VirtualChassisBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
class VirtualChassisBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=VirtualChassis.objects.all(),
widget=forms.MultipleHiddenInput()
Expand All @@ -4281,7 +4271,7 @@ class Meta:
nullable_fields = ['domain']


class VirtualChassisCSVForm(CSVModelForm):
class VirtualChassisCSVForm(CustomFieldModelCSVForm):
master = CSVModelChoiceField(
queryset=Device.objects.all(),
to_field_name='name',
Expand Down Expand Up @@ -4368,7 +4358,7 @@ class Meta:
]


class PowerPanelCSVForm(CSVModelForm):
class PowerPanelCSVForm(CustomFieldModelCSVForm):
site = CSVModelChoiceField(
queryset=Site.objects.all(),
to_field_name='name',
Expand All @@ -4394,7 +4384,7 @@ def __init__(self, data=None, *args, **kwargs):
self.fields['rack_group'].queryset = self.fields['rack_group'].queryset.filter(**params)


class PowerPanelBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
class PowerPanelBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=PowerPanel.objects.all(),
widget=forms.MultipleHiddenInput
Expand Down Expand Up @@ -4422,9 +4412,7 @@ class PowerPanelBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
)

class Meta:
nullable_fields = (
'rack_group',
)
nullable_fields = ['rack_group']


class PowerPanelFilterForm(BootstrapMixin, CustomFieldFilterForm):
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 @@ -164,6 +164,15 @@ def instantiate(self, device):
allocated_draw=self.allocated_draw
)

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

if self.maximum_draw is not None and self.allocated_draw is not None:
if self.allocated_draw > self.maximum_draw:
raise ValidationError({
'allocated_draw': f"Allocated draw cannot exceed the maximum draw ({self.maximum_draw}W)."
})


class PowerOutletTemplate(ComponentTemplateModel):
"""
Expand Down Expand Up @@ -193,6 +202,7 @@ class Meta:
unique_together = ('device_type', 'name')

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

# Validate power port assignment
if self.power_port and self.power_port.device_type != self.device_type:
Expand Down Expand Up @@ -278,6 +288,7 @@ class Meta:
)

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

# Validate rear port assignment
if self.rear_port.device_type != self.device_type:
Expand Down
Loading