mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-14 09:51:22 -06:00
Merge release v2.4.6
This commit is contained in:
commit
22ed4f1b53
20
CHANGELOG.md
20
CHANGELOG.md
@ -1,7 +1,5 @@
|
|||||||
v2.5.0 (FUTURE)
|
v2.5.0 (FUTURE)
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
* As promised, Python 2 support has been completed removed. Python 3.5 or higher is now required to run NetBox.
|
* As promised, Python 2 support has been completed removed. Python 3.5 or higher is now required to run NetBox.
|
||||||
@ -19,6 +17,24 @@ v2.5.0 (FUTURE)
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
v2.4.6 (2018-10-05)
|
||||||
|
|
||||||
|
## Enhancements
|
||||||
|
|
||||||
|
* [#2479](https://github.com/digitalocean/netbox/issues/2479) - Add user permissions for creating/modifying API tokens
|
||||||
|
* [#2487](https://github.com/digitalocean/netbox/issues/2487) - Return abbreviated API output when passed `?brief=1`
|
||||||
|
|
||||||
|
## Bug Fixes
|
||||||
|
|
||||||
|
* [#2393](https://github.com/digitalocean/netbox/issues/2393) - Fix Unicode support for CSV import under Python 2
|
||||||
|
* [#2483](https://github.com/digitalocean/netbox/issues/2483) - Set max item count of API-populated form fields to MAX_PAGE_SIZE
|
||||||
|
* [#2484](https://github.com/digitalocean/netbox/issues/2484) - Local config context not available on the Virtual Machine Edit Form
|
||||||
|
* [#2485](https://github.com/digitalocean/netbox/issues/2485) - Fix cancel button when assigning a service to a device/VM
|
||||||
|
* [#2491](https://github.com/digitalocean/netbox/issues/2491) - Fix exception when importing devices with invalid device type
|
||||||
|
* [#2492](https://github.com/digitalocean/netbox/issues/2492) - Sanitize hostname and port values returned through LLDP
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
v2.4.5 (2018-10-02)
|
v2.4.5 (2018-10-02)
|
||||||
|
|
||||||
## Enhancements
|
## Enhancements
|
||||||
|
@ -4,6 +4,9 @@ The NetBox API employs token-based authentication. For convenience, cookie authe
|
|||||||
|
|
||||||
A token is a unique identifier that identifies a user to the API. Each user in NetBox may have one or more tokens which he or she can use to authenticate to the API. To create a token, navigate to the API tokens page at `/user/api-tokens/`.
|
A token is a unique identifier that identifies a user to the API. Each user in NetBox may have one or more tokens which he or she can use to authenticate to the API. To create a token, navigate to the API tokens page at `/user/api-tokens/`.
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
The creation and modification of API tokens can be restricted per user by an administrator. If you don't see an option to create an API token, ask an administrator to grant you access.
|
||||||
|
|
||||||
Each token contains a 160-bit key represented as 40 hexadecimal characters. When creating a token, you'll typically leave the key field blank so that a random key will be automatically generated. However, NetBox allows you to specify a key in case you need to restore a previously deleted token to operation.
|
Each token contains a 160-bit key represented as 40 hexadecimal characters. When creating a token, you'll typically leave the key field blank so that a random key will be automatically generated. However, NetBox allows you to specify a key in case you need to restore a previously deleted token to operation.
|
||||||
|
|
||||||
By default, a token can be used for all operations available via the API. Deselecting the "write enabled" option will restrict API requests made with the token to read operations (e.g. GET) only.
|
By default, a token can be used for all operations available via the API. Deselecting the "write enabled" option will restrict API requests made with the token to read operations (e.g. GET) only.
|
||||||
|
@ -54,6 +54,16 @@ class ProviderTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.data['count'], 3)
|
self.assertEqual(response.data['count'], 3)
|
||||||
|
|
||||||
|
def test_list_providers_brief(self):
|
||||||
|
|
||||||
|
url = reverse('circuits-api:provider-list')
|
||||||
|
response = self.client.get('{}?brief=1'.format(url), **self.header)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(response.data['results'][0]),
|
||||||
|
['id', 'name', 'slug', 'url']
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_provider(self):
|
def test_create_provider(self):
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
@ -145,6 +155,16 @@ class CircuitTypeTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.data['count'], 3)
|
self.assertEqual(response.data['count'], 3)
|
||||||
|
|
||||||
|
def test_list_circuittypes_brief(self):
|
||||||
|
|
||||||
|
url = reverse('circuits-api:circuittype-list')
|
||||||
|
response = self.client.get('{}?brief=1'.format(url), **self.header)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(response.data['results'][0]),
|
||||||
|
['id', 'name', 'slug', 'url']
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_circuittype(self):
|
def test_create_circuittype(self):
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
@ -214,6 +234,16 @@ class CircuitTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.data['count'], 3)
|
self.assertEqual(response.data['count'], 3)
|
||||||
|
|
||||||
|
def test_list_circuits_brief(self):
|
||||||
|
|
||||||
|
url = reverse('circuits-api:circuit-list')
|
||||||
|
response = self.client.get('{}?brief=1'.format(url), **self.header)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(response.data['results'][0]),
|
||||||
|
['cid', 'id', 'url']
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_circuit(self):
|
def test_create_circuit(self):
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
|
@ -490,6 +490,15 @@ class ConsolePortSerializer(TaggitSerializer, ValidatedModelSerializer):
|
|||||||
fields = ['id', 'device', 'name', 'cs_port', 'connection_status', 'tags']
|
fields = ['id', 'device', 'name', 'cs_port', 'connection_status', 'tags']
|
||||||
|
|
||||||
|
|
||||||
|
class NestedConsolePortSerializer(TaggitSerializer, ValidatedModelSerializer):
|
||||||
|
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:consoleport-detail')
|
||||||
|
device = NestedDeviceSerializer(read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ConsolePort
|
||||||
|
fields = ['id', 'url', 'device', 'name']
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Power outlets
|
# Power outlets
|
||||||
#
|
#
|
||||||
@ -527,6 +536,15 @@ class PowerPortSerializer(TaggitSerializer, ValidatedModelSerializer):
|
|||||||
fields = ['id', 'device', 'name', 'power_outlet', 'connection_status', 'tags']
|
fields = ['id', 'device', 'name', 'power_outlet', 'connection_status', 'tags']
|
||||||
|
|
||||||
|
|
||||||
|
class NestedPowerPortSerializer(TaggitSerializer, ValidatedModelSerializer):
|
||||||
|
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:powerport-detail')
|
||||||
|
device = NestedDeviceSerializer(read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = PowerPort
|
||||||
|
fields = ['id', 'url', 'device', 'name']
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Interfaces
|
# Interfaces
|
||||||
#
|
#
|
||||||
@ -650,10 +668,11 @@ class DeviceBaySerializer(TaggitSerializer, ValidatedModelSerializer):
|
|||||||
|
|
||||||
class NestedDeviceBaySerializer(WritableNestedSerializer):
|
class NestedDeviceBaySerializer(WritableNestedSerializer):
|
||||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicebay-detail')
|
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicebay-detail')
|
||||||
|
device = NestedDeviceSerializer(read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = DeviceBay
|
model = DeviceBay
|
||||||
fields = ['id', 'url', 'name']
|
fields = ['id', 'url', 'device', 'name']
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -236,6 +236,11 @@ class DeviceViewSet(CustomFieldModelViewSet):
|
|||||||
"""
|
"""
|
||||||
if self.action == 'retrieve':
|
if self.action == 'retrieve':
|
||||||
return serializers.DeviceWithConfigContextSerializer
|
return serializers.DeviceWithConfigContextSerializer
|
||||||
|
|
||||||
|
request = self.get_serializer_context()['request']
|
||||||
|
if request.query_params.get('brief', False):
|
||||||
|
return serializers.NestedDeviceSerializer
|
||||||
|
|
||||||
return serializers.DeviceSerializer
|
return serializers.DeviceSerializer
|
||||||
|
|
||||||
@action(detail=True, url_path='napalm')
|
@action(detail=True, url_path='napalm')
|
||||||
|
@ -1370,7 +1370,7 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
|
|||||||
})
|
})
|
||||||
|
|
||||||
# Validate manufacturer/platform
|
# Validate manufacturer/platform
|
||||||
if self.device_type and self.platform:
|
if hasattr(self, 'device_type') and self.platform:
|
||||||
if self.platform.manufacturer and self.platform.manufacturer != self.device_type.manufacturer:
|
if self.platform.manufacturer and self.platform.manufacturer != self.device_type.manufacturer:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'platform': "The assigned platform is limited to {} device types, but this device's type belongs "
|
'platform': "The assigned platform is limited to {} device types, but this device's type belongs "
|
||||||
|
@ -42,6 +42,16 @@ class RegionTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.data['count'], 3)
|
self.assertEqual(response.data['count'], 3)
|
||||||
|
|
||||||
|
def test_list_regions_brief(self):
|
||||||
|
|
||||||
|
url = reverse('dcim-api:region-list')
|
||||||
|
response = self.client.get('{}?brief=1'.format(url), **self.header)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(response.data['results'][0]),
|
||||||
|
['id', 'name', 'slug', 'url']
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_region(self):
|
def test_create_region(self):
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
@ -156,6 +166,16 @@ class SiteTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.data['count'], 3)
|
self.assertEqual(response.data['count'], 3)
|
||||||
|
|
||||||
|
def test_list_sites_brief(self):
|
||||||
|
|
||||||
|
url = reverse('dcim-api:site-list')
|
||||||
|
response = self.client.get('{}?brief=1'.format(url), **self.header)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(response.data['results'][0]),
|
||||||
|
['id', 'name', 'slug', 'url']
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_site(self):
|
def test_create_site(self):
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
@ -260,6 +280,16 @@ class RackGroupTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.data['count'], 3)
|
self.assertEqual(response.data['count'], 3)
|
||||||
|
|
||||||
|
def test_list_rackgroups_brief(self):
|
||||||
|
|
||||||
|
url = reverse('dcim-api:rackgroup-list')
|
||||||
|
response = self.client.get('{}?brief=1'.format(url), **self.header)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(response.data['results'][0]),
|
||||||
|
['id', 'name', 'slug', 'url']
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_rackgroup(self):
|
def test_create_rackgroup(self):
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
@ -358,6 +388,16 @@ class RackRoleTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.data['count'], 3)
|
self.assertEqual(response.data['count'], 3)
|
||||||
|
|
||||||
|
def test_list_rackroles_brief(self):
|
||||||
|
|
||||||
|
url = reverse('dcim-api:rackrole-list')
|
||||||
|
response = self.client.get('{}?brief=1'.format(url), **self.header)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(response.data['results'][0]),
|
||||||
|
['id', 'name', 'slug', 'url']
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_rackrole(self):
|
def test_create_rackrole(self):
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
@ -475,6 +515,16 @@ class RackTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.data['count'], 3)
|
self.assertEqual(response.data['count'], 3)
|
||||||
|
|
||||||
|
def test_list_racks_brief(self):
|
||||||
|
|
||||||
|
url = reverse('dcim-api:rack-list')
|
||||||
|
response = self.client.get('{}?brief=1'.format(url), **self.header)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(response.data['results'][0]),
|
||||||
|
['display_name', 'id', 'name', 'url']
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_rack(self):
|
def test_create_rack(self):
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
@ -691,6 +741,16 @@ class ManufacturerTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.data['count'], 3)
|
self.assertEqual(response.data['count'], 3)
|
||||||
|
|
||||||
|
def test_list_manufacturers_brief(self):
|
||||||
|
|
||||||
|
url = reverse('dcim-api:manufacturer-list')
|
||||||
|
response = self.client.get('{}?brief=1'.format(url), **self.header)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(response.data['results'][0]),
|
||||||
|
['id', 'name', 'slug', 'url']
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_manufacturer(self):
|
def test_create_manufacturer(self):
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
@ -790,6 +850,16 @@ class DeviceTypeTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.data['count'], 3)
|
self.assertEqual(response.data['count'], 3)
|
||||||
|
|
||||||
|
def test_list_devicetypes_brief(self):
|
||||||
|
|
||||||
|
url = reverse('dcim-api:devicetype-list')
|
||||||
|
response = self.client.get('{}?brief=1'.format(url), **self.header)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(response.data['results'][0]),
|
||||||
|
['id', 'manufacturer', 'model', 'slug', 'url']
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_devicetype(self):
|
def test_create_devicetype(self):
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
@ -1494,6 +1564,16 @@ class DeviceRoleTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.data['count'], 3)
|
self.assertEqual(response.data['count'], 3)
|
||||||
|
|
||||||
|
def test_list_deviceroles_brief(self):
|
||||||
|
|
||||||
|
url = reverse('dcim-api:devicerole-list')
|
||||||
|
response = self.client.get('{}?brief=1'.format(url), **self.header)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(response.data['results'][0]),
|
||||||
|
['id', 'name', 'slug', 'url']
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_devicerole(self):
|
def test_create_devicerole(self):
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
@ -1592,6 +1672,16 @@ class PlatformTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.data['count'], 3)
|
self.assertEqual(response.data['count'], 3)
|
||||||
|
|
||||||
|
def test_list_platforms_brief(self):
|
||||||
|
|
||||||
|
url = reverse('dcim-api:platform-list')
|
||||||
|
response = self.client.get('{}?brief=1'.format(url), **self.header)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(response.data['results'][0]),
|
||||||
|
['id', 'name', 'slug', 'url']
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_platform(self):
|
def test_create_platform(self):
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
@ -1720,6 +1810,16 @@ class DeviceTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.data['count'], 3)
|
self.assertEqual(response.data['count'], 3)
|
||||||
|
|
||||||
|
def test_list_devices_brief(self):
|
||||||
|
|
||||||
|
url = reverse('dcim-api:device-list')
|
||||||
|
response = self.client.get('{}?brief=1'.format(url), **self.header)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(response.data['results'][0]),
|
||||||
|
['display_name', 'id', 'name', 'url']
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_device(self):
|
def test_create_device(self):
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
@ -1846,6 +1946,16 @@ class ConsolePortTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.data['count'], 3)
|
self.assertEqual(response.data['count'], 3)
|
||||||
|
|
||||||
|
def test_list_consoleports_brief(self):
|
||||||
|
|
||||||
|
url = reverse('dcim-api:consoleport-list')
|
||||||
|
response = self.client.get('{}?brief=1'.format(url), **self.header)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(response.data['results'][0]),
|
||||||
|
['device', 'id', 'name', 'url']
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_consoleport(self):
|
def test_create_consoleport(self):
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
@ -1951,6 +2061,16 @@ class ConsoleServerPortTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.data['count'], 3)
|
self.assertEqual(response.data['count'], 3)
|
||||||
|
|
||||||
|
def test_list_consoleserverports_brief(self):
|
||||||
|
|
||||||
|
url = reverse('dcim-api:consoleserverport-list')
|
||||||
|
response = self.client.get('{}?brief=1'.format(url), **self.header)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(response.data['results'][0]),
|
||||||
|
['device', 'id', 'name', 'url']
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_consoleserverport(self):
|
def test_create_consoleserverport(self):
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
@ -2052,6 +2172,16 @@ class PowerPortTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.data['count'], 3)
|
self.assertEqual(response.data['count'], 3)
|
||||||
|
|
||||||
|
def test_list_powerports_brief(self):
|
||||||
|
|
||||||
|
url = reverse('dcim-api:powerport-list')
|
||||||
|
response = self.client.get('{}?brief=1'.format(url), **self.header)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(response.data['results'][0]),
|
||||||
|
['device', 'id', 'name', 'url']
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_powerport(self):
|
def test_create_powerport(self):
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
@ -2157,6 +2287,16 @@ class PowerOutletTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.data['count'], 3)
|
self.assertEqual(response.data['count'], 3)
|
||||||
|
|
||||||
|
def test_list_poweroutlets_brief(self):
|
||||||
|
|
||||||
|
url = reverse('dcim-api:poweroutlet-list')
|
||||||
|
response = self.client.get('{}?brief=1'.format(url), **self.header)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(response.data['results'][0]),
|
||||||
|
['device', 'id', 'name', 'url']
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_poweroutlet(self):
|
def test_create_poweroutlet(self):
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
@ -2283,6 +2423,16 @@ class InterfaceTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.data['count'], 3)
|
self.assertEqual(response.data['count'], 3)
|
||||||
|
|
||||||
|
def test_list_interfaces_brief(self):
|
||||||
|
|
||||||
|
url = reverse('dcim-api:interface-list')
|
||||||
|
response = self.client.get('{}?brief=1'.format(url), **self.header)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(response.data['results'][0]),
|
||||||
|
['device', 'id', 'name', 'url']
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_interface(self):
|
def test_create_interface(self):
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
@ -2454,6 +2604,16 @@ class DeviceBayTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.data['count'], 3)
|
self.assertEqual(response.data['count'], 3)
|
||||||
|
|
||||||
|
def test_list_devicebays_brief(self):
|
||||||
|
|
||||||
|
url = reverse('dcim-api:devicebay-list')
|
||||||
|
response = self.client.get('{}?brief=1'.format(url), **self.header)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(response.data['results'][0]),
|
||||||
|
['device', 'id', 'name', 'url']
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_devicebay(self):
|
def test_create_devicebay(self):
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
@ -2776,6 +2936,16 @@ class InterfaceConnectionTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.data['count'], 3)
|
self.assertEqual(response.data['count'], 3)
|
||||||
|
|
||||||
|
def test_list_interfaceconnections_brief(self):
|
||||||
|
|
||||||
|
url = reverse('dcim-api:interfaceconnection-list')
|
||||||
|
response = self.client.get('{}?brief=1'.format(url), **self.header)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(response.data['results'][0]),
|
||||||
|
['connection_status', 'id', 'url']
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_interfaceconnection(self):
|
def test_create_interfaceconnection(self):
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
@ -2971,6 +3141,16 @@ class VirtualChassisTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.data['count'], 2)
|
self.assertEqual(response.data['count'], 2)
|
||||||
|
|
||||||
|
def test_list_virtualchassis_brief(self):
|
||||||
|
|
||||||
|
url = reverse('dcim-api:virtualchassis-list')
|
||||||
|
response = self.client.get('{}?brief=1'.format(url), **self.header)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(response.data['results'][0]),
|
||||||
|
['id', 'url']
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_virtualchassis(self):
|
def test_create_virtualchassis(self):
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
|
@ -32,6 +32,16 @@ class VRFTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.data['count'], 3)
|
self.assertEqual(response.data['count'], 3)
|
||||||
|
|
||||||
|
def test_list_vrfs_brief(self):
|
||||||
|
|
||||||
|
url = reverse('ipam-api:vrf-list')
|
||||||
|
response = self.client.get('{}?brief=1'.format(url), **self.header)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(response.data['results'][0]),
|
||||||
|
['id', 'name', 'rd', 'url']
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_vrf(self):
|
def test_create_vrf(self):
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
@ -123,6 +133,16 @@ class RIRTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.data['count'], 3)
|
self.assertEqual(response.data['count'], 3)
|
||||||
|
|
||||||
|
def test_list_rirs_brief(self):
|
||||||
|
|
||||||
|
url = reverse('ipam-api:rir-list')
|
||||||
|
response = self.client.get('{}?brief=1'.format(url), **self.header)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(response.data['results'][0]),
|
||||||
|
['id', 'name', 'slug', 'url']
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_rir(self):
|
def test_create_rir(self):
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
@ -216,6 +236,16 @@ class AggregateTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.data['count'], 3)
|
self.assertEqual(response.data['count'], 3)
|
||||||
|
|
||||||
|
def test_list_aggregates_brief(self):
|
||||||
|
|
||||||
|
url = reverse('ipam-api:aggregate-list')
|
||||||
|
response = self.client.get('{}?brief=1'.format(url), **self.header)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(response.data['results'][0]),
|
||||||
|
['family', 'id', 'prefix', 'url']
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_aggregate(self):
|
def test_create_aggregate(self):
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
@ -307,6 +337,16 @@ class RoleTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.data['count'], 3)
|
self.assertEqual(response.data['count'], 3)
|
||||||
|
|
||||||
|
def test_list_roles_brief(self):
|
||||||
|
|
||||||
|
url = reverse('ipam-api:role-list')
|
||||||
|
response = self.client.get('{}?brief=1'.format(url), **self.header)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(response.data['results'][0]),
|
||||||
|
['id', 'name', 'slug', 'url']
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_role(self):
|
def test_create_role(self):
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
@ -395,13 +435,23 @@ class PrefixTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.data['prefix'], str(self.prefix1.prefix))
|
self.assertEqual(response.data['prefix'], str(self.prefix1.prefix))
|
||||||
|
|
||||||
def test_list_prefixs(self):
|
def test_list_prefixes(self):
|
||||||
|
|
||||||
url = reverse('ipam-api:prefix-list')
|
url = reverse('ipam-api:prefix-list')
|
||||||
response = self.client.get(url, **self.header)
|
response = self.client.get(url, **self.header)
|
||||||
|
|
||||||
self.assertEqual(response.data['count'], 3)
|
self.assertEqual(response.data['count'], 3)
|
||||||
|
|
||||||
|
def test_list_prefixes_brief(self):
|
||||||
|
|
||||||
|
url = reverse('ipam-api:prefix-list')
|
||||||
|
response = self.client.get('{}?brief=1'.format(url), **self.header)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(response.data['results'][0]),
|
||||||
|
['family', 'id', 'prefix', 'url']
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_prefix(self):
|
def test_create_prefix(self):
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
@ -628,6 +678,16 @@ class IPAddressTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.data['count'], 3)
|
self.assertEqual(response.data['count'], 3)
|
||||||
|
|
||||||
|
def test_list_ipaddresses_brief(self):
|
||||||
|
|
||||||
|
url = reverse('ipam-api:ipaddress-list')
|
||||||
|
response = self.client.get('{}?brief=1'.format(url), **self.header)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(response.data['results'][0]),
|
||||||
|
['address', 'family', 'id', 'url']
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_ipaddress(self):
|
def test_create_ipaddress(self):
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
@ -716,6 +776,16 @@ class VLANGroupTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.data['count'], 3)
|
self.assertEqual(response.data['count'], 3)
|
||||||
|
|
||||||
|
def test_list_vlangroups_brief(self):
|
||||||
|
|
||||||
|
url = reverse('ipam-api:vlangroup-list')
|
||||||
|
response = self.client.get('{}?brief=1'.format(url), **self.header)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(response.data['results'][0]),
|
||||||
|
['id', 'name', 'slug', 'url']
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_vlangroup(self):
|
def test_create_vlangroup(self):
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
@ -807,6 +877,16 @@ class VLANTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.data['count'], 3)
|
self.assertEqual(response.data['count'], 3)
|
||||||
|
|
||||||
|
def test_list_vlans_brief(self):
|
||||||
|
|
||||||
|
url = reverse('ipam-api:vlan-list')
|
||||||
|
response = self.client.get('{}?brief=1'.format(url), **self.header)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(response.data['results'][0]),
|
||||||
|
['display_name', 'id', 'name', 'url', 'vid']
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_vlan(self):
|
def test_create_vlan(self):
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
|
@ -989,6 +989,9 @@ class ServiceCreateView(PermissionRequiredMixin, ObjectEditView):
|
|||||||
obj.virtual_machine = get_object_or_404(VirtualMachine, pk=url_kwargs['virtualmachine'])
|
obj.virtual_machine = get_object_or_404(VirtualMachine, pk=url_kwargs['virtualmachine'])
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
def get_return_url(self, request, service):
|
||||||
|
return service.parent.get_absolute_url()
|
||||||
|
|
||||||
|
|
||||||
class ServiceEditView(ServiceCreateView):
|
class ServiceEditView(ServiceCreateView):
|
||||||
permission_required = 'ipam.change_service'
|
permission_required = 'ipam.change_service'
|
||||||
|
@ -82,7 +82,7 @@ $(document).ready(function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($(parent).val() || $(parent).attr('nullable') == 'true') {
|
if ($(parent).val() || $(parent).attr('nullable') == 'true') {
|
||||||
var api_url = child_field.attr('api-url');
|
var api_url = child_field.attr('api-url') + '&limit=0&brief=1';
|
||||||
var disabled_indicator = child_field.attr('disabled-indicator');
|
var disabled_indicator = child_field.attr('disabled-indicator');
|
||||||
var initial_value = child_field.attr('initial');
|
var initial_value = child_field.attr('initial');
|
||||||
var display_field = child_field.attr('display-field') || 'name';
|
var display_field = child_field.attr('display-field') || 'name';
|
||||||
|
@ -71,6 +71,16 @@ class SecretRoleTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.data['count'], 3)
|
self.assertEqual(response.data['count'], 3)
|
||||||
|
|
||||||
|
def test_list_secretroles_brief(self):
|
||||||
|
|
||||||
|
url = reverse('secrets-api:secretrole-list')
|
||||||
|
response = self.client.get('{}?brief=1'.format(url), **self.header)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(response.data['results'][0]),
|
||||||
|
['id', 'name', 'slug', 'url']
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_secretrole(self):
|
def test_create_secretrole(self):
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
|
@ -64,8 +64,10 @@ $(document).ready(function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clean up hostnames/interfaces learned via LLDP
|
// Clean up hostnames/interfaces learned via LLDP
|
||||||
var lldp_device = neighbor['hostname'].split(".")[0]; // Strip off any trailing domain name
|
var neighbor_host = neighbor['hostname'] || ""; // sanitize hostname if it's null to avoid breaking the split func
|
||||||
var lldp_interface = neighbor['port'].split(".")[0]; // Strip off any trailing subinterface ID
|
var neighbor_port = neighbor['port'] || ""; // sanitize port if it's null to avoid breaking the split func
|
||||||
|
var lldp_device = neighbor_host.split(".")[0]; // Strip off any trailing domain name
|
||||||
|
var lldp_interface = neighbor_port.split(".")[0]; // Strip off any trailing subinterface ID
|
||||||
|
|
||||||
// Add LLDP neighbors to table
|
// Add LLDP neighbors to table
|
||||||
row.children('td.device').html(lldp_device);
|
row.children('td.device').html(lldp_device);
|
||||||
|
@ -10,8 +10,12 @@
|
|||||||
<div class="panel panel-{% if token.is_expired %}danger{% else %}default{% endif %}">
|
<div class="panel panel-{% if token.is_expired %}danger{% else %}default{% endif %}">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
|
{% if perms.users.change_token %}
|
||||||
<a href="{% url 'user:token_edit' pk=token.pk %}" class="btn btn-xs btn-warning">Edit</a>
|
<a href="{% url 'user:token_edit' pk=token.pk %}" class="btn btn-xs btn-warning">Edit</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if perms.users.delete_token %}
|
||||||
<a href="{% url 'user:token_delete' pk=token.pk %}" class="btn btn-xs btn-danger">Delete</a>
|
<a href="{% url 'user:token_delete' pk=token.pk %}" class="btn btn-xs btn-danger">Delete</a>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<i class="fa fa-key"></i> {{ token.key }}
|
<i class="fa fa-key"></i> {{ token.key }}
|
||||||
{% if token.is_expired %}
|
{% if token.is_expired %}
|
||||||
@ -49,10 +53,16 @@
|
|||||||
{% empty %}
|
{% empty %}
|
||||||
<p>You do not have any API tokens.</p>
|
<p>You do not have any API tokens.</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% if perms.users.add_token %}
|
||||||
<a href="{% url 'user:token_add' %}" class="btn btn-primary">
|
<a href="{% url 'user:token_add' %}" class="btn btn-primary">
|
||||||
<span class="fa fa-plus" aria-hidden="true"></span>
|
<span class="fa fa-plus" aria-hidden="true"></span>
|
||||||
Add a token
|
Add a token
|
||||||
</a>
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<div class="alert alert-info text-center" role="alert">
|
||||||
|
You do not have permission to create new API tokens. If needed, ask an administrator to enable token creation for your account or an assigned group.
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -29,6 +29,16 @@ class TenantGroupTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.data['count'], 3)
|
self.assertEqual(response.data['count'], 3)
|
||||||
|
|
||||||
|
def test_list_tenantgroups_brief(self):
|
||||||
|
|
||||||
|
url = reverse('tenancy-api:tenantgroup-list')
|
||||||
|
response = self.client.get('{}?brief=1'.format(url), **self.header)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(response.data['results'][0]),
|
||||||
|
['id', 'name', 'slug', 'url']
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_tenantgroup(self):
|
def test_create_tenantgroup(self):
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
@ -122,6 +132,16 @@ class TenantTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.data['count'], 3)
|
self.assertEqual(response.data['count'], 3)
|
||||||
|
|
||||||
|
def test_list_tenants_brief(self):
|
||||||
|
|
||||||
|
url = reverse('tenancy-api:tenant-list')
|
||||||
|
response = self.client.get('{}?brief=1'.format(url), **self.header)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(response.data['results'][0]),
|
||||||
|
['id', 'name', 'slug', 'url']
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_tenant(self):
|
def test_create_tenant(self):
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
|
17
netbox/users/migrations/0003_token_permissions.py
Normal file
17
netbox/users/migrations/0003_token_permissions.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 2.0.8 on 2018-10-05 14:32
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('users', '0001_api_tokens_squashed_0002_unicode_literals'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='token',
|
||||||
|
options={},
|
||||||
|
),
|
||||||
|
]
|
@ -39,7 +39,7 @@ class Token(models.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
default_permissions = []
|
pass
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
# Only display the last 24 bits of the token to avoid accidental exposure.
|
# Only display the last 24 bits of the token to avoid accidental exposure.
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth import login as auth_login, logout as auth_logout, update_session_auth_hash
|
from django.contrib.auth import login as auth_login, logout as auth_logout, update_session_auth_hash
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseForbidden, HttpResponseRedirect
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
@ -217,8 +217,12 @@ class TokenEditView(LoginRequiredMixin, View):
|
|||||||
def get(self, request, pk=None):
|
def get(self, request, pk=None):
|
||||||
|
|
||||||
if pk is not None:
|
if pk is not None:
|
||||||
|
if not request.user.has_perm('users.change_token'):
|
||||||
|
return HttpResponseForbidden()
|
||||||
token = get_object_or_404(Token.objects.filter(user=request.user), pk=pk)
|
token = get_object_or_404(Token.objects.filter(user=request.user), pk=pk)
|
||||||
else:
|
else:
|
||||||
|
if not request.user.has_perm('users.add_token'):
|
||||||
|
return HttpResponseForbidden()
|
||||||
token = Token(user=request.user)
|
token = Token(user=request.user)
|
||||||
|
|
||||||
form = TokenForm(instance=token)
|
form = TokenForm(instance=token)
|
||||||
@ -260,7 +264,8 @@ class TokenEditView(LoginRequiredMixin, View):
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
class TokenDeleteView(LoginRequiredMixin, View):
|
class TokenDeleteView(PermissionRequiredMixin, View):
|
||||||
|
permission_required = 'users.delete_token'
|
||||||
|
|
||||||
def get(self, request, pk):
|
def get(self, request, pk):
|
||||||
|
|
||||||
|
@ -190,6 +190,19 @@ class ModelViewSet(_ModelViewSet):
|
|||||||
|
|
||||||
return super(ModelViewSet, self).get_serializer(*args, **kwargs)
|
return super(ModelViewSet, self).get_serializer(*args, **kwargs)
|
||||||
|
|
||||||
|
def get_serializer_class(self):
|
||||||
|
|
||||||
|
# If 'brief' has been passed as a query param, find and return the nested serializer for this model, if one
|
||||||
|
# exists
|
||||||
|
request = self.get_serializer_context()['request']
|
||||||
|
if request.query_params.get('brief', False):
|
||||||
|
serializer_class = get_serializer_for_model(self.queryset.model, prefix='Nested')
|
||||||
|
if serializer_class is not None:
|
||||||
|
return serializer_class
|
||||||
|
|
||||||
|
# Fall back to the hard-coded serializer class
|
||||||
|
return self.serializer_class
|
||||||
|
|
||||||
|
|
||||||
class FieldChoicesViewSet(ViewSet):
|
class FieldChoicesViewSet(ViewSet):
|
||||||
"""
|
"""
|
||||||
|
@ -2,6 +2,7 @@ import csv
|
|||||||
from io import StringIO
|
from io import StringIO
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@ -148,6 +149,11 @@ def add_blank_choice(choices):
|
|||||||
return ((None, '---------'),) + tuple(choices)
|
return ((None, '---------'),) + tuple(choices)
|
||||||
|
|
||||||
|
|
||||||
|
def utf8_encoder(data):
|
||||||
|
for line in data:
|
||||||
|
yield line.encode('utf-8')
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Widgets
|
# Widgets
|
||||||
#
|
#
|
||||||
@ -301,6 +307,11 @@ class CSVDataField(forms.CharField):
|
|||||||
def to_python(self, value):
|
def to_python(self, value):
|
||||||
|
|
||||||
records = []
|
records = []
|
||||||
|
|
||||||
|
# Python 2 hack for Unicode support in the CSV reader
|
||||||
|
if sys.version_info[0] < 3:
|
||||||
|
reader = csv.reader(utf8_encoder(StringIO(value)))
|
||||||
|
else:
|
||||||
reader = csv.reader(StringIO(value))
|
reader = csv.reader(StringIO(value))
|
||||||
|
|
||||||
# Consume and validate the first line of CSV data as column headers
|
# Consume and validate the first line of CSV data as column headers
|
||||||
|
@ -167,7 +167,8 @@ class InterfaceSerializer(TaggitSerializer, ValidatedModelSerializer):
|
|||||||
|
|
||||||
class NestedInterfaceSerializer(WritableNestedSerializer):
|
class NestedInterfaceSerializer(WritableNestedSerializer):
|
||||||
url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:interface-detail')
|
url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:interface-detail')
|
||||||
|
virtual_machine = NestedVirtualMachineSerializer(read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Interface
|
model = Interface
|
||||||
fields = ['id', 'url', 'name']
|
fields = ['id', 'url', 'virtual_machine', 'name']
|
||||||
|
@ -54,6 +54,11 @@ class VirtualMachineViewSet(CustomFieldModelViewSet):
|
|||||||
"""
|
"""
|
||||||
if self.action == 'retrieve':
|
if self.action == 'retrieve':
|
||||||
return serializers.VirtualMachineWithConfigContextSerializer
|
return serializers.VirtualMachineWithConfigContextSerializer
|
||||||
|
|
||||||
|
request = self.get_serializer_context()['request']
|
||||||
|
if request.query_params.get('brief', False):
|
||||||
|
return serializers.NestedVirtualMachineSerializer
|
||||||
|
|
||||||
return serializers.VirtualMachineSerializer
|
return serializers.VirtualMachineSerializer
|
||||||
|
|
||||||
|
|
||||||
@ -63,3 +68,10 @@ class InterfaceViewSet(ModelViewSet):
|
|||||||
).select_related('virtual_machine').prefetch_related('tags')
|
).select_related('virtual_machine').prefetch_related('tags')
|
||||||
serializer_class = serializers.InterfaceSerializer
|
serializer_class = serializers.InterfaceSerializer
|
||||||
filter_class = filters.InterfaceFilter
|
filter_class = filters.InterfaceFilter
|
||||||
|
|
||||||
|
def get_serializer_class(self):
|
||||||
|
request = self.get_serializer_context()['request']
|
||||||
|
if request.query_params.get('brief', False):
|
||||||
|
# Override get_serializer_for_model(), which will return the DCIM NestedInterfaceSerializer
|
||||||
|
return serializers.NestedInterfaceSerializer
|
||||||
|
return serializers.InterfaceSerializer
|
||||||
|
@ -251,7 +251,7 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
|||||||
model = VirtualMachine
|
model = VirtualMachine
|
||||||
fields = [
|
fields = [
|
||||||
'name', 'status', 'cluster_group', 'cluster', 'role', 'tenant', 'platform', 'primary_ip4', 'primary_ip6',
|
'name', 'status', 'cluster_group', 'cluster', 'role', 'tenant', 'platform', 'primary_ip4', 'primary_ip6',
|
||||||
'vcpus', 'memory', 'disk', 'comments', 'tags',
|
'vcpus', 'memory', 'disk', 'comments', 'tags', 'local_context_data',
|
||||||
]
|
]
|
||||||
help_texts = {
|
help_texts = {
|
||||||
'local_context_data': "Local config context data overwrites all sources contexts in the final rendered config context",
|
'local_context_data': "Local config context data overwrites all sources contexts in the final rendered config context",
|
||||||
|
@ -33,6 +33,16 @@ class ClusterTypeTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.data['count'], 3)
|
self.assertEqual(response.data['count'], 3)
|
||||||
|
|
||||||
|
def test_list_clustertypes_brief(self):
|
||||||
|
|
||||||
|
url = reverse('virtualization-api:clustertype-list')
|
||||||
|
response = self.client.get('{}?brief=1'.format(url), **self.header)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(response.data['results'][0]),
|
||||||
|
['id', 'name', 'slug', 'url']
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_clustertype(self):
|
def test_create_clustertype(self):
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
@ -124,6 +134,16 @@ class ClusterGroupTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.data['count'], 3)
|
self.assertEqual(response.data['count'], 3)
|
||||||
|
|
||||||
|
def test_list_clustergroups_brief(self):
|
||||||
|
|
||||||
|
url = reverse('virtualization-api:clustergroup-list')
|
||||||
|
response = self.client.get('{}?brief=1'.format(url), **self.header)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(response.data['results'][0]),
|
||||||
|
['id', 'name', 'slug', 'url']
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_clustergroup(self):
|
def test_create_clustergroup(self):
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
@ -218,6 +238,16 @@ class ClusterTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.data['count'], 3)
|
self.assertEqual(response.data['count'], 3)
|
||||||
|
|
||||||
|
def test_list_clusters_brief(self):
|
||||||
|
|
||||||
|
url = reverse('virtualization-api:cluster-list')
|
||||||
|
response = self.client.get('{}?brief=1'.format(url), **self.header)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(response.data['results'][0]),
|
||||||
|
['id', 'name', 'url']
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_cluster(self):
|
def test_create_cluster(self):
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
@ -322,6 +352,16 @@ class VirtualMachineTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.data['count'], 3)
|
self.assertEqual(response.data['count'], 3)
|
||||||
|
|
||||||
|
def test_list_virtualmachines_brief(self):
|
||||||
|
|
||||||
|
url = reverse('virtualization-api:virtualmachine-list')
|
||||||
|
response = self.client.get('{}?brief=1'.format(url), **self.header)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(response.data['results'][0]),
|
||||||
|
['id', 'name', 'url']
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_virtualmachine(self):
|
def test_create_virtualmachine(self):
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
@ -445,6 +485,16 @@ class InterfaceTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.data['count'], 3)
|
self.assertEqual(response.data['count'], 3)
|
||||||
|
|
||||||
|
def test_list_interfaces_brief(self):
|
||||||
|
|
||||||
|
url = reverse('virtualization-api:interface-list')
|
||||||
|
response = self.client.get('{}?brief=1'.format(url), **self.header)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(response.data['results'][0]),
|
||||||
|
['id', 'name', 'url', 'virtual_machine']
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_interface(self):
|
def test_create_interface(self):
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
|
Loading…
Reference in New Issue
Block a user