Merge remote-tracking branch 'inventree/master'

This commit is contained in:
Oliver Walters 2019-08-15 22:02:45 +10:00
commit d1dc0fae19
6 changed files with 284 additions and 31 deletions

View File

@ -4,8 +4,8 @@ omit =
# Do not run coverage on migration files
*/migrations/*
InvenTree/manage.py
InvenTree/keygen.py
Inventree/InvenTree/middleware.py
Inventree/InvenTree/utils.py
Inventree/InvenTree/wsgi.py
InvenTree/setup.py
InvenTree/InvenTree/middleware.py
InvenTree/InvenTree/utils.py
InvenTree/InvenTree/wsgi.py
InvenTree/users/apps.py

View File

@ -159,16 +159,28 @@ WSGI_APPLICATION = 'InvenTree.wsgi.application'
DATABASES = {}
# Database backend selection
if 'database' in CONFIG:
DATABASES['default'] = CONFIG['database']
else:
eprint("Warning: Database backend not specified - using default (sqlite)")
"""
When running unit tests, enforce usage of sqlite3 database,
so that the tests can be run in RAM without any setup requirements
"""
if 'test' in sys.argv:
eprint('Running tests - Using sqlite3 memory database')
DATABASES['default'] = {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'inventree_db.sqlite3'),
'NAME': 'test_db.sqlite3'
}
# Database backend selection
else:
if 'database' in CONFIG:
DATABASES['default'] = CONFIG['database']
else:
eprint("Warning: Database backend not specified - using default (sqlite)")
DATABASES['default'] = {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'inventree_db.sqlite3'),
}
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',

View File

@ -0,0 +1,21 @@
# Construct build objects
- model: build.build
fields:
part: 25
batch: 'B1'
title: 'Building 7 parts'
quantity: 7
notes: 'Some simple notes'
status: 10 # PENDING
creation_date: '2019-03-16'
- model: build.build
fields:
part: 50
title: 'Making things'
batch: 'B2'
status: 40 # COMPLETE
quantity: 21
notes: 'Some more simple notes'
creation_date: '2019-03-16'

View File

@ -2,32 +2,35 @@
from __future__ import unicode_literals
from django.test import TestCase
from django.urls import reverse
from django.contrib.auth import get_user_model
from rest_framework.test import APITestCase
from rest_framework import status
import json
from .models import Build
from part.models import Part
from InvenTree.status_codes import BuildStatus
class BuildTestSimple(TestCase):
fixtures = [
'category',
'part',
'location',
'build',
]
def setUp(self):
part = Part.objects.create(name='Test part',
description='Simple description')
# Create a user for auth
User = get_user_model()
User.objects.create_user('testuser', 'test@testing.com', 'password')
Build.objects.create(part=part,
batch='B1',
status=BuildStatus.PENDING,
title='Building 7 parts',
quantity=7,
notes='Some simple notes')
Build.objects.create(part=part,
batch='B2',
status=BuildStatus.COMPLETE,
title='Building 21 parts',
quantity=21,
notes='Some simple notes')
self.user = User.objects.get(username='testuser')
self.client.login(username='testuser', password='password')
def test_build_objects(self):
# Ensure the Build objects were correctly created
@ -36,7 +39,7 @@ class BuildTestSimple(TestCase):
self.assertEqual(b.batch, 'B2')
self.assertEqual(b.quantity, 21)
self.assertEqual(str(b), 'Build 21 x Test part - Simple description')
self.assertEqual(str(b), 'Build 21 x Orphan - A part without a category')
def test_url(self):
b1 = Build.objects.get(pk=1)
@ -62,13 +65,226 @@ class BuildTestSimple(TestCase):
# TODO - Generate BOM for test part
pass
def cancel_build(self):
def test_cancel_build(self):
""" Test build cancellation function """
build = Build.objects.get(id=1)
self.assertEqual(build.status, BuildStatus.PENDING)
build.cancelBuild()
build.cancelBuild(self.user)
self.assertEqual(build.status, BuildStatus.CANCELLED)
class TestBuildAPI(APITestCase):
"""
Series of tests for the Build DRF API
- Tests for Build API
- Tests for BuildItem API
"""
fixtures = [
'category',
'part',
'location',
'build',
]
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')
def test_get_build_list(self):
""" Test that we can retrieve list of build objects """
url = reverse('api-build-list')
response = self.client.get(url, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_get_build_item_list(self):
""" Test that we can retrieve list of BuildItem objects """
url = reverse('api-build-item-list')
response = self.client.get(url, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
# Test again, filtering by park ID
response = self.client.get(url, {'part': '1'}, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
class TestBuildViews(TestCase):
""" Tests for Build app views """
fixtures = [
'category',
'part',
'location',
'build',
]
def setUp(self):
super().setUp()
# Create a user
User = get_user_model()
User.objects.create_user('username', 'user@email.com', 'password')
self.client.login(username='username', password='password')
def test_build_index(self):
""" test build index view """
response = self.client.get(reverse('build-index'))
self.assertEqual(response.status_code, 200)
content = str(response.content)
# Content should contain build titles
for build in Build.objects.all():
self.assertIn(build.title, content)
def test_build_detail(self):
""" Test the detail view for a Build object """
pk = 1
response = self.client.get(reverse('build-detail', args=(pk,)))
self.assertEqual(response.status_code, 200)
build = Build.objects.get(pk=pk)
content = str(response.content)
self.assertIn(build.title, content)
def test_build_create(self):
""" Test the build creation view (ajax form) """
url = reverse('build-create')
# Create build without specifying part
response = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200)
# Create build with valid part
response = self.client.get(url, {'part': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200)
# Create build with invalid part
response = self.client.get(url, {'part': 9999}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200)
def test_build_allocate(self):
""" Test the part allocation view for a Build """
url = reverse('build-allocate', args=(1,))
# Get the page normally
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
# Get the page in editing mode
response = self.client.get(url, {'edit': 1})
self.assertEqual(response.status_code, 200)
def test_build_item_create(self):
""" Test the BuildItem creation view (ajax form) """
url = reverse('build-item-create')
# Try without a part specified
response = self.client.get(url, {'build': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200)
# Try with an invalid build ID
response = self.client.get(url, {'build': 9999}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200)
# Try with a valid part specified
response = self.client.get(url, {'build': 1, 'part': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200)
# Try with an invalid part specified
response = self.client.get(url, {'build': 1, 'part': 9999}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200)
def test_build_item_edit(self):
""" Test the BuildItem edit view (ajax form) """
# TODO
# url = reverse('build-item-edit')
pass
def test_build_complete(self):
""" Test the build completion form """
url = reverse('build-complete', args=(1,))
# Test without confirmation
response = self.client.post(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200)
data = json.loads(response.content)
self.assertFalse(data['form_valid'])
# Test with confirmation, valid location
response = self.client.post(url, {'confirm': 1, 'location': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200)
data = json.loads(response.content)
self.assertTrue(data['form_valid'])
# Test with confirmation, invalid location
response = self.client.post(url, {'confirm': 1, 'location': 9999}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200)
data = json.loads(response.content)
self.assertFalse(data['form_valid'])
def test_build_cancel(self):
""" Test the build cancellation form """
url = reverse('build-cancel', args=(1,))
# Test without confirmation
response = self.client.post(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200)
data = json.loads(response.content)
self.assertFalse(data['form_valid'])
b = Build.objects.get(pk=1)
self.assertEqual(b.status, 10) # Build status is still PENDING
# Test with confirmation
response = self.client.post(url, {'confirm_cancel': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200)
data = json.loads(response.content)
self.assertTrue(data['form_valid'])
b = Build.objects.get(pk=1)
self.assertEqual(b.status, 30) # Build status is now CANCELLED
def test_build_unallocate(self):
""" Test the build unallocation view (ajax form) """
url = reverse('build-unallocate', args=(1,))
# Test without confirmation
response = self.client.post(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200)
data = json.loads(response.content)
self.assertFalse(data['form_valid'])
# Test with confirmation
response = self.client.post(url, {'confirm': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200)
data = json.loads(response.content)
self.assertTrue(data['form_valid'])

View File

@ -518,7 +518,9 @@ class BuildItemCreate(AjaxCreateView):
part = Part.objects.get(pk=part_id)
except Part.DoesNotExist:
part = None
else:
part = None
if build_id:
try:
build = Build.objects.get(pk=build_id)

View File

@ -1,6 +1,8 @@
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Build Status](https://travis-ci.org/inventree/InvenTree.svg?branch=master)](https://travis-ci.org/inventree/InvenTree) [![Documentation Status](https://readthedocs.org/projects/inventree/badge/?version=latest)](https://inventree.readthedocs.io/en/latest/?badge=latest) [![Coverage Status](https://coveralls.io/repos/github/inventree/InvenTree/badge.svg)](https://coveralls.io/github/inventree/InvenTree)
# InvenTree
<img src="images/logo/inventree.png" alt="InvenTree" width="128"/>
# InvenTree
InvenTree is an open-source Inventory Management System which provides powerful low-level stock control and part tracking. The core of the InvenTree system is a Python/Django database backend which provides an admin interface (web-based) and a JSON API for interaction with external interfaces and applications.
InvenTree is designed to be lightweight and easy to use for SME or hobbyist applications, where many existing stock management solutions are bloated and cumbersome to use. Updating stock is a single-action process and does not require a complex system of work orders or stock transactions.