mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-25 01:48:38 -06:00
commit
d8ae65a762
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: 🐛 Bug Report
|
name: 🐛 Bug Report
|
||||||
about: Report a reproducible bug in the current release of NetBox
|
description: Report a reproducible bug in the current release of NetBox
|
||||||
labels: ["type: bug"]
|
labels: ["type: bug"]
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: 📖 Documentation Change
|
name: 📖 Documentation Change
|
||||||
about: Suggest an addition or modification to the NetBox documentation
|
description: Suggest an addition or modification to the NetBox documentation
|
||||||
labels: ["type: documentation"]
|
labels: ["type: documentation"]
|
||||||
body:
|
body:
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
|
2
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
2
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: ✨ Feature Request
|
name: ✨ Feature Request
|
||||||
about: Propose a new NetBox feature or enhancement
|
description: Propose a new NetBox feature or enhancement
|
||||||
labels: ["type: feature"]
|
labels: ["type: feature"]
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
|
2
.github/ISSUE_TEMPLATE/housekeeping.yaml
vendored
2
.github/ISSUE_TEMPLATE/housekeeping.yaml
vendored
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: 🏡 Housekeeping
|
name: 🏡 Housekeeping
|
||||||
about: A change pertaining to the codebase itself (developers only)
|
description: A change pertaining to the codebase itself (developers only)
|
||||||
labels: ["type: housekeeping"]
|
labels: ["type: housekeeping"]
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
|
@ -1,5 +1,24 @@
|
|||||||
# NetBox v2.11
|
# NetBox v2.11
|
||||||
|
|
||||||
|
## v2.11.1 (2021-04-21)
|
||||||
|
|
||||||
|
### Enhancements
|
||||||
|
|
||||||
|
* [#6161](https://github.com/netbox-community/netbox/issues/6161) - Enable ordering of device component tables
|
||||||
|
* [#6179](https://github.com/netbox-community/netbox/issues/6179) - Enable natural ordering for virtual machines
|
||||||
|
* [#6189](https://github.com/netbox-community/netbox/issues/6189) - Add ability to search for locations by name or description
|
||||||
|
* [#6190](https://github.com/netbox-community/netbox/issues/6190) - Allow filtering devices with no location assigned
|
||||||
|
* [#6210](https://github.com/netbox-community/netbox/issues/6210) - Include child locations on location view
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* [#6184](https://github.com/netbox-community/netbox/issues/6184) - Fix parent object table column in prefix IP addresses list
|
||||||
|
* [#6188](https://github.com/netbox-community/netbox/issues/6188) - Support custom field filtering for regions, site groups, and locations
|
||||||
|
* [#6196](https://github.com/netbox-community/netbox/issues/6196) - Fix object list display for users with read-only permissions
|
||||||
|
* [#6215](https://github.com/netbox-community/netbox/issues/6215) - Restore tenancy section in virtual machine form
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## v2.11.0 (2021-04-16)
|
## v2.11.0 (2021-04-16)
|
||||||
|
|
||||||
**Note:** NetBox v2.11 is the last major release that will support Python 3.6. Beginning with NetBox v2.12, Python 3.7 or later will be required.
|
**Note:** NetBox v2.11 is the last major release that will support Python 3.6. Beginning with NetBox v2.12, Python 3.7 or later will be required.
|
||||||
|
@ -209,6 +209,14 @@ class LocationFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
|
|||||||
model = Location
|
model = Location
|
||||||
fields = ['id', 'name', 'slug', 'description']
|
fields = ['id', 'name', 'slug', 'description']
|
||||||
|
|
||||||
|
def search(self, queryset, name, value):
|
||||||
|
if not value.strip():
|
||||||
|
return queryset
|
||||||
|
return queryset.filter(
|
||||||
|
Q(name__icontains=value) |
|
||||||
|
Q(description__icontains=value)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class RackRoleFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
|
class RackRoleFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
|
||||||
|
|
||||||
|
@ -230,7 +230,7 @@ class RegionBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
|||||||
nullable_fields = ['parent', 'description']
|
nullable_fields = ['parent', 'description']
|
||||||
|
|
||||||
|
|
||||||
class RegionFilterForm(BootstrapMixin, forms.Form):
|
class RegionFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||||
model = Site
|
model = Site
|
||||||
q = forms.CharField(
|
q = forms.CharField(
|
||||||
required=False,
|
required=False,
|
||||||
@ -287,8 +287,8 @@ class SiteGroupBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
|||||||
nullable_fields = ['parent', 'description']
|
nullable_fields = ['parent', 'description']
|
||||||
|
|
||||||
|
|
||||||
class SiteGroupFilterForm(BootstrapMixin, forms.Form):
|
class SiteGroupFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||||
model = Site
|
model = SiteGroup
|
||||||
q = forms.CharField(
|
q = forms.CharField(
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Search')
|
label=_('Search')
|
||||||
@ -557,7 +557,12 @@ class LocationBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
|||||||
nullable_fields = ['parent', 'description']
|
nullable_fields = ['parent', 'description']
|
||||||
|
|
||||||
|
|
||||||
class LocationFilterForm(BootstrapMixin, forms.Form):
|
class LocationFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||||
|
model = Location
|
||||||
|
q = forms.CharField(
|
||||||
|
required=False,
|
||||||
|
label=_('Search')
|
||||||
|
)
|
||||||
region_id = DynamicModelMultipleChoiceField(
|
region_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Region.objects.all(),
|
queryset=Region.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
@ -2424,10 +2429,11 @@ class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilt
|
|||||||
location_id = DynamicModelMultipleChoiceField(
|
location_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Location.objects.all(),
|
queryset=Location.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Location'),
|
null_option='None',
|
||||||
query_params={
|
query_params={
|
||||||
'site_id': '$site_id'
|
'site_id': '$site_id'
|
||||||
}
|
},
|
||||||
|
label=_('Location')
|
||||||
)
|
)
|
||||||
rack_id = DynamicModelMultipleChoiceField(
|
rack_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Rack.objects.all(),
|
queryset=Rack.objects.all(),
|
||||||
|
@ -291,6 +291,7 @@ class ConsolePortTable(DeviceComponentTable, PathEndpointTable):
|
|||||||
class DeviceConsolePortTable(ConsolePortTable):
|
class DeviceConsolePortTable(ConsolePortTable):
|
||||||
name = tables.TemplateColumn(
|
name = tables.TemplateColumn(
|
||||||
template_code='<i class="mdi mdi-console"></i> <a href="{{ record.get_absolute_url }}">{{ value }}</a>',
|
template_code='<i class="mdi mdi-console"></i> <a href="{{ record.get_absolute_url }}">{{ value }}</a>',
|
||||||
|
order_by=Accessor('_name'),
|
||||||
attrs={'td': {'class': 'text-nowrap'}}
|
attrs={'td': {'class': 'text-nowrap'}}
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(
|
actions = ButtonsColumn(
|
||||||
@ -335,6 +336,7 @@ class DeviceConsoleServerPortTable(ConsoleServerPortTable):
|
|||||||
name = tables.TemplateColumn(
|
name = tables.TemplateColumn(
|
||||||
template_code='<i class="mdi mdi-console-network-outline"></i> '
|
template_code='<i class="mdi mdi-console-network-outline"></i> '
|
||||||
'<a href="{{ record.get_absolute_url }}">{{ value }}</a>',
|
'<a href="{{ record.get_absolute_url }}">{{ value }}</a>',
|
||||||
|
order_by=Accessor('_name'),
|
||||||
attrs={'td': {'class': 'text-nowrap'}}
|
attrs={'td': {'class': 'text-nowrap'}}
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(
|
actions = ButtonsColumn(
|
||||||
@ -379,6 +381,7 @@ class DevicePowerPortTable(PowerPortTable):
|
|||||||
name = tables.TemplateColumn(
|
name = tables.TemplateColumn(
|
||||||
template_code='<i class="mdi mdi-power-plug-outline"></i> <a href="{{ record.get_absolute_url }}">'
|
template_code='<i class="mdi mdi-power-plug-outline"></i> <a href="{{ record.get_absolute_url }}">'
|
||||||
'{{ value }}</a>',
|
'{{ value }}</a>',
|
||||||
|
order_by=Accessor('_name'),
|
||||||
attrs={'td': {'class': 'text-nowrap'}}
|
attrs={'td': {'class': 'text-nowrap'}}
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(
|
actions = ButtonsColumn(
|
||||||
@ -428,6 +431,7 @@ class PowerOutletTable(DeviceComponentTable, PathEndpointTable):
|
|||||||
class DevicePowerOutletTable(PowerOutletTable):
|
class DevicePowerOutletTable(PowerOutletTable):
|
||||||
name = tables.TemplateColumn(
|
name = tables.TemplateColumn(
|
||||||
template_code='<i class="mdi mdi-power-socket"></i> <a href="{{ record.get_absolute_url }}">{{ value }}</a>',
|
template_code='<i class="mdi mdi-power-socket"></i> <a href="{{ record.get_absolute_url }}">{{ value }}</a>',
|
||||||
|
order_by=Accessor('_name'),
|
||||||
attrs={'td': {'class': 'text-nowrap'}}
|
attrs={'td': {'class': 'text-nowrap'}}
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(
|
actions = ButtonsColumn(
|
||||||
@ -492,6 +496,7 @@ class DeviceInterfaceTable(InterfaceTable):
|
|||||||
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 iface.mgmt_only %}wrench{% elif iface.is_lag %}drag-horizontal-variant'
|
||||||
'{% elif iface.is_virtual %}circle{% elif iface.is_wireless %}wifi{% else %}ethernet'
|
'{% elif iface.is_virtual %}circle{% elif iface.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'),
|
||||||
attrs={'td': {'class': 'text-nowrap'}}
|
attrs={'td': {'class': 'text-nowrap'}}
|
||||||
)
|
)
|
||||||
parent = tables.Column(
|
parent = tables.Column(
|
||||||
@ -555,6 +560,7 @@ class DeviceFrontPortTable(FrontPortTable):
|
|||||||
name = tables.TemplateColumn(
|
name = tables.TemplateColumn(
|
||||||
template_code='<i class="mdi mdi-square-rounded{% if not record.cable %}-outline{% endif %}"></i> '
|
template_code='<i class="mdi mdi-square-rounded{% if not record.cable %}-outline{% endif %}"></i> '
|
||||||
'<a href="{{ record.get_absolute_url }}">{{ value }}</a>',
|
'<a href="{{ record.get_absolute_url }}">{{ value }}</a>',
|
||||||
|
order_by=Accessor('_name'),
|
||||||
attrs={'td': {'class': 'text-nowrap'}}
|
attrs={'td': {'class': 'text-nowrap'}}
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(
|
actions = ButtonsColumn(
|
||||||
@ -602,6 +608,7 @@ class DeviceRearPortTable(RearPortTable):
|
|||||||
name = tables.TemplateColumn(
|
name = tables.TemplateColumn(
|
||||||
template_code='<i class="mdi mdi-square-rounded{% if not record.cable %}-outline{% endif %}"></i> '
|
template_code='<i class="mdi mdi-square-rounded{% if not record.cable %}-outline{% endif %}"></i> '
|
||||||
'<a href="{{ record.get_absolute_url }}">{{ value }}</a>',
|
'<a href="{{ record.get_absolute_url }}">{{ value }}</a>',
|
||||||
|
order_by=Accessor('_name'),
|
||||||
attrs={'td': {'class': 'text-nowrap'}}
|
attrs={'td': {'class': 'text-nowrap'}}
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(
|
actions = ButtonsColumn(
|
||||||
@ -651,6 +658,7 @@ class DeviceDeviceBayTable(DeviceBayTable):
|
|||||||
name = tables.TemplateColumn(
|
name = tables.TemplateColumn(
|
||||||
template_code='<i class="mdi mdi-circle{% if record.installed_device %}slice-8{% else %}outline{% endif %}'
|
template_code='<i class="mdi mdi-circle{% if record.installed_device %}slice-8{% else %}outline{% endif %}'
|
||||||
'"></i> <a href="{{ record.get_absolute_url }}">{{ value }}</a>',
|
'"></i> <a href="{{ record.get_absolute_url }}">{{ value }}</a>',
|
||||||
|
order_by=Accessor('_name'),
|
||||||
attrs={'td': {'class': 'text-nowrap'}}
|
attrs={'td': {'class': 'text-nowrap'}}
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(
|
actions = ButtonsColumn(
|
||||||
@ -698,6 +706,7 @@ class DeviceInventoryItemTable(InventoryItemTable):
|
|||||||
name = tables.TemplateColumn(
|
name = tables.TemplateColumn(
|
||||||
template_code='<a href="{{ record.get_absolute_url }}" style="padding-left: {{ record.level }}0px">'
|
template_code='<a href="{{ record.get_absolute_url }}" style="padding-left: {{ record.level }}0px">'
|
||||||
'{{ value }}</a>',
|
'{{ value }}</a>',
|
||||||
|
order_by=Accessor('_name'),
|
||||||
attrs={'td': {'class': 'text-nowrap'}}
|
attrs={'td': {'class': 'text-nowrap'}}
|
||||||
)
|
)
|
||||||
actions = ButtonsColumn(
|
actions = ButtonsColumn(
|
||||||
|
@ -364,16 +364,30 @@ class LocationView(generic.ObjectView):
|
|||||||
queryset = Location.objects.all()
|
queryset = Location.objects.all()
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
devices = Device.objects.restrict(request.user, 'view').filter(
|
location_ids = instance.get_descendants(include_self=True).values_list('pk', flat=True)
|
||||||
location=instance
|
rack_count = Rack.objects.filter(location__in=location_ids).count()
|
||||||
)
|
device_count = Device.objects.filter(location__in=location_ids).count()
|
||||||
|
|
||||||
devices_table = tables.DeviceTable(devices)
|
child_locations = Location.objects.add_related_count(
|
||||||
devices_table.columns.hide('location')
|
Location.objects.add_related_count(
|
||||||
paginate_table(devices_table, request)
|
Location.objects.all(),
|
||||||
|
Device,
|
||||||
|
'location',
|
||||||
|
'device_count',
|
||||||
|
cumulative=True
|
||||||
|
),
|
||||||
|
Rack,
|
||||||
|
'location',
|
||||||
|
'rack_count',
|
||||||
|
cumulative=True
|
||||||
|
).filter(pk__in=location_ids).exclude(pk=instance.pk)
|
||||||
|
child_locations_table = tables.LocationTable(child_locations)
|
||||||
|
paginate_table(child_locations_table, request)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'devices_table': devices_table,
|
'rack_count': rack_count,
|
||||||
|
'device_count': device_count,
|
||||||
|
'child_locations_table': child_locations_table,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1305,8 +1319,7 @@ class DeviceConsolePortsView(generic.ObjectView):
|
|||||||
)
|
)
|
||||||
consoleport_table = tables.DeviceConsolePortTable(
|
consoleport_table = tables.DeviceConsolePortTable(
|
||||||
data=consoleports,
|
data=consoleports,
|
||||||
user=request.user,
|
user=request.user
|
||||||
orderable=False
|
|
||||||
)
|
)
|
||||||
if request.user.has_perm('dcim.change_consoleport') or request.user.has_perm('dcim.delete_consoleport'):
|
if request.user.has_perm('dcim.change_consoleport') or request.user.has_perm('dcim.delete_consoleport'):
|
||||||
consoleport_table.columns.show('pk')
|
consoleport_table.columns.show('pk')
|
||||||
@ -1330,8 +1343,7 @@ class DeviceConsoleServerPortsView(generic.ObjectView):
|
|||||||
)
|
)
|
||||||
consoleserverport_table = tables.DeviceConsoleServerPortTable(
|
consoleserverport_table = tables.DeviceConsoleServerPortTable(
|
||||||
data=consoleserverports,
|
data=consoleserverports,
|
||||||
user=request.user,
|
user=request.user
|
||||||
orderable=False
|
|
||||||
)
|
)
|
||||||
if request.user.has_perm('dcim.change_consoleserverport') or \
|
if request.user.has_perm('dcim.change_consoleserverport') or \
|
||||||
request.user.has_perm('dcim.delete_consoleserverport'):
|
request.user.has_perm('dcim.delete_consoleserverport'):
|
||||||
@ -1354,8 +1366,7 @@ class DevicePowerPortsView(generic.ObjectView):
|
|||||||
)
|
)
|
||||||
powerport_table = tables.DevicePowerPortTable(
|
powerport_table = tables.DevicePowerPortTable(
|
||||||
data=powerports,
|
data=powerports,
|
||||||
user=request.user,
|
user=request.user
|
||||||
orderable=False
|
|
||||||
)
|
)
|
||||||
if request.user.has_perm('dcim.change_powerport') or request.user.has_perm('dcim.delete_powerport'):
|
if request.user.has_perm('dcim.change_powerport') or request.user.has_perm('dcim.delete_powerport'):
|
||||||
powerport_table.columns.show('pk')
|
powerport_table.columns.show('pk')
|
||||||
@ -1377,8 +1388,7 @@ class DevicePowerOutletsView(generic.ObjectView):
|
|||||||
)
|
)
|
||||||
poweroutlet_table = tables.DevicePowerOutletTable(
|
poweroutlet_table = tables.DevicePowerOutletTable(
|
||||||
data=poweroutlets,
|
data=poweroutlets,
|
||||||
user=request.user,
|
user=request.user
|
||||||
orderable=False
|
|
||||||
)
|
)
|
||||||
if request.user.has_perm('dcim.change_poweroutlet') or request.user.has_perm('dcim.delete_poweroutlet'):
|
if request.user.has_perm('dcim.change_poweroutlet') or request.user.has_perm('dcim.delete_poweroutlet'):
|
||||||
poweroutlet_table.columns.show('pk')
|
poweroutlet_table.columns.show('pk')
|
||||||
@ -1402,8 +1412,7 @@ class DeviceInterfacesView(generic.ObjectView):
|
|||||||
)
|
)
|
||||||
interface_table = tables.DeviceInterfaceTable(
|
interface_table = tables.DeviceInterfaceTable(
|
||||||
data=interfaces,
|
data=interfaces,
|
||||||
user=request.user,
|
user=request.user
|
||||||
orderable=False
|
|
||||||
)
|
)
|
||||||
if request.user.has_perm('dcim.change_interface') or request.user.has_perm('dcim.delete_interface'):
|
if request.user.has_perm('dcim.change_interface') or request.user.has_perm('dcim.delete_interface'):
|
||||||
interface_table.columns.show('pk')
|
interface_table.columns.show('pk')
|
||||||
@ -1425,8 +1434,7 @@ class DeviceFrontPortsView(generic.ObjectView):
|
|||||||
)
|
)
|
||||||
frontport_table = tables.DeviceFrontPortTable(
|
frontport_table = tables.DeviceFrontPortTable(
|
||||||
data=frontports,
|
data=frontports,
|
||||||
user=request.user,
|
user=request.user
|
||||||
orderable=False
|
|
||||||
)
|
)
|
||||||
if request.user.has_perm('dcim.change_frontport') or request.user.has_perm('dcim.delete_frontport'):
|
if request.user.has_perm('dcim.change_frontport') or request.user.has_perm('dcim.delete_frontport'):
|
||||||
frontport_table.columns.show('pk')
|
frontport_table.columns.show('pk')
|
||||||
@ -1446,8 +1454,7 @@ class DeviceRearPortsView(generic.ObjectView):
|
|||||||
rearports = RearPort.objects.restrict(request.user, 'view').filter(device=instance).prefetch_related('cable')
|
rearports = RearPort.objects.restrict(request.user, 'view').filter(device=instance).prefetch_related('cable')
|
||||||
rearport_table = tables.DeviceRearPortTable(
|
rearport_table = tables.DeviceRearPortTable(
|
||||||
data=rearports,
|
data=rearports,
|
||||||
user=request.user,
|
user=request.user
|
||||||
orderable=False
|
|
||||||
)
|
)
|
||||||
if request.user.has_perm('dcim.change_rearport') or request.user.has_perm('dcim.delete_rearport'):
|
if request.user.has_perm('dcim.change_rearport') or request.user.has_perm('dcim.delete_rearport'):
|
||||||
rearport_table.columns.show('pk')
|
rearport_table.columns.show('pk')
|
||||||
@ -1469,8 +1476,7 @@ class DeviceDeviceBaysView(generic.ObjectView):
|
|||||||
)
|
)
|
||||||
devicebay_table = tables.DeviceDeviceBayTable(
|
devicebay_table = tables.DeviceDeviceBayTable(
|
||||||
data=devicebays,
|
data=devicebays,
|
||||||
user=request.user,
|
user=request.user
|
||||||
orderable=False
|
|
||||||
)
|
)
|
||||||
if request.user.has_perm('dcim.change_devicebay') or request.user.has_perm('dcim.delete_devicebay'):
|
if request.user.has_perm('dcim.change_devicebay') or request.user.has_perm('dcim.delete_devicebay'):
|
||||||
devicebay_table.columns.show('pk')
|
devicebay_table.columns.show('pk')
|
||||||
@ -1492,8 +1498,7 @@ class DeviceInventoryView(generic.ObjectView):
|
|||||||
).prefetch_related('manufacturer')
|
).prefetch_related('manufacturer')
|
||||||
inventoryitem_table = tables.DeviceInventoryItemTable(
|
inventoryitem_table = tables.DeviceInventoryItemTable(
|
||||||
data=inventoryitems,
|
data=inventoryitems,
|
||||||
user=request.user,
|
user=request.user
|
||||||
orderable=False
|
|
||||||
)
|
)
|
||||||
if request.user.has_perm('dcim.change_inventoryitem') or request.user.has_perm('dcim.delete_inventoryitem'):
|
if request.user.has_perm('dcim.change_inventoryitem') or request.user.has_perm('dcim.delete_inventoryitem'):
|
||||||
inventoryitem_table.columns.show('pk')
|
inventoryitem_table.columns.show('pk')
|
||||||
|
@ -340,10 +340,10 @@ class IPAddressTable(BaseTable):
|
|||||||
verbose_name='Interface'
|
verbose_name='Interface'
|
||||||
)
|
)
|
||||||
assigned_object_parent = tables.Column(
|
assigned_object_parent = tables.Column(
|
||||||
accessor='assigned_object__parent',
|
accessor='assigned_object.parent_object',
|
||||||
linkify=True,
|
linkify=True,
|
||||||
orderable=False,
|
orderable=False,
|
||||||
verbose_name='Interface Parent'
|
verbose_name='Device/VM'
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
|
@ -16,7 +16,7 @@ from django.core.validators import URLValidator
|
|||||||
# Environment setup
|
# Environment setup
|
||||||
#
|
#
|
||||||
|
|
||||||
VERSION = '2.11.0'
|
VERSION = '2.11.1'
|
||||||
|
|
||||||
# Hostname
|
# Hostname
|
||||||
HOSTNAME = platform.node()
|
HOSTNAME = platform.node()
|
||||||
|
@ -43,13 +43,13 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Racks</td>
|
<td>Racks</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{% url 'dcim:rack_list' %}?location_id={{ object.pk }}">{{ object.racks.count }}</a>
|
<a href="{% url 'dcim:rack_list' %}?location_id={{ object.pk }}">{{ rack_count }}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Devices</td>
|
<td>Devices</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{% url 'dcim:device_list' %}?location_id={{ object.pk }}">{{ devices_table.rows|length }}</a>
|
<a href="{% url 'dcim:device_list' %}?location_id={{ object.pk }}">{{ device_count }}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
@ -79,18 +79,18 @@
|
|||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<strong>Devices</strong>
|
<strong>Locations</strong>
|
||||||
</div>
|
</div>
|
||||||
{% include 'inc/table.html' with table=devices_table %}
|
{% include 'inc/table.html' with table=child_locations_table %}
|
||||||
{% if perms.dcim.add_device %}
|
{% if perms.dcim.add_location %}
|
||||||
<div class="panel-footer text-right noprint">
|
<div class="panel-footer text-right noprint">
|
||||||
<a href="{% url 'dcim:device_add' %}?location={{ object.pk }}" class="btn btn-xs btn-primary">
|
<a href="{% url 'dcim:location_add' %}?site={{ object.site.pk }}&parent={{ object.pk }}" class="btn btn-xs btn-primary">
|
||||||
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add device
|
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add location
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% include 'inc/paginator.html' with paginator=devices_table.paginator page=devices_table.page %}
|
{% include 'inc/paginator.html' with paginator=child_locations_table.paginator page=child_locations_table.page %}
|
||||||
{% plugin_full_width_page object %}
|
{% plugin_full_width_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -76,7 +76,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% render_table table 'inc/table.html' %}
|
<div class="table-responsive">
|
||||||
|
{% render_table table 'inc/table.html' %}
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% include 'inc/paginator.html' with paginator=table.paginator page=table.page %}
|
{% include 'inc/paginator.html' with paginator=table.paginator page=table.page %}
|
||||||
|
@ -376,6 +376,7 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
|||||||
fieldsets = (
|
fieldsets = (
|
||||||
('Virtual Machine', ('name', 'role', 'status', 'tags')),
|
('Virtual Machine', ('name', 'role', 'status', 'tags')),
|
||||||
('Cluster', ('cluster_group', 'cluster')),
|
('Cluster', ('cluster_group', 'cluster')),
|
||||||
|
('Tenancy', ('tenant_group', 'tenant')),
|
||||||
('Management', ('platform', 'primary_ip4', 'primary_ip6')),
|
('Management', ('platform', 'primary_ip4', 'primary_ip6')),
|
||||||
('Resources', ('vcpus', 'memory', 'disk')),
|
('Resources', ('vcpus', 'memory', 'disk')),
|
||||||
('Config Context', ('local_context_data',)),
|
('Config Context', ('local_context_data',)),
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
from django.db import migrations
|
||||||
|
import utilities.fields
|
||||||
|
import utilities.ordering
|
||||||
|
|
||||||
|
|
||||||
|
def naturalize_virtualmachines(apps, schema_editor):
|
||||||
|
VirtualMachine = apps.get_model('virtualization', 'VirtualMachine')
|
||||||
|
for name in VirtualMachine.objects.values_list('name', flat=True).order_by('name').distinct():
|
||||||
|
VirtualMachine.objects.filter(name=name).update(_name=utilities.ordering.naturalize(name, max_length=100))
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('virtualization', '0022_vminterface_parent'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='virtualmachine',
|
||||||
|
options={'ordering': ('_name', 'pk')},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='virtualmachine',
|
||||||
|
name='_name',
|
||||||
|
field=utilities.fields.NaturalOrderingField('name', max_length=100, blank=True, naturalize_function=utilities.ordering.naturalize),
|
||||||
|
),
|
||||||
|
migrations.RunPython(
|
||||||
|
code=naturalize_virtualmachines,
|
||||||
|
reverse_code=migrations.RunPython.noop
|
||||||
|
),
|
||||||
|
]
|
@ -226,6 +226,11 @@ class VirtualMachine(PrimaryModel, ConfigContextModel):
|
|||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
max_length=64
|
max_length=64
|
||||||
)
|
)
|
||||||
|
_name = NaturalOrderingField(
|
||||||
|
target_field='name',
|
||||||
|
max_length=100,
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
status = models.CharField(
|
status = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=VirtualMachineStatusChoices,
|
choices=VirtualMachineStatusChoices,
|
||||||
@ -296,7 +301,7 @@ class VirtualMachine(PrimaryModel, ConfigContextModel):
|
|||||||
]
|
]
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('name', 'pk') # Name may be non-unique
|
ordering = ('_name', 'pk') # Name may be non-unique
|
||||||
unique_together = [
|
unique_together = [
|
||||||
['cluster', 'tenant', 'name']
|
['cluster', 'tenant', 'name']
|
||||||
]
|
]
|
||||||
|
@ -8,7 +8,7 @@ django-pglocks==1.0.4
|
|||||||
django-prometheus==2.1.0
|
django-prometheus==2.1.0
|
||||||
django-rq==2.4.1
|
django-rq==2.4.1
|
||||||
django-tables2==2.3.4
|
django-tables2==2.3.4
|
||||||
django-taggit==1.3.0
|
django-taggit==1.4.0
|
||||||
django-timezone-field==4.1.2
|
django-timezone-field==4.1.2
|
||||||
djangorestframework==3.12.4
|
djangorestframework==3.12.4
|
||||||
drf-yasg[validation]==1.20.0
|
drf-yasg[validation]==1.20.0
|
||||||
|
Loading…
Reference in New Issue
Block a user