فهرست منبع

Merge branch 'develop' into feature

jeremystretch 4 سال پیش
والد
کامیت
a8a9e061a1

+ 15 - 0
docs/release-notes/version-2.11.md

@@ -1,5 +1,20 @@
 # NetBox v2.11
 
+## v2.11.8 (FUTURE)
+
+### Enhancements
+
+* [#6620](https://github.com/netbox-community/netbox/issues/6620) - Show assigned VMs count under device role view
+
+### Bug Fixes
+
+* [#6626](https://github.com/netbox-community/netbox/issues/6626) - Fix site field on VM search form; add site group
+* [#6637](https://github.com/netbox-community/netbox/issues/6637) - Fix group assignment in "available VLANs" link under VLAN group view
+* [#6640](https://github.com/netbox-community/netbox/issues/6640) - Disallow numeric values in custom text fields
+* [#6652](https://github.com/netbox-community/netbox/issues/6652) - Fix exception when adding components in bulk to multiple devices
+
+---
+
 ## v2.11.7 (2021-06-16)
 
 ### Enhancements

+ 2 - 0
netbox/dcim/views.py

@@ -1171,6 +1171,8 @@ class DeviceRoleView(generic.ObjectView):
 
         return {
             'devices_table': devices_table,
+            'device_count': Device.objects.filter(device_role=instance).count(),
+            'virtualmachine_count': VirtualMachine.objects.filter(role=instance).count(),
         }
 
 

+ 4 - 2
netbox/extras/models/customfields.py

@@ -280,8 +280,10 @@ class CustomField(BigIDModel):
         if value not in [None, '']:
 
             # Validate text field
-            if self.type == CustomFieldTypeChoices.TYPE_TEXT and self.validation_regex:
-                if not re.match(self.validation_regex, value):
+            if self.type == CustomFieldTypeChoices.TYPE_TEXT:
+                if type(value) is not str:
+                    raise ValidationError(f"Value must be a string.")
+                if self.validation_regex and not re.match(self.validation_regex, value):
                     raise ValidationError(f"Value must match regex '{self.validation_regex}'")
 
             # Validate integer

+ 1 - 1
netbox/ipam/tables.py

@@ -65,7 +65,7 @@ VLAN_LINK = """
 {% if record.pk %}
     <a href="{{ record.get_absolute_url }}">{{ record.vid }}</a>
 {% elif perms.ipam.add_vlan %}
-    <a href="{% url 'ipam:vlan_add' %}?vid={{ record.vid }}&group={{ vlan_group.pk }}{% if vlan_group.site %}&site={{ vlan_group.site.pk }}{% endif %}" class="btn btn-sm btn-success">{{ record.available }} VLAN{{ record.available|pluralize }} available</a>
+    <a href="{% url 'ipam:vlan_add' %}?vid={{ record.vid }}{% if record.vlan_group %}&group={{ record.vlan_group.pk }}{% endif %}" class="btn btn-sm btn-success">{{ record.available }} VLAN{{ record.available|pluralize }} available</a>
 {% else %}
     {{ record.available }} VLAN{{ record.available|pluralize }} available
 {% endif %}

+ 21 - 5
netbox/ipam/utils.py

@@ -68,24 +68,40 @@ def add_available_ipaddresses(prefix, ipaddress_list, is_pool=False):
     return output
 
 
-def add_available_vlans(vlan_group, vlans):
+def add_available_vlans(vlans, vlan_group=None):
     """
     Create fake records for all gaps between used VLANs
     """
     if not vlans:
-        return [{'vid': VLAN_VID_MIN, 'available': VLAN_VID_MAX - VLAN_VID_MIN + 1}]
+        return [{
+            'vid': VLAN_VID_MIN,
+            'vlan_group': vlan_group,
+            'available': VLAN_VID_MAX - VLAN_VID_MIN + 1
+        }]
 
     prev_vid = VLAN_VID_MAX
     new_vlans = []
     for vlan in vlans:
         if vlan.vid - prev_vid > 1:
-            new_vlans.append({'vid': prev_vid + 1, 'available': vlan.vid - prev_vid - 1})
+            new_vlans.append({
+                'vid': prev_vid + 1,
+                'vlan_group': vlan_group,
+                'available': vlan.vid - prev_vid - 1,
+            })
         prev_vid = vlan.vid
 
     if vlans[0].vid > VLAN_VID_MIN:
-        new_vlans.append({'vid': VLAN_VID_MIN, 'available': vlans[0].vid - VLAN_VID_MIN})
+        new_vlans.append({
+            'vid': VLAN_VID_MIN,
+            'vlan_group': vlan_group,
+            'available': vlans[0].vid - VLAN_VID_MIN,
+        })
     if prev_vid < VLAN_VID_MAX:
-        new_vlans.append({'vid': prev_vid + 1, 'available': VLAN_VID_MAX - prev_vid})
+        new_vlans.append({
+            'vid': prev_vid + 1,
+            'vlan_group': vlan_group,
+            'available': VLAN_VID_MAX - prev_vid,
+        })
 
     vlans = list(vlans) + new_vlans
     vlans.sort(key=lambda v: v.vid if type(v) == VLAN else v['vid'])

+ 1 - 2
netbox/ipam/views.py

@@ -145,7 +145,6 @@ class RIRListView(generic.ObjectListView):
     filterset = filtersets.RIRFilterSet
     filterset_form = forms.RIRFilterForm
     table = tables.RIRTable
-    template_name = 'ipam/rir_list.html'
 
 
 class RIRView(generic.ObjectView):
@@ -676,7 +675,7 @@ class VLANGroupView(generic.ObjectView):
             Prefetch('prefixes', queryset=Prefix.objects.restrict(request.user))
         ).order_by('vid')
         vlans_count = vlans.count()
-        vlans = add_available_vlans(instance, vlans)
+        vlans = add_available_vlans(vlans, vlan_group=instance)
 
         vlans_table = tables.VLANDetailTable(vlans)
         if request.user.has_perm('ipam.change_vlan') or request.user.has_perm('ipam.delete_vlan'):

+ 1 - 1
netbox/netbox/views/generic.py

@@ -1218,7 +1218,7 @@ class BulkComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin,
                                 component_form = self.model_form(component_data)
                                 if component_form.is_valid():
                                     instance = component_form.save()
-                                    logger.debug(f"Created {instance} on {instance.parent}")
+                                    logger.debug(f"Created {instance} on {instance.parent_object}")
                                     new_components.append(instance)
                                 else:
                                     for field, errors in component_form.errors.as_data().items():

+ 11 - 1
netbox/templates/dcim/devicerole.html

@@ -43,7 +43,17 @@
           <tr>
             <th scope="row">Devices</th>
             <td>
-              <a href="{% url 'dcim:device_list' %}?role_id={{ object.pk }}">{{ devices_table.rows|length }}</a>
+              <a href="{% url 'dcim:device_list' %}?role_id={{ object.pk }}">{{ device_count }}</a>
+            </td>
+          </tr>
+          <tr>
+            <td>Virtual Machines</td>
+            <td>
+              {% if object.vm_role %}
+                <a href="{% url 'virtualization:virtualmachine_list' %}?role_id={{ object.pk }}">{{ virtualmachine_count }}</a>
+              {% else %}
+                &mdash;
+              {% endif %}
             </td>
           </tr>
         </table>

+ 0 - 23
netbox/templates/ipam/rir_list.html

@@ -1,23 +0,0 @@
-{% extends 'generic/object_list.html' %}
-
-{% block extra_controls %}
-    {% if request.GET.family == '6' %}
-        <a href="{% url 'ipam:rir_list' %}" class="btn btn-sm btn-outline-secondary m-1">
-            <span class="mdi mdi-table" aria-hidden="true"></span>
-            IPv4 Stats
-        </a>
-    {% else %}
-        <a href="{% url 'ipam:rir_list' %}?family=6{% if request.GET %}&{{ request.GET.urlencode }}{% endif %}" class="btn btn-sm btn-outline-secondary m-1">
-            <span class="mdi mdi-table" aria-hidden="true"></span>
-            IPv6 Stats
-        </a>
-    {% endif %}
-{% endblock %}
-
-{% block sidebar %}
-    {% if request.GET.family == '6' %}
-        <div class="alert alert-info small">
-            <i class="mdi mdi-information-outline"></i> Numbers shown indicate /64 prefixes.
-        </div>
-    {% endif %}
-{% endblock %}

+ 10 - 4
netbox/virtualization/forms.py

@@ -531,8 +531,8 @@ class VirtualMachineBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldB
 class VirtualMachineFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
     model = VirtualMachine
     field_order = [
-        'cluster_group_id', 'cluster_type_id', 'cluster_id', 'status', 'role_id', 'region_id', 'site_id',
-        'tenant_group_id', 'tenant_id', 'platform_id', 'mac_address',
+        'q', 'cluster_group_id', 'cluster_type_id', 'cluster_id', 'status', 'role_id', 'region_id', 'site_group_id',
+        'site_id', 'tenant_group_id', 'tenant_id', 'platform_id', 'mac_address',
     ]
     field_groups = [
         ['status', 'role_id'],
@@ -564,14 +564,20 @@ class VirtualMachineFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFil
         required=False,
         label=_('Region')
     )
+    site_group_id = DynamicModelMultipleChoiceField(
+        queryset=SiteGroup.objects.all(),
+        required=False,
+        label=_('Site group')
+    )
     site_id = DynamicModelMultipleChoiceField(
         queryset=Site.objects.all(),
         required=False,
         null_option='None',
         query_params={
-            'region_id': '$region_id'
+            'region_id': '$region_id',
+            'group_id': '$site_group_id',
         },
-        label=_('Cluster')
+        label=_('Site')
     )
     role_id = DynamicModelMultipleChoiceField(
         queryset=DeviceRole.objects.all(),