From 1356718bb709c0ce4b47b9d477eb21585fa4c884 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 7 Apr 2020 00:19:43 +1000 Subject: [PATCH 01/14] Improver company website icon --- InvenTree/company/templates/company/company_base.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 }} From 97f605ef55076dc4ee72abff26ddf381627effbc Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 7 Apr 2020 00:19:51 +1000 Subject: [PATCH 02/14] Remove some weird trailing zeros --- InvenTree/stock/templates/stock/item_base.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 %}

From 7fb89e4dbe2cbed4ef37e13073d4fa3f2a650049 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 7 Apr 2020 08:08:54 +1000 Subject: [PATCH 03/14] Check for missing part thumbnails when the server first runs --- InvenTree/part/apps.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/InvenTree/part/apps.py b/InvenTree/part/apps.py index 43f6429c19..ae17f7ffe0 100644 --- a/InvenTree/part/apps.py +++ b/InvenTree/part/apps.py @@ -1,7 +1,36 @@ 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("Checking Part image thumbnails") + + try: + for part in Part.objects.all(): + if part.image: + url = part.image.thumbnail.name + #if url.startswith('/'): + # url = url[1:] + 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") From 16d3a87e789d7ac994e273b9863c064b705e9d9e Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 7 Apr 2020 08:12:10 +1000 Subject: [PATCH 04/14] Delete old code --- InvenTree/part/apps.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/InvenTree/part/apps.py b/InvenTree/part/apps.py index ae17f7ffe0..7b61443d24 100644 --- a/InvenTree/part/apps.py +++ b/InvenTree/part/apps.py @@ -26,8 +26,6 @@ class PartConfig(AppConfig): for part in Part.objects.all(): if part.image: url = part.image.thumbnail.name - #if url.startswith('/'): - # url = url[1:] 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)) From 1a233e7949616115d3f6fc3cbe6fcdd0ebb87665 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 7 Apr 2020 11:17:00 +1000 Subject: [PATCH 05/14] Create thumbnails for Company model --- .../migrations/0014_auto_20200407_0116.py | 20 +++++++++++++++++++ InvenTree/company/models.py | 10 +++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 InvenTree/company/migrations/0014_auto_20200407_0116.py 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..a7dd2e53e8 100644 --- a/InvenTree/company/models.py +++ b/InvenTree/company/models.py @@ -21,6 +21,8 @@ from django.conf import settings from markdownx.models import MarkdownxField +from stdimage.models import StdImageField + 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={'thumnbnail': (128, 128)}, + delete_orphans=True, + ) notes = MarkdownxField(blank=True) From e0655f61d8c2ff1ffabcfb75e0e6837a739ba1cc Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 7 Apr 2020 11:23:30 +1000 Subject: [PATCH 06/14] Check if Company thumbnails are created on Company app start --- InvenTree/company/apps.py | 31 ++++++++++++++++++++++++++++++- InvenTree/company/models.py | 2 +- InvenTree/part/apps.py | 3 ++- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/InvenTree/company/apps.py b/InvenTree/company/apps.py index c55451e882..25f3bd96dc 100644 --- a/InvenTree/company/apps.py +++ b/InvenTree/company/apps.py @@ -1,7 +1,36 @@ from __future__ import unicode_literals -from django.apps import AppConfig +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") + \ No newline at end of file diff --git a/InvenTree/company/models.py b/InvenTree/company/models.py index a7dd2e53e8..59a6a2b37a 100644 --- a/InvenTree/company/models.py +++ b/InvenTree/company/models.py @@ -96,7 +96,7 @@ class Company(models.Model): upload_to=rename_company_image, null=True, blank=True, - variations={'thumnbnail': (128, 128)}, + variations={'thumbnail': (128, 128)}, delete_orphans=True, ) diff --git a/InvenTree/part/apps.py b/InvenTree/part/apps.py index 7b61443d24..95193a9527 100644 --- a/InvenTree/part/apps.py +++ b/InvenTree/part/apps.py @@ -20,13 +20,14 @@ class PartConfig(AppConfig): def generate_part_thumbnails(self): from .models import Part - print("Checking Part image thumbnails") + 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) From ae9ef040130dc5aa7ba62cf9b8584e0e2c28f840 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 7 Apr 2020 11:27:56 +1000 Subject: [PATCH 07/14] Use the company thumbnail in the Company API --- InvenTree/company/models.py | 8 ++++++++ InvenTree/company/serializers.py | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/InvenTree/company/models.py b/InvenTree/company/models.py index 59a6a2b37a..69c98a008a 100644 --- a/InvenTree/company/models.py +++ b/InvenTree/company/models.py @@ -122,6 +122,14 @@ class Company(models.Model): else: return os.path.join(settings.STATIC_URL, 'img/blank_image.png') + def get_thumbnail_url(self): + """ Return the URL for the thumbnail image for this Company """ + + if self.image: + return os.path.join(settings.MEDIA_URL, str(self.image.thumbnail.url)) + else: + return os.path.join(settings.STATIC_URL, 'img/blank_image.thumbnail.png') + @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) From 95032141ce4c7384f1731b23bb0276adec003bc9 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 7 Apr 2020 11:38:57 +1000 Subject: [PATCH 08/14] Toot toot! It's the refactor tractor. - Create helper functions to qualify media and static files --- InvenTree/InvenTree/helpers.py | 35 ++++++++++++++++++++++++++++++++++ InvenTree/company/models.py | 11 ++++++----- InvenTree/part/models.py | 10 +++++----- 3 files changed, 46 insertions(+), 10 deletions(-) diff --git a/InvenTree/InvenTree/helpers.py b/InvenTree/InvenTree/helpers.py index 5492f13a05..d46f35daf4 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): diff --git a/InvenTree/company/models.py b/InvenTree/company/models.py index 69c98a008a..0493cbbfa1 100644 --- a/InvenTree/company/models.py +++ b/InvenTree/company/models.py @@ -23,6 +23,7 @@ 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 @@ -118,18 +119,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 os.path.join(settings.MEDIA_URL, str(self.image.thumbnail.url)) + return getMediaUrl(self.image.thumbnail.url) else: - return os.path.join(settings.STATIC_URL, 'img/blank_image.thumbnail.png') - + return getBlankThumbnail() + @property def part_count(self): """ The number of parts supplied by this company """ diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index da0bff16b8..f255d2a77b 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -300,9 +300,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,10 +310,10 @@ 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'. Uniqueness is checked across the following (case insensitive) fields: From d06018cbbe22b56aaaad94a8d69da7699a7a6854 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 7 Apr 2020 11:40:10 +1000 Subject: [PATCH 09/14] PEP fixes --- InvenTree/company/apps.py | 2 +- InvenTree/company/models.py | 1 - InvenTree/part/models.py | 3 +-- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/InvenTree/company/apps.py b/InvenTree/company/apps.py index 25f3bd96dc..949ee152c0 100644 --- a/InvenTree/company/apps.py +++ b/InvenTree/company/apps.py @@ -6,6 +6,7 @@ from django.apps import AppConfig from django.db.utils import OperationalError, ProgrammingError from django.conf import settings + class CompanyConfig(AppConfig): name = 'company' @@ -33,4 +34,3 @@ class CompanyConfig(AppConfig): company.image.render_variations(replace=False) except (OperationalError, ProgrammingError): print("Could not generate Company thumbnails") - \ No newline at end of file diff --git a/InvenTree/company/models.py b/InvenTree/company/models.py index 0493cbbfa1..462b4ff847 100644 --- a/InvenTree/company/models.py +++ b/InvenTree/company/models.py @@ -17,7 +17,6 @@ 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 diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index f255d2a77b..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 @@ -313,7 +312,7 @@ class Part(models.Model): return helpers.getMediaUrl(self.image.thumbnail.url) else: return helpers.getBlankThumbnail() - + def validate_unique(self, exclude=None): """ Validate that a part is 'unique'. Uniqueness is checked across the following (case insensitive) fields: From 623a0844d3c01d5584863168ed06d93eee22dcc2 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 7 Apr 2020 11:50:46 +1000 Subject: [PATCH 10/14] Coverage tests for new functions --- InvenTree/InvenTree/helpers.py | 2 +- InvenTree/InvenTree/tests.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/InvenTree/InvenTree/helpers.py b/InvenTree/InvenTree/helpers.py index d46f35daf4..6b29adb4c4 100644 --- a/InvenTree/InvenTree/helpers.py +++ b/InvenTree/InvenTree/helpers.py @@ -101,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..1fc660c7d8 100644 --- a/InvenTree/InvenTree/tests.py +++ b/InvenTree/InvenTree/tests.py @@ -72,6 +72,24 @@ 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') + class TestQuoteWrap(TestCase): """ Tests for string wrapping """ From e94592e42d83568fc21b16ae40a0c78a3cdd0aea Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 7 Apr 2020 12:09:25 +1000 Subject: [PATCH 11/14] Moar unit testing plz --- InvenTree/InvenTree/tests.py | 18 ++++++++++++++++++ Makefile | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/InvenTree/InvenTree/tests.py b/InvenTree/InvenTree/tests.py index 1fc660c7d8..75ece9748d 100644 --- a/InvenTree/InvenTree/tests.py +++ b/InvenTree/InvenTree/tests.py @@ -1,12 +1,18 @@ + +import os + from django.test import TestCase import django.core.exceptions as django_exceptions from django.core.exceptions import ValidationError from .validators import validate_overage, validate_part_name from . import helpers +from .settings import STATIC_ROOT from mptt.exceptions import InvalidMove +from decimal import Decimal + from stock.models import StockLocation @@ -48,6 +54,13 @@ class ValidatorTest(TestCase): class TestHelpers(TestCase): """ Tests for InvenTree helper functions """ + def test_is_image(self): + img = os.path.abspath(os.path.join(STATIC_ROOT, 'img/blank_image.png')) + self.assertTrue(helpers.TestIfImage(img)) + + css = os.path.abspath(os.path.join(STATIC_ROOT, 'css/inventree.css')) + self.assertFalse(helpers.TestIfImage(css)) + def test_image_url(self): """ Test if a filename looks like an image """ @@ -90,6 +103,11 @@ class TestHelpers(TestCase): 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/Makefile b/Makefile index cb38a601d8..32e1675b45 100644 --- a/Makefile +++ b/Makefile @@ -56,7 +56,7 @@ test: # Run code coverage coverage: cd InvenTree && python3 manage.py check - coverage run InvenTree/manage.py test build common company order part stock InvenTree + cd InvenTree && coverage run manage.py test build common company order part stock InvenTree coverage html # Install packages required to generate code docs From 8786776fd6de87d17dadf4c30cfb067ea9c6f550 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 7 Apr 2020 13:08:30 +1000 Subject: [PATCH 12/14] Remove some tests - CI complications --- InvenTree/InvenTree/tests.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/InvenTree/InvenTree/tests.py b/InvenTree/InvenTree/tests.py index 75ece9748d..7553d1b10c 100644 --- a/InvenTree/InvenTree/tests.py +++ b/InvenTree/InvenTree/tests.py @@ -54,13 +54,6 @@ class ValidatorTest(TestCase): class TestHelpers(TestCase): """ Tests for InvenTree helper functions """ - def test_is_image(self): - img = os.path.abspath(os.path.join(STATIC_ROOT, 'img/blank_image.png')) - self.assertTrue(helpers.TestIfImage(img)) - - css = os.path.abspath(os.path.join(STATIC_ROOT, 'css/inventree.css')) - self.assertFalse(helpers.TestIfImage(css)) - def test_image_url(self): """ Test if a filename looks like an image """ From 1a0a4622a2e9ff0e6eed22be01c62f1a40c58409 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 7 Apr 2020 13:16:23 +1000 Subject: [PATCH 13/14] Revert makefile change --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 32e1675b45..cb38a601d8 100644 --- a/Makefile +++ b/Makefile @@ -56,7 +56,7 @@ test: # Run code coverage coverage: cd InvenTree && python3 manage.py check - cd InvenTree && coverage run manage.py test build common company order part stock InvenTree + coverage run InvenTree/manage.py test build common company order part stock InvenTree coverage html # Install packages required to generate code docs From 5aec63d9e4e651bb110e135efc6908304e494f3c Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 7 Apr 2020 14:20:43 +1000 Subject: [PATCH 14/14] Remove unused includes --- InvenTree/InvenTree/tests.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/InvenTree/InvenTree/tests.py b/InvenTree/InvenTree/tests.py index 7553d1b10c..d93a40e631 100644 --- a/InvenTree/InvenTree/tests.py +++ b/InvenTree/InvenTree/tests.py @@ -1,13 +1,10 @@ -import os - from django.test import TestCase import django.core.exceptions as django_exceptions from django.core.exceptions import ValidationError from .validators import validate_overage, validate_part_name from . import helpers -from .settings import STATIC_ROOT from mptt.exceptions import InvalidMove