From df227a375c1cf5fdd0ad23505799e7c6f7177b9c Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 14 May 2019 15:42:53 +1000 Subject: [PATCH 01/10] Allow some more chars in part names --- InvenTree/InvenTree/validators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/InvenTree/validators.py b/InvenTree/InvenTree/validators.py index 88da882e10..f3fd9ef306 100644 --- a/InvenTree/InvenTree/validators.py +++ b/InvenTree/InvenTree/validators.py @@ -8,7 +8,7 @@ from django.utils.translation import gettext_lazy as _ def validate_part_name(value): # Prevent some illegal characters in part names - for c in ['/', '\\', '|', '#', '$']: + for c in ['|', '#', '$']: if c in str(value): raise ValidationError( _('Invalid character in part name') From 74c8b6768da1dd22ba0f4ecdba82296407eb39d9 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 14 May 2019 15:43:16 +1000 Subject: [PATCH 02/10] BOM table fixes - Reorder columns - Allow proper part name filtering --- InvenTree/static/script/inventree/bom.js | 49 ++++++++++++------------ 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/InvenTree/static/script/inventree/bom.js b/InvenTree/static/script/inventree/bom.js index eaa1d40d3d..7d7fe6ab28 100644 --- a/InvenTree/static/script/inventree/bom.js +++ b/InvenTree/static/script/inventree/bom.js @@ -89,11 +89,11 @@ function loadBomTable(table, options) { // Part column cols.push( { - field: 'sub_part_detail', + field: 'sub_part_detail.full_name', title: 'Part', sortable: true, formatter: function(value, row, index, field) { - return imageHoverIcon(value.image_url) + renderLink(value.full_name, value.url); + return imageHoverIcon(row.sub_part_detail.image_url) + renderLink(row.sub_part_detail.full_name, row.sub_part_detail.url); } } ); @@ -115,29 +115,8 @@ function loadBomTable(table, options) { sortable: true, } ); - - // Part notes - cols.push( - { - field: 'note', - title: 'Notes', - searchable: true, - sortable: true, - } - ); - if (options.editable) { - cols.push({ - formatter: function(value, row, index, field) { - var bEdit = ""; - var bDelt = ""; - - return "
" + bEdit + bDelt + "
"; - } - }); - } - - else { + if (!options.editable) { cols.push( { field: 'sub_part_detail.available_stock', @@ -161,6 +140,27 @@ function loadBomTable(table, options) { } ); } + + // Part notes + cols.push( + { + field: 'note', + title: 'Notes', + searchable: true, + sortable: true, + } + ); + + if (options.editable) { + cols.push({ + formatter: function(value, row, index, field) { + var bEdit = ""; + var bDelt = ""; + + return "
" + bEdit + bDelt + "
"; + } + }); + } // Configure the table (bootstrap-table) @@ -172,6 +172,7 @@ function loadBomTable(table, options) { queryParams: function(p) { return { part: options.parent_id, + ordering: 'name', } }, columns: cols, From 7447561f77931314fdf5320bd3b94406a1d0be06 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 14 May 2019 17:12:06 +1000 Subject: [PATCH 03/10] Fix link for part stock in BOM table --- InvenTree/static/script/inventree/bom.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/static/script/inventree/bom.js b/InvenTree/static/script/inventree/bom.js index 7d7fe6ab28..fd7f2ebd9d 100644 --- a/InvenTree/static/script/inventree/bom.js +++ b/InvenTree/static/script/inventree/bom.js @@ -135,7 +135,7 @@ function loadBomTable(table, options) { text = "" + value + ""; } - return renderLink(text, row.sub_part.url + "stock/"); + return renderLink(text, row.sub_part_detail.url + "stock/"); } } ); From 02033c2157a3cc239325b8dc1f9b56062f03c795 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 14 May 2019 17:23:20 +1000 Subject: [PATCH 04/10] Add 'keywords' field to Part - Shows up in search results --- InvenTree/part/api.py | 1 + InvenTree/part/forms.py | 3 ++- .../part/migrations/0023_part_keywords.py | 18 ++++++++++++++++++ InvenTree/part/models.py | 5 ++++- InvenTree/part/serializers.py | 15 ++++++++------- InvenTree/part/templates/part/detail.html | 14 ++++++++++---- 6 files changed, 43 insertions(+), 13 deletions(-) create mode 100644 InvenTree/part/migrations/0023_part_keywords.py diff --git a/InvenTree/part/api.py b/InvenTree/part/api.py index 0a2bdbfef6..e671b49e4f 100644 --- a/InvenTree/part/api.py +++ b/InvenTree/part/api.py @@ -157,6 +157,7 @@ class PartList(generics.ListCreateAPIView): '$name', 'description', '$IPN', + 'keywords', ] diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py index 580ed737a4..5dce37ac3b 100644 --- a/InvenTree/part/forms.py +++ b/InvenTree/part/forms.py @@ -92,9 +92,10 @@ class EditPartForm(HelperForm): 'confirm_creation', 'category', 'name', + 'IPN', 'variant', 'description', - 'IPN', + 'keywords', 'URL', 'default_location', 'default_supplier', diff --git a/InvenTree/part/migrations/0023_part_keywords.py b/InvenTree/part/migrations/0023_part_keywords.py new file mode 100644 index 0000000000..4752d80740 --- /dev/null +++ b/InvenTree/part/migrations/0023_part_keywords.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2 on 2019-05-14 07:15 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('part', '0022_auto_20190512_1246'), + ] + + operations = [ + migrations.AddField( + model_name='part', + name='keywords', + field=models.CharField(blank=True, help_text='Part keywords to improve visibility in search results', max_length=250), + ), + ] diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 5a609c0e59..db9e79224b 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -179,8 +179,9 @@ class Part(models.Model): Attributes: name: Brief name for this part variant: Optional variant number for this part - Must be unique for the part name - description: Longer form description of the part category: The PartCategory to which this part belongs + description: Longer form description of the part + keywords: Optional keywords for improving part search results IPN: Internal part number (optional) URL: Link to an external page with more information about this part (e.g. internal Wiki) image: Image of this part @@ -250,6 +251,8 @@ class Part(models.Model): description = models.CharField(max_length=250, blank=False, help_text='Part description') + keywords = models.CharField(max_length=250, blank=True, help_text='Part keywords to improve visibility in search results') + category = models.ForeignKey(PartCategory, related_name='parts', null=True, blank=True, on_delete=models.DO_NOTHING, diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index 957bfa5951..4d667eb356 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -62,15 +62,16 @@ class PartSerializer(serializers.ModelSerializer): fields = [ 'pk', 'url', # Link to the part detail page - 'full_name', - 'name', - 'variant', - 'image_url', - 'IPN', - 'URL', # Link to an external URL (optional) - 'description', 'category', 'category_name', + 'image_url', + 'full_name', + 'name', + 'IPN', + 'variant', + 'description', + 'keywords', + 'URL', 'total_stock', 'available_stock', 'units', diff --git a/InvenTree/part/templates/part/detail.html b/InvenTree/part/templates/part/detail.html index c0f823ce13..a7bad6cae4 100644 --- a/InvenTree/part/templates/part/detail.html +++ b/InvenTree/part/templates/part/detail.html @@ -35,16 +35,22 @@ Part name {{ part.full_name }} - - Description - {{ part.description }} - {% if part.IPN %} IPN {{ part.IPN }} {% endif %} + + Description + {{ part.description }} + + {% if part.keywords %} + + Keywords + {{ part.keywords }} + + {% endif %} {% if part.URL %} URL From 0842bd5833bb31e878aa4e5deb39237264f4c52b Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 14 May 2019 17:30:24 +1000 Subject: [PATCH 05/10] Add 'default_keywords' field to a category --- InvenTree/part/forms.py | 3 ++- .../0024_partcategory_default_keywords.py | 18 ++++++++++++++++++ InvenTree/part/models.py | 8 ++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 InvenTree/part/migrations/0024_partcategory_default_keywords.py diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py index 5dce37ac3b..88c6c11385 100644 --- a/InvenTree/part/forms.py +++ b/InvenTree/part/forms.py @@ -119,7 +119,8 @@ class EditCategoryForm(HelperForm): 'parent', 'name', 'description', - 'default_location' + 'default_location', + 'default_keywords', ] diff --git a/InvenTree/part/migrations/0024_partcategory_default_keywords.py b/InvenTree/part/migrations/0024_partcategory_default_keywords.py new file mode 100644 index 0000000000..317d982f7d --- /dev/null +++ b/InvenTree/part/migrations/0024_partcategory_default_keywords.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2 on 2019-05-14 07:27 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('part', '0023_part_keywords'), + ] + + operations = [ + migrations.AddField( + model_name='partcategory', + name='default_keywords', + field=models.CharField(blank=True, help_text='Default keywords for parts in this category', max_length=250), + ), + ] diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index db9e79224b..3aeb94700a 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -37,6 +37,12 @@ from company.models import Company class PartCategory(InvenTreeTree): """ PartCategory provides hierarchical organization of Part objects. + + Attributes: + name: Name of this category + parent: Parent category + default_location: Default storage location for parts in this category or child categories + default_keywords: Default keywords for parts created in this category """ default_location = models.ForeignKey( @@ -46,6 +52,8 @@ class PartCategory(InvenTreeTree): help_text='Default location for parts in this category' ) + default_keywords = models.CharField(blank=True, max_length=250, help_text='Default keywords for parts in this category') + def get_absolute_url(self): return reverse('category-detail', kwargs={'pk': self.id}) From d1de6eb1f96b296e5b05cde5892259706a604f3c Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 14 May 2019 17:32:29 +1000 Subject: [PATCH 06/10] Copy across default tags when creating a new part --- InvenTree/part/views.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index 8650b85fb4..fef88197f7 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -339,7 +339,9 @@ class PartCreate(AjaxCreateView): if self.get_category_id(): try: - initials['category'] = PartCategory.objects.get(pk=self.get_category_id()) + category = PartCategory.objects.get(pk=self.get_category_id()) + initials['category'] = category + initials['keywords'] = category.default_keywords except PartCategory.DoesNotExist: pass From e87f545d75fbbdbef977a12e0eb87bf2f7a21604 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 14 May 2019 18:12:17 +1000 Subject: [PATCH 07/10] Render zero stock as 'No Stock' in BOM table --- InvenTree/static/script/inventree/bom.js | 3 +++ InvenTree/static/script/inventree/part.js | 7 +++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/InvenTree/static/script/inventree/bom.js b/InvenTree/static/script/inventree/bom.js index fd7f2ebd9d..46bdc249ef 100644 --- a/InvenTree/static/script/inventree/bom.js +++ b/InvenTree/static/script/inventree/bom.js @@ -132,6 +132,9 @@ function loadBomTable(table, options) { } else { + if (!value) { + value = 'No Stock'; + } text = "" + value + ""; } diff --git a/InvenTree/static/script/inventree/part.js b/InvenTree/static/script/inventree/part.js index c66fe405b2..51d86f1c7a 100644 --- a/InvenTree/static/script/inventree/part.js +++ b/InvenTree/static/script/inventree/part.js @@ -119,13 +119,12 @@ function loadPartTable(table, url, options={}) { visible: false, }, { - field: 'name', + field: 'full_name', title: 'Part', sortable: true, formatter: function(value, row, index, field) { - var name = row.full_name; - var display = imageHoverIcon(row.image_url) + renderLink(name, row.url); + var display = imageHoverIcon(row.image_url) + renderLink(value, row.url); if (!row.active) { display = display + "INACTIVE"; } @@ -160,7 +159,7 @@ function loadPartTable(table, url, options={}) { return renderLink(value, row.url + 'stock/'); } else { - return "No stock"; + return "No Stock"; } } } From 9986df2074996d00c4f1cb9339d6183089a93421 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 14 May 2019 18:20:54 +1000 Subject: [PATCH 08/10] Add confirmation to cancel a build --- InvenTree/build/forms.py | 12 +++++++++ InvenTree/build/templates/build/cancel.html | 6 ++++- InvenTree/build/views.py | 29 ++++++++++++++------- 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/InvenTree/build/forms.py b/InvenTree/build/forms.py index 66ec98ac77..01024230f4 100644 --- a/InvenTree/build/forms.py +++ b/InvenTree/build/forms.py @@ -58,6 +58,18 @@ class CompleteBuildForm(HelperForm): ] +class CancelBuildForm(HelperForm): + """ Form for cancelling a build """ + + confirm_cancel = forms.BooleanField(required=False, help_text='Confirm build cancellation') + + class Meta: + model = Build + fields = [ + 'confirm_cancel' + ] + + class EditBuildItemForm(HelperForm): """ Form for adding a new BuildItem to a Build """ diff --git a/InvenTree/build/templates/build/cancel.html b/InvenTree/build/templates/build/cancel.html index d273a14ff5..d7e4d51b10 100644 --- a/InvenTree/build/templates/build/cancel.html +++ b/InvenTree/build/templates/build/cancel.html @@ -1,3 +1,7 @@ +{% extends "modal_form.html" %} + +{% block pre_form_content %} + Are you sure you wish to cancel this build? -{% include "modal_csrf.html" %} \ No newline at end of file +{% endblock %} \ No newline at end of file diff --git a/InvenTree/build/views.py b/InvenTree/build/views.py index 85d07858fb..e4149c171b 100644 --- a/InvenTree/build/views.py +++ b/InvenTree/build/views.py @@ -16,6 +16,7 @@ from . import forms from stock.models import StockLocation, StockItem from InvenTree.views import AjaxView, AjaxUpdateView, AjaxCreateView, AjaxDeleteView +from InvenTree.helpers import str2bool class BuildIndex(ListView): @@ -41,31 +42,41 @@ class BuildIndex(ListView): return context -class BuildCancel(AjaxView): +class BuildCancel(AjaxUpdateView): """ View to cancel a Build. Provides a cancellation information dialog """ + model = Build ajax_template_name = 'build/cancel.html' ajax_form_title = 'Cancel Build' context_object_name = 'build' - fields = [] + form_class = forms.CancelBuildForm def post(self, request, *args, **kwargs): """ Handle POST request. Mark the build status as CANCELLED """ - build = get_object_or_404(Build, pk=self.kwargs['pk']) + build = self.get_object() - build.cancelBuild(request.user) + form = self.get_form() - return self.renderJsonResponse(request, None) + valid = form.is_valid() - def get_data(self): - """ Provide JSON context data. """ - return { + confirm = str2bool(request.POST.get('confirm_cancel', False)) + + if confirm: + build.cancelBuild(request.user) + else: + form.errors['confirm_cancel'] = ['Confirm build cancellation'] + valid = False + + data = { + 'form_valid': valid, 'danger': 'Build was cancelled' } + return self.renderJsonResponse(request, form, data=data) + class BuildAutoAllocate(AjaxUpdateView): """ View to auto-allocate parts for a build. @@ -217,7 +228,7 @@ class BuildComplete(AjaxUpdateView): form = self.get_form() - confirm = request.POST.get('confirm', False) + confirm = str2bool(request.POST.get('confirm', False)) loc_id = request.POST.get('location', None) From 46ab6e40ebeaa117ed58e3dcbc0033d775c4db63 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 14 May 2019 18:31:19 +1000 Subject: [PATCH 09/10] Bug fix for build allocation - If the part did not have an image file the template failed to render --- InvenTree/build/templates/build/auto_allocate.html | 4 ++-- InvenTree/build/views.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/InvenTree/build/templates/build/auto_allocate.html b/InvenTree/build/templates/build/auto_allocate.html index f850d094b2..dc2160a006 100644 --- a/InvenTree/build/templates/build/auto_allocate.html +++ b/InvenTree/build/templates/build/auto_allocate.html @@ -22,8 +22,8 @@ Automatically allocate stock to this build? - - + + diff --git a/InvenTree/build/views.py b/InvenTree/build/views.py index e4149c171b..93611bae45 100644 --- a/InvenTree/build/views.py +++ b/InvenTree/build/views.py @@ -101,7 +101,7 @@ class BuildAutoAllocate(AjaxUpdateView): context['build'] = build context['allocations'] = build.getAutoAllocations() except Build.DoesNotExist: - context['error'] = 'No matching buidl found' + context['error'] = 'No matching build found' return context From 2164cac28a5e0518daa6779d97a028bbe2619d9f Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 14 May 2019 18:32:20 +1000 Subject: [PATCH 10/10] PEP --- InvenTree/build/views.py | 4 +--- InvenTree/part/serializers.py | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/InvenTree/build/views.py b/InvenTree/build/views.py index 93611bae45..7cef486d55 100644 --- a/InvenTree/build/views.py +++ b/InvenTree/build/views.py @@ -5,8 +5,6 @@ Django views for interacting with Build objects # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.shortcuts import get_object_or_404 - from django.views.generic import DetailView, ListView from django.forms import HiddenInput @@ -15,7 +13,7 @@ from .models import Build, BuildItem from . import forms from stock.models import StockLocation, StockItem -from InvenTree.views import AjaxView, AjaxUpdateView, AjaxCreateView, AjaxDeleteView +from InvenTree.views import AjaxUpdateView, AjaxCreateView, AjaxDeleteView from InvenTree.helpers import str2bool diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index 4d667eb356..37ccb639a0 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -71,7 +71,7 @@ class PartSerializer(serializers.ModelSerializer): 'variant', 'description', 'keywords', - 'URL', + 'URL', 'total_stock', 'available_stock', 'units',