Merge branch 'develop' into feature

This commit is contained in:
jeremystretch 2021-11-09 16:52:36 -05:00
commit f93d6813a9
20 changed files with 83 additions and 45 deletions

View File

@ -13,10 +13,7 @@ body:
- type: input - type: input
attributes: attributes:
label: NetBox version label: NetBox version
description: > description: What version of NetBox are you currently running?
What version of NetBox are you currently running? (If you don't have access to the most
recent NetBox release, consider testing on our [demo instance](https://demo.netbox.dev/)
before opening a bug report to see if your issue has already been addressed.)
placeholder: v3.0.9 placeholder: v3.0.9
validations: validations:
required: true required: true

View File

@ -76,14 +76,10 @@ free to add a comment with any additional justification for the feature.
(However, note that comments with no substance other than a "+1" will be (However, note that comments with no substance other than a "+1" will be
deleted. Please use GitHub's reactions feature to indicate your support.) deleted. Please use GitHub's reactions feature to indicate your support.)
* Due to a large backlog of feature requests, we are not currently accepting * Before filing a new feature request, consider raising your idea in a
any proposals which substantially extend NetBox's functionality beyond its [GitHub discussion](https://github.com/netbox-community/netbox/discussions)
current feature set. This includes the introduction of any new views or models first. Feedback you receive there will help validate and shape the proposed
which have not already been proposed in an existing feature request. feature before filing a formal issue.
* Before filing a new feature request, consider raising your idea on the
mailing list first. Feedback you receive there will help validate and shape the
proposed feature before filing a formal issue.
* Good feature requests are very narrowly defined. Be sure to thoroughly * Good feature requests are very narrowly defined. Be sure to thoroughly
describe the functionality and data model(s) being proposed. The more effort describe the functionality and data model(s) being proposed. The more effort

View File

@ -27,3 +27,13 @@ Device components represent discrete objects within a device which are used to t
--- ---
{!models/dcim/cable.md!} {!models/dcim/cable.md!}
In the example below, three individual cables comprise a path between devices A and D:
![Cable path](../media/models/dcim_cable_trace.png)
Traced from Interface 1 on Device A, NetBox will show the following path:
* Cable 1: Interface 1 to Front Port 1
* Cable 2: Rear Port 1 to Rear Port 2
* Cable 3: Front Port 2 to Interface 2

View File

@ -22,13 +22,3 @@ Each cable may be assigned a type, label, length, and color. Each cable is also
## Tracing Cables ## Tracing Cables
A cable may be traced from either of its endpoints by clicking the "trace" button. (A REST API endpoint also provides this functionality.) NetBox will follow the path of connected cables from this termination across the directly connected cable to the far-end termination. If the cable connects to a pass-through port, and the peer port has another cable connected, NetBox will continue following the cable path until it encounters a non-pass-through or unconnected termination point. The entire path will be displayed to the user. A cable may be traced from either of its endpoints by clicking the "trace" button. (A REST API endpoint also provides this functionality.) NetBox will follow the path of connected cables from this termination across the directly connected cable to the far-end termination. If the cable connects to a pass-through port, and the peer port has another cable connected, NetBox will continue following the cable path until it encounters a non-pass-through or unconnected termination point. The entire path will be displayed to the user.
In the example below, three individual cables comprise a path between devices A and D:
![Cable path](../media/models/dcim_cable_trace.png)
Traced from Interface 1 on Device A, NetBox will show the following path:
* Cable 1: Interface 1 to Front Port 1
* Cable 2: Rear Port 1 to Rear Port 2
* Cable 3: Front Port 2 to Interface 2

View File

@ -2,9 +2,19 @@
## v3.0.10 (FUTURE) ## v3.0.10 (FUTURE)
### Enhancements
* [#7740](https://github.com/netbox-community/netbox/issues/7740) - Add mini-DIN 8 console port type
* [#7760](https://github.com/netbox-community/netbox/issues/7760) - Add `vid` filter field to VLANs list
### Bug Fixes ### Bug Fixes
* [#7701](https://github.com/netbox-community/netbox/issues/7701) - Fix conflation of assigned IP status & role in interface tables
* [#7741](https://github.com/netbox-community/netbox/issues/7741) - Fix 404 when attaching multiple images in succession
* [#7752](https://github.com/netbox-community/netbox/issues/7752) - Fix minimum version check under Python v3.10 * [#7752](https://github.com/netbox-community/netbox/issues/7752) - Fix minimum version check under Python v3.10
* [#7766](https://github.com/netbox-community/netbox/issues/7766) - Add missing outer dimension columns to rack table
* [#7780](https://github.com/netbox-community/netbox/issues/7780) - Preserve mutli-line values during CSV file import
* [#7783](https://github.com/netbox-community/netbox/issues/7783) - Fix indentation of locations under site view
--- ---

View File

@ -204,6 +204,7 @@ class ConsolePortTypeChoices(ChoiceSet):
TYPE_RJ11 = 'rj-11' TYPE_RJ11 = 'rj-11'
TYPE_RJ12 = 'rj-12' TYPE_RJ12 = 'rj-12'
TYPE_RJ45 = 'rj-45' TYPE_RJ45 = 'rj-45'
TYPE_MINI_DIN_8 = 'mini-din-8'
TYPE_USB_A = 'usb-a' TYPE_USB_A = 'usb-a'
TYPE_USB_B = 'usb-b' TYPE_USB_B = 'usb-b'
TYPE_USB_C = 'usb-c' TYPE_USB_C = 'usb-c'
@ -221,6 +222,7 @@ class ConsolePortTypeChoices(ChoiceSet):
(TYPE_RJ11, 'RJ-11'), (TYPE_RJ11, 'RJ-11'),
(TYPE_RJ12, 'RJ-12'), (TYPE_RJ12, 'RJ-12'),
(TYPE_RJ45, 'RJ-45'), (TYPE_RJ45, 'RJ-45'),
(TYPE_MINI_DIN_8, 'Mini-DIN 8'),
)), )),
('USB', ( ('USB', (
(TYPE_USB_A, 'USB Type A'), (TYPE_USB_A, 'USB Type A'),

View File

@ -75,12 +75,20 @@ class RackTable(BaseTable):
tags = TagColumn( tags = TagColumn(
url_name='dcim:rack_list' url_name='dcim:rack_list'
) )
outer_width = tables.TemplateColumn(
template_code="{{ record.outer_width }} {{ record.outer_unit }}",
verbose_name='Outer Width'
)
outer_depth = tables.TemplateColumn(
template_code="{{ record.outer_depth }} {{ record.outer_unit }}",
verbose_name='Outer Depth'
)
class Meta(BaseTable.Meta): class Meta(BaseTable.Meta):
model = Rack model = Rack
fields = ( fields = (
'pk', 'id', 'name', 'site', 'location', 'status', 'facility_id', 'tenant', 'role', 'serial', 'asset_tag', 'type', 'pk', 'id', 'name', 'site', 'location', 'status', 'facility_id', 'tenant', 'role', 'serial', 'asset_tag', 'type',
'width', 'u_height', 'comments', 'device_count', 'get_utilization', 'get_power_utilization', 'tags', 'width', 'outer_width', 'outer_depth', 'u_height', 'comments', 'device_count', 'get_utilization', 'get_power_utilization', 'tags',
) )
default_columns = ( default_columns = (
'pk', 'name', 'site', 'location', 'status', 'facility_id', 'tenant', 'role', 'u_height', 'device_count', 'pk', 'name', 'site', 'location', 'status', 'facility_id', 'tenant', 'role', 'u_height', 'device_count',

View File

@ -40,17 +40,13 @@ DEVICEBAY_STATUS = """
INTERFACE_IPADDRESSES = """ INTERFACE_IPADDRESSES = """
<div class="table-badge-group"> <div class="table-badge-group">
{% for ip in record.ip_addresses.all %} {% for ip in record.ip_addresses.all %}
<a {% if ip.status != 'active' %}
class="table-badge{% if ip.status != 'active' %} badge bg-{{ ip.get_status_class }}{% elif ip.role %} badge bg-{{ ip.get_role_class }}{% endif %}" <a href="{{ ip.get_absolute_url }}" class="table-badge badge bg-{{ ip.get_status_class }}" data-bs-toggle="tooltip" data-bs-placement="left" title="{{ ip.get_status_display }}">{{ ip }}</a>
href="{{ ip.get_absolute_url }}" {% else %}
{% if ip.status != 'active'%}data-bs-toggle="tooltip" data-bs-placement="left" title="{{ ip.get_status_display }}" <a href="{{ ip.get_absolute_url }}" class="table-badge">{{ ip }}</a>
{% elif ip.role %}data-bs-toggle="tooltip" data-bs-placement="left" title="{{ ip.get_role_display }}" {% endif %}
{% endif %} {% endfor %}
>
{{ ip }}
</a>
{% endfor %}
</div> </div>
""" """

View File

@ -353,6 +353,8 @@ class ImageAttachment(BigIDModel):
objects = RestrictedQuerySet.as_manager() objects = RestrictedQuerySet.as_manager()
clone_fields = ('content_type', 'object_id')
class Meta: class Meta:
ordering = ('name', 'pk') # name may be non-unique ordering = ('name', 'pk') # name may be non-unique

View File

@ -475,11 +475,7 @@ class ImageAttachmentEditView(generic.ObjectEditView):
def alter_obj(self, instance, request, args, kwargs): def alter_obj(self, instance, request, args, kwargs):
if not instance.pk: if not instance.pk:
# Assign the parent object based on URL kwargs # Assign the parent object based on URL kwargs
try: content_type = get_object_or_404(ContentType, pk=request.GET.get('content_type'))
app_label, model = request.GET.get('content_type').split('.')
except (AttributeError, ValueError):
raise Http404("Content type not specified")
content_type = get_object_or_404(ContentType, app_label=app_label, model=model)
instance.parent = get_object_or_404(content_type.model_class(), pk=request.GET.get('object_id')) instance.parent = get_object_or_404(content_type.model_class(), pk=request.GET.get('object_id'))
return instance return instance

View File

@ -1,3 +1,4 @@
import django_filters
from django import forms from django import forms
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
@ -471,7 +472,7 @@ class VLANFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterFo
field_groups = [ field_groups = [
['q', 'tag'], ['q', 'tag'],
['region_id', 'site_group_id', 'site_id'], ['region_id', 'site_group_id', 'site_id'],
['group_id', 'status', 'role_id'], ['group_id', 'status', 'role_id', 'vid'],
['tenant_group_id', 'tenant_id'], ['tenant_group_id', 'tenant_id'],
] ]
q = forms.CharField( q = forms.CharField(
@ -523,6 +524,10 @@ class VLANFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterFo
label=_('Role'), label=_('Role'),
fetch_trigger='open' fetch_trigger='open'
) )
vid = forms.IntegerField(
required=False,
label='VLAN ID'
)
tag = TagFilterField(model) tag = TagFilterField(model)

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -814,7 +814,7 @@ table .table-badge-group {
} }
&.badge:not(:last-of-type):not(:only-child) { &.badge:not(:last-of-type):not(:only-child) {
margin-bottom: map.get($spacers, 2); margin-bottom: map.get($spacers, 1);
} }
} }
} }

View File

@ -236,8 +236,8 @@
</tr> </tr>
{% for location in locations %} {% for location in locations %}
<tr> <tr>
<td style="padding-left: {{ location.level }}8px"> <td>
<i class="mdi mdi-folder-open"></i> {% for i in location.level|as_range %}<i class="mdi mdi-circle-small"></i>{% endfor %}
<a href="{{ location.get_absolute_url }}">{{ location }}</a> <a href="{{ location.get_absolute_url }}">{{ location }}</a>
</td> </td>
<td> <td>

View File

@ -44,7 +44,7 @@
</div> </div>
{% if perms.extras.add_imageattachment %} {% if perms.extras.add_imageattachment %}
<div class="card-footer text-end noprint"> <div class="card-footer text-end noprint">
<a href="{% url 'extras:imageattachment_add' %}?content_type={{ object|meta:"app_label" }}.{{ object|meta:"model_name" }}&object_id={{ object.pk }}" class="btn btn-primary btn-sm"> <a href="{% url 'extras:imageattachment_add' %}?content_type={{ object|content_type_id }}&object_id={{ object.pk }}" class="btn btn-primary btn-sm">
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Attach an image <i class="mdi mdi-plus-thick" aria-hidden="true"></i> Attach an image
</a> </a>
</div> </div>

View File

@ -248,7 +248,7 @@ class CSVFileField(forms.FileField):
return None return None
csv_str = file.read().decode('utf-8').strip() csv_str = file.read().decode('utf-8').strip()
reader = csv.reader(csv_str.splitlines()) reader = csv.reader(StringIO(csv_str))
headers, records = parse_csv(reader) headers, records = parse_csv(reader)
return headers, records return headers, records

View File

@ -7,6 +7,7 @@ from typing import Dict, Any
import yaml import yaml
from django import template from django import template
from django.conf import settings from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.template.defaultfilters import date from django.template.defaultfilters import date
from django.urls import NoReverseMatch, reverse from django.urls import NoReverseMatch, reverse
from django.utils import timezone from django.utils import timezone
@ -80,6 +81,25 @@ def meta(obj, attr):
return getattr(obj._meta, attr, '') return getattr(obj._meta, attr, '')
@register.filter()
def content_type(obj):
"""
Return the ContentType for the given object.
"""
return ContentType.objects.get_for_model(obj)
@register.filter()
def content_type_id(obj):
"""
Return the ContentType ID for the given object.
"""
content_type = ContentType.objects.get_for_model(obj)
if content_type:
return content_type.pk
return None
@register.filter() @register.filter()
def viewname(model, action): def viewname(model, action):
""" """

View File

@ -11,6 +11,7 @@ exec 1>&2
EXIT=0 EXIT=0
RED='\033[0;31m' RED='\033[0;31m'
YELLOW='\033[0;33m'
NOCOLOR='\033[0m' NOCOLOR='\033[0m'
if [ -d ./venv/ ]; then if [ -d ./venv/ ]; then
@ -22,6 +23,11 @@ if [ -d ./venv/ ]; then
fi fi
fi fi
if [ ${NOVALIDATE} ]; then
echo "${YELLOW}Skipping validation checks${NOCOLOR}"
exit $EXIT
fi
echo "Validating PEP8 compliance..." echo "Validating PEP8 compliance..."
pycodestyle --ignore=W504,E501 --exclude=node_modules netbox/ pycodestyle --ignore=W504,E501 --exclude=node_modules netbox/
if [ $? != 0 ]; then if [ $? != 0 ]; then