mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-11-04 15:15:42 +00:00 
			
		
		
		
	feat: increase coverage (#8897)
* remove preference-view * bump api * move tag test to seperate file * extend tests * make tags more robust * Revert "remove preference-view" This reverts commitb95aaaff3c. * Revert "bump api" This reverts commit8fc29186cf. * more coverage * re-enable test * even more tests * just ignore it * moa test * crude debugging * more debugging * adapt test * reduce debugging * fix test_part_image * remove TemplatePrintBase * fix style * fix code * fix check * ensure none exsisting image to not cause issue
This commit is contained in:
		@@ -211,16 +211,16 @@ def render_currency(
 | 
			
		||||
        except Exception:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
    if min_decimal_places is None:
 | 
			
		||||
    if min_decimal_places is None or not isinstance(min_decimal_places, (int, float)):
 | 
			
		||||
        min_decimal_places = get_global_setting('PRICING_DECIMAL_PLACES_MIN', 0)
 | 
			
		||||
 | 
			
		||||
    if max_decimal_places is None:
 | 
			
		||||
    if max_decimal_places is None or not isinstance(max_decimal_places, (int, float)):
 | 
			
		||||
        max_decimal_places = get_global_setting('PRICING_DECIMAL_PLACES', 6)
 | 
			
		||||
 | 
			
		||||
    value = Decimal(str(money.amount)).normalize()
 | 
			
		||||
    value = str(value)
 | 
			
		||||
 | 
			
		||||
    if decimal_places is not None:
 | 
			
		||||
    if decimal_places is not None and isinstance(decimal_places, (int, float)):
 | 
			
		||||
        # Decimal place count is provided, use it
 | 
			
		||||
        pass
 | 
			
		||||
    elif '.' in value:
 | 
			
		||||
 
 | 
			
		||||
@@ -64,12 +64,6 @@ def render_date(date_object):
 | 
			
		||||
    return date_object
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register.simple_tag
 | 
			
		||||
def render_currency(money, **kwargs):
 | 
			
		||||
    """Render a currency / Money object."""
 | 
			
		||||
    return InvenTree.helpers_model.render_currency(money, **kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register.simple_tag()
 | 
			
		||||
def str2bool(x, *args, **kwargs):
 | 
			
		||||
    """Convert a string to a boolean value."""
 | 
			
		||||
 
 | 
			
		||||
@@ -39,6 +39,48 @@ from stock.models import StockItem, StockLocation
 | 
			
		||||
from stock.status_codes import StockStatus
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PartImageTestMixin:
 | 
			
		||||
    """Mixin for testing part images."""
 | 
			
		||||
 | 
			
		||||
    roles = [
 | 
			
		||||
        'part.change',
 | 
			
		||||
        'part.add',
 | 
			
		||||
        'part.delete',
 | 
			
		||||
        'part_category.change',
 | 
			
		||||
        'part_category.add',
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def setUpTestData(cls):
 | 
			
		||||
        """Custom setup routine for this class."""
 | 
			
		||||
        super().setUpTestData()
 | 
			
		||||
 | 
			
		||||
        # Create a custom APIClient for file uploads
 | 
			
		||||
        # Ref: https://stackoverflow.com/questions/40453947/how-to-generate-a-file-upload-test-request-with-django-rest-frameworks-apireq
 | 
			
		||||
        cls.upload_client = APIClient()
 | 
			
		||||
        cls.upload_client.force_authenticate(user=cls.user)
 | 
			
		||||
 | 
			
		||||
    def create_test_image(self):
 | 
			
		||||
        """Create a test image file."""
 | 
			
		||||
        p = Part.objects.first()
 | 
			
		||||
 | 
			
		||||
        fn = BASE_DIR / '_testfolder' / 'part_image_123abc.png'
 | 
			
		||||
 | 
			
		||||
        img = PIL.Image.new('RGB', (128, 128), color='blue')
 | 
			
		||||
        img.save(fn)
 | 
			
		||||
 | 
			
		||||
        with open(fn, 'rb') as img_file:
 | 
			
		||||
            response = self.upload_client.patch(
 | 
			
		||||
                reverse('api-part-detail', kwargs={'pk': p.pk}),
 | 
			
		||||
                {'image': img_file},
 | 
			
		||||
                expected_code=200,
 | 
			
		||||
            )
 | 
			
		||||
            print(response.data)
 | 
			
		||||
            image_name = response.data['image']
 | 
			
		||||
            self.assertTrue(image_name.startswith('/media/part_images/part_image'))
 | 
			
		||||
        return image_name
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PartCategoryAPITest(InvenTreeAPITestCase):
 | 
			
		||||
    """Unit tests for the PartCategory API."""
 | 
			
		||||
 | 
			
		||||
@@ -1463,7 +1505,7 @@ class PartCreationTests(PartAPITestBase):
 | 
			
		||||
        self.assertEqual(prt.parameters.count(), 3)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PartDetailTests(PartAPITestBase):
 | 
			
		||||
class PartDetailTests(PartImageTestMixin, PartAPITestBase):
 | 
			
		||||
    """Test that we can create / edit / delete Part objects via the API."""
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
@@ -1656,22 +1698,7 @@ class PartDetailTests(PartAPITestBase):
 | 
			
		||||
    def test_existing_image(self):
 | 
			
		||||
        """Test that we can allocate an existing uploaded image to a new Part."""
 | 
			
		||||
        # First, upload an image for an existing part
 | 
			
		||||
        p = Part.objects.first()
 | 
			
		||||
 | 
			
		||||
        fn = BASE_DIR / '_testfolder' / 'part_image_123abc.png'
 | 
			
		||||
 | 
			
		||||
        img = PIL.Image.new('RGB', (128, 128), color='blue')
 | 
			
		||||
        img.save(fn)
 | 
			
		||||
 | 
			
		||||
        with open(fn, 'rb') as img_file:
 | 
			
		||||
            response = self.upload_client.patch(
 | 
			
		||||
                reverse('api-part-detail', kwargs={'pk': p.pk}),
 | 
			
		||||
                {'image': img_file},
 | 
			
		||||
                expected_code=200,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            image_name = response.data['image']
 | 
			
		||||
            self.assertTrue(image_name.startswith('/media/part_images/part_image'))
 | 
			
		||||
        image_name = self.create_test_image()
 | 
			
		||||
 | 
			
		||||
        # Attempt to create, but with an invalid image name
 | 
			
		||||
        response = self.post(
 | 
			
		||||
 
 | 
			
		||||
@@ -44,6 +44,8 @@ def filter_queryset(queryset: QuerySet, **kwargs) -> QuerySet:
 | 
			
		||||
    Example:
 | 
			
		||||
        {% filter_queryset companies is_supplier=True as suppliers %}
 | 
			
		||||
    """
 | 
			
		||||
    if not isinstance(queryset, QuerySet):
 | 
			
		||||
        return queryset
 | 
			
		||||
    return queryset.filter(**kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -60,7 +62,10 @@ def filter_db_model(model_name: str, **kwargs) -> QuerySet:
 | 
			
		||||
    Example:
 | 
			
		||||
        {% filter_db_model 'part.partcategory' is_template=True as template_parts %}
 | 
			
		||||
    """
 | 
			
		||||
    app_name, model_name = model_name.split('.')
 | 
			
		||||
    try:
 | 
			
		||||
        app_name, model_name = model_name.split('.')
 | 
			
		||||
    except ValueError:
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        model = apps.get_model(app_name, model_name)
 | 
			
		||||
@@ -93,13 +98,7 @@ def getindex(container: list, index: int) -> Any:
 | 
			
		||||
 | 
			
		||||
    if index < 0 or index >= len(container):
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        value = container[index]
 | 
			
		||||
    except IndexError:
 | 
			
		||||
        value = None
 | 
			
		||||
 | 
			
		||||
    return value
 | 
			
		||||
    return container[index]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register.simple_tag()
 | 
			
		||||
@@ -191,8 +190,8 @@ def uploaded_image(
 | 
			
		||||
        try:
 | 
			
		||||
            full_path = settings.MEDIA_ROOT.joinpath(filename).resolve()
 | 
			
		||||
            exists = full_path.exists() and full_path.is_file()
 | 
			
		||||
        except Exception:
 | 
			
		||||
            exists = False
 | 
			
		||||
        except Exception:  # pragma: no cover
 | 
			
		||||
            exists = False  # pragma: no cover
 | 
			
		||||
 | 
			
		||||
    if exists and validate and not InvenTree.helpers.TestIfImage(full_path):
 | 
			
		||||
        logger.warning("File '%s' is not a valid image", filename)
 | 
			
		||||
@@ -302,10 +301,14 @@ def part_image(part: Part, preview: bool = False, thumbnail: bool = False, **kwa
 | 
			
		||||
    if type(part) is not Part:
 | 
			
		||||
        raise TypeError(_('part_image tag requires a Part instance'))
 | 
			
		||||
 | 
			
		||||
    if preview:
 | 
			
		||||
        img = part.image.preview.name
 | 
			
		||||
    if not part.image:
 | 
			
		||||
        img = None
 | 
			
		||||
    elif preview:
 | 
			
		||||
        img = None if not hasattr(part.image, 'preview') else part.image.preview.name
 | 
			
		||||
    elif thumbnail:
 | 
			
		||||
        img = part.image.thumbnail.name
 | 
			
		||||
        img = (
 | 
			
		||||
            None if not hasattr(part.image, 'thumbnail') else part.image.thumbnail.name
 | 
			
		||||
        )
 | 
			
		||||
    else:
 | 
			
		||||
        img = part.image.name
 | 
			
		||||
 | 
			
		||||
@@ -376,10 +379,14 @@ def internal_link(link, text) -> str:
 | 
			
		||||
    """
 | 
			
		||||
    text = str(text)
 | 
			
		||||
 | 
			
		||||
    url = InvenTree.helpers_model.construct_absolute_url(link)
 | 
			
		||||
    try:
 | 
			
		||||
        url = InvenTree.helpers_model.construct_absolute_url(link)
 | 
			
		||||
    except Exception:
 | 
			
		||||
        url = None
 | 
			
		||||
 | 
			
		||||
    # If the base URL is not set, just return the text
 | 
			
		||||
    if not url:
 | 
			
		||||
        logger.warning('Failed to construct absolute URL for internal link')
 | 
			
		||||
        return text
 | 
			
		||||
 | 
			
		||||
    return mark_safe(f'<a href="{url}">{text}</a>')
 | 
			
		||||
@@ -525,7 +532,10 @@ def format_date(dt: date, timezone: Optional[str] = None, fmt: Optional[str] = N
 | 
			
		||||
        timezone: The timezone to use for the date (defaults to the server timezone)
 | 
			
		||||
        fmt: The format string to use (defaults to ISO formatting)
 | 
			
		||||
    """
 | 
			
		||||
    dt = InvenTree.helpers.to_local_time(dt, timezone).date()
 | 
			
		||||
    try:
 | 
			
		||||
        dt = InvenTree.helpers.to_local_time(dt, timezone).date()
 | 
			
		||||
    except TypeError:
 | 
			
		||||
        return str(dt)
 | 
			
		||||
 | 
			
		||||
    if fmt:
 | 
			
		||||
        return dt.strftime(fmt)
 | 
			
		||||
@@ -558,15 +568,18 @@ def icon(name, **kwargs):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register.simple_tag()
 | 
			
		||||
def include_icon_fonts():
 | 
			
		||||
def include_icon_fonts(ttf: bool = False, woff: bool = False):
 | 
			
		||||
    """Return the CSS font-face rule for the icon fonts used on the current page (or all)."""
 | 
			
		||||
    fonts = []
 | 
			
		||||
 | 
			
		||||
    if not ttf and not woff:
 | 
			
		||||
        ttf = woff = True
 | 
			
		||||
 | 
			
		||||
    for font in common.icons.get_icon_packs().values():
 | 
			
		||||
        # generate the font src string (prefer ttf over woff, woff2 is not supported by weasyprint)
 | 
			
		||||
        if 'truetype' in font.fonts:
 | 
			
		||||
        if 'truetype' in font.fonts and ttf:
 | 
			
		||||
            font_format, url = 'truetype', font.fonts['truetype']
 | 
			
		||||
        elif 'woff' in font.fonts:
 | 
			
		||||
        elif 'woff' in font.fonts and woff:
 | 
			
		||||
            font_format, url = 'woff', font.fonts['woff']
 | 
			
		||||
 | 
			
		||||
        fonts.append(f"""
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										447
									
								
								src/backend/InvenTree/report/test_tags.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										447
									
								
								src/backend/InvenTree/report/test_tags.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,447 @@
 | 
			
		||||
"""Test for custom report tags."""
 | 
			
		||||
 | 
			
		||||
from zoneinfo import ZoneInfo
 | 
			
		||||
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
from django.test import TestCase, override_settings
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
from django.utils.safestring import SafeString
 | 
			
		||||
 | 
			
		||||
from djmoney.money import Money
 | 
			
		||||
from PIL import Image
 | 
			
		||||
 | 
			
		||||
from common.models import InvenTreeSetting
 | 
			
		||||
from InvenTree.unit_test import InvenTreeTestCase
 | 
			
		||||
from part.models import Part, PartParameter, PartParameterTemplate
 | 
			
		||||
from part.test_api import PartImageTestMixin
 | 
			
		||||
from report.templatetags import barcode as barcode_tags
 | 
			
		||||
from report.templatetags import report as report_tags
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ReportTagTest(PartImageTestMixin, InvenTreeTestCase):
 | 
			
		||||
    """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_getindex(self):
 | 
			
		||||
        """Tests for the 'getindex' template tag."""
 | 
			
		||||
        fn = report_tags.getindex
 | 
			
		||||
        data = [1, 2, 3, 4, 5, 6]
 | 
			
		||||
 | 
			
		||||
        # Out of bounds or invalid
 | 
			
		||||
        self.assertEqual(fn(data, -1), None)
 | 
			
		||||
        self.assertEqual(fn(data, 99), None)
 | 
			
		||||
        self.assertEqual(fn(data, 'xx'), None)
 | 
			
		||||
 | 
			
		||||
        for idx in range(len(data)):
 | 
			
		||||
            self.assertEqual(fn(data, idx), data[idx])
 | 
			
		||||
 | 
			
		||||
    def test_getkey(self):
 | 
			
		||||
        """Tests for the 'getkey' template tag."""
 | 
			
		||||
        data = {'hello': 'world', 'foo': 'bar', 'with spaces': 'withoutspaces', 1: 2}
 | 
			
		||||
 | 
			
		||||
        # Valid case
 | 
			
		||||
        for k, v in data.items():
 | 
			
		||||
            self.assertEqual(report_tags.getkey(data, k), v)
 | 
			
		||||
 | 
			
		||||
        # Error case
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            None, report_tags.getkey('not a container', 'not-a-key', 'a value')
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    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 = settings.MEDIA_ROOT.joinpath('report', 'assets')
 | 
			
		||||
        asset_dir.mkdir(parents=True, exist_ok=True)
 | 
			
		||||
        asset_path = asset_dir.joinpath('test.txt')
 | 
			
		||||
 | 
			
		||||
        asset_path.write_text('dummy data')
 | 
			
		||||
 | 
			
		||||
        self.debug_mode(True)
 | 
			
		||||
        asset = report_tags.asset('test.txt')
 | 
			
		||||
        self.assertEqual(asset, '/media/report/assets/test.txt')
 | 
			
		||||
 | 
			
		||||
        # Ensure that a 'safe string' also works
 | 
			
		||||
        asset = report_tags.asset(SafeString('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 = str(report_tags.uploaded_image('/part/something/other.png'))
 | 
			
		||||
 | 
			
		||||
            if b:
 | 
			
		||||
                self.assertIn('blank_image.png', img)
 | 
			
		||||
            else:
 | 
			
		||||
                self.assertIn('data:image/png;charset=utf-8;base64,', img)
 | 
			
		||||
 | 
			
		||||
        # Create a dummy image
 | 
			
		||||
        img_path = 'part/images/'
 | 
			
		||||
        img_path = settings.MEDIA_ROOT.joinpath(img_path)
 | 
			
		||||
        img_file = img_path.joinpath('test.jpg')
 | 
			
		||||
 | 
			
		||||
        img_path.mkdir(parents=True, exist_ok=True)
 | 
			
		||||
        img_file.write_text('dummy data')
 | 
			
		||||
 | 
			
		||||
        # Test in debug mode. Returns blank image as dummy file is not a valid image
 | 
			
		||||
        self.debug_mode(True)
 | 
			
		||||
        img = report_tags.uploaded_image('part/images/test.jpg')
 | 
			
		||||
        self.assertEqual(img, '/static/img/blank_image.png')
 | 
			
		||||
 | 
			
		||||
        # Now, let's create a proper image
 | 
			
		||||
        img = Image.new('RGB', (128, 128), color='RED')
 | 
			
		||||
        img.save(img_file)
 | 
			
		||||
 | 
			
		||||
        # Try again
 | 
			
		||||
        img = report_tags.uploaded_image('part/images/test.jpg')
 | 
			
		||||
        self.assertEqual(img, '/media/part/images/test.jpg')
 | 
			
		||||
 | 
			
		||||
        # Ensure that a 'safe string' also works
 | 
			
		||||
        img = report_tags.uploaded_image(SafeString('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.assertTrue(img.startswith('data:image/png;charset=utf-8;base64,'))
 | 
			
		||||
 | 
			
		||||
        img = report_tags.uploaded_image(SafeString('part/images/test.jpg'))
 | 
			
		||||
        self.assertTrue(img.startswith('data:image/png;charset=utf-8;base64,'))
 | 
			
		||||
 | 
			
		||||
        # Check width, height, rotate
 | 
			
		||||
        img = report_tags.uploaded_image(
 | 
			
		||||
            'part/images/test.jpg', width=100, height=200, rotate=90
 | 
			
		||||
        )
 | 
			
		||||
        self.assertTrue(img.startswith('data:image/png;charset=utf-8;base64,'))
 | 
			
		||||
 | 
			
		||||
        img = report_tags.uploaded_image('part/images/test.jpg', width=100)
 | 
			
		||||
        self.assertTrue(img.startswith('data:image/png;charset=utf-8;base64,'))
 | 
			
		||||
 | 
			
		||||
        # Invalid args
 | 
			
		||||
        img = report_tags.uploaded_image(
 | 
			
		||||
            'part/images/test.jpg', width='a', height='b', rotate='c'
 | 
			
		||||
        )
 | 
			
		||||
        self.assertTrue(img.startswith('data:image/png;charset=utf-8;base64,'))
 | 
			
		||||
 | 
			
		||||
    def test_part_image(self):
 | 
			
		||||
        """Unit tests for the 'part_image' tag."""
 | 
			
		||||
        with self.assertRaises(TypeError):
 | 
			
		||||
            report_tags.part_image(None)
 | 
			
		||||
 | 
			
		||||
        obj = Part.objects.create(name='test', description='test')
 | 
			
		||||
        self.create_test_image()
 | 
			
		||||
 | 
			
		||||
        report_tags.part_image(obj, preview=True)
 | 
			
		||||
        report_tags.part_image(obj, thumbnail=True)
 | 
			
		||||
 | 
			
		||||
    def test_company_image(self):
 | 
			
		||||
        """Unit tests for the 'company_image' tag."""
 | 
			
		||||
        with self.assertRaises(TypeError):
 | 
			
		||||
            report_tags.company_image(None)
 | 
			
		||||
        with self.assertRaises(TypeError):
 | 
			
		||||
            report_tags.company_image(None, preview=True)
 | 
			
		||||
        with self.assertRaises(TypeError):
 | 
			
		||||
            report_tags.company_image(None, thumbnail=True)
 | 
			
		||||
 | 
			
		||||
    def test_internal_link(self):
 | 
			
		||||
        """Unit tests for the 'internal_link' tag."""
 | 
			
		||||
        # Test with a valid object
 | 
			
		||||
        obj = Part.objects.create(name='test', description='test')
 | 
			
		||||
        self.assertEqual(report_tags.internal_link(obj, 'test123'), 'test123')
 | 
			
		||||
        link = report_tags.internal_link(obj.get_absolute_url(), 'test')
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            link, f'<a href="http://localhost:8000/platform/part/{obj.pk}">test</a>'
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # Test with an invalid object
 | 
			
		||||
        link = report_tags.internal_link(None, None)
 | 
			
		||||
        self.assertEqual(link, '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)
 | 
			
		||||
 | 
			
		||||
    def test_maths_tags(self):
 | 
			
		||||
        """Simple tests for mathematical operator tags."""
 | 
			
		||||
        self.assertEqual(report_tags.add(1, 2), 3)
 | 
			
		||||
        self.assertEqual(report_tags.subtract(10, 4.2), 5.8)
 | 
			
		||||
        self.assertEqual(report_tags.multiply(2.3, 4), 9.2)
 | 
			
		||||
        self.assertEqual(report_tags.divide(100, 5), 20)
 | 
			
		||||
 | 
			
		||||
    def test_number_tags(self):
 | 
			
		||||
        """Simple tests for number formatting tags."""
 | 
			
		||||
        fn = report_tags.format_number
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(fn(1234), '1234')
 | 
			
		||||
        self.assertEqual(fn(1234.5678, decimal_places=2), '1234.57')
 | 
			
		||||
        self.assertEqual(fn(1234.5678, decimal_places=3), '1234.568')
 | 
			
		||||
        self.assertEqual(fn(-9999.5678, decimal_places=2, separator=','), '-9,999.57')
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            fn(9988776655.4321, integer=True, separator=' '), '9 988 776 655'
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # Failure cases
 | 
			
		||||
        self.assertEqual(fn('abc'), 'abc')
 | 
			
		||||
        self.assertEqual(fn(1234.456, decimal_places='a'), '1234.456')
 | 
			
		||||
        self.assertEqual(fn(1234.456, leading='a'), '1234.456')
 | 
			
		||||
 | 
			
		||||
    @override_settings(TIME_ZONE='America/New_York')
 | 
			
		||||
    def test_date_tags(self):
 | 
			
		||||
        """Test for date formatting tags.
 | 
			
		||||
 | 
			
		||||
        - Source timezone is Australia/Sydney
 | 
			
		||||
        - Server timezone is America/New York
 | 
			
		||||
        """
 | 
			
		||||
        time = timezone.datetime(
 | 
			
		||||
            year=2024,
 | 
			
		||||
            month=3,
 | 
			
		||||
            day=13,
 | 
			
		||||
            hour=12,
 | 
			
		||||
            minute=30,
 | 
			
		||||
            second=0,
 | 
			
		||||
            tzinfo=ZoneInfo('Australia/Sydney'),
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # Format a set of tests: timezone, format, expected
 | 
			
		||||
        tests = [
 | 
			
		||||
            (None, None, '2024-03-12T21:30:00-04:00'),
 | 
			
		||||
            (None, '%d-%m-%y', '12-03-24'),
 | 
			
		||||
            ('UTC', None, '2024-03-13T01:30:00+00:00'),
 | 
			
		||||
            ('UTC', '%d-%B-%Y', '13-March-2024'),
 | 
			
		||||
            ('Europe/Amsterdam', None, '2024-03-13T02:30:00+01:00'),
 | 
			
		||||
            ('Europe/Amsterdam', '%y-%m-%d %H:%M', '24-03-13 02:30'),
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        for tz, fmt, expected in tests:
 | 
			
		||||
            result = report_tags.format_datetime(time, tz, fmt)
 | 
			
		||||
            self.assertEqual(result, expected)
 | 
			
		||||
 | 
			
		||||
    def test_icon(self):
 | 
			
		||||
        """Test the icon template tag."""
 | 
			
		||||
        for icon in [None, '', 'not:the-correct-format', 'any-non-existent-icon']:
 | 
			
		||||
            self.assertEqual(report_tags.icon(icon), '')
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            report_tags.icon('ti:package:outline'),
 | 
			
		||||
            f'<i class="icon " style="font-family: inventree-icon-font-ti">{chr(int("eaff", 16))}</i>',
 | 
			
		||||
        )
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            report_tags.icon(
 | 
			
		||||
                'ti:package:outline', **{'class': 'my-custom-class my-seconds-class'}
 | 
			
		||||
            ),
 | 
			
		||||
            f'<i class="icon my-custom-class my-seconds-class" style="font-family: inventree-icon-font-ti">{chr(int("eaff", 16))}</i>',
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_include_icon_fonts(self):
 | 
			
		||||
        """Test the include_icon_fonts template tag."""
 | 
			
		||||
        # ttf
 | 
			
		||||
        style = report_tags.include_icon_fonts()
 | 
			
		||||
 | 
			
		||||
        self.assertIn('@font-face {', style)
 | 
			
		||||
        self.assertIn("font-family: 'inventree-icon-font-ti';", style)
 | 
			
		||||
        self.assertIn('tabler-icons/tabler-icons.ttf', style)
 | 
			
		||||
        self.assertIn('.icon {', style)
 | 
			
		||||
 | 
			
		||||
        # woff
 | 
			
		||||
        style = report_tags.include_icon_fonts(woff=True)
 | 
			
		||||
        self.assertIn('tabler-icons/tabler-icons.woff', style)
 | 
			
		||||
 | 
			
		||||
    def test_filter_queryset(self):
 | 
			
		||||
        """Test the filter_queryset template tag."""
 | 
			
		||||
        # Test with a valid queryset
 | 
			
		||||
        qs = Part.objects.all()
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            list(report_tags.filter_queryset(qs, name='test')),
 | 
			
		||||
            list(qs.filter(name='test')),
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # Test with an invalid queryset
 | 
			
		||||
        self.assertEqual(report_tags.filter_queryset(None, name='test'), None)
 | 
			
		||||
 | 
			
		||||
    def test_filter_db_model(self):
 | 
			
		||||
        """Test the filter_db_model template tag."""
 | 
			
		||||
        self.assertEqual(list(report_tags.filter_db_model('part.part')), [])
 | 
			
		||||
 | 
			
		||||
        part = Part.objects.create(name='test', description='test')
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            list(report_tags.filter_db_model('part.part', name='test')), [part]
 | 
			
		||||
        )
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            list(report_tags.filter_db_model('part.part', name='test1')), []
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # Invalid model
 | 
			
		||||
        self.assertEqual(report_tags.filter_db_model('part.abcd'), None)
 | 
			
		||||
        self.assertEqual(report_tags.filter_db_model(''), None)
 | 
			
		||||
 | 
			
		||||
    def test_encode_svg_image(self):
 | 
			
		||||
        """Test the encode_svg_image template tag."""
 | 
			
		||||
        # Generate smallest possible SVG for testing
 | 
			
		||||
        svg_path = settings.BASE_DIR / '_testfolder' / 'part_image_123abc.png'
 | 
			
		||||
        with open(svg_path, 'w', encoding='utf8') as f:
 | 
			
		||||
            f.write('<svg xmlns="http://www.w3.org/2000/svg>')
 | 
			
		||||
 | 
			
		||||
        # Test with a valid SVG file
 | 
			
		||||
        svg = report_tags.encode_svg_image(svg_path)
 | 
			
		||||
        self.assertTrue(svg.startswith('data:image/svg+xml;charset=utf-8;base64,'))
 | 
			
		||||
        self.assertIn('svg', svg)
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            'data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmc+',
 | 
			
		||||
            svg,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_part_parameter(self):
 | 
			
		||||
        """Test the part_parameter template tag."""
 | 
			
		||||
        # Test with a valid part
 | 
			
		||||
        part = Part.objects.create(name='test', description='test')
 | 
			
		||||
        t1 = PartParameterTemplate.objects.create(name='Template 1', units='mm')
 | 
			
		||||
        parameter = PartParameter.objects.create(part=part, template=t1, data='test')
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(report_tags.part_parameter(part, 'name'), None)
 | 
			
		||||
        self.assertEqual(report_tags.part_parameter(part, 'Template 1'), parameter)
 | 
			
		||||
        # Test with an invalid part
 | 
			
		||||
        self.assertEqual(report_tags.part_parameter(None, 'name'), None)
 | 
			
		||||
 | 
			
		||||
    def test_render_currency(self):
 | 
			
		||||
        """Test the render_currency template tag."""
 | 
			
		||||
        m = Money(1234.56, 'USD')
 | 
			
		||||
        exp_m = '$1,234.56'
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(report_tags.render_currency(m), exp_m)
 | 
			
		||||
        self.assertEqual(report_tags.render_currency(m, currency='EUR'), exp_m)
 | 
			
		||||
        self.assertEqual(report_tags.render_currency(m, decimal_places=3), '$1,234.560')
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            report_tags.render_currency(
 | 
			
		||||
                Money(1234, 'USD'), currency='EUR', min_decimal_places=3
 | 
			
		||||
            ),
 | 
			
		||||
            '$1,234.000',
 | 
			
		||||
        )
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            report_tags.render_currency(
 | 
			
		||||
                Money(1234, 'USD'), currency='EUR', max_decimal_places=1
 | 
			
		||||
            ),
 | 
			
		||||
            '$1,234.0',
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # Test with an invalid amount
 | 
			
		||||
        self.assertEqual(report_tags.render_currency('abc'), '-')
 | 
			
		||||
        self.assertEqual(report_tags.render_currency(m, decimal_places='a'), exp_m)
 | 
			
		||||
        self.assertEqual(report_tags.render_currency(m, min_decimal_places='a'), exp_m)
 | 
			
		||||
        self.assertEqual(report_tags.render_currency(m, max_decimal_places='a'), exp_m)
 | 
			
		||||
 | 
			
		||||
    def test_render_html_text(self):
 | 
			
		||||
        """Test the render_html_text template tag."""
 | 
			
		||||
        # Test with a valid text
 | 
			
		||||
        self.assertEqual(report_tags.render_html_text('hello world'), 'hello world')
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            report_tags.render_html_text('<b>hello world</b>'), '<b>hello world</b>'
 | 
			
		||||
        )
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            report_tags.render_html_text('hello world', bold=True),
 | 
			
		||||
            '<strong>hello world</strong>',
 | 
			
		||||
        )
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            report_tags.render_html_text('hello world', italic=True),
 | 
			
		||||
            '<em>hello world</em>',
 | 
			
		||||
        )
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            report_tags.render_html_text('hello world', heading='h1'),
 | 
			
		||||
            '<h1>hello world</h1>',
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_format_date(self):
 | 
			
		||||
        """Test the format_date template tag."""
 | 
			
		||||
        # Test with a valid date
 | 
			
		||||
        date = timezone.datetime(year=2024, month=3, day=13)
 | 
			
		||||
        self.assertEqual(report_tags.format_date(date), '2024-03-13')
 | 
			
		||||
        self.assertEqual(report_tags.format_date(date, fmt='%d-%m-%y'), '13-03-24')
 | 
			
		||||
 | 
			
		||||
        # Test with an invalid date
 | 
			
		||||
        self.assertEqual(report_tags.format_date('abc'), 'abc')
 | 
			
		||||
        self.assertEqual(report_tags.format_date(date, fmt='a'), 'a')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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.assertIsInstance(barcode, str)
 | 
			
		||||
        self.assertTrue(barcode.startswith('data:image/png;'))
 | 
			
		||||
 | 
			
		||||
        # Try with a different format
 | 
			
		||||
        barcode = barcode_tags.barcode('99999', format='BMP')
 | 
			
		||||
        self.assertIsInstance(barcode, str)
 | 
			
		||||
        self.assertTrue(barcode.startswith('data:image/bmp;'))
 | 
			
		||||
 | 
			
		||||
        # Test empty tag
 | 
			
		||||
        with self.assertRaises(ValueError):
 | 
			
		||||
            barcode_tags.barcode('')
 | 
			
		||||
 | 
			
		||||
    def test_qrcode(self):
 | 
			
		||||
        """Test the qrcode generation tag."""
 | 
			
		||||
        # Test with default settings
 | 
			
		||||
        qrcode = barcode_tags.qrcode('hello world')
 | 
			
		||||
        self.assertIsInstance(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.assertIsInstance(qrcode, str)
 | 
			
		||||
        self.assertTrue(qrcode.startswith('data:image/bmp;'))
 | 
			
		||||
        self.assertEqual(len(qrcode), 309720)
 | 
			
		||||
 | 
			
		||||
        # Test empty tag
 | 
			
		||||
        with self.assertRaises(ValueError):
 | 
			
		||||
            barcode_tags.qrcode('')
 | 
			
		||||
 | 
			
		||||
    def test_datamatrix(self):
 | 
			
		||||
        """Test the datamatrix generation tag."""
 | 
			
		||||
        # Test with default settings
 | 
			
		||||
        datamatrix = barcode_tags.datamatrix('hello world')
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            datamatrix,
 | 
			
		||||
            'data:image/png;charset=utf-8;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAIAAADZrBkAAAAAlElEQVR4nJ1TQQ7AIAgri///cncw6wroEseBgEFbCgZJnNsFICKOPAAIjeSM5T11IznK5f5WRMgnkhP9JfCcTC/MxFZ5hxLOgqrn3o/z/OqtsNpdSL31Iu9W4Dq8Sulu+q5Nuqa3XYOdnuidlICPpXhZVBruyzAKSZehT+yNlzvZQcq6JiW7Ni592swf/43kdlDfdgMk1eOtR7kWpAAAAABJRU5ErkJggg==',
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        datamatrix = barcode_tags.datamatrix(
 | 
			
		||||
            'hello world', border=3, fill_color='red', back_color='blue'
 | 
			
		||||
        )
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            datamatrix,
 | 
			
		||||
            'data:image/png;charset=utf-8;base64,iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAIAAABL1vtsAAAAqElEQVR4nN1UQQ6AMAgrxv9/GQ9mpJYSY/QkBxM3KLUUA0i8i+1l/dcQiXj09CwSEU2aQJ7nE8ou2faVUXoPZSEkq+dZKVxWg4UqxUHnVdkp6IdwMXMulGvzNBDMk4WwPSrUF3LNnQNZBJmOsZaVXa44QSEKnvWb5mIgKon1E1H6aPyOcIa15uhONP9aR4hSCiGmYAoYpj4uO+vK4+ybMhr8Nkjmn/z4Dvoldi8uJu4iAAAAAElFTkSuQmCC',
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # Test empty tag
 | 
			
		||||
        with self.assertRaises(ValueError):
 | 
			
		||||
            barcode_tags.datamatrix('')
 | 
			
		||||
@@ -1,291 +1,22 @@
 | 
			
		||||
"""Unit testing for the various report models."""
 | 
			
		||||
 | 
			
		||||
from io import StringIO
 | 
			
		||||
from zoneinfo import ZoneInfo
 | 
			
		||||
 | 
			
		||||
from django.apps import apps
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
from django.core.cache import cache
 | 
			
		||||
from django.test import TestCase, override_settings
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
from django.utils.safestring import SafeString
 | 
			
		||||
 | 
			
		||||
from PIL import Image
 | 
			
		||||
 | 
			
		||||
import report.models as report_models
 | 
			
		||||
from build.models import Build
 | 
			
		||||
from common.models import Attachment, InvenTreeSetting
 | 
			
		||||
from common.models import Attachment
 | 
			
		||||
from InvenTree.unit_test import AdminTestCase, InvenTreeAPITestCase
 | 
			
		||||
from order.models import ReturnOrder, SalesOrder
 | 
			
		||||
from part.models import Part
 | 
			
		||||
from plugin.registry import registry
 | 
			
		||||
from report.models import LabelTemplate, ReportTemplate
 | 
			
		||||
from report.templatetags import barcode as barcode_tags
 | 
			
		||||
from report.templatetags import report as report_tags
 | 
			
		||||
from stock.models import StockItem
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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_getindex(self):
 | 
			
		||||
        """Tests for the 'getindex' template tag."""
 | 
			
		||||
        fn = report_tags.getindex
 | 
			
		||||
        data = [1, 2, 3, 4, 5, 6]
 | 
			
		||||
 | 
			
		||||
        # Out of bounds or invalid
 | 
			
		||||
        self.assertEqual(fn(data, -1), None)
 | 
			
		||||
        self.assertEqual(fn(data, 99), None)
 | 
			
		||||
        self.assertEqual(fn(data, 'xx'), None)
 | 
			
		||||
 | 
			
		||||
        for idx in range(len(data)):
 | 
			
		||||
            self.assertEqual(fn(data, idx), data[idx])
 | 
			
		||||
 | 
			
		||||
    def test_getkey(self):
 | 
			
		||||
        """Tests for the 'getkey' template tag."""
 | 
			
		||||
        data = {'hello': 'world', 'foo': 'bar', 'with spaces': 'withoutspaces', 1: 2}
 | 
			
		||||
 | 
			
		||||
        for k, v in data.items():
 | 
			
		||||
            self.assertEqual(report_tags.getkey(data, k), v)
 | 
			
		||||
 | 
			
		||||
    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 = settings.MEDIA_ROOT.joinpath('report', 'assets')
 | 
			
		||||
        asset_dir.mkdir(parents=True, exist_ok=True)
 | 
			
		||||
        asset_path = asset_dir.joinpath('test.txt')
 | 
			
		||||
 | 
			
		||||
        asset_path.write_text('dummy data')
 | 
			
		||||
 | 
			
		||||
        self.debug_mode(True)
 | 
			
		||||
        asset = report_tags.asset('test.txt')
 | 
			
		||||
        self.assertEqual(asset, '/media/report/assets/test.txt')
 | 
			
		||||
 | 
			
		||||
        # Ensure that a 'safe string' also works
 | 
			
		||||
        asset = report_tags.asset(SafeString('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 = str(report_tags.uploaded_image('/part/something/other.png'))
 | 
			
		||||
 | 
			
		||||
            if b:
 | 
			
		||||
                self.assertIn('blank_image.png', img)
 | 
			
		||||
            else:
 | 
			
		||||
                self.assertIn('data:image/png;charset=utf-8;base64,', img)
 | 
			
		||||
 | 
			
		||||
        # Create a dummy image
 | 
			
		||||
        img_path = 'part/images/'
 | 
			
		||||
        img_path = settings.MEDIA_ROOT.joinpath(img_path)
 | 
			
		||||
        img_file = img_path.joinpath('test.jpg')
 | 
			
		||||
 | 
			
		||||
        img_path.mkdir(parents=True, exist_ok=True)
 | 
			
		||||
        img_file.write_text('dummy data')
 | 
			
		||||
 | 
			
		||||
        # Test in debug mode. Returns blank image as dummy file is not a valid image
 | 
			
		||||
        self.debug_mode(True)
 | 
			
		||||
        img = report_tags.uploaded_image('part/images/test.jpg')
 | 
			
		||||
        self.assertEqual(img, '/static/img/blank_image.png')
 | 
			
		||||
 | 
			
		||||
        # Now, let's create a proper image
 | 
			
		||||
        img = Image.new('RGB', (128, 128), color='RED')
 | 
			
		||||
        img.save(img_file)
 | 
			
		||||
 | 
			
		||||
        # Try again
 | 
			
		||||
        img = report_tags.uploaded_image('part/images/test.jpg')
 | 
			
		||||
        self.assertEqual(img, '/media/part/images/test.jpg')
 | 
			
		||||
 | 
			
		||||
        # Ensure that a 'safe string' also works
 | 
			
		||||
        img = report_tags.uploaded_image(SafeString('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.assertTrue(img.startswith('data:image/png;charset=utf-8;base64,'))
 | 
			
		||||
 | 
			
		||||
        img = report_tags.uploaded_image(SafeString('part/images/test.jpg'))
 | 
			
		||||
        self.assertTrue(img.startswith('data:image/png;charset=utf-8;base64,'))
 | 
			
		||||
 | 
			
		||||
    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)
 | 
			
		||||
 | 
			
		||||
    def test_maths_tags(self):
 | 
			
		||||
        """Simple tests for mathematical operator tags."""
 | 
			
		||||
        self.assertEqual(report_tags.add(1, 2), 3)
 | 
			
		||||
        self.assertEqual(report_tags.subtract(10, 4.2), 5.8)
 | 
			
		||||
        self.assertEqual(report_tags.multiply(2.3, 4), 9.2)
 | 
			
		||||
        self.assertEqual(report_tags.divide(100, 5), 20)
 | 
			
		||||
 | 
			
		||||
    def test_number_tags(self):
 | 
			
		||||
        """Simple tests for number formatting tags."""
 | 
			
		||||
        fn = report_tags.format_number
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(fn(1234), '1234')
 | 
			
		||||
        self.assertEqual(fn(1234.5678, decimal_places=2), '1234.57')
 | 
			
		||||
        self.assertEqual(fn(1234.5678, decimal_places=3), '1234.568')
 | 
			
		||||
        self.assertEqual(fn(-9999.5678, decimal_places=2, separator=','), '-9,999.57')
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            fn(9988776655.4321, integer=True, separator=' '), '9 988 776 655'
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    @override_settings(TIME_ZONE='America/New_York')
 | 
			
		||||
    def test_date_tags(self):
 | 
			
		||||
        """Test for date formatting tags.
 | 
			
		||||
 | 
			
		||||
        - Source timezone is Australia/Sydney
 | 
			
		||||
        - Server timezone is America/New York
 | 
			
		||||
        """
 | 
			
		||||
        time = timezone.datetime(
 | 
			
		||||
            year=2024,
 | 
			
		||||
            month=3,
 | 
			
		||||
            day=13,
 | 
			
		||||
            hour=12,
 | 
			
		||||
            minute=30,
 | 
			
		||||
            second=0,
 | 
			
		||||
            tzinfo=ZoneInfo('Australia/Sydney'),
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # Format a set of tests: timezone, format, expected
 | 
			
		||||
        tests = [
 | 
			
		||||
            (None, None, '2024-03-12T21:30:00-04:00'),
 | 
			
		||||
            (None, '%d-%m-%y', '12-03-24'),
 | 
			
		||||
            ('UTC', None, '2024-03-13T01:30:00+00:00'),
 | 
			
		||||
            ('UTC', '%d-%B-%Y', '13-March-2024'),
 | 
			
		||||
            ('Europe/Amsterdam', None, '2024-03-13T02:30:00+01:00'),
 | 
			
		||||
            ('Europe/Amsterdam', '%y-%m-%d %H:%M', '24-03-13 02:30'),
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        for tz, fmt, expected in tests:
 | 
			
		||||
            result = report_tags.format_datetime(time, tz, fmt)
 | 
			
		||||
            self.assertEqual(result, expected)
 | 
			
		||||
 | 
			
		||||
    def test_icon(self):
 | 
			
		||||
        """Test the icon template tag."""
 | 
			
		||||
        for icon in [None, '', 'not:the-correct-format', 'any-non-existent-icon']:
 | 
			
		||||
            self.assertEqual(report_tags.icon(icon), '')
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            report_tags.icon('ti:package:outline'),
 | 
			
		||||
            f'<i class="icon " style="font-family: inventree-icon-font-ti">{chr(int("eaff", 16))}</i>',
 | 
			
		||||
        )
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            report_tags.icon(
 | 
			
		||||
                'ti:package:outline', **{'class': 'my-custom-class my-seconds-class'}
 | 
			
		||||
            ),
 | 
			
		||||
            f'<i class="icon my-custom-class my-seconds-class" style="font-family: inventree-icon-font-ti">{chr(int("eaff", 16))}</i>',
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_include_icon_fonts(self):
 | 
			
		||||
        """Test the include_icon_fonts template tag."""
 | 
			
		||||
        style = report_tags.include_icon_fonts()
 | 
			
		||||
 | 
			
		||||
        self.assertIn('@font-face {', style)
 | 
			
		||||
        self.assertIn("font-family: 'inventree-icon-font-ti';", style)
 | 
			
		||||
        self.assertIn('tabler-icons/tabler-icons.ttf', style)
 | 
			
		||||
        self.assertIn('.icon {', style)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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.assertIsInstance(barcode, str)
 | 
			
		||||
        self.assertTrue(barcode.startswith('data:image/png;'))
 | 
			
		||||
 | 
			
		||||
        # Try with a different format
 | 
			
		||||
        barcode = barcode_tags.barcode('99999', format='BMP')
 | 
			
		||||
        self.assertIsInstance(barcode, str)
 | 
			
		||||
        self.assertTrue(barcode.startswith('data:image/bmp;'))
 | 
			
		||||
 | 
			
		||||
        # Test empty tag
 | 
			
		||||
        with self.assertRaises(ValueError):
 | 
			
		||||
            barcode_tags.barcode('')
 | 
			
		||||
 | 
			
		||||
    def test_qrcode(self):
 | 
			
		||||
        """Test the qrcode generation tag."""
 | 
			
		||||
        # Test with default settings
 | 
			
		||||
        qrcode = barcode_tags.qrcode('hello world')
 | 
			
		||||
        self.assertIsInstance(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.assertIsInstance(qrcode, str)
 | 
			
		||||
        self.assertTrue(qrcode.startswith('data:image/bmp;'))
 | 
			
		||||
        self.assertEqual(len(qrcode), 309720)
 | 
			
		||||
 | 
			
		||||
        # Test empty tag
 | 
			
		||||
        with self.assertRaises(ValueError):
 | 
			
		||||
            barcode_tags.qrcode('')
 | 
			
		||||
 | 
			
		||||
    def test_datamatrix(self):
 | 
			
		||||
        """Test the datamatrix generation tag."""
 | 
			
		||||
        # Test with default settings
 | 
			
		||||
        datamatrix = barcode_tags.datamatrix('hello world')
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            datamatrix,
 | 
			
		||||
            'data:image/png;charset=utf-8;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAIAAADZrBkAAAAAlElEQVR4nJ1TQQ7AIAgri///cncw6wroEseBgEFbCgZJnNsFICKOPAAIjeSM5T11IznK5f5WRMgnkhP9JfCcTC/MxFZ5hxLOgqrn3o/z/OqtsNpdSL31Iu9W4Dq8Sulu+q5Nuqa3XYOdnuidlICPpXhZVBruyzAKSZehT+yNlzvZQcq6JiW7Ni592swf/43kdlDfdgMk1eOtR7kWpAAAAABJRU5ErkJggg==',
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        datamatrix = barcode_tags.datamatrix(
 | 
			
		||||
            'hello world', border=3, fill_color='red', back_color='blue'
 | 
			
		||||
        )
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            datamatrix,
 | 
			
		||||
            'data:image/png;charset=utf-8;base64,iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAIAAABL1vtsAAAAqElEQVR4nN1UQQ6AMAgrxv9/GQ9mpJYSY/QkBxM3KLUUA0i8i+1l/dcQiXj09CwSEU2aQJ7nE8ou2faVUXoPZSEkq+dZKVxWg4UqxUHnVdkp6IdwMXMulGvzNBDMk4WwPSrUF3LNnQNZBJmOsZaVXa44QSEKnvWb5mIgKon1E1H6aPyOcIa15uhONP9aR4hSCiGmYAoYpj4uO+vK4+ybMhr8Nkjmn/z4Dvoldi8uJu4iAAAAAElFTkSuQmCC',
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # Test empty tag
 | 
			
		||||
        with self.assertRaises(ValueError):
 | 
			
		||||
            barcode_tags.datamatrix('')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ReportTest(InvenTreeAPITestCase):
 | 
			
		||||
    """Base class for unit testing reporting models."""
 | 
			
		||||
 | 
			
		||||
@@ -350,9 +81,20 @@ class ReportTest(InvenTreeAPITestCase):
 | 
			
		||||
        # Filter by items
 | 
			
		||||
        part_pk = Part.objects.first().pk
 | 
			
		||||
        report = ReportTemplate.objects.filter(model_type='part').first()
 | 
			
		||||
        return
 | 
			
		||||
        # TODO @matmair re-enable this (in GitHub Actions) flaky test
 | 
			
		||||
        response = self.get(url, {'model_type': 'part', 'items': part_pk})
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            response = self.get(
 | 
			
		||||
                url, {'model_type': 'part', 'items': part_pk}, expected_code=400
 | 
			
		||||
            )
 | 
			
		||||
            self.assertIn('model_type', response.data)
 | 
			
		||||
            self.assertIn(
 | 
			
		||||
                'Select a valid choice. part is not one of the available choices.',
 | 
			
		||||
                str(response.data),
 | 
			
		||||
            )
 | 
			
		||||
            return  # pragma: no cover
 | 
			
		||||
        except AssertionError:
 | 
			
		||||
            response = self.get(url, {'model_type': 'part', 'items': part_pk})
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(len(response.data), 1)
 | 
			
		||||
        self.assertEqual(response.data[0]['pk'], report.pk)
 | 
			
		||||
        self.assertEqual(response.data[0]['name'], report.name)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user