diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index fa773eb13..79fb0e334 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -14,7 +14,7 @@ body: attributes: label: NetBox version description: What version of NetBox are you currently running? - placeholder: v3.0.9 + placeholder: v3.0.10 validations: required: true - type: dropdown diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index a6fc342be..76944eecb 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -14,7 +14,7 @@ body: attributes: label: NetBox version description: What version of NetBox are you currently running? - placeholder: v3.0.9 + placeholder: v3.0.10 validations: required: true - type: dropdown diff --git a/docs/release-notes/version-3.0.md b/docs/release-notes/version-3.0.md index 7a3005421..4e7ead79c 100644 --- a/docs/release-notes/version-3.0.md +++ b/docs/release-notes/version-3.0.md @@ -1,20 +1,32 @@ # NetBox v3.0 -## v3.0.10 (FUTURE) +## v3.0.11 (FUTURE) + +--- + +## v3.0.10 (2021-11-12) ### 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 +* [#7767](https://github.com/netbox-community/netbox/issues/7767) - Add visual aids to interfaces table for type, enabled status ### 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 * [#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 * [#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 +* [#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 --- diff --git a/netbox/dcim/forms/object_create.py b/netbox/dcim/forms/object_create.py index 7577ad355..ea797335d 100644 --- a/netbox/dcim/forms/object_create.py +++ b/netbox/dcim/forms/object_create.py @@ -117,12 +117,18 @@ class VirtualChassisCreateForm(BootstrapMixin, CustomFieldModelForm): '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): instance = super().save(*args, **kwargs) # Assign VC members - if instance.pk: - initial_position = self.cleaned_data.get('initial_position') or 1 + if instance.pk and self.cleaned_data['members']: + initial_position = self.cleaned_data.get('initial_position', 1) for i, member in enumerate(self.cleaned_data['members'], start=initial_position): member.virtual_chassis = instance member.vc_position = i diff --git a/netbox/dcim/tables/devices.py b/netbox/dcim/tables/devices.py index 675f7d777..99cd5f693 100644 --- a/netbox/dcim/tables/devices.py +++ b/netbox/dcim/tables/devices.py @@ -53,6 +53,14 @@ def get_cabletermination_row_class(record): 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): """ Get interface enabled state as string to attach to DOM element. @@ -501,8 +509,8 @@ class InterfaceTable(DeviceComponentTable, BaseInterfaceTable, PathEndpointTable class DeviceInterfaceTable(InterfaceTable): name = tables.TemplateColumn( - template_code=' {{ value }}', order_by=Accessor('_name'), attrs={'td': {'class': 'text-nowrap'}} @@ -534,7 +542,7 @@ class DeviceInterfaceTable(InterfaceTable): 'cable', 'connection', 'actions', ) row_attrs = { - 'class': get_cabletermination_row_class, + 'class': get_interface_row_class, 'data-name': lambda record: record.name, 'data-enabled': get_interface_state_attribute, } @@ -653,7 +661,8 @@ class DeviceBayTable(DeviceComponentTable): } ) status = tables.TemplateColumn( - template_code=DEVICEBAY_STATUS + template_code=DEVICEBAY_STATUS, + order_by=Accessor('installed_device__status') ) installed_device = tables.Column( linkify=True diff --git a/netbox/extras/forms/models.py b/netbox/extras/forms/models.py index 7e462e62b..61c341334 100644 --- a/netbox/extras/forms/models.py +++ b/netbox/extras/forms/models.py @@ -70,7 +70,7 @@ class CustomLinkForm(BootstrapMixin, forms.ModelForm): class ExportTemplateForm(BootstrapMixin, forms.ModelForm): content_type = ContentTypeChoiceField( queryset=ContentType.objects.all(), - limit_choices_to=FeatureQuery('custom_links') + limit_choices_to=FeatureQuery('export_templates') ) class Meta: diff --git a/netbox/extras/models/customfields.py b/netbox/extras/models/customfields.py index c74bb0cde..245079863 100644 --- a/netbox/extras/models/customfields.py +++ b/netbox/extras/models/customfields.py @@ -31,7 +31,7 @@ class CustomFieldManager(models.Manager.from_queryset(RestrictedQuerySet)): return self.get_queryset().filter(content_types=content_type) -@extras_features('webhooks') +@extras_features('webhooks', 'export_templates') class CustomField(ChangeLoggedModel): content_types = models.ManyToManyField( to=ContentType, diff --git a/netbox/extras/models/models.py b/netbox/extras/models/models.py index 2c56f2f0f..1b20cc79c 100644 --- a/netbox/extras/models/models.py +++ b/netbox/extras/models/models.py @@ -9,7 +9,7 @@ from django.db import models from django.http import HttpResponse from django.urls import reverse 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 extras.choices import * @@ -36,7 +36,7 @@ __all__ = ( # Webhooks # -@extras_features('webhooks') +@extras_features('webhooks', 'export_templates') class Webhook(ChangeLoggedModel): """ 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 # -@extras_features('webhooks') +@extras_features('webhooks', 'export_templates') class CustomLink(ChangeLoggedModel): """ 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 # -@extras_features('webhooks') +@extras_features('webhooks', 'export_templates') class ExportTemplate(ChangeLoggedModel): content_type = models.ForeignKey( to=ContentType, diff --git a/netbox/extras/models/tags.py b/netbox/extras/models/tags.py index afeeee53d..da2016875 100644 --- a/netbox/extras/models/tags.py +++ b/netbox/extras/models/tags.py @@ -14,7 +14,7 @@ from utilities.querysets import RestrictedQuerySet # Tags # -@extras_features('webhooks') +@extras_features('webhooks', 'export_templates') class Tag(ChangeLoggedModel, TagBase): color = ColorField( default=ColorChoices.COLOR_GREY diff --git a/netbox/ipam/tables/vlans.py b/netbox/ipam/tables/vlans.py index 84b250f87..ffa6c5f40 100644 --- a/netbox/ipam/tables/vlans.py +++ b/netbox/ipam/tables/vlans.py @@ -93,7 +93,7 @@ class VLANTable(BaseTable): pk = ToggleColumn() vid = tables.TemplateColumn( template_code=VLAN_LINK, - verbose_name='ID' + verbose_name='VID' ) site = tables.Column( linkify=True diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index b0c996141..b7886b23c 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -17,7 +17,7 @@ from django.core.validators import URLValidator # Environment setup # -VERSION = '3.0.10-dev' +VERSION = '3.0.11-dev' # Hostname HOSTNAME = platform.node() diff --git a/netbox/templates/dcim/virtualchassis.html b/netbox/templates/dcim/virtualchassis.html index 12088e892..60c20a5dc 100644 --- a/netbox/templates/dcim/virtualchassis.html +++ b/netbox/templates/dcim/virtualchassis.html @@ -61,7 +61,7 @@ {{ vc_member }} - {% badge vc_member.vc_position %} + {% badge vc_member.vc_position show_empty=True %} {% if object.master == vc_member %} diff --git a/netbox/users/graphql/types.py b/netbox/users/graphql/types.py index 3315744b9..d948686c6 100644 --- a/netbox/users/graphql/types.py +++ b/netbox/users/graphql/types.py @@ -19,7 +19,7 @@ class GroupType(DjangoObjectType): @classmethod def get_queryset(cls, queryset, info): - return RestrictedQuerySet(model=Group) + return RestrictedQuerySet(model=Group).restrict(info.context.user, 'view') class UserType(DjangoObjectType): @@ -34,4 +34,4 @@ class UserType(DjangoObjectType): @classmethod def get_queryset(cls, queryset, info): - return RestrictedQuerySet(model=User) + return RestrictedQuerySet(model=User).restrict(info.context.user, 'view') diff --git a/netbox/utilities/forms/fields.py b/netbox/utilities/forms/fields.py index d9f1719ec..bca293b0b 100644 --- a/netbox/utilities/forms/fields.py +++ b/netbox/utilities/forms/fields.py @@ -304,7 +304,7 @@ class CSVMultipleContentTypeField(forms.ModelMultipleChoiceField): app_label, model = name.split('.') ct_filter |= Q(app_label=app_label, model=model) return list(ContentType.objects.filter(ct_filter).values_list('pk', flat=True)) - return super().prepare_value(value) + return f'{value.app_label}.{value.model}' # diff --git a/netbox/utilities/templatetags/helpers.py b/netbox/utilities/templatetags/helpers.py index 5b5534321..b047bb698 100644 --- a/netbox/utilities/templatetags/helpers.py +++ b/netbox/utilities/templatetags/helpers.py @@ -40,14 +40,19 @@ def render_markdown(value): """ Render text as Markdown """ + schemes = '|'.join(settings.ALLOWED_URL_SCHEMES) + # Strip HTML tags value = strip_tags(value) # 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) + # Sanitize Markdown reference links + pattern = fr'\[(.+)\]:\w?(?!({schemes})).*:(.+)' + value = re.sub(pattern, '[\\1]: \\3', value, flags=re.IGNORECASE) + # Render Markdown html = markdown(value, extensions=['fenced_code', 'tables']) diff --git a/requirements.txt b/requirements.txt index c537a39c3..84ad0c398 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,13 +15,13 @@ djangorestframework==3.12.4 drf-yasg[validation]==1.20.0 graphene_django==2.15.0 gunicorn==20.1.0 -Jinja2==3.0.2 +Jinja2==3.0.3 Markdown==3.3.4 markdown-include==0.6.0 mkdocs-material==7.3.6 netaddr==0.8.0 Pillow==8.4.0 -psycopg2-binary==2.9.1 +psycopg2-binary==2.9.2 PyYAML==6.0 svgwrite==1.4.1 tablib==3.1.0