mirror of
https://github.com/inventree/InvenTree.git
synced 2025-08-13 07:10:53 +00:00
Merge branch 'inventree:master' into matmair/issue2279
This commit is contained in:
@@ -40,6 +40,6 @@ class PartConfig(AppConfig):
|
||||
item.part.trackable = True
|
||||
item.part.clean()
|
||||
item.part.save()
|
||||
except (OperationalError, ProgrammingError):
|
||||
except (OperationalError, ProgrammingError): # pragma: no cover
|
||||
# Exception if the database has not been migrated yet
|
||||
pass
|
||||
|
@@ -11,7 +11,7 @@ import InvenTree.validators
|
||||
import part.models
|
||||
|
||||
|
||||
def attach_file(instance, filename):
|
||||
def attach_file(instance, filename): # pragma: no cover
|
||||
"""
|
||||
Generate a filename for the uploaded attachment.
|
||||
|
||||
|
@@ -10,7 +10,7 @@ def update_tree(apps, schema_editor):
|
||||
Part.objects.rebuild()
|
||||
|
||||
|
||||
def nupdate_tree(apps, schema_editor):
|
||||
def nupdate_tree(apps, schema_editor): # pragma: no cover
|
||||
pass
|
||||
|
||||
|
||||
|
@@ -33,7 +33,7 @@ def migrate_currencies(apps, schema_editor):
|
||||
|
||||
remap = {}
|
||||
|
||||
for index, row in enumerate(results):
|
||||
for index, row in enumerate(results): # pragma: no cover
|
||||
pk, suffix, description = row
|
||||
|
||||
suffix = suffix.strip().upper()
|
||||
@@ -57,7 +57,7 @@ def migrate_currencies(apps, schema_editor):
|
||||
|
||||
count = 0
|
||||
|
||||
for index, row in enumerate(results):
|
||||
for index, row in enumerate(results): # pragma: no cover
|
||||
pk, cost, currency_id, price, price_currency = row
|
||||
|
||||
# Copy the 'cost' field across to the 'price' field
|
||||
@@ -71,10 +71,10 @@ def migrate_currencies(apps, schema_editor):
|
||||
|
||||
count += 1
|
||||
|
||||
if count > 0:
|
||||
if count > 0: # pragma: no cover
|
||||
print(f"Updated {count} SupplierPriceBreak rows")
|
||||
|
||||
def reverse_currencies(apps, schema_editor):
|
||||
def reverse_currencies(apps, schema_editor): # pragma: no cover
|
||||
"""
|
||||
Reverse the "update" process.
|
||||
|
||||
|
@@ -1498,6 +1498,16 @@ class Part(MPTTModel):
|
||||
def has_bom(self):
|
||||
return self.get_bom_items().count() > 0
|
||||
|
||||
def get_trackable_parts(self):
|
||||
"""
|
||||
Return a queryset of all trackable parts in the BOM for this part
|
||||
"""
|
||||
|
||||
queryset = self.get_bom_items()
|
||||
queryset = queryset.filter(sub_part__trackable=True)
|
||||
|
||||
return queryset
|
||||
|
||||
@property
|
||||
def has_trackable_parts(self):
|
||||
"""
|
||||
@@ -1505,11 +1515,7 @@ class Part(MPTTModel):
|
||||
This is important when building the part.
|
||||
"""
|
||||
|
||||
for bom_item in self.get_bom_items().all():
|
||||
if bom_item.sub_part.trackable:
|
||||
return True
|
||||
|
||||
return False
|
||||
return self.get_trackable_parts().count() > 0
|
||||
|
||||
@property
|
||||
def bom_count(self):
|
||||
|
@@ -960,6 +960,9 @@ class BomExtractSerializer(serializers.Serializer):
|
||||
"""
|
||||
quantity = self.find_matching_data(row, 'quantity', self.dataset.headers)
|
||||
|
||||
# Ensure quantity field is provided
|
||||
row['quantity'] = quantity
|
||||
|
||||
if quantity is None:
|
||||
row_error['quantity'] = _('Quantity not provided')
|
||||
else:
|
||||
|
@@ -1,99 +1,2 @@
|
||||
{% extends "part/import_wizard/part_upload.html" %}
|
||||
{% load inventree_extras %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
{% block form_alert %}
|
||||
{% if missing_columns and missing_columns|length > 0 %}
|
||||
<div class='alert alert-danger alert-block' role='alert'>
|
||||
{% trans "Missing selections for the following required columns" %}:
|
||||
<br>
|
||||
<ul>
|
||||
{% for col in missing_columns %}
|
||||
<li>{{ col }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if duplicates and duplicates|length > 0 %}
|
||||
<div class='alert alert-danger alert-block' role='alert'>
|
||||
{% trans "Duplicate selections found, see below. Fix them then retry submitting." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock form_alert %}
|
||||
|
||||
{% block form_buttons_top %}
|
||||
{% if wizard.steps.prev %}
|
||||
<button name="wizard_goto_step" type="submit" value="{{ wizard.steps.prev }}" class="save btn btn-outline-secondary">{% trans "Previous Step" %}</button>
|
||||
{% endif %}
|
||||
<button type="submit" class="save btn btn-outline-secondary">{% trans "Submit Selections" %}</button>
|
||||
{% endblock form_buttons_top %}
|
||||
|
||||
{% block form_content %}
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "File Fields" %}</th>
|
||||
<th></th>
|
||||
{% for col in form %}
|
||||
<th>
|
||||
<div>
|
||||
<input type='hidden' name='col_name_{{ forloop.counter0 }}' value='{{ col.name }}'/>
|
||||
{{ col.name }}
|
||||
<button class='btn btn-outline-secondary btn-remove' onClick='removeColFromBomWizard()' id='del_col_{{ forloop.counter0 }}' style='display: inline; float: right;' title='{% trans "Remove column" %}'>
|
||||
<span col_id='{{ forloop.counter0 }}' class='fas fa-trash-alt icon-red'></span>
|
||||
</button>
|
||||
</div>
|
||||
</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{% trans "Match Fields" %}</td>
|
||||
<td></td>
|
||||
{% for col in form %}
|
||||
<td>
|
||||
{{ col }}
|
||||
{% for duplicate in duplicates %}
|
||||
{% if duplicate == col.value %}
|
||||
<div class='alert alert-danger alert-block text-center' role='alert' style='padding:2px; margin-top:6px; margin-bottom:2px'>
|
||||
<strong>{% trans "Duplicate selection" %}</strong>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% for row in rows %}
|
||||
{% with forloop.counter as row_index %}
|
||||
<tr>
|
||||
<td style='width: 32px;'>
|
||||
<button class='btn btn-outline-secondary btn-remove' onClick='removeRowFromBomWizard()' id='del_row_{{ row_index }}' style='display: inline; float: left;' title='{% trans "Remove row" %}'>
|
||||
<span row_id='{{ row_index }}' class='fas fa-trash-alt icon-red'></span>
|
||||
</button>
|
||||
</td>
|
||||
<td style='text-align: left;'>{{ row_index }}</td>
|
||||
{% for item in row.data %}
|
||||
<td>
|
||||
<input type='hidden' name='row_{{ row_index }}_col_{{ forloop.counter0 }}' value='{{ item }}'/>
|
||||
{{ item }}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
{% endblock form_content %}
|
||||
|
||||
{% block form_buttons_bottom %}
|
||||
{% endblock form_buttons_bottom %}
|
||||
|
||||
{% block js_ready %}
|
||||
{{ block.super }}
|
||||
|
||||
$('.fieldselect').select2({
|
||||
width: '100%',
|
||||
matcher: partialMatcher,
|
||||
});
|
||||
|
||||
{% endblock %}
|
||||
{% include "patterns/wizard/match_fields.html" %}
|
@@ -10,51 +10,10 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class='panel'>
|
||||
<div class='panel-heading'>
|
||||
<h4>
|
||||
{% trans "Import Parts from File" %}
|
||||
{{ wizard.form.media }}
|
||||
</h4>
|
||||
</div>
|
||||
<div class='panel-content'>
|
||||
{% if roles.part.change %}
|
||||
|
||||
<p>{% blocktrans with step=wizard.steps.step1 count=wizard.steps.count %}Step {{step}} of {{count}}{% endblocktrans %}
|
||||
{% if description %}- {{ description }}{% endif %}</p>
|
||||
|
||||
{% block form_alert %}
|
||||
{% endblock form_alert %}
|
||||
|
||||
<form action="" method="post" class='js-modal-form' enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{% load crispy_forms_tags %}
|
||||
|
||||
{% block form_buttons_top %}
|
||||
{% endblock form_buttons_top %}
|
||||
|
||||
<table class='table table-striped' style='margin-top: 12px; margin-bottom: 0px'>
|
||||
{{ wizard.management_form }}
|
||||
{% block form_content %}
|
||||
{% crispy wizard.form %}
|
||||
{% endblock form_content %}
|
||||
</table>
|
||||
|
||||
{% block form_buttons_bottom %}
|
||||
{% if wizard.steps.prev %}
|
||||
<button name="wizard_goto_step" type="submit" value="{{ wizard.steps.prev }}" class="save btn btn-outline-secondary">{% trans "Previous Step" %}</button>
|
||||
{% endif %}
|
||||
<button type="submit" class="save btn btn-outline-secondary">{% trans "Upload File" %}</button>
|
||||
</form>
|
||||
{% endblock form_buttons_bottom %}
|
||||
|
||||
{% else %}
|
||||
<div class='alert alert-danger alert-block' role='alert'>
|
||||
{% trans "Unsuffitient privileges." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% trans "Import Parts from File" as header_text %}
|
||||
{% roles.part.change as upload_go_ahead %}
|
||||
{% trans "Unsuffitient privileges." as error_text %}
|
||||
{% include "patterns/wizard/upload.html" with header_text=header_text upload_go_ahead=upload_go_ahead error_text=error_text %}
|
||||
{% endblock %}
|
||||
|
||||
{% block js_ready %}
|
||||
|
@@ -14,7 +14,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
<a href='#' id='breadcrumb-tree-toggle' class="breadcrumb-item"><i class="fas fa-bars"></i></a>
|
||||
<a href='#' id='breadcrumb-tree-toggle' class="breadcrumb-item"><span class="fas fa-bars"></span></a>
|
||||
{% if part %}
|
||||
{% include "part/cat_link.html" with category=part.category part=part %}
|
||||
{% else %}
|
||||
|
@@ -89,8 +89,11 @@ $('#bom-upload').click(function() {
|
||||
},
|
||||
title: '{% trans "Upload BOM File" %}',
|
||||
onSuccess: function(response) {
|
||||
$('#bom-upload').hide();
|
||||
|
||||
// Clear existing entries from the table
|
||||
$('.bom-import-row').remove();
|
||||
|
||||
// Disable the "submit" button
|
||||
$('#bom-submit').show();
|
||||
|
||||
constructBomUploadTable(response);
|
||||
|
@@ -855,7 +855,7 @@ class PartAPIAggregationTest(InvenTreeAPITestCase):
|
||||
return part
|
||||
|
||||
# We should never get here!
|
||||
self.assertTrue(False)
|
||||
self.assertTrue(False) # pragma: no cover
|
||||
|
||||
def test_stock_quantity(self):
|
||||
"""
|
||||
|
@@ -55,7 +55,7 @@ class BomItemTest(TestCase):
|
||||
with self.assertRaises(django_exceptions.ValidationError):
|
||||
# A validation error should be raised here
|
||||
item = BomItem.objects.create(part=self.bob, sub_part=self.bob, quantity=7)
|
||||
item.clean()
|
||||
item.clean() # pragma: no cover
|
||||
|
||||
def test_integer_quantity(self):
|
||||
"""
|
||||
|
@@ -127,7 +127,7 @@ class CategoryTest(TestCase):
|
||||
|
||||
with self.assertRaises(ValidationError) as err:
|
||||
cat.full_clean()
|
||||
cat.save()
|
||||
cat.save() # pragma: no cover
|
||||
|
||||
self.assertIn('Illegal character in name', str(err.exception.error_dict.get('name')))
|
||||
|
||||
@@ -160,10 +160,6 @@ class CategoryTest(TestCase):
|
||||
|
||||
self.assertEqual(str(self.fasteners.default_location), 'Office/Drawer_1 - In my desk')
|
||||
|
||||
# Test that parts in this location return the same default location, too
|
||||
for p in self.fasteners.children.all():
|
||||
self.assert_equal(p.get_default_location().pathstring, 'Office/Drawer_1')
|
||||
|
||||
# Any part under electronics should default to 'Home'
|
||||
r1 = Part.objects.get(name='R_2K2_0805')
|
||||
self.assertIsNone(r1.default_location)
|
||||
|
@@ -44,7 +44,7 @@ class TestParams(TestCase):
|
||||
with self.assertRaises(django_exceptions.ValidationError):
|
||||
t3 = PartParameterTemplate(name='aBcde', units='dd')
|
||||
t3.full_clean()
|
||||
t3.save()
|
||||
t3.save() # pragma: no cover
|
||||
|
||||
|
||||
class TestCategoryTemplates(TransactionTestCase):
|
||||
|
@@ -111,7 +111,7 @@ class PartTest(TestCase):
|
||||
|
||||
try:
|
||||
part.save()
|
||||
self.assertTrue(False)
|
||||
self.assertTrue(False) # pragma: no cover
|
||||
except:
|
||||
pass
|
||||
|
||||
|
Reference in New Issue
Block a user