Просмотр исходного кода

Merge branch 'develop' into feature

jeremystretch 4 лет назад
Родитель
Сommit
cede27b5fe

+ 1 - 1
.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

+ 1 - 1
.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

+ 14 - 2
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
 
 ---
 

+ 8 - 2
netbox/dcim/forms/object_create.py

@@ -118,12 +118,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

+ 12 - 3
netbox/dcim/tables/devices.py

@@ -49,6 +49,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 <tr/> DOM element.
@@ -508,7 +516,7 @@ class InterfaceTable(DeviceComponentTable, BaseInterfaceTable, PathEndpointTable
 
 class DeviceInterfaceTable(InterfaceTable):
     name = tables.TemplateColumn(
-        template_code='<i class="mdi mdi-{% if record.mgmt_only %}wrench{% elif record.is_lag %}drag-horizontal-variant'
+        template_code='<i class="mdi mdi-{% if record.mgmt_only %}wrench{% elif record.is_lag %}reorder-horizontal'
                       '{% elif record.is_virtual %}circle{% elif record.is_wireless %}wifi{% else %}ethernet'
                       '{% endif %}"></i> <a href="{{ record.get_absolute_url }}">{{ value }}</a>',
         order_by=Accessor('_name'),
@@ -544,7 +552,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,
         }
@@ -663,7 +671,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

+ 1 - 1
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:

+ 1 - 1
netbox/extras/models/customfields.py

@@ -33,7 +33,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,

+ 3 - 3
netbox/extras/models/models.py

@@ -35,7 +35,7 @@ __all__ = (
 )
 
 
-@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
@@ -177,7 +177,7 @@ class Webhook(ChangeLoggedModel):
             return json.dumps(context, cls=JSONEncoder)
 
 
-@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
@@ -230,7 +230,7 @@ class CustomLink(ChangeLoggedModel):
         return reverse('extras:customlink', args=[self.pk])
 
 
-@extras_features('webhooks')
+@extras_features('webhooks', 'export_templates')
 class ExportTemplate(ChangeLoggedModel):
     content_type = models.ForeignKey(
         to=ContentType,

+ 1 - 1
netbox/extras/models/tags.py

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

+ 1 - 1
netbox/ipam/tables/vlans.py

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

+ 1 - 1
netbox/templates/dcim/virtualchassis.html

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

+ 2 - 2
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')

+ 1 - 1
netbox/utilities/forms/fields.py

@@ -328,7 +328,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}'
 
 
 #

+ 7 - 3
netbox/utilities/templatetags/helpers.py

@@ -15,7 +15,6 @@ from django.utils.html import strip_tags
 from django.utils.safestring import mark_safe
 from markdown import markdown
 
-from netbox.config import get_config
 from utilities.forms import get_selected_values, TableConfigForm
 from utilities.utils import foreground_color
 
@@ -42,14 +41,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(get_config().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'])
 

+ 2 - 2
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
 social-auth-app-django==5.0.0
 social-auth-core==4.1.0