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')
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/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/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..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,8 @@ 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
class BuildIndex(ListView):
@@ -41,31 +40,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.
@@ -90,7 +99,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
@@ -217,7 +226,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)
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..88c6c11385 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',
@@ -118,7 +119,8 @@ class EditCategoryForm(HelperForm):
'parent',
'name',
'description',
- 'default_location'
+ 'default_location',
+ 'default_keywords',
]
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/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 5a609c0e59..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})
@@ -179,8 +187,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 +259,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..37ccb639a0 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 }} |
-