mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Merge branch 'develop' into feature
This commit is contained in:
@ -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
|
||||
|
@ -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(),
|
||||
}
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 %}
|
||||
|
@ -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'])
|
||||
|
@ -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'):
|
||||
|
@ -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():
|
||||
|
@ -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 %}
|
||||
—
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
@ -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 %}
|
@ -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(),
|
||||
|
Reference in New Issue
Block a user