Skip to content

Commit

Permalink
Merge branch 'develop' into feature
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremystretch committed Aug 8, 2022
2 parents 56433cf + 064d7f3 commit 1b88b36
Show file tree
Hide file tree
Showing 14 changed files with 53 additions and 37 deletions.
2 changes: 1 addition & 1 deletion base_requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ bleach

# The Python web framework on which NetBox is built
# https://github.com/django/django
Django
Django<4.1

# Django middleware which permits cross-domain API requests
# https://github.com/OttoYiu/django-cors-headers
Expand Down
13 changes: 12 additions & 1 deletion docs/release-notes/version-3.2.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# NetBox v3.2

## v3.2.8 (FUTURE)
## v3.2.9 (FUTURE)

---

## v3.2.8 (2022-08-08)

### Enhancements

Expand All @@ -11,13 +15,20 @@
* [#9881](https://github.com/netbox-community/netbox/issues/9881) - Increase granularity in utilization graph values
* [#9882](https://github.com/netbox-community/netbox/issues/9882) - Add manufacturer column to modules table
* [#9883](https://github.com/netbox-community/netbox/issues/9883) - Linkify location column in power panels table
* [#9906](https://github.com/netbox-community/netbox/issues/9906) - Include `color` attribute in front & rear port YAML import/export

### Bug Fixes

* [#9827](https://github.com/netbox-community/netbox/issues/9827) - Fix assignment of module bay position during bulk creation
* [#9871](https://github.com/netbox-community/netbox/issues/9871) - Fix utilization graph value alignments
* [#9884](https://github.com/netbox-community/netbox/issues/9884) - Prevent querying assigned VRF on prefix object init
* [#9885](https://github.com/netbox-community/netbox/issues/9885) - Fix child prefix counts when editing/deleting aggregates in bulk
* [#9891](https://github.com/netbox-community/netbox/issues/9891) - Ensure consistent ordering for tags during object serialization
* [#9919](https://github.com/netbox-community/netbox/issues/9919) - Fix potential XSS avenue via linked objects in tables
* [#9948](https://github.com/netbox-community/netbox/issues/9948) - Fix TypeError exception when requesting API tokens list as non-authenticated user
* [#9949](https://github.com/netbox-community/netbox/issues/9949) - Fix KeyError exception resulting from invalid API token provisioning request
* [#9950](https://github.com/netbox-community/netbox/issues/9950) - Prevent redirection to arbitrary URLs via `next` parameter on login URL
* [#9952](https://github.com/netbox-community/netbox/issues/9952) - Prevent InvalidMove when attempting to assign a nested child object as parent

---

Expand Down
4 changes: 2 additions & 2 deletions netbox/dcim/forms/object_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ def clean_module_type(self):
class Meta:
model = FrontPortTemplate
fields = [
'device_type', 'module_type', 'name', 'type', 'rear_port', 'rear_port_position', 'label', 'description',
'device_type', 'module_type', 'name', 'type', 'color', 'rear_port', 'rear_port_position', 'label', 'description',
]


Expand All @@ -168,7 +168,7 @@ class RearPortTemplateImportForm(ComponentTemplateImportForm):
class Meta:
model = RearPortTemplate
fields = [
'device_type', 'module_type', 'name', 'type', 'positions', 'label', 'description',
'device_type', 'module_type', 'name', 'type', 'color', 'positions', 'label', 'description',
]


Expand Down
2 changes: 2 additions & 0 deletions netbox/dcim/models/device_component_templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,7 @@ def to_yaml(self):
return {
'name': self.name,
'type': self.type,
'color': self.color,
'rear_port': self.rear_port.name,
'rear_port_position': self.rear_port_position,
'label': self.label,
Expand Down Expand Up @@ -527,6 +528,7 @@ def to_yaml(self):
return {
'name': self.name,
'type': self.type,
'color': self.color,
'positions': self.positions,
'label': self.label,
'description': self.description,
Expand Down
6 changes: 3 additions & 3 deletions netbox/dcim/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2721,6 +2721,7 @@ class DeviceBulkAddModuleBayView(generic.BulkComponentCreateView):
filterset = filtersets.DeviceFilterSet
table = tables.DeviceTable
default_return_url = 'dcim:device_list'
patterned_fields = ('name', 'label', 'position')


class DeviceBulkAddDeviceBayView(generic.BulkComponentCreateView):
Expand Down Expand Up @@ -3066,7 +3067,7 @@ def post(self, request, pk):
if membership_form.is_valid():

membership_form.save()
msg = 'Added member <a href="{}">{}</a>'.format(device.get_absolute_url(), escape(device))
msg = f'Added member <a href="{device.get_absolute_url()}">{escape(device)}</a>'
messages.success(request, mark_safe(msg))

if '_addanother' in request.POST:
Expand Down Expand Up @@ -3111,8 +3112,7 @@ def post(self, request, pk):
# Protect master device from being removed
virtual_chassis = VirtualChassis.objects.filter(master=device).first()
if virtual_chassis is not None:
msg = 'Unable to remove master device {} from the virtual chassis.'.format(escape(device))
messages.error(request, mark_safe(msg))
messages.error(request, f'Unable to remove master device {device} from the virtual chassis.')
return redirect(device.get_absolute_url())

if form.is_valid():
Expand Down
4 changes: 2 additions & 2 deletions netbox/netbox/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,9 @@ def clean(self):
super().clean()

# An MPTT model cannot be its own parent
if self.pk and self.parent_id == self.pk:
if self.pk and self.parent and self.parent in self.get_descendants(include_self=True):
raise ValidationError({
"parent": "Cannot assign self as parent."
"parent": f"Cannot assign self or child {self._meta.verbose_name} as parent."
})


Expand Down
11 changes: 6 additions & 5 deletions netbox/netbox/tables/columns.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from django.db.models import DateField, DateTimeField
from django.template import Context, Template
from django.urls import reverse
from django.utils.html import escape
from django.utils.formats import date_format
from django.utils.safestring import mark_safe
from django_tables2.columns import library
Expand Down Expand Up @@ -428,22 +429,22 @@ def __init__(self, customfield, *args, **kwargs):
@staticmethod
def _likify_item(item):
if hasattr(item, 'get_absolute_url'):
return f'<a href="{item.get_absolute_url()}">{item}</a>'
return item
return f'<a href="{item.get_absolute_url()}">{escape(item)}</a>'
return escape(item)

def render(self, value):
if self.customfield.type == CustomFieldTypeChoices.TYPE_BOOLEAN and value is True:
return mark_safe('<i class="mdi mdi-check-bold text-success"></i>')
if self.customfield.type == CustomFieldTypeChoices.TYPE_BOOLEAN and value is False:
return mark_safe('<i class="mdi mdi-close-thick text-danger"></i>')
if self.customfield.type == CustomFieldTypeChoices.TYPE_URL:
return mark_safe(f'<a href="{value}">{value}</a>')
return mark_safe(f'<a href="{escape(value)}">{escape(value)}</a>')
if self.customfield.type == CustomFieldTypeChoices.TYPE_MULTISELECT:
return ', '.join(v for v in value)
if self.customfield.type == CustomFieldTypeChoices.TYPE_MULTIOBJECT:
return mark_safe(', '.join([
return mark_safe(', '.join(
self._likify_item(obj) for obj in self.customfield.deserialize(value)
]))
))
if value is not None:
obj = self.customfield.deserialize(value)
return mark_safe(self._likify_item(obj))
Expand Down
17 changes: 9 additions & 8 deletions netbox/netbox/views/generic/bulk_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,7 @@ class BulkComponentCreateView(GetReturnURLMixin, BaseMultiObjectView):
model_form = None
filterset = None
table = None
patterned_fields = ('name', 'label')

def get_required_permission(self):
return f'dcim.add_{self.queryset.model._meta.model_name}'
Expand Down Expand Up @@ -805,16 +806,16 @@ def post(self, request):

for obj in data['pk']:

names = data['name_pattern']
labels = data['label_pattern'] if 'label_pattern' in data else None
for i, name in enumerate(names):
label = labels[i] if labels else None

pattern_count = len(data[f'{self.patterned_fields[0]}_pattern'])
for i in range(pattern_count):
component_data = {
self.parent_field: obj.pk,
'name': name,
'label': label
self.parent_field: obj.pk
}

for field_name in self.patterned_fields:
if data.get(f'{field_name}_pattern'):
component_data[field_name] = data[f'{field_name}_pattern'][i]

component_data.update(data)
component_form = self.model_form(component_data)
if component_form.is_valid():
Expand Down
6 changes: 3 additions & 3 deletions netbox/netbox/views/generic/object_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -389,10 +389,10 @@ def post(self, request, *args, **kwargs):
)
logger.info(f"{msg} {obj} (PK: {obj.pk})")
if hasattr(obj, 'get_absolute_url'):
msg = '{} <a href="{}">{}</a>'.format(msg, obj.get_absolute_url(), escape(obj))
msg = mark_safe(f'{msg} <a href="{obj.get_absolute_url()}">{escape(obj)}</a>')
else:
msg = '{} {}'.format(msg, escape(obj))
messages.success(request, mark_safe(msg))
msg = f'{msg} {obj}'
messages.success(request, msg)

if '_addanother' in request.POST:
redirect_url = request.path
Expand Down
12 changes: 7 additions & 5 deletions netbox/users/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ def get_queryset(self):
# Workaround for schema generation (drf_yasg)
if getattr(self, 'swagger_fake_view', False):
return queryset.none()
if not self.request.user.is_authenticated:
return queryset.none()
if self.request.user.is_superuser:
return queryset
return queryset.filter(user=self.request.user)
Expand All @@ -74,11 +76,11 @@ def post(self, request):
serializer.is_valid()

# Authenticate the user account based on the provided credentials
user = authenticate(
request=request,
username=serializer.data['username'],
password=serializer.data['password']
)
username = serializer.data.get('username')
password = serializer.data.get('password')
if not username or not password:
raise AuthenticationFailed("Username and password must be provided to provision a token.")
user = authenticate(request=request, username=username, password=password)
if user is None:
raise AuthenticationFailed("Invalid username/password")

Expand Down
3 changes: 2 additions & 1 deletion netbox/users/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from django.utils.decorators import method_decorator
from django.utils.http import url_has_allowed_host_and_scheme
from django.views.decorators.debug import sensitive_post_parameters
from django.views.generic import View
from social_core.backends.utils import load_backends
Expand Down Expand Up @@ -92,7 +93,7 @@ def redirect_to_next(self, request, logger):
data = request.POST if request.method == "POST" else request.GET
redirect_url = data.get('next', settings.LOGIN_REDIRECT_URL)

if redirect_url and redirect_url.startswith('/'):
if redirect_url and url_has_allowed_host_and_scheme(redirect_url, allowed_hosts=None):
logger.debug(f"Redirecting user to {redirect_url}")
else:
if redirect_url:
Expand Down
4 changes: 2 additions & 2 deletions netbox/utilities/templatetags/builtins/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ def placeholder(value):
"""
if value not in ('', None):
return value
placeholder = '<span class="text-muted">&mdash;</span>'
return mark_safe(placeholder)

return mark_safe('<span class="text-muted">&mdash;</span>')


@register.filter()
Expand Down
4 changes: 1 addition & 3 deletions netbox/utilities/templatetags/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,7 @@ def annotated_date(date_value):
long_ts = date(date_value, 'DATETIME_FORMAT')
short_ts = date(date_value, 'SHORT_DATETIME_FORMAT')

span = f'<span title="{long_ts}">{short_ts}</span>'

return mark_safe(span)
return mark_safe(f'<span title="{long_ts}">{short_ts}</span>')


@register.simple_tag
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ netaddr==0.8.0
Pillow==9.2.0
psycopg2-binary==2.9.3
PyYAML==6.0
sentry-sdk==1.9.0
sentry-sdk==1.9.2
social-auth-app-django==5.0.0
social-auth-core==4.3.0
svgwrite==1.4.3
Expand Down

0 comments on commit 1b88b36

Please sign in to comment.