InvenTree/InvenTree/InvenTree/models.py

199 lines
6.1 KiB
Python

from __future__ import unicode_literals
from django.db import models
from django.contrib.contenttypes.models import ContentType
class Company(models.Model):
""" Abstract model representing an external company
"""
class Meta:
abstract = True
name = models.CharField(max_length=100, unique=True)
website = models.URLField(blank=True)
address = models.CharField(max_length=200,
blank=True)
phone = models.CharField(max_length=50,
blank=True)
email = models.EmailField(blank=True)
contact = models.CharField(max_length=100,
blank=True)
notes = models.CharField(max_length=500,
blank=True)
def __str__(self):
return self.name
class InvenTreeTree(models.Model):
""" Provides an abstracted self-referencing tree model for data categories.
- Each Category has one parent Category, which can be blank (for a top-level Category).
- Each Category can have zero-or-more child Categor(y/ies)
"""
class Meta:
abstract = True
unique_together = ('name', 'parent')
name = models.CharField(max_length=100)
description = models.CharField(max_length=250, blank=True)
parent = models.ForeignKey('self',
on_delete=models.CASCADE,
blank=True,
null=True,
related_name='children')
def getUniqueParents(self, unique=None):
""" Return a flat set of all parent items that exist above this node.
If any parents are repeated (which would be very bad!), the process is halted
"""
if unique is None:
unique = set()
else:
unique.add(self.id)
if self.parent and self.parent.id not in unique:
self.parent.getUniqueParents(unique)
return unique
def getUniqueChildren(self, unique=None):
""" Return a flat set of all child items that exist under this node.
If any child items are repeated, the repetitions are omitted.
"""
if unique is None:
unique = set()
if self.id in unique:
return unique
unique.add(self.id)
# Some magic to get around the limitations of abstract models
contents = ContentType.objects.get_for_model(type(self))
children = contents.get_all_objects_for_this_type(parent=self.id)
for child in children:
child.getUniqueChildren(unique)
return unique
@property
def children(self):
contents = ContentType.objects.get_for_model(type(self))
children = contents.get_all_objects_for_this_type(parent=self.id)
return children
def getAcceptableParents(self):
""" Returns a list of acceptable parent items within this model
Acceptable parents are ones which are not underneath this item.
Setting the parent of an item to its own child results in recursion.
"""
contents = ContentType.objects.get_for_model(type(self))
available = contents.get_all_objects_for_this_type()
# List of child IDs
childs = self.getUniqueChildren()
acceptable = [None]
for a in available:
if a.id not in childs:
acceptable.append(a)
return acceptable
@property
def parentpath(self):
""" Return the parent path of this category
Todo:
This function is recursive and expensive.
It should be reworked such that only a single db call is required
"""
if self.parent:
return self.parent.parentpath + [self.parent]
else:
return []
@property
def path(self):
if self.parent:
return "/".join([p.name for p in self.parentpath]) + "/" + self.name
else:
return self.name
def __setattr__(self, attrname, val):
""" Custom Attribute Setting function
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
"""
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:
return
# 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:
return
# Prohibit certain characters from tree node names
elif attrname == 'name':
val = val.translate({ord(c): None for c in "!@#$%^&*'\"\\/[]{}<>,|+=~`"})
super(InvenTreeTree, self).__setattr__(attrname, val)
def __str__(self):
""" String representation of a category is the full path to that category
Todo:
This is recursive - Make it not so.
"""
return self.path
def FilterChildren(queryset, parent):
""" Filter a queryset, limit to only objects that are a child of the given parent
Filter is passed in the URL string, e.g. '/?parent=123'
To accommodate for items without a parent, top-level items can be specified as:
none / false / null / top / 0
"""
if not parent:
return queryset
elif str(parent).lower() in ['none', 'false', 'null', 'top', '0']:
return queryset.filter(parent=None)
else:
try:
parent_id = int(parent)
if parent_id == 0:
return queryset.filter(parent=None)
else:
return queryset.filter(parent=parent_id)
except:
return queryset