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/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/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/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 %}