From 3650410ea324b3562296e0b64e0225aea548f201 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sun, 29 May 2022 08:36:36 +1000 Subject: [PATCH 1/2] Remove outdated function --- InvenTree/build/views.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/InvenTree/build/views.py b/InvenTree/build/views.py index 87790b04ac..652943ee36 100644 --- a/InvenTree/build/views.py +++ b/InvenTree/build/views.py @@ -22,19 +22,6 @@ class BuildIndex(InvenTreeRoleMixin, ListView): """Return all Build objects (order by date, newest first)""" 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): """Detail view of a single Build object.""" From 2792abc0ec56d7902d6e318a7be206e2ac306230 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sun, 29 May 2022 09:12:18 +1000 Subject: [PATCH 2/2] Add docstrings for the 'build' app --- InvenTree/build/admin.py | 5 +++ InvenTree/build/api.py | 17 ++++++---- InvenTree/build/apps.py | 3 ++ InvenTree/build/models.py | 23 +++++++------ InvenTree/build/serializers.py | 53 +++++++++++++++++++----------- InvenTree/build/tasks.py | 2 ++ InvenTree/build/test_api.py | 12 +++---- InvenTree/build/test_build.py | 16 ++++----- InvenTree/build/test_migrations.py | 4 +-- InvenTree/build/tests.py | 14 ++++++-- InvenTree/build/views.py | 2 +- 11 files changed, 93 insertions(+), 58 deletions(-) diff --git a/InvenTree/build/admin.py b/InvenTree/build/admin.py index 640cf0778a..eec7376ede 100644 --- a/InvenTree/build/admin.py +++ b/InvenTree/build/admin.py @@ -1,3 +1,5 @@ +"""Admin functionality for the BuildOrder app""" + from django.contrib import admin from import_export.admin import ImportExportModelAdmin @@ -39,6 +41,7 @@ class BuildResource(ModelResource): notes = Field(attribute='notes') class Meta: + """Metaclass options""" models = Build skip_unchanged = True report_skipped = False @@ -50,6 +53,7 @@ class BuildResource(ModelResource): class BuildAdmin(ImportExportModelAdmin): + """Class for managing the Build model via the admin interface""" exclude = [ 'reference_int', @@ -81,6 +85,7 @@ class BuildAdmin(ImportExportModelAdmin): class BuildItemAdmin(admin.ModelAdmin): + """Class for managing the BuildItem model via the admin interface""" list_display = ( 'build', diff --git a/InvenTree/build/api.py b/InvenTree/build/api.py index 143e3643f9..a84473a139 100644 --- a/InvenTree/build/api.py +++ b/InvenTree/build/api.py @@ -27,7 +27,7 @@ class BuildFilter(rest_filters.FilterSet): active = rest_filters.BooleanFilter(label='Build is active', method='filter_active') def filter_active(self, queryset, name, value): - + """Filter the queryset to either include or exclude orders which are active""" if str2bool(value): queryset = queryset.filter(status__in=BuildStatus.ACTIVE_CODES) else: @@ -38,7 +38,7 @@ class BuildFilter(rest_filters.FilterSet): overdue = rest_filters.BooleanFilter(label='Build is overdue', method='filter_overdue') def filter_overdue(self, queryset, name, value): - + """Filter the queryset to either include or exclude orders which are overdue""" if str2bool(value): queryset = queryset.filter(Build.OVERDUE_FILTER) else: @@ -112,6 +112,7 @@ class BuildList(APIDownloadMixin, generics.ListCreateAPIView): return queryset def download_queryset(self, queryset, export_format): + """Download the queryset data as a file""" dataset = build.admin.BuildResource().export(queryset=queryset) filedata = dataset.export(export_format) @@ -120,7 +121,7 @@ class BuildList(APIDownloadMixin, generics.ListCreateAPIView): return DownloadFile(filedata, filename) def filter_queryset(self, queryset): - + """Custom query filtering for the BuildList endpoint""" queryset = super().filter_queryset(queryset) params = self.request.query_params @@ -184,7 +185,7 @@ class BuildList(APIDownloadMixin, generics.ListCreateAPIView): return queryset def get_serializer(self, *args, **kwargs): - + """Add extra context information to the endpoint serializer""" try: part_detail = str2bool(self.request.GET.get('part_detail', None)) except AttributeError: @@ -215,7 +216,7 @@ class BuildUnallocate(generics.CreateAPIView): serializer_class = build.serializers.BuildUnallocationSerializer def get_serializer_context(self): - + """Add extra context information to the endpoint serializer""" ctx = super().get_serializer_context() try: @@ -232,6 +233,7 @@ class BuildOrderContextMixin: """Mixin class which adds build order as serializer context variable.""" def get_serializer_context(self): + """Add extra context information to the endpoint serializer""" ctx = super().get_serializer_context() ctx['request'] = self.request @@ -265,6 +267,7 @@ class BuildOutputDelete(BuildOrderContextMixin, generics.CreateAPIView): """API endpoint for deleting multiple build outputs.""" def get_serializer_context(self): + """Add extra context information to the endpoint serializer""" ctx = super().get_serializer_context() ctx['to_complete'] = False @@ -338,7 +341,7 @@ class BuildItemList(generics.ListCreateAPIView): serializer_class = build.serializers.BuildItemSerializer def get_serializer(self, *args, **kwargs): - + """Returns a BuildItemSerializer instance based on the request""" try: params = self.request.query_params @@ -361,7 +364,7 @@ class BuildItemList(generics.ListCreateAPIView): return query def filter_queryset(self, queryset): - + """Customm query filtering for the BuildItem list""" queryset = super().filter_queryset(queryset) params = self.request.query_params diff --git a/InvenTree/build/apps.py b/InvenTree/build/apps.py index 9025e77e0e..683e410b66 100644 --- a/InvenTree/build/apps.py +++ b/InvenTree/build/apps.py @@ -1,5 +1,8 @@ +"""Django app for the BuildOrder module""" + from django.apps import AppConfig class BuildConfig(AppConfig): + """BuildOrder app config class""" name = 'build' diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py index 76be632e10..cc81fd7f26 100644 --- a/InvenTree/build/models.py +++ b/InvenTree/build/models.py @@ -92,10 +92,11 @@ class Build(MPTTModel, ReferenceIndexingMixin): @staticmethod def get_api_url(): + """Return the API URL associated with the BuildOrder model""" return reverse('api-build-list') def api_instance_filters(self): - + """Returns custom API filters for the particular BuildOrder instance""" return { 'parent': { 'exclude_tree': self.pk, @@ -115,7 +116,7 @@ class Build(MPTTModel, ReferenceIndexingMixin): return defaults def save(self, *args, **kwargs): - + """Custom save method for the BuildOrder model""" self.rebuild_reference_field() try: @@ -126,6 +127,7 @@ class Build(MPTTModel, ReferenceIndexingMixin): }) class Meta: + """Metaclass options for the BuildOrder model""" verbose_name = _("Build Order") verbose_name_plural = _("Build Orders") @@ -170,12 +172,13 @@ class Build(MPTTModel, ReferenceIndexingMixin): return queryset def __str__(self): - + """String representation of a BuildOrder""" prefix = getSetting("BUILDORDER_REFERENCE_PREFIX") return f"{prefix}{self.reference}" def get_absolute_url(self): + """Return the web URL associated with this BuildOrder""" return reverse('build-detail', kwargs={'pk': self.id}) reference = models.CharField( @@ -393,9 +396,11 @@ class Build(MPTTModel, ReferenceIndexingMixin): @property def output_count(self): + """Return the number of build outputs (StockItem) associated with this build order""" return self.build_outputs.count() def has_build_outputs(self): + """Returns True if this build has more than zero build outputs""" return self.output_count > 0 def get_build_outputs(self, **kwargs): @@ -436,7 +441,7 @@ class Build(MPTTModel, ReferenceIndexingMixin): @property def complete_count(self): - + """Return the total quantity of completed outputs""" quantity = 0 for output in self.complete_outputs: @@ -1049,6 +1054,7 @@ class BuildOrderAttachment(InvenTreeAttachment): """Model for storing file attachments against a BuildOrder object.""" def getSubdir(self): + """Return the media file subdirectory for storing BuildOrder attachments""" return os.path.join('bo_files', str(self.build.id)) build = models.ForeignKey(Build, on_delete=models.CASCADE, related_name='attachments') @@ -1069,20 +1075,17 @@ class BuildItem(models.Model): @staticmethod def get_api_url(): + """Return the API URL used to access this model""" 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: + """Serializer metaclass""" unique_together = [ ('build', 'stock_item', 'install_into'), ] def save(self, *args, **kwargs): - + """Custom save method for the BuildItem model""" self.clean() super().save() diff --git a/InvenTree/build/serializers.py b/InvenTree/build/serializers.py index ea4321c04b..6659dd1e42 100644 --- a/InvenTree/build/serializers.py +++ b/InvenTree/build/serializers.py @@ -66,6 +66,7 @@ class BuildSerializer(ReferenceIndexingSerializerMixin, InvenTreeModelSerializer return queryset def __init__(self, *args, **kwargs): + """Determine if extra serializer fields are required""" part_detail = kwargs.pop('part_detail', True) super().__init__(*args, **kwargs) @@ -74,6 +75,7 @@ class BuildSerializer(ReferenceIndexingSerializerMixin, InvenTreeModelSerializer self.fields.pop('part_detail') class Meta: + """Serializer metaclass""" model = Build fields = [ 'pk', @@ -127,7 +129,7 @@ class BuildOutputSerializer(serializers.Serializer): ) def validate_output(self, output): - + """Perform validation for the output (StockItem) provided to the serializer""" build = self.context['build'] # 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 class Meta: + """Serializer metaclass""" fields = [ 'output', ] @@ -182,13 +185,15 @@ class BuildOutputCreateSerializer(serializers.Serializer): ) def get_build(self): + """Return the Build instance associated with this serializer""" return self.context["build"] def get_part(self): + """Return the Part instance associated with the build""" return self.get_build().part def validate_quantity(self, quantity): - + """Validate the provided quantity field""" if quantity <= 0: raise ValidationError(_("Quantity must be greater than zero")) @@ -219,7 +224,7 @@ class BuildOutputCreateSerializer(serializers.Serializer): ) def validate_serial_numbers(self, serial_numbers): - + """Clean the provided serial number string""" serial_numbers = serial_numbers.strip() return serial_numbers @@ -292,6 +297,7 @@ class BuildOutputDeleteSerializer(serializers.Serializer): """DRF serializer for deleting (cancelling) one or more build outputs.""" class Meta: + """Serializer metaclass""" fields = [ 'outputs', ] @@ -302,7 +308,7 @@ class BuildOutputDeleteSerializer(serializers.Serializer): ) def validate(self, data): - + """Perform data validation for this serializer""" data = super().validate(data) outputs = data.get('outputs', []) @@ -329,6 +335,7 @@ class BuildOutputCompleteSerializer(serializers.Serializer): """DRF serializer for completing one or more build outputs.""" class Meta: + """Serializer metaclass""" fields = [ 'outputs', 'location', @@ -370,7 +377,7 @@ class BuildOutputCompleteSerializer(serializers.Serializer): ) def validate(self, data): - + """Perform data validation for this serializer""" super().validate(data) outputs = data.get('outputs', []) @@ -409,15 +416,17 @@ class BuildOutputCompleteSerializer(serializers.Serializer): class BuildCancelSerializer(serializers.Serializer): + """DRF serializer class for cancelling an active BuildOrder""" class Meta: + """Serializer metaclass""" fields = [ 'remove_allocated_stock', 'remove_incomplete_outputs', ] def get_context_data(self): - + """Retrieve extra context data from this serializer""" build = self.context['build'] return { @@ -441,7 +450,7 @@ class BuildCancelSerializer(serializers.Serializer): ) def save(self): - + """Cancel the specified build""" build = self.context['build'] request = self.context['request'] @@ -465,7 +474,7 @@ class BuildCompleteSerializer(serializers.Serializer): ) def validate_accept_unallocated(self, value): - + """Check if the 'accept_unallocated' field is required""" build = self.context['build'] if not build.are_untracked_parts_allocated() and not value: @@ -481,7 +490,7 @@ class BuildCompleteSerializer(serializers.Serializer): ) def validate_accept_incomplete(self, value): - + """Check if the 'accept_incomplete' field is required""" build = self.context['build'] if build.remaining > 0 and not value: @@ -490,7 +499,7 @@ class BuildCompleteSerializer(serializers.Serializer): return value def validate(self, data): - + """Perform validation of this serializer prior to saving""" build = self.context['build'] if build.incomplete_count > 0: @@ -502,7 +511,7 @@ class BuildCompleteSerializer(serializers.Serializer): return data def save(self): - + """Complete the specified build output""" request = self.context['request'] build = self.context['build'] @@ -537,8 +546,7 @@ class BuildUnallocationSerializer(serializers.Serializer): ) def validate_output(self, stock_item): - - # Stock item must point to the same build order! + """Validation for the output StockItem instance. Stock item must point to the same build order!""" build = self.context['build'] if stock_item and stock_item.build != build: @@ -573,7 +581,7 @@ class BuildAllocationItemSerializer(serializers.Serializer): ) def validate_bom_item(self, bom_item): - """Check if the parts match!""" + """Check if the parts match""" build = self.context['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): - + """Perform validation of the stock_item field""" if not stock_item.in_stock: raise ValidationError(_("Item must be in stock")) @@ -610,7 +618,7 @@ class BuildAllocationItemSerializer(serializers.Serializer): ) def validate_quantity(self, quantity): - + """Perform validation of the 'quantity' field""" if quantity <= 0: raise ValidationError(_("Quantity must be greater than zero")) @@ -625,6 +633,7 @@ class BuildAllocationItemSerializer(serializers.Serializer): ) class Meta: + """Serializer metaclass""" fields = [ 'bom_item', 'stock_item', @@ -633,7 +642,7 @@ class BuildAllocationItemSerializer(serializers.Serializer): ] def validate(self, data): - + """Perfofrm data validation for this item""" super().validate(data) build = self.context['build'] @@ -684,6 +693,7 @@ class BuildAllocationSerializer(serializers.Serializer): items = BuildAllocationItemSerializer(many=True) class Meta: + """Serializer metaclass""" fields = [ 'items', ] @@ -700,7 +710,7 @@ class BuildAllocationSerializer(serializers.Serializer): return data def save(self): - + """Perform the allocation""" data = self.validated_data items = data.get('items', []) @@ -732,6 +742,7 @@ class BuildAutoAllocationSerializer(serializers.Serializer): """DRF serializer for auto allocating stock items against a build order.""" class Meta: + """Serializer metaclass""" fields = [ 'location', 'exclude_location', @@ -770,7 +781,7 @@ class BuildAutoAllocationSerializer(serializers.Serializer): ) def save(self): - + """Perform the auto-allocation step""" data = self.validated_data build = self.context['build'] @@ -799,7 +810,7 @@ class BuildItemSerializer(InvenTreeModelSerializer): quantity = InvenTreeDecimalField() def __init__(self, *args, **kwargs): - + """Determine which extra details fields should be included""" build_detail = kwargs.pop('build_detail', False) part_detail = kwargs.pop('part_detail', False) location_detail = kwargs.pop('location_detail', False) @@ -816,6 +827,7 @@ class BuildItemSerializer(InvenTreeModelSerializer): self.fields.pop('location_detail') class Meta: + """Serializer metaclass""" model = BuildItem fields = [ 'pk', @@ -837,6 +849,7 @@ class BuildAttachmentSerializer(InvenTreeAttachmentSerializer): """Serializer for a BuildAttachment.""" class Meta: + """Serializer metaclass""" model = BuildOrderAttachment fields = [ diff --git a/InvenTree/build/tasks.py b/InvenTree/build/tasks.py index 8192e83a43..ef32fc73ee 100644 --- a/InvenTree/build/tasks.py +++ b/InvenTree/build/tasks.py @@ -1,3 +1,5 @@ +"""Background task definitions for the BuildOrder app""" + from decimal import Decimal import logging diff --git a/InvenTree/build/test_api.py b/InvenTree/build/test_api.py index 95fbf9a17b..bd969bcea9 100644 --- a/InvenTree/build/test_api.py +++ b/InvenTree/build/test_api.py @@ -1,3 +1,5 @@ +"""Unit tests for the BuildOrder API""" + from datetime import datetime, timedelta from django.urls import reverse @@ -91,16 +93,12 @@ class BuildAPITest(InvenTreeAPITestCase): 'build.add' ] - def setUp(self): - - super().setUp() - class BuildTest(BuildAPITest): """Unit testing for the build complete API endpoint.""" def setUp(self): - + """Basic setup for this test suite""" super().setUp() 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)) def test_download_build_orders(self): - + """Test that we can download a list of build orders via the API""" required_cols = [ 'reference', 'status', @@ -532,7 +530,7 @@ class BuildAllocationTest(BuildAPITest): """ def setUp(self): - + """Basic operation as part of test suite setup""" super().setUp() self.assignRole('build.add') diff --git a/InvenTree/build/test_build.py b/InvenTree/build/test_build.py index 298fbb583a..d3bf330246 100644 --- a/InvenTree/build/test_build.py +++ b/InvenTree/build/test_build.py @@ -1,3 +1,5 @@ +"""Unit tests for the BuildOrder app""" + from ctypes import Union from django.test import TestCase @@ -114,6 +116,7 @@ class BuildTestBase(TestCase): class BuildTest(BuildTestBase): + """Basic set of tests for the Build model""" def test_ref_int(self): """Test the "integer reference" field used for natural sorting.""" @@ -133,8 +136,7 @@ class BuildTest(BuildTestBase): self.assertEqual(build.reference_int, ii) 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) # Build is PENDING @@ -158,8 +160,7 @@ class BuildTest(BuildTestBase): self.assertFalse(self.build.is_complete) 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) # Create a BuiltItem which points to an invalid StockItem @@ -185,8 +186,7 @@ class BuildTest(BuildTestBase): b.save() 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): BomItem.objects.create( part=self.assembly, @@ -291,7 +291,7 @@ class BuildTest(BuildTestBase): self.assertEqual(BuildItem.objects.count(), 0) """ - pass + ... def test_complete(self): """Test completion of a build output.""" @@ -370,7 +370,7 @@ class AutoAllocationTests(BuildTestBase): """Tests for auto allocating stock against a build order.""" def setUp(self): - + """Create some data as part of this test suite""" super().setUp() # Add a "substitute" part for bom_item_2 diff --git a/InvenTree/build/test_migrations.py b/InvenTree/build/test_migrations.py index 831d1fda67..0a6ea3dd8b 100644 --- a/InvenTree/build/test_migrations.py +++ b/InvenTree/build/test_migrations.py @@ -38,7 +38,7 @@ class TestForwardMigrations(MigratorTestCase): ) def test_items_exist(self): - + """Test to ensure that the 'assembly' field is correctly configured""" Part = self.new_state.apps.get_model('part', 'part') self.assertEqual(Part.objects.count(), 1) @@ -96,7 +96,7 @@ class TestReferenceMigration(MigratorTestCase): print(build.reference) 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') self.assertEqual(Build.objects.count(), 3) diff --git a/InvenTree/build/tests.py b/InvenTree/build/tests.py index 89534fae19..e09e59d958 100644 --- a/InvenTree/build/tests.py +++ b/InvenTree/build/tests.py @@ -1,3 +1,5 @@ +"""Basic unit tests for the BuildOrder app""" + from django.urls import reverse from datetime import datetime, timedelta @@ -11,6 +13,7 @@ from InvenTree.status_codes import BuildStatus class BuildTestSimple(InvenTreeTestCase): + """Basic set of tests for the BuildOrder model functionality""" fixtures = [ 'category', @@ -26,7 +29,7 @@ class BuildTestSimple(InvenTreeTestCase): ] 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) b = Build.objects.get(pk=2) self.assertEqual(b.batch, 'B2') @@ -35,10 +38,12 @@ class BuildTestSimple(InvenTreeTestCase): self.assertEqual(str(b), 'BO0002') def test_url(self): + """Test URL lookup""" b1 = Build.objects.get(pk=1) self.assertEqual(b1.get_absolute_url(), '/build/1/') def test_is_complete(self): + """Test build completion status""" b1 = Build.objects.get(pk=1) b2 = Build.objects.get(pk=2) @@ -63,6 +68,7 @@ class BuildTestSimple(InvenTreeTestCase): self.assertFalse(build.is_overdue) def test_is_active(self): + """Test active / inactive build status""" b1 = Build.objects.get(pk=1) b2 = Build.objects.get(pk=2) @@ -70,8 +76,9 @@ class BuildTestSimple(InvenTreeTestCase): self.assertEqual(b2.is_active, False) def test_required_parts(self): - # TODO - Generate BOM for test part - pass + """Test set of required BOM items for the build""" + # TODO: Generate BOM for test part + ... def test_cancel_build(self): """Test build cancellation function.""" @@ -101,6 +108,7 @@ class TestBuildViews(InvenTreeTestCase): ] def setUp(self): + """Fixturing for this suite of unit tests""" super().setUp() # Create a build output for build # 1 diff --git a/InvenTree/build/views.py b/InvenTree/build/views.py index 652943ee36..9d01ddc3d6 100644 --- a/InvenTree/build/views.py +++ b/InvenTree/build/views.py @@ -31,7 +31,7 @@ class BuildDetail(InvenTreeRoleMixin, InvenTreePluginViewMixin, DetailView): context_object_name = 'build' def get_context_data(self, **kwargs): - + """Return extra context information for the BuildDetail view""" ctx = super().get_context_data(**kwargs) build = self.get_object()