mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge branch 'part-image-hover'
This commit is contained in:
commit
67cfd22084
29
InvenTree/part/fixtures/bom.yaml
Normal file
29
InvenTree/part/fixtures/bom.yaml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# Construct a BOM for item 100 'Bob'
|
||||||
|
|
||||||
|
# 10 x M2x4 LPHS
|
||||||
|
- model: part.bomitem
|
||||||
|
fields:
|
||||||
|
part: 100
|
||||||
|
sub_part: 1
|
||||||
|
quantity: 10
|
||||||
|
|
||||||
|
# 40 x R_2K2_0805
|
||||||
|
- model: part.bomitem
|
||||||
|
fields:
|
||||||
|
part: 100
|
||||||
|
sub_part: 3
|
||||||
|
quantity: 40
|
||||||
|
|
||||||
|
# 25 x C_22N_0805
|
||||||
|
- model: part.bomitem
|
||||||
|
fields:
|
||||||
|
part: 100
|
||||||
|
sub_part: 5
|
||||||
|
quantity: 25
|
||||||
|
|
||||||
|
# 3 x Orphan
|
||||||
|
- model: part.bomitem
|
||||||
|
fields:
|
||||||
|
part: 100
|
||||||
|
sub_part: 50
|
||||||
|
quantity: 3
|
64
InvenTree/part/fixtures/category.yaml
Normal file
64
InvenTree/part/fixtures/category.yaml
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
# Create some PartCategory objects
|
||||||
|
|
||||||
|
- model: part.partcategory
|
||||||
|
pk: 1
|
||||||
|
fields:
|
||||||
|
name: Electronics
|
||||||
|
description: Electronic components
|
||||||
|
parent: null
|
||||||
|
default_location: 1 # Home
|
||||||
|
|
||||||
|
- model: part.partcategory
|
||||||
|
pk: 2
|
||||||
|
fields:
|
||||||
|
name: Resistors
|
||||||
|
description: Resistors
|
||||||
|
parent: 1
|
||||||
|
default_location: null
|
||||||
|
|
||||||
|
- model: part.partcategory
|
||||||
|
pk: 3
|
||||||
|
fields:
|
||||||
|
name: Capacitors
|
||||||
|
description: Capacitors
|
||||||
|
parent: 1
|
||||||
|
default_location: null
|
||||||
|
|
||||||
|
- model: part.partcategory
|
||||||
|
pk: 4
|
||||||
|
fields:
|
||||||
|
name: IC
|
||||||
|
description: Integrated Circuits
|
||||||
|
parent: 1
|
||||||
|
default_location: null
|
||||||
|
|
||||||
|
- model: part.partcategory
|
||||||
|
pk: 5
|
||||||
|
fields:
|
||||||
|
name: MCU
|
||||||
|
description: Microcontrollers
|
||||||
|
parent: 4
|
||||||
|
default_location: null
|
||||||
|
|
||||||
|
- model: part.partcategory
|
||||||
|
pk: 6
|
||||||
|
fields:
|
||||||
|
name: Transceivers
|
||||||
|
description: Communication interfaces
|
||||||
|
parent: 4
|
||||||
|
default_location: null
|
||||||
|
|
||||||
|
- model: part.partcategory
|
||||||
|
pk: 7
|
||||||
|
fields:
|
||||||
|
name: Mechanical
|
||||||
|
description: Mechanical componenets
|
||||||
|
default_location: null
|
||||||
|
|
||||||
|
- model: part.partcategory
|
||||||
|
pk: 8
|
||||||
|
fields:
|
||||||
|
name: Fasteners
|
||||||
|
description: Screws, bolts, etc
|
||||||
|
parent: 7
|
||||||
|
default_location: 5
|
57
InvenTree/part/fixtures/part.yaml
Normal file
57
InvenTree/part/fixtures/part.yaml
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
# Create some fasteners
|
||||||
|
|
||||||
|
- model: part.part
|
||||||
|
fields:
|
||||||
|
name: 'M2x4 LPHS'
|
||||||
|
description: 'M2x4 low profile head screw'
|
||||||
|
category: 8
|
||||||
|
|
||||||
|
- model: part.part
|
||||||
|
fields:
|
||||||
|
name: 'M3x12 SHCS'
|
||||||
|
description: 'M3x12 socket head cap screw'
|
||||||
|
category: 8
|
||||||
|
|
||||||
|
# Create some resistors
|
||||||
|
|
||||||
|
- model: part.part
|
||||||
|
fields:
|
||||||
|
name: 'R_2K2_0805'
|
||||||
|
description: '2.2kOhm resistor in 0805 package'
|
||||||
|
category: 2
|
||||||
|
|
||||||
|
- model: part.part
|
||||||
|
fields:
|
||||||
|
name: 'R_4K7_0603'
|
||||||
|
description: '4.7kOhm resistor in 0603 package'
|
||||||
|
category: 2
|
||||||
|
default_location: 2 # Home/Bathroom
|
||||||
|
|
||||||
|
# Create some capacitors
|
||||||
|
- model: part.part
|
||||||
|
fields:
|
||||||
|
name: 'C_22N_0805'
|
||||||
|
description: '22nF capacitor in 0805 package'
|
||||||
|
category: 3
|
||||||
|
|
||||||
|
- model: part.part
|
||||||
|
fields:
|
||||||
|
name: 'Widget'
|
||||||
|
description: 'A watchamacallit'
|
||||||
|
category: 7
|
||||||
|
|
||||||
|
- model: part.part
|
||||||
|
pk: 50
|
||||||
|
fields:
|
||||||
|
name: 'Orphan'
|
||||||
|
description: 'A part without a category'
|
||||||
|
category: null
|
||||||
|
|
||||||
|
# A part that can be made from other parts
|
||||||
|
- model: part.part
|
||||||
|
pk: 100
|
||||||
|
fields:
|
||||||
|
name: 'Bob'
|
||||||
|
description: 'Can we build it?'
|
||||||
|
buildable: true
|
||||||
|
|
@ -217,6 +217,9 @@ class Part(models.Model):
|
|||||||
"Part",
|
"Part",
|
||||||
self.id,
|
self.id,
|
||||||
reverse('api-part-detail', kwargs={'pk': self.id}),
|
reverse('api-part-detail', kwargs={'pk': self.id}),
|
||||||
|
{
|
||||||
|
'name': self.name,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -474,8 +477,8 @@ class BomItem(models.Model):
|
|||||||
unique_together = ('part', 'sub_part')
|
unique_together = ('part', 'sub_part')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "{par} -> {child} ({n})".format(
|
return "{n} x {child} to make {parent}".format(
|
||||||
par=self.part.name,
|
parent=self.part.name,
|
||||||
child=self.sub_part.name,
|
child=self.sub_part.name,
|
||||||
n=self.quantity)
|
n=self.quantity)
|
||||||
|
|
||||||
|
@ -3,9 +3,6 @@ from rest_framework import status
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
|
||||||
from .models import Part, PartCategory
|
|
||||||
from .models import BomItem
|
|
||||||
|
|
||||||
|
|
||||||
class PartAPITest(APITestCase):
|
class PartAPITest(APITestCase):
|
||||||
"""
|
"""
|
||||||
@ -14,6 +11,13 @@ class PartAPITest(APITestCase):
|
|||||||
- Tests for PartCategory API
|
- Tests for PartCategory API
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
fixtures = [
|
||||||
|
'category',
|
||||||
|
'part',
|
||||||
|
'location',
|
||||||
|
'bom',
|
||||||
|
]
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
# Create a user for auth
|
# Create a user for auth
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
@ -21,29 +25,15 @@ class PartAPITest(APITestCase):
|
|||||||
|
|
||||||
self.client.login(username='testuser', password='password')
|
self.client.login(username='testuser', password='password')
|
||||||
|
|
||||||
# Create some test data
|
|
||||||
TOP = PartCategory.objects.create(name='Top', description='Top level category')
|
|
||||||
|
|
||||||
A = PartCategory.objects.create(name='A', description='Cat A', parent=TOP)
|
|
||||||
B = PartCategory.objects.create(name='B', description='Cat B', parent=TOP)
|
|
||||||
C = PartCategory.objects.create(name='C', description='Cat C', parent=TOP)
|
|
||||||
|
|
||||||
Part.objects.create(name='Top.t', description='t in TOP', category=TOP)
|
|
||||||
|
|
||||||
Part.objects.create(name='A.a', description='a in A', category=A)
|
|
||||||
Part.objects.create(name='B.b', description='b in B', category=B)
|
|
||||||
Part.objects.create(name='C.c1', description='c1 in C', category=C)
|
|
||||||
Part.objects.create(name='C.c2', description='c2 in C', category=C)
|
|
||||||
|
|
||||||
def test_get_categories(self):
|
def test_get_categories(self):
|
||||||
# Test that we can retrieve list of part categories
|
""" Test that we can retrieve list of part categories """
|
||||||
url = reverse('api-part-category-list')
|
url = reverse('api-part-category-list')
|
||||||
response = self.client.get(url, format='json')
|
response = self.client.get(url, format='json')
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertEqual(len(response.data), 4)
|
self.assertEqual(len(response.data), 8)
|
||||||
|
|
||||||
def test_add_categories(self):
|
def test_add_categories(self):
|
||||||
# Check that we can add categories
|
""" Check that we can add categories """
|
||||||
data = {
|
data = {
|
||||||
'name': 'Animals',
|
'name': 'Animals',
|
||||||
'description': 'All animals go here'
|
'description': 'All animals go here'
|
||||||
@ -52,31 +42,32 @@ class PartAPITest(APITestCase):
|
|||||||
url = reverse('api-part-category-list')
|
url = reverse('api-part-category-list')
|
||||||
response = self.client.post(url, data, format='json')
|
response = self.client.post(url, data, format='json')
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
self.assertEqual(response.data['pk'], 5)
|
|
||||||
|
parent = response.data['pk']
|
||||||
|
|
||||||
# Add some sub-categories to the top-level 'Animals' category
|
# Add some sub-categories to the top-level 'Animals' category
|
||||||
for animal in ['cat', 'dog', 'zebra']:
|
for animal in ['cat', 'dog', 'zebra']:
|
||||||
data = {
|
data = {
|
||||||
'name': animal,
|
'name': animal,
|
||||||
'description': 'A sort of animal',
|
'description': 'A sort of animal',
|
||||||
'parent': 5,
|
'parent': parent,
|
||||||
}
|
}
|
||||||
response = self.client.post(url, data, format='json')
|
response = self.client.post(url, data, format='json')
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
self.assertEqual(response.data['parent'], 5)
|
self.assertEqual(response.data['parent'], parent)
|
||||||
self.assertEqual(response.data['name'], animal)
|
self.assertEqual(response.data['name'], animal)
|
||||||
self.assertEqual(response.data['pathstring'], 'Animals/' + animal)
|
self.assertEqual(response.data['pathstring'], 'Animals/' + animal)
|
||||||
|
|
||||||
# There should be now 8 categories
|
# There should be now 8 categories
|
||||||
response = self.client.get(url, format='json')
|
response = self.client.get(url, format='json')
|
||||||
self.assertEqual(len(response.data), 8)
|
self.assertEqual(len(response.data), 12)
|
||||||
|
|
||||||
def test_cat_detail(self):
|
def test_cat_detail(self):
|
||||||
url = reverse('api-part-category-detail', kwargs={'pk': 4})
|
url = reverse('api-part-category-detail', kwargs={'pk': 4})
|
||||||
response = self.client.get(url, format='json')
|
response = self.client.get(url, format='json')
|
||||||
|
|
||||||
# Test that we have retrieved the category
|
# Test that we have retrieved the category
|
||||||
self.assertEqual(response.data['description'], 'Cat C')
|
self.assertEqual(response.data['description'], 'Integrated Circuits')
|
||||||
self.assertEqual(response.data['parent'], 1)
|
self.assertEqual(response.data['parent'], 1)
|
||||||
|
|
||||||
# Change some data and post it back
|
# Change some data and post it back
|
||||||
@ -87,16 +78,17 @@ class PartAPITest(APITestCase):
|
|||||||
response = self.client.patch(url, data, format='json')
|
response = self.client.patch(url, data, format='json')
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertEqual(response.data['description'], 'Changing the description')
|
self.assertEqual(response.data['description'], 'Changing the description')
|
||||||
|
self.assertIsNone(response.data['parent'])
|
||||||
|
|
||||||
def test_get_all_parts(self):
|
def test_get_all_parts(self):
|
||||||
url = reverse('api-part-list')
|
url = reverse('api-part-list')
|
||||||
response = self.client.get(url, format='json')
|
response = self.client.get(url, format='json')
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertEqual(len(response.data), 5)
|
self.assertEqual(len(response.data), 8)
|
||||||
|
|
||||||
def test_get_parts_by_cat(self):
|
def test_get_parts_by_cat(self):
|
||||||
url = reverse('api-part-list')
|
url = reverse('api-part-list')
|
||||||
data = {'category': 4}
|
data = {'category': 2}
|
||||||
response = self.client.get(url, data, format='json')
|
response = self.client.get(url, data, format='json')
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
@ -104,7 +96,7 @@ class PartAPITest(APITestCase):
|
|||||||
self.assertEqual(len(response.data), 2)
|
self.assertEqual(len(response.data), 2)
|
||||||
|
|
||||||
for part in response.data:
|
for part in response.data:
|
||||||
self.assertEqual(part['category'], 4)
|
self.assertEqual(part['category'], 2)
|
||||||
|
|
||||||
def test_include_children(self):
|
def test_include_children(self):
|
||||||
""" Test the special 'include_child_categories' flag
|
""" Test the special 'include_child_categories' flag
|
||||||
@ -116,7 +108,7 @@ class PartAPITest(APITestCase):
|
|||||||
response = self.client.get(url, data, format='json')
|
response = self.client.get(url, data, format='json')
|
||||||
|
|
||||||
# There should be 1 part in this category
|
# There should be 1 part in this category
|
||||||
self.assertEqual(len(response.data), 1)
|
self.assertEqual(len(response.data), 0)
|
||||||
|
|
||||||
data['include_child_categories'] = 1
|
data['include_child_categories'] = 1
|
||||||
|
|
||||||
@ -125,35 +117,10 @@ class PartAPITest(APITestCase):
|
|||||||
|
|
||||||
# Now there should be 5 total parts
|
# Now there should be 5 total parts
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertEqual(len(response.data), 5)
|
self.assertEqual(len(response.data), 3)
|
||||||
|
|
||||||
|
|
||||||
class BomAPITest(APITestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
# Create a user for auth
|
|
||||||
User = get_user_model()
|
|
||||||
User.objects.create_user('testuser', 'test@testing.com', 'password')
|
|
||||||
|
|
||||||
self.client.login(username='testuser', password='password')
|
|
||||||
|
|
||||||
# Create some parts
|
|
||||||
m1 = Part.objects.create(name='A thing', description='Made from other parts', buildable=True)
|
|
||||||
m2 = Part.objects.create(name='Another thing', description='Made from other parts', buildable=True)
|
|
||||||
|
|
||||||
s1 = Part.objects.create(name='Sub 1', description='Required to make a thing')
|
|
||||||
s2 = Part.objects.create(name='Sub 2', description='Required to make a thing')
|
|
||||||
s3 = Part.objects.create(name='Sub 3', description='Required to make a thing')
|
|
||||||
|
|
||||||
# Link BOM items together
|
|
||||||
BomItem.objects.create(part=m1, sub_part=s1, quantity=10)
|
|
||||||
BomItem.objects.create(part=m1, sub_part=s2, quantity=100)
|
|
||||||
BomItem.objects.create(part=m1, sub_part=s3, quantity=40)
|
|
||||||
|
|
||||||
BomItem.objects.create(part=m2, sub_part=s3, quantity=7)
|
|
||||||
|
|
||||||
def test_get_bom_list(self):
|
def test_get_bom_list(self):
|
||||||
# There should be 4 BomItem objects in the database
|
""" There should be 4 BomItem objects in the database """
|
||||||
url = reverse('api-bom-list')
|
url = reverse('api-bom-list')
|
||||||
response = self.client.get(url, format='json')
|
response = self.client.get(url, format='json')
|
||||||
self.assertEqual(len(response.data), 4)
|
self.assertEqual(len(response.data), 4)
|
||||||
@ -163,7 +130,7 @@ class BomAPITest(APITestCase):
|
|||||||
url = reverse('api-bom-detail', kwargs={'pk': 3})
|
url = reverse('api-bom-detail', kwargs={'pk': 3})
|
||||||
response = self.client.get(url, format='json')
|
response = self.client.get(url, format='json')
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertEqual(response.data['quantity'], 40)
|
self.assertEqual(response.data['quantity'], 25)
|
||||||
|
|
||||||
# Increase the quantity
|
# Increase the quantity
|
||||||
data = response.data
|
data = response.data
|
||||||
@ -181,7 +148,7 @@ class BomAPITest(APITestCase):
|
|||||||
url = reverse('api-bom-list')
|
url = reverse('api-bom-list')
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'part': 2,
|
'part': 100,
|
||||||
'sub_part': 4,
|
'sub_part': 4,
|
||||||
'quantity': 777,
|
'quantity': 777,
|
||||||
}
|
}
|
||||||
@ -194,9 +161,7 @@ class BomAPITest(APITestCase):
|
|||||||
response = self.client.post(url, data, format='json')
|
response = self.client.post(url, data, format='json')
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
# Now try to create a BomItem which references itself
|
# TODO - Now try to create a BomItem which references itself
|
||||||
data['part'] = 2
|
data['part'] = 2
|
||||||
data['sub_part'] = 2
|
data['sub_part'] = 2
|
||||||
response = self.client.post(url, data, format='json')
|
response = self.client.post(url, data, format='json')
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
|
||||||
self.assertIn('cannot be added to its own', str(response.data['sub_part'][0]))
|
|
||||||
|
@ -1,7 +1,54 @@
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
import django.core.exceptions as django_exceptions
|
||||||
|
|
||||||
|
from .models import Part, BomItem
|
||||||
|
|
||||||
|
|
||||||
class BomItemTest(TestCase):
|
class BomItemTest(TestCase):
|
||||||
|
|
||||||
|
fixtures = [
|
||||||
|
'category',
|
||||||
|
'part',
|
||||||
|
'location',
|
||||||
|
'bom',
|
||||||
|
]
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
pass
|
self.bob = Part.objects.get(id=100)
|
||||||
|
self.orphan = Part.objects.get(name='Orphan')
|
||||||
|
|
||||||
|
def test_str(self):
|
||||||
|
b = BomItem.objects.get(id=1)
|
||||||
|
self.assertEqual(str(b), '10 x M2x4 LPHS to make Bob')
|
||||||
|
|
||||||
|
def test_has_bom(self):
|
||||||
|
self.assertFalse(self.orphan.has_bom)
|
||||||
|
self.assertTrue(self.bob.has_bom)
|
||||||
|
|
||||||
|
self.assertEqual(self.bob.bom_count, 4)
|
||||||
|
|
||||||
|
def test_in_bom(self):
|
||||||
|
parts = self.bob.required_parts()
|
||||||
|
|
||||||
|
self.assertIn(self.orphan, parts)
|
||||||
|
|
||||||
|
def test_bom_export(self):
|
||||||
|
parts = self.bob.required_parts()
|
||||||
|
|
||||||
|
data = self.bob.export_bom(format='csv')
|
||||||
|
|
||||||
|
for p in parts:
|
||||||
|
self.assertIn(p.name, data)
|
||||||
|
self.assertIn(p.description, data)
|
||||||
|
|
||||||
|
def test_used_in(self):
|
||||||
|
self.assertEqual(self.bob.used_in_count, 0)
|
||||||
|
self.assertEqual(self.orphan.used_in_count, 1)
|
||||||
|
|
||||||
|
def test_self_reference(self):
|
||||||
|
""" Test that we get an appropriate error when we create a BomItem which points to itself """
|
||||||
|
|
||||||
|
with self.assertRaises(django_exceptions.ValidationError):
|
||||||
|
# A validation error should be raised here
|
||||||
|
item = BomItem.objects.create(part=self.bob, sub_part=self.bob, quantity=7)
|
||||||
|
item.clean()
|
||||||
|
@ -6,71 +6,126 @@ from .models import Part, PartCategory
|
|||||||
class CategoryTest(TestCase):
|
class CategoryTest(TestCase):
|
||||||
"""
|
"""
|
||||||
Tests to ensure that the relational category tree functions correctly.
|
Tests to ensure that the relational category tree functions correctly.
|
||||||
|
|
||||||
|
Loads the following test fixtures:
|
||||||
|
- category.yaml
|
||||||
"""
|
"""
|
||||||
|
fixtures = [
|
||||||
|
'category',
|
||||||
|
'part',
|
||||||
|
'location',
|
||||||
|
]
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.p1 = PartCategory.objects.create(name='A',
|
# Extract some interesting categories for time-saving
|
||||||
description='Most highest level',
|
self.electronics = PartCategory.objects.get(name='Electronics')
|
||||||
parent=None)
|
self.mechanical = PartCategory.objects.get(name='Mechanical')
|
||||||
|
self.resistors = PartCategory.objects.get(name='Resistors')
|
||||||
self.p2 = PartCategory.objects.create(name='B',
|
self.capacitors = PartCategory.objects.get(name='Capacitors')
|
||||||
description='Sits under second',
|
self.fasteners = PartCategory.objects.get(name='Fasteners')
|
||||||
parent=self.p1)
|
self.ic = PartCategory.objects.get(name='IC')
|
||||||
|
self.transceivers = PartCategory.objects.get(name='Transceivers')
|
||||||
self.p3 = PartCategory.objects.create(name='C',
|
|
||||||
description='Third tier category',
|
|
||||||
parent=self.p2)
|
|
||||||
|
|
||||||
# Add two parts in p2
|
|
||||||
Part.objects.create(name='Flange', category=self.p2)
|
|
||||||
Part.objects.create(name='Flob', category=self.p2)
|
|
||||||
|
|
||||||
# Add one part in p3
|
|
||||||
Part.objects.create(name='Blob', category=self.p3)
|
|
||||||
|
|
||||||
def test_parents(self):
|
def test_parents(self):
|
||||||
self.assertEqual(self.p1.parent, None)
|
""" Test that the parent fields are properly set,
|
||||||
self.assertEqual(self.p2.parent, self.p1)
|
based on the test fixtures """
|
||||||
self.assertEqual(self.p3.parent, self.p2)
|
|
||||||
|
self.assertEqual(self.resistors.parent, self.electronics)
|
||||||
|
self.assertEqual(self.capacitors.parent, self.electronics)
|
||||||
|
self.assertEqual(self.electronics.parent, None)
|
||||||
|
|
||||||
|
self.assertEqual(self.fasteners.parent, self.mechanical)
|
||||||
|
|
||||||
def test_children_count(self):
|
def test_children_count(self):
|
||||||
self.assertEqual(self.p1.has_children, True)
|
""" Test that categories have the correct number of children """
|
||||||
self.assertEqual(self.p2.has_children, True)
|
|
||||||
self.assertEqual(self.p3.has_children, False)
|
self.assertTrue(self.electronics.has_children)
|
||||||
|
self.assertTrue(self.mechanical.has_children)
|
||||||
|
|
||||||
|
self.assertEqual(len(self.electronics.children.all()), 3)
|
||||||
|
self.assertEqual(len(self.mechanical.children.all()), 1)
|
||||||
|
|
||||||
def test_unique_childs(self):
|
def test_unique_childs(self):
|
||||||
childs = self.p1.getUniqueChildren()
|
""" Test the 'unique_children' functionality """
|
||||||
|
|
||||||
self.assertIn(self.p2.id, childs)
|
childs = self.electronics.getUniqueChildren()
|
||||||
self.assertIn(self.p3.id, childs)
|
|
||||||
|
self.assertIn(self.transceivers.id, childs)
|
||||||
|
self.assertIn(self.ic.id, childs)
|
||||||
|
|
||||||
|
self.assertNotIn(self.fasteners.id, childs)
|
||||||
|
|
||||||
def test_unique_parents(self):
|
def test_unique_parents(self):
|
||||||
parents = self.p2.getUniqueParents()
|
""" Test the 'unique_parents' functionality """
|
||||||
|
|
||||||
self.assertIn(self.p1.id, parents)
|
parents = self.transceivers.getUniqueParents()
|
||||||
|
|
||||||
|
self.assertIn(self.electronics.id, parents)
|
||||||
|
self.assertIn(self.ic.id, parents)
|
||||||
|
self.assertNotIn(self.fasteners.id, parents)
|
||||||
|
|
||||||
def test_path_string(self):
|
def test_path_string(self):
|
||||||
self.assertEqual(str(self.p3), 'A/B/C')
|
""" Test that the category path string works correctly """
|
||||||
|
|
||||||
|
self.assertEqual(str(self.resistors), 'Electronics/Resistors')
|
||||||
|
self.assertEqual(str(self.transceivers), 'Electronics/IC/Transceivers')
|
||||||
|
|
||||||
def test_url(self):
|
def test_url(self):
|
||||||
self.assertEqual(self.p1.get_absolute_url(), '/part/category/1/')
|
""" Test that the PartCategory URL works """
|
||||||
|
|
||||||
|
self.assertEqual(self.capacitors.get_absolute_url(), '/part/category/3/')
|
||||||
|
|
||||||
def test_part_count(self):
|
def test_part_count(self):
|
||||||
# No direct parts in the top-level category
|
""" Test that the Category part count works """
|
||||||
self.assertEqual(self.p1.has_parts, False)
|
|
||||||
self.assertEqual(self.p2.has_parts, True)
|
|
||||||
self.assertEqual(self.p3.has_parts, True)
|
|
||||||
|
|
||||||
self.assertEqual(self.p1.partcount, 3)
|
self.assertTrue(self.resistors.has_parts)
|
||||||
self.assertEqual(self.p2.partcount, 3)
|
self.assertTrue(self.fasteners.has_parts)
|
||||||
self.assertEqual(self.p3.partcount, 1)
|
self.assertFalse(self.transceivers.has_parts)
|
||||||
|
|
||||||
|
self.assertEqual(self.fasteners.partcount, 2)
|
||||||
|
self.assertEqual(self.capacitors.partcount, 1)
|
||||||
|
|
||||||
|
self.assertEqual(self.electronics.partcount, 3)
|
||||||
|
|
||||||
def test_delete(self):
|
def test_delete(self):
|
||||||
self.assertEqual(Part.objects.filter(category=self.p1).count(), 0)
|
""" Test that category deletion moves the children properly """
|
||||||
|
|
||||||
# Delete p2 (it has 2 direct parts and one child category)
|
# Delete the 'IC' category and 'Transceiver' should move to be under 'Electronics'
|
||||||
self.p2.delete()
|
self.assertEqual(self.transceivers.parent, self.ic)
|
||||||
|
self.assertEqual(self.ic.parent, self.electronics)
|
||||||
|
|
||||||
self.assertEqual(Part.objects.filter(category=self.p1).count(), 2)
|
self.ic.delete()
|
||||||
|
|
||||||
self.assertEqual(PartCategory.objects.get(pk=self.p3.id).parent, self.p1)
|
# Get the data again
|
||||||
|
transceivers = PartCategory.objects.get(name='Transceivers')
|
||||||
|
self.assertEqual(transceivers.parent, self.electronics)
|
||||||
|
|
||||||
|
# Now delete the 'fasteners' category - the parts should move to 'mechanical'
|
||||||
|
self.fasteners.delete()
|
||||||
|
|
||||||
|
fasteners = Part.objects.filter(description__contains='screw')
|
||||||
|
|
||||||
|
for f in fasteners:
|
||||||
|
self.assertEqual(f.category, self.mechanical)
|
||||||
|
|
||||||
|
def test_default_locations(self):
|
||||||
|
""" Test traversal for default locations """
|
||||||
|
|
||||||
|
self.assertEqual(str(self.fasteners.default_location), 'Office/Drawer')
|
||||||
|
|
||||||
|
# Test that parts in this location return the same default location, too
|
||||||
|
for p in self.fasteners.children.all():
|
||||||
|
self.assert_equal(p.get_default_location(), 'Office/Drawer')
|
||||||
|
|
||||||
|
# Any part under electronics should default to 'Home'
|
||||||
|
R1 = Part.objects.get(name='R_2K2_0805')
|
||||||
|
self.assertIsNone(R1.default_location)
|
||||||
|
self.assertEqual(R1.get_default_location().name, 'Home')
|
||||||
|
|
||||||
|
# But one part has a default_location set
|
||||||
|
R2 = Part.objects.get(name='R_4K7_0603')
|
||||||
|
self.assertEqual(R2.get_default_location().name, 'Bathroom')
|
||||||
|
|
||||||
|
# And one part should have no default location at all
|
||||||
|
W = Part.objects.get(name='Widget')
|
||||||
|
self.assertIsNone(W.get_default_location())
|
||||||
|
@ -2,37 +2,69 @@ from django.test import TestCase
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from .models import Part, PartCategory
|
from .models import Part
|
||||||
from .models import rename_part_image
|
from .models import rename_part_image
|
||||||
|
from .templatetags import inventree_extras
|
||||||
|
|
||||||
|
|
||||||
class SimplePartTest(TestCase):
|
class TemplateTagTest(TestCase):
|
||||||
|
""" Tests for the custom template tag code """
|
||||||
|
|
||||||
|
def test_multiply(self):
|
||||||
|
self.assertEqual(inventree_extras.multiply(3, 5), 15)
|
||||||
|
|
||||||
|
def test_version(self):
|
||||||
|
self.assertEqual(type(inventree_extras.inventree_version()), str)
|
||||||
|
|
||||||
|
def test_hash(self):
|
||||||
|
hash = inventree_extras.inventree_commit()
|
||||||
|
self.assertEqual(len(hash), 7)
|
||||||
|
|
||||||
|
def test_github(self):
|
||||||
|
self.assertIn('github.com', inventree_extras.inventree_github())
|
||||||
|
|
||||||
|
|
||||||
|
class PartTest(TestCase):
|
||||||
|
""" Tests for the Part model """
|
||||||
|
|
||||||
|
fixtures = [
|
||||||
|
'category',
|
||||||
|
'part',
|
||||||
|
'location',
|
||||||
|
]
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
self.R1 = Part.objects.get(name='R_2K2_0805')
|
||||||
|
self.R2 = Part.objects.get(name='R_4K7_0603')
|
||||||
|
|
||||||
cat = PartCategory.objects.create(name='TLC', description='Top level category')
|
self.C1 = Part.objects.get(name='C_22N_0805')
|
||||||
|
|
||||||
self.px = Part.objects.create(name='x', description='A part called x', buildable=True)
|
|
||||||
self.py = Part.objects.create(name='y', description='A part called y', consumable=False)
|
|
||||||
self.pz = Part.objects.create(name='z', description='A part called z', category=cat)
|
|
||||||
|
|
||||||
def test_metadata(self):
|
def test_metadata(self):
|
||||||
self.assertEqual(self.px.name, 'x')
|
self.assertEqual(self.R1.name, 'R_2K2_0805')
|
||||||
self.assertEqual(self.py.get_absolute_url(), '/part/2/')
|
self.assertEqual(self.R1.get_absolute_url(), '/part/3/')
|
||||||
self.assertEqual(str(self.pz), 'z - A part called z')
|
|
||||||
|
|
||||||
def test_category(self):
|
def test_category(self):
|
||||||
self.assertEqual(self.px.category_path, '')
|
self.assertEqual(str(self.C1.category), 'Electronics/Capacitors')
|
||||||
self.assertEqual(self.pz.category_path, 'TLC')
|
|
||||||
|
orphan = Part.objects.get(name='Orphan')
|
||||||
|
self.assertIsNone(orphan.category)
|
||||||
|
self.assertEqual(orphan.category_path, '')
|
||||||
|
|
||||||
def test_rename_img(self):
|
def test_rename_img(self):
|
||||||
img = rename_part_image(self.px, 'hello.png')
|
img = rename_part_image(self.R1, 'hello.png')
|
||||||
self.assertEqual(img, os.path.join('part_images', 'part_1_img.png'))
|
self.assertEqual(img, os.path.join('part_images', 'part_3_img.png'))
|
||||||
|
|
||||||
img = rename_part_image(self.pz, 'test')
|
img = rename_part_image(self.R2, 'test')
|
||||||
self.assertEqual(img, os.path.join('part_images', 'part_3_img'))
|
self.assertEqual(img, os.path.join('part_images', 'part_4_img'))
|
||||||
|
|
||||||
def test_stock(self):
|
def test_stock(self):
|
||||||
# Stock should initially be zero
|
# No stock of any resistors
|
||||||
self.assertEqual(self.px.total_stock, 0)
|
res = Part.objects.filter(description__contains='resistor')
|
||||||
self.assertEqual(self.py.available_stock, 0)
|
for r in res:
|
||||||
|
self.assertEqual(r.total_stock, 0)
|
||||||
|
self.assertEqual(r.available_stock, 0)
|
||||||
|
|
||||||
|
def test_barcode(self):
|
||||||
|
barcode = self.R1.format_barcode()
|
||||||
|
self.assertIn('InvenTree', barcode)
|
||||||
|
self.assertIn(self.R1.name, barcode)
|
||||||
|
31
InvenTree/stock/fixtures/location.yaml
Normal file
31
InvenTree/stock/fixtures/location.yaml
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# Create some locations for stock
|
||||||
|
|
||||||
|
- model: stock.stocklocation
|
||||||
|
pk: 1
|
||||||
|
fields:
|
||||||
|
name: 'Home'
|
||||||
|
description: 'My house'
|
||||||
|
- model: stock.stocklocation
|
||||||
|
pk: 2
|
||||||
|
fields:
|
||||||
|
name: 'Bathroom'
|
||||||
|
description: 'Where I keep my bath'
|
||||||
|
parent: 1
|
||||||
|
- model: stock.stocklocation
|
||||||
|
pk: 3
|
||||||
|
fields:
|
||||||
|
name: 'Dining Room'
|
||||||
|
description: 'A table lives here'
|
||||||
|
parent: 1
|
||||||
|
|
||||||
|
- model: stock.stocklocation
|
||||||
|
pk: 4
|
||||||
|
fields:
|
||||||
|
name: 'Office'
|
||||||
|
description: 'Place of work'
|
||||||
|
- model: stock.stocklocation
|
||||||
|
pk: 5
|
||||||
|
fields:
|
||||||
|
name: 'Drawer'
|
||||||
|
description: 'In my desk'
|
||||||
|
parent: 4
|
Loading…
Reference in New Issue
Block a user