mirror of
https://github.com/inventree/InvenTree.git
synced 2026-05-28 03:49:20 +00:00
Tree delete API (#11979)
* Add API serializer for deleting a location * Add serializer for part category delete * Bump API version * Fix unit tests --------- Co-authored-by: Matthias Mair <code@mjmair.com>
This commit is contained in:
@@ -1,11 +1,15 @@
|
|||||||
"""InvenTree API version information."""
|
"""InvenTree API version information."""
|
||||||
|
|
||||||
# InvenTree API version
|
# InvenTree API version
|
||||||
INVENTREE_API_VERSION = 490
|
INVENTREE_API_VERSION = 491
|
||||||
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
||||||
|
|
||||||
INVENTREE_API_TEXT = """
|
INVENTREE_API_TEXT = """
|
||||||
|
|
||||||
|
v491 -> 2026-05-21 : https://github.com/inventree/InvenTree/pull/11979
|
||||||
|
- Add API serializer for deleting a part category
|
||||||
|
- Add API serializer for deleting a stock location
|
||||||
|
|
||||||
v490 -> 2026-05-19 : https://github.com/inventree/InvenTree/pull/11963
|
v490 -> 2026-05-19 : https://github.com/inventree/InvenTree/pull/11963
|
||||||
- moves user-self-filtered endpoints to /user/me/ to make their security boundaries clearer
|
- moves user-self-filtered endpoints to /user/me/ to make their security boundaries clearer
|
||||||
|
|
||||||
|
|||||||
@@ -264,9 +264,12 @@ class CategoryDetail(CategoryMixin, OutputOptionsMixin, CustomRetrieveUpdateDest
|
|||||||
|
|
||||||
def destroy(self, request, *args, **kwargs):
|
def destroy(self, request, *args, **kwargs):
|
||||||
"""Delete a Part category instance via the API."""
|
"""Delete a Part category instance via the API."""
|
||||||
delete_parts = str2bool(request.data.get('delete_parts', False))
|
serializer = part_serializers.CategoryDeleteSerializer(data=request.data)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
|
delete_parts = str2bool(serializer.validated_data.get('delete_parts', False))
|
||||||
delete_child_categories = str2bool(
|
delete_child_categories = str2bool(
|
||||||
request.data.get('delete_child_categories', False)
|
serializer.validated_data.get('delete_child_categories', False)
|
||||||
)
|
)
|
||||||
|
|
||||||
return super().destroy(
|
return super().destroy(
|
||||||
|
|||||||
@@ -54,6 +54,27 @@ from .models import (
|
|||||||
logger = structlog.get_logger('inventree')
|
logger = structlog.get_logger('inventree')
|
||||||
|
|
||||||
|
|
||||||
|
class CategoryDeleteSerializer(serializers.Serializer):
|
||||||
|
"""Serializer for deleting a PartCategory instance."""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Metaclass options."""
|
||||||
|
|
||||||
|
fields = ['delete_child_categories', 'delete_parts']
|
||||||
|
|
||||||
|
delete_child_categories = serializers.BooleanField(
|
||||||
|
label=_('Delete Subcategories'),
|
||||||
|
help_text=_('Delete all sub-categories contained within this category'),
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
delete_parts = serializers.BooleanField(
|
||||||
|
label=_('Delete Parts'),
|
||||||
|
help_text=_('Delete all parts contained within this category'),
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@register_importer()
|
@register_importer()
|
||||||
class CategorySerializer(
|
class CategorySerializer(
|
||||||
InvenTree.serializers.FilterableSerializerMixin,
|
InvenTree.serializers.FilterableSerializerMixin,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
import os
|
import os
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from enum import IntEnum
|
|
||||||
from random import randint
|
from random import randint
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
@@ -331,116 +330,94 @@ class PartCategoryAPITest(InvenTreeAPITestCase):
|
|||||||
|
|
||||||
def test_category_delete(self):
|
def test_category_delete(self):
|
||||||
"""Test category deletion with different parameters."""
|
"""Test category deletion with different parameters."""
|
||||||
|
for delete_child_categories in [False, True]:
|
||||||
class Target(IntEnum):
|
for delete_parts in [False, True]:
|
||||||
move_subcategories_to_parent_move_parts_to_parent = (0,)
|
# Create a parent category
|
||||||
move_subcategories_to_parent_delete_parts = (1,)
|
parent_category = PartCategory.objects.create(
|
||||||
delete_subcategories_move_parts_to_parent = (2,)
|
name='Parent category',
|
||||||
delete_subcategories_delete_parts = (3,)
|
description='This is the parent category where the child categories and parts are moved to',
|
||||||
|
parent=None,
|
||||||
for i in range(4):
|
|
||||||
delete_child_categories: bool = False
|
|
||||||
delete_parts: bool = False
|
|
||||||
|
|
||||||
if i in (
|
|
||||||
Target.move_subcategories_to_parent_delete_parts,
|
|
||||||
Target.delete_subcategories_delete_parts,
|
|
||||||
):
|
|
||||||
delete_parts = True
|
|
||||||
if i in (
|
|
||||||
Target.delete_subcategories_move_parts_to_parent,
|
|
||||||
Target.delete_subcategories_delete_parts,
|
|
||||||
):
|
|
||||||
delete_child_categories = True
|
|
||||||
|
|
||||||
# Create a parent category
|
|
||||||
parent_category = PartCategory.objects.create(
|
|
||||||
name='Parent category',
|
|
||||||
description='This is the parent category where the child categories and parts are moved to',
|
|
||||||
parent=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
category_count_before = PartCategory.objects.count()
|
|
||||||
part_count_before = Part.objects.count()
|
|
||||||
|
|
||||||
# Create a category to delete
|
|
||||||
cat_to_delete = PartCategory.objects.create(
|
|
||||||
name='Category to delete',
|
|
||||||
description='This is the category to be deleted',
|
|
||||||
parent=parent_category,
|
|
||||||
)
|
|
||||||
|
|
||||||
url = reverse('api-part-category-detail', kwargs={'pk': cat_to_delete.id})
|
|
||||||
|
|
||||||
parts = []
|
|
||||||
# Create parts in the category to be deleted
|
|
||||||
for jj in range(3):
|
|
||||||
parts.append(
|
|
||||||
Part.objects.create(
|
|
||||||
name=f'Part xyz {i}_{jj}',
|
|
||||||
description='Child part of the deleted category',
|
|
||||||
category=cat_to_delete,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
child_categories = []
|
category_count_before = PartCategory.objects.count()
|
||||||
child_categories_parts = []
|
|
||||||
# Create child categories under the category to be deleted
|
|
||||||
for ii in range(3):
|
|
||||||
child = PartCategory.objects.create(
|
|
||||||
name=f'Child parent_cat {i}_{ii}',
|
|
||||||
description='A child category of the deleted category',
|
|
||||||
parent=cat_to_delete,
|
|
||||||
)
|
|
||||||
child_categories.append(child)
|
|
||||||
|
|
||||||
# Create parts in the child categories
|
# Create a category to delete
|
||||||
|
cat_to_delete = PartCategory.objects.create(
|
||||||
|
name='Category to delete',
|
||||||
|
description='This is the category to be deleted',
|
||||||
|
parent=parent_category,
|
||||||
|
)
|
||||||
|
|
||||||
|
url = reverse(
|
||||||
|
'api-part-category-detail', kwargs={'pk': cat_to_delete.id}
|
||||||
|
)
|
||||||
|
|
||||||
|
parts = []
|
||||||
|
# Create parts in the category to be deleted
|
||||||
for jj in range(3):
|
for jj in range(3):
|
||||||
child_categories_parts.append(
|
parts.append(
|
||||||
Part.objects.create(
|
Part.objects.create(
|
||||||
name=f'Part xyz {i}_{jj}_{ii}',
|
name=f'Part {"A" if delete_child_categories else "B"}{"C" if delete_parts else "D"}-{jj}',
|
||||||
description='Child part in the child category of the deleted category',
|
description='Child part of the deleted category',
|
||||||
category=child,
|
category=cat_to_delete,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Delete the created category (sub categories and their parts will be moved under the parent)
|
child_categories = []
|
||||||
params = {}
|
child_categories_parts = []
|
||||||
if delete_parts:
|
# Create child categories under the category to be deleted
|
||||||
params['delete_parts'] = '1'
|
for ii in range(3):
|
||||||
if delete_child_categories:
|
child = PartCategory.objects.create(
|
||||||
params['delete_child_categories'] = '1'
|
name=f'Child parent_cat {ii}',
|
||||||
self.delete(url, params, expected_code=204)
|
description='A child category of the deleted category',
|
||||||
|
parent=cat_to_delete,
|
||||||
if delete_parts:
|
|
||||||
if i == Target.delete_subcategories_delete_parts:
|
|
||||||
# Check if all parts deleted
|
|
||||||
self.assertEqual(Part.objects.count(), part_count_before)
|
|
||||||
elif i == Target.move_subcategories_to_parent_delete_parts:
|
|
||||||
# Check if all parts deleted
|
|
||||||
self.assertEqual(
|
|
||||||
Part.objects.count(),
|
|
||||||
part_count_before + len(child_categories_parts),
|
|
||||||
)
|
)
|
||||||
else:
|
child_categories.append(child)
|
||||||
# parts moved to the parent category
|
|
||||||
for part in parts:
|
|
||||||
part.refresh_from_db()
|
|
||||||
self.assertEqual(part.category, parent_category)
|
|
||||||
|
|
||||||
if delete_child_categories:
|
# Create parts in the child categories
|
||||||
for part in child_categories_parts:
|
for jj in range(3):
|
||||||
|
child_categories_parts.append(
|
||||||
|
Part.objects.create(
|
||||||
|
name=f'Part xyz {jj}_{ii}-{"E" if delete_child_categories else "F"}{"G" if delete_parts else "H"}',
|
||||||
|
description='Child part in the child category of the deleted category',
|
||||||
|
category=child,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Delete the created category (sub categories and their parts will be moved under the parent)
|
||||||
|
params = {
|
||||||
|
'delete_parts': delete_parts,
|
||||||
|
'delete_child_categories': delete_child_categories,
|
||||||
|
}
|
||||||
|
|
||||||
|
self.delete(url, params, expected_code=204)
|
||||||
|
|
||||||
|
if delete_parts:
|
||||||
|
# Check if all parts deleted
|
||||||
|
for p in parts:
|
||||||
|
with self.assertRaises(Part.DoesNotExist):
|
||||||
|
p.refresh_from_db()
|
||||||
|
else:
|
||||||
|
# parts moved to the parent category
|
||||||
|
for part in parts:
|
||||||
part.refresh_from_db()
|
part.refresh_from_db()
|
||||||
self.assertEqual(part.category, parent_category)
|
self.assertEqual(part.category, parent_category)
|
||||||
|
|
||||||
if delete_child_categories:
|
if delete_child_categories:
|
||||||
# Check if all categories are deleted
|
for part in child_categories_parts:
|
||||||
self.assertEqual(PartCategory.objects.count(), category_count_before)
|
part.refresh_from_db()
|
||||||
else:
|
self.assertEqual(part.category, parent_category)
|
||||||
# Check if all subcategories to parent moved to parent and all parts deleted
|
|
||||||
for child in child_categories:
|
if delete_child_categories:
|
||||||
child.refresh_from_db()
|
# Check if all categories are deleted
|
||||||
self.assertEqual(child.parent, parent_category)
|
self.assertEqual(
|
||||||
|
PartCategory.objects.count(), category_count_before
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Check if all subcategories to parent moved to parent and all parts deleted
|
||||||
|
for child in child_categories:
|
||||||
|
child.refresh_from_db()
|
||||||
|
self.assertEqual(child.parent, parent_category)
|
||||||
|
|
||||||
def test_structural(self):
|
def test_structural(self):
|
||||||
"""Test the effectiveness of structural categories.
|
"""Test the effectiveness of structural categories.
|
||||||
|
|||||||
@@ -430,11 +430,15 @@ class StockLocationDetail(
|
|||||||
|
|
||||||
def destroy(self, request, *args, **kwargs):
|
def destroy(self, request, *args, **kwargs):
|
||||||
"""Delete a Stock location instance via the API."""
|
"""Delete a Stock location instance via the API."""
|
||||||
|
serializer = StockSerializers.LocationDeleteSerializer(data=request.data)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
delete_stock_items = InvenTree.helpers.str2bool(
|
delete_stock_items = InvenTree.helpers.str2bool(
|
||||||
request.data.get('delete_stock_items', False)
|
serializer.validated_data.get('delete_stock_items', False)
|
||||||
)
|
)
|
||||||
|
|
||||||
delete_sub_locations = InvenTree.helpers.str2bool(
|
delete_sub_locations = InvenTree.helpers.str2bool(
|
||||||
request.data.get('delete_sub_locations', False)
|
serializer.validated_data.get('delete_sub_locations', False)
|
||||||
)
|
)
|
||||||
|
|
||||||
return super().destroy(
|
return super().destroy(
|
||||||
|
|||||||
@@ -1167,6 +1167,27 @@ class LocationTreeSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
|||||||
return queryset.annotate(sublocations=stock.filters.annotate_sub_locations())
|
return queryset.annotate(sublocations=stock.filters.annotate_sub_locations())
|
||||||
|
|
||||||
|
|
||||||
|
class LocationDeleteSerializer(serializers.Serializer):
|
||||||
|
"""Serializer for deleting a stock location."""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Metaclass options."""
|
||||||
|
|
||||||
|
fields = ['delete_stock_items', 'delete_sub_locations']
|
||||||
|
|
||||||
|
delete_stock_items = serializers.BooleanField(
|
||||||
|
required=True,
|
||||||
|
label=_('Delete Stock Items'),
|
||||||
|
help_text=_('Delete all stock items contained within this location'),
|
||||||
|
)
|
||||||
|
|
||||||
|
delete_sub_locations = serializers.BooleanField(
|
||||||
|
required=True,
|
||||||
|
label=_('Delete Sublocations'),
|
||||||
|
help_text=_('Delete all sub-locations contained within this location'),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@register_importer()
|
@register_importer()
|
||||||
class LocationSerializer(
|
class LocationSerializer(
|
||||||
InvenTree.serializers.FilterableSerializerMixin,
|
InvenTree.serializers.FilterableSerializerMixin,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from enum import IntEnum
|
|
||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
@@ -142,13 +141,6 @@ class StockLocationTest(StockAPITestCase):
|
|||||||
|
|
||||||
def test_stock_location_delete(self):
|
def test_stock_location_delete(self):
|
||||||
"""Test stock location deletion with different parameters."""
|
"""Test stock location deletion with different parameters."""
|
||||||
|
|
||||||
class Target(IntEnum):
|
|
||||||
move_sub_locations_to_parent_move_stockitems_to_parent = (0,)
|
|
||||||
move_sub_locations_to_parent_delete_stockitems = (1,)
|
|
||||||
delete_sub_locations_move_stockitems_to_parent = (2,)
|
|
||||||
delete_sub_locations_delete_stockitems = (3,)
|
|
||||||
|
|
||||||
# First, construct a set of template / variant parts
|
# First, construct a set of template / variant parts
|
||||||
part = Part.objects.create(
|
part = Part.objects.create(
|
||||||
name='Part for stock item creation',
|
name='Part for stock item creation',
|
||||||
@@ -157,117 +149,95 @@ class StockLocationTest(StockAPITestCase):
|
|||||||
is_template=False,
|
is_template=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
for i in range(4):
|
for delete_sub_locations in [False, True]:
|
||||||
delete_sub_locations: bool = False
|
for delete_stock_items in [False, True]:
|
||||||
delete_stock_items: bool = False
|
# Create a parent stock location
|
||||||
|
parent_stock_location = StockLocation.objects.create(
|
||||||
if i in (
|
name='Parent stock location',
|
||||||
Target.move_sub_locations_to_parent_delete_stockitems,
|
description='This is the parent stock location where the sub categories and stock items are moved to',
|
||||||
Target.delete_sub_locations_delete_stockitems,
|
parent=None,
|
||||||
):
|
|
||||||
delete_stock_items = True
|
|
||||||
if i in (
|
|
||||||
Target.delete_sub_locations_move_stockitems_to_parent,
|
|
||||||
Target.delete_sub_locations_delete_stockitems,
|
|
||||||
):
|
|
||||||
delete_sub_locations = True
|
|
||||||
|
|
||||||
# Create a parent stock location
|
|
||||||
parent_stock_location = StockLocation.objects.create(
|
|
||||||
name='Parent stock location',
|
|
||||||
description='This is the parent stock location where the sub categories and stock items are moved to',
|
|
||||||
parent=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
stocklocation_count_before = StockLocation.objects.count()
|
|
||||||
stock_location_count_before = StockItem.objects.count()
|
|
||||||
|
|
||||||
# Create a stock location to be deleted
|
|
||||||
stock_location_to_delete = StockLocation.objects.create(
|
|
||||||
name='Stock location to delete',
|
|
||||||
description='This is the stock location to be deleted',
|
|
||||||
parent=parent_stock_location,
|
|
||||||
)
|
|
||||||
|
|
||||||
url = reverse(
|
|
||||||
'api-location-detail', kwargs={'pk': stock_location_to_delete.id}
|
|
||||||
)
|
|
||||||
|
|
||||||
stock_items = []
|
|
||||||
# Create stock items in the location to be deleted
|
|
||||||
for jj in range(3):
|
|
||||||
stock_items.append(
|
|
||||||
StockItem.objects.create(
|
|
||||||
batch=f'Batch xyz {jj}',
|
|
||||||
location=stock_location_to_delete,
|
|
||||||
part=part,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
child_stock_locations = []
|
location_count_before = StockLocation.objects.count()
|
||||||
child_stock_locations_items = []
|
item_count_before = StockItem.objects.count()
|
||||||
# Create sub location under the stock location to be deleted
|
|
||||||
for ii in range(3):
|
|
||||||
child = StockLocation.objects.create(
|
|
||||||
name=f'Sub-location {ii}',
|
|
||||||
description='A sub-location of the deleted stock location',
|
|
||||||
parent=stock_location_to_delete,
|
|
||||||
)
|
|
||||||
child_stock_locations.append(child)
|
|
||||||
|
|
||||||
# Create stock items in the sub locations
|
# Create a stock location to be deleted
|
||||||
|
location_to_delete = StockLocation.objects.create(
|
||||||
|
name='Stock location to delete',
|
||||||
|
description='This is the stock location to be deleted',
|
||||||
|
parent=parent_stock_location,
|
||||||
|
)
|
||||||
|
|
||||||
|
url = reverse(
|
||||||
|
'api-location-detail', kwargs={'pk': location_to_delete.id}
|
||||||
|
)
|
||||||
|
|
||||||
|
stock_items = []
|
||||||
|
|
||||||
|
# Create stock items in the location to be deleted
|
||||||
for jj in range(3):
|
for jj in range(3):
|
||||||
child_stock_locations_items.append(
|
stock_items.append(
|
||||||
StockItem.objects.create(
|
StockItem.objects.create(
|
||||||
batch=f'B xyz {jj}', part=part, location=child
|
batch=f'Batch xyz {jj}',
|
||||||
|
location=location_to_delete,
|
||||||
|
part=part,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Delete the created stock location
|
child_locations = []
|
||||||
params = {}
|
child_locations_items = []
|
||||||
if delete_stock_items:
|
|
||||||
params['delete_stock_items'] = '1'
|
|
||||||
if delete_sub_locations:
|
|
||||||
params['delete_sub_locations'] = '1'
|
|
||||||
response = self.delete(url, params, expected_code=204)
|
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 204)
|
# Create sub location under the stock location to be deleted
|
||||||
|
for ii in range(3):
|
||||||
|
child = StockLocation.objects.create(
|
||||||
|
name=f'Sub-location {ii}',
|
||||||
|
description='A sub-location of the deleted stock location',
|
||||||
|
parent=location_to_delete,
|
||||||
|
)
|
||||||
|
child_locations.append(child)
|
||||||
|
|
||||||
if delete_stock_items:
|
# Create stock items in the sub locations
|
||||||
if i == Target.delete_sub_locations_delete_stockitems:
|
for jj in range(3):
|
||||||
# Check if all sub-categories deleted
|
child_locations_items.append(
|
||||||
|
StockItem.objects.create(
|
||||||
|
batch=f'B xyz {jj}', part=part, location=child
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Delete the created stock location
|
||||||
|
params = {
|
||||||
|
'delete_stock_items': delete_stock_items,
|
||||||
|
'delete_sub_locations': delete_sub_locations,
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.delete(url, data=params, expected_code=204)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 204)
|
||||||
|
|
||||||
|
# If we were deleting stock items, the count must not have changed
|
||||||
|
if delete_stock_items:
|
||||||
|
extra_items = 0 if delete_sub_locations else 9
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
StockItem.objects.count(), stock_location_count_before
|
StockItem.objects.count(), item_count_before + extra_items
|
||||||
)
|
)
|
||||||
elif i == Target.move_sub_locations_to_parent_delete_stockitems:
|
else:
|
||||||
# Check if all stock locations deleted
|
# Stock items moved to the parent location
|
||||||
self.assertEqual(
|
self.assertGreater(StockItem.objects.count(), item_count_before)
|
||||||
StockItem.objects.count(),
|
|
||||||
stock_location_count_before + len(child_stock_locations_items),
|
for stock_item in stock_items:
|
||||||
)
|
stock_item.refresh_from_db()
|
||||||
else:
|
self.assertEqual(stock_item.location, parent_stock_location)
|
||||||
# Stock locations moved to the parent location
|
|
||||||
for stock_item in stock_items:
|
|
||||||
stock_item.refresh_from_db()
|
|
||||||
self.assertEqual(stock_item.location, parent_stock_location)
|
|
||||||
|
|
||||||
if delete_sub_locations:
|
if delete_sub_locations:
|
||||||
for child_stock_location_item in child_stock_locations_items:
|
# Check if all sub-categories deleted
|
||||||
child_stock_location_item.refresh_from_db()
|
self.assertEqual(
|
||||||
self.assertEqual(
|
StockLocation.objects.count(), location_count_before
|
||||||
child_stock_location_item.location, parent_stock_location
|
)
|
||||||
)
|
else:
|
||||||
|
# Check if all sub-categories moved to the parent category
|
||||||
if delete_sub_locations:
|
for location in child_locations:
|
||||||
# Check if all sub-locations are deleted
|
location.refresh_from_db()
|
||||||
self.assertEqual(
|
self.assertEqual(location.parent, parent_stock_location)
|
||||||
StockLocation.objects.count(), stocklocation_count_before
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
# Check if all sub-locations moved to the parent
|
|
||||||
for child in child_stock_locations:
|
|
||||||
child.refresh_from_db()
|
|
||||||
self.assertEqual(child.parent, parent_stock_location)
|
|
||||||
|
|
||||||
def test_output_options(self):
|
def test_output_options(self):
|
||||||
"""Test output options."""
|
"""Test output options."""
|
||||||
|
|||||||
Reference in New Issue
Block a user