Skip to content

Commit

Permalink
Merge pull request #130 from inventree/bulk-delete
Browse files Browse the repository at this point in the history
Add support for bulk delete operations
  • Loading branch information
SchrodingersGat authored Jul 30, 2022
2 parents 33bfe74 + 7793089 commit 44ea1fb
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 23 deletions.
53 changes: 51 additions & 2 deletions inventree/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import logging
import json

from . import api as inventree_api


INVENTREE_PYTHON_VERSION = "0.8.1"

Expand Down Expand Up @@ -273,11 +275,58 @@ def __setitem__(self, name, value):
raise KeyError(f"Key '{name}' does not exist in dataset")


class Attachment(InventreeObject):
class BulkDeleteMixin:
"""Mixin class for models which support 'bulk deletion'
- Perform a DELETE operation against the LIST endpoint for the model
- Provide a list of items to be deleted, or filters to apply
Requires API version 58
"""

@classmethod
def bulkDelete(cls, api: inventree_api.InvenTreeAPI, items=None, filters=None):
"""Perform bulk delete operation
Arguments:
api: InventreeAPI instance
items: Optional list of items (pk values) to be deleted
filters: Optional query filters to delete
Returns:
API response object
Throws:
NotImplementError: The server API version is too old (requires v58)
ValueError: Neither items or filters are supplied
"""

if api.api_version < 58:
raise NotImplementedError("bulkDelete requires API version 58 or newer")

if not items and not filters:
raise ValueError("Must supply either 'items' or 'filters' argument")

data = {}

if items:
data['items'] = items

if filters:
data['filters'] = filters

return api.delete(
cls.URL,
json=data,
)


class Attachment(BulkDeleteMixin, InventreeObject):
"""
Class representing a file attachment object
Multiple sub-classes exist, representing various types of attachment models in the database
Multiple sub-classes exist, representing various types of attachment models in the database.
"""

# List of required kwargs required for the particular subclass
Expand Down
21 changes: 13 additions & 8 deletions inventree/company.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,11 @@ def createSalesOrder(self, **kwargs):
)


class SupplierPart(inventree.base.InventreeObject):
""" Class representing the SupplierPart database model """
class SupplierPart(inventree.base.BulkDeleteMixin, inventree.base.InventreeObject):
"""Class representing the SupplierPart database model
- Implements the BulkDeleteMixin
"""

URL = 'company/part'

Expand All @@ -74,9 +77,10 @@ def getPriceBreaks(self):
return SupplierPriceBreak.list(self._api, part=self.pk)


class ManufacturerPart(inventree.base.InventreeObject):
"""
Class representing the ManufacturerPart database model
class ManufacturerPart(inventree.base.BulkDeleteMixin, inventree.base.InventreeObject):
"""Class representing the ManufacturerPart database model
- Implements the BulkDeleteMixin
"""

URL = 'company/part/manufacturer'
Expand All @@ -102,9 +106,10 @@ def uploadAttachment(self, attachment, comment=''):
)


class ManufacturerPartParameter(inventree.base.InventreeObject):
"""
Class representing the ManufacturerPartParameter database model.
class ManufacturerPartParameter(inventree.base.BulkDeleteMixin, inventree.base.InventreeObject):
"""Class representing the ManufacturerPartParameter database model.
- Implements the BulkDeleteMixin
"""

URL = 'company/part/manufacturer/parameter'
Expand Down
17 changes: 4 additions & 13 deletions inventree/stock.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,8 @@ def getChildLocations(self, **kwargs):
return StockLocation.list(self._api, parent=self.pk, **kwargs)


class StockItem(inventree.base.MetadataMixin, inventree.base.InventreeObject):
""" Class representing the StockItem database model.
Stock items can be filtered by:
- location: Where the stock item is stored
- category: The category of the part this stock item points to
- supplier: Who supplied this stock item
- part: The part referenced by this stock item
- supplier_part: Matching SupplierPart object
"""
class StockItem(inventree.base.BulkDeleteMixin, inventree.base.MetadataMixin, inventree.base.InventreeObject):
"""Class representing the StockItem database model."""

URL = 'stock'

Expand Down Expand Up @@ -256,8 +247,8 @@ class StockItemTracking(inventree.base.InventreeObject):
URL = 'stock/track'


class StockItemTestResult(inventree.base.InventreeObject):
""" Class representing a StockItemTestResult object """
class StockItemTestResult(inventree.base.BulkDeleteMixin, inventree.base.InventreeObject):
"""Class representing a StockItemTestResult object"""

URL = 'stock/test'

Expand Down
44 changes: 44 additions & 0 deletions test/test_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,47 @@ def test_build_attachment(self):
self.assertEqual(response['comment'], 'A self referencing upload!')

self.assertEqual(len(build.getAttachments()), n + 1)

def test_build_attachment_bulk_delete(self):
"""Test 'bulk delete' operation for the BuildAttachment class"""

build = self.get_build()

n = len(BuildAttachment.list(self.api, build=build.pk))

fn = os.path.join(os.path.dirname(__file__), 'test_build.py')

pk_values = []

# Create a number of new attachments
for i in range(10):
response = build.uploadAttachment(fn, comment=f"Build attachment {i}")
pk_values.append(response['pk'])

self.assertEqual(len(BuildAttachment.list(self.api, build=build.pk)), n + 10)

# Call without providing required arguments
with self.assertRaises(ValueError):
BuildAttachment.bulkDelete(self.api)

BuildAttachment.bulkDelete(self.api, items=pk_values)

# The number of attachments has been reduced to the original value
self.assertEqual(len(BuildAttachment.list(self.api, build=build.pk)), n)

# Now, delete using the 'filters' argument
for i in range(99, 109):
response = build.uploadAttachment(fn, comment=f"Build attachment {i}")
pk_values.append(response['pk'])

self.assertEqual(len(BuildAttachment.list(self.api, build=build.pk)), n + 10)

response = BuildAttachment.bulkDelete(
self.api,
filters={
"build": build.pk,
}
)

# All attachments for this Build should have been deleted
self.assertEqual(len(BuildAttachment.list(self.api, build=build.pk)), 0)
22 changes: 22 additions & 0 deletions test/test_stock.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,28 @@ def test_get_stock_item(self):
self.assertEqual(location.pk, 3)
self.assertEqual(location.name, "Dining Room")

def test_bulk_delete(self):
"""Test bulk deletion of stock items"""

# Add some items to location 3
for i in range(10):
StockItem.create(self.api, {
'location': 3,
'part': 1,
'quantity': i + 50,
})

self.assertTrue(len(StockItem.list(self.api, location=3)) >= 10)

# Delete *all* items from location 3
StockItem.bulkDelete(self.api, filters={
'location': 3
})

loc = StockLocation(self.api, pk=3)
items = loc.getStockItems()
self.assertEqual(len(items), 0)


class StockAdjustTest(InvenTreeTestCase):
"""Unit tests for stock 'adjustment' actions"""
Expand Down

0 comments on commit 44ea1fb

Please sign in to comment.