Merge branch 'docupdates' of https://github.com/matmair/InvenTree into docupdates

This commit is contained in:
Matthias 2022-05-29 01:28:29 +02:00
commit 6c25872f81
No known key found for this signature in database
GPG Key ID: AB6D0E6C4CB65093
11 changed files with 93 additions and 71 deletions

View File

@ -1,3 +1,5 @@
"""Admin functionality for the BuildOrder app"""
from django.contrib import admin from django.contrib import admin
from import_export.admin import ImportExportModelAdmin from import_export.admin import ImportExportModelAdmin
@ -39,6 +41,7 @@ class BuildResource(ModelResource):
notes = Field(attribute='notes') notes = Field(attribute='notes')
class Meta: class Meta:
"""Metaclass options"""
models = Build models = Build
skip_unchanged = True skip_unchanged = True
report_skipped = False report_skipped = False
@ -50,6 +53,7 @@ class BuildResource(ModelResource):
class BuildAdmin(ImportExportModelAdmin): class BuildAdmin(ImportExportModelAdmin):
"""Class for managing the Build model via the admin interface"""
exclude = [ exclude = [
'reference_int', 'reference_int',
@ -81,6 +85,7 @@ class BuildAdmin(ImportExportModelAdmin):
class BuildItemAdmin(admin.ModelAdmin): class BuildItemAdmin(admin.ModelAdmin):
"""Class for managing the BuildItem model via the admin interface"""
list_display = ( list_display = (
'build', 'build',

View File

@ -27,7 +27,7 @@ class BuildFilter(rest_filters.FilterSet):
active = rest_filters.BooleanFilter(label='Build is active', method='filter_active') active = rest_filters.BooleanFilter(label='Build is active', method='filter_active')
def filter_active(self, queryset, name, value): def filter_active(self, queryset, name, value):
"""Filter the queryset to either include or exclude orders which are active"""
if str2bool(value): if str2bool(value):
queryset = queryset.filter(status__in=BuildStatus.ACTIVE_CODES) queryset = queryset.filter(status__in=BuildStatus.ACTIVE_CODES)
else: else:
@ -38,7 +38,7 @@ class BuildFilter(rest_filters.FilterSet):
overdue = rest_filters.BooleanFilter(label='Build is overdue', method='filter_overdue') overdue = rest_filters.BooleanFilter(label='Build is overdue', method='filter_overdue')
def filter_overdue(self, queryset, name, value): def filter_overdue(self, queryset, name, value):
"""Filter the queryset to either include or exclude orders which are overdue"""
if str2bool(value): if str2bool(value):
queryset = queryset.filter(Build.OVERDUE_FILTER) queryset = queryset.filter(Build.OVERDUE_FILTER)
else: else:
@ -112,6 +112,7 @@ class BuildList(APIDownloadMixin, generics.ListCreateAPIView):
return queryset return queryset
def download_queryset(self, queryset, export_format): def download_queryset(self, queryset, export_format):
"""Download the queryset data as a file"""
dataset = build.admin.BuildResource().export(queryset=queryset) dataset = build.admin.BuildResource().export(queryset=queryset)
filedata = dataset.export(export_format) filedata = dataset.export(export_format)
@ -120,7 +121,7 @@ class BuildList(APIDownloadMixin, generics.ListCreateAPIView):
return DownloadFile(filedata, filename) return DownloadFile(filedata, filename)
def filter_queryset(self, queryset): def filter_queryset(self, queryset):
"""Custom query filtering for the BuildList endpoint"""
queryset = super().filter_queryset(queryset) queryset = super().filter_queryset(queryset)
params = self.request.query_params params = self.request.query_params
@ -184,7 +185,7 @@ class BuildList(APIDownloadMixin, generics.ListCreateAPIView):
return queryset return queryset
def get_serializer(self, *args, **kwargs): def get_serializer(self, *args, **kwargs):
"""Add extra context information to the endpoint serializer"""
try: try:
part_detail = str2bool(self.request.GET.get('part_detail', None)) part_detail = str2bool(self.request.GET.get('part_detail', None))
except AttributeError: except AttributeError:
@ -215,7 +216,7 @@ class BuildUnallocate(generics.CreateAPIView):
serializer_class = build.serializers.BuildUnallocationSerializer serializer_class = build.serializers.BuildUnallocationSerializer
def get_serializer_context(self): def get_serializer_context(self):
"""Add extra context information to the endpoint serializer"""
ctx = super().get_serializer_context() ctx = super().get_serializer_context()
try: try:
@ -232,6 +233,7 @@ class BuildOrderContextMixin:
"""Mixin class which adds build order as serializer context variable.""" """Mixin class which adds build order as serializer context variable."""
def get_serializer_context(self): def get_serializer_context(self):
"""Add extra context information to the endpoint serializer"""
ctx = super().get_serializer_context() ctx = super().get_serializer_context()
ctx['request'] = self.request ctx['request'] = self.request
@ -265,6 +267,7 @@ class BuildOutputDelete(BuildOrderContextMixin, generics.CreateAPIView):
"""API endpoint for deleting multiple build outputs.""" """API endpoint for deleting multiple build outputs."""
def get_serializer_context(self): def get_serializer_context(self):
"""Add extra context information to the endpoint serializer"""
ctx = super().get_serializer_context() ctx = super().get_serializer_context()
ctx['to_complete'] = False ctx['to_complete'] = False
@ -338,7 +341,7 @@ class BuildItemList(generics.ListCreateAPIView):
serializer_class = build.serializers.BuildItemSerializer serializer_class = build.serializers.BuildItemSerializer
def get_serializer(self, *args, **kwargs): def get_serializer(self, *args, **kwargs):
"""Returns a BuildItemSerializer instance based on the request"""
try: try:
params = self.request.query_params params = self.request.query_params
@ -361,7 +364,7 @@ class BuildItemList(generics.ListCreateAPIView):
return query return query
def filter_queryset(self, queryset): def filter_queryset(self, queryset):
"""Customm query filtering for the BuildItem list"""
queryset = super().filter_queryset(queryset) queryset = super().filter_queryset(queryset)
params = self.request.query_params params = self.request.query_params

View File

@ -1,5 +1,8 @@
"""Django app for the BuildOrder module"""
from django.apps import AppConfig from django.apps import AppConfig
class BuildConfig(AppConfig): class BuildConfig(AppConfig):
"""BuildOrder app config class"""
name = 'build' name = 'build'

View File

@ -92,10 +92,11 @@ class Build(MPTTModel, ReferenceIndexingMixin):
@staticmethod @staticmethod
def get_api_url(): def get_api_url():
"""Return the API URL associated with the BuildOrder model"""
return reverse('api-build-list') return reverse('api-build-list')
def api_instance_filters(self): def api_instance_filters(self):
"""Returns custom API filters for the particular BuildOrder instance"""
return { return {
'parent': { 'parent': {
'exclude_tree': self.pk, 'exclude_tree': self.pk,
@ -115,7 +116,7 @@ class Build(MPTTModel, ReferenceIndexingMixin):
return defaults return defaults
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
"""Custom save method for the BuildOrder model"""
self.rebuild_reference_field() self.rebuild_reference_field()
try: try:
@ -126,6 +127,7 @@ class Build(MPTTModel, ReferenceIndexingMixin):
}) })
class Meta: class Meta:
"""Metaclass options for the BuildOrder model"""
verbose_name = _("Build Order") verbose_name = _("Build Order")
verbose_name_plural = _("Build Orders") verbose_name_plural = _("Build Orders")
@ -170,12 +172,13 @@ class Build(MPTTModel, ReferenceIndexingMixin):
return queryset return queryset
def __str__(self): def __str__(self):
"""String representation of a BuildOrder"""
prefix = getSetting("BUILDORDER_REFERENCE_PREFIX") prefix = getSetting("BUILDORDER_REFERENCE_PREFIX")
return f"{prefix}{self.reference}" return f"{prefix}{self.reference}"
def get_absolute_url(self): def get_absolute_url(self):
"""Return the web URL associated with this BuildOrder"""
return reverse('build-detail', kwargs={'pk': self.id}) return reverse('build-detail', kwargs={'pk': self.id})
reference = models.CharField( reference = models.CharField(
@ -393,9 +396,11 @@ class Build(MPTTModel, ReferenceIndexingMixin):
@property @property
def output_count(self): def output_count(self):
"""Return the number of build outputs (StockItem) associated with this build order"""
return self.build_outputs.count() return self.build_outputs.count()
def has_build_outputs(self): def has_build_outputs(self):
"""Returns True if this build has more than zero build outputs"""
return self.output_count > 0 return self.output_count > 0
def get_build_outputs(self, **kwargs): def get_build_outputs(self, **kwargs):
@ -436,7 +441,7 @@ class Build(MPTTModel, ReferenceIndexingMixin):
@property @property
def complete_count(self): def complete_count(self):
"""Return the total quantity of completed outputs"""
quantity = 0 quantity = 0
for output in self.complete_outputs: for output in self.complete_outputs:
@ -1049,6 +1054,7 @@ class BuildOrderAttachment(InvenTreeAttachment):
"""Model for storing file attachments against a BuildOrder object.""" """Model for storing file attachments against a BuildOrder object."""
def getSubdir(self): def getSubdir(self):
"""Return the media file subdirectory for storing BuildOrder attachments"""
return os.path.join('bo_files', str(self.build.id)) return os.path.join('bo_files', str(self.build.id))
build = models.ForeignKey(Build, on_delete=models.CASCADE, related_name='attachments') build = models.ForeignKey(Build, on_delete=models.CASCADE, related_name='attachments')
@ -1069,20 +1075,17 @@ class BuildItem(models.Model):
@staticmethod @staticmethod
def get_api_url(): def get_api_url():
"""Return the API URL used to access this model"""
return reverse('api-build-item-list') return reverse('api-build-item-list')
def get_absolute_url(self):
# TODO - Fix!
return '/build/item/{pk}/'.format(pk=self.id)
# return reverse('build-detail', kwargs={'pk': self.id})
class Meta: class Meta:
"""Serializer metaclass"""
unique_together = [ unique_together = [
('build', 'stock_item', 'install_into'), ('build', 'stock_item', 'install_into'),
] ]
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
"""Custom save method for the BuildItem model"""
self.clean() self.clean()
super().save() super().save()

View File

@ -66,6 +66,7 @@ class BuildSerializer(ReferenceIndexingSerializerMixin, InvenTreeModelSerializer
return queryset return queryset
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
"""Determine if extra serializer fields are required"""
part_detail = kwargs.pop('part_detail', True) part_detail = kwargs.pop('part_detail', True)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@ -74,6 +75,7 @@ class BuildSerializer(ReferenceIndexingSerializerMixin, InvenTreeModelSerializer
self.fields.pop('part_detail') self.fields.pop('part_detail')
class Meta: class Meta:
"""Serializer metaclass"""
model = Build model = Build
fields = [ fields = [
'pk', 'pk',
@ -127,7 +129,7 @@ class BuildOutputSerializer(serializers.Serializer):
) )
def validate_output(self, output): def validate_output(self, output):
"""Perform validation for the output (StockItem) provided to the serializer"""
build = self.context['build'] build = self.context['build']
# As this serializer can be used in multiple contexts, we need to work out why we are here # As this serializer can be used in multiple contexts, we need to work out why we are here
@ -159,6 +161,7 @@ class BuildOutputSerializer(serializers.Serializer):
return output return output
class Meta: class Meta:
"""Serializer metaclass"""
fields = [ fields = [
'output', 'output',
] ]
@ -182,13 +185,15 @@ class BuildOutputCreateSerializer(serializers.Serializer):
) )
def get_build(self): def get_build(self):
"""Return the Build instance associated with this serializer"""
return self.context["build"] return self.context["build"]
def get_part(self): def get_part(self):
"""Return the Part instance associated with the build"""
return self.get_build().part return self.get_build().part
def validate_quantity(self, quantity): def validate_quantity(self, quantity):
"""Validate the provided quantity field"""
if quantity <= 0: if quantity <= 0:
raise ValidationError(_("Quantity must be greater than zero")) raise ValidationError(_("Quantity must be greater than zero"))
@ -219,7 +224,7 @@ class BuildOutputCreateSerializer(serializers.Serializer):
) )
def validate_serial_numbers(self, serial_numbers): def validate_serial_numbers(self, serial_numbers):
"""Clean the provided serial number string"""
serial_numbers = serial_numbers.strip() serial_numbers = serial_numbers.strip()
return serial_numbers return serial_numbers
@ -292,6 +297,7 @@ class BuildOutputDeleteSerializer(serializers.Serializer):
"""DRF serializer for deleting (cancelling) one or more build outputs.""" """DRF serializer for deleting (cancelling) one or more build outputs."""
class Meta: class Meta:
"""Serializer metaclass"""
fields = [ fields = [
'outputs', 'outputs',
] ]
@ -302,7 +308,7 @@ class BuildOutputDeleteSerializer(serializers.Serializer):
) )
def validate(self, data): def validate(self, data):
"""Perform data validation for this serializer"""
data = super().validate(data) data = super().validate(data)
outputs = data.get('outputs', []) outputs = data.get('outputs', [])
@ -329,6 +335,7 @@ class BuildOutputCompleteSerializer(serializers.Serializer):
"""DRF serializer for completing one or more build outputs.""" """DRF serializer for completing one or more build outputs."""
class Meta: class Meta:
"""Serializer metaclass"""
fields = [ fields = [
'outputs', 'outputs',
'location', 'location',
@ -370,7 +377,7 @@ class BuildOutputCompleteSerializer(serializers.Serializer):
) )
def validate(self, data): def validate(self, data):
"""Perform data validation for this serializer"""
super().validate(data) super().validate(data)
outputs = data.get('outputs', []) outputs = data.get('outputs', [])
@ -409,15 +416,17 @@ class BuildOutputCompleteSerializer(serializers.Serializer):
class BuildCancelSerializer(serializers.Serializer): class BuildCancelSerializer(serializers.Serializer):
"""DRF serializer class for cancelling an active BuildOrder"""
class Meta: class Meta:
"""Serializer metaclass"""
fields = [ fields = [
'remove_allocated_stock', 'remove_allocated_stock',
'remove_incomplete_outputs', 'remove_incomplete_outputs',
] ]
def get_context_data(self): def get_context_data(self):
"""Retrieve extra context data from this serializer"""
build = self.context['build'] build = self.context['build']
return { return {
@ -441,7 +450,7 @@ class BuildCancelSerializer(serializers.Serializer):
) )
def save(self): def save(self):
"""Cancel the specified build"""
build = self.context['build'] build = self.context['build']
request = self.context['request'] request = self.context['request']
@ -465,7 +474,7 @@ class BuildCompleteSerializer(serializers.Serializer):
) )
def validate_accept_unallocated(self, value): def validate_accept_unallocated(self, value):
"""Check if the 'accept_unallocated' field is required"""
build = self.context['build'] build = self.context['build']
if not build.are_untracked_parts_allocated() and not value: if not build.are_untracked_parts_allocated() and not value:
@ -481,7 +490,7 @@ class BuildCompleteSerializer(serializers.Serializer):
) )
def validate_accept_incomplete(self, value): def validate_accept_incomplete(self, value):
"""Check if the 'accept_incomplete' field is required"""
build = self.context['build'] build = self.context['build']
if build.remaining > 0 and not value: if build.remaining > 0 and not value:
@ -490,7 +499,7 @@ class BuildCompleteSerializer(serializers.Serializer):
return value return value
def validate(self, data): def validate(self, data):
"""Perform validation of this serializer prior to saving"""
build = self.context['build'] build = self.context['build']
if build.incomplete_count > 0: if build.incomplete_count > 0:
@ -502,7 +511,7 @@ class BuildCompleteSerializer(serializers.Serializer):
return data return data
def save(self): def save(self):
"""Complete the specified build output"""
request = self.context['request'] request = self.context['request']
build = self.context['build'] build = self.context['build']
@ -537,8 +546,7 @@ class BuildUnallocationSerializer(serializers.Serializer):
) )
def validate_output(self, stock_item): def validate_output(self, stock_item):
"""Validation for the output StockItem instance. Stock item must point to the same build order!"""
# Stock item must point to the same build order!
build = self.context['build'] build = self.context['build']
if stock_item and stock_item.build != build: if stock_item and stock_item.build != build:
@ -573,7 +581,7 @@ class BuildAllocationItemSerializer(serializers.Serializer):
) )
def validate_bom_item(self, bom_item): def validate_bom_item(self, bom_item):
"""Check if the parts match!""" """Check if the parts match"""
build = self.context['build'] build = self.context['build']
# BomItem should point to the same 'part' as the parent build # BomItem should point to the same 'part' as the parent build
@ -596,7 +604,7 @@ class BuildAllocationItemSerializer(serializers.Serializer):
) )
def validate_stock_item(self, stock_item): def validate_stock_item(self, stock_item):
"""Perform validation of the stock_item field"""
if not stock_item.in_stock: if not stock_item.in_stock:
raise ValidationError(_("Item must be in stock")) raise ValidationError(_("Item must be in stock"))
@ -610,7 +618,7 @@ class BuildAllocationItemSerializer(serializers.Serializer):
) )
def validate_quantity(self, quantity): def validate_quantity(self, quantity):
"""Perform validation of the 'quantity' field"""
if quantity <= 0: if quantity <= 0:
raise ValidationError(_("Quantity must be greater than zero")) raise ValidationError(_("Quantity must be greater than zero"))
@ -625,6 +633,7 @@ class BuildAllocationItemSerializer(serializers.Serializer):
) )
class Meta: class Meta:
"""Serializer metaclass"""
fields = [ fields = [
'bom_item', 'bom_item',
'stock_item', 'stock_item',
@ -633,7 +642,7 @@ class BuildAllocationItemSerializer(serializers.Serializer):
] ]
def validate(self, data): def validate(self, data):
"""Perfofrm data validation for this item"""
super().validate(data) super().validate(data)
build = self.context['build'] build = self.context['build']
@ -684,6 +693,7 @@ class BuildAllocationSerializer(serializers.Serializer):
items = BuildAllocationItemSerializer(many=True) items = BuildAllocationItemSerializer(many=True)
class Meta: class Meta:
"""Serializer metaclass"""
fields = [ fields = [
'items', 'items',
] ]
@ -700,7 +710,7 @@ class BuildAllocationSerializer(serializers.Serializer):
return data return data
def save(self): def save(self):
"""Perform the allocation"""
data = self.validated_data data = self.validated_data
items = data.get('items', []) items = data.get('items', [])
@ -732,6 +742,7 @@ class BuildAutoAllocationSerializer(serializers.Serializer):
"""DRF serializer for auto allocating stock items against a build order.""" """DRF serializer for auto allocating stock items against a build order."""
class Meta: class Meta:
"""Serializer metaclass"""
fields = [ fields = [
'location', 'location',
'exclude_location', 'exclude_location',
@ -770,7 +781,7 @@ class BuildAutoAllocationSerializer(serializers.Serializer):
) )
def save(self): def save(self):
"""Perform the auto-allocation step"""
data = self.validated_data data = self.validated_data
build = self.context['build'] build = self.context['build']
@ -799,7 +810,7 @@ class BuildItemSerializer(InvenTreeModelSerializer):
quantity = InvenTreeDecimalField() quantity = InvenTreeDecimalField()
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
"""Determine which extra details fields should be included"""
build_detail = kwargs.pop('build_detail', False) build_detail = kwargs.pop('build_detail', False)
part_detail = kwargs.pop('part_detail', False) part_detail = kwargs.pop('part_detail', False)
location_detail = kwargs.pop('location_detail', False) location_detail = kwargs.pop('location_detail', False)
@ -816,6 +827,7 @@ class BuildItemSerializer(InvenTreeModelSerializer):
self.fields.pop('location_detail') self.fields.pop('location_detail')
class Meta: class Meta:
"""Serializer metaclass"""
model = BuildItem model = BuildItem
fields = [ fields = [
'pk', 'pk',
@ -837,6 +849,7 @@ class BuildAttachmentSerializer(InvenTreeAttachmentSerializer):
"""Serializer for a BuildAttachment.""" """Serializer for a BuildAttachment."""
class Meta: class Meta:
"""Serializer metaclass"""
model = BuildOrderAttachment model = BuildOrderAttachment
fields = [ fields = [

View File

@ -1,3 +1,5 @@
"""Background task definitions for the BuildOrder app"""
from decimal import Decimal from decimal import Decimal
import logging import logging

View File

@ -1,3 +1,5 @@
"""Unit tests for the BuildOrder API"""
from datetime import datetime, timedelta from datetime import datetime, timedelta
from django.urls import reverse from django.urls import reverse
@ -91,16 +93,12 @@ class BuildAPITest(InvenTreeAPITestCase):
'build.add' 'build.add'
] ]
def setUp(self):
super().setUp()
class BuildTest(BuildAPITest): class BuildTest(BuildAPITest):
"""Unit testing for the build complete API endpoint.""" """Unit testing for the build complete API endpoint."""
def setUp(self): def setUp(self):
"""Basic setup for this test suite"""
super().setUp() super().setUp()
self.build = Build.objects.get(pk=1) self.build = Build.objects.get(pk=1)
@ -477,7 +475,7 @@ class BuildTest(BuildAPITest):
self.assertIn('This build output has already been completed', str(response.data)) self.assertIn('This build output has already been completed', str(response.data))
def test_download_build_orders(self): def test_download_build_orders(self):
"""Test that we can download a list of build orders via the API"""
required_cols = [ required_cols = [
'reference', 'reference',
'status', 'status',
@ -532,7 +530,7 @@ class BuildAllocationTest(BuildAPITest):
""" """
def setUp(self): def setUp(self):
"""Basic operation as part of test suite setup"""
super().setUp() super().setUp()
self.assignRole('build.add') self.assignRole('build.add')

View File

@ -1,3 +1,5 @@
"""Unit tests for the BuildOrder app"""
from ctypes import Union from ctypes import Union
from django.test import TestCase from django.test import TestCase
@ -114,6 +116,7 @@ class BuildTestBase(TestCase):
class BuildTest(BuildTestBase): class BuildTest(BuildTestBase):
"""Basic set of tests for the Build model"""
def test_ref_int(self): def test_ref_int(self):
"""Test the "integer reference" field used for natural sorting.""" """Test the "integer reference" field used for natural sorting."""
@ -133,8 +136,7 @@ class BuildTest(BuildTestBase):
self.assertEqual(build.reference_int, ii) self.assertEqual(build.reference_int, ii)
def test_init(self): def test_init(self):
# Perform some basic tests before we start the ball rolling """Perform some basic tests before we start the ball rolling"""
self.assertEqual(StockItem.objects.count(), 10) self.assertEqual(StockItem.objects.count(), 10)
# Build is PENDING # Build is PENDING
@ -158,8 +160,7 @@ class BuildTest(BuildTestBase):
self.assertFalse(self.build.is_complete) self.assertFalse(self.build.is_complete)
def test_build_item_clean(self): def test_build_item_clean(self):
# Ensure that dodgy BuildItem objects cannot be created """Ensure that dodgy BuildItem objects cannot be created"""
stock = StockItem.objects.create(part=self.assembly, quantity=99) stock = StockItem.objects.create(part=self.assembly, quantity=99)
# Create a BuiltItem which points to an invalid StockItem # Create a BuiltItem which points to an invalid StockItem
@ -185,8 +186,7 @@ class BuildTest(BuildTestBase):
b.save() b.save()
def test_duplicate_bom_line(self): def test_duplicate_bom_line(self):
# Try to add a duplicate BOM item - it should fail! """Try to add a duplicate BOM item - it should fail!"""
with self.assertRaises(IntegrityError): with self.assertRaises(IntegrityError):
BomItem.objects.create( BomItem.objects.create(
part=self.assembly, part=self.assembly,
@ -291,7 +291,7 @@ class BuildTest(BuildTestBase):
self.assertEqual(BuildItem.objects.count(), 0) self.assertEqual(BuildItem.objects.count(), 0)
""" """
pass ...
def test_complete(self): def test_complete(self):
"""Test completion of a build output.""" """Test completion of a build output."""
@ -370,7 +370,7 @@ class AutoAllocationTests(BuildTestBase):
"""Tests for auto allocating stock against a build order.""" """Tests for auto allocating stock against a build order."""
def setUp(self): def setUp(self):
"""Create some data as part of this test suite"""
super().setUp() super().setUp()
# Add a "substitute" part for bom_item_2 # Add a "substitute" part for bom_item_2

View File

@ -38,7 +38,7 @@ class TestForwardMigrations(MigratorTestCase):
) )
def test_items_exist(self): def test_items_exist(self):
"""Test to ensure that the 'assembly' field is correctly configured"""
Part = self.new_state.apps.get_model('part', 'part') Part = self.new_state.apps.get_model('part', 'part')
self.assertEqual(Part.objects.count(), 1) self.assertEqual(Part.objects.count(), 1)
@ -96,7 +96,7 @@ class TestReferenceMigration(MigratorTestCase):
print(build.reference) print(build.reference)
def test_build_reference(self): def test_build_reference(self):
"""Test that the build reference is correctly assigned to the PK of the Build"""
Build = self.new_state.apps.get_model('build', 'build') Build = self.new_state.apps.get_model('build', 'build')
self.assertEqual(Build.objects.count(), 3) self.assertEqual(Build.objects.count(), 3)

View File

@ -1,3 +1,5 @@
"""Basic unit tests for the BuildOrder app"""
from django.urls import reverse from django.urls import reverse
from datetime import datetime, timedelta from datetime import datetime, timedelta
@ -11,6 +13,7 @@ from InvenTree.status_codes import BuildStatus
class BuildTestSimple(InvenTreeTestCase): class BuildTestSimple(InvenTreeTestCase):
"""Basic set of tests for the BuildOrder model functionality"""
fixtures = [ fixtures = [
'category', 'category',
@ -26,7 +29,7 @@ class BuildTestSimple(InvenTreeTestCase):
] ]
def test_build_objects(self): def test_build_objects(self):
# Ensure the Build objects were correctly created """Ensure the Build objects were correctly created"""
self.assertEqual(Build.objects.count(), 5) self.assertEqual(Build.objects.count(), 5)
b = Build.objects.get(pk=2) b = Build.objects.get(pk=2)
self.assertEqual(b.batch, 'B2') self.assertEqual(b.batch, 'B2')
@ -35,10 +38,12 @@ class BuildTestSimple(InvenTreeTestCase):
self.assertEqual(str(b), 'BO0002') self.assertEqual(str(b), 'BO0002')
def test_url(self): def test_url(self):
"""Test URL lookup"""
b1 = Build.objects.get(pk=1) b1 = Build.objects.get(pk=1)
self.assertEqual(b1.get_absolute_url(), '/build/1/') self.assertEqual(b1.get_absolute_url(), '/build/1/')
def test_is_complete(self): def test_is_complete(self):
"""Test build completion status"""
b1 = Build.objects.get(pk=1) b1 = Build.objects.get(pk=1)
b2 = Build.objects.get(pk=2) b2 = Build.objects.get(pk=2)
@ -63,6 +68,7 @@ class BuildTestSimple(InvenTreeTestCase):
self.assertFalse(build.is_overdue) self.assertFalse(build.is_overdue)
def test_is_active(self): def test_is_active(self):
"""Test active / inactive build status"""
b1 = Build.objects.get(pk=1) b1 = Build.objects.get(pk=1)
b2 = Build.objects.get(pk=2) b2 = Build.objects.get(pk=2)
@ -70,8 +76,9 @@ class BuildTestSimple(InvenTreeTestCase):
self.assertEqual(b2.is_active, False) self.assertEqual(b2.is_active, False)
def test_required_parts(self): def test_required_parts(self):
# TODO - Generate BOM for test part """Test set of required BOM items for the build"""
pass # TODO: Generate BOM for test part
...
def test_cancel_build(self): def test_cancel_build(self):
"""Test build cancellation function.""" """Test build cancellation function."""
@ -101,6 +108,7 @@ class TestBuildViews(InvenTreeTestCase):
] ]
def setUp(self): def setUp(self):
"""Fixturing for this suite of unit tests"""
super().setUp() super().setUp()
# Create a build output for build # 1 # Create a build output for build # 1

View File

@ -22,19 +22,6 @@ class BuildIndex(InvenTreeRoleMixin, ListView):
"""Return all Build objects (order by date, newest first)""" """Return all Build objects (order by date, newest first)"""
return Build.objects.order_by('status', '-completion_date') return Build.objects.order_by('status', '-completion_date')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['BuildStatus'] = BuildStatus
context['active'] = self.get_queryset().filter(status__in=BuildStatus.ACTIVE_CODES)
context['completed'] = self.get_queryset().filter(status=BuildStatus.COMPLETE)
context['cancelled'] = self.get_queryset().filter(status=BuildStatus.CANCELLED)
return context
class BuildDetail(InvenTreeRoleMixin, InvenTreePluginViewMixin, DetailView): class BuildDetail(InvenTreeRoleMixin, InvenTreePluginViewMixin, DetailView):
"""Detail view of a single Build object.""" """Detail view of a single Build object."""
@ -44,7 +31,7 @@ class BuildDetail(InvenTreeRoleMixin, InvenTreePluginViewMixin, DetailView):
context_object_name = 'build' context_object_name = 'build'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
"""Return extra context information for the BuildDetail view"""
ctx = super().get_context_data(**kwargs) ctx = super().get_context_data(**kwargs)
build = self.get_object() build = self.get_object()