diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 4acf4daf9b..037dd724d1 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -2083,6 +2083,16 @@ class Part(InvenTreeBarcodeMixin, MetadataMixin, MPTTModel): return tests + def getTestTemplateMap(self, **kwargs): + """Return a map of all test templates associated with this Part""" + + templates = {} + + for template in self.getTestTemplates(**kwargs): + templates[template.key] = template + + return templates + def getRequiredTests(self): """Return the tests which are required by this part""" return self.getTestTemplates(required=True) diff --git a/InvenTree/report/models.py b/InvenTree/report/models.py index ca9e526727..1beb991fd0 100644 --- a/InvenTree/report/models.py +++ b/InvenTree/report/models.py @@ -307,6 +307,30 @@ class TestReport(ReportTemplateBase): return items.exists() + def get_test_keys(self, stock_item): + """Construct a flattened list of test 'keys' for this StockItem: + + - First, any 'required' tests + - Second, any 'non required' tests + - Finally, any test results which do not match a test + """ + + keys = [] + + for test in stock_item.part.getTestTemplates(required=True): + if test.key not in keys: + keys.append(test.key) + + for test in stock_item.part.getTestTemplates(required=False): + if test.key not in keys: + keys.append(test.key) + + for result in stock_item.testResultList(include_installed=self.include_installed): + if result.key not in keys: + keys.append(result.key) + + return list(keys) + def get_context_data(self, request): """Return custom context data for the TestReport template""" stock_item = self.object_to_print @@ -316,6 +340,9 @@ class TestReport(ReportTemplateBase): 'serial': stock_item.serial, 'part': stock_item.part, 'parameters': stock_item.part.parameters_map(), + 'test_keys': self.get_test_keys(stock_item), + 'test_template_list': stock_item.part.getTestTemplates(), + 'test_template_map': stock_item.part.getTestTemplateMap(), 'results': stock_item.testResultMap(include_installed=self.include_installed), 'result_list': stock_item.testResultList(include_installed=self.include_installed), 'installed_items': stock_item.get_installed_items(cascade=True), diff --git a/InvenTree/report/templates/report/inventree_test_report_base.html b/InvenTree/report/templates/report/inventree_test_report_base.html index 7074cb0ca6..fbb5f34d3f 100644 --- a/InvenTree/report/templates/report/inventree_test_report_base.html +++ b/InvenTree/report/templates/report/inventree_test_report_base.html @@ -33,6 +33,15 @@ content: "{% trans 'Stock Item Test Report' %}"; color: #F55; } +.test-not-found { + color: #33A; +} + +.required-test-not-found { + color: #EEE; + background-color: #F55; +} + .container { padding: 5px; border: 1px solid; @@ -84,7 +93,7 @@ content: "{% trans 'Stock Item Test Report' %}"; -{% if resul_list|length > 0 %} +{% if test_keys|length > 0 %}

{% trans "Test Results" %}

@@ -101,22 +110,44 @@ content: "{% trans 'Stock Item Test Report' %}"; - {% for test in result_list %} + {% for key in test_keys %} + + {% getkey test_template_map key as test_template %} + {% getkey results key as test_result %} - - {% if test.result %} + + {% if test_result %} + {% if test_result.result %} {% else %} {% endif %} - - - + + + + {% else %} + {% if test_template.required %} + + {% else %} + + {% endif %} + {% endif %} {% endfor %}

{{ test.test }} + {% if test_template %} + {% render_html_text test_template.test_name bold=test_template.required %} + {% elif test_result %} + {% render_html_text test_result.test italic=True %} + {% else %} + + {{ key }} + {% endif %} + {% trans "Pass" %}{% trans "Fail" %}{{ test.value }}{{ test.user.username }}{{ test.date.date.isoformat }}{{ test_result.value }}{{ test_result.user.username }}{{ test_result.date.date.isoformat }}{% trans "No result (required)" %}{% trans "No result" %}
+{% else %} +No tests defined for this stock item {% endif %} {% if installed_items|length > 0 %} diff --git a/InvenTree/report/templatetags/report.py b/InvenTree/report/templatetags/report.py index 474cb8fa02..c7f3365c04 100644 --- a/InvenTree/report/templatetags/report.py +++ b/InvenTree/report/templatetags/report.py @@ -19,17 +19,52 @@ logger = logging.getLogger('inventree') @register.simple_tag() -def getkey(value: dict, arg): +def getindex(container: list, index: int): + """Return the value contained at the specified index of the list. + + This function is provideed to get around template rendering limitations. + + Arguments: + container: A python list object + index: The index to retrieve from the list + """ + + # Index *must* be an integer + try: + index = int(index) + except ValueError: + return None + + if index < 0 or index >= len(container): + return None + + try: + value = container[index] + except IndexError: + value = None + + return value + + +@register.simple_tag() +def getkey(container: dict, key): """Perform key lookup in the provided dict object. This function is provided to get around template rendering limitations. Ref: https://stackoverflow.com/questions/1906129/dict-keys-with-spaces-in-django-templates Arguments: - value: A python dict object - arg: The 'key' to be found within the dict + container: A python dict object + key: The 'key' to be found within the dict """ - return value[arg] + if type(container) is not dict: + logger.warning("getkey() called with non-dict object") + return None + + if key in container: + return container[key] + else: + return None @register.simple_tag() @@ -215,3 +250,31 @@ def render_currency(money, **kwargs): """Render a currency / Money object""" return InvenTree.helpers.render_currency(money, **kwargs) + + +@register.simple_tag +def render_html_text(text: str, **kwargs): + """Render a text item with some simple html tags. + + kwargs: + bold: Boolean, whether bold (or not) + italic: Boolean, whether italic (or not) + heading: str, heading level e.g. 'h3' + """ + + tags = [] + + if kwargs.get('bold', False): + tags.append('strong') + + if kwargs.get('italic', False): + tags.append('em') + + if heading := kwargs.get('heading', ''): + tags.append(heading) + + output = ''.join([f'<{tag}>' for tag in tags]) + output += text + output += ''.join([f'' for tag in tags]) + + return mark_safe(output)