mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-24 09:28:38 -06:00
Merge pull request #5166 from netbox-community/4882-api-bulk-update
#4882: Support bulk updates via REST API
This commit is contained in:
commit
6195a34db5
@ -6,6 +6,20 @@
|
|||||||
|
|
||||||
### New Features
|
### New Features
|
||||||
|
|
||||||
|
#### REST API Bulk Deletion ([#3436](https://github.com/netbox-community/netbox/issues/3436))
|
||||||
|
|
||||||
|
The REST API now supports the bulk deletion of objects of the same type in a single request. Send a `DELETE` HTTP request to the list to the model's list endpoint (e.g. `/api/dcim/sites/`) with a list of JSON objects specifying the numeric ID of each object to be deleted. For example, to delete sites with IDs 10, 11, and 12, issue the following request:
|
||||||
|
|
||||||
|
```no-highlight
|
||||||
|
curl -s -X DELETE \
|
||||||
|
-H "Authorization: Token $TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
http://netbox/api/dcim/sites/ \
|
||||||
|
--data '[{"id": 10}, {"id": 11}, {"id": 12}]'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Enhancements
|
||||||
|
|
||||||
* [#1503](https://github.com/netbox-community/netbox/issues/1503) - Allow assigment of secrets to virtual machines
|
* [#1503](https://github.com/netbox-community/netbox/issues/1503) - Allow assigment of secrets to virtual machines
|
||||||
* [#1692](https://github.com/netbox-community/netbox/issues/1692) - Allow assigment of inventory items to parent items in web UI
|
* [#1692](https://github.com/netbox-community/netbox/issues/1692) - Allow assigment of inventory items to parent items in web UI
|
||||||
* [#2179](https://github.com/netbox-community/netbox/issues/2179) - Support the assignment of multiple port numbers for services
|
* [#2179](https://github.com/netbox-community/netbox/issues/2179) - Support the assignment of multiple port numbers for services
|
||||||
@ -23,6 +37,7 @@
|
|||||||
|
|
||||||
### REST API Changes
|
### REST API Changes
|
||||||
|
|
||||||
|
* Added support for `DELETE` operations on list endpoints
|
||||||
* dcim.Cable: Added `custom_fields`
|
* dcim.Cable: Added `custom_fields`
|
||||||
* dcim.InventoryItem: The `_depth` field has been added to reflect MPTT positioning
|
* dcim.InventoryItem: The `_depth` field has been added to reflect MPTT positioning
|
||||||
* dcim.PowerPanel: Added `custom_fields`
|
* dcim.PowerPanel: Added `custom_fields`
|
||||||
|
@ -468,16 +468,16 @@ http://netbox/api/dcim/sites/ \
|
|||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
### Modifying an Object
|
### Updating an Object
|
||||||
|
|
||||||
To modify an object which has already been created, make a `PATCH` request to the model's _detail_ endpoint specifying its unique numeric ID. Include any data which you wish to update on the object. As with object creation, the `Authorization` and `Content-Type` headers must also be specified.
|
To modify an object which has already been created, make a `PATCH` request to the model's _detail_ endpoint specifying its unique numeric ID. Include any data which you wish to update on the object. As with object creation, the `Authorization` and `Content-Type` headers must also be specified.
|
||||||
|
|
||||||
```no-highlight
|
```no-highlight
|
||||||
curl -s -X PATCH \
|
curl -s -X PATCH \
|
||||||
> -H "Authorization: Token $TOKEN" \
|
-H "Authorization: Token $TOKEN" \
|
||||||
> -H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
> http://netbox/api/ipam/prefixes/18691/ \
|
http://netbox/api/ipam/prefixes/18691/ \
|
||||||
> --data '{"status": "reserved"}' | jq '.'
|
--data '{"status": "reserved"}' | jq '.'
|
||||||
```
|
```
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@ -515,6 +515,23 @@ curl -s -X PATCH \
|
|||||||
!!! note "PUT versus PATCH"
|
!!! note "PUT versus PATCH"
|
||||||
The NetBox REST API support the use of either `PUT` or `PATCH` to modify an existing object. The difference is that a `PUT` request requires the user to specify a _complete_ representation of the object being modified, whereas a `PATCH` request need include only the attributes that are being updated. For most purposes, using `PATCH` is recommended.
|
The NetBox REST API support the use of either `PUT` or `PATCH` to modify an existing object. The difference is that a `PUT` request requires the user to specify a _complete_ representation of the object being modified, whereas a `PATCH` request need include only the attributes that are being updated. For most purposes, using `PATCH` is recommended.
|
||||||
|
|
||||||
|
### Updating Multiple Objects
|
||||||
|
|
||||||
|
Multiple objects can be updated simultaneously by issuing a `PUT` or `PATCH` request to a model's list endpoint with a list of dictionaries specifying the numeric ID of each object to be deleted and the attributes to be updated. For example, to update sites with IDs 10 and 11 to a status of "active", issue the following request:
|
||||||
|
|
||||||
|
```no-highlight
|
||||||
|
curl -s -X PATCH \
|
||||||
|
-H "Authorization: Token $TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
http://netbox/api/dcim/sites/ \
|
||||||
|
--data '[{"id": 10, "status": "active"}, {"id": 11, "status": "active"}]'
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that there is no requirement for the attributes to be identical among objects. For instance, it's possible to update the status of one site along with the name of another in the same request.
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
The bulk update of objects is an all-or-none operation, meaning that if NetBox fails to successfully update any of the specified objects (e.g. due a validation error), the entire operation will be aborted and none of the objects will be updated.
|
||||||
|
|
||||||
### Deleting an Object
|
### Deleting an Object
|
||||||
|
|
||||||
To delete an object from NetBox, make a `DELETE` request to the model's _detail_ endpoint specifying its unique numeric ID. The `Authorization` header must be included to specify an authorization token, however this type of request does not support passing any data in the body.
|
To delete an object from NetBox, make a `DELETE` request to the model's _detail_ endpoint specifying its unique numeric ID. The `Authorization` header must be included to specify an authorization token, however this type of request does not support passing any data in the body.
|
||||||
@ -537,6 +554,7 @@ NetBox supports the simultaneous deletion of multiple objects of the same type b
|
|||||||
```no-highlight
|
```no-highlight
|
||||||
curl -s -X DELETE \
|
curl -s -X DELETE \
|
||||||
-H "Authorization: Token $TOKEN" \
|
-H "Authorization: Token $TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
http://netbox/api/dcim/sites/ \
|
http://netbox/api/dcim/sites/ \
|
||||||
--data '[{"id": 10}, {"id": 11}, {"id": 12}]'
|
--data '[{"id": 10}, {"id": 11}, {"id": 12}]'
|
||||||
```
|
```
|
||||||
|
@ -32,6 +32,9 @@ class ProviderTest(APIViewTestCases.APIViewTestCase):
|
|||||||
'slug': 'provider-6',
|
'slug': 'provider-6',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
bulk_update_data = {
|
||||||
|
'asn': 1234,
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -61,6 +64,9 @@ class CircuitTypeTest(APIViewTestCases.APIViewTestCase):
|
|||||||
'slug': 'circuit-type-6',
|
'slug': 'circuit-type-6',
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -76,6 +82,9 @@ class CircuitTypeTest(APIViewTestCases.APIViewTestCase):
|
|||||||
class CircuitTest(APIViewTestCases.APIViewTestCase):
|
class CircuitTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = Circuit
|
model = Circuit
|
||||||
brief_fields = ['cid', 'id', 'url']
|
brief_fields = ['cid', 'id', 'url']
|
||||||
|
bulk_update_data = {
|
||||||
|
'status': 'planned',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
|
@ -80,6 +80,9 @@ class RegionTest(APIViewTestCases.APIViewTestCase):
|
|||||||
'slug': 'region-6',
|
'slug': 'region-6',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -92,6 +95,9 @@ class RegionTest(APIViewTestCases.APIViewTestCase):
|
|||||||
class SiteTest(APIViewTestCases.APIViewTestCase):
|
class SiteTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = Site
|
model = Site
|
||||||
brief_fields = ['id', 'name', 'slug', 'url']
|
brief_fields = ['id', 'name', 'slug', 'url']
|
||||||
|
bulk_update_data = {
|
||||||
|
'status': 'planned',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -133,6 +139,9 @@ class SiteTest(APIViewTestCases.APIViewTestCase):
|
|||||||
class RackGroupTest(APIViewTestCases.APIViewTestCase):
|
class RackGroupTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = RackGroup
|
model = RackGroup
|
||||||
brief_fields = ['_depth', 'id', 'name', 'rack_count', 'slug', 'url']
|
brief_fields = ['_depth', 'id', 'name', 'rack_count', 'slug', 'url']
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -194,6 +203,9 @@ class RackRoleTest(APIViewTestCases.APIViewTestCase):
|
|||||||
'color': 'ffff00',
|
'color': 'ffff00',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -209,6 +221,9 @@ class RackRoleTest(APIViewTestCases.APIViewTestCase):
|
|||||||
class RackTest(APIViewTestCases.APIViewTestCase):
|
class RackTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = Rack
|
model = Rack
|
||||||
brief_fields = ['device_count', 'display_name', 'id', 'name', 'url']
|
brief_fields = ['device_count', 'display_name', 'id', 'name', 'url']
|
||||||
|
bulk_update_data = {
|
||||||
|
'status': 'planned',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -294,6 +309,9 @@ class RackTest(APIViewTestCases.APIViewTestCase):
|
|||||||
class RackReservationTest(APIViewTestCases.APIViewTestCase):
|
class RackReservationTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = RackReservation
|
model = RackReservation
|
||||||
brief_fields = ['id', 'units', 'url', 'user']
|
brief_fields = ['id', 'units', 'url', 'user']
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -356,6 +374,9 @@ class ManufacturerTest(APIViewTestCases.APIViewTestCase):
|
|||||||
'slug': 'manufacturer-6',
|
'slug': 'manufacturer-6',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -371,6 +392,9 @@ class ManufacturerTest(APIViewTestCases.APIViewTestCase):
|
|||||||
class DeviceTypeTest(APIViewTestCases.APIViewTestCase):
|
class DeviceTypeTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = DeviceType
|
model = DeviceType
|
||||||
brief_fields = ['device_count', 'display_name', 'id', 'manufacturer', 'model', 'slug', 'url']
|
brief_fields = ['device_count', 'display_name', 'id', 'manufacturer', 'model', 'slug', 'url']
|
||||||
|
bulk_update_data = {
|
||||||
|
'part_number': 'ABC123',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -410,6 +434,9 @@ class DeviceTypeTest(APIViewTestCases.APIViewTestCase):
|
|||||||
class ConsolePortTemplateTest(APIViewTestCases.APIViewTestCase):
|
class ConsolePortTemplateTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = ConsolePortTemplate
|
model = ConsolePortTemplate
|
||||||
brief_fields = ['id', 'name', 'url']
|
brief_fields = ['id', 'name', 'url']
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -444,6 +471,9 @@ class ConsolePortTemplateTest(APIViewTestCases.APIViewTestCase):
|
|||||||
class ConsoleServerPortTemplateTest(APIViewTestCases.APIViewTestCase):
|
class ConsoleServerPortTemplateTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = ConsoleServerPortTemplate
|
model = ConsoleServerPortTemplate
|
||||||
brief_fields = ['id', 'name', 'url']
|
brief_fields = ['id', 'name', 'url']
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -478,6 +508,9 @@ class ConsoleServerPortTemplateTest(APIViewTestCases.APIViewTestCase):
|
|||||||
class PowerPortTemplateTest(APIViewTestCases.APIViewTestCase):
|
class PowerPortTemplateTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = PowerPortTemplate
|
model = PowerPortTemplate
|
||||||
brief_fields = ['id', 'name', 'url']
|
brief_fields = ['id', 'name', 'url']
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -512,6 +545,9 @@ class PowerPortTemplateTest(APIViewTestCases.APIViewTestCase):
|
|||||||
class PowerOutletTemplateTest(APIViewTestCases.APIViewTestCase):
|
class PowerOutletTemplateTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = PowerOutletTemplate
|
model = PowerOutletTemplate
|
||||||
brief_fields = ['id', 'name', 'url']
|
brief_fields = ['id', 'name', 'url']
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -546,6 +582,9 @@ class PowerOutletTemplateTest(APIViewTestCases.APIViewTestCase):
|
|||||||
class InterfaceTemplateTest(APIViewTestCases.APIViewTestCase):
|
class InterfaceTemplateTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = InterfaceTemplate
|
model = InterfaceTemplate
|
||||||
brief_fields = ['id', 'name', 'url']
|
brief_fields = ['id', 'name', 'url']
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -583,6 +622,9 @@ class InterfaceTemplateTest(APIViewTestCases.APIViewTestCase):
|
|||||||
class FrontPortTemplateTest(APIViewTestCases.APIViewTestCase):
|
class FrontPortTemplateTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = FrontPortTemplate
|
model = FrontPortTemplate
|
||||||
brief_fields = ['id', 'name', 'url']
|
brief_fields = ['id', 'name', 'url']
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -651,6 +693,9 @@ class FrontPortTemplateTest(APIViewTestCases.APIViewTestCase):
|
|||||||
class RearPortTemplateTest(APIViewTestCases.APIViewTestCase):
|
class RearPortTemplateTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = RearPortTemplate
|
model = RearPortTemplate
|
||||||
brief_fields = ['id', 'name', 'url']
|
brief_fields = ['id', 'name', 'url']
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -688,6 +733,9 @@ class RearPortTemplateTest(APIViewTestCases.APIViewTestCase):
|
|||||||
class DeviceBayTemplateTest(APIViewTestCases.APIViewTestCase):
|
class DeviceBayTemplateTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = DeviceBayTemplate
|
model = DeviceBayTemplate
|
||||||
brief_fields = ['id', 'name', 'url']
|
brief_fields = ['id', 'name', 'url']
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -739,6 +787,9 @@ class DeviceRoleTest(APIViewTestCases.APIViewTestCase):
|
|||||||
'color': 'ffff00',
|
'color': 'ffff00',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -768,6 +819,9 @@ class PlatformTest(APIViewTestCases.APIViewTestCase):
|
|||||||
'slug': 'platform-6',
|
'slug': 'platform-6',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -783,6 +837,9 @@ class PlatformTest(APIViewTestCases.APIViewTestCase):
|
|||||||
class DeviceTest(APIViewTestCases.APIViewTestCase):
|
class DeviceTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = Device
|
model = Device
|
||||||
brief_fields = ['display_name', 'id', 'name', 'url']
|
brief_fields = ['display_name', 'id', 'name', 'url']
|
||||||
|
bulk_update_data = {
|
||||||
|
'status': 'failed',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -921,6 +978,9 @@ class DeviceTest(APIViewTestCases.APIViewTestCase):
|
|||||||
class ConsolePortTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase):
|
class ConsolePortTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase):
|
||||||
model = ConsolePort
|
model = ConsolePort
|
||||||
brief_fields = ['cable', 'connection_status', 'device', 'id', 'name', 'url']
|
brief_fields = ['cable', 'connection_status', 'device', 'id', 'name', 'url']
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
peer_termination_type = ConsoleServerPort
|
peer_termination_type = ConsoleServerPort
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -957,6 +1017,9 @@ class ConsolePortTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCa
|
|||||||
class ConsoleServerPortTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase):
|
class ConsoleServerPortTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase):
|
||||||
model = ConsoleServerPort
|
model = ConsoleServerPort
|
||||||
brief_fields = ['cable', 'connection_status', 'device', 'id', 'name', 'url']
|
brief_fields = ['cable', 'connection_status', 'device', 'id', 'name', 'url']
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
peer_termination_type = ConsolePort
|
peer_termination_type = ConsolePort
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -993,6 +1056,9 @@ class ConsoleServerPortTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIView
|
|||||||
class PowerPortTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase):
|
class PowerPortTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase):
|
||||||
model = PowerPort
|
model = PowerPort
|
||||||
brief_fields = ['cable', 'connection_status', 'device', 'id', 'name', 'url']
|
brief_fields = ['cable', 'connection_status', 'device', 'id', 'name', 'url']
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
peer_termination_type = PowerOutlet
|
peer_termination_type = PowerOutlet
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -1029,6 +1095,9 @@ class PowerPortTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase
|
|||||||
class PowerOutletTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase):
|
class PowerOutletTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase):
|
||||||
model = PowerOutlet
|
model = PowerOutlet
|
||||||
brief_fields = ['cable', 'connection_status', 'device', 'id', 'name', 'url']
|
brief_fields = ['cable', 'connection_status', 'device', 'id', 'name', 'url']
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
peer_termination_type = PowerPort
|
peer_termination_type = PowerPort
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -1065,6 +1134,9 @@ class PowerOutletTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCa
|
|||||||
class InterfaceTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase):
|
class InterfaceTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase):
|
||||||
model = Interface
|
model = Interface
|
||||||
brief_fields = ['cable', 'connection_status', 'device', 'id', 'name', 'url']
|
brief_fields = ['cable', 'connection_status', 'device', 'id', 'name', 'url']
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
peer_termination_type = Interface
|
peer_termination_type = Interface
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -1120,6 +1192,9 @@ class InterfaceTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase
|
|||||||
class FrontPortTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase):
|
class FrontPortTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase):
|
||||||
model = FrontPort
|
model = FrontPort
|
||||||
brief_fields = ['cable', 'device', 'id', 'name', 'url']
|
brief_fields = ['cable', 'device', 'id', 'name', 'url']
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
peer_termination_type = Interface
|
peer_termination_type = Interface
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -1175,6 +1250,9 @@ class FrontPortTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase
|
|||||||
class RearPortTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase):
|
class RearPortTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase):
|
||||||
model = RearPort
|
model = RearPort
|
||||||
brief_fields = ['cable', 'device', 'id', 'name', 'url']
|
brief_fields = ['cable', 'device', 'id', 'name', 'url']
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
peer_termination_type = Interface
|
peer_termination_type = Interface
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -1214,6 +1292,9 @@ class RearPortTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase)
|
|||||||
class DeviceBayTest(APIViewTestCases.APIViewTestCase):
|
class DeviceBayTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = DeviceBay
|
model = DeviceBay
|
||||||
brief_fields = ['device', 'id', 'name', 'url']
|
brief_fields = ['device', 'id', 'name', 'url']
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -1274,6 +1355,9 @@ class DeviceBayTest(APIViewTestCases.APIViewTestCase):
|
|||||||
class InventoryItemTest(APIViewTestCases.APIViewTestCase):
|
class InventoryItemTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = InventoryItem
|
model = InventoryItem
|
||||||
brief_fields = ['_depth', 'device', 'id', 'name', 'url']
|
brief_fields = ['_depth', 'device', 'id', 'name', 'url']
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -1309,6 +1393,10 @@ class InventoryItemTest(APIViewTestCases.APIViewTestCase):
|
|||||||
class CableTest(APIViewTestCases.APIViewTestCase):
|
class CableTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = Cable
|
model = Cable
|
||||||
brief_fields = ['id', 'label', 'url']
|
brief_fields = ['id', 'label', 'url']
|
||||||
|
bulk_update_data = {
|
||||||
|
'length': 100,
|
||||||
|
'length_unit': 'm',
|
||||||
|
}
|
||||||
|
|
||||||
# TODO: Allow updating cable terminations
|
# TODO: Allow updating cable terminations
|
||||||
test_update_object = None
|
test_update_object = None
|
||||||
@ -1894,6 +1982,9 @@ class PowerPanelTest(APIViewTestCases.APIViewTestCase):
|
|||||||
class PowerFeedTest(APIViewTestCases.APIViewTestCase):
|
class PowerFeedTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = PowerFeed
|
model = PowerFeed
|
||||||
brief_fields = ['cable', 'id', 'name', 'url']
|
brief_fields = ['cable', 'id', 'name', 'url']
|
||||||
|
bulk_update_data = {
|
||||||
|
'status': 'planned',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
|
@ -49,6 +49,9 @@ class ExportTemplateTest(APIViewTestCases.APIViewTestCase):
|
|||||||
'template_code': '{% for obj in queryset %}{{ obj.name }}\n{% endfor %}',
|
'template_code': '{% for obj in queryset %}{{ obj.name }}\n{% endfor %}',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -91,6 +94,9 @@ class TagTest(APIViewTestCases.APIViewTestCase):
|
|||||||
'slug': 'tag-6',
|
'slug': 'tag-6',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -164,6 +170,9 @@ class ConfigContextTest(APIViewTestCases.APIViewTestCase):
|
|||||||
'data': {'more_baz': None},
|
'data': {'more_baz': None},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
|
@ -37,6 +37,9 @@ class VRFTest(APIViewTestCases.APIViewTestCase):
|
|||||||
'rd': '65000:6',
|
'rd': '65000:6',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -66,6 +69,9 @@ class RIRTest(APIViewTestCases.APIViewTestCase):
|
|||||||
'slug': 'rir-6',
|
'slug': 'rir-6',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -81,6 +87,9 @@ class RIRTest(APIViewTestCases.APIViewTestCase):
|
|||||||
class AggregateTest(APIViewTestCases.APIViewTestCase):
|
class AggregateTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = Aggregate
|
model = Aggregate
|
||||||
brief_fields = ['family', 'id', 'prefix', 'url']
|
brief_fields = ['family', 'id', 'prefix', 'url']
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -131,6 +140,9 @@ class RoleTest(APIViewTestCases.APIViewTestCase):
|
|||||||
'slug': 'role-6',
|
'slug': 'role-6',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -157,6 +169,9 @@ class PrefixTest(APIViewTestCases.APIViewTestCase):
|
|||||||
'prefix': '192.168.6.0/24',
|
'prefix': '192.168.6.0/24',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -328,6 +343,9 @@ class IPAddressTest(APIViewTestCases.APIViewTestCase):
|
|||||||
'address': '192.168.0.6/24',
|
'address': '192.168.0.6/24',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -357,6 +375,9 @@ class VLANGroupTest(APIViewTestCases.APIViewTestCase):
|
|||||||
'slug': 'vlan-group-6',
|
'slug': 'vlan-group-6',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -372,6 +393,9 @@ class VLANGroupTest(APIViewTestCases.APIViewTestCase):
|
|||||||
class VLANTest(APIViewTestCases.APIViewTestCase):
|
class VLANTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = VLAN
|
model = VLAN
|
||||||
brief_fields = ['display_name', 'id', 'name', 'url', 'vid']
|
brief_fields = ['display_name', 'id', 'name', 'url', 'vid']
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -429,6 +453,9 @@ class VLANTest(APIViewTestCases.APIViewTestCase):
|
|||||||
class ServiceTest(APIViewTestCases.APIViewTestCase):
|
class ServiceTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = Service
|
model = Service
|
||||||
brief_fields = ['id', 'name', 'ports', 'protocol', 'url']
|
brief_fields = ['id', 'name', 'ports', 'protocol', 'url']
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
|
@ -12,7 +12,10 @@ from users.models import Token
|
|||||||
|
|
||||||
def is_custom_action(action):
|
def is_custom_action(action):
|
||||||
return action not in {
|
return action not in {
|
||||||
'retrieve', 'list', 'create', 'update', 'partial_update', 'destroy', 'bulk_destroy'
|
# Default actions
|
||||||
|
'retrieve', 'list', 'create', 'update', 'partial_update', 'destroy',
|
||||||
|
# Bulk operations
|
||||||
|
'bulk_update', 'bulk_partial_update', 'bulk_destroy',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,6 +17,9 @@ class AppTest(APITestCase):
|
|||||||
class TenantGroupTest(APIViewTestCases.APIViewTestCase):
|
class TenantGroupTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = TenantGroup
|
model = TenantGroup
|
||||||
brief_fields = ['_depth', 'id', 'name', 'slug', 'tenant_count', 'url']
|
brief_fields = ['_depth', 'id', 'name', 'slug', 'tenant_count', 'url']
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -52,6 +55,9 @@ class TenantGroupTest(APIViewTestCases.APIViewTestCase):
|
|||||||
class TenantTest(APIViewTestCases.APIViewTestCase):
|
class TenantTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = Tenant
|
model = Tenant
|
||||||
brief_fields = ['id', 'name', 'slug', 'url']
|
brief_fields = ['id', 'name', 'slug', 'url']
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
|
@ -291,7 +291,7 @@ class WritableNestedSerializer(serializers.ModelSerializer):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class BulkDeleteSerializer(serializers.Serializer):
|
class BulkOperationSerializer(serializers.Serializer):
|
||||||
id = serializers.IntegerField()
|
id = serializers.IntegerField()
|
||||||
|
|
||||||
|
|
||||||
@ -299,6 +299,54 @@ class BulkDeleteSerializer(serializers.Serializer):
|
|||||||
# Mixins
|
# Mixins
|
||||||
#
|
#
|
||||||
|
|
||||||
|
class BulkUpdateModelMixin:
|
||||||
|
"""
|
||||||
|
Support bulk modification of objects using the list endpoint for a model. Accepts a PATCH action with a list of one
|
||||||
|
or more JSON objects, each specifying the numeric ID of an object to be updated as well as the attributes to be set.
|
||||||
|
For example:
|
||||||
|
|
||||||
|
PATCH /api/dcim/sites/
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 123,
|
||||||
|
"name": "New name"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 456,
|
||||||
|
"status": "planned"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
def bulk_update(self, request, *args, **kwargs):
|
||||||
|
partial = kwargs.pop('partial', False)
|
||||||
|
serializer = BulkOperationSerializer(data=request.data, many=True)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
qs = self.get_queryset().filter(
|
||||||
|
pk__in=[o['id'] for o in serializer.data]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Map update data by object ID
|
||||||
|
update_data = {
|
||||||
|
obj.pop('id'): obj for obj in request.data
|
||||||
|
}
|
||||||
|
|
||||||
|
self.perform_bulk_update(qs, update_data, partial=partial)
|
||||||
|
|
||||||
|
return Response(status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
def perform_bulk_update(self, objects, update_data, partial):
|
||||||
|
with transaction.atomic():
|
||||||
|
for obj in objects:
|
||||||
|
data = update_data.get(obj.id)
|
||||||
|
serializer = self.get_serializer(obj, data=data, partial=partial)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
self.perform_update(serializer)
|
||||||
|
|
||||||
|
def bulk_partial_update(self, request, *args, **kwargs):
|
||||||
|
kwargs['partial'] = True
|
||||||
|
return self.bulk_update(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class BulkDestroyModelMixin:
|
class BulkDestroyModelMixin:
|
||||||
"""
|
"""
|
||||||
Support bulk deletion of objects using the list endpoint for a model. Accepts a DELETE action with a list of one
|
Support bulk deletion of objects using the list endpoint for a model. Accepts a DELETE action with a list of one
|
||||||
@ -310,12 +358,12 @@ class BulkDestroyModelMixin:
|
|||||||
{"id": 456}
|
{"id": 456}
|
||||||
]
|
]
|
||||||
"""
|
"""
|
||||||
def bulk_destroy(self, request):
|
def bulk_destroy(self, request, *args, **kwargs):
|
||||||
serializer = BulkDeleteSerializer(data=request.data, many=True)
|
serializer = BulkOperationSerializer(data=request.data, many=True)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
|
qs = self.get_queryset().filter(
|
||||||
pk_list = [o['id'] for o in serializer.data]
|
pk__in=[o['id'] for o in serializer.data]
|
||||||
qs = self.get_queryset().filter(pk__in=pk_list)
|
)
|
||||||
|
|
||||||
self.perform_bulk_destroy(qs)
|
self.perform_bulk_destroy(qs)
|
||||||
|
|
||||||
@ -336,6 +384,7 @@ class ModelViewSet(mixins.CreateModelMixin,
|
|||||||
mixins.UpdateModelMixin,
|
mixins.UpdateModelMixin,
|
||||||
mixins.DestroyModelMixin,
|
mixins.DestroyModelMixin,
|
||||||
mixins.ListModelMixin,
|
mixins.ListModelMixin,
|
||||||
|
BulkUpdateModelMixin,
|
||||||
BulkDestroyModelMixin,
|
BulkDestroyModelMixin,
|
||||||
GenericViewSet):
|
GenericViewSet):
|
||||||
"""
|
"""
|
||||||
@ -455,6 +504,8 @@ class OrderedDefaultRouter(DefaultRouter):
|
|||||||
|
|
||||||
# Extend the list view mappings to support the DELETE operation
|
# Extend the list view mappings to support the DELETE operation
|
||||||
self.routes[0].mapping.update({
|
self.routes[0].mapping.update({
|
||||||
|
'put': 'bulk_update',
|
||||||
|
'patch': 'bulk_partial_update',
|
||||||
'delete': 'bulk_destroy',
|
'delete': 'bulk_destroy',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -234,6 +234,7 @@ class APIViewTestCases:
|
|||||||
|
|
||||||
class UpdateObjectViewTestCase(APITestCase):
|
class UpdateObjectViewTestCase(APITestCase):
|
||||||
update_data = {}
|
update_data = {}
|
||||||
|
bulk_update_data = None
|
||||||
|
|
||||||
def test_update_object_without_permission(self):
|
def test_update_object_without_permission(self):
|
||||||
"""
|
"""
|
||||||
@ -268,6 +269,32 @@ class APIViewTestCases:
|
|||||||
instance.refresh_from_db()
|
instance.refresh_from_db()
|
||||||
self.assertInstanceEqual(instance, self.update_data, api=True)
|
self.assertInstanceEqual(instance, self.update_data, api=True)
|
||||||
|
|
||||||
|
def test_bulk_update_objects(self):
|
||||||
|
"""
|
||||||
|
PATCH a set of objects in a single request.
|
||||||
|
"""
|
||||||
|
if self.bulk_update_data is None:
|
||||||
|
self.skipTest("Bulk update data not set")
|
||||||
|
|
||||||
|
# Add object-level permission
|
||||||
|
obj_perm = ObjectPermission(
|
||||||
|
actions=['change']
|
||||||
|
)
|
||||||
|
obj_perm.save()
|
||||||
|
obj_perm.users.add(self.user)
|
||||||
|
obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
|
||||||
|
|
||||||
|
id_list = self._get_queryset().values_list('id', flat=True)[:3]
|
||||||
|
self.assertEqual(len(id_list), 3, "Insufficient number of objects to test bulk update")
|
||||||
|
data = [
|
||||||
|
{'id': id, **self.bulk_update_data} for id in id_list
|
||||||
|
]
|
||||||
|
|
||||||
|
response = self.client.patch(self._get_list_url(), data, format='json', **self.header)
|
||||||
|
self.assertHttpStatus(response, status.HTTP_200_OK)
|
||||||
|
for instance in self._get_queryset().filter(pk__in=id_list):
|
||||||
|
self.assertInstanceEqual(instance, self.bulk_update_data, api=True)
|
||||||
|
|
||||||
class DeleteObjectViewTestCase(APITestCase):
|
class DeleteObjectViewTestCase(APITestCase):
|
||||||
|
|
||||||
def test_delete_object_without_permission(self):
|
def test_delete_object_without_permission(self):
|
||||||
|
@ -34,6 +34,9 @@ class ClusterTypeTest(APIViewTestCases.APIViewTestCase):
|
|||||||
'slug': 'cluster-type-6',
|
'slug': 'cluster-type-6',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -63,6 +66,9 @@ class ClusterGroupTest(APIViewTestCases.APIViewTestCase):
|
|||||||
'slug': 'cluster-type-6',
|
'slug': 'cluster-type-6',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -123,6 +129,9 @@ class ClusterTest(APIViewTestCases.APIViewTestCase):
|
|||||||
class VirtualMachineTest(APIViewTestCases.APIViewTestCase):
|
class VirtualMachineTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = VirtualMachine
|
model = VirtualMachine
|
||||||
brief_fields = ['id', 'name', 'url']
|
brief_fields = ['id', 'name', 'url']
|
||||||
|
bulk_update_data = {
|
||||||
|
'status': 'staged',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -196,6 +205,9 @@ class VirtualMachineTest(APIViewTestCases.APIViewTestCase):
|
|||||||
class VMInterfaceTest(APIViewTestCases.APIViewTestCase):
|
class VMInterfaceTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = VMInterface
|
model = VMInterface
|
||||||
brief_fields = ['id', 'name', 'url', 'virtual_machine']
|
brief_fields = ['id', 'name', 'url', 'virtual_machine']
|
||||||
|
bulk_update_data = {
|
||||||
|
'description': 'New description',
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
|
Loading…
Reference in New Issue
Block a user