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 json
|
||||
import logging
|
||||
import os.path
|
||||
import re
|
||||
from decimal import Decimal, InvalidOperation
|
||||
from wsgiref.util import FileWrapper
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.core.exceptions import FieldError, ValidationError
|
||||
from django.core.files.storage import default_storage
|
||||
from django.http import StreamingHttpResponse
|
||||
from django.test import TestCase
|
||||
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 .settings import MEDIA_URL, STATIC_URL
|
||||
|
||||
logger = logging.getLogger('inventree')
|
||||
|
||||
|
||||
def getSetting(key, backup_value=None):
|
||||
"""Shortcut for reading a setting value from the database."""
|
||||
@ -82,6 +87,15 @@ def construct_absolute_url(*arg):
|
||||
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():
|
||||
"""Return the qualified path for the 'blank image' placeholder."""
|
||||
return getStaticUrl("img/blank_image.png")
|
||||
@ -92,13 +106,23 @@ def getBlankThumbnail():
|
||||
return getStaticUrl("img/blank_image.thumbnail.png")
|
||||
|
||||
|
||||
def TestIfImage(img):
|
||||
"""Test if an image file is indeed an image."""
|
||||
try:
|
||||
Image.open(img).verify()
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
def getLogoImage(as_file=False):
|
||||
"""Return the InvenTree logo image, or a custom logo if available."""
|
||||
|
||||
"""Return the path to the logo-file."""
|
||||
if settings.CUSTOM_LOGO:
|
||||
|
||||
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):
|
||||
|
@ -967,5 +967,5 @@ CUSTOM_LOGO = get_setting(
|
||||
|
||||
# check that the logo-file exsists in media
|
||||
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
|
||||
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('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):
|
||||
"""Tests for string wrapping."""
|
||||
|
@ -17,10 +17,10 @@ from stdimage.models import StdImageField
|
||||
import common.models
|
||||
import common.settings
|
||||
import InvenTree.fields
|
||||
import InvenTree.helpers
|
||||
import InvenTree.validators
|
||||
from common.settings import currency_code_default
|
||||
from InvenTree.fields import InvenTreeURLField
|
||||
from InvenTree.helpers import getBlankImage, getBlankThumbnail, getMediaUrl
|
||||
from InvenTree.models import InvenTreeAttachment
|
||||
from InvenTree.status_codes import PurchaseOrderStatus
|
||||
|
||||
@ -177,16 +177,16 @@ class Company(models.Model):
|
||||
def get_image_url(self):
|
||||
"""Return the URL of the image for this company."""
|
||||
if self.image:
|
||||
return getMediaUrl(self.image.url)
|
||||
return InvenTree.helpers.getMediaUrl(self.image.url)
|
||||
else:
|
||||
return getBlankImage()
|
||||
return InvenTree.helpers.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)
|
||||
return InvenTree.helpers.getMediaUrl(self.image.thumbnail.url)
|
||||
else:
|
||||
return getBlankThumbnail()
|
||||
return InvenTree.helpers.getBlankThumbnail()
|
||||
|
||||
@property
|
||||
def parts(self):
|
||||
|
@ -7,8 +7,7 @@ from datetime import date, datetime
|
||||
|
||||
from django import template
|
||||
from django.conf import settings as djangosettings
|
||||
from django.core.files.storage import default_storage
|
||||
from django.templatetags.static import StaticNode, static
|
||||
from django.templatetags.static import StaticNode
|
||||
from django.urls import reverse
|
||||
from django.utils.html import format_html
|
||||
from django.utils.safestring import mark_safe
|
||||
@ -174,6 +173,16 @@ def inventree_title(*args, **kwargs):
|
||||
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()
|
||||
def inventree_base_url(*args, **kwargs):
|
||||
"""Return the INVENTREE_BASE_URL setting."""
|
||||
@ -473,14 +482,6 @@ def inventree_customize(reference, *args, **kwargs):
|
||||
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):
|
||||
"""Custom StaticNode.
|
||||
|
||||
|
@ -535,14 +535,23 @@ class ReportAsset(models.Model):
|
||||
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):
|
||||
"""String representation of a ReportAsset instance"""
|
||||
return os.path.basename(self.asset.name)
|
||||
|
||||
# Asset file
|
||||
asset = models.FileField(
|
||||
upload_to=rename_asset,
|
||||
verbose_name=_('Asset'),
|
||||
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 %}
|
||||
|
||||
{% block header_content %}
|
||||
<!-- TODO - Make the company logo asset generic -->
|
||||
<img class='logo' src="{% asset 'company_logo.png' %}" alt="logo" width="150">
|
||||
<img class='logo' src="{% logo_image %}" alt="logo" width="150">
|
||||
|
||||
<div class='header-right'>
|
||||
<h3>
|
||||
|
@ -28,21 +28,29 @@ def image_data(img, fmt='PNG'):
|
||||
def qrcode(data, **kwargs):
|
||||
"""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
|
||||
|
||||
"""
|
||||
# Construct "default" values
|
||||
params = dict(
|
||||
box_size=20,
|
||||
border=1,
|
||||
version=1,
|
||||
)
|
||||
|
||||
fill_color = kwargs.pop('fill_color', 'black')
|
||||
back_color = kwargs.pop('back_color', 'white')
|
||||
|
||||
format = kwargs.pop('format', 'PNG')
|
||||
|
||||
params.update(**kwargs)
|
||||
|
||||
qr = python_qrcode.QRCode(**params)
|
||||
@ -50,9 +58,13 @@ def qrcode(data, **kwargs):
|
||||
qr.add_data(data, optimize=20)
|
||||
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()
|
||||
@ -60,6 +72,8 @@ def barcode(data, barcode_class='code128', **kwargs):
|
||||
"""Render a barcode."""
|
||||
constructor = python_barcode.get_barcode_class(barcode_class)
|
||||
|
||||
format = kwargs.pop('format', 'PNG')
|
||||
|
||||
data = str(data).zfill(constructor.digits)
|
||||
|
||||
writer = python_barcode.writer.ImageWriter
|
||||
@ -68,5 +82,5 @@ def barcode(data, barcode_class='code128', **kwargs):
|
||||
|
||||
image = barcode_image.render(writer_options=kwargs)
|
||||
|
||||
# Render to byte-encoded PNG
|
||||
return image_data(image)
|
||||
# Render to byte-encoded image
|
||||
return image_data(image, fmt=format)
|
||||
|
@ -10,22 +10,77 @@ import InvenTree.helpers
|
||||
from common.models import InvenTreeSetting
|
||||
from company.models import Company
|
||||
from part.models import Part
|
||||
from stock.models import StockItem
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.simple_tag()
|
||||
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
|
||||
debug_mode = InvenTreeSetting.get_setting('REPORT_DEBUG_MODE')
|
||||
|
||||
if debug_mode:
|
||||
path = os.path.join(settings.MEDIA_URL, 'report', 'assets', filename)
|
||||
else:
|
||||
# Test if the file actually exists
|
||||
full_path = os.path.join(settings.MEDIA_ROOT, 'report', 'assets', filename)
|
||||
|
||||
path = os.path.join(settings.MEDIA_ROOT, 'report', 'assets', filename)
|
||||
if not os.path.exists(full_path) or not os.path.isfile(full_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}"
|
||||
@ -33,66 +88,55 @@ def asset(filename):
|
||||
|
||||
@register.simple_tag()
|
||||
def part_image(part):
|
||||
"""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')
|
||||
"""Return a fully-qualified path for a part image.
|
||||
|
||||
Arguments:
|
||||
part: a Part model instance
|
||||
|
||||
Raises:
|
||||
TypeError if provided part is not a Part instance
|
||||
"""
|
||||
|
||||
if type(part) is Part:
|
||||
img = part.image.name
|
||||
|
||||
elif type(part) is StockItem:
|
||||
img = part.part.image.name
|
||||
|
||||
else:
|
||||
img = ''
|
||||
raise TypeError("part_image tag requires a Part instance")
|
||||
|
||||
if debug_mode:
|
||||
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}"
|
||||
return uploaded_image(img)
|
||||
|
||||
|
||||
@register.simple_tag()
|
||||
def company_image(company):
|
||||
"""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')
|
||||
"""Return a fully-qualified path for a company image.
|
||||
|
||||
Arguments:
|
||||
company: a Company model instance
|
||||
|
||||
Raises:
|
||||
TypeError if provided company is not a Company instance
|
||||
"""
|
||||
|
||||
if type(company) is Company:
|
||||
img = company.image.name
|
||||
else:
|
||||
img = ''
|
||||
raise TypeError("company_image tag requires a Company instance")
|
||||
|
||||
if debug_mode:
|
||||
if img:
|
||||
return os.path.join(settings.MEDIA_URL, img)
|
||||
else:
|
||||
return os.path.join(settings.STATIC_URL, 'img', 'blank_image.png')
|
||||
return uploaded_image(img)
|
||||
|
||||
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)
|
||||
@register.simple_tag()
|
||||
def logo_image():
|
||||
"""Return a fully-qualified path for the logo image.
|
||||
|
||||
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()
|
||||
|
@ -6,15 +6,142 @@ import shutil
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from django.http.response import StreamingHttpResponse
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
import report.models as report_models
|
||||
from build.models import Build
|
||||
from common.models import InvenTreeSetting, InvenTreeUserSetting
|
||||
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
|
||||
|
||||
|
||||
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):
|
||||
"""Base class for unit testing reporting models"""
|
||||
fixtures = [
|
||||
|
Loading…
Reference in New Issue
Block a user