diff --git a/InvenTree/InvenTree/forms.py b/InvenTree/InvenTree/forms.py
index 4be8a28acb..56a1a116da 100644
--- a/InvenTree/InvenTree/forms.py
+++ b/InvenTree/InvenTree/forms.py
@@ -28,6 +28,7 @@ class HelperForm(forms.ModelForm):
self.helper = FormHelper()
self.helper.form_tag = False
+ self.helper.form_show_errors = True
"""
Create a default 'layout' for this form.
@@ -43,11 +44,6 @@ class HelperForm(forms.ModelForm):
valid = super(HelperForm, self).is_valid()
- # Check for errors from model validation
- # If none, disable crispy form errors
- if not self.errors:
- self.helper.form_show_errors = False
-
return valid
def rebuild_layout(self):
diff --git a/InvenTree/company/templates/company/supplier_part_create.html b/InvenTree/company/templates/company/supplier_part_create.html
new file mode 100644
index 0000000000..21c23f9075
--- /dev/null
+++ b/InvenTree/company/templates/company/supplier_part_create.html
@@ -0,0 +1,17 @@
+{% extends "modal_form.html" %}
+
+{% load i18n %}
+
+{% block pre_form_content %}
+{{ block.super }}
+
+{% if part %}
+
+ {% include "hover_image.html" with image=part.image %}
+ {{ part.full_name}}
+
+ {{ part.description }}
+
+{% endif %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/InvenTree/company/views.py b/InvenTree/company/views.py
index 2720f4ccac..e863fa1d72 100644
--- a/InvenTree/company/views.py
+++ b/InvenTree/company/views.py
@@ -291,7 +291,7 @@ class SupplierPartCreate(AjaxCreateView):
model = SupplierPart
form_class = EditSupplierPartForm
- ajax_template_name = 'modal_form.html'
+ ajax_template_name = 'company/supplier_part_create.html'
ajax_form_title = _('Create new Supplier Part')
context_object_name = 'part'
role_required = 'purchase_order.add'
@@ -304,6 +304,27 @@ class SupplierPartCreate(AjaxCreateView):
# TODO - What validation steps can be performed on the single_pricing field?
pass
+ def get_context_data(self):
+ """
+ Supply context data to the form
+ """
+
+ ctx = super().get_context_data()
+
+ # Add 'part' object
+ form = self.get_form()
+
+ part = form['part'].value()
+
+ try:
+ part = Part.objects.get(pk=part)
+ except (ValueError, Part.DoesNotExist):
+ part = None
+
+ ctx['part'] = part
+
+ return ctx
+
def save(self, form):
"""
If single_pricing is defined, add a price break for quantity=1
diff --git a/InvenTree/order/templates/order/order_wizard/select_parts.html b/InvenTree/order/templates/order/order_wizard/select_parts.html
index 9c7c90ac26..c93e26e363 100644
--- a/InvenTree/order/templates/order/order_wizard/select_parts.html
+++ b/InvenTree/order/templates/order/order_wizard/select_parts.html
@@ -11,11 +11,11 @@
{% if parts|length > 0 %}
- {% trans "Select suppliers." %}
+ {% trans "Select suppliers" %}
{% else %}
- {% trans "No purchaseable parts selected." %}
+ {% trans "No purchaseable parts selected" %}
{% endif %}
@@ -39,8 +39,8 @@
{{ part.full_name }} {{ part.description }}
- |
diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py
index 7bec904ce0..0eebe6617d 100644
--- a/InvenTree/part/serializers.py
+++ b/InvenTree/part/serializers.py
@@ -336,7 +336,7 @@ class PartStarSerializer(InvenTreeModelSerializer):
class BomItemSerializer(InvenTreeModelSerializer):
""" Serializer for BomItem object """
- price_range = serializers.CharField(read_only=True)
+ # price_range = serializers.CharField(read_only=True)
quantity = serializers.FloatField()
@@ -387,7 +387,7 @@ class BomItemSerializer(InvenTreeModelSerializer):
'sub_part_detail',
'quantity',
'reference',
- 'price_range',
+ # 'price_range',
'optional',
'overage',
'note',
diff --git a/InvenTree/part/templates/part/bom_upload/select_fields.html b/InvenTree/part/templates/part/bom_upload/select_fields.html
index bd0c222881..60f0efcfad 100644
--- a/InvenTree/part/templates/part/bom_upload/select_fields.html
+++ b/InvenTree/part/templates/part/bom_upload/select_fields.html
@@ -59,7 +59,7 @@
{% endfor %}
{% if col.duplicate %}
- Duplicate column selection
+ {% trans "Duplicate column selection" %}
{% endif %}
|
{% endfor %}
diff --git a/InvenTree/part/templates/part/bom_upload/select_parts.html b/InvenTree/part/templates/part/bom_upload/select_parts.html
index ede92c6c30..1ee5e8821b 100644
--- a/InvenTree/part/templates/part/bom_upload/select_parts.html
+++ b/InvenTree/part/templates/part/bom_upload/select_parts.html
@@ -63,7 +63,7 @@
{% for part in row.part_options %}
{% endfor %}
diff --git a/InvenTree/templates/js/bom.js b/InvenTree/templates/js/bom.js
index c55429faba..299045cfa5 100644
--- a/InvenTree/templates/js/bom.js
+++ b/InvenTree/templates/js/bom.js
@@ -140,6 +140,12 @@ function loadBomTable(table, options) {
});
}
+ // Set the parent ID of the multi-level table.
+ // We prepend this with the literal string value 'top-level-',
+ // because otherwise the unfortunate situation where BomItem.pk == BomItem.part.pk
+ // AND THIS BREAKS EVERYTHING
+ var parent_id = `top-level-${options.parent_id}`;
+
// Part column
cols.push(
{
@@ -197,11 +203,11 @@ function loadBomTable(table, options) {
text = parseFloat(text);
if (row.optional) {
- text += " ({% trans "Optional" %})";
+ text += ' ({% trans "Optional" %})';
}
if (row.overage) {
- text += " (+" + row.overage + ") ";
+ text += ` (${row.overage}) `;
}
return text;
@@ -228,6 +234,12 @@ function loadBomTable(table, options) {
}
});
+ /*
+
+ // TODO - Re-introduce the pricing column at a later stage,
+ // once the pricing has been "fixed"
+ // O.W. 2020-11-24
+
cols.push(
{
field: 'price_range',
@@ -241,6 +253,7 @@ function loadBomTable(table, options) {
}
}
});
+ */
}
// Part notes
@@ -327,9 +340,8 @@ function loadBomTable(table, options) {
table.inventreeTable({
treeEnable: !options.editable,
- rootParentId: options.parent_id,
+ rootParentId: parent_id,
idField: 'pk',
- //uniqueId: 'pk',
parentIdField: 'parentId',
treeShowField: 'sub_part',
showColumns: true,
@@ -338,12 +350,18 @@ function loadBomTable(table, options) {
search: true,
rowStyle: function(row, index) {
if (row.validated) {
- return {classes: 'rowvalid'};
+ return {
+ classes: 'rowvalid'
+ };
} else {
- return {classes: 'rowinvalid'};
+ return {
+ classes: 'rowinvalid'
+ };
}
},
- formatNoMatches: function() { return '{% trans "No BOM items found" %}'; },
+ formatNoMatches: function() {
+ return '{% trans "No BOM items found" %}';
+ },
clickToSelect: true,
queryParams: filters,
original: params,
@@ -376,7 +394,7 @@ function loadBomTable(table, options) {
}
// Set the parent ID of the top-level rows
- row.parentId = options.parent_id;
+ row.parentId = parent_id;
table.bootstrapTable('updateRow', idx, row, true);
diff --git a/InvenTree/templates/js/build.js b/InvenTree/templates/js/build.js
index ab6f0e4e0a..a3c7bd5186 100644
--- a/InvenTree/templates/js/build.js
+++ b/InvenTree/templates/js/build.js
@@ -243,6 +243,22 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
});
});
+ // Callback for 'buy' button
+ $(table).find('.button-buy').click(function() {
+ var pk = $(this).attr('pk');
+
+ var idx = $(this).closest('tr').attr('data-index');
+ var row = $(table).bootstrapTable('getData')[idx];
+
+ launchModalForm('{% url "order-parts" %}', {
+ data: {
+ parts: [
+ pk,
+ ]
+ }
+ });
+ });
+
// Callback for 'build' button
$(table).find('.button-build').click(function() {
var pk = $(this).attr('pk');
@@ -563,7 +579,7 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
}
if (row.sub_part_detail.purchaseable) {
- html += makeIconButton('fa-shopping-cart icon-blue', 'button-buy', row.sub_part, '{% trans "Order stock" %}', {disabled: true});
+ html += makeIconButton('fa-shopping-cart icon-blue', 'button-buy', row.sub_part, '{% trans "Order stock" %}');
}
html += makeIconButton('fa-sign-in-alt icon-green', 'button-add', row.sub_part, '{% trans "Allocate stock" %}');
diff --git a/InvenTree/templates/js/order.js b/InvenTree/templates/js/order.js
index 41a1b4c046..69d4f584d9 100644
--- a/InvenTree/templates/js/order.js
+++ b/InvenTree/templates/js/order.js
@@ -21,9 +21,16 @@ function newSupplierPartFromOrderWizard(e) {
e = e || window.event;
- var src = e.target || e.srcElement;
+ var src = e.srcElement || e.target;
- var part = $(src).attr('part-id');
+ var part = $(src).attr('part');
+
+ console.log('part: ' + part);
+
+ if (!part) {
+ part = $(src).closest('button').attr('part');
+ console.log('parent: ' + part);
+ }
launchModalForm("/supplier-part/new/", {
modal: '#modal-form-secondary',
@@ -125,7 +132,7 @@ function loadPurchaseOrderTable(table, options) {
name: 'purchaseorder',
groupBy: false,
original: options.params,
- formatNoMatches: function() { return "{% trans "No purchase orders found" %}"; },
+ formatNoMatches: function() { return '{% trans "No purchase orders found" %}'; },
columns: [
{
field: 'pk',
@@ -208,7 +215,7 @@ function loadSalesOrderTable(table, options) {
name: 'salesorder',
groupBy: false,
original: options.params,
- formatNoMatches: function() { return "{% trans "No sales orders found" %}"; },
+ formatNoMatches: function() { return '{% trans "No sales orders found" %}'; },
columns: [
{
field: 'pk',
@@ -265,7 +272,7 @@ function loadSalesOrderTable(table, options) {
{
sortable: true,
field: 'shipment_date',
- title: "{% trans "Shipment Date" %}",
+ title: '{% trans "Shipment Date" %}',
},
{
sortable: true,
diff --git a/InvenTree/templates/modal_form.html b/InvenTree/templates/modal_form.html
index 0a5ae59916..94bf032579 100644
--- a/InvenTree/templates/modal_form.html
+++ b/InvenTree/templates/modal_form.html
@@ -1,3 +1,5 @@
+{% load i18n %}
+
{% if form.pre_form_info %}
@@ -9,18 +11,6 @@
{{ form.pre_form_warning }}
{% endif %}
-{% block non_field_error %}
-{% if form.non_field_errors %}
-
-
Error Submitting Form:
-
- {% for error in form.non_field_errors %}
- {{ error }}
- {% endfor %}
-
-
-{% endif %}
-{% endblock %}
{% block pre_form_content %}
@@ -41,4 +31,4 @@
{% endblock %}
{% block post_form_content %}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/tasks.py b/tasks.py
index 30641c7c81..69c53b83d4 100644
--- a/tasks.py
+++ b/tasks.py
@@ -102,6 +102,16 @@ def install(c):
print("Config file 'config.yaml' does not exist - copying from template.")
copyfile(CONFIG_TEMPLATE_FILE, CONFIG_FILE)
+
+@task
+def shell(c):
+ """
+ Open a python shell with access to the InvenTree database models.
+ """
+
+ manage(c, 'shell', pty=True)
+
+
@task
def superuser(c):
"""