mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Refactor template helpers for displaying uploaded images (#3377)
* Refactor template helpers for displaying uploaded images * Unit test for asset tag * Unit tests for 'uploaded_image' tag * Add simple tests for part_image and company_image functions * Unit test for barcode constructor * Unit tests for qrcode * Refactor the 'company_logo.png' to be a new template tag - Add unit tests * Adds a new field to the report asset model - Unique key which can be used to identify particular assets - e.g. company logo * Refactor logo image tags - Make use of existing CUSTOM_LOGO setting - Adds a "logo_image" template tag for reports * Remove previous migration - strategy no longer required
This commit is contained in:
parent
2bc8556993
commit
d2ab6b012d
@ -2,13 +2,16 @@
|
|||||||
|
|
||||||
import io
|
import io
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
import os.path
|
import os.path
|
||||||
import re
|
import re
|
||||||
from decimal import Decimal, InvalidOperation
|
from decimal import Decimal, InvalidOperation
|
||||||
from wsgiref.util import FileWrapper
|
from wsgiref.util import FileWrapper
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import Permission
|
from django.contrib.auth.models import Permission
|
||||||
from django.core.exceptions import FieldError, ValidationError
|
from django.core.exceptions import FieldError, ValidationError
|
||||||
|
from django.core.files.storage import default_storage
|
||||||
from django.http import StreamingHttpResponse
|
from django.http import StreamingHttpResponse
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
@ -25,6 +28,8 @@ from common.settings import currency_code_default
|
|||||||
from .api_tester import UserMixin
|
from .api_tester import UserMixin
|
||||||
from .settings import MEDIA_URL, STATIC_URL
|
from .settings import MEDIA_URL, STATIC_URL
|
||||||
|
|
||||||
|
logger = logging.getLogger('inventree')
|
||||||
|
|
||||||
|
|
||||||
def getSetting(key, backup_value=None):
|
def getSetting(key, backup_value=None):
|
||||||
"""Shortcut for reading a setting value from the database."""
|
"""Shortcut for reading a setting value from the database."""
|
||||||
@ -82,6 +87,15 @@ def construct_absolute_url(*arg):
|
|||||||
return url
|
return url
|
||||||
|
|
||||||
|
|
||||||
|
def TestIfImage(img):
|
||||||
|
"""Test if an image file is indeed an image."""
|
||||||
|
try:
|
||||||
|
Image.open(img).verify()
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def getBlankImage():
|
def getBlankImage():
|
||||||
"""Return the qualified path for the 'blank image' placeholder."""
|
"""Return the qualified path for the 'blank image' placeholder."""
|
||||||
return getStaticUrl("img/blank_image.png")
|
return getStaticUrl("img/blank_image.png")
|
||||||
@ -92,13 +106,23 @@ def getBlankThumbnail():
|
|||||||
return getStaticUrl("img/blank_image.thumbnail.png")
|
return getStaticUrl("img/blank_image.thumbnail.png")
|
||||||
|
|
||||||
|
|
||||||
def TestIfImage(img):
|
def getLogoImage(as_file=False):
|
||||||
"""Test if an image file is indeed an image."""
|
"""Return the InvenTree logo image, or a custom logo if available."""
|
||||||
try:
|
|
||||||
Image.open(img).verify()
|
"""Return the path to the logo-file."""
|
||||||
return True
|
if settings.CUSTOM_LOGO:
|
||||||
except Exception:
|
|
||||||
return False
|
if as_file:
|
||||||
|
return f"file://{default_storage.path(settings.CUSTOM_LOGO)}"
|
||||||
|
else:
|
||||||
|
return default_storage.url(settings.CUSTOM_LOGO)
|
||||||
|
|
||||||
|
else:
|
||||||
|
if as_file:
|
||||||
|
path = os.path.join(settings.STATIC_ROOT, 'img/inventree.png')
|
||||||
|
return f"file://{path}"
|
||||||
|
else:
|
||||||
|
return getStaticUrl('img/inventree.png')
|
||||||
|
|
||||||
|
|
||||||
def TestIfImageURL(url):
|
def TestIfImageURL(url):
|
||||||
|
@ -967,5 +967,5 @@ CUSTOM_LOGO = get_setting(
|
|||||||
|
|
||||||
# check that the logo-file exsists in media
|
# check that the logo-file exsists in media
|
||||||
if CUSTOM_LOGO and not default_storage.exists(CUSTOM_LOGO): # pragma: no cover
|
if CUSTOM_LOGO and not default_storage.exists(CUSTOM_LOGO): # pragma: no cover
|
||||||
|
logger.warning(f"The custom logo file '{CUSTOM_LOGO}' could not be found in the default media storage")
|
||||||
CUSTOM_LOGO = False
|
CUSTOM_LOGO = False
|
||||||
logger.warning("The custom logo file could not be found in the default media storage")
|
|
||||||
|
@ -240,6 +240,17 @@ class TestHelpers(TestCase):
|
|||||||
self.assertEqual(helpers.decimal2string(Decimal('1.2345000')), '1.2345')
|
self.assertEqual(helpers.decimal2string(Decimal('1.2345000')), '1.2345')
|
||||||
self.assertEqual(helpers.decimal2string('test'), 'test')
|
self.assertEqual(helpers.decimal2string('test'), 'test')
|
||||||
|
|
||||||
|
def test_logo_image(self):
|
||||||
|
"""Test for retrieving logo image"""
|
||||||
|
|
||||||
|
# By default, there is no custom logo provided
|
||||||
|
|
||||||
|
logo = helpers.getLogoImage()
|
||||||
|
self.assertEqual(logo, '/static/img/inventree.png')
|
||||||
|
|
||||||
|
logo = helpers.getLogoImage(as_file=True)
|
||||||
|
self.assertEqual(logo, f'file://{settings.STATIC_ROOT}/img/inventree.png')
|
||||||
|
|
||||||
|
|
||||||
class TestQuoteWrap(TestCase):
|
class TestQuoteWrap(TestCase):
|
||||||
"""Tests for string wrapping."""
|
"""Tests for string wrapping."""
|
||||||
|
@ -17,10 +17,10 @@ from stdimage.models import StdImageField
|
|||||||
import common.models
|
import common.models
|
||||||
import common.settings
|
import common.settings
|
||||||
import InvenTree.fields
|
import InvenTree.fields
|
||||||
|
import InvenTree.helpers
|
||||||
import InvenTree.validators
|
import InvenTree.validators
|
||||||
from common.settings import currency_code_default
|
from common.settings import currency_code_default
|
||||||
from InvenTree.fields import InvenTreeURLField
|
from InvenTree.fields import InvenTreeURLField
|
||||||
from InvenTree.helpers import getBlankImage, getBlankThumbnail, getMediaUrl
|
|
||||||
from InvenTree.models import InvenTreeAttachment
|
from InvenTree.models import InvenTreeAttachment
|
||||||
from InvenTree.status_codes import PurchaseOrderStatus
|
from InvenTree.status_codes import PurchaseOrderStatus
|
||||||
|
|
||||||
@ -177,16 +177,16 @@ class Company(models.Model):
|
|||||||
def get_image_url(self):
|
def get_image_url(self):
|
||||||
"""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 getMediaUrl(self.image.url)
|
return InvenTree.helpers.getMediaUrl(self.image.url)
|
||||||
else:
|
else:
|
||||||
return getBlankImage()
|
return InvenTree.helpers.getBlankImage()
|
||||||
|
|
||||||
def get_thumbnail_url(self):
|
def get_thumbnail_url(self):
|
||||||
"""Return the URL for the thumbnail image for this Company."""
|
"""Return the URL for the thumbnail image for this Company."""
|
||||||
if self.image:
|
if self.image:
|
||||||
return getMediaUrl(self.image.thumbnail.url)
|
return InvenTree.helpers.getMediaUrl(self.image.thumbnail.url)
|
||||||
else:
|
else:
|
||||||
return getBlankThumbnail()
|
return InvenTree.helpers.getBlankThumbnail()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def parts(self):
|
def parts(self):
|
||||||
|
@ -7,8 +7,7 @@ from datetime import date, datetime
|
|||||||
|
|
||||||
from django import template
|
from django import template
|
||||||
from django.conf import settings as djangosettings
|
from django.conf import settings as djangosettings
|
||||||
from django.core.files.storage import default_storage
|
from django.templatetags.static import StaticNode
|
||||||
from django.templatetags.static import StaticNode, static
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
@ -174,6 +173,16 @@ def inventree_title(*args, **kwargs):
|
|||||||
return version.inventreeInstanceTitle()
|
return version.inventreeInstanceTitle()
|
||||||
|
|
||||||
|
|
||||||
|
@register.simple_tag()
|
||||||
|
def inventree_logo(**kwargs):
|
||||||
|
"""Return the InvenTree logo, *or* a custom logo if the user has uploaded one.
|
||||||
|
|
||||||
|
Returns a path to an image file, which can be rendered in the web interface
|
||||||
|
"""
|
||||||
|
|
||||||
|
return InvenTree.helpers.getLogoImage(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag()
|
@register.simple_tag()
|
||||||
def inventree_base_url(*args, **kwargs):
|
def inventree_base_url(*args, **kwargs):
|
||||||
"""Return the INVENTREE_BASE_URL setting."""
|
"""Return the INVENTREE_BASE_URL setting."""
|
||||||
@ -473,14 +482,6 @@ def inventree_customize(reference, *args, **kwargs):
|
|||||||
return djangosettings.CUSTOMIZE.get(reference, '')
|
return djangosettings.CUSTOMIZE.get(reference, '')
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag()
|
|
||||||
def inventree_logo(*args, **kwargs):
|
|
||||||
"""Return the path to the logo-file."""
|
|
||||||
if settings.CUSTOM_LOGO:
|
|
||||||
return default_storage.url(settings.CUSTOM_LOGO)
|
|
||||||
return static('img/inventree.png')
|
|
||||||
|
|
||||||
|
|
||||||
class I18nStaticNode(StaticNode):
|
class I18nStaticNode(StaticNode):
|
||||||
"""Custom StaticNode.
|
"""Custom StaticNode.
|
||||||
|
|
||||||
|
@ -535,14 +535,23 @@ class ReportAsset(models.Model):
|
|||||||
and can be loaded in a template using the {% report_asset <filename> %} tag.
|
and can be loaded in a template using the {% report_asset <filename> %} tag.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# String keys used for uniquely indentifying particular assets
|
||||||
|
ASSET_COMPANY_LOGO = "COMPANY_LOGO"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""String representation of a ReportAsset instance"""
|
"""String representation of a ReportAsset instance"""
|
||||||
return os.path.basename(self.asset.name)
|
return os.path.basename(self.asset.name)
|
||||||
|
|
||||||
|
# Asset file
|
||||||
asset = models.FileField(
|
asset = models.FileField(
|
||||||
upload_to=rename_asset,
|
upload_to=rename_asset,
|
||||||
verbose_name=_('Asset'),
|
verbose_name=_('Asset'),
|
||||||
help_text=_("Report asset file"),
|
help_text=_("Report asset file"),
|
||||||
)
|
)
|
||||||
|
|
||||||
description = models.CharField(max_length=250, verbose_name=_('Description'), help_text=_("Asset file description"))
|
# Asset description (user facing string, not used internally)
|
||||||
|
description = models.CharField(
|
||||||
|
max_length=250,
|
||||||
|
verbose_name=_('Description'),
|
||||||
|
help_text=_("Asset file description")
|
||||||
|
)
|
||||||
|
@ -78,8 +78,7 @@ content: "v{{report_revision}} - {{ date.isoformat }}";
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block header_content %}
|
{% block header_content %}
|
||||||
<!-- TODO - Make the company logo asset generic -->
|
<img class='logo' src="{% logo_image %}" alt="logo" width="150">
|
||||||
<img class='logo' src="{% asset 'company_logo.png' %}" alt="logo" width="150">
|
|
||||||
|
|
||||||
<div class='header-right'>
|
<div class='header-right'>
|
||||||
<h3>
|
<h3>
|
||||||
|
@ -28,21 +28,29 @@ def image_data(img, fmt='PNG'):
|
|||||||
def qrcode(data, **kwargs):
|
def qrcode(data, **kwargs):
|
||||||
"""Return a byte-encoded QR code image.
|
"""Return a byte-encoded QR code image.
|
||||||
|
|
||||||
Optional kwargs
|
kwargs:
|
||||||
---------------
|
fill_color: Fill color (default = black)
|
||||||
|
back_color: Background color (default = white)
|
||||||
|
version: Default = 1
|
||||||
|
box_size: Default = 20
|
||||||
|
border: Default = 1
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
base64 encoded image data
|
||||||
|
|
||||||
fill_color: Fill color (default = black)
|
|
||||||
back_color: Background color (default = white)
|
|
||||||
"""
|
"""
|
||||||
# Construct "default" values
|
# Construct "default" values
|
||||||
params = dict(
|
params = dict(
|
||||||
box_size=20,
|
box_size=20,
|
||||||
border=1,
|
border=1,
|
||||||
|
version=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
fill_color = kwargs.pop('fill_color', 'black')
|
fill_color = kwargs.pop('fill_color', 'black')
|
||||||
back_color = kwargs.pop('back_color', 'white')
|
back_color = kwargs.pop('back_color', 'white')
|
||||||
|
|
||||||
|
format = kwargs.pop('format', 'PNG')
|
||||||
|
|
||||||
params.update(**kwargs)
|
params.update(**kwargs)
|
||||||
|
|
||||||
qr = python_qrcode.QRCode(**params)
|
qr = python_qrcode.QRCode(**params)
|
||||||
@ -50,9 +58,13 @@ def qrcode(data, **kwargs):
|
|||||||
qr.add_data(data, optimize=20)
|
qr.add_data(data, optimize=20)
|
||||||
qr.make(fit=True)
|
qr.make(fit=True)
|
||||||
|
|
||||||
qri = qr.make_image(fill_color=fill_color, back_color=back_color)
|
qri = qr.make_image(
|
||||||
|
fill_color=fill_color,
|
||||||
|
back_color=back_color
|
||||||
|
)
|
||||||
|
|
||||||
return image_data(qri)
|
# Render to byte-encoded image
|
||||||
|
return image_data(qri, fmt=format)
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag()
|
@register.simple_tag()
|
||||||
@ -60,6 +72,8 @@ def barcode(data, barcode_class='code128', **kwargs):
|
|||||||
"""Render a barcode."""
|
"""Render a barcode."""
|
||||||
constructor = python_barcode.get_barcode_class(barcode_class)
|
constructor = python_barcode.get_barcode_class(barcode_class)
|
||||||
|
|
||||||
|
format = kwargs.pop('format', 'PNG')
|
||||||
|
|
||||||
data = str(data).zfill(constructor.digits)
|
data = str(data).zfill(constructor.digits)
|
||||||
|
|
||||||
writer = python_barcode.writer.ImageWriter
|
writer = python_barcode.writer.ImageWriter
|
||||||
@ -68,5 +82,5 @@ def barcode(data, barcode_class='code128', **kwargs):
|
|||||||
|
|
||||||
image = barcode_image.render(writer_options=kwargs)
|
image = barcode_image.render(writer_options=kwargs)
|
||||||
|
|
||||||
# Render to byte-encoded PNG
|
# Render to byte-encoded image
|
||||||
return image_data(image)
|
return image_data(image, fmt=format)
|
||||||
|
@ -10,89 +10,133 @@ import InvenTree.helpers
|
|||||||
from common.models import InvenTreeSetting
|
from common.models import InvenTreeSetting
|
||||||
from company.models import Company
|
from company.models import Company
|
||||||
from part.models import Part
|
from part.models import Part
|
||||||
from stock.models import StockItem
|
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag()
|
@register.simple_tag()
|
||||||
def asset(filename):
|
def asset(filename):
|
||||||
"""Return fully-qualified path for an upload report asset file."""
|
"""Return fully-qualified path for an upload report asset file.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
filename: Asset filename (relative to the 'assets' media directory)
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
FileNotFoundError if file does not exist
|
||||||
|
"""
|
||||||
# If in debug mode, return URL to the image, not a local file
|
# If in debug mode, return URL to the image, not a local file
|
||||||
debug_mode = InvenTreeSetting.get_setting('REPORT_DEBUG_MODE')
|
debug_mode = InvenTreeSetting.get_setting('REPORT_DEBUG_MODE')
|
||||||
|
|
||||||
if debug_mode:
|
# Test if the file actually exists
|
||||||
path = os.path.join(settings.MEDIA_URL, 'report', 'assets', filename)
|
full_path = os.path.join(settings.MEDIA_ROOT, 'report', 'assets', filename)
|
||||||
else:
|
|
||||||
|
|
||||||
path = os.path.join(settings.MEDIA_ROOT, 'report', 'assets', filename)
|
if not os.path.exists(full_path) or not os.path.isfile(full_path):
|
||||||
path = os.path.abspath(path)
|
raise FileNotFoundError(f"Asset file '{filename}' does not exist")
|
||||||
|
|
||||||
|
if debug_mode:
|
||||||
|
return os.path.join(settings.MEDIA_URL, 'report', 'assets', filename)
|
||||||
|
else:
|
||||||
|
return f"file://{full_path}"
|
||||||
|
|
||||||
|
|
||||||
|
@register.simple_tag()
|
||||||
|
def uploaded_image(filename, replace_missing=True, replacement_file='blank_image.png'):
|
||||||
|
"""Return a fully-qualified path for an 'uploaded' image.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
filename: The filename of the image relative to the MEDIA_ROOT directory
|
||||||
|
replace_missing: Optionally return a placeholder image if the provided filename does not exist
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A fully qualified path to the image
|
||||||
|
"""
|
||||||
|
|
||||||
|
# If in debug mode, return URL to the image, not a local file
|
||||||
|
debug_mode = InvenTreeSetting.get_setting('REPORT_DEBUG_MODE')
|
||||||
|
|
||||||
|
# Check if the file exists
|
||||||
|
if not filename:
|
||||||
|
exists = False
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
full_path = os.path.join(settings.MEDIA_ROOT, filename)
|
||||||
|
full_path = os.path.abspath(full_path)
|
||||||
|
exists = os.path.exists(full_path) and os.path.isfile(full_path)
|
||||||
|
except Exception:
|
||||||
|
exists = False
|
||||||
|
|
||||||
|
if not exists and not replace_missing:
|
||||||
|
raise FileNotFoundError(f"Image file '{filename}' not found")
|
||||||
|
|
||||||
|
if debug_mode:
|
||||||
|
# In debug mode, return a web path
|
||||||
|
if exists:
|
||||||
|
return os.path.join(settings.MEDIA_URL, filename)
|
||||||
|
else:
|
||||||
|
return os.path.join(settings.STATIC_URL, 'img', replacement_file)
|
||||||
|
else:
|
||||||
|
# Return file path
|
||||||
|
if exists:
|
||||||
|
path = os.path.join(settings.MEDIA_ROOT, filename)
|
||||||
|
path = os.path.abspath(path)
|
||||||
|
else:
|
||||||
|
path = os.path.join(settings.STATIC_ROOT, 'img', replacement_file)
|
||||||
|
path = os.path.abspath(path)
|
||||||
|
|
||||||
return f"file://{path}"
|
return f"file://{path}"
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag()
|
@register.simple_tag()
|
||||||
def part_image(part):
|
def part_image(part):
|
||||||
"""Return a fully-qualified path for a part image."""
|
"""Return a fully-qualified path for a part image.
|
||||||
# If in debug mode, return URL to the image, not a local file
|
|
||||||
debug_mode = InvenTreeSetting.get_setting('REPORT_DEBUG_MODE')
|
Arguments:
|
||||||
|
part: a Part model instance
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
TypeError if provided part is not a Part instance
|
||||||
|
"""
|
||||||
|
|
||||||
if type(part) is Part:
|
if type(part) is Part:
|
||||||
img = part.image.name
|
img = part.image.name
|
||||||
|
|
||||||
elif type(part) is StockItem:
|
|
||||||
img = part.part.image.name
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
img = ''
|
raise TypeError("part_image tag requires a Part instance")
|
||||||
|
|
||||||
if debug_mode:
|
return uploaded_image(img)
|
||||||
if img:
|
|
||||||
return os.path.join(settings.MEDIA_URL, img)
|
|
||||||
else:
|
|
||||||
return os.path.join(settings.STATIC_URL, 'img', 'blank_image.png')
|
|
||||||
|
|
||||||
else:
|
|
||||||
path = os.path.join(settings.MEDIA_ROOT, img)
|
|
||||||
path = os.path.abspath(path)
|
|
||||||
|
|
||||||
if not os.path.exists(path) or not os.path.isfile(path):
|
|
||||||
# Image does not exist
|
|
||||||
# Return the 'blank' image
|
|
||||||
path = os.path.join(settings.STATIC_ROOT, 'img', 'blank_image.png')
|
|
||||||
path = os.path.abspath(path)
|
|
||||||
|
|
||||||
return f"file://{path}"
|
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag()
|
@register.simple_tag()
|
||||||
def company_image(company):
|
def company_image(company):
|
||||||
"""Return a fully-qualified path for a company image."""
|
"""Return a fully-qualified path for a company image.
|
||||||
# If in debug mode, return the URL to the image, not a local file
|
|
||||||
debug_mode = InvenTreeSetting.get_setting('REPORT_DEBUG_MODE')
|
Arguments:
|
||||||
|
company: a Company model instance
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
TypeError if provided company is not a Company instance
|
||||||
|
"""
|
||||||
|
|
||||||
if type(company) is Company:
|
if type(company) is Company:
|
||||||
img = company.image.name
|
img = company.image.name
|
||||||
else:
|
else:
|
||||||
img = ''
|
raise TypeError("company_image tag requires a Company instance")
|
||||||
|
|
||||||
if debug_mode:
|
return uploaded_image(img)
|
||||||
if img:
|
|
||||||
return os.path.join(settings.MEDIA_URL, img)
|
|
||||||
else:
|
|
||||||
return os.path.join(settings.STATIC_URL, 'img', 'blank_image.png')
|
|
||||||
|
|
||||||
else:
|
|
||||||
path = os.path.join(settings.MEDIA_ROOT, img)
|
|
||||||
path = os.path.abspath(path)
|
|
||||||
|
|
||||||
if not os.path.exists(path) or not os.path.isfile(path):
|
@register.simple_tag()
|
||||||
# Image does not exist
|
def logo_image():
|
||||||
# Return the 'blank' image
|
"""Return a fully-qualified path for the logo image.
|
||||||
path = os.path.join(settings.STATIC_ROOT, 'img', 'blank_image.png')
|
|
||||||
path = os.path.abspath(path)
|
|
||||||
|
|
||||||
return f"file://{path}"
|
- If a custom logo has been provided, return a path to that logo
|
||||||
|
- Otherwise, return a path to the default InvenTree logo
|
||||||
|
"""
|
||||||
|
|
||||||
|
# If in debug mode, return URL to the image, not a local file
|
||||||
|
debug_mode = InvenTreeSetting.get_setting('REPORT_DEBUG_MODE')
|
||||||
|
|
||||||
|
return InvenTree.helpers.getLogoImage(as_file=not debug_mode)
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag()
|
@register.simple_tag()
|
||||||
|
@ -6,15 +6,142 @@ import shutil
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.http.response import StreamingHttpResponse
|
from django.http.response import StreamingHttpResponse
|
||||||
|
from django.test import TestCase
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
import report.models as report_models
|
import report.models as report_models
|
||||||
from build.models import Build
|
from build.models import Build
|
||||||
from common.models import InvenTreeSetting, InvenTreeUserSetting
|
from common.models import InvenTreeSetting, InvenTreeUserSetting
|
||||||
from InvenTree.api_tester import InvenTreeAPITestCase
|
from InvenTree.api_tester import InvenTreeAPITestCase
|
||||||
|
from report.templatetags import barcode as barcode_tags
|
||||||
|
from report.templatetags import report as report_tags
|
||||||
from stock.models import StockItem, StockItemAttachment
|
from stock.models import StockItem, StockItemAttachment
|
||||||
|
|
||||||
|
|
||||||
|
class ReportTagTest(TestCase):
|
||||||
|
"""Unit tests for the report template tags"""
|
||||||
|
|
||||||
|
def debug_mode(self, value: bool):
|
||||||
|
"""Enable or disable debug mode for reports"""
|
||||||
|
InvenTreeSetting.set_setting('REPORT_DEBUG_MODE', value, change_user=None)
|
||||||
|
|
||||||
|
def test_asset(self):
|
||||||
|
"""Tests for asset files"""
|
||||||
|
|
||||||
|
# Test that an error is raised if the file does not exist
|
||||||
|
for b in [True, False]:
|
||||||
|
self.debug_mode(b)
|
||||||
|
|
||||||
|
with self.assertRaises(FileNotFoundError):
|
||||||
|
report_tags.asset("bad_file.txt")
|
||||||
|
|
||||||
|
# Create an asset file
|
||||||
|
asset_dir = os.path.join(settings.MEDIA_ROOT, 'report', 'assets')
|
||||||
|
os.makedirs(asset_dir, exist_ok=True)
|
||||||
|
asset_path = os.path.join(asset_dir, 'test.txt')
|
||||||
|
|
||||||
|
with open(asset_path, 'w') as f:
|
||||||
|
f.write("dummy data")
|
||||||
|
|
||||||
|
self.debug_mode(True)
|
||||||
|
asset = report_tags.asset('test.txt')
|
||||||
|
self.assertEqual(asset, '/media/report/assets/test.txt')
|
||||||
|
|
||||||
|
self.debug_mode(False)
|
||||||
|
asset = report_tags.asset('test.txt')
|
||||||
|
self.assertEqual(asset, f'file://{asset_dir}/test.txt')
|
||||||
|
|
||||||
|
def test_uploaded_image(self):
|
||||||
|
"""Tests for retrieving uploaded images"""
|
||||||
|
|
||||||
|
# Test for a missing image
|
||||||
|
for b in [True, False]:
|
||||||
|
self.debug_mode(b)
|
||||||
|
|
||||||
|
with self.assertRaises(FileNotFoundError):
|
||||||
|
report_tags.uploaded_image('/part/something/test.png', replace_missing=False)
|
||||||
|
|
||||||
|
img = report_tags.uploaded_image('/part/something/other.png')
|
||||||
|
self.assertTrue('blank_image.png' in img)
|
||||||
|
|
||||||
|
# Create a dummy image
|
||||||
|
img_path = 'part/images/'
|
||||||
|
img_path = os.path.join(settings.MEDIA_ROOT, img_path)
|
||||||
|
img_file = os.path.join(img_path, 'test.jpg')
|
||||||
|
|
||||||
|
os.makedirs(img_path, exist_ok=True)
|
||||||
|
|
||||||
|
with open(img_file, 'w') as f:
|
||||||
|
f.write("dummy data")
|
||||||
|
|
||||||
|
# Test in debug mode
|
||||||
|
self.debug_mode(True)
|
||||||
|
img = report_tags.uploaded_image('part/images/test.jpg')
|
||||||
|
self.assertEqual(img, '/media/part/images/test.jpg')
|
||||||
|
|
||||||
|
self.debug_mode(False)
|
||||||
|
img = report_tags.uploaded_image('part/images/test.jpg')
|
||||||
|
self.assertEqual(img, f'file://{img_path}test.jpg')
|
||||||
|
|
||||||
|
def test_part_image(self):
|
||||||
|
"""Unit tests for the 'part_image' tag"""
|
||||||
|
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
report_tags.part_image(None)
|
||||||
|
|
||||||
|
def test_company_image(self):
|
||||||
|
"""Unit tests for the 'company_image' tag"""
|
||||||
|
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
report_tags.company_image(None)
|
||||||
|
|
||||||
|
def test_logo_image(self):
|
||||||
|
"""Unit tests for the 'logo_image' tag"""
|
||||||
|
|
||||||
|
# By default, should return the core InvenTree logo
|
||||||
|
for b in [True, False]:
|
||||||
|
self.debug_mode(b)
|
||||||
|
logo = report_tags.logo_image()
|
||||||
|
self.assertIn('inventree.png', logo)
|
||||||
|
|
||||||
|
|
||||||
|
class BarcodeTagTest(TestCase):
|
||||||
|
"""Unit tests for the barcode template tags"""
|
||||||
|
|
||||||
|
def test_barcode(self):
|
||||||
|
"""Test the barcode generation tag"""
|
||||||
|
|
||||||
|
barcode = barcode_tags.barcode("12345")
|
||||||
|
|
||||||
|
self.assertTrue(type(barcode) == str)
|
||||||
|
self.assertTrue(barcode.startswith('data:image/png;'))
|
||||||
|
|
||||||
|
# Try with a different format
|
||||||
|
barcode = barcode_tags.barcode('99999', format='BMP')
|
||||||
|
self.assertTrue(type(barcode) == str)
|
||||||
|
self.assertTrue(barcode.startswith('data:image/bmp;'))
|
||||||
|
|
||||||
|
def test_qrcode(self):
|
||||||
|
"""Test the qrcode generation tag"""
|
||||||
|
|
||||||
|
# Test with default settings
|
||||||
|
qrcode = barcode_tags.qrcode("hello world")
|
||||||
|
self.assertTrue(type(qrcode) == str)
|
||||||
|
self.assertTrue(qrcode.startswith('data:image/png;'))
|
||||||
|
self.assertEqual(len(qrcode), 700)
|
||||||
|
|
||||||
|
# Generate a much larger qrcode
|
||||||
|
qrcode = barcode_tags.qrcode(
|
||||||
|
"hello_world",
|
||||||
|
version=2,
|
||||||
|
box_size=50,
|
||||||
|
format='BMP',
|
||||||
|
)
|
||||||
|
self.assertTrue(type(qrcode) == str)
|
||||||
|
self.assertTrue(qrcode.startswith('data:image/bmp;'))
|
||||||
|
self.assertEqual(len(qrcode), 309720)
|
||||||
|
|
||||||
|
|
||||||
class ReportTest(InvenTreeAPITestCase):
|
class ReportTest(InvenTreeAPITestCase):
|
||||||
"""Base class for unit testing reporting models"""
|
"""Base class for unit testing reporting models"""
|
||||||
fixtures = [
|
fixtures = [
|
||||||
|
Loading…
Reference in New Issue
Block a user