diff --git a/InvenTree/InvenTree/models.py b/InvenTree/InvenTree/models.py index 4ca630625f..eeee6378c6 100644 --- a/InvenTree/InvenTree/models.py +++ b/InvenTree/InvenTree/models.py @@ -11,6 +11,8 @@ from rest_framework.exceptions import ValidationError from django.db.models.signals import pre_delete from django.dispatch import receiver +from .validators import validate_tree_name + class InvenTreeTree(models.Model): """ Provides an abstracted self-referencing tree model for data categories. @@ -31,7 +33,8 @@ class InvenTreeTree(models.Model): name = models.CharField( blank=False, max_length=100, - unique=True + unique=True, + validators=[validate_tree_name] ) description = models.CharField( @@ -104,14 +107,6 @@ class InvenTreeTree(models.Model): """ True if there are any children under this item """ return self.children.count() > 0 - @property - def children(self): - """ Return the children of this item """ - contents = ContentType.objects.get_for_model(type(self)) - childs = contents.get_all_objects_for_this_type(parent=self.id) - - return childs - def getAcceptableParents(self): """ Returns a list of acceptable parent items within this model Acceptable parents are ones which are not underneath this item. @@ -164,8 +159,8 @@ class InvenTreeTree(models.Model): """ return '/'.join([item.name for item in self.path]) - def __setattr__(self, attrname, val): - """ Custom Attribute Setting function + def clean(self): + """ Custom cleaning Parent: Setting the parent of an item to its own child results in an infinite loop. @@ -177,28 +172,18 @@ class InvenTreeTree(models.Model): Tree node names are limited to a reduced character set """ - if attrname == 'parent_id': - # If current ID is None, continue - # - This object is just being created - if self.id is None: - pass - # Parent cannot be set to same ID (this would cause looping) - elif val == self.id: - raise ValidationError("Category cannot set itself as parent") - # Null parent is OK - elif val is None: - pass - # Ensure that the new parent is not already a child - else: - kids = self.getUniqueChildren() - if val in kids: - raise ValidationError("Category cannot set a child as parent") + super().clean() - # Prohibit certain characters from tree node names - elif attrname == 'name': - val = val.translate({ord(c): None for c in "!@#$%^&*'\"\\/[]{}<>,|+=~`"}) + # 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 - super(InvenTreeTree, self).__setattr__(attrname, val) + # Ensure that the new parent is not already a child + if self.id in self.getUniqueChildren(): + 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/validators.py b/InvenTree/InvenTree/validators.py index 0e1622a49a..36eda4d451 100644 --- a/InvenTree/InvenTree/validators.py +++ b/InvenTree/InvenTree/validators.py @@ -17,6 +17,14 @@ def validate_part_name(value): ) +def validate_tree_name(value): + """ Prevent illegal characters in tree item names """ + + for c in "!@#$%^&*'\"\\/[]{}<>,|+=~`\"": + if c in str(value): + raise ValidationError({'name': _('Illegal character in name')}) + + def validate_overage(value): """ Validate that a BOM overage string is properly formatted. diff --git a/InvenTree/InvenTree/views.py b/InvenTree/InvenTree/views.py index f8f20a96b1..a564705ac2 100644 --- a/InvenTree/InvenTree/views.py +++ b/InvenTree/InvenTree/views.py @@ -52,10 +52,8 @@ class TreeSerializer(views.APIView): if item.has_children: nodes = [] - """ for child in item.children.all().order_by('name'): nodes.append(self.itemToJson(child)) - """ data['nodes'] = nodes diff --git a/InvenTree/part/api.py b/InvenTree/part/api.py index 7f3ad61e75..bf0555a216 100644 --- a/InvenTree/part/api.py +++ b/InvenTree/part/api.py @@ -35,8 +35,6 @@ class PartCategoryTree(TreeSerializer): return reverse('part-index') def get_items(self): - - print("hello world") return PartCategory.objects.all().prefetch_related('parts', 'children') @@ -112,7 +110,9 @@ class PartList(generics.ListCreateAPIView): if cat_id: try: category = PartCategory.objects.get(pk=cat_id) - parts_list = parts_list.filter(category__in=category.getUniqueChildren()) + cats = [category.id] + cats += [cat for cat in category.getUniqueChildren()] + parts_list = parts_list.filter(category__in=cats) except PartCategory.DoesNotExist: pass diff --git a/InvenTree/part/migrations/0008_auto_20190618_0042.py b/InvenTree/part/migrations/0008_auto_20190618_0042.py new file mode 100644 index 0000000000..c9e0699f9b --- /dev/null +++ b/InvenTree/part/migrations/0008_auto_20190618_0042.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.2 on 2019-06-17 14:42 + +import InvenTree.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('part', '0007_auto_20190602_1944'), + ] + + operations = [ + migrations.AlterField( + model_name='partcategory', + name='name', + field=models.CharField(max_length=100, unique=True, validators=[InvenTree.validators.validate_tree_name]), + ), + ] diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 26dc54c4f9..7d5476db5d 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -73,10 +73,12 @@ class PartCategory(InvenTreeTree): (including children of child categories) """ + cats = [self.id] + if cascade: - query = Part.objects.filter(category__in=self.getUniqueChildren()) - else: - query = Part.objects.filter(category=self) + cats += [cat for cat in self.getUniqueChildren()] + + query = Part.objects.filter(category__in=cats) if active: query = query.filter(active=True) diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py index 092724407f..8240c6b837 100644 --- a/InvenTree/stock/api.py +++ b/InvenTree/stock/api.py @@ -38,6 +38,9 @@ class StockCategoryTree(TreeSerializer): def root_url(self): return reverse('stock-index') + def get_items(self): + return StockLocation.objects.all().prefetch_related('stock_items', 'children') + class StockDetail(generics.RetrieveUpdateDestroyAPIView): """ API detail endpoint for Stock object diff --git a/InvenTree/stock/migrations/0007_auto_20190618_0042.py b/InvenTree/stock/migrations/0007_auto_20190618_0042.py new file mode 100644 index 0000000000..862ea94273 --- /dev/null +++ b/InvenTree/stock/migrations/0007_auto_20190618_0042.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.2 on 2019-06-17 14:42 + +import InvenTree.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('stock', '0006_stockitem_purchase_order'), + ] + + operations = [ + migrations.AlterField( + model_name='stocklocation', + name='name', + field=models.CharField(max_length=100, unique=True, validators=[InvenTree.validators.validate_tree_name]), + ), + ]