mirror of
https://github.com/inventree/InvenTree.git
synced 2025-05-01 13:06:45 +00:00
Report image rendering fix (#5907)
* Allow different image variations to be rendered in when using a part image in a report * Use preview image in default test report * Fix api_version - Missed in https://github.com/inventree/InvenTree/pull/5906 * Update docstring * Add similar functionality for company_image tag * Update report documentation * base-64 encode images for rendering in reports - Allows image manipulation operations to be performed on the images - Avoids any file pathing issues * Update docs * Fix unit tests * More unit test fixes * More unit test * Handle missing file * Instrument unit test - Trying to determine what is going on here * Fix for image resize * Translate error messages * Update default report templates - Specify image size
This commit is contained in:
parent
3e063750b5
commit
2fcd6ae0b9
@ -2,10 +2,15 @@
|
|||||||
|
|
||||||
|
|
||||||
# InvenTree API version
|
# InvenTree API version
|
||||||
INVENTREE_API_VERSION = 150
|
INVENTREE_API_VERSION = 151
|
||||||
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
||||||
|
|
||||||
INVENTREE_API_TEXT = """
|
INVENTREE_API_TEXT = """
|
||||||
|
|
||||||
|
v151 -> 2023-11-13 : https://github.com/inventree/InvenTree/pull/5906
|
||||||
|
- Allow user list API to be filtered by user active status
|
||||||
|
- Allow owner list API to be filtered by user active status
|
||||||
|
|
||||||
v150 -> 2023-11-07: https://github.com/inventree/InvenTree/pull/5875
|
v150 -> 2023-11-07: https://github.com/inventree/InvenTree/pull/5875
|
||||||
- Extended user API endpoints to enable ordering
|
- Extended user API endpoints to enable ordering
|
||||||
- Extended user API endpoints to enable user role changes
|
- Extended user API endpoints to enable user role changes
|
||||||
|
@ -106,7 +106,7 @@ class LabelTest(InvenTreeAPITestCase):
|
|||||||
<!-- Test InvenTree URL -->
|
<!-- Test InvenTree URL -->
|
||||||
url: {{ qr_url|safe }}
|
url: {{ qr_url|safe }}
|
||||||
<!-- Test image URL generation -->
|
<!-- Test image URL generation -->
|
||||||
image: {% part_image part %}
|
image: {% part_image part width=128 %}
|
||||||
<!-- Test InvenTree logo -->
|
<!-- Test InvenTree logo -->
|
||||||
logo: {% logo_image %}
|
logo: {% logo_image %}
|
||||||
</html>
|
</html>
|
||||||
@ -154,8 +154,9 @@ class LabelTest(InvenTreeAPITestCase):
|
|||||||
self.assertIn(f"part: {part_pk} - {part_name}", content)
|
self.assertIn(f"part: {part_pk} - {part_name}", content)
|
||||||
self.assertIn(f'data: {{"part": {part_pk}}}', content)
|
self.assertIn(f'data: {{"part": {part_pk}}}', content)
|
||||||
self.assertIn(f'http://testserver/part/{part_pk}/', content)
|
self.assertIn(f'http://testserver/part/{part_pk}/', content)
|
||||||
self.assertIn("img/blank_image.png", content)
|
|
||||||
self.assertIn("img/inventree.png", content)
|
# Check that a encoded image has been generated
|
||||||
|
self.assertIn('data:image/png;charset=utf-8;base64,', content)
|
||||||
|
|
||||||
def test_metadata(self):
|
def test_metadata(self):
|
||||||
"""Unit tests for the metadata field."""
|
"""Unit tests for the metadata field."""
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
"""Helper functions for report generation."""
|
"""Helper functions for report generation."""
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import io
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
@ -48,3 +50,24 @@ def report_page_size_default():
|
|||||||
page_size = 'A4'
|
page_size = 'A4'
|
||||||
|
|
||||||
return page_size
|
return page_size
|
||||||
|
|
||||||
|
|
||||||
|
def encode_image_base64(image, format: str = 'PNG'):
|
||||||
|
"""Return a base-64 encoded image which can be rendered in an <img> tag
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
image {Image} -- Image object
|
||||||
|
format {str} -- Image format (e.g. 'PNG')
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str -- Base64 encoded image data e.g. 'data:image/png;base64,xxxxxxxxx'
|
||||||
|
"""
|
||||||
|
|
||||||
|
fmt = format.lower()
|
||||||
|
|
||||||
|
buffered = io.BytesIO()
|
||||||
|
image.save(buffered, fmt)
|
||||||
|
|
||||||
|
img_str = base64.b64encode(buffered.getvalue())
|
||||||
|
|
||||||
|
return f"data:image/{fmt};charset=utf-8;base64," + img_str.decode()
|
||||||
|
@ -123,7 +123,7 @@ table td.expand {
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class='part-logo'>
|
<div class='part-logo'>
|
||||||
<img src='{% part_image part %}' alt='{% trans "Image" %}' class='part-logo'>
|
<img src='{% part_image part height=480 %}' alt='{% trans "Image" %}' class='part-logo'>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -145,7 +145,7 @@ table td.expand {
|
|||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<div class='thumb-container'>
|
<div class='thumb-container'>
|
||||||
<img src='{% part_image line.sub_part %}' alt='{% trans "Image" %}' class='part-thumb'>
|
<img src='{% part_image line.sub_part height=240 %}' alt='{% trans "Image" %}' class='part-thumb'>
|
||||||
</div>
|
</div>
|
||||||
<div class='part-text'>
|
<div class='part-text'>
|
||||||
{{ line.sub_part.full_name }}
|
{{ line.sub_part.full_name }}
|
||||||
|
@ -95,7 +95,7 @@ content: "v{{ report_revision }} - {{ date.isoformat }}";
|
|||||||
|
|
||||||
<div class='details'>
|
<div class='details'>
|
||||||
<div class='details-image'>
|
<div class='details-image'>
|
||||||
<img class='part-image' alt="{% trans 'Part image' %}" src="{% part_image part %}">
|
<img class='part-image' alt="{% trans 'Part image' %}" src="{% part_image part height=480 %}">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class='details-container'>
|
<div class='details-container'>
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<div class='thumb-container'>
|
<div class='thumb-container'>
|
||||||
<img src='{% part_image line.part.part %}' class='part-thumb' alt="{% trans 'Part image' %}">
|
<img src='{% part_image line.part.part height=240 %}' class='part-thumb' alt="{% trans 'Part image' %}">
|
||||||
</div>
|
</div>
|
||||||
<div class='part-text'>
|
<div class='part-text'>
|
||||||
{{ line.part.part.full_name }}
|
{{ line.part.part.full_name }}
|
||||||
|
@ -32,7 +32,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<div class='thumb-container'>
|
<div class='thumb-container'>
|
||||||
<img src='{% part_image line.item.part %}' alt='{% trans "Image" %}' class='part-thumb'>
|
<img src='{% part_image line.item.part height=240 %}' alt='{% trans "Image" %}' class='part-thumb'>
|
||||||
</div>
|
</div>
|
||||||
<div class='part-text'>
|
<div class='part-text'>
|
||||||
{{ line.item.part.full_name }}
|
{{ line.item.part.full_name }}
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<div class='thumb-container'>
|
<div class='thumb-container'>
|
||||||
<img src='{% part_image line.part %}' alt='{% trans "Part image" %}' class='part-thumb'>
|
<img src='{% part_image line.part height=240 %}' alt='{% trans "Part image" %}' class='part-thumb'>
|
||||||
</div>
|
</div>
|
||||||
<div class='part-text'>
|
<div class='part-text'>
|
||||||
{{ line.part.full_name }}
|
{{ line.part.full_name }}
|
||||||
|
@ -81,7 +81,7 @@ content: "{% trans 'Stock Item Test Report' %}";
|
|||||||
<p><em>Stock Item ID: {{ stock_item.pk }}</em></p>
|
<p><em>Stock Item ID: {{ stock_item.pk }}</em></p>
|
||||||
</div>
|
</div>
|
||||||
<div class='img-right'>
|
<div class='img-right'>
|
||||||
<img class='part-img' alt='{% trans "Part image" %}' src="{% part_image part %}">
|
<img class='part-img' alt='{% trans "Part image" %}' src="{% part_image part height=480 %}">
|
||||||
<hr>
|
<hr>
|
||||||
<h4>
|
<h4>
|
||||||
{% if stock_item.is_serialized %}
|
{% if stock_item.is_serialized %}
|
||||||
@ -160,7 +160,7 @@ content: "{% trans 'Stock Item Test Report' %}";
|
|||||||
{% for sub_item in installed_items %}
|
{% for sub_item in installed_items %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<img src='{% part_image sub_item.part %}' class='part-img' alt='{% trans "Part image" %}' style='max-width: 24px; max-height: 24px;'>
|
<img src='{% part_image sub_item.part height=240 %}' class='part-img' alt='{% trans "Part image" %}' style='max-width: 24px; max-height: 24px;'>
|
||||||
{{ sub_item.part.full_name }}
|
{{ sub_item.part.full_name }}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
"""Template tags for rendering various barcodes."""
|
"""Template tags for rendering various barcodes."""
|
||||||
|
|
||||||
import base64
|
|
||||||
from io import BytesIO
|
|
||||||
|
|
||||||
from django import template
|
from django import template
|
||||||
|
|
||||||
import barcode as python_barcode
|
import barcode as python_barcode
|
||||||
import qrcode as python_qrcode
|
import qrcode as python_qrcode
|
||||||
|
|
||||||
|
import report.helpers
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
@ -16,12 +15,8 @@ def image_data(img, fmt='PNG'):
|
|||||||
|
|
||||||
Returns a string ``data:image/FMT;base64,xxxxxxxxx`` which can be rendered to an <img> tag
|
Returns a string ``data:image/FMT;base64,xxxxxxxxx`` which can be rendered to an <img> tag
|
||||||
"""
|
"""
|
||||||
buffered = BytesIO()
|
|
||||||
img.save(buffered, format=fmt)
|
|
||||||
|
|
||||||
img_str = base64.b64encode(buffered.getvalue())
|
return report.helpers.encode_image_base64(img, fmt)
|
||||||
|
|
||||||
return f"data:image/{fmt.lower()};charset=utf-8;base64," + img_str.decode()
|
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag()
|
@register.simple_tag()
|
||||||
|
@ -7,9 +7,13 @@ import os
|
|||||||
from django import template
|
from django import template
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.safestring import SafeString, mark_safe
|
from django.utils.safestring import SafeString, mark_safe
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
import InvenTree.helpers
|
import InvenTree.helpers
|
||||||
import InvenTree.helpers_model
|
import InvenTree.helpers_model
|
||||||
|
import report.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
|
||||||
@ -88,7 +92,7 @@ def asset(filename):
|
|||||||
full_path = settings.MEDIA_ROOT.joinpath('report', 'assets', filename).resolve()
|
full_path = settings.MEDIA_ROOT.joinpath('report', 'assets', filename).resolve()
|
||||||
|
|
||||||
if not full_path.exists() or not full_path.is_file():
|
if not full_path.exists() or not full_path.is_file():
|
||||||
raise FileNotFoundError(f"Asset file '{filename}' does not exist")
|
raise FileNotFoundError(_("Asset file does not exist") + f": '{filename}'")
|
||||||
|
|
||||||
if debug_mode:
|
if debug_mode:
|
||||||
return os.path.join(settings.MEDIA_URL, 'report', 'assets', filename)
|
return os.path.join(settings.MEDIA_URL, 'report', 'assets', filename)
|
||||||
@ -96,7 +100,7 @@ def asset(filename):
|
|||||||
|
|
||||||
|
|
||||||
@register.simple_tag()
|
@register.simple_tag()
|
||||||
def uploaded_image(filename, replace_missing=True, replacement_file='blank_image.png', validate=True):
|
def uploaded_image(filename, replace_missing=True, replacement_file='blank_image.png', validate=True, **kwargs):
|
||||||
"""Return a fully-qualified path for an 'uploaded' image.
|
"""Return a fully-qualified path for an 'uploaded' image.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
@ -104,8 +108,16 @@ def uploaded_image(filename, replace_missing=True, replacement_file='blank_image
|
|||||||
replace_missing: Optionally return a placeholder image if the provided filename does not exist
|
replace_missing: Optionally return a placeholder image if the provided filename does not exist
|
||||||
validate: Optionally validate that the file is a valid image file (default = True)
|
validate: Optionally validate that the file is a valid image file (default = True)
|
||||||
|
|
||||||
|
kwargs:
|
||||||
|
width: Optional width of the image (default = None)
|
||||||
|
height: Optional height of the image (default = None)
|
||||||
|
rotate: Optional rotation to apply to the image
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A fully qualified path to the image
|
A fully qualified path to the image
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
FileNotFoundError if the file does not exist
|
||||||
"""
|
"""
|
||||||
if type(filename) is SafeString:
|
if type(filename) is SafeString:
|
||||||
# Prepend an empty string to enforce 'stringiness'
|
# Prepend an empty string to enforce 'stringiness'
|
||||||
@ -129,21 +141,51 @@ def uploaded_image(filename, replace_missing=True, replacement_file='blank_image
|
|||||||
exists = False
|
exists = False
|
||||||
|
|
||||||
if not exists and not replace_missing:
|
if not exists and not replace_missing:
|
||||||
raise FileNotFoundError(f"Image file '{filename}' not found")
|
raise FileNotFoundError(_("Image file not found") + f": '{filename}'")
|
||||||
|
|
||||||
if debug_mode:
|
if debug_mode:
|
||||||
# In debug mode, return a web path
|
# In debug mode, return a web path (rather than an encoded image blob)
|
||||||
if exists:
|
if exists:
|
||||||
return os.path.join(settings.MEDIA_URL, filename)
|
return os.path.join(settings.MEDIA_URL, filename)
|
||||||
return os.path.join(settings.STATIC_URL, 'img', replacement_file)
|
return os.path.join(settings.STATIC_URL, 'img', replacement_file)
|
||||||
else:
|
|
||||||
# Return file path
|
|
||||||
if exists:
|
|
||||||
path = settings.MEDIA_ROOT.joinpath(filename).resolve()
|
|
||||||
else:
|
|
||||||
path = settings.STATIC_ROOT.joinpath('img', replacement_file).resolve()
|
|
||||||
|
|
||||||
return f"file://{path}"
|
elif not exists:
|
||||||
|
full_path = settings.STATIC_ROOT.joinpath('img', replacement_file).resolve()
|
||||||
|
|
||||||
|
# Load the image, check that it is valid
|
||||||
|
if full_path.exists() and full_path.is_file():
|
||||||
|
img = Image.open(full_path)
|
||||||
|
else:
|
||||||
|
# A placeholder image showing that the image is missing
|
||||||
|
img = Image.new('RGB', (64, 64), color='red')
|
||||||
|
|
||||||
|
width = kwargs.get('width', None)
|
||||||
|
height = kwargs.get('height', None)
|
||||||
|
|
||||||
|
if width is not None and height is not None:
|
||||||
|
# Resize the image, width *and* height are provided
|
||||||
|
img = img.resize((width, height))
|
||||||
|
elif width is not None:
|
||||||
|
# Resize the image, width only
|
||||||
|
wpercent = (width / float(img.size[0]))
|
||||||
|
hsize = int((float(img.size[1]) * float(wpercent)))
|
||||||
|
img = img.resize((width, hsize))
|
||||||
|
elif height is not None:
|
||||||
|
# Resize the image, height only
|
||||||
|
hpercent = (height / float(img.size[1]))
|
||||||
|
wsize = int((float(img.size[0]) * float(hpercent)))
|
||||||
|
img = img.resize((wsize, height))
|
||||||
|
|
||||||
|
# Optionally rotate the image
|
||||||
|
rotate = kwargs.get('rotate', None)
|
||||||
|
|
||||||
|
if rotate is not None:
|
||||||
|
img = img.rotate(rotate)
|
||||||
|
|
||||||
|
# Return a base-64 encoded image
|
||||||
|
img_data = report.helpers.encode_image_base64(img)
|
||||||
|
|
||||||
|
return img_data
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag()
|
@register.simple_tag()
|
||||||
@ -164,7 +206,7 @@ def encode_svg_image(filename):
|
|||||||
exists = False
|
exists = False
|
||||||
|
|
||||||
if not exists:
|
if not exists:
|
||||||
raise FileNotFoundError(f"Image file '{filename}' not found")
|
raise FileNotFoundError(_("Image file not found") + f": '{filename}'")
|
||||||
|
|
||||||
# Read the file data
|
# Read the file data
|
||||||
with open(full_path, 'rb') as f:
|
with open(full_path, 'rb') as f:
|
||||||
@ -175,7 +217,7 @@ def encode_svg_image(filename):
|
|||||||
|
|
||||||
|
|
||||||
@register.simple_tag()
|
@register.simple_tag()
|
||||||
def part_image(part: Part):
|
def part_image(part: Part, preview=False, thumbnail=False, **kwargs):
|
||||||
"""Return a fully-qualified path for a part image.
|
"""Return a fully-qualified path for a part image.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
@ -184,13 +226,17 @@ def part_image(part: Part):
|
|||||||
Raises:
|
Raises:
|
||||||
TypeError if provided part is not a Part instance
|
TypeError if provided part is not a Part instance
|
||||||
"""
|
"""
|
||||||
if type(part) is Part:
|
if type(part) is not Part:
|
||||||
|
raise TypeError(_("part_image tag requires a Part instance"))
|
||||||
|
|
||||||
|
if preview:
|
||||||
|
img = part.image.preview.name
|
||||||
|
elif thumbnail:
|
||||||
|
img = part.image.thumbnail.name
|
||||||
|
else:
|
||||||
img = part.image.name
|
img = part.image.name
|
||||||
|
|
||||||
else:
|
return uploaded_image(img, **kwargs)
|
||||||
raise TypeError("part_image tag requires a Part instance")
|
|
||||||
|
|
||||||
return uploaded_image(img)
|
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag()
|
@register.simple_tag()
|
||||||
@ -210,7 +256,7 @@ def part_parameter(part: Part, parameter_name: str):
|
|||||||
|
|
||||||
|
|
||||||
@register.simple_tag()
|
@register.simple_tag()
|
||||||
def company_image(company):
|
def company_image(company, preview=False, thumbnail=False, **kwargs):
|
||||||
"""Return a fully-qualified path for a company image.
|
"""Return a fully-qualified path for a company image.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
@ -219,12 +265,17 @@ def company_image(company):
|
|||||||
Raises:
|
Raises:
|
||||||
TypeError if provided company is not a Company instance
|
TypeError if provided company is not a Company instance
|
||||||
"""
|
"""
|
||||||
if type(company) is Company:
|
if type(company) is not Company:
|
||||||
img = company.image.name
|
raise TypeError(_("company_image tag requires a Company instance"))
|
||||||
else:
|
|
||||||
raise TypeError("company_image tag requires a Company instance")
|
|
||||||
|
|
||||||
return uploaded_image(img)
|
if preview:
|
||||||
|
img = company.image.preview.name
|
||||||
|
elif thumbnail:
|
||||||
|
img = company.image.thumbnail.name
|
||||||
|
else:
|
||||||
|
img = company.image.name
|
||||||
|
|
||||||
|
return uploaded_image(img, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag()
|
@register.simple_tag()
|
||||||
|
@ -91,8 +91,12 @@ class ReportTagTest(TestCase):
|
|||||||
with self.assertRaises(FileNotFoundError):
|
with self.assertRaises(FileNotFoundError):
|
||||||
report_tags.uploaded_image('/part/something/test.png', replace_missing=False)
|
report_tags.uploaded_image('/part/something/test.png', replace_missing=False)
|
||||||
|
|
||||||
img = report_tags.uploaded_image('/part/something/other.png')
|
img = str(report_tags.uploaded_image('/part/something/other.png'))
|
||||||
self.assertTrue('blank_image.png' in img)
|
|
||||||
|
if b:
|
||||||
|
self.assertIn('blank_image.png', img)
|
||||||
|
else:
|
||||||
|
self.assertIn('data:image/png;charset=utf-8;base64,', img)
|
||||||
|
|
||||||
# Create a dummy image
|
# Create a dummy image
|
||||||
img_path = 'part/images/'
|
img_path = 'part/images/'
|
||||||
@ -121,10 +125,10 @@ class ReportTagTest(TestCase):
|
|||||||
|
|
||||||
self.debug_mode(False)
|
self.debug_mode(False)
|
||||||
img = report_tags.uploaded_image('part/images/test.jpg')
|
img = report_tags.uploaded_image('part/images/test.jpg')
|
||||||
self.assertEqual(img, f'file://{img_path.joinpath("test.jpg")}')
|
self.assertTrue(img.startswith('data:image/png;charset=utf-8;base64,'))
|
||||||
|
|
||||||
img = report_tags.uploaded_image(SafeString('part/images/test.jpg'))
|
img = report_tags.uploaded_image(SafeString('part/images/test.jpg'))
|
||||||
self.assertEqual(img, f'file://{img_path.joinpath("test.jpg")}')
|
self.assertTrue(img.startswith('data:image/png;charset=utf-8;base64,'))
|
||||||
|
|
||||||
def test_part_image(self):
|
def test_part_image(self):
|
||||||
"""Unit tests for the 'part_image' tag"""
|
"""Unit tests for the 'part_image' tag"""
|
||||||
|
@ -138,7 +138,7 @@ You can access an uploaded image file if you know the *path* of the image, relat
|
|||||||
{% raw %}
|
{% raw %}
|
||||||
<!-- Load the report helper functions -->
|
<!-- Load the report helper functions -->
|
||||||
{% load report %}
|
{% load report %}
|
||||||
<img src='{% uploaded_image "subdir/my_image.png" %}'/>
|
<img src='{% uploaded_image "subdir/my_image.png" width=480 rotate=45 %}'/>
|
||||||
{% endraw %}
|
{% endraw %}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -148,6 +148,16 @@ You can access an uploaded image file if you know the *path* of the image, relat
|
|||||||
!!! warning "Invalid Image"
|
!!! warning "Invalid Image"
|
||||||
If the supplied file is not a valid image, it will be replaced with a placeholder image file
|
If the supplied file is not a valid image, it will be replaced with a placeholder image file
|
||||||
|
|
||||||
|
#### Image Manipulation
|
||||||
|
|
||||||
|
The `{% raw %}{% uploaded_image %}{% endraw %}` tag supports some optional parameters for image manipulation. These can be used to adjust or resize the image - to reduce the size of the generated report file, for example.
|
||||||
|
|
||||||
|
```html
|
||||||
|
{% raw %}
|
||||||
|
{% load report %}
|
||||||
|
<img src='{% uploaded_image "image_file.png" width=500 rotate=45 %}'>
|
||||||
|
{% endraw %}```
|
||||||
|
|
||||||
|
|
||||||
### SVG Images
|
### SVG Images
|
||||||
|
|
||||||
@ -173,6 +183,26 @@ A shortcut function is provided for rendering an image associated with a Part in
|
|||||||
{% endraw %}
|
{% endraw %}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Image Arguments
|
||||||
|
|
||||||
|
Any optional arguments which can be used in the [uploaded_image tag](#uploaded-images) can be used here too.
|
||||||
|
|
||||||
|
#### Image Variations
|
||||||
|
|
||||||
|
The *Part* model supports *preview* (256 x 256) and *thumbnail* (128 x 128) versions of the uploaded image. These variations can be used in the generated reports (e.g. to reduce generated file size):
|
||||||
|
|
||||||
|
```html
|
||||||
|
{% raw %}
|
||||||
|
{% load report %}
|
||||||
|
<!-- Render the "preview" image variation -->
|
||||||
|
<img src='{% part_image part preview=True %}'>
|
||||||
|
|
||||||
|
<!-- Render the "thumbnail" image variation -->
|
||||||
|
<img src='{% part_image part thumbnail=True %}'>
|
||||||
|
{% endraw %}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### Company Images
|
### Company Images
|
||||||
|
|
||||||
A shortcut function is provided for rendering an image associated with a Company instance. You can render the image of the company using the `{% raw %}{% company_image ... %}{% endraw %}` template tag:
|
A shortcut function is provided for rendering an image associated with a Company instance. You can render the image of the company using the `{% raw %}{% company_image ... %}{% endraw %}` template tag:
|
||||||
@ -185,6 +215,10 @@ A shortcut function is provided for rendering an image associated with a Company
|
|||||||
{% endraw %}
|
{% endraw %}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Image Variations
|
||||||
|
|
||||||
|
*Preview* and *thumbnail* image variations can be rendered for the `company_image` tag, in a similar manner to [part image variations](#image-variations)
|
||||||
|
|
||||||
## InvenTree Logo
|
## InvenTree Logo
|
||||||
|
|
||||||
A template tag is provided to load the InvenTree logo image into a report. You can render the logo using the `{% raw %}{% logo_image %}{% endraw %}` tag:
|
A template tag is provided to load the InvenTree logo image into a report. You can render the logo using the `{% raw %}{% logo_image %}{% endraw %}` tag:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user