Merge remote-tracking branch 'inventree/master'

This commit is contained in:
Oliver Walters 2021-02-09 20:37:48 +11:00
commit fe6c4687d9
7 changed files with 214 additions and 145 deletions

View File

@ -174,6 +174,24 @@ class InvenTreeSetting(models.Model):
'validator': bool, 'validator': bool,
}, },
'REPORT_DEBUG_MODE': {
'name': _('Debug Mode'),
'description': _('Generate reports in debug mode (HTML output)'),
'default': False,
'validator': bool,
},
'REPORT_DEFAULT_PAGE_SIZE': {
'name': _('Page Size'),
'description': _('Default page size for PDF reports'),
'default': 'A4',
'choices': [
('A4', 'A4'),
('Legal', 'Legal'),
('Letter', 'Letter')
],
},
'REPORT_ENABLE_TEST_REPORT': { 'REPORT_ENABLE_TEST_REPORT': {
'name': _('Test Reports'), 'name': _('Test Reports'),
'description': _('Enable generation of test reports'), 'description': _('Enable generation of test reports'),

View File

@ -3,12 +3,14 @@ from __future__ import unicode_literals
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.conf.urls import url, include from django.conf.urls import url, include
from django.http import HttpResponse
from django_filters.rest_framework import DjangoFilterBackend from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import generics, filters from rest_framework import generics, filters
from rest_framework.response import Response from rest_framework.response import Response
import common.models
import InvenTree.helpers import InvenTree.helpers
from stock.models import StockItem from stock.models import StockItem
@ -165,31 +167,53 @@ class StockItemTestReportPrint(generics.RetrieveAPIView, StockItemReportMixin):
outputs = [] outputs = []
# In debug mode, generate single HTML output, rather than PDF
debug_mode = common.models.InvenTreeSetting.get_setting('REPORT_DEBUG_MODE')
# Merge one or more PDF files into a single download # Merge one or more PDF files into a single download
for item in items: for item in items:
report = self.get_object() report = self.get_object()
report.stock_item = item report.stock_item = item
outputs.append(report.render(request)) if debug_mode:
outputs.append(report.render_to_string(request))
else:
outputs.append(report.render(request))
pages = [] if debug_mode:
"""
Contatenate all rendered templates into a single HTML string,
and return the string as a HTML response.
"""
if len(outputs) > 1: html = "\n".join(outputs)
# If more than one output is generated, merge them into a single file
for output in outputs: return HttpResponse(html)
doc = output.get_document()
for page in doc.pages:
pages.append(page)
pdf = outputs[0].get_document().copy(pages).write_pdf()
else: else:
pdf = outputs[0].get_document().write_pdf() """
Concatenate all rendered pages into a single PDF object,
and return the resulting document!
"""
return InvenTree.helpers.DownloadFile( pages = []
pdf,
'test_report.pdf', if len(outputs) > 1:
content_type='application/pdf' # If more than one output is generated, merge them into a single file
) for output in outputs:
doc = output.get_document()
for page in doc.pages:
pages.append(page)
pdf = outputs[0].get_document().copy(pages).write_pdf()
else:
pdf = outputs[0].get_document().write_pdf()
return InvenTree.helpers.DownloadFile(
pdf,
'test_report.pdf',
content_type='application/pdf'
)
report_api_urls = [ report_api_urls = [

View File

@ -14,10 +14,13 @@ import datetime
from django.db import models from django.db import models
from django.conf import settings from django.conf import settings
from django.template.loader import render_to_string
from django.core.files.storage import FileSystemStorage from django.core.files.storage import FileSystemStorage
from django.core.validators import FileExtensionValidator from django.core.validators import FileExtensionValidator
import stock.models import stock.models
import common.models
from InvenTree.helpers import validateFilterString from InvenTree.helpers import validateFilterString
@ -174,6 +177,33 @@ class ReportTemplateBase(ReportBase):
return {} return {}
def context(self, request):
"""
All context to be passed to the renderer.
"""
context = self.get_context_data(request)
context['date'] = datetime.datetime.now().date()
context['datetime'] = datetime.datetime.now()
context['default_page_size'] = common.models.InvenTreeSetting.get_setting('REPORT_DEFAULT_PAGE_SIZE')
context['report_description'] = self.description
context['report_name'] = self.name
context['report_revision'] = self.revision
context['request'] = request
context['user'] = request.user
return context
def render_to_string(self, request, **kwargs):
"""
Render the report to a HTML stiring.
Useful for debug mode (viewing generated code)
"""
return render_to_string(self.template_name, self.context(request), request)
def render(self, request, **kwargs): def render(self, request, **kwargs):
""" """
Render the template to a PDF file. Render the template to a PDF file.
@ -184,18 +214,6 @@ class ReportTemplateBase(ReportBase):
# TODO: Support custom filename generation! # TODO: Support custom filename generation!
# filename = kwargs.get('filename', 'report.pdf') # filename = kwargs.get('filename', 'report.pdf')
context = self.get_context_data(request)
context['media'] = settings.MEDIA_ROOT
context['report_name'] = self.name
context['report_description'] = self.description
context['report_revision'] = self.revision
context['request'] = request
context['user'] = request.user
context['date'] = datetime.datetime.now().date()
context['datetime'] = datetime.datetime.now()
# Render HTML template to PDF # Render HTML template to PDF
wp = WeasyprintReportMixin( wp = WeasyprintReportMixin(
request, request,
@ -205,7 +223,7 @@ class ReportTemplateBase(ReportBase):
**kwargs) **kwargs)
return wp.render_to_response( return wp.render_to_response(
context, self.context(request),
**kwargs) **kwargs)
enabled = models.BooleanField( enabled = models.BooleanField(

View File

@ -3,14 +3,12 @@
<head> <head>
<style> <style>
@page { @page {
{% block page_size %} {% block page_style %}
size: A4; size: {% block page_size %}{{ default_page_size }}{% endblock %};
{% endblock %} margin: {% block page_margin %}2cm{% endblock %};
{% block page_margin %}
margin: 2cm;
{% endblock %}
font-family: Arial, Helvetica, sans-serif; font-family: Arial, Helvetica, sans-serif;
font-size: 75%; font-size: 75%;
{% endblock %}
@top-left { @top-left {
{% block top_left %} {% block top_left %}
@ -45,7 +43,9 @@
} }
body { body {
{% block body_style %}
font-family: Arial, Helvetica, sans-serif; font-family: Arial, Helvetica, sans-serif;
{% endblock %}
} }
.header { .header {

View File

@ -1,112 +1,3 @@
{% extends "report/inventree_report_base.html" %} {% extends "report/inventree_test_report_base.html" %}
{% load i18n %} <!-- Refer to the source code for inventree_test_report.html -->
{% load report %}
{% load inventree_extras %}
{% block style %}
.test-table {
width: 100%;
}
{% block bottom_left %}
content: "{{ date.isoformat }}";
{% endblock %}
{% block bottom_center %}
content: "InvenTree v{% inventree_version %}";
{% endblock %}
{% block top_center %}
content: "{% trans 'Stock Item Test Report' %}";
{% endblock %}
.test-row {
padding: 3px;
}
.test-pass {
color: #5f5;
}
.test-fail {
color: #F55;
}
.container {
padding: 5px;
border: 1px solid;
}
.text-left {
display: inline-block;
width: 50%;
}
.img-right {
display: inline;
align-content: right;
align-items: right;
width: 50%;
}
{% endblock %}
{% block page_content %}
<div class='container'>
<div class='text-left'>
<h2>
{{ part.full_name }}
</h2>
<p>{{ part.description }}</p>
<p><i>{{ stock_item.location }}</i></p>
<p><i>Stock Item ID: {{ stock_item.pk }}</i></p>
</div>
<div class='img-right'>
<img src="{% part_image part %}">
<hr>
<h4>
{% if stock_item.is_serialized %}
{% trans "Serial Number" %}: {{ stock_item.serial }}
{% else %}
{% trans "Quantity" %}: {% decimal stock_item.quantity %}
{% endif %}
</h4>
</div>
</div>
<h3>{% trans "Test Results" %}</h3>
<table class='table test-table'>
<thead>
<tr>
<th>{% trans "Test" %}</th>
<th>{% trans "Result" %}</th>
<th>{% trans "Value" %}</th>
<th>{% trans "User" %}</th>
<th>{% trans "Date" %}</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan='5'><hr></td>
</tr>
{% for test in result_list %}
<tr class='test-row'>
<td>{{ test.test }}</td>
{% if test.result %}
<td class='test-pass'>{% trans "Pass" %}</td>
{% else %}
<td class='test-fail'>{% trans "Fail" %}</td>
{% endif %}
<td>{{ test.value }}</td>
<td>{{ test.user.username }}</td>
<td>{{ test.date.date.isoformat }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

View File

@ -0,0 +1,116 @@
{% extends "report/inventree_report_base.html" %}
{% load i18n %}
{% load report %}
{% load inventree_extras %}
{% block style %}
.test-table {
width: 100%;
}
{% block bottom_left %}
content: "{{ date.isoformat }}";
{% endblock %}
{% block bottom_center %}
content: "InvenTree v{% inventree_version %}";
{% endblock %}
{% block top_center %}
content: "{% trans 'Stock Item Test Report' %}";
{% endblock %}
.test-row {
padding: 3px;
}
.test-pass {
color: #5f5;
}
.test-fail {
color: #F55;
}
.container {
padding: 5px;
border: 1px solid;
}
.text-left {
display: inline-block;
width: 50%;
}
.img-right {
display: inline;
align-content: right;
align-items: right;
width: 50%;
}
.part-img {
height: 4cm;
}
{% endblock %}
{% block page_content %}
<div class='container'>
<div class='text-left'>
<h2>
{{ part.full_name }}
</h2>
<p>{{ part.description }}</p>
<p><i>{{ stock_item.location }}</i></p>
<p><i>Stock Item ID: {{ stock_item.pk }}</i></p>
</div>
<div class='img-right'>
<img class='part-img' src="{% part_image part %}">
<hr>
<h4>
{% if stock_item.is_serialized %}
{% trans "Serial Number" %}: {{ stock_item.serial }}
{% else %}
{% trans "Quantity" %}: {% decimal stock_item.quantity %}
{% endif %}
</h4>
</div>
</div>
<h3>{% trans "Test Results" %}</h3>
<table class='table test-table'>
<thead>
<tr>
<th>{% trans "Test" %}</th>
<th>{% trans "Result" %}</th>
<th>{% trans "Value" %}</th>
<th>{% trans "User" %}</th>
<th>{% trans "Date" %}</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan='5'><hr></td>
</tr>
{% for test in result_list %}
<tr class='test-row'>
<td>{{ test.test }}</td>
{% if test.result %}
<td class='test-pass'>{% trans "Pass" %}</td>
{% else %}
<td class='test-fail'>{% trans "Fail" %}</td>
{% endif %}
<td>{{ test.value }}</td>
<td>{{ test.user.username }}</td>
<td>{{ test.date.date.isoformat }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

View File

@ -15,6 +15,8 @@
<table class='table table-striped table-condensed'> <table class='table table-striped table-condensed'>
{% include "InvenTree/settings/header.html" %} {% include "InvenTree/settings/header.html" %}
<tbody> <tbody>
{% include "InvenTree/settings/setting.html" with key="REPORT_DEFAULT_PAGE_SIZE" %}
{% include "InvenTree/settings/setting.html" with key="REPORT_DEBUG_MODE" %}
{% include "InvenTree/settings/setting.html" with key="REPORT_ENABLE_TEST_REPORT" %} {% include "InvenTree/settings/setting.html" with key="REPORT_ENABLE_TEST_REPORT" %}
</tbody> </tbody>
</table> </table>