diff --git a/InvenTree/InvenTree/helpers.py b/InvenTree/InvenTree/helpers.py
index 5492f13a05..6b29adb4c4 100644
--- a/InvenTree/InvenTree/helpers.py
+++ b/InvenTree/InvenTree/helpers.py
@@ -14,6 +14,41 @@ from django.core.exceptions import ValidationError
from django.utils.translation import ugettext as _
from .version import inventreeVersion, inventreeInstanceName
+from .settings import MEDIA_URL, STATIC_URL
+
+
+def getMediaUrl(filename):
+ """
+ Return the qualified access path for the given file,
+ under the media directory.
+ """
+
+ return os.path.join(MEDIA_URL, str(filename))
+
+
+def getStaticUrl(filename):
+ """
+ Return the qualified access path for the given file,
+ under the static media directory.
+ """
+
+ return os.path.join(STATIC_URL, str(filename))
+
+
+def getBlankImage():
+ """
+ Return the qualified path for the 'blank image' placeholder.
+ """
+
+ return getStaticUrl("img/blank_image.png")
+
+
+def getBlankThumbnail():
+ """
+ Return the qualified path for the 'blank image' thumbnail placeholder.
+ """
+
+ return getStaticUrl("img/blank_image.thumbnail.png")
def TestIfImage(img):
@@ -66,7 +101,7 @@ def isNull(text):
True if the text looks like a null value
"""
- return str(text).strip().lower() in ['top', 'null', 'none', 'empty', 'false', '-1']
+ return str(text).strip().lower() in ['top', 'null', 'none', 'empty', 'false', '-1', '']
def decimal2string(d):
diff --git a/InvenTree/InvenTree/tests.py b/InvenTree/InvenTree/tests.py
index 2511cf4318..d93a40e631 100644
--- a/InvenTree/InvenTree/tests.py
+++ b/InvenTree/InvenTree/tests.py
@@ -1,3 +1,4 @@
+
from django.test import TestCase
import django.core.exceptions as django_exceptions
from django.core.exceptions import ValidationError
@@ -7,6 +8,8 @@ from . import helpers
from mptt.exceptions import InvalidMove
+from decimal import Decimal
+
from stock.models import StockLocation
@@ -72,6 +75,29 @@ class TestHelpers(TestCase):
self.assertFalse(helpers.str2bool(s))
self.assertFalse(helpers.str2bool(s, test=False))
+ def test_isnull(self):
+
+ for s in ['null', 'none', '', '-1', 'false']:
+ self.assertTrue(helpers.isNull(s))
+
+ for s in ['yes', 'frog', 'llama', 'true']:
+ self.assertFalse(helpers.isNull(s))
+
+ def testStaticUrl(self):
+
+ self.assertEqual(helpers.getStaticUrl('test.jpg'), '/static/test.jpg')
+ self.assertEqual(helpers.getBlankImage(), '/static/img/blank_image.png')
+ self.assertEqual(helpers.getBlankThumbnail(), '/static/img/blank_image.thumbnail.png')
+
+ def testMediaUrl(self):
+
+ self.assertEqual(helpers.getMediaUrl('xx/yy.png'), '/media/xx/yy.png')
+
+ def testDecimal2String(self):
+
+ self.assertEqual(helpers.decimal2string(Decimal('1.2345000')), '1.2345')
+ self.assertEqual(helpers.decimal2string('test'), 'test')
+
class TestQuoteWrap(TestCase):
""" Tests for string wrapping """
diff --git a/InvenTree/InvenTree/version.py b/InvenTree/InvenTree/version.py
index ae812a2695..386409646a 100644
--- a/InvenTree/InvenTree/version.py
+++ b/InvenTree/InvenTree/version.py
@@ -5,7 +5,7 @@ Provides information on the current InvenTree version
import subprocess
from common.models import InvenTreeSetting
-INVENTREE_SW_VERSION = "0.0.10"
+INVENTREE_SW_VERSION = "0.0.11_pre"
def inventreeInstanceName():
diff --git a/InvenTree/company/apps.py b/InvenTree/company/apps.py
index c55451e882..949ee152c0 100644
--- a/InvenTree/company/apps.py
+++ b/InvenTree/company/apps.py
@@ -1,7 +1,36 @@
from __future__ import unicode_literals
+import os
+
from django.apps import AppConfig
+from django.db.utils import OperationalError, ProgrammingError
+from django.conf import settings
class CompanyConfig(AppConfig):
name = 'company'
+
+ def ready(self):
+ """
+ This function is called whenever the Company app is loaded.
+ """
+
+ self.generate_company_thumbs()
+
+ def generate_company_thumbs(self):
+
+ from .models import Company
+
+ print("InvenTree: Checking Company image thumbnails")
+
+ try:
+ for company in Company.objects.all():
+ if company.image:
+ url = company.image.thumbnail.name
+ loc = os.path.join(settings.MEDIA_ROOT, url)
+
+ if not os.path.exists(loc):
+ print("InvenTree: Generating thumbnail for Company '{c}'".format(c=company.name))
+ company.image.render_variations(replace=False)
+ except (OperationalError, ProgrammingError):
+ print("Could not generate Company thumbnails")
diff --git a/InvenTree/company/migrations/0014_auto_20200407_0116.py b/InvenTree/company/migrations/0014_auto_20200407_0116.py
new file mode 100644
index 0000000000..03985a1ef3
--- /dev/null
+++ b/InvenTree/company/migrations/0014_auto_20200407_0116.py
@@ -0,0 +1,20 @@
+# Generated by Django 2.2.10 on 2020-04-07 01:16
+
+import company.models
+from django.db import migrations
+import stdimage.models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('company', '0013_auto_20200406_0131'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='company',
+ name='image',
+ field=stdimage.models.StdImageField(blank=True, null=True, upload_to=company.models.rename_company_image),
+ ),
+ ]
diff --git a/InvenTree/company/models.py b/InvenTree/company/models.py
index 6f14700184..462b4ff847 100644
--- a/InvenTree/company/models.py
+++ b/InvenTree/company/models.py
@@ -17,10 +17,12 @@ from django.db.models import Sum
from django.apps import apps
from django.urls import reverse
-from django.conf import settings
from markdownx.models import MarkdownxField
+from stdimage.models import StdImageField
+
+from InvenTree.helpers import getMediaUrl, getBlankImage, getBlankThumbnail
from InvenTree.fields import InvenTreeURLField, RoundingDecimalField
from InvenTree.status_codes import OrderStatus
from common.models import Currency
@@ -90,7 +92,13 @@ class Company(models.Model):
link = InvenTreeURLField(blank=True, help_text=_('Link to external company information'))
- image = models.ImageField(upload_to=rename_company_image, max_length=255, null=True, blank=True)
+ image = StdImageField(
+ upload_to=rename_company_image,
+ null=True,
+ blank=True,
+ variations={'thumbnail': (128, 128)},
+ delete_orphans=True,
+ )
notes = MarkdownxField(blank=True)
@@ -110,10 +118,18 @@ class Company(models.Model):
""" Return the URL of the image for this company """
if self.image:
- return os.path.join(settings.MEDIA_URL, str(self.image.url))
+ return getMediaUrl(self.image.url)
else:
- return os.path.join(settings.STATIC_URL, 'img/blank_image.png')
+ return getBlankImage()
+ def get_thumbnail_url(self):
+ """ Return the URL for the thumbnail image for this Company """
+
+ if self.image:
+ return getMediaUrl(self.image.thumbnail.url)
+ else:
+ return getBlankThumbnail()
+
@property
def part_count(self):
""" The number of parts supplied by this company """
diff --git a/InvenTree/company/serializers.py b/InvenTree/company/serializers.py
index 161edd286e..935712a180 100644
--- a/InvenTree/company/serializers.py
+++ b/InvenTree/company/serializers.py
@@ -32,7 +32,7 @@ class CompanySerializer(InvenTreeModelSerializer):
url = serializers.CharField(source='get_absolute_url', read_only=True)
part_count = serializers.CharField(read_only=True)
- image = serializers.CharField(source='get_image_url', read_only=True)
+ image = serializers.CharField(source='get_thumbnail_url', read_only=True)
class Meta:
model = Company
@@ -64,7 +64,7 @@ class SupplierPartSerializer(InvenTreeModelSerializer):
part_detail = PartBriefSerializer(source='part', many=False, read_only=True)
supplier_name = serializers.CharField(source='supplier.name', read_only=True)
- supplier_logo = serializers.CharField(source='supplier.get_image_url', read_only=True)
+ supplier_logo = serializers.CharField(source='supplier.get_thumbnail_url', read_only=True)
pricing = serializers.CharField(source='unit_pricing', read_only=True)
diff --git a/InvenTree/company/templates/company/company_base.html b/InvenTree/company/templates/company/company_base.html
index 19ef2a0e60..8ec68a401c 100644
--- a/InvenTree/company/templates/company/company_base.html
+++ b/InvenTree/company/templates/company/company_base.html
@@ -46,7 +46,7 @@ InvenTree | {% trans "Company" %} - {{ company.name }}
{% if company.website %}
- |
+ |
{% trans "Website" %} |
{{ company.website }} |
diff --git a/InvenTree/locale/de/LC_MESSAGES/django.mo b/InvenTree/locale/de/LC_MESSAGES/django.mo
index 1632eb47b2..4038658f79 100644
Binary files a/InvenTree/locale/de/LC_MESSAGES/django.mo and b/InvenTree/locale/de/LC_MESSAGES/django.mo differ
diff --git a/InvenTree/locale/de/LC_MESSAGES/django.po b/InvenTree/locale/de/LC_MESSAGES/django.po
index 22ea2c52ad..212ae38002 100644
--- a/InvenTree/locale/de/LC_MESSAGES/django.po
+++ b/InvenTree/locale/de/LC_MESSAGES/django.po
@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2020-04-05 10:55+0000\n"
+"POT-Creation-Date: 2020-04-09 15:04+0000\n"
"PO-Revision-Date: 2020-02-02 08:07+0100\n"
"Last-Translator: Christian Schlüter \n"
"Language-Team: C \n"
@@ -17,30 +17,30 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Lokalize 19.12.0\n"
-#: InvenTree/helpers.py:201 order/models.py:164 order/models.py:215
+#: InvenTree/helpers.py:240 order/models.py:164 order/models.py:215
msgid "Invalid quantity provided"
msgstr "Keine gültige Menge"
-#: InvenTree/helpers.py:204
+#: InvenTree/helpers.py:243
msgid "Empty serial number string"
msgstr "Keine Seriennummer angegeben"
-#: InvenTree/helpers.py:225 InvenTree/helpers.py:242
+#: InvenTree/helpers.py:264 InvenTree/helpers.py:281
#, python-brace-format
msgid "Duplicate serial: {n}"
msgstr "Doppelte Seriennummer: {n}"
-#: InvenTree/helpers.py:229 InvenTree/helpers.py:232 InvenTree/helpers.py:235
-#: InvenTree/helpers.py:246
+#: InvenTree/helpers.py:268 InvenTree/helpers.py:271 InvenTree/helpers.py:274
+#: InvenTree/helpers.py:285
#, python-brace-format
msgid "Invalid group: {g}"
msgstr "Ungültige Gruppe: {g}"
-#: InvenTree/helpers.py:252
+#: InvenTree/helpers.py:291
msgid "No serial numbers found"
msgstr "Keine Seriennummern gefunden"
-#: InvenTree/helpers.py:256
+#: InvenTree/helpers.py:295
#, python-brace-format
msgid "Number of unique serial number ({s}) must match quantity ({q})"
msgstr ""
@@ -113,7 +113,7 @@ msgstr "Zerstört"
#: InvenTree/status_codes.py:98 build/templates/build/allocate_edit.html:28
#: build/templates/build/allocate_view.html:21
-#: part/templates/part/part_base.html:106 part/templates/part/tabs.html:21
+#: part/templates/part/part_base.html:109 part/templates/part/tabs.html:21
msgid "Allocated"
msgstr "Zugeordnet"
@@ -142,7 +142,7 @@ msgstr "Überschuss darf 100% nicht überschreiten"
msgid "Overage must be an integer value or a percentage"
msgstr "Überschuss muss eine Ganzzahl oder ein Prozentwert sein"
-#: InvenTree/views.py:548
+#: InvenTree/views.py:549
msgid "Database Statistics"
msgstr ""
@@ -186,7 +186,7 @@ msgstr "Bau-Status"
msgid "Batch code for this build output"
msgstr "Chargennummer für diese Bau-Ausgabe"
-#: build/models.py:97
+#: build/models.py:97 stock/models.py:331
msgid "Link to external URL"
msgstr "Link zu einer externen URL"
@@ -231,7 +231,7 @@ msgstr "Zuweisung aufheben"
#: build/templates/build/allocate_edit.html:19
#: build/templates/build/allocate_view.html:17
-#: build/templates/build/detail.html:17
+#: build/templates/build/detail.html:21
#: company/templates/company/detail_part.html:65
#: order/templates/order/order_wizard/select_parts.html:30
#: order/templates/order/purchase_order_detail.html:25
@@ -267,12 +267,12 @@ msgstr "Teile bestellen"
#: company/templates/company/supplier_part_base.html:50
#: company/templates/company/supplier_part_detail.html:27
#: order/templates/order/purchase_order_detail.html:26
-#: part/templates/part/detail.html:37
+#: part/templates/part/detail.html:38
msgid "Description"
msgstr "Beschreibung"
#: build/templates/build/allocate_view.html:22
-#: part/templates/part/part_base.html:112
+#: part/templates/part/part_base.html:115
msgid "On Order"
msgstr "bestellt"
@@ -293,65 +293,64 @@ msgstr ""
msgid "Build Details"
msgstr "Bau-Status"
-#: build/templates/build/detail.html:14
+#: build/templates/build/detail.html:16
msgid "Title"
msgstr "Titel"
-#: build/templates/build/detail.html:20
+#: build/templates/build/detail.html:26
#: company/templates/company/supplier_part_pricing.html:27
#: order/templates/order/order_wizard/select_parts.html:32
#: order/templates/order/purchase_order_detail.html:29
-#: stock/templates/stock/item_base.html:101
+#: stock/templates/stock/item_base.html:107
#: stock/templates/stock/stock_adjust.html:18
msgid "Quantity"
msgstr "Anzahl"
-#: build/templates/build/detail.html:23
+#: build/templates/build/detail.html:30
msgid "Stock Source"
msgstr "Lagerobjekt"
-#: build/templates/build/detail.html:28
+#: build/templates/build/detail.html:35
msgid "Stock can be taken from any available location."
msgstr "Bestand kann jedem verfügbaren Lagerort entnommen werden."
-#: build/templates/build/detail.html:33
+#: build/templates/build/detail.html:41
#: order/templates/order/order_base.html:71
-#: stock/templates/stock/item_base.html:158
+#: stock/templates/stock/item_base.html:174
msgid "Status"
msgstr "Status"
-#: build/templates/build/detail.html:37
-#: stock/templates/stock/item_base.html:107
+#: build/templates/build/detail.html:47
+#: stock/templates/stock/item_base.html:114
msgid "Batch"
msgstr "Los"
-#: build/templates/build/detail.html:42
-#: company/templates/company/detail_part.html:90
+#: build/templates/build/detail.html:54
#: company/templates/company/supplier_part_base.html:47
#: company/templates/company/supplier_part_detail.html:24
-#: part/templates/part/part_base.html:81
-#: stock/templates/stock/item_base.html:131
-msgid "URL"
-msgstr "URL"
+#: part/templates/part/detail.html:67 part/templates/part/part_base.html:84
+#: stock/templates/stock/item_base.html:142
+msgid "External Link"
+msgstr ""
-#: build/templates/build/detail.html:46
-#: order/templates/order/order_base.html:75
+#: build/templates/build/detail.html:60
+#: order/templates/order/order_base.html:83
msgid "Created"
msgstr "Erstellt"
-#: build/templates/build/detail.html:50
+#: build/templates/build/detail.html:66
msgid "Enough Parts?"
msgstr "Genügend Teile?"
-#: build/templates/build/detail.html:53
+#: build/templates/build/detail.html:69
msgid "Yes"
msgstr "Ja"
-#: build/templates/build/detail.html:55
+#: build/templates/build/detail.html:71
msgid "No"
msgstr "Nein"
-#: build/templates/build/detail.html:62
+#: build/templates/build/detail.html:79
msgid "Completed"
msgstr "Fertig"
@@ -380,7 +379,7 @@ msgstr "Details"
msgid "Outputs"
msgstr ""
-#: build/templates/build/tabs.html:11 company/models.py:248
+#: build/templates/build/tabs.html:11 company/models.py:264
#: company/templates/company/tabs.html:26 order/templates/order/tabs.html:15
#: part/templates/part/tabs.html:58 stock/templates/stock/tabs.html:17
msgid "Notes"
@@ -571,81 +570,81 @@ msgstr ""
msgid "Delete Currency"
msgstr ""
-#: company/models.py:74
+#: company/models.py:76
msgid "Company name"
msgstr "Firmenname"
-#: company/models.py:76
+#: company/models.py:78
msgid "Description of the company"
msgstr "Firmenbeschreibung"
-#: company/models.py:78
+#: company/models.py:80
msgid "Company website URL"
msgstr "Firmenwebsite"
-#: company/models.py:81
+#: company/models.py:83
msgid "Company address"
msgstr "Firmenadresse"
-#: company/models.py:84
+#: company/models.py:86
msgid "Contact phone number"
msgstr "Kontakt-Tel."
-#: company/models.py:86
+#: company/models.py:88
msgid "Contact email address"
msgstr "Kontakt-Email"
-#: company/models.py:89
+#: company/models.py:91
msgid "Point of contact"
msgstr "Anlaufstelle"
-#: company/models.py:91
+#: company/models.py:93
msgid "Link to external company information"
msgstr "Link auf externe Firmeninformation"
-#: company/models.py:97
+#: company/models.py:105
msgid "Do you sell items to this company?"
msgstr "Verkaufen Sie Teile an diese Firma?"
-#: company/models.py:99
+#: company/models.py:107
msgid "Do you purchase items from this company?"
msgstr "Kaufen Sie Teile von dieser Firma?"
-#: company/models.py:229
+#: company/models.py:245
msgid "Select part"
msgstr "Teil auswählen"
-#: company/models.py:235
+#: company/models.py:251
msgid "Select supplier"
msgstr "Zulieferer auswählen"
-#: company/models.py:238
+#: company/models.py:254
msgid "Supplier stock keeping unit"
msgstr "Stock Keeping Units (SKU) des Zulieferers"
-#: company/models.py:240 company/templates/company/detail_part.html:81
+#: company/models.py:256 company/templates/company/detail_part.html:81
#: company/templates/company/supplier_part_base.html:53
#: company/templates/company/supplier_part_detail.html:30
msgid "Manufacturer"
msgstr "Hersteller"
-#: company/models.py:242
+#: company/models.py:258
msgid "Manufacturer part number"
msgstr "Hersteller-Teilenummer"
-#: company/models.py:244
+#: company/models.py:260
msgid "URL for external supplier part link"
msgstr "Teil-URL des Zulieferers"
-#: company/models.py:246
+#: company/models.py:262
msgid "Supplier part description"
msgstr "Zuliefererbeschreibung des Teils"
-#: company/models.py:250
+#: company/models.py:266
msgid "Minimum charge (e.g. stocking fee)"
msgstr "Mindestpreis"
-#: company/models.py:252
+#: company/models.py:268
msgid "Part packaging"
msgstr "Teile-Packaging"
@@ -653,24 +652,24 @@ msgstr "Teile-Packaging"
msgid "Company"
msgstr "Firma"
-#: company/templates/company/company_base.html:48
+#: company/templates/company/company_base.html:50
#: company/templates/company/index.html:59
msgid "Website"
msgstr ""
-#: company/templates/company/company_base.html:53
+#: company/templates/company/company_base.html:57
msgid "Address"
msgstr ""
-#: company/templates/company/company_base.html:58
+#: company/templates/company/company_base.html:64
msgid "Phone"
msgstr ""
-#: company/templates/company/company_base.html:63
+#: company/templates/company/company_base.html:71
msgid "Email"
msgstr ""
-#: company/templates/company/company_base.html:68
+#: company/templates/company/company_base.html:78
msgid "Contact"
msgstr ""
@@ -680,18 +679,18 @@ msgstr ""
msgid "Company Details"
msgstr "Firmenbemerkungen"
-#: company/templates/company/detail.html:13
-#: stock/templates/stock/item_base.html:125
+#: company/templates/company/detail.html:16
+#: stock/templates/stock/item_base.html:135
msgid "Customer"
msgstr "Kunde"
-#: company/templates/company/detail.html:17
+#: company/templates/company/detail.html:21
#: company/templates/company/index.html:46
#: company/templates/company/supplier_part_base.html:44
#: company/templates/company/supplier_part_detail.html:21
-#: order/templates/order/order_base.html:67
+#: order/templates/order/order_base.html:66
#: order/templates/order/order_wizard/select_pos.html:30
-#: stock/templates/stock/item_base.html:137
+#: stock/templates/stock/item_base.html:149
msgid "Supplier"
msgstr "Zulieferer"
@@ -723,6 +722,10 @@ msgstr "Anhang löschen"
msgid "SKU"
msgstr ""
+#: company/templates/company/detail_part.html:90
+msgid "Link"
+msgstr ""
+
#: company/templates/company/detail_purchase_orders.html:8
#: company/templates/company/tabs.html:15 part/templates/part/tabs.html:43
msgid "Purchase Orders"
@@ -763,8 +766,8 @@ msgstr "Zulieferer"
msgid "ID"
msgstr ""
-#: company/templates/company/index.html:69 part/templates/part/category.html:81
-#: templates/navbar.html:10 templates/stats.html:7 templates/stats.html:10
+#: company/templates/company/index.html:69 part/templates/part/category.html:83
+#: templates/navbar.html:10 templates/stats.html:8 templates/stats.html:17
msgid "Parts"
msgstr "Teile"
@@ -780,7 +783,7 @@ msgstr ""
#: company/templates/company/supplier_part_base.html:6
#: company/templates/company/supplier_part_base.html:13
-#: stock/templates/stock/item_base.html:141
+#: stock/templates/stock/item_base.html:154
msgid "Supplier Part"
msgstr "Zulieferer-Teil"
@@ -1003,7 +1006,7 @@ msgid "Order notes"
msgstr "Bestell-Notizen"
#: order/models.py:162 order/models.py:213 part/views.py:1119
-#: stock/models.py:463
+#: stock/models.py:467
msgid "Quantity must be greater than zero"
msgstr "Anzahl muss größer Null sein"
@@ -1027,7 +1030,7 @@ msgstr "Position - Referenz"
msgid "Line item notes"
msgstr "Position - Notizen"
-#: order/models.py:298 stock/templates/stock/item_base.html:119
+#: order/models.py:298 stock/templates/stock/item_base.html:128
msgid "Purchase Order"
msgstr "Kaufvertrag"
@@ -1039,15 +1042,15 @@ msgstr "Zulieferer-Teil"
msgid "Number of items received"
msgstr "Empfangene Objekt-Anzahl"
-#: order/templates/order/order_base.html:64
+#: order/templates/order/order_base.html:61
msgid "Purchase Order Details"
msgstr "Bestelldetails"
-#: order/templates/order/order_base.html:80
+#: order/templates/order/order_base.html:89
msgid "Issued"
msgstr "Aufgegeben"
-#: order/templates/order/order_base.html:86
+#: order/templates/order/order_base.html:96
#: order/templates/order/purchase_order_detail.html:31
msgid "Received"
msgstr "Empfangen"
@@ -1362,173 +1365,173 @@ msgstr "Eintragsmenge zur Preisberechnung"
msgid "Select currency for price calculation"
msgstr "Währung zur Preisberechnung wählen"
-#: part/models.py:62
+#: part/models.py:61
msgid "Default location for parts in this category"
msgstr "Standard-Standort für Teile dieser Kategorie"
-#: part/models.py:65
+#: part/models.py:64
msgid "Default keywords for parts in this category"
msgstr "Standard-Stichworte für Teile dieser Kategorie"
-#: part/models.py:339
+#: part/models.py:338
msgid "Part must be unique for name, IPN and revision"
msgstr "Namen, Teile- und Revisionsnummern müssen eindeutig sein"
-#: part/models.py:353
+#: part/models.py:352
msgid "Part cannot be a template part if it is a variant of another part"
msgstr "Teil kann keine Vorlage sein wenn es Variante eines anderen Teils ist"
-#: part/models.py:354
+#: part/models.py:353
msgid "Part cannot be a variant of another part if it is already a template"
msgstr ""
"Teil kann keine Variante eines anderen Teils sein wenn es bereits eine "
"Vorlage ist"
-#: part/models.py:358 part/templates/part/detail.html:18
+#: part/models.py:357 part/templates/part/detail.html:19
msgid "Part name"
msgstr "Name des Teils"
-#: part/models.py:362
+#: part/models.py:361
msgid "Is this part a template part?"
msgstr "Ist dieses Teil eine Vorlage?"
-#: part/models.py:371
+#: part/models.py:370
msgid "Is this part a variant of another part?"
msgstr "Ist dieses Teil eine Variante eines anderen Teils?"
-#: part/models.py:373
+#: part/models.py:372
msgid "Part description"
msgstr "Beschreibung des Teils"
-#: part/models.py:375
+#: part/models.py:374
msgid "Part keywords to improve visibility in search results"
msgstr "Schlüsselworte um die Sichtbarkeit in Suchergebnissen zu verbessern"
-#: part/models.py:380
+#: part/models.py:379
msgid "Part category"
msgstr "Teile-Kategorie"
-#: part/models.py:382
+#: part/models.py:381
msgid "Internal Part Number"
msgstr "Interne Teilenummer"
-#: part/models.py:384
+#: part/models.py:383
msgid "Part revision or version number"
msgstr "Revisions- oder Versionsnummer"
-#: part/models.py:386
+#: part/models.py:385
msgid "Link to extenal URL"
msgstr "Link zu einer Externen URL"
-#: part/models.py:398
+#: part/models.py:397
msgid "Where is this item normally stored?"
msgstr "Wo wird dieses Teil normalerweise gelagert?"
-#: part/models.py:442
+#: part/models.py:441
msgid "Default supplier part"
msgstr "Standard-Zulieferer?"
-#: part/models.py:445
+#: part/models.py:444
msgid "Minimum allowed stock level"
msgstr "Minimal zulässiger Lagerbestand"
-#: part/models.py:447
+#: part/models.py:446
msgid "Stock keeping units for this part"
msgstr "Stock Keeping Units (SKU) für dieses Teil"
-#: part/models.py:449
+#: part/models.py:448
msgid "Can this part be built from other parts?"
msgstr "Kann dieses Teil aus anderen Teilen angefertigt werden?"
-#: part/models.py:451
+#: part/models.py:450
msgid "Can this part be used to build other parts?"
msgstr "Kann dieses Teil zum Bau von anderen genutzt werden?"
-#: part/models.py:453
+#: part/models.py:452
msgid "Does this part have tracking for unique items?"
msgstr "Hat dieses Teil Tracking für einzelne Objekte?"
-#: part/models.py:455
+#: part/models.py:454
msgid "Can this part be purchased from external suppliers?"
msgstr "Kann dieses Teil von externen Zulieferern gekauft werden?"
-#: part/models.py:457
+#: part/models.py:456
msgid "Can this part be sold to customers?"
msgstr "Kann dieses Teil an Kunden verkauft werden?"
-#: part/models.py:459
+#: part/models.py:458
msgid "Is this part active?"
msgstr "Ist dieses Teil aktiv?"
-#: part/models.py:461
+#: part/models.py:460
msgid "Is this a virtual part, such as a software product or license?"
msgstr "Ist dieses Teil virtuell, wie zum Beispiel eine Software oder Lizenz?"
-#: part/models.py:463
+#: part/models.py:462
msgid "Part notes - supports Markdown formatting"
msgstr "Bemerkungen - unterstüzt Markdown-Formatierung"
-#: part/models.py:465
+#: part/models.py:464
msgid "Stored BOM checksum"
msgstr "Prüfsumme der Stückliste gespeichert"
-#: part/models.py:1041
+#: part/models.py:1040
msgid "Parameter template name must be unique"
msgstr "Vorlagen-Name des Parameters muss eindeutig sein"
-#: part/models.py:1046
+#: part/models.py:1045
msgid "Parameter Name"
msgstr "Name des Parameters"
-#: part/models.py:1048
+#: part/models.py:1047
msgid "Parameter Units"
msgstr "Parameter Einheit"
-#: part/models.py:1074
+#: part/models.py:1073
msgid "Parent Part"
msgstr "Ausgangsteil"
-#: part/models.py:1076
+#: part/models.py:1075
msgid "Parameter Template"
msgstr "Parameter Vorlage"
-#: part/models.py:1078
+#: part/models.py:1077
msgid "Parameter Value"
msgstr "Parameter Wert"
-#: part/models.py:1102
+#: part/models.py:1101
msgid "Select parent part"
msgstr "Ausgangsteil auswählen"
-#: part/models.py:1111
+#: part/models.py:1110
msgid "Select part to be used in BOM"
msgstr "Teil für die Nutzung in der Stückliste auswählen"
-#: part/models.py:1118
+#: part/models.py:1117
msgid "BOM quantity for this BOM item"
msgstr "Stücklisten-Anzahl für dieses Stücklisten-Teil"
-#: part/models.py:1121
+#: part/models.py:1120
msgid "Estimated build wastage quantity (absolute or percentage)"
msgstr "Geschätzter Ausschuss (absolut oder prozentual)"
-#: part/models.py:1124
+#: part/models.py:1123
msgid "BOM item reference"
msgstr "Referenz des Objekts auf der Stückliste"
-#: part/models.py:1127
+#: part/models.py:1126
msgid "BOM item notes"
msgstr "Notizen zum Stücklisten-Objekt"
-#: part/models.py:1129
+#: part/models.py:1128
msgid "BOM line checksum"
msgstr "Prüfsumme der Stückliste"
-#: part/models.py:1192
+#: part/models.py:1191
msgid "Part cannot be added to its own Bill of Materials"
msgstr "Teil kann nicht zu seiner eigenen Stückliste hinzugefügt werden"
-#: part/models.py:1199
+#: part/models.py:1198
#, python-brace-format
msgid "Part '{p1}' is used in BOM for '{p2}' (recursive)"
msgstr "Teil '{p1}' wird in Stückliste für Teil '{p2}' benutzt (rekursiv)"
@@ -1537,8 +1540,8 @@ msgstr "Teil '{p1}' wird in Stückliste für Teil '{p2}' benutzt (rekursiv)"
msgid "Part Attachments"
msgstr "Anhänge"
-#: part/templates/part/category.html:13 part/templates/part/category.html:76
-#: templates/stats.html:14
+#: part/templates/part/category.html:13 part/templates/part/category.html:78
+#: templates/stats.html:12
msgid "Part Categories"
msgstr "Teile-Kategorien"
@@ -1546,31 +1549,31 @@ msgstr "Teile-Kategorien"
msgid "All parts"
msgstr "Alle Teile"
-#: part/templates/part/category.html:34 part/templates/part/category.html:72
+#: part/templates/part/category.html:34 part/templates/part/category.html:73
msgid "Category Details"
msgstr "Kategorie-Details"
-#: part/templates/part/category.html:38
+#: part/templates/part/category.html:39
msgid "Category Path"
msgstr "Pfad zur Kategorie"
-#: part/templates/part/category.html:43
+#: part/templates/part/category.html:44
msgid "Category Description"
msgstr "Kategorie-Beschreibung"
-#: part/templates/part/category.html:49 part/templates/part/detail.html:73
+#: part/templates/part/category.html:50 part/templates/part/detail.html:74
msgid "Default Location"
msgstr "Standard-Lagerort"
-#: part/templates/part/category.html:56 part/templates/part/detail.html:50
+#: part/templates/part/category.html:57 part/templates/part/detail.html:51
msgid "Keywords"
msgstr "Schlüsselwörter"
-#: part/templates/part/category.html:62
+#: part/templates/part/category.html:63
msgid "Subcategories"
msgstr "Unter-Kategorien"
-#: part/templates/part/category.html:67
+#: part/templates/part/category.html:68
msgid "Parts (Including subcategories)"
msgstr "Teile (inklusive Unter-Kategorien)"
@@ -1578,119 +1581,115 @@ msgstr "Teile (inklusive Unter-Kategorien)"
msgid "Part Details"
msgstr "Teile-Details"
-#: part/templates/part/detail.html:24 part/templates/part/part_base.html:75
+#: part/templates/part/detail.html:25 part/templates/part/part_base.html:77
msgid "IPN"
msgstr "IPN (Interne Produktnummer)"
-#: part/templates/part/detail.html:31
+#: part/templates/part/detail.html:32
msgid "Revision"
msgstr "Revision"
-#: part/templates/part/detail.html:43
+#: part/templates/part/detail.html:44
msgid "Variant Of"
msgstr "Variante von"
-#: part/templates/part/detail.html:56
+#: part/templates/part/detail.html:57
msgid "Category"
msgstr "Kategorie"
-#: part/templates/part/detail.html:66
-msgid "Link"
-msgstr ""
-
-#: part/templates/part/detail.html:80
+#: part/templates/part/detail.html:81
msgid "Default Supplier"
msgstr "Standard-Zulieferer"
-#: part/templates/part/detail.html:88
+#: part/templates/part/detail.html:89
msgid "Units"
msgstr "Einheiten"
-#: part/templates/part/detail.html:94
+#: part/templates/part/detail.html:95
msgid "Minimum Stock"
msgstr "Minimaler Lagerbestand"
-#: part/templates/part/detail.html:100
+#: part/templates/part/detail.html:101
#, fuzzy
#| msgid "Create new Stock Item"
msgid "Creation Date"
msgstr "Neues Lagerobjekt hinzufügen"
-#: part/templates/part/detail.html:106
+#: part/templates/part/detail.html:107
#, fuzzy
#| msgid "Created"
msgid "Created By"
msgstr "Erstellt"
-#: part/templates/part/detail.html:113
+#: part/templates/part/detail.html:114
msgid "Responsible User"
msgstr ""
-#: part/templates/part/detail.html:122
+#: part/templates/part/detail.html:123
msgid "Virtual"
msgstr "Virtuell"
-#: part/templates/part/detail.html:125
+#: part/templates/part/detail.html:126
msgid "Part is virtual (not a physical part)"
msgstr "Teil ist virtuell (kein physisches Teil)"
-#: part/templates/part/detail.html:127
+#: part/templates/part/detail.html:128
msgid "Part is not a virtual part"
msgstr "Teil ist nicht virtuell"
-#: part/templates/part/detail.html:131
+#: part/templates/part/detail.html:132
msgid "Assembly"
msgstr "Baugruppe"
-#: part/templates/part/detail.html:134
+#: part/templates/part/detail.html:135
msgid "Part can be assembled from other parts"
msgstr "Teil kann aus anderen Teilen angefertigt werden"
-#: part/templates/part/detail.html:136
+#: part/templates/part/detail.html:137
msgid "Part cannot be assembled from other parts"
msgstr "Teil kann nicht aus anderen Teilen angefertigt werden"
-#: part/templates/part/detail.html:140
+#: part/templates/part/detail.html:141
msgid "Component"
msgstr "Komponente"
-#: part/templates/part/detail.html:143
+#: part/templates/part/detail.html:144
msgid "Part can be used in assemblies"
msgstr "Teil kann in Baugruppen benutzt werden"
-#: part/templates/part/detail.html:145
+#: part/templates/part/detail.html:146
msgid "Part cannot be used in assemblies"
msgstr "Teil kann nicht in Baugruppen benutzt werden"
-#: part/templates/part/detail.html:149
+#: part/templates/part/detail.html:150
msgid "Trackable"
msgstr "nachverfolgbar"
-#: part/templates/part/detail.html:152
+#: part/templates/part/detail.html:153
msgid "Part stock is tracked by serial number"
msgstr "Teilebestand in der Seriennummer hinterlegt"
-#: part/templates/part/detail.html:154
+#: part/templates/part/detail.html:155
msgid "Part stock is not tracked by serial number"
msgstr "Teilebestand ist nicht in der Seriennummer hinterlegt"
-#: part/templates/part/detail.html:158
+#: part/templates/part/detail.html:159
msgid "Purchaseable"
msgstr "Kaufbar"
-#: part/templates/part/detail.html:161 part/templates/part/detail.html:163
+#: part/templates/part/detail.html:162 part/templates/part/detail.html:164
msgid "Part can be purchased from external suppliers"
msgstr "Teil kann von externen Zulieferern gekauft werden"
-#: part/templates/part/detail.html:168
+#: part/templates/part/detail.html:169
msgid "Sellable"
msgstr "Verkaufbar"
-#: part/templates/part/detail.html:171
+#: part/templates/part/detail.html:172
msgid "Part can be sold to customers"
msgstr "Teil kann an Kunden verkauft werden"
-#: part/templates/part/detail.html:173
+#: part/templates/part/detail.html:174
msgid "Part cannot be sold to customers"
msgstr "Teil kann nicht an Kunden verkauft werden"
@@ -1722,23 +1721,23 @@ msgstr "Teil favorisieren"
msgid "Show pricing information"
msgstr "Kosteninformationen ansehen"
-#: part/templates/part/part_base.html:95
+#: part/templates/part/part_base.html:98
msgid "Available Stock"
msgstr "Verfügbarer Lagerbestand"
-#: part/templates/part/part_base.html:100
+#: part/templates/part/part_base.html:103
msgid "In Stock"
msgstr "Auf Lager"
-#: part/templates/part/part_base.html:121
+#: part/templates/part/part_base.html:124
msgid "Build Status"
msgstr "Bau-Status"
-#: part/templates/part/part_base.html:125
+#: part/templates/part/part_base.html:128
msgid "Can Build"
msgstr "Herstellbar?"
-#: part/templates/part/part_base.html:130
+#: part/templates/part/part_base.html:133
msgid "Underway"
msgstr "unterwegs"
@@ -1788,7 +1787,7 @@ msgstr "Varianten"
msgid "BOM"
msgstr "Stückliste"
-#: part/templates/part/tabs.html:28 stock/templates/stock/item_base.html:113
+#: part/templates/part/tabs.html:28 stock/templates/stock/item_base.html:121
#: templates/navbar.html:12
msgid "Build"
msgstr "Bau"
@@ -1932,93 +1931,93 @@ msgstr "Anzahl angeben"
msgid "Export Bill of Materials"
msgstr ""
-#: part/views.py:1406
+#: part/views.py:1404
#, fuzzy
#| msgid "Confirm part creation"
msgid "Confirm Part Deletion"
msgstr "Erstellen des Teils bestätigen"
-#: part/views.py:1413
+#: part/views.py:1411
msgid "Part was deleted"
msgstr ""
-#: part/views.py:1422
+#: part/views.py:1420
#, fuzzy
#| msgid "Part packaging"
msgid "Part Pricing"
msgstr "Teile-Packaging"
-#: part/views.py:1544
+#: part/views.py:1542
#, fuzzy
#| msgid "Parameter Template"
msgid "Create Part Parameter Template"
msgstr "Parameter Vorlage"
-#: part/views.py:1552
+#: part/views.py:1550
#, fuzzy
#| msgid "Parameter Template"
msgid "Edit Part Parameter Template"
msgstr "Parameter Vorlage"
-#: part/views.py:1559
+#: part/views.py:1557
#, fuzzy
#| msgid "Parameter Template"
msgid "Delete Part Parameter Template"
msgstr "Parameter Vorlage"
-#: part/views.py:1567
+#: part/views.py:1565
msgid "Create Part Parameter"
msgstr ""
-#: part/views.py:1617
+#: part/views.py:1615
#, fuzzy
#| msgid "Edit attachment"
msgid "Edit Part Parameter"
msgstr "Anhang bearbeiten"
-#: part/views.py:1631
+#: part/views.py:1629
#, fuzzy
#| msgid "Delete attachment"
msgid "Delete Part Parameter"
msgstr "Anhang löschen"
-#: part/views.py:1647
+#: part/views.py:1645
#, fuzzy
#| msgid "Part category"
msgid "Edit Part Category"
msgstr "Teile-Kategorie"
-#: part/views.py:1682
+#: part/views.py:1680
#, fuzzy
#| msgid "Select part category"
msgid "Delete Part Category"
msgstr "Teilekategorie wählen"
-#: part/views.py:1688
+#: part/views.py:1686
#, fuzzy
#| msgid "Part category"
msgid "Part category was deleted"
msgstr "Teile-Kategorie"
-#: part/views.py:1696
+#: part/views.py:1694
#, fuzzy
#| msgid "Select part category"
msgid "Create new part category"
msgstr "Teilekategorie wählen"
-#: part/views.py:1747
+#: part/views.py:1745
#, fuzzy
#| msgid "Created new stock item"
msgid "Create BOM item"
msgstr "Neues Lagerobjekt erstellt"
-#: part/views.py:1813
+#: part/views.py:1811
#, fuzzy
#| msgid "Edit Stock Item"
msgid "Edit BOM item"
msgstr "Lagerobjekt bearbeiten"
-#: part/views.py:1861
+#: part/views.py:1859
#, fuzzy
#| msgid "Confirm build completion"
msgid "Confim BOM item deletion"
@@ -2119,45 +2118,45 @@ msgstr "Objekt löschen wenn Lagerbestand aufgebraucht"
msgid "Stock Item Notes"
msgstr "Lagerobjekt-Notizen"
-#: stock/models.py:460
+#: stock/models.py:464
msgid "Quantity must be integer"
msgstr "Anzahl muss eine Ganzzahl sein"
-#: stock/models.py:466
+#: stock/models.py:470
#, python-brace-format
msgid "Quantity must not exceed available stock quantity ({n})"
msgstr "Anzahl darf nicht die verfügbare Anzahl überschreiten ({n})"
-#: stock/models.py:469 stock/models.py:472
+#: stock/models.py:473 stock/models.py:476
msgid "Serial numbers must be a list of integers"
msgstr "Seriennummern muss eine Liste von Ganzzahlen sein"
-#: stock/models.py:475
+#: stock/models.py:479
msgid "Quantity does not match serial numbers"
msgstr "Anzahl stimmt nicht mit den Seriennummern überein"
-#: stock/models.py:485
+#: stock/models.py:489
msgid "Serial numbers already exist: "
msgstr "Seriennummern existieren bereits:"
-#: stock/models.py:507
+#: stock/models.py:511
msgid "Add serial number"
msgstr "Seriennummer hinzufügen"
-#: stock/models.py:510
+#: stock/models.py:514
#, python-brace-format
msgid "Serialized {n} items"
msgstr "{n} Teile serialisiert"
-#: stock/models.py:810
+#: stock/models.py:814
msgid "Tracking entry title"
msgstr "Name des Eintrags-Trackings"
-#: stock/models.py:812
+#: stock/models.py:816
msgid "Entry notes"
msgstr "Eintrags-Notizen"
-#: stock/models.py:814
+#: stock/models.py:818
msgid "Link to external page for further information"
msgstr "Link auf externe Seite für weitere Informationen"
@@ -2194,28 +2193,28 @@ msgstr ""
msgid "This stock item was split from "
msgstr ""
-#: stock/templates/stock/item_base.html:85
+#: stock/templates/stock/item_base.html:88
msgid "Belongs To"
msgstr "Gehört zu"
-#: stock/templates/stock/item_base.html:90
+#: stock/templates/stock/item_base.html:94
#: stock/templates/stock/stock_adjust.html:17
msgid "Location"
msgstr "Standort"
-#: stock/templates/stock/item_base.html:96
+#: stock/templates/stock/item_base.html:101
msgid "Serial Number"
msgstr "Seriennummer"
-#: stock/templates/stock/item_base.html:146
+#: stock/templates/stock/item_base.html:160
msgid "Last Updated"
msgstr "Zuletzt aktualisiert"
-#: stock/templates/stock/item_base.html:150
+#: stock/templates/stock/item_base.html:165
msgid "Last Stocktake"
msgstr "Letzte Inventur"
-#: stock/templates/stock/item_base.html:154
+#: stock/templates/stock/item_base.html:169
msgid "No stocktake performed"
msgstr "Keine Inventur ausgeführt"
@@ -2233,29 +2232,29 @@ msgstr ""
msgid "Location Details"
msgstr "Standort-Details"
-#: stock/templates/stock/location.html:41
+#: stock/templates/stock/location.html:42
msgid "Location Path"
msgstr "Standord-Pfad"
-#: stock/templates/stock/location.html:46
+#: stock/templates/stock/location.html:47
msgid "Location Description"
msgstr "Standort-Beschreibung"
-#: stock/templates/stock/location.html:51
+#: stock/templates/stock/location.html:52
msgid "Sublocations"
msgstr "Sub-Standorte"
-#: stock/templates/stock/location.html:56
-#: stock/templates/stock/location.html:70 templates/stats.html:18
-#: templates/stats.html:21
+#: stock/templates/stock/location.html:57
+#: stock/templates/stock/location.html:72 templates/stats.html:21
+#: templates/stats.html:30
msgid "Stock Items"
msgstr "Lagerobjekte"
-#: stock/templates/stock/location.html:61
+#: stock/templates/stock/location.html:62
msgid "Stock Details"
msgstr "Objekt-Details"
-#: stock/templates/stock/location.html:65
+#: stock/templates/stock/location.html:67
#: templates/InvenTree/search_stock_location.html:6 templates/stats.html:25
msgid "Stock Locations"
msgstr "Lagerobjekt-Standorte"
@@ -2433,30 +2432,40 @@ msgstr ""
msgid "No results found"
msgstr "Keine Seriennummern gefunden"
-#: templates/about.html:18
+#: templates/about.html:13
msgid "InvenTree Version Information"
msgstr "InvenTree-Versionsinformationen"
#: templates/about.html:21
-msgid "Version"
-msgstr "Version"
+msgid "Instance Name"
+msgstr ""
-#: templates/about.html:24
+#: templates/about.html:26
+#, fuzzy
+#| msgid "InvenTree Version Information"
+msgid "InvenTree Version"
+msgstr "InvenTree-Versionsinformationen"
+
+#: templates/about.html:30
msgid "Commit Hash"
msgstr "Commit-Hash"
-#: templates/about.html:27
+#: templates/about.html:34
msgid "Commit Date"
msgstr "Commit-Datum"
-#: templates/about.html:33
+#: templates/about.html:38
msgid "InvenTree Documentation"
msgstr "InvenTree-Dokumentation"
-#: templates/about.html:37
+#: templates/about.html:43
msgid "View Code on GitHub"
msgstr "Code auf GitHub ansehen"
+#: templates/about.html:47
+msgid "Submit Bug Report"
+msgstr ""
+
#: templates/navbar.html:23
msgid "Admin"
msgstr ""
@@ -2530,3 +2539,9 @@ msgstr "bestellt"
#| msgid "Delete Stock Item"
msgid "Delete Stock"
msgstr "Lagerobjekt löschen"
+
+#~ msgid "URL"
+#~ msgstr "URL"
+
+#~ msgid "Version"
+#~ msgstr "Version"
diff --git a/InvenTree/locale/en/LC_MESSAGES/django.po b/InvenTree/locale/en/LC_MESSAGES/django.po
index def566c9da..9291ccd461 100644
--- a/InvenTree/locale/en/LC_MESSAGES/django.po
+++ b/InvenTree/locale/en/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2020-04-05 10:55+0000\n"
+"POT-Creation-Date: 2020-04-09 15:04+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -18,30 +18,30 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-#: InvenTree/helpers.py:201 order/models.py:164 order/models.py:215
+#: InvenTree/helpers.py:240 order/models.py:164 order/models.py:215
msgid "Invalid quantity provided"
msgstr ""
-#: InvenTree/helpers.py:204
+#: InvenTree/helpers.py:243
msgid "Empty serial number string"
msgstr ""
-#: InvenTree/helpers.py:225 InvenTree/helpers.py:242
+#: InvenTree/helpers.py:264 InvenTree/helpers.py:281
#, python-brace-format
msgid "Duplicate serial: {n}"
msgstr ""
-#: InvenTree/helpers.py:229 InvenTree/helpers.py:232 InvenTree/helpers.py:235
-#: InvenTree/helpers.py:246
+#: InvenTree/helpers.py:268 InvenTree/helpers.py:271 InvenTree/helpers.py:274
+#: InvenTree/helpers.py:285
#, python-brace-format
msgid "Invalid group: {g}"
msgstr ""
-#: InvenTree/helpers.py:252
+#: InvenTree/helpers.py:291
msgid "No serial numbers found"
msgstr ""
-#: InvenTree/helpers.py:256
+#: InvenTree/helpers.py:295
#, python-brace-format
msgid "Number of unique serial number ({s}) must match quantity ({q})"
msgstr ""
@@ -112,7 +112,7 @@ msgstr ""
#: InvenTree/status_codes.py:98 build/templates/build/allocate_edit.html:28
#: build/templates/build/allocate_view.html:21
-#: part/templates/part/part_base.html:106 part/templates/part/tabs.html:21
+#: part/templates/part/part_base.html:109 part/templates/part/tabs.html:21
msgid "Allocated"
msgstr ""
@@ -141,7 +141,7 @@ msgstr ""
msgid "Overage must be an integer value or a percentage"
msgstr ""
-#: InvenTree/views.py:548
+#: InvenTree/views.py:549
msgid "Database Statistics"
msgstr ""
@@ -183,7 +183,7 @@ msgstr ""
msgid "Batch code for this build output"
msgstr ""
-#: build/models.py:97
+#: build/models.py:97 stock/models.py:331
msgid "Link to external URL"
msgstr ""
@@ -227,7 +227,7 @@ msgstr ""
#: build/templates/build/allocate_edit.html:19
#: build/templates/build/allocate_view.html:17
-#: build/templates/build/detail.html:17
+#: build/templates/build/detail.html:21
#: company/templates/company/detail_part.html:65
#: order/templates/order/order_wizard/select_parts.html:30
#: order/templates/order/purchase_order_detail.html:25
@@ -263,12 +263,12 @@ msgstr ""
#: company/templates/company/supplier_part_base.html:50
#: company/templates/company/supplier_part_detail.html:27
#: order/templates/order/purchase_order_detail.html:26
-#: part/templates/part/detail.html:37
+#: part/templates/part/detail.html:38
msgid "Description"
msgstr ""
#: build/templates/build/allocate_view.html:22
-#: part/templates/part/part_base.html:112
+#: part/templates/part/part_base.html:115
msgid "On Order"
msgstr ""
@@ -284,65 +284,64 @@ msgstr ""
msgid "Build Details"
msgstr ""
-#: build/templates/build/detail.html:14
+#: build/templates/build/detail.html:16
msgid "Title"
msgstr ""
-#: build/templates/build/detail.html:20
+#: build/templates/build/detail.html:26
#: company/templates/company/supplier_part_pricing.html:27
#: order/templates/order/order_wizard/select_parts.html:32
#: order/templates/order/purchase_order_detail.html:29
-#: stock/templates/stock/item_base.html:101
+#: stock/templates/stock/item_base.html:107
#: stock/templates/stock/stock_adjust.html:18
msgid "Quantity"
msgstr ""
-#: build/templates/build/detail.html:23
+#: build/templates/build/detail.html:30
msgid "Stock Source"
msgstr ""
-#: build/templates/build/detail.html:28
+#: build/templates/build/detail.html:35
msgid "Stock can be taken from any available location."
msgstr ""
-#: build/templates/build/detail.html:33
+#: build/templates/build/detail.html:41
#: order/templates/order/order_base.html:71
-#: stock/templates/stock/item_base.html:158
+#: stock/templates/stock/item_base.html:174
msgid "Status"
msgstr ""
-#: build/templates/build/detail.html:37
-#: stock/templates/stock/item_base.html:107
+#: build/templates/build/detail.html:47
+#: stock/templates/stock/item_base.html:114
msgid "Batch"
msgstr ""
-#: build/templates/build/detail.html:42
-#: company/templates/company/detail_part.html:90
+#: build/templates/build/detail.html:54
#: company/templates/company/supplier_part_base.html:47
#: company/templates/company/supplier_part_detail.html:24
-#: part/templates/part/part_base.html:81
-#: stock/templates/stock/item_base.html:131
-msgid "URL"
+#: part/templates/part/detail.html:67 part/templates/part/part_base.html:84
+#: stock/templates/stock/item_base.html:142
+msgid "External Link"
msgstr ""
-#: build/templates/build/detail.html:46
-#: order/templates/order/order_base.html:75
+#: build/templates/build/detail.html:60
+#: order/templates/order/order_base.html:83
msgid "Created"
msgstr ""
-#: build/templates/build/detail.html:50
+#: build/templates/build/detail.html:66
msgid "Enough Parts?"
msgstr ""
-#: build/templates/build/detail.html:53
+#: build/templates/build/detail.html:69
msgid "Yes"
msgstr ""
-#: build/templates/build/detail.html:55
+#: build/templates/build/detail.html:71
msgid "No"
msgstr ""
-#: build/templates/build/detail.html:62
+#: build/templates/build/detail.html:79
msgid "Completed"
msgstr ""
@@ -371,7 +370,7 @@ msgstr ""
msgid "Outputs"
msgstr ""
-#: build/templates/build/tabs.html:11 company/models.py:248
+#: build/templates/build/tabs.html:11 company/models.py:264
#: company/templates/company/tabs.html:26 order/templates/order/tabs.html:15
#: part/templates/part/tabs.html:58 stock/templates/stock/tabs.html:17
msgid "Notes"
@@ -530,81 +529,81 @@ msgstr ""
msgid "Delete Currency"
msgstr ""
-#: company/models.py:74
+#: company/models.py:76
msgid "Company name"
msgstr ""
-#: company/models.py:76
+#: company/models.py:78
msgid "Description of the company"
msgstr ""
-#: company/models.py:78
+#: company/models.py:80
msgid "Company website URL"
msgstr ""
-#: company/models.py:81
+#: company/models.py:83
msgid "Company address"
msgstr ""
-#: company/models.py:84
+#: company/models.py:86
msgid "Contact phone number"
msgstr ""
-#: company/models.py:86
+#: company/models.py:88
msgid "Contact email address"
msgstr ""
-#: company/models.py:89
+#: company/models.py:91
msgid "Point of contact"
msgstr ""
-#: company/models.py:91
+#: company/models.py:93
msgid "Link to external company information"
msgstr ""
-#: company/models.py:97
+#: company/models.py:105
msgid "Do you sell items to this company?"
msgstr ""
-#: company/models.py:99
+#: company/models.py:107
msgid "Do you purchase items from this company?"
msgstr ""
-#: company/models.py:229
+#: company/models.py:245
msgid "Select part"
msgstr ""
-#: company/models.py:235
+#: company/models.py:251
msgid "Select supplier"
msgstr ""
-#: company/models.py:238
+#: company/models.py:254
msgid "Supplier stock keeping unit"
msgstr ""
-#: company/models.py:240 company/templates/company/detail_part.html:81
+#: company/models.py:256 company/templates/company/detail_part.html:81
#: company/templates/company/supplier_part_base.html:53
#: company/templates/company/supplier_part_detail.html:30
msgid "Manufacturer"
msgstr ""
-#: company/models.py:242
+#: company/models.py:258
msgid "Manufacturer part number"
msgstr ""
-#: company/models.py:244
+#: company/models.py:260
msgid "URL for external supplier part link"
msgstr ""
-#: company/models.py:246
+#: company/models.py:262
msgid "Supplier part description"
msgstr ""
-#: company/models.py:250
+#: company/models.py:266
msgid "Minimum charge (e.g. stocking fee)"
msgstr ""
-#: company/models.py:252
+#: company/models.py:268
msgid "Part packaging"
msgstr ""
@@ -612,24 +611,24 @@ msgstr ""
msgid "Company"
msgstr ""
-#: company/templates/company/company_base.html:48
+#: company/templates/company/company_base.html:50
#: company/templates/company/index.html:59
msgid "Website"
msgstr ""
-#: company/templates/company/company_base.html:53
+#: company/templates/company/company_base.html:57
msgid "Address"
msgstr ""
-#: company/templates/company/company_base.html:58
+#: company/templates/company/company_base.html:64
msgid "Phone"
msgstr ""
-#: company/templates/company/company_base.html:63
+#: company/templates/company/company_base.html:71
msgid "Email"
msgstr ""
-#: company/templates/company/company_base.html:68
+#: company/templates/company/company_base.html:78
msgid "Contact"
msgstr ""
@@ -637,18 +636,18 @@ msgstr ""
msgid "Company Details"
msgstr ""
-#: company/templates/company/detail.html:13
-#: stock/templates/stock/item_base.html:125
+#: company/templates/company/detail.html:16
+#: stock/templates/stock/item_base.html:135
msgid "Customer"
msgstr ""
-#: company/templates/company/detail.html:17
+#: company/templates/company/detail.html:21
#: company/templates/company/index.html:46
#: company/templates/company/supplier_part_base.html:44
#: company/templates/company/supplier_part_detail.html:21
-#: order/templates/order/order_base.html:67
+#: order/templates/order/order_base.html:66
#: order/templates/order/order_wizard/select_pos.html:30
-#: stock/templates/stock/item_base.html:137
+#: stock/templates/stock/item_base.html:149
msgid "Supplier"
msgstr ""
@@ -676,6 +675,10 @@ msgstr ""
msgid "SKU"
msgstr ""
+#: company/templates/company/detail_part.html:90
+msgid "Link"
+msgstr ""
+
#: company/templates/company/detail_purchase_orders.html:8
#: company/templates/company/tabs.html:15 part/templates/part/tabs.html:43
msgid "Purchase Orders"
@@ -708,8 +711,8 @@ msgstr ""
msgid "ID"
msgstr ""
-#: company/templates/company/index.html:69 part/templates/part/category.html:81
-#: templates/navbar.html:10 templates/stats.html:7 templates/stats.html:10
+#: company/templates/company/index.html:69 part/templates/part/category.html:83
+#: templates/navbar.html:10 templates/stats.html:8 templates/stats.html:17
msgid "Parts"
msgstr ""
@@ -724,7 +727,7 @@ msgstr ""
#: company/templates/company/supplier_part_base.html:6
#: company/templates/company/supplier_part_base.html:13
-#: stock/templates/stock/item_base.html:141
+#: stock/templates/stock/item_base.html:154
msgid "Supplier Part"
msgstr ""
@@ -903,7 +906,7 @@ msgid "Order notes"
msgstr ""
#: order/models.py:162 order/models.py:213 part/views.py:1119
-#: stock/models.py:463
+#: stock/models.py:467
msgid "Quantity must be greater than zero"
msgstr ""
@@ -927,7 +930,7 @@ msgstr ""
msgid "Line item notes"
msgstr ""
-#: order/models.py:298 stock/templates/stock/item_base.html:119
+#: order/models.py:298 stock/templates/stock/item_base.html:128
msgid "Purchase Order"
msgstr ""
@@ -939,15 +942,15 @@ msgstr ""
msgid "Number of items received"
msgstr ""
-#: order/templates/order/order_base.html:64
+#: order/templates/order/order_base.html:61
msgid "Purchase Order Details"
msgstr ""
-#: order/templates/order/order_base.html:80
+#: order/templates/order/order_base.html:89
msgid "Issued"
msgstr ""
-#: order/templates/order/order_base.html:86
+#: order/templates/order/order_base.html:96
#: order/templates/order/purchase_order_detail.html:31
msgid "Received"
msgstr ""
@@ -1213,171 +1216,171 @@ msgstr ""
msgid "Select currency for price calculation"
msgstr ""
-#: part/models.py:62
+#: part/models.py:61
msgid "Default location for parts in this category"
msgstr ""
-#: part/models.py:65
+#: part/models.py:64
msgid "Default keywords for parts in this category"
msgstr ""
-#: part/models.py:339
+#: part/models.py:338
msgid "Part must be unique for name, IPN and revision"
msgstr ""
-#: part/models.py:353
+#: part/models.py:352
msgid "Part cannot be a template part if it is a variant of another part"
msgstr ""
-#: part/models.py:354
+#: part/models.py:353
msgid "Part cannot be a variant of another part if it is already a template"
msgstr ""
-#: part/models.py:358 part/templates/part/detail.html:18
+#: part/models.py:357 part/templates/part/detail.html:19
msgid "Part name"
msgstr ""
-#: part/models.py:362
+#: part/models.py:361
msgid "Is this part a template part?"
msgstr ""
-#: part/models.py:371
+#: part/models.py:370
msgid "Is this part a variant of another part?"
msgstr ""
-#: part/models.py:373
+#: part/models.py:372
msgid "Part description"
msgstr ""
-#: part/models.py:375
+#: part/models.py:374
msgid "Part keywords to improve visibility in search results"
msgstr ""
-#: part/models.py:380
+#: part/models.py:379
msgid "Part category"
msgstr ""
-#: part/models.py:382
+#: part/models.py:381
msgid "Internal Part Number"
msgstr ""
-#: part/models.py:384
+#: part/models.py:383
msgid "Part revision or version number"
msgstr ""
-#: part/models.py:386
+#: part/models.py:385
msgid "Link to extenal URL"
msgstr ""
-#: part/models.py:398
+#: part/models.py:397
msgid "Where is this item normally stored?"
msgstr ""
-#: part/models.py:442
+#: part/models.py:441
msgid "Default supplier part"
msgstr ""
-#: part/models.py:445
+#: part/models.py:444
msgid "Minimum allowed stock level"
msgstr ""
-#: part/models.py:447
+#: part/models.py:446
msgid "Stock keeping units for this part"
msgstr ""
-#: part/models.py:449
+#: part/models.py:448
msgid "Can this part be built from other parts?"
msgstr ""
-#: part/models.py:451
+#: part/models.py:450
msgid "Can this part be used to build other parts?"
msgstr ""
-#: part/models.py:453
+#: part/models.py:452
msgid "Does this part have tracking for unique items?"
msgstr ""
-#: part/models.py:455
+#: part/models.py:454
msgid "Can this part be purchased from external suppliers?"
msgstr ""
-#: part/models.py:457
+#: part/models.py:456
msgid "Can this part be sold to customers?"
msgstr ""
-#: part/models.py:459
+#: part/models.py:458
msgid "Is this part active?"
msgstr ""
-#: part/models.py:461
+#: part/models.py:460
msgid "Is this a virtual part, such as a software product or license?"
msgstr ""
-#: part/models.py:463
+#: part/models.py:462
msgid "Part notes - supports Markdown formatting"
msgstr ""
-#: part/models.py:465
+#: part/models.py:464
msgid "Stored BOM checksum"
msgstr ""
-#: part/models.py:1041
+#: part/models.py:1040
msgid "Parameter template name must be unique"
msgstr ""
-#: part/models.py:1046
+#: part/models.py:1045
msgid "Parameter Name"
msgstr ""
-#: part/models.py:1048
+#: part/models.py:1047
msgid "Parameter Units"
msgstr ""
-#: part/models.py:1074
+#: part/models.py:1073
msgid "Parent Part"
msgstr ""
-#: part/models.py:1076
+#: part/models.py:1075
msgid "Parameter Template"
msgstr ""
-#: part/models.py:1078
+#: part/models.py:1077
msgid "Parameter Value"
msgstr ""
-#: part/models.py:1102
+#: part/models.py:1101
msgid "Select parent part"
msgstr ""
-#: part/models.py:1111
+#: part/models.py:1110
msgid "Select part to be used in BOM"
msgstr ""
-#: part/models.py:1118
+#: part/models.py:1117
msgid "BOM quantity for this BOM item"
msgstr ""
-#: part/models.py:1121
+#: part/models.py:1120
msgid "Estimated build wastage quantity (absolute or percentage)"
msgstr ""
-#: part/models.py:1124
+#: part/models.py:1123
msgid "BOM item reference"
msgstr ""
-#: part/models.py:1127
+#: part/models.py:1126
msgid "BOM item notes"
msgstr ""
-#: part/models.py:1129
+#: part/models.py:1128
msgid "BOM line checksum"
msgstr ""
-#: part/models.py:1192
+#: part/models.py:1191
msgid "Part cannot be added to its own Bill of Materials"
msgstr ""
-#: part/models.py:1199
+#: part/models.py:1198
#, python-brace-format
msgid "Part '{p1}' is used in BOM for '{p2}' (recursive)"
msgstr ""
@@ -1386,8 +1389,8 @@ msgstr ""
msgid "Part Attachments"
msgstr ""
-#: part/templates/part/category.html:13 part/templates/part/category.html:76
-#: templates/stats.html:14
+#: part/templates/part/category.html:13 part/templates/part/category.html:78
+#: templates/stats.html:12
msgid "Part Categories"
msgstr ""
@@ -1395,31 +1398,31 @@ msgstr ""
msgid "All parts"
msgstr ""
-#: part/templates/part/category.html:34 part/templates/part/category.html:72
+#: part/templates/part/category.html:34 part/templates/part/category.html:73
msgid "Category Details"
msgstr ""
-#: part/templates/part/category.html:38
+#: part/templates/part/category.html:39
msgid "Category Path"
msgstr ""
-#: part/templates/part/category.html:43
+#: part/templates/part/category.html:44
msgid "Category Description"
msgstr ""
-#: part/templates/part/category.html:49 part/templates/part/detail.html:73
+#: part/templates/part/category.html:50 part/templates/part/detail.html:74
msgid "Default Location"
msgstr ""
-#: part/templates/part/category.html:56 part/templates/part/detail.html:50
+#: part/templates/part/category.html:57 part/templates/part/detail.html:51
msgid "Keywords"
msgstr ""
-#: part/templates/part/category.html:62
+#: part/templates/part/category.html:63
msgid "Subcategories"
msgstr ""
-#: part/templates/part/category.html:67
+#: part/templates/part/category.html:68
msgid "Parts (Including subcategories)"
msgstr ""
@@ -1427,115 +1430,111 @@ msgstr ""
msgid "Part Details"
msgstr ""
-#: part/templates/part/detail.html:24 part/templates/part/part_base.html:75
+#: part/templates/part/detail.html:25 part/templates/part/part_base.html:77
msgid "IPN"
msgstr ""
-#: part/templates/part/detail.html:31
+#: part/templates/part/detail.html:32
msgid "Revision"
msgstr ""
-#: part/templates/part/detail.html:43
+#: part/templates/part/detail.html:44
msgid "Variant Of"
msgstr ""
-#: part/templates/part/detail.html:56
+#: part/templates/part/detail.html:57
msgid "Category"
msgstr ""
-#: part/templates/part/detail.html:66
-msgid "Link"
-msgstr ""
-
-#: part/templates/part/detail.html:80
+#: part/templates/part/detail.html:81
msgid "Default Supplier"
msgstr ""
-#: part/templates/part/detail.html:88
+#: part/templates/part/detail.html:89
msgid "Units"
msgstr ""
-#: part/templates/part/detail.html:94
+#: part/templates/part/detail.html:95
msgid "Minimum Stock"
msgstr ""
-#: part/templates/part/detail.html:100
+#: part/templates/part/detail.html:101
msgid "Creation Date"
msgstr ""
-#: part/templates/part/detail.html:106
+#: part/templates/part/detail.html:107
msgid "Created By"
msgstr ""
-#: part/templates/part/detail.html:113
+#: part/templates/part/detail.html:114
msgid "Responsible User"
msgstr ""
-#: part/templates/part/detail.html:122
+#: part/templates/part/detail.html:123
msgid "Virtual"
msgstr ""
-#: part/templates/part/detail.html:125
+#: part/templates/part/detail.html:126
msgid "Part is virtual (not a physical part)"
msgstr ""
-#: part/templates/part/detail.html:127
+#: part/templates/part/detail.html:128
msgid "Part is not a virtual part"
msgstr ""
-#: part/templates/part/detail.html:131
+#: part/templates/part/detail.html:132
msgid "Assembly"
msgstr ""
-#: part/templates/part/detail.html:134
+#: part/templates/part/detail.html:135
msgid "Part can be assembled from other parts"
msgstr ""
-#: part/templates/part/detail.html:136
+#: part/templates/part/detail.html:137
msgid "Part cannot be assembled from other parts"
msgstr ""
-#: part/templates/part/detail.html:140
+#: part/templates/part/detail.html:141
msgid "Component"
msgstr ""
-#: part/templates/part/detail.html:143
+#: part/templates/part/detail.html:144
msgid "Part can be used in assemblies"
msgstr ""
-#: part/templates/part/detail.html:145
+#: part/templates/part/detail.html:146
msgid "Part cannot be used in assemblies"
msgstr ""
-#: part/templates/part/detail.html:149
+#: part/templates/part/detail.html:150
msgid "Trackable"
msgstr ""
-#: part/templates/part/detail.html:152
+#: part/templates/part/detail.html:153
msgid "Part stock is tracked by serial number"
msgstr ""
-#: part/templates/part/detail.html:154
+#: part/templates/part/detail.html:155
msgid "Part stock is not tracked by serial number"
msgstr ""
-#: part/templates/part/detail.html:158
+#: part/templates/part/detail.html:159
msgid "Purchaseable"
msgstr ""
-#: part/templates/part/detail.html:161 part/templates/part/detail.html:163
+#: part/templates/part/detail.html:162 part/templates/part/detail.html:164
msgid "Part can be purchased from external suppliers"
msgstr ""
-#: part/templates/part/detail.html:168
+#: part/templates/part/detail.html:169
msgid "Sellable"
msgstr ""
-#: part/templates/part/detail.html:171
+#: part/templates/part/detail.html:172
msgid "Part can be sold to customers"
msgstr ""
-#: part/templates/part/detail.html:173
+#: part/templates/part/detail.html:174
msgid "Part cannot be sold to customers"
msgstr ""
@@ -1563,23 +1562,23 @@ msgstr ""
msgid "Show pricing information"
msgstr ""
-#: part/templates/part/part_base.html:95
+#: part/templates/part/part_base.html:98
msgid "Available Stock"
msgstr ""
-#: part/templates/part/part_base.html:100
+#: part/templates/part/part_base.html:103
msgid "In Stock"
msgstr ""
-#: part/templates/part/part_base.html:121
+#: part/templates/part/part_base.html:124
msgid "Build Status"
msgstr ""
-#: part/templates/part/part_base.html:125
+#: part/templates/part/part_base.html:128
msgid "Can Build"
msgstr ""
-#: part/templates/part/part_base.html:130
+#: part/templates/part/part_base.html:133
msgid "Underway"
msgstr ""
@@ -1619,7 +1618,7 @@ msgstr ""
msgid "BOM"
msgstr ""
-#: part/templates/part/tabs.html:28 stock/templates/stock/item_base.html:113
+#: part/templates/part/tabs.html:28 stock/templates/stock/item_base.html:121
#: templates/navbar.html:12
msgid "Build"
msgstr ""
@@ -1737,67 +1736,67 @@ msgstr ""
msgid "Export Bill of Materials"
msgstr ""
-#: part/views.py:1406
+#: part/views.py:1404
msgid "Confirm Part Deletion"
msgstr ""
-#: part/views.py:1413
+#: part/views.py:1411
msgid "Part was deleted"
msgstr ""
-#: part/views.py:1422
+#: part/views.py:1420
msgid "Part Pricing"
msgstr ""
-#: part/views.py:1544
+#: part/views.py:1542
msgid "Create Part Parameter Template"
msgstr ""
-#: part/views.py:1552
+#: part/views.py:1550
msgid "Edit Part Parameter Template"
msgstr ""
-#: part/views.py:1559
+#: part/views.py:1557
msgid "Delete Part Parameter Template"
msgstr ""
-#: part/views.py:1567
+#: part/views.py:1565
msgid "Create Part Parameter"
msgstr ""
-#: part/views.py:1617
+#: part/views.py:1615
msgid "Edit Part Parameter"
msgstr ""
-#: part/views.py:1631
+#: part/views.py:1629
msgid "Delete Part Parameter"
msgstr ""
-#: part/views.py:1647
+#: part/views.py:1645
msgid "Edit Part Category"
msgstr ""
-#: part/views.py:1682
+#: part/views.py:1680
msgid "Delete Part Category"
msgstr ""
-#: part/views.py:1688
+#: part/views.py:1686
msgid "Part category was deleted"
msgstr ""
-#: part/views.py:1696
+#: part/views.py:1694
msgid "Create new part category"
msgstr ""
-#: part/views.py:1747
+#: part/views.py:1745
msgid "Create BOM item"
msgstr ""
-#: part/views.py:1813
+#: part/views.py:1811
msgid "Edit BOM item"
msgstr ""
-#: part/views.py:1861
+#: part/views.py:1859
msgid "Confim BOM item deletion"
msgstr ""
@@ -1893,45 +1892,45 @@ msgstr ""
msgid "Stock Item Notes"
msgstr ""
-#: stock/models.py:460
+#: stock/models.py:464
msgid "Quantity must be integer"
msgstr ""
-#: stock/models.py:466
+#: stock/models.py:470
#, python-brace-format
msgid "Quantity must not exceed available stock quantity ({n})"
msgstr ""
-#: stock/models.py:469 stock/models.py:472
+#: stock/models.py:473 stock/models.py:476
msgid "Serial numbers must be a list of integers"
msgstr ""
-#: stock/models.py:475
+#: stock/models.py:479
msgid "Quantity does not match serial numbers"
msgstr ""
-#: stock/models.py:485
+#: stock/models.py:489
msgid "Serial numbers already exist: "
msgstr ""
-#: stock/models.py:507
+#: stock/models.py:511
msgid "Add serial number"
msgstr ""
-#: stock/models.py:510
+#: stock/models.py:514
#, python-brace-format
msgid "Serialized {n} items"
msgstr ""
-#: stock/models.py:810
+#: stock/models.py:814
msgid "Tracking entry title"
msgstr ""
-#: stock/models.py:812
+#: stock/models.py:816
msgid "Entry notes"
msgstr ""
-#: stock/models.py:814
+#: stock/models.py:818
msgid "Link to external page for further information"
msgstr ""
@@ -1962,28 +1961,28 @@ msgstr ""
msgid "This stock item was split from "
msgstr ""
-#: stock/templates/stock/item_base.html:85
+#: stock/templates/stock/item_base.html:88
msgid "Belongs To"
msgstr ""
-#: stock/templates/stock/item_base.html:90
+#: stock/templates/stock/item_base.html:94
#: stock/templates/stock/stock_adjust.html:17
msgid "Location"
msgstr ""
-#: stock/templates/stock/item_base.html:96
+#: stock/templates/stock/item_base.html:101
msgid "Serial Number"
msgstr ""
-#: stock/templates/stock/item_base.html:146
+#: stock/templates/stock/item_base.html:160
msgid "Last Updated"
msgstr ""
-#: stock/templates/stock/item_base.html:150
+#: stock/templates/stock/item_base.html:165
msgid "Last Stocktake"
msgstr ""
-#: stock/templates/stock/item_base.html:154
+#: stock/templates/stock/item_base.html:169
msgid "No stocktake performed"
msgstr ""
@@ -1999,29 +1998,29 @@ msgstr ""
msgid "Location Details"
msgstr ""
-#: stock/templates/stock/location.html:41
+#: stock/templates/stock/location.html:42
msgid "Location Path"
msgstr ""
-#: stock/templates/stock/location.html:46
+#: stock/templates/stock/location.html:47
msgid "Location Description"
msgstr ""
-#: stock/templates/stock/location.html:51
+#: stock/templates/stock/location.html:52
msgid "Sublocations"
msgstr ""
-#: stock/templates/stock/location.html:56
-#: stock/templates/stock/location.html:70 templates/stats.html:18
-#: templates/stats.html:21
+#: stock/templates/stock/location.html:57
+#: stock/templates/stock/location.html:72 templates/stats.html:21
+#: templates/stats.html:30
msgid "Stock Items"
msgstr ""
-#: stock/templates/stock/location.html:61
+#: stock/templates/stock/location.html:62
msgid "Stock Details"
msgstr ""
-#: stock/templates/stock/location.html:65
+#: stock/templates/stock/location.html:67
#: templates/InvenTree/search_stock_location.html:6 templates/stats.html:25
msgid "Stock Locations"
msgstr ""
@@ -2194,30 +2193,38 @@ msgstr ""
msgid "No results found"
msgstr ""
-#: templates/about.html:18
+#: templates/about.html:13
msgid "InvenTree Version Information"
msgstr ""
#: templates/about.html:21
-msgid "Version"
+msgid "Instance Name"
msgstr ""
-#: templates/about.html:24
+#: templates/about.html:26
+msgid "InvenTree Version"
+msgstr ""
+
+#: templates/about.html:30
msgid "Commit Hash"
msgstr ""
-#: templates/about.html:27
+#: templates/about.html:34
msgid "Commit Date"
msgstr ""
-#: templates/about.html:33
+#: templates/about.html:38
msgid "InvenTree Documentation"
msgstr ""
-#: templates/about.html:37
+#: templates/about.html:43
msgid "View Code on GitHub"
msgstr ""
+#: templates/about.html:47
+msgid "Submit Bug Report"
+msgstr ""
+
#: templates/navbar.html:23
msgid "Admin"
msgstr ""
diff --git a/InvenTree/locale/es/LC_MESSAGES/django.po b/InvenTree/locale/es/LC_MESSAGES/django.po
index def566c9da..9291ccd461 100644
--- a/InvenTree/locale/es/LC_MESSAGES/django.po
+++ b/InvenTree/locale/es/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2020-04-05 10:55+0000\n"
+"POT-Creation-Date: 2020-04-09 15:04+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -18,30 +18,30 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-#: InvenTree/helpers.py:201 order/models.py:164 order/models.py:215
+#: InvenTree/helpers.py:240 order/models.py:164 order/models.py:215
msgid "Invalid quantity provided"
msgstr ""
-#: InvenTree/helpers.py:204
+#: InvenTree/helpers.py:243
msgid "Empty serial number string"
msgstr ""
-#: InvenTree/helpers.py:225 InvenTree/helpers.py:242
+#: InvenTree/helpers.py:264 InvenTree/helpers.py:281
#, python-brace-format
msgid "Duplicate serial: {n}"
msgstr ""
-#: InvenTree/helpers.py:229 InvenTree/helpers.py:232 InvenTree/helpers.py:235
-#: InvenTree/helpers.py:246
+#: InvenTree/helpers.py:268 InvenTree/helpers.py:271 InvenTree/helpers.py:274
+#: InvenTree/helpers.py:285
#, python-brace-format
msgid "Invalid group: {g}"
msgstr ""
-#: InvenTree/helpers.py:252
+#: InvenTree/helpers.py:291
msgid "No serial numbers found"
msgstr ""
-#: InvenTree/helpers.py:256
+#: InvenTree/helpers.py:295
#, python-brace-format
msgid "Number of unique serial number ({s}) must match quantity ({q})"
msgstr ""
@@ -112,7 +112,7 @@ msgstr ""
#: InvenTree/status_codes.py:98 build/templates/build/allocate_edit.html:28
#: build/templates/build/allocate_view.html:21
-#: part/templates/part/part_base.html:106 part/templates/part/tabs.html:21
+#: part/templates/part/part_base.html:109 part/templates/part/tabs.html:21
msgid "Allocated"
msgstr ""
@@ -141,7 +141,7 @@ msgstr ""
msgid "Overage must be an integer value or a percentage"
msgstr ""
-#: InvenTree/views.py:548
+#: InvenTree/views.py:549
msgid "Database Statistics"
msgstr ""
@@ -183,7 +183,7 @@ msgstr ""
msgid "Batch code for this build output"
msgstr ""
-#: build/models.py:97
+#: build/models.py:97 stock/models.py:331
msgid "Link to external URL"
msgstr ""
@@ -227,7 +227,7 @@ msgstr ""
#: build/templates/build/allocate_edit.html:19
#: build/templates/build/allocate_view.html:17
-#: build/templates/build/detail.html:17
+#: build/templates/build/detail.html:21
#: company/templates/company/detail_part.html:65
#: order/templates/order/order_wizard/select_parts.html:30
#: order/templates/order/purchase_order_detail.html:25
@@ -263,12 +263,12 @@ msgstr ""
#: company/templates/company/supplier_part_base.html:50
#: company/templates/company/supplier_part_detail.html:27
#: order/templates/order/purchase_order_detail.html:26
-#: part/templates/part/detail.html:37
+#: part/templates/part/detail.html:38
msgid "Description"
msgstr ""
#: build/templates/build/allocate_view.html:22
-#: part/templates/part/part_base.html:112
+#: part/templates/part/part_base.html:115
msgid "On Order"
msgstr ""
@@ -284,65 +284,64 @@ msgstr ""
msgid "Build Details"
msgstr ""
-#: build/templates/build/detail.html:14
+#: build/templates/build/detail.html:16
msgid "Title"
msgstr ""
-#: build/templates/build/detail.html:20
+#: build/templates/build/detail.html:26
#: company/templates/company/supplier_part_pricing.html:27
#: order/templates/order/order_wizard/select_parts.html:32
#: order/templates/order/purchase_order_detail.html:29
-#: stock/templates/stock/item_base.html:101
+#: stock/templates/stock/item_base.html:107
#: stock/templates/stock/stock_adjust.html:18
msgid "Quantity"
msgstr ""
-#: build/templates/build/detail.html:23
+#: build/templates/build/detail.html:30
msgid "Stock Source"
msgstr ""
-#: build/templates/build/detail.html:28
+#: build/templates/build/detail.html:35
msgid "Stock can be taken from any available location."
msgstr ""
-#: build/templates/build/detail.html:33
+#: build/templates/build/detail.html:41
#: order/templates/order/order_base.html:71
-#: stock/templates/stock/item_base.html:158
+#: stock/templates/stock/item_base.html:174
msgid "Status"
msgstr ""
-#: build/templates/build/detail.html:37
-#: stock/templates/stock/item_base.html:107
+#: build/templates/build/detail.html:47
+#: stock/templates/stock/item_base.html:114
msgid "Batch"
msgstr ""
-#: build/templates/build/detail.html:42
-#: company/templates/company/detail_part.html:90
+#: build/templates/build/detail.html:54
#: company/templates/company/supplier_part_base.html:47
#: company/templates/company/supplier_part_detail.html:24
-#: part/templates/part/part_base.html:81
-#: stock/templates/stock/item_base.html:131
-msgid "URL"
+#: part/templates/part/detail.html:67 part/templates/part/part_base.html:84
+#: stock/templates/stock/item_base.html:142
+msgid "External Link"
msgstr ""
-#: build/templates/build/detail.html:46
-#: order/templates/order/order_base.html:75
+#: build/templates/build/detail.html:60
+#: order/templates/order/order_base.html:83
msgid "Created"
msgstr ""
-#: build/templates/build/detail.html:50
+#: build/templates/build/detail.html:66
msgid "Enough Parts?"
msgstr ""
-#: build/templates/build/detail.html:53
+#: build/templates/build/detail.html:69
msgid "Yes"
msgstr ""
-#: build/templates/build/detail.html:55
+#: build/templates/build/detail.html:71
msgid "No"
msgstr ""
-#: build/templates/build/detail.html:62
+#: build/templates/build/detail.html:79
msgid "Completed"
msgstr ""
@@ -371,7 +370,7 @@ msgstr ""
msgid "Outputs"
msgstr ""
-#: build/templates/build/tabs.html:11 company/models.py:248
+#: build/templates/build/tabs.html:11 company/models.py:264
#: company/templates/company/tabs.html:26 order/templates/order/tabs.html:15
#: part/templates/part/tabs.html:58 stock/templates/stock/tabs.html:17
msgid "Notes"
@@ -530,81 +529,81 @@ msgstr ""
msgid "Delete Currency"
msgstr ""
-#: company/models.py:74
+#: company/models.py:76
msgid "Company name"
msgstr ""
-#: company/models.py:76
+#: company/models.py:78
msgid "Description of the company"
msgstr ""
-#: company/models.py:78
+#: company/models.py:80
msgid "Company website URL"
msgstr ""
-#: company/models.py:81
+#: company/models.py:83
msgid "Company address"
msgstr ""
-#: company/models.py:84
+#: company/models.py:86
msgid "Contact phone number"
msgstr ""
-#: company/models.py:86
+#: company/models.py:88
msgid "Contact email address"
msgstr ""
-#: company/models.py:89
+#: company/models.py:91
msgid "Point of contact"
msgstr ""
-#: company/models.py:91
+#: company/models.py:93
msgid "Link to external company information"
msgstr ""
-#: company/models.py:97
+#: company/models.py:105
msgid "Do you sell items to this company?"
msgstr ""
-#: company/models.py:99
+#: company/models.py:107
msgid "Do you purchase items from this company?"
msgstr ""
-#: company/models.py:229
+#: company/models.py:245
msgid "Select part"
msgstr ""
-#: company/models.py:235
+#: company/models.py:251
msgid "Select supplier"
msgstr ""
-#: company/models.py:238
+#: company/models.py:254
msgid "Supplier stock keeping unit"
msgstr ""
-#: company/models.py:240 company/templates/company/detail_part.html:81
+#: company/models.py:256 company/templates/company/detail_part.html:81
#: company/templates/company/supplier_part_base.html:53
#: company/templates/company/supplier_part_detail.html:30
msgid "Manufacturer"
msgstr ""
-#: company/models.py:242
+#: company/models.py:258
msgid "Manufacturer part number"
msgstr ""
-#: company/models.py:244
+#: company/models.py:260
msgid "URL for external supplier part link"
msgstr ""
-#: company/models.py:246
+#: company/models.py:262
msgid "Supplier part description"
msgstr ""
-#: company/models.py:250
+#: company/models.py:266
msgid "Minimum charge (e.g. stocking fee)"
msgstr ""
-#: company/models.py:252
+#: company/models.py:268
msgid "Part packaging"
msgstr ""
@@ -612,24 +611,24 @@ msgstr ""
msgid "Company"
msgstr ""
-#: company/templates/company/company_base.html:48
+#: company/templates/company/company_base.html:50
#: company/templates/company/index.html:59
msgid "Website"
msgstr ""
-#: company/templates/company/company_base.html:53
+#: company/templates/company/company_base.html:57
msgid "Address"
msgstr ""
-#: company/templates/company/company_base.html:58
+#: company/templates/company/company_base.html:64
msgid "Phone"
msgstr ""
-#: company/templates/company/company_base.html:63
+#: company/templates/company/company_base.html:71
msgid "Email"
msgstr ""
-#: company/templates/company/company_base.html:68
+#: company/templates/company/company_base.html:78
msgid "Contact"
msgstr ""
@@ -637,18 +636,18 @@ msgstr ""
msgid "Company Details"
msgstr ""
-#: company/templates/company/detail.html:13
-#: stock/templates/stock/item_base.html:125
+#: company/templates/company/detail.html:16
+#: stock/templates/stock/item_base.html:135
msgid "Customer"
msgstr ""
-#: company/templates/company/detail.html:17
+#: company/templates/company/detail.html:21
#: company/templates/company/index.html:46
#: company/templates/company/supplier_part_base.html:44
#: company/templates/company/supplier_part_detail.html:21
-#: order/templates/order/order_base.html:67
+#: order/templates/order/order_base.html:66
#: order/templates/order/order_wizard/select_pos.html:30
-#: stock/templates/stock/item_base.html:137
+#: stock/templates/stock/item_base.html:149
msgid "Supplier"
msgstr ""
@@ -676,6 +675,10 @@ msgstr ""
msgid "SKU"
msgstr ""
+#: company/templates/company/detail_part.html:90
+msgid "Link"
+msgstr ""
+
#: company/templates/company/detail_purchase_orders.html:8
#: company/templates/company/tabs.html:15 part/templates/part/tabs.html:43
msgid "Purchase Orders"
@@ -708,8 +711,8 @@ msgstr ""
msgid "ID"
msgstr ""
-#: company/templates/company/index.html:69 part/templates/part/category.html:81
-#: templates/navbar.html:10 templates/stats.html:7 templates/stats.html:10
+#: company/templates/company/index.html:69 part/templates/part/category.html:83
+#: templates/navbar.html:10 templates/stats.html:8 templates/stats.html:17
msgid "Parts"
msgstr ""
@@ -724,7 +727,7 @@ msgstr ""
#: company/templates/company/supplier_part_base.html:6
#: company/templates/company/supplier_part_base.html:13
-#: stock/templates/stock/item_base.html:141
+#: stock/templates/stock/item_base.html:154
msgid "Supplier Part"
msgstr ""
@@ -903,7 +906,7 @@ msgid "Order notes"
msgstr ""
#: order/models.py:162 order/models.py:213 part/views.py:1119
-#: stock/models.py:463
+#: stock/models.py:467
msgid "Quantity must be greater than zero"
msgstr ""
@@ -927,7 +930,7 @@ msgstr ""
msgid "Line item notes"
msgstr ""
-#: order/models.py:298 stock/templates/stock/item_base.html:119
+#: order/models.py:298 stock/templates/stock/item_base.html:128
msgid "Purchase Order"
msgstr ""
@@ -939,15 +942,15 @@ msgstr ""
msgid "Number of items received"
msgstr ""
-#: order/templates/order/order_base.html:64
+#: order/templates/order/order_base.html:61
msgid "Purchase Order Details"
msgstr ""
-#: order/templates/order/order_base.html:80
+#: order/templates/order/order_base.html:89
msgid "Issued"
msgstr ""
-#: order/templates/order/order_base.html:86
+#: order/templates/order/order_base.html:96
#: order/templates/order/purchase_order_detail.html:31
msgid "Received"
msgstr ""
@@ -1213,171 +1216,171 @@ msgstr ""
msgid "Select currency for price calculation"
msgstr ""
-#: part/models.py:62
+#: part/models.py:61
msgid "Default location for parts in this category"
msgstr ""
-#: part/models.py:65
+#: part/models.py:64
msgid "Default keywords for parts in this category"
msgstr ""
-#: part/models.py:339
+#: part/models.py:338
msgid "Part must be unique for name, IPN and revision"
msgstr ""
-#: part/models.py:353
+#: part/models.py:352
msgid "Part cannot be a template part if it is a variant of another part"
msgstr ""
-#: part/models.py:354
+#: part/models.py:353
msgid "Part cannot be a variant of another part if it is already a template"
msgstr ""
-#: part/models.py:358 part/templates/part/detail.html:18
+#: part/models.py:357 part/templates/part/detail.html:19
msgid "Part name"
msgstr ""
-#: part/models.py:362
+#: part/models.py:361
msgid "Is this part a template part?"
msgstr ""
-#: part/models.py:371
+#: part/models.py:370
msgid "Is this part a variant of another part?"
msgstr ""
-#: part/models.py:373
+#: part/models.py:372
msgid "Part description"
msgstr ""
-#: part/models.py:375
+#: part/models.py:374
msgid "Part keywords to improve visibility in search results"
msgstr ""
-#: part/models.py:380
+#: part/models.py:379
msgid "Part category"
msgstr ""
-#: part/models.py:382
+#: part/models.py:381
msgid "Internal Part Number"
msgstr ""
-#: part/models.py:384
+#: part/models.py:383
msgid "Part revision or version number"
msgstr ""
-#: part/models.py:386
+#: part/models.py:385
msgid "Link to extenal URL"
msgstr ""
-#: part/models.py:398
+#: part/models.py:397
msgid "Where is this item normally stored?"
msgstr ""
-#: part/models.py:442
+#: part/models.py:441
msgid "Default supplier part"
msgstr ""
-#: part/models.py:445
+#: part/models.py:444
msgid "Minimum allowed stock level"
msgstr ""
-#: part/models.py:447
+#: part/models.py:446
msgid "Stock keeping units for this part"
msgstr ""
-#: part/models.py:449
+#: part/models.py:448
msgid "Can this part be built from other parts?"
msgstr ""
-#: part/models.py:451
+#: part/models.py:450
msgid "Can this part be used to build other parts?"
msgstr ""
-#: part/models.py:453
+#: part/models.py:452
msgid "Does this part have tracking for unique items?"
msgstr ""
-#: part/models.py:455
+#: part/models.py:454
msgid "Can this part be purchased from external suppliers?"
msgstr ""
-#: part/models.py:457
+#: part/models.py:456
msgid "Can this part be sold to customers?"
msgstr ""
-#: part/models.py:459
+#: part/models.py:458
msgid "Is this part active?"
msgstr ""
-#: part/models.py:461
+#: part/models.py:460
msgid "Is this a virtual part, such as a software product or license?"
msgstr ""
-#: part/models.py:463
+#: part/models.py:462
msgid "Part notes - supports Markdown formatting"
msgstr ""
-#: part/models.py:465
+#: part/models.py:464
msgid "Stored BOM checksum"
msgstr ""
-#: part/models.py:1041
+#: part/models.py:1040
msgid "Parameter template name must be unique"
msgstr ""
-#: part/models.py:1046
+#: part/models.py:1045
msgid "Parameter Name"
msgstr ""
-#: part/models.py:1048
+#: part/models.py:1047
msgid "Parameter Units"
msgstr ""
-#: part/models.py:1074
+#: part/models.py:1073
msgid "Parent Part"
msgstr ""
-#: part/models.py:1076
+#: part/models.py:1075
msgid "Parameter Template"
msgstr ""
-#: part/models.py:1078
+#: part/models.py:1077
msgid "Parameter Value"
msgstr ""
-#: part/models.py:1102
+#: part/models.py:1101
msgid "Select parent part"
msgstr ""
-#: part/models.py:1111
+#: part/models.py:1110
msgid "Select part to be used in BOM"
msgstr ""
-#: part/models.py:1118
+#: part/models.py:1117
msgid "BOM quantity for this BOM item"
msgstr ""
-#: part/models.py:1121
+#: part/models.py:1120
msgid "Estimated build wastage quantity (absolute or percentage)"
msgstr ""
-#: part/models.py:1124
+#: part/models.py:1123
msgid "BOM item reference"
msgstr ""
-#: part/models.py:1127
+#: part/models.py:1126
msgid "BOM item notes"
msgstr ""
-#: part/models.py:1129
+#: part/models.py:1128
msgid "BOM line checksum"
msgstr ""
-#: part/models.py:1192
+#: part/models.py:1191
msgid "Part cannot be added to its own Bill of Materials"
msgstr ""
-#: part/models.py:1199
+#: part/models.py:1198
#, python-brace-format
msgid "Part '{p1}' is used in BOM for '{p2}' (recursive)"
msgstr ""
@@ -1386,8 +1389,8 @@ msgstr ""
msgid "Part Attachments"
msgstr ""
-#: part/templates/part/category.html:13 part/templates/part/category.html:76
-#: templates/stats.html:14
+#: part/templates/part/category.html:13 part/templates/part/category.html:78
+#: templates/stats.html:12
msgid "Part Categories"
msgstr ""
@@ -1395,31 +1398,31 @@ msgstr ""
msgid "All parts"
msgstr ""
-#: part/templates/part/category.html:34 part/templates/part/category.html:72
+#: part/templates/part/category.html:34 part/templates/part/category.html:73
msgid "Category Details"
msgstr ""
-#: part/templates/part/category.html:38
+#: part/templates/part/category.html:39
msgid "Category Path"
msgstr ""
-#: part/templates/part/category.html:43
+#: part/templates/part/category.html:44
msgid "Category Description"
msgstr ""
-#: part/templates/part/category.html:49 part/templates/part/detail.html:73
+#: part/templates/part/category.html:50 part/templates/part/detail.html:74
msgid "Default Location"
msgstr ""
-#: part/templates/part/category.html:56 part/templates/part/detail.html:50
+#: part/templates/part/category.html:57 part/templates/part/detail.html:51
msgid "Keywords"
msgstr ""
-#: part/templates/part/category.html:62
+#: part/templates/part/category.html:63
msgid "Subcategories"
msgstr ""
-#: part/templates/part/category.html:67
+#: part/templates/part/category.html:68
msgid "Parts (Including subcategories)"
msgstr ""
@@ -1427,115 +1430,111 @@ msgstr ""
msgid "Part Details"
msgstr ""
-#: part/templates/part/detail.html:24 part/templates/part/part_base.html:75
+#: part/templates/part/detail.html:25 part/templates/part/part_base.html:77
msgid "IPN"
msgstr ""
-#: part/templates/part/detail.html:31
+#: part/templates/part/detail.html:32
msgid "Revision"
msgstr ""
-#: part/templates/part/detail.html:43
+#: part/templates/part/detail.html:44
msgid "Variant Of"
msgstr ""
-#: part/templates/part/detail.html:56
+#: part/templates/part/detail.html:57
msgid "Category"
msgstr ""
-#: part/templates/part/detail.html:66
-msgid "Link"
-msgstr ""
-
-#: part/templates/part/detail.html:80
+#: part/templates/part/detail.html:81
msgid "Default Supplier"
msgstr ""
-#: part/templates/part/detail.html:88
+#: part/templates/part/detail.html:89
msgid "Units"
msgstr ""
-#: part/templates/part/detail.html:94
+#: part/templates/part/detail.html:95
msgid "Minimum Stock"
msgstr ""
-#: part/templates/part/detail.html:100
+#: part/templates/part/detail.html:101
msgid "Creation Date"
msgstr ""
-#: part/templates/part/detail.html:106
+#: part/templates/part/detail.html:107
msgid "Created By"
msgstr ""
-#: part/templates/part/detail.html:113
+#: part/templates/part/detail.html:114
msgid "Responsible User"
msgstr ""
-#: part/templates/part/detail.html:122
+#: part/templates/part/detail.html:123
msgid "Virtual"
msgstr ""
-#: part/templates/part/detail.html:125
+#: part/templates/part/detail.html:126
msgid "Part is virtual (not a physical part)"
msgstr ""
-#: part/templates/part/detail.html:127
+#: part/templates/part/detail.html:128
msgid "Part is not a virtual part"
msgstr ""
-#: part/templates/part/detail.html:131
+#: part/templates/part/detail.html:132
msgid "Assembly"
msgstr ""
-#: part/templates/part/detail.html:134
+#: part/templates/part/detail.html:135
msgid "Part can be assembled from other parts"
msgstr ""
-#: part/templates/part/detail.html:136
+#: part/templates/part/detail.html:137
msgid "Part cannot be assembled from other parts"
msgstr ""
-#: part/templates/part/detail.html:140
+#: part/templates/part/detail.html:141
msgid "Component"
msgstr ""
-#: part/templates/part/detail.html:143
+#: part/templates/part/detail.html:144
msgid "Part can be used in assemblies"
msgstr ""
-#: part/templates/part/detail.html:145
+#: part/templates/part/detail.html:146
msgid "Part cannot be used in assemblies"
msgstr ""
-#: part/templates/part/detail.html:149
+#: part/templates/part/detail.html:150
msgid "Trackable"
msgstr ""
-#: part/templates/part/detail.html:152
+#: part/templates/part/detail.html:153
msgid "Part stock is tracked by serial number"
msgstr ""
-#: part/templates/part/detail.html:154
+#: part/templates/part/detail.html:155
msgid "Part stock is not tracked by serial number"
msgstr ""
-#: part/templates/part/detail.html:158
+#: part/templates/part/detail.html:159
msgid "Purchaseable"
msgstr ""
-#: part/templates/part/detail.html:161 part/templates/part/detail.html:163
+#: part/templates/part/detail.html:162 part/templates/part/detail.html:164
msgid "Part can be purchased from external suppliers"
msgstr ""
-#: part/templates/part/detail.html:168
+#: part/templates/part/detail.html:169
msgid "Sellable"
msgstr ""
-#: part/templates/part/detail.html:171
+#: part/templates/part/detail.html:172
msgid "Part can be sold to customers"
msgstr ""
-#: part/templates/part/detail.html:173
+#: part/templates/part/detail.html:174
msgid "Part cannot be sold to customers"
msgstr ""
@@ -1563,23 +1562,23 @@ msgstr ""
msgid "Show pricing information"
msgstr ""
-#: part/templates/part/part_base.html:95
+#: part/templates/part/part_base.html:98
msgid "Available Stock"
msgstr ""
-#: part/templates/part/part_base.html:100
+#: part/templates/part/part_base.html:103
msgid "In Stock"
msgstr ""
-#: part/templates/part/part_base.html:121
+#: part/templates/part/part_base.html:124
msgid "Build Status"
msgstr ""
-#: part/templates/part/part_base.html:125
+#: part/templates/part/part_base.html:128
msgid "Can Build"
msgstr ""
-#: part/templates/part/part_base.html:130
+#: part/templates/part/part_base.html:133
msgid "Underway"
msgstr ""
@@ -1619,7 +1618,7 @@ msgstr ""
msgid "BOM"
msgstr ""
-#: part/templates/part/tabs.html:28 stock/templates/stock/item_base.html:113
+#: part/templates/part/tabs.html:28 stock/templates/stock/item_base.html:121
#: templates/navbar.html:12
msgid "Build"
msgstr ""
@@ -1737,67 +1736,67 @@ msgstr ""
msgid "Export Bill of Materials"
msgstr ""
-#: part/views.py:1406
+#: part/views.py:1404
msgid "Confirm Part Deletion"
msgstr ""
-#: part/views.py:1413
+#: part/views.py:1411
msgid "Part was deleted"
msgstr ""
-#: part/views.py:1422
+#: part/views.py:1420
msgid "Part Pricing"
msgstr ""
-#: part/views.py:1544
+#: part/views.py:1542
msgid "Create Part Parameter Template"
msgstr ""
-#: part/views.py:1552
+#: part/views.py:1550
msgid "Edit Part Parameter Template"
msgstr ""
-#: part/views.py:1559
+#: part/views.py:1557
msgid "Delete Part Parameter Template"
msgstr ""
-#: part/views.py:1567
+#: part/views.py:1565
msgid "Create Part Parameter"
msgstr ""
-#: part/views.py:1617
+#: part/views.py:1615
msgid "Edit Part Parameter"
msgstr ""
-#: part/views.py:1631
+#: part/views.py:1629
msgid "Delete Part Parameter"
msgstr ""
-#: part/views.py:1647
+#: part/views.py:1645
msgid "Edit Part Category"
msgstr ""
-#: part/views.py:1682
+#: part/views.py:1680
msgid "Delete Part Category"
msgstr ""
-#: part/views.py:1688
+#: part/views.py:1686
msgid "Part category was deleted"
msgstr ""
-#: part/views.py:1696
+#: part/views.py:1694
msgid "Create new part category"
msgstr ""
-#: part/views.py:1747
+#: part/views.py:1745
msgid "Create BOM item"
msgstr ""
-#: part/views.py:1813
+#: part/views.py:1811
msgid "Edit BOM item"
msgstr ""
-#: part/views.py:1861
+#: part/views.py:1859
msgid "Confim BOM item deletion"
msgstr ""
@@ -1893,45 +1892,45 @@ msgstr ""
msgid "Stock Item Notes"
msgstr ""
-#: stock/models.py:460
+#: stock/models.py:464
msgid "Quantity must be integer"
msgstr ""
-#: stock/models.py:466
+#: stock/models.py:470
#, python-brace-format
msgid "Quantity must not exceed available stock quantity ({n})"
msgstr ""
-#: stock/models.py:469 stock/models.py:472
+#: stock/models.py:473 stock/models.py:476
msgid "Serial numbers must be a list of integers"
msgstr ""
-#: stock/models.py:475
+#: stock/models.py:479
msgid "Quantity does not match serial numbers"
msgstr ""
-#: stock/models.py:485
+#: stock/models.py:489
msgid "Serial numbers already exist: "
msgstr ""
-#: stock/models.py:507
+#: stock/models.py:511
msgid "Add serial number"
msgstr ""
-#: stock/models.py:510
+#: stock/models.py:514
#, python-brace-format
msgid "Serialized {n} items"
msgstr ""
-#: stock/models.py:810
+#: stock/models.py:814
msgid "Tracking entry title"
msgstr ""
-#: stock/models.py:812
+#: stock/models.py:816
msgid "Entry notes"
msgstr ""
-#: stock/models.py:814
+#: stock/models.py:818
msgid "Link to external page for further information"
msgstr ""
@@ -1962,28 +1961,28 @@ msgstr ""
msgid "This stock item was split from "
msgstr ""
-#: stock/templates/stock/item_base.html:85
+#: stock/templates/stock/item_base.html:88
msgid "Belongs To"
msgstr ""
-#: stock/templates/stock/item_base.html:90
+#: stock/templates/stock/item_base.html:94
#: stock/templates/stock/stock_adjust.html:17
msgid "Location"
msgstr ""
-#: stock/templates/stock/item_base.html:96
+#: stock/templates/stock/item_base.html:101
msgid "Serial Number"
msgstr ""
-#: stock/templates/stock/item_base.html:146
+#: stock/templates/stock/item_base.html:160
msgid "Last Updated"
msgstr ""
-#: stock/templates/stock/item_base.html:150
+#: stock/templates/stock/item_base.html:165
msgid "Last Stocktake"
msgstr ""
-#: stock/templates/stock/item_base.html:154
+#: stock/templates/stock/item_base.html:169
msgid "No stocktake performed"
msgstr ""
@@ -1999,29 +1998,29 @@ msgstr ""
msgid "Location Details"
msgstr ""
-#: stock/templates/stock/location.html:41
+#: stock/templates/stock/location.html:42
msgid "Location Path"
msgstr ""
-#: stock/templates/stock/location.html:46
+#: stock/templates/stock/location.html:47
msgid "Location Description"
msgstr ""
-#: stock/templates/stock/location.html:51
+#: stock/templates/stock/location.html:52
msgid "Sublocations"
msgstr ""
-#: stock/templates/stock/location.html:56
-#: stock/templates/stock/location.html:70 templates/stats.html:18
-#: templates/stats.html:21
+#: stock/templates/stock/location.html:57
+#: stock/templates/stock/location.html:72 templates/stats.html:21
+#: templates/stats.html:30
msgid "Stock Items"
msgstr ""
-#: stock/templates/stock/location.html:61
+#: stock/templates/stock/location.html:62
msgid "Stock Details"
msgstr ""
-#: stock/templates/stock/location.html:65
+#: stock/templates/stock/location.html:67
#: templates/InvenTree/search_stock_location.html:6 templates/stats.html:25
msgid "Stock Locations"
msgstr ""
@@ -2194,30 +2193,38 @@ msgstr ""
msgid "No results found"
msgstr ""
-#: templates/about.html:18
+#: templates/about.html:13
msgid "InvenTree Version Information"
msgstr ""
#: templates/about.html:21
-msgid "Version"
+msgid "Instance Name"
msgstr ""
-#: templates/about.html:24
+#: templates/about.html:26
+msgid "InvenTree Version"
+msgstr ""
+
+#: templates/about.html:30
msgid "Commit Hash"
msgstr ""
-#: templates/about.html:27
+#: templates/about.html:34
msgid "Commit Date"
msgstr ""
-#: templates/about.html:33
+#: templates/about.html:38
msgid "InvenTree Documentation"
msgstr ""
-#: templates/about.html:37
+#: templates/about.html:43
msgid "View Code on GitHub"
msgstr ""
+#: templates/about.html:47
+msgid "Submit Bug Report"
+msgstr ""
+
#: templates/navbar.html:23
msgid "Admin"
msgstr ""
diff --git a/InvenTree/part/apps.py b/InvenTree/part/apps.py
index 43f6429c19..95193a9527 100644
--- a/InvenTree/part/apps.py
+++ b/InvenTree/part/apps.py
@@ -1,7 +1,35 @@
from __future__ import unicode_literals
+import os
+
+from django.db.utils import OperationalError, ProgrammingError
from django.apps import AppConfig
+from django.conf import settings
class PartConfig(AppConfig):
name = 'part'
+
+ def ready(self):
+ """
+ This function is called whenever the Part app is loaded.
+ """
+
+ self.generate_part_thumbnails()
+
+ def generate_part_thumbnails(self):
+ from .models import Part
+
+ print("InvenTree: Checking Part image thumbnails")
+
+ try:
+ for part in Part.objects.all():
+ if part.image:
+ url = part.image.thumbnail.name
+ loc = os.path.join(settings.MEDIA_ROOT, url)
+
+ if not os.path.exists(loc):
+ print("InvenTree: Generating thumbnail for Part '{p}'".format(p=part.name))
+ part.image.render_variations(replace=False)
+ except (OperationalError, ProgrammingError):
+ print("Could not generate Part thumbnails")
diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py
index da0bff16b8..d751dca215 100644
--- a/InvenTree/part/models.py
+++ b/InvenTree/part/models.py
@@ -10,7 +10,6 @@ import os
from django.utils.translation import gettext_lazy as _
from django.core.exceptions import ValidationError
from django.urls import reverse
-from django.conf import settings
from django.db import models, transaction
from django.db.models import Sum
@@ -300,9 +299,9 @@ class Part(models.Model):
""" Return the URL of the image for this part """
if self.image:
- return os.path.join(settings.MEDIA_URL, str(self.image.url))
+ return helpers.getMediaUrl(self.image.url)
else:
- return os.path.join(settings.STATIC_URL, 'img/blank_image.png')
+ return helpers.getBlankImage()
def get_thumbnail_url(self):
"""
@@ -310,9 +309,9 @@ class Part(models.Model):
"""
if self.image:
- return os.path.join(settings.MEDIA_URL, str(self.image.thumbnail.url))
+ return helpers.getMediaUrl(self.image.thumbnail.url)
else:
- return os.path.join(settings.STATIC_URL, 'img/blank_image.thumbnail.png')
+ return helpers.getBlankThumbnail()
def validate_unique(self, exclude=None):
""" Validate that a part is 'unique'.
diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py
index b0d3b3bd62..ad989459ee 100644
--- a/InvenTree/stock/api.py
+++ b/InvenTree/stock/api.py
@@ -14,7 +14,7 @@ from .models import StockItemTracking
from part.models import Part, PartCategory
-from .serializers import StockItemSerializer, StockQuantitySerializer
+from .serializers import StockItemSerializer
from .serializers import LocationSerializer
from .serializers import StockTrackingSerializer
@@ -23,11 +23,12 @@ from InvenTree.helpers import str2bool, isNull
from InvenTree.status_codes import StockStatus
import os
+from decimal import Decimal, InvalidOperation
from rest_framework.serializers import ValidationError
from rest_framework.views import APIView
from rest_framework.response import Response
-from rest_framework import generics, response, filters, permissions
+from rest_framework import generics, filters, permissions
class StockCategoryTree(TreeSerializer):
@@ -95,144 +96,154 @@ class StockFilter(FilterSet):
fields = ['quantity', 'part', 'location']
-class StockStocktake(APIView):
- """ Stocktake API endpoint provides stock update of multiple items simultaneously.
- The 'action' field tells the type of stock action to perform:
- - stocktake: Count the stock item(s)
- - remove: Remove the quantity provided from stock
- - add: Add the quantity provided from stock
+class StockAdjust(APIView):
+ """
+ A generic class for handling stocktake actions.
+
+ Subclasses exist for:
+
+ - StockCount: count stock items
+ - StockAdd: add stock items
+ - StockRemove: remove stock items
+ - StockTransfer: transfer stock items
"""
permission_classes = [
permissions.IsAuthenticated,
]
+ def get_items(self, request):
+ """
+ Return a list of items posted to the endpoint.
+ Will raise validation errors if the items are not
+ correctly formatted.
+ """
+
+ _items = []
+
+ if 'item' in request.data:
+ _items = [request.data['item']]
+ elif 'items' in request.data:
+ _items = request.data['items']
+ else:
+ raise ValidationError({'items': 'Request must contain list of stock items'})
+
+ # List of validated items
+ self.items = []
+
+ for entry in _items:
+
+ if not type(entry) == dict:
+ raise ValidationError({'error': 'Improperly formatted data'})
+
+ try:
+ pk = entry.get('pk', None)
+ item = StockItem.objects.get(pk=pk)
+ except (ValueError, StockItem.DoesNotExist):
+ raise ValidationError({'pk': 'Each entry must contain a valid pk field'})
+
+ try:
+ quantity = Decimal(str(entry.get('quantity', None)))
+ except (ValueError, TypeError, InvalidOperation):
+ raise ValidationError({'quantity': 'Each entry must contain a valid quantity field'})
+
+ if quantity < 0:
+ raise ValidationError({'quantity': 'Quantity field must not be less than zero'})
+
+ self.items.append({
+ 'item': item,
+ 'quantity': quantity
+ })
+
+ self.notes = str(request.data.get('notes', ''))
+
+
+class StockCount(StockAdjust):
+ """
+ Endpoint for counting stock (performing a stocktake).
+ """
+
def post(self, request, *args, **kwargs):
- if 'action' not in request.data:
- raise ValidationError({'action': 'Stocktake action must be provided'})
-
- action = request.data['action']
-
- ACTIONS = ['stocktake', 'remove', 'add']
-
- if action not in ACTIONS:
- raise ValidationError({'action': 'Action must be one of ' + ','.join(ACTIONS)})
-
- elif 'items[]' not in request.data:
- raise ValidationError({'items[]:' 'Request must contain list of items'})
-
- items = []
-
- # Ensure each entry is valid
- for entry in request.data['items[]']:
- if 'pk' not in entry:
- raise ValidationError({'pk': 'Each entry must contain pk field'})
- elif 'quantity' not in entry:
- raise ValidationError({'quantity': 'Each entry must contain quantity field'})
-
- item = {}
- try:
- item['item'] = StockItem.objects.get(pk=entry['pk'])
- except StockItem.DoesNotExist:
- raise ValidationError({'pk': 'No matching StockItem found for pk={pk}'.format(pk=entry['pk'])})
- try:
- item['quantity'] = int(entry['quantity'])
- except ValueError:
- raise ValidationError({'quantity': 'Quantity must be an integer'})
-
- if item['quantity'] < 0:
- raise ValidationError({'quantity': 'Quantity must be >= 0'})
-
- items.append(item)
-
- # Stocktake notes
- notes = ''
-
- if 'notes' in request.data:
- notes = request.data['notes']
+ self.get_items(request)
n = 0
- for item in items:
- quantity = int(item['quantity'])
+ for item in self.items:
- if action == u'stocktake':
- if item['item'].stocktake(quantity, request.user, notes=notes):
- n += 1
- elif action == u'remove':
- if item['item'].take_stock(quantity, request.user, notes=notes):
- n += 1
- elif action == u'add':
- if item['item'].add_stock(quantity, request.user, notes=notes):
- n += 1
+ if item['item'].stocktake(item['quantity'], request.user, notes=self.notes):
+ n += 1
return Response({'success': 'Updated stock for {n} items'.format(n=n)})
-class StockMove(APIView):
- """ API endpoint for performing stock movements """
-
- permission_classes = [
- permissions.IsAuthenticated,
- ]
+class StockAdd(StockAdjust):
+ """
+ Endpoint for adding stock
+ """
def post(self, request, *args, **kwargs):
+ self.get_items(request)
+
+ n = 0
+
+ for item in self.items:
+ if item['item'].add_stock(item['quantity'], request.user, notes=self.notes):
+ n += 1
+
+ return Response({"success": "Added stock for {n} items".format(n=n)})
+
+
+class StockRemove(StockAdjust):
+ """
+ Endpoint for removing stock.
+ """
+
+ def post(self, request, *args, **kwargs):
+
+ self.get_items(request)
+
+ n = 0
+
+ for item in self.items:
+
+ if item['item'].take_stock(item['quantity'], request.user, notes=self.notes):
+ n += 1
+
+ return Response({"success": "Removed stock for {n} items".format(n=n)})
+
+
+class StockTransfer(StockAdjust):
+ """
+ API endpoint for performing stock movements
+ """
+
+ def post(self, request, *args, **kwargs):
+
+ self.get_items(request)
+
data = request.data
- if 'location' not in data:
- raise ValidationError({'location': 'Destination must be specified'})
-
try:
- loc_id = int(data.get('location'))
- except ValueError:
- raise ValidationError({'location': 'Integer ID required'})
+ location = StockLocation.objects.get(pk=data.get('location', None))
+ except (ValueError, StockLocation.DoesNotExist):
+ raise ValidationError({'location': 'Valid location must be specified'})
- try:
- location = StockLocation.objects.get(pk=loc_id)
- except StockLocation.DoesNotExist:
- raise ValidationError({'location': 'Location does not exist'})
+ n = 0
- if 'stock' not in data:
- raise ValidationError({'stock': 'Stock list must be specified'})
-
- stock_list = data.get('stock')
+ for item in self.items:
- if type(stock_list) is not list:
- raise ValidationError({'stock': 'Stock must be supplied as a list'})
+ # If quantity is not specified, move the entire stock
+ if item['quantity'] in [0, None]:
+ item['quantity'] = item['item'].quantity
- if 'notes' not in data:
- raise ValidationError({'notes': 'Notes field must be supplied'})
+ if item['item'].move(location, self.notes, request.user, quantity=item['quantity']):
+ n += 1
- for item in stock_list:
- try:
- stock_id = int(item['pk'])
- if 'quantity' in item:
- quantity = int(item['quantity'])
- else:
- # If quantity not supplied, we'll move the entire stock
- quantity = None
- except ValueError:
- # Ignore this one
- continue
-
- # Ignore a zero quantity movement
- if quantity <= 0:
- continue
-
- try:
- stock = StockItem.objects.get(pk=stock_id)
- except StockItem.DoesNotExist:
- continue
-
- if quantity is None:
- quantity = stock.quantity
-
- stock.move(location, data.get('notes'), request.user, quantity=quantity)
-
- return Response({'success': 'Moved parts to {loc}'.format(
- loc=str(location)
+ return Response({'success': 'Moved {n} parts to {loc}'.format(
+ n=n,
+ loc=str(location),
)})
@@ -512,22 +523,6 @@ class StockList(generics.ListCreateAPIView):
]
-class StockStocktakeEndpoint(generics.UpdateAPIView):
- """ API endpoint for performing stocktake """
-
- queryset = StockItem.objects.all()
- serializer_class = StockQuantitySerializer
- permission_classes = (permissions.IsAuthenticated,)
-
- def update(self, request, *args, **kwargs):
- object = self.get_object()
- object.stocktake(request.data['quantity'], request.user)
-
- serializer = self.get_serializer(object)
-
- return response.Response(serializer.data)
-
-
class StockTrackingList(generics.ListCreateAPIView):
""" API endpoint for list view of StockItemTracking objects.
@@ -591,8 +586,10 @@ stock_api_urls = [
url(r'location/', include(location_endpoints)),
# These JSON endpoints have been replaced (for now) with server-side form rendering - 02/06/2019
- # url(r'stocktake/?', StockStocktake.as_view(), name='api-stock-stocktake'),
- # url(r'move/?', StockMove.as_view(), name='api-stock-move'),
+ url(r'count/?', StockCount.as_view(), name='api-stock-count'),
+ url(r'add/?', StockAdd.as_view(), name='api-stock-add'),
+ url(r'remove/?', StockRemove.as_view(), name='api-stock-remove'),
+ url(r'transfer/?', StockTransfer.as_view(), name='api-stock-transfer'),
url(r'track/?', StockTrackingList.as_view(), name='api-stock-track'),
diff --git a/InvenTree/stock/templates/stock/item_base.html b/InvenTree/stock/templates/stock/item_base.html
index 949823b83b..6c38335b3e 100644
--- a/InvenTree/stock/templates/stock/item_base.html
+++ b/InvenTree/stock/templates/stock/item_base.html
@@ -11,7 +11,7 @@
{% if item.serialized %}
{{ item.part.full_name}} # {{ item.serial }}
{% else %}
- {{ item.quantity }} × {{ item.part.full_name }}
+ {% decimal item.quantity %} × {{ item.part.full_name }}
{% endif %}
diff --git a/InvenTree/stock/test_api.py b/InvenTree/stock/test_api.py
index 27207020b4..fe49547cee 100644
--- a/InvenTree/stock/test_api.py
+++ b/InvenTree/stock/test_api.py
@@ -63,3 +63,119 @@ class StockItemTest(APITestCase):
def test_get_stock_list(self):
response = self.client.get(self.list_url, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
+
+
+class StocktakeTest(APITestCase):
+ """
+ Series of tests for the Stocktake API
+ """
+
+ fixtures = [
+ 'category',
+ 'part',
+ 'company',
+ 'location',
+ 'supplier_part',
+ 'stock',
+ ]
+
+ def setUp(self):
+ User = get_user_model()
+ User.objects.create_user('testuser', 'test@testing.com', 'password')
+ self.client.login(username='testuser', password='password')
+
+ def doPost(self, url, data={}):
+ response = self.client.post(url, data=data, format='json')
+
+ return response
+
+ def test_action(self):
+ """
+ Test each stocktake action endpoint,
+ for validation
+ """
+
+ for endpoint in ['api-stock-count', 'api-stock-add', 'api-stock-remove']:
+
+ url = reverse(endpoint)
+
+ data = {}
+
+ # POST with a valid action
+ response = self.doPost(url, data)
+ self.assertContains(response, "must contain list", status_code=status.HTTP_400_BAD_REQUEST)
+
+ data['items'] = [{
+ 'no': 'aa'
+ }]
+
+ # POST without a PK
+ response = self.doPost(url, data)
+ self.assertContains(response, 'must contain a valid pk', status_code=status.HTTP_400_BAD_REQUEST)
+
+ # POST with a PK but no quantity
+ data['items'] = [{
+ 'pk': 10
+ }]
+
+ response = self.doPost(url, data)
+ self.assertContains(response, 'must contain a valid pk', status_code=status.HTTP_400_BAD_REQUEST)
+
+ data['items'] = [{
+ 'pk': 1234
+ }]
+
+ response = self.doPost(url, data)
+ self.assertContains(response, 'must contain a valid quantity', status_code=status.HTTP_400_BAD_REQUEST)
+
+ data['items'] = [{
+ 'pk': 1234,
+ 'quantity': '10x0d'
+ }]
+
+ response = self.doPost(url, data)
+ self.assertContains(response, 'must contain a valid quantity', status_code=status.HTTP_400_BAD_REQUEST)
+
+ data['items'] = [{
+ 'pk': 1234,
+ 'quantity': "-1.234"
+ }]
+
+ response = self.doPost(url, data)
+ self.assertContains(response, 'must not be less than zero', status_code=status.HTTP_400_BAD_REQUEST)
+
+ # Test with a single item
+ data = {
+ 'item': {
+ 'pk': 1234,
+ 'quantity': '10',
+ }
+ }
+
+ response = self.doPost(url, data)
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+
+ def test_transfer(self):
+ """
+ Test stock transfers
+ """
+
+ data = {
+ 'item': {
+ 'pk': 1234,
+ 'quantity': 10,
+ },
+ 'location': 1,
+ 'notes': "Moving to a new location"
+ }
+
+ url = reverse('api-stock-transfer')
+
+ response = self.doPost(url, data)
+ self.assertContains(response, "Moved 1 parts to", status_code=status.HTTP_200_OK)
+
+ # Now try one which will fail due to a bad location
+ data['location'] = 'not a location'
+
+ response = self.doPost(url, data)
+ self.assertContains(response, 'Valid location must be specified', status_code=status.HTTP_400_BAD_REQUEST)