diff --git a/InvenTree/InvenTree/models.py b/InvenTree/InvenTree/models.py index 38539d0118..d5259b2acc 100644 --- a/InvenTree/InvenTree/models.py +++ b/InvenTree/InvenTree/models.py @@ -6,7 +6,6 @@ from __future__ import unicode_literals from django.db import models from django.contrib.contenttypes.models import ContentType -from rest_framework.exceptions import ValidationError from django.db.models.signals import pre_delete from django.dispatch import receiver @@ -134,32 +133,6 @@ class InvenTreeTree(MPTTModel): """ return '/'.join([item.name for item in self.path]) - def clean(self): - """ Custom cleaning - - Parent: - Setting the parent of an item to its own child results in an infinite loop. - The parent of an item cannot be set to: - a) Its own ID - b) The ID of any child items that exist underneath it - - Name: - Tree node names are limited to a reduced character set - """ - - super().clean() - - # Parent cannot be set to same ID (this would cause looping) - try: - if self.parent.id == self.id: - raise ValidationError("Category cannot set itself as parent") - except: - pass - - # Ensure that the new parent is not already a child - if self.pk is not None and self.id in self.getUniqueChildren(include_self=False): - raise ValidationError("Category cannot set a child as parent") - def __str__(self): """ String representation of a category is the full path to that category """ diff --git a/InvenTree/InvenTree/tests.py b/InvenTree/InvenTree/tests.py index 4e238a6c80..2511cf4318 100644 --- a/InvenTree/InvenTree/tests.py +++ b/InvenTree/InvenTree/tests.py @@ -5,6 +5,10 @@ from django.core.exceptions import ValidationError from .validators import validate_overage, validate_part_name from . import helpers +from mptt.exceptions import InvalidMove + +from stock.models import StockLocation + class ValidatorTest(TestCase): @@ -103,6 +107,54 @@ class TestDownloadFile(TestCase): helpers.DownloadFile(bytes("hello world".encode("utf8")), "out.bin") +class TestMPTT(TestCase): + """ Tests for the MPTT tree models """ + + fixtures = [ + 'location', + ] + + def setUp(self): + super().setUp() + + StockLocation.objects.rebuild() + + def test_self_as_parent(self): + """ Test that we cannot set self as parent """ + + loc = StockLocation.objects.get(pk=4) + loc.parent = loc + + with self.assertRaises(InvalidMove): + loc.save() + + def test_child_as_parent(self): + """ Test that we cannot set a child as parent """ + + parent = StockLocation.objects.get(pk=4) + child = StockLocation.objects.get(pk=5) + + parent.parent = child + + with self.assertRaises(InvalidMove): + parent.save() + + def test_move(self): + """ Move an item to a different tree """ + + drawer = StockLocation.objects.get(name='Drawer_1') + + # Record the tree ID + tree = drawer.tree_id + + home = StockLocation.objects.get(name='Home') + + drawer.parent = home + drawer.save() + + self.assertNotEqual(tree, drawer.tree_id) + + class TestSerialNumberExtraction(TestCase): """ Tests for serial number extraction code """ diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py index 29b5c5d6bb..26a957a644 100644 --- a/InvenTree/stock/views.py +++ b/InvenTree/stock/views.py @@ -223,6 +223,9 @@ class StockExport(AjaxView): stock_items = stock_items.filter(customer=None) stock_items = stock_items.filter(belongs_to=None) + # Pre-fetch related fields to reduce DB queries + stock_items = stock_items.prefetch_related('part', 'supplier_part__supplier', 'location', 'purchase_order', 'build') + # Column headers headers = [ _('Stock ID'),