mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge remote-tracking branch 'inventree/master'
This commit is contained in:
commit
fe6c4687d9
@ -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'),
|
||||||
|
@ -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 = [
|
||||||
|
@ -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(
|
||||||
|
@ -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 {
|
||||||
|
@ -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 %}
|
|
||||||
|
@ -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 %}
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user