mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-11-03 22:55:43 +00:00 
			
		
		
		
	Merge pull request #703 from SchrodingersGat/fixes
Check for missing part thumbnails
This commit is contained in:
		@@ -14,6 +14,41 @@ from django.core.exceptions import ValidationError
 | 
				
			|||||||
from django.utils.translation import ugettext as _
 | 
					from django.utils.translation import ugettext as _
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .version import inventreeVersion, inventreeInstanceName
 | 
					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):
 | 
					def TestIfImage(img):
 | 
				
			||||||
@@ -66,7 +101,7 @@ def isNull(text):
 | 
				
			|||||||
        True if the text looks like a null value
 | 
					        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):
 | 
					def decimal2string(d):
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from django.test import TestCase
 | 
					from django.test import TestCase
 | 
				
			||||||
import django.core.exceptions as django_exceptions
 | 
					import django.core.exceptions as django_exceptions
 | 
				
			||||||
from django.core.exceptions import ValidationError
 | 
					from django.core.exceptions import ValidationError
 | 
				
			||||||
@@ -7,6 +8,8 @@ from . import helpers
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from mptt.exceptions import InvalidMove
 | 
					from mptt.exceptions import InvalidMove
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from decimal import Decimal
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from stock.models import StockLocation
 | 
					from stock.models import StockLocation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -72,6 +75,29 @@ class TestHelpers(TestCase):
 | 
				
			|||||||
            self.assertFalse(helpers.str2bool(s))
 | 
					            self.assertFalse(helpers.str2bool(s))
 | 
				
			||||||
            self.assertFalse(helpers.str2bool(s, test=False))
 | 
					            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):
 | 
					class TestQuoteWrap(TestCase):
 | 
				
			||||||
    """ Tests for string wrapping """
 | 
					    """ Tests for string wrapping """
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,36 @@
 | 
				
			|||||||
from __future__ import unicode_literals
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from django.apps import AppConfig
 | 
					from django.apps import AppConfig
 | 
				
			||||||
 | 
					from django.db.utils import OperationalError, ProgrammingError
 | 
				
			||||||
 | 
					from django.conf import settings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class CompanyConfig(AppConfig):
 | 
					class CompanyConfig(AppConfig):
 | 
				
			||||||
    name = 'company'
 | 
					    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")
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										20
									
								
								InvenTree/company/migrations/0014_auto_20200407_0116.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								InvenTree/company/migrations/0014_auto_20200407_0116.py
									
									
									
									
									
										Normal file
									
								
							@@ -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),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
@@ -17,10 +17,12 @@ from django.db.models import Sum
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from django.apps import apps
 | 
					from django.apps import apps
 | 
				
			||||||
from django.urls import reverse
 | 
					from django.urls import reverse
 | 
				
			||||||
from django.conf import settings
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
from markdownx.models import MarkdownxField
 | 
					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.fields import InvenTreeURLField, RoundingDecimalField
 | 
				
			||||||
from InvenTree.status_codes import OrderStatus
 | 
					from InvenTree.status_codes import OrderStatus
 | 
				
			||||||
from common.models import Currency
 | 
					from common.models import Currency
 | 
				
			||||||
@@ -90,7 +92,13 @@ class Company(models.Model):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    link = InvenTreeURLField(blank=True, help_text=_('Link to external company information'))
 | 
					    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)
 | 
					    notes = MarkdownxField(blank=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -110,10 +118,18 @@ class Company(models.Model):
 | 
				
			|||||||
        """ Return the URL of the image for this company """
 | 
					        """ Return the URL of the image for this company """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self.image:
 | 
					        if self.image:
 | 
				
			||||||
            return os.path.join(settings.MEDIA_URL, str(self.image.url))
 | 
					            return getMediaUrl(self.image.url)
 | 
				
			||||||
        else:
 | 
					        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
 | 
					    @property
 | 
				
			||||||
    def part_count(self):
 | 
					    def part_count(self):
 | 
				
			||||||
        """ The number of parts supplied by this company """
 | 
					        """ The number of parts supplied by this company """
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -32,7 +32,7 @@ class CompanySerializer(InvenTreeModelSerializer):
 | 
				
			|||||||
    url = serializers.CharField(source='get_absolute_url', read_only=True)
 | 
					    url = serializers.CharField(source='get_absolute_url', read_only=True)
 | 
				
			||||||
    part_count = serializers.CharField(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:
 | 
					    class Meta:
 | 
				
			||||||
        model = Company
 | 
					        model = Company
 | 
				
			||||||
@@ -64,7 +64,7 @@ class SupplierPartSerializer(InvenTreeModelSerializer):
 | 
				
			|||||||
    part_detail = PartBriefSerializer(source='part', many=False, read_only=True)
 | 
					    part_detail = PartBriefSerializer(source='part', many=False, read_only=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    supplier_name = serializers.CharField(source='supplier.name', 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)
 | 
					    pricing = serializers.CharField(source='unit_pricing', read_only=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -46,7 +46,7 @@ InvenTree | {% trans "Company" %} - {{ company.name }}
 | 
				
			|||||||
            <col width='25'>
 | 
					            <col width='25'>
 | 
				
			||||||
        {% if company.website %}
 | 
					        {% if company.website %}
 | 
				
			||||||
        <tr>
 | 
					        <tr>
 | 
				
			||||||
            <td><span class='fas fa-link'></span></td>
 | 
					            <td><span class='fas fa-globe'></span></td>
 | 
				
			||||||
            <td>{% trans "Website" %}</td>
 | 
					            <td>{% trans "Website" %}</td>
 | 
				
			||||||
            <td><a href="{{ company.website }}">{{ company.website }}</a></td>
 | 
					            <td><a href="{{ company.website }}">{{ company.website }}</a></td>
 | 
				
			||||||
        </tr>
 | 
					        </tr>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,35 @@
 | 
				
			|||||||
from __future__ import unicode_literals
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.db.utils import OperationalError, ProgrammingError
 | 
				
			||||||
from django.apps import AppConfig
 | 
					from django.apps import AppConfig
 | 
				
			||||||
 | 
					from django.conf import settings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PartConfig(AppConfig):
 | 
					class PartConfig(AppConfig):
 | 
				
			||||||
    name = 'part'
 | 
					    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")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,7 +10,6 @@ import os
 | 
				
			|||||||
from django.utils.translation import gettext_lazy as _
 | 
					from django.utils.translation import gettext_lazy as _
 | 
				
			||||||
from django.core.exceptions import ValidationError
 | 
					from django.core.exceptions import ValidationError
 | 
				
			||||||
from django.urls import reverse
 | 
					from django.urls import reverse
 | 
				
			||||||
from django.conf import settings
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
from django.db import models, transaction
 | 
					from django.db import models, transaction
 | 
				
			||||||
from django.db.models import Sum
 | 
					from django.db.models import Sum
 | 
				
			||||||
@@ -300,9 +299,9 @@ class Part(models.Model):
 | 
				
			|||||||
        """ Return the URL of the image for this part """
 | 
					        """ Return the URL of the image for this part """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self.image:
 | 
					        if self.image:
 | 
				
			||||||
            return os.path.join(settings.MEDIA_URL, str(self.image.url))
 | 
					            return helpers.getMediaUrl(self.image.url)
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            return os.path.join(settings.STATIC_URL, 'img/blank_image.png')
 | 
					            return helpers.getBlankImage()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_thumbnail_url(self):
 | 
					    def get_thumbnail_url(self):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
@@ -310,9 +309,9 @@ class Part(models.Model):
 | 
				
			|||||||
        """
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self.image:
 | 
					        if self.image:
 | 
				
			||||||
            return os.path.join(settings.MEDIA_URL, str(self.image.thumbnail.url))
 | 
					            return helpers.getMediaUrl(self.image.thumbnail.url)
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            return os.path.join(settings.STATIC_URL, 'img/blank_image.thumbnail.png')
 | 
					            return helpers.getBlankThumbnail()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def validate_unique(self, exclude=None):
 | 
					    def validate_unique(self, exclude=None):
 | 
				
			||||||
        """ Validate that a part is 'unique'.
 | 
					        """ Validate that a part is 'unique'.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,7 +11,7 @@
 | 
				
			|||||||
        {% if item.serialized %}
 | 
					        {% if item.serialized %}
 | 
				
			||||||
        <p><i>{{ item.part.full_name}} # {{ item.serial }}</i></p>
 | 
					        <p><i>{{ item.part.full_name}} # {{ item.serial }}</i></p>
 | 
				
			||||||
        {% else %}
 | 
					        {% else %}
 | 
				
			||||||
        <p><i>{{ item.quantity }} × {{ item.part.full_name }}</i></p>
 | 
					        <p><i>{% decimal item.quantity %} × {{ item.part.full_name }}</i></p>
 | 
				
			||||||
        {% endif %}
 | 
					        {% endif %}
 | 
				
			||||||
        <p>
 | 
					        <p>
 | 
				
			||||||
            <div class='btn-group'>
 | 
					            <div class='btn-group'>
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user