Merge branch 'netbox-community:develop' into script-reload

This commit is contained in:
kkthxbye 2021-11-12 17:07:11 +01:00 committed by GitHub
commit 9458521f3e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 59 additions and 27 deletions

View File

@ -14,7 +14,7 @@ body:
attributes: attributes:
label: NetBox version label: NetBox version
description: What version of NetBox are you currently running? description: What version of NetBox are you currently running?
placeholder: v3.0.9 placeholder: v3.0.10
validations: validations:
required: true required: true
- type: dropdown - type: dropdown

View File

@ -14,7 +14,7 @@ body:
attributes: attributes:
label: NetBox version label: NetBox version
description: What version of NetBox are you currently running? description: What version of NetBox are you currently running?
placeholder: v3.0.9 placeholder: v3.0.10
validations: validations:
required: true required: true
- type: dropdown - type: dropdown

View File

@ -1,20 +1,32 @@
# NetBox v3.0 # NetBox v3.0
## v3.0.10 (FUTURE) ## v3.0.11 (FUTURE)
---
## v3.0.10 (2021-11-12)
### Enhancements ### Enhancements
* [#7740](https://github.com/netbox-community/netbox/issues/7740) - Add mini-DIN 8 console port type * [#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 * [#7760](https://github.com/netbox-community/netbox/issues/7760) - Add `vid` filter field to VLANs list
* [#7767](https://github.com/netbox-community/netbox/issues/7767) - Add visual aids to interfaces table for type, enabled status
### Bug Fixes ### Bug Fixes
* [#7564](https://github.com/netbox-community/netbox/issues/7564) - Fix assignment of members to virtual chassis with initial position of zero
* [#7701](https://github.com/netbox-community/netbox/issues/7701) - Fix conflation of assigned IP status & role in interface tables * [#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 * [#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 * [#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 * [#7780](https://github.com/netbox-community/netbox/issues/7780) - Preserve multi-line values during CSV file import
* [#7783](https://github.com/netbox-community/netbox/issues/7783) - Fix indentation of locations under site view * [#7783](https://github.com/netbox-community/netbox/issues/7783) - Fix indentation of locations under site view
* [#7788](https://github.com/netbox-community/netbox/issues/7788) - Improve XSS mitigation in Markdown renderer
* [#7791](https://github.com/netbox-community/netbox/issues/7791) - Enable sorting device bays table by installed device status
* [#7802](https://github.com/netbox-community/netbox/issues/7802) - Differentiate ID and VID columns in VLANs table
* [#7808](https://github.com/netbox-community/netbox/issues/7808) - Fix reference values for content type under custom field import form
* [#7809](https://github.com/netbox-community/netbox/issues/7809) - Add missing export template support for various models
* [#7814](https://github.com/netbox-community/netbox/issues/7814) - Fix restriction of user & group objects in GraphQL API queries
--- ---

View File

@ -117,12 +117,18 @@ class VirtualChassisCreateForm(BootstrapMixin, CustomFieldModelForm):
'name', 'domain', 'region', 'site_group', 'site', 'rack', 'members', 'initial_position', 'tags', 'name', 'domain', 'region', 'site_group', 'site', 'rack', 'members', 'initial_position', 'tags',
] ]
def clean(self):
if self.cleaned_data['members'] and self.cleaned_data['initial_position'] is None:
raise forms.ValidationError({
'initial_position': "A position must be specified for the first VC member."
})
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
instance = super().save(*args, **kwargs) instance = super().save(*args, **kwargs)
# Assign VC members # Assign VC members
if instance.pk: if instance.pk and self.cleaned_data['members']:
initial_position = self.cleaned_data.get('initial_position') or 1 initial_position = self.cleaned_data.get('initial_position', 1)
for i, member in enumerate(self.cleaned_data['members'], start=initial_position): for i, member in enumerate(self.cleaned_data['members'], start=initial_position):
member.virtual_chassis = instance member.virtual_chassis = instance
member.vc_position = i member.vc_position = i

View File

@ -53,6 +53,14 @@ def get_cabletermination_row_class(record):
return '' return ''
def get_interface_row_class(record):
if not record.enabled:
return 'danger'
elif record.is_virtual:
return 'primary'
return get_cabletermination_row_class(record)
def get_interface_state_attribute(record): def get_interface_state_attribute(record):
""" """
Get interface enabled state as string to attach to <tr/> DOM element. Get interface enabled state as string to attach to <tr/> DOM element.
@ -501,8 +509,8 @@ class InterfaceTable(DeviceComponentTable, BaseInterfaceTable, PathEndpointTable
class DeviceInterfaceTable(InterfaceTable): class DeviceInterfaceTable(InterfaceTable):
name = tables.TemplateColumn( name = tables.TemplateColumn(
template_code='<i class="mdi mdi-{% if iface.mgmt_only %}wrench{% elif iface.is_lag %}drag-horizontal-variant' template_code='<i class="mdi mdi-{% if record.mgmt_only %}wrench{% elif record.is_lag %}reorder-horizontal'
'{% elif iface.is_virtual %}circle{% elif iface.is_wireless %}wifi{% else %}ethernet' '{% elif record.is_virtual %}circle{% elif record.is_wireless %}wifi{% else %}ethernet'
'{% endif %}"></i> <a href="{{ record.get_absolute_url }}">{{ value }}</a>', '{% endif %}"></i> <a href="{{ record.get_absolute_url }}">{{ value }}</a>',
order_by=Accessor('_name'), order_by=Accessor('_name'),
attrs={'td': {'class': 'text-nowrap'}} attrs={'td': {'class': 'text-nowrap'}}
@ -534,7 +542,7 @@ class DeviceInterfaceTable(InterfaceTable):
'cable', 'connection', 'actions', 'cable', 'connection', 'actions',
) )
row_attrs = { row_attrs = {
'class': get_cabletermination_row_class, 'class': get_interface_row_class,
'data-name': lambda record: record.name, 'data-name': lambda record: record.name,
'data-enabled': get_interface_state_attribute, 'data-enabled': get_interface_state_attribute,
} }
@ -653,7 +661,8 @@ class DeviceBayTable(DeviceComponentTable):
} }
) )
status = tables.TemplateColumn( status = tables.TemplateColumn(
template_code=DEVICEBAY_STATUS template_code=DEVICEBAY_STATUS,
order_by=Accessor('installed_device__status')
) )
installed_device = tables.Column( installed_device = tables.Column(
linkify=True linkify=True

View File

@ -70,7 +70,7 @@ class CustomLinkForm(BootstrapMixin, forms.ModelForm):
class ExportTemplateForm(BootstrapMixin, forms.ModelForm): class ExportTemplateForm(BootstrapMixin, forms.ModelForm):
content_type = ContentTypeChoiceField( content_type = ContentTypeChoiceField(
queryset=ContentType.objects.all(), queryset=ContentType.objects.all(),
limit_choices_to=FeatureQuery('custom_links') limit_choices_to=FeatureQuery('export_templates')
) )
class Meta: class Meta:

View File

@ -31,7 +31,7 @@ class CustomFieldManager(models.Manager.from_queryset(RestrictedQuerySet)):
return self.get_queryset().filter(content_types=content_type) return self.get_queryset().filter(content_types=content_type)
@extras_features('webhooks') @extras_features('webhooks', 'export_templates')
class CustomField(ChangeLoggedModel): class CustomField(ChangeLoggedModel):
content_types = models.ManyToManyField( content_types = models.ManyToManyField(
to=ContentType, to=ContentType,

View File

@ -9,7 +9,7 @@ from django.db import models
from django.http import HttpResponse from django.http import HttpResponse
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from django.utils.formats import date_format, time_format from django.utils.formats import date_format
from rest_framework.utils.encoders import JSONEncoder from rest_framework.utils.encoders import JSONEncoder
from extras.choices import * from extras.choices import *
@ -36,7 +36,7 @@ __all__ = (
# Webhooks # Webhooks
# #
@extras_features('webhooks') @extras_features('webhooks', 'export_templates')
class Webhook(ChangeLoggedModel): class Webhook(ChangeLoggedModel):
""" """
A Webhook defines a request that will be sent to a remote application when an object is created, updated, and/or A Webhook defines a request that will be sent to a remote application when an object is created, updated, and/or
@ -175,7 +175,7 @@ class Webhook(ChangeLoggedModel):
# Custom links # Custom links
# #
@extras_features('webhooks') @extras_features('webhooks', 'export_templates')
class CustomLink(ChangeLoggedModel): class CustomLink(ChangeLoggedModel):
""" """
A custom link to an external representation of a NetBox object. The link text and URL fields accept Jinja2 template A custom link to an external representation of a NetBox object. The link text and URL fields accept Jinja2 template
@ -234,7 +234,7 @@ class CustomLink(ChangeLoggedModel):
# Export templates # Export templates
# #
@extras_features('webhooks') @extras_features('webhooks', 'export_templates')
class ExportTemplate(ChangeLoggedModel): class ExportTemplate(ChangeLoggedModel):
content_type = models.ForeignKey( content_type = models.ForeignKey(
to=ContentType, to=ContentType,

View File

@ -14,7 +14,7 @@ from utilities.querysets import RestrictedQuerySet
# Tags # Tags
# #
@extras_features('webhooks') @extras_features('webhooks', 'export_templates')
class Tag(ChangeLoggedModel, TagBase): class Tag(ChangeLoggedModel, TagBase):
color = ColorField( color = ColorField(
default=ColorChoices.COLOR_GREY default=ColorChoices.COLOR_GREY

View File

@ -93,7 +93,7 @@ class VLANTable(BaseTable):
pk = ToggleColumn() pk = ToggleColumn()
vid = tables.TemplateColumn( vid = tables.TemplateColumn(
template_code=VLAN_LINK, template_code=VLAN_LINK,
verbose_name='ID' verbose_name='VID'
) )
site = tables.Column( site = tables.Column(
linkify=True linkify=True

View File

@ -17,7 +17,7 @@ from django.core.validators import URLValidator
# Environment setup # Environment setup
# #
VERSION = '3.0.10-dev' VERSION = '3.0.11-dev'
# Hostname # Hostname
HOSTNAME = platform.node() HOSTNAME = platform.node()

View File

@ -61,7 +61,7 @@
<a href="{{ vc_member.get_absolute_url }}">{{ vc_member }}</a> <a href="{{ vc_member.get_absolute_url }}">{{ vc_member }}</a>
</td> </td>
<td> <td>
{% badge vc_member.vc_position %} {% badge vc_member.vc_position show_empty=True %}
</td> </td>
<td> <td>
{% if object.master == vc_member %} {% if object.master == vc_member %}

View File

@ -19,7 +19,7 @@ class GroupType(DjangoObjectType):
@classmethod @classmethod
def get_queryset(cls, queryset, info): def get_queryset(cls, queryset, info):
return RestrictedQuerySet(model=Group) return RestrictedQuerySet(model=Group).restrict(info.context.user, 'view')
class UserType(DjangoObjectType): class UserType(DjangoObjectType):
@ -34,4 +34,4 @@ class UserType(DjangoObjectType):
@classmethod @classmethod
def get_queryset(cls, queryset, info): def get_queryset(cls, queryset, info):
return RestrictedQuerySet(model=User) return RestrictedQuerySet(model=User).restrict(info.context.user, 'view')

View File

@ -304,7 +304,7 @@ class CSVMultipleContentTypeField(forms.ModelMultipleChoiceField):
app_label, model = name.split('.') app_label, model = name.split('.')
ct_filter |= Q(app_label=app_label, model=model) ct_filter |= Q(app_label=app_label, model=model)
return list(ContentType.objects.filter(ct_filter).values_list('pk', flat=True)) return list(ContentType.objects.filter(ct_filter).values_list('pk', flat=True))
return super().prepare_value(value) return f'{value.app_label}.{value.model}'
# #

View File

@ -40,14 +40,19 @@ def render_markdown(value):
""" """
Render text as Markdown Render text as Markdown
""" """
schemes = '|'.join(settings.ALLOWED_URL_SCHEMES)
# Strip HTML tags # Strip HTML tags
value = strip_tags(value) value = strip_tags(value)
# Sanitize Markdown links # Sanitize Markdown links
schemes = '|'.join(settings.ALLOWED_URL_SCHEMES) pattern = fr'\[([^\]]+)\]\((?!({schemes})).*:(.+)\)'
pattern = fr'\[(.+)\]\((?!({schemes})).*:(.+)\)'
value = re.sub(pattern, '[\\1](\\3)', value, flags=re.IGNORECASE) value = re.sub(pattern, '[\\1](\\3)', value, flags=re.IGNORECASE)
# Sanitize Markdown reference links
pattern = fr'\[(.+)\]:\w?(?!({schemes})).*:(.+)'
value = re.sub(pattern, '[\\1]: \\3', value, flags=re.IGNORECASE)
# Render Markdown # Render Markdown
html = markdown(value, extensions=['fenced_code', 'tables']) html = markdown(value, extensions=['fenced_code', 'tables'])

View File

@ -15,13 +15,13 @@ djangorestframework==3.12.4
drf-yasg[validation]==1.20.0 drf-yasg[validation]==1.20.0
graphene_django==2.15.0 graphene_django==2.15.0
gunicorn==20.1.0 gunicorn==20.1.0
Jinja2==3.0.2 Jinja2==3.0.3
Markdown==3.3.4 Markdown==3.3.4
markdown-include==0.6.0 markdown-include==0.6.0
mkdocs-material==7.3.6 mkdocs-material==7.3.6
netaddr==0.8.0 netaddr==0.8.0
Pillow==8.4.0 Pillow==8.4.0
psycopg2-binary==2.9.1 psycopg2-binary==2.9.2
PyYAML==6.0 PyYAML==6.0
svgwrite==1.4.1 svgwrite==1.4.1
tablib==3.1.0 tablib==3.1.0