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/InvenTree/version.py b/InvenTree/InvenTree/version.py index 4a9a23e7ef..1766168a30 100644 --- a/InvenTree/InvenTree/version.py +++ b/InvenTree/InvenTree/version.py @@ -4,7 +4,7 @@ Provides information on the current InvenTree version import subprocess -INVENTREE_SW_VERSION = "0.0.4" +INVENTREE_SW_VERSION = "0.0.5" def inventreeVersion(): diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index e9220cabcb..c659801a0d 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -188,12 +188,12 @@ class StockItem(models.Model): if self.part.variant_of is not None: if StockItem.objects.filter(part__variant_of=self.part.variant_of, serial=self.serial).exclude(id=self.id).exists(): raise ValidationError({ - 'serial': _('A part with this serial number already exists for template part {part}'.format(part=self.part.variant_of)) + 'serial': _('A stock item with this serial number already exists for template part {part}'.format(part=self.part.variant_of)) }) else: - if StockItem.objects.filter(serial=self.serial).exclude(id=self.id).exists(): + if StockItem.objects.filter(part=self.part, serial=self.serial).exclude(id=self.id).exists(): raise ValidationError({ - 'serial': _('A part with this serial number already exists') + 'serial': _('A stock item with this serial number already exists') }) except Part.DoesNotExist: pass 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'), diff --git a/Makefile b/Makefile index f6b054dab0..5d0f88babe 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ clean: rm -rf .tox rm -f .coverage -update: backup migrate +update: backup install migrate # Perform database migrations (after schema changes are made) migrate: diff --git a/docs/start.rst b/docs/start.rst index 4abf01436c..f239bde858 100644 --- a/docs/start.rst +++ b/docs/start.rst @@ -83,6 +83,7 @@ Development and Testing Other shorthand functions are provided for the development and testing process: * ``make install`` - Install all required underlying packages using PIP +* ``make update`` - Update InvenTree installation (after database configuration) * ``make superuser`` - Create a superuser account * ``make migrate`` - Perform database migrations * ``make mysql`` - Install packages required for MySQL database backend