From 2186a66465c6895a15c24070ca94f7b795936723 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Mon, 22 Feb 2021 22:05:20 +1100 Subject: [PATCH 1/4] Build: Filter by parent or ancestor in API - Add unit testing --- InvenTree/build/api.py | 22 +++++ InvenTree/build/test_api.py | 163 ++++++++++++++++++++++++++++++++++++ 2 files changed, 185 insertions(+) create mode 100644 InvenTree/build/test_api.py diff --git a/InvenTree/build/api.py b/InvenTree/build/api.py index d76c0a4b51..cb6b3f6b2b 100644 --- a/InvenTree/build/api.py +++ b/InvenTree/build/api.py @@ -56,6 +56,28 @@ class BuildList(generics.ListCreateAPIView): params = self.request.query_params + # Filter by "parent" + parent = params.get('parent', None) + + if parent is not None: + queryset = queryset.filter(parent=parent) + + # Filter by "ancestor" builds + ancestor = params.get('ancestor', None) + + if ancestor is not None: + try: + ancestor = Build.objects.get(pk=ancestor) + + descendants = ancestor.get_descendants(include_self=True) + + queryset = queryset.filter( + parent__pk__in=[b.pk for b in descendants] + ) + + except (ValueError, Build.DoesNotExist): + pass + # Filter by build status? status = params.get('status', None) diff --git a/InvenTree/build/test_api.py b/InvenTree/build/test_api.py new file mode 100644 index 0000000000..41b09c8e08 --- /dev/null +++ b/InvenTree/build/test_api.py @@ -0,0 +1,163 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from datetime import datetime, timedelta + +from rest_framework.test import APITestCase +from rest_framework import status + +from django.urls import reverse +from django.contrib.auth import get_user_model +from django.contrib.auth.models import Group + +from part.models import Part +from build.models import Build + +from InvenTree.status_codes import BuildStatus + + +class BuildAPITest(APITestCase): + """ + Series of tests for the Build DRF API + """ + + fixtures = [ + 'category', + 'part', + 'location', + 'bom', + 'build', + ] + + def setUp(self): + # Create a user for auth + user = get_user_model() + + self.user = user.objects.create_user( + username='testuser', + email='test@testing.com', + password='password' + ) + + # Put the user into a group with the correct permissions + group = Group.objects.create(name='mygroup') + self.user.groups.add(group) + + # Give the group *all* the permissions! + for rule in group.rule_sets.all(): + rule.can_view = True + rule.can_change = True + rule.can_add = True + rule.can_delete = True + + rule.save() + + group.save() + + self.client.login(username='testuser', password='password') + + +class BuildListTest(BuildAPITest): + """ + Tests for the BuildOrder LIST API + """ + + url = reverse('api-build-list') + + def get(self, status=200, data={}): + + response = self.client.get(self.url, data, format='json') + + self.assertEqual(response.status_code, status) + + return response.data + + def test_get_all_builds(self): + """ + Retrieve *all* builds via the API + """ + + builds = self.get() + + self.assertEqual(len(builds), 5) + + builds = self.get(data={'active': True}) + self.assertEqual(len(builds), 1) + + builds = self.get(data={'status': BuildStatus.COMPLETE}) + self.assertEqual(len(builds), 4) + + builds = self.get(data={'overdue': False}) + self.assertEqual(len(builds), 5) + + builds = self.get(data={'overdue': True}) + self.assertEqual(len(builds), 0) + + def test_overdue(self): + """ + Create a new build, in the past + """ + + in_the_past = datetime.now().date() - timedelta(days=50) + + part = Part.objects.get(pk=50) + + build = Build.objects.create( + part=part, + quantity=10, + title='Just some thing', + status=BuildStatus.PRODUCTION, + target_date=in_the_past + ) + + builds = self.get(data={'overdue': True}) + + self.assertEqual(len(builds), 1) + + def test_sub_builds(self): + """ + Test the build / sub-build relationship + """ + + parent = Build.objects.get(pk=5) + + part = Part.objects.get(pk=50) + + n = Build.objects.count() + + # Make some sub builds + for i in range(5): + Build.objects.create( + part=part, + quantity=10, + reference=f"build-000{i}", + title=f"Sub build {i}", + parent=parent + ) + + # And some sub-sub builds + for sub_build in Build.objects.filter(parent=parent): + + for i in range(3): + Build.objects.create( + part=part, + reference=f"{sub_build.reference}-00{i}-sub", + quantity=40, + title=f"sub sub build {i}", + parent=sub_build + ) + + # 20 new builds should have been created! + self.assertEqual(Build.objects.count(), (n + 20)) + + Build.objects.rebuild() + + # Search by parent + builds = self.get(data={'parent': parent.pk}) + + self.assertEqual(len(builds), 5) + + # Search by ancestor + builds = self.get(data={'ancestor': parent.pk}) + + self.assertEqual(len(builds), 20) From c1dd5b1ca14e597753bc01ddf435f4fed44a3b2b Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Mon, 22 Feb 2021 22:21:46 +1100 Subject: [PATCH 2/4] Add "child build" page --- InvenTree/build/models.py | 17 +++++++++++++++++ .../build/templates/build/build_children.html | 16 ++++++++++++++++ InvenTree/build/templates/build/tabs.html | 6 ++++++ InvenTree/build/urls.py | 1 + InvenTree/build/views.py | 1 + 5 files changed, 41 insertions(+) create mode 100644 InvenTree/build/templates/build/build_children.html diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py index 2872fecb55..b3399ece59 100644 --- a/InvenTree/build/models.py +++ b/InvenTree/build/models.py @@ -259,6 +259,23 @@ class Build(MPTTModel): blank=True, help_text=_('Extra build notes') ) + def sub_builds(self, cascade=True): + """ + Return all Build Order objects under this one. + """ + + if cascade: + return Build.objects.filter(parent=self.pk) + else: + descendants = self.get_descendants(include_self=True) + Build.objects.filter(parent__pk__in=[d.pk for d in descendants]) + + + def sub_build_count(self, cascade=True): + + return self.sub_builds(cascade=cascade).count() + + @property def is_overdue(self): """ diff --git a/InvenTree/build/templates/build/build_children.html b/InvenTree/build/templates/build/build_children.html new file mode 100644 index 0000000000..f655a549eb --- /dev/null +++ b/InvenTree/build/templates/build/build_children.html @@ -0,0 +1,16 @@ +{% extends "build/build_base.html" %} +{% load static %} +{% load i18n %} + +{% block details %} + +{% include "build/tabs.html" with tab="children" %} + +

{% trans "Child Build Orders" %}

+ +
+ +{% endblock %} + +{% block js_ready %} +{% endblock %} \ No newline at end of file diff --git a/InvenTree/build/templates/build/tabs.html b/InvenTree/build/templates/build/tabs.html index c6d2893620..6b36c3d052 100644 --- a/InvenTree/build/templates/build/tabs.html +++ b/InvenTree/build/templates/build/tabs.html @@ -24,6 +24,12 @@ {{ build.output_count }} +
  • + + {% trans "Child Builds" %} + {{ build.sub_build_count }} + +
  • {% trans "Notes" %} diff --git a/InvenTree/build/urls.py b/InvenTree/build/urls.py index 6f681f5488..877b368817 100644 --- a/InvenTree/build/urls.py +++ b/InvenTree/build/urls.py @@ -20,6 +20,7 @@ build_detail_urls = [ url(r'^notes/', views.BuildNotes.as_view(), name='build-notes'), + url(r'^children/', views.BuildDetail.as_view(template_name='build/build_children.html'), name='build-children'), url(r'^parts/', views.BuildDetail.as_view(template_name='build/parts.html'), name='build-parts'), url(r'^attachments/', views.BuildDetail.as_view(template_name='build/attachments.html'), name='build-attachments'), url(r'^output/', views.BuildDetail.as_view(template_name='build/build_output.html'), name='build-output'), diff --git a/InvenTree/build/views.py b/InvenTree/build/views.py index 3c4b94c43d..272cd1e5d8 100644 --- a/InvenTree/build/views.py +++ b/InvenTree/build/views.py @@ -618,6 +618,7 @@ class BuildDetail(DetailView): ctx['bom_price'] = build.part.get_price_info(build.quantity, buy=False) ctx['BuildStatus'] = BuildStatus + ctx['sub_build_count'] = build.sub_build_count() return ctx From 58863b1924c51b7012aa1358df6405c0cfa949b2 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Mon, 22 Feb 2021 22:35:54 +1100 Subject: [PATCH 3/4] Show child builds --- .../build/templates/build/build_children.html | 21 +++++++++++++++++++ InvenTree/part/templates/part/build.html | 2 +- InvenTree/templates/js/build.js | 4 +++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/InvenTree/build/templates/build/build_children.html b/InvenTree/build/templates/build/build_children.html index f655a549eb..87c8bd8608 100644 --- a/InvenTree/build/templates/build/build_children.html +++ b/InvenTree/build/templates/build/build_children.html @@ -10,7 +10,28 @@
    +
    +
    +
    + +
    +
    + +
    + +
    + {% endblock %} {% block js_ready %} + +loadBuildTable($('#sub-build-table'), { + url: '{% url "api-build-list" %}', + filterTarget: "#filter-list-sub-build", + params: { + part_detail: true, + ancestor: {{ build.pk }}, + } +}); + {% endblock %} \ No newline at end of file diff --git a/InvenTree/part/templates/part/build.html b/InvenTree/part/templates/part/build.html index 9641181af8..46685ad064 100644 --- a/InvenTree/part/templates/part/build.html +++ b/InvenTree/part/templates/part/build.html @@ -9,7 +9,7 @@
    -
    +
    {% if part.active %} {% if roles.build.add %} diff --git a/InvenTree/templates/js/build.js b/InvenTree/templates/js/build.js index cb7e27486b..01d5fcdef0 100644 --- a/InvenTree/templates/js/build.js +++ b/InvenTree/templates/js/build.js @@ -618,7 +618,9 @@ function loadBuildTable(table, options) { filters[key] = params[key]; } - setupFilterList("build", table); + var filterTarget = options.filterTarget || null; + + setupFilterList("build", table, filterTarget); $(table).inventreeTable({ method: 'get', From 832a6ef9a25a8608fb22e718f382da4417f9db13 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 23 Feb 2021 09:01:03 +1100 Subject: [PATCH 4/4] PEP fixes --- InvenTree/build/models.py | 8 ++++++-- InvenTree/build/templates/build/build_children.html | 2 ++ InvenTree/build/test_api.py | 7 +++---- .../templates/report/inventree_build_order_base.html | 2 +- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py index b3399ece59..ada9db1e08 100644 --- a/InvenTree/build/models.py +++ b/InvenTree/build/models.py @@ -269,13 +269,17 @@ class Build(MPTTModel): else: descendants = self.get_descendants(include_self=True) Build.objects.filter(parent__pk__in=[d.pk for d in descendants]) - def sub_build_count(self, cascade=True): + """ + Return the number of sub builds under this one. + + Args: + cascade: If True (defualt), include cascading builds under sub builds + """ return self.sub_builds(cascade=cascade).count() - @property def is_overdue(self): """ diff --git a/InvenTree/build/templates/build/build_children.html b/InvenTree/build/templates/build/build_children.html index 87c8bd8608..c996aaa84f 100644 --- a/InvenTree/build/templates/build/build_children.html +++ b/InvenTree/build/templates/build/build_children.html @@ -25,6 +25,8 @@ {% block js_ready %} +{{ block.super }} + loadBuildTable($('#sub-build-table'), { url: '{% url "api-build-list" %}', filterTarget: "#filter-list-sub-build", diff --git a/InvenTree/build/test_api.py b/InvenTree/build/test_api.py index 41b09c8e08..bcfd600e9e 100644 --- a/InvenTree/build/test_api.py +++ b/InvenTree/build/test_api.py @@ -4,7 +4,6 @@ from __future__ import unicode_literals from datetime import datetime, timedelta from rest_framework.test import APITestCase -from rest_framework import status from django.urls import reverse from django.contrib.auth import get_user_model @@ -64,11 +63,11 @@ class BuildListTest(BuildAPITest): url = reverse('api-build-list') - def get(self, status=200, data={}): + def get(self, status_code=200, data={}): response = self.client.get(self.url, data, format='json') - self.assertEqual(response.status_code, status) + self.assertEqual(response.status_code, status_code) return response.data @@ -102,7 +101,7 @@ class BuildListTest(BuildAPITest): part = Part.objects.get(pk=50) - build = Build.objects.create( + Build.objects.create( part=part, quantity=10, title='Just some thing', diff --git a/InvenTree/report/templates/report/inventree_build_order_base.html b/InvenTree/report/templates/report/inventree_build_order_base.html index 0a4f8b3bb6..46e7544df5 100644 --- a/InvenTree/report/templates/report/inventree_build_order_base.html +++ b/InvenTree/report/templates/report/inventree_build_order_base.html @@ -2,9 +2,9 @@ {% load i18n %} {% load report %} +{% load barcode %} {% load inventree_extras %} {% load markdownify %} -{% load qr_code %} {% block page_margin %} margin: 2cm;