2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-07-16 17:56:30 +00:00

Label sheet printer (#5883)

* Add skeleton for new label sheet plugin

* Add custom printing options serializer

* Render individual label outputs to HTML

* Extract page size and column size

* Check label dimensions before printing

* Split labels into multiple pages / sheets

* Render out multiple labels onto a single sheet

* Cleanup base label template

- Allow @page style to *not* be generated
- Pass through as optional context variable
- Check that it still works for single label printing (default behaviour unchanged)
- Prevents multiple @page styles from being generated on label sheet output

* Fix stylesheets for part labels

* Cleanup stock location labels

* Cleanup more label templates

* Check if label can actually fit on page

* Generate output to PDF and return correct response

* Update panel.md

* Fix unit tests

* More unit test fixes
This commit is contained in:
Oliver
2023-11-09 09:00:23 +11:00
committed by GitHub
parent e674ca7437
commit a0b1ba62a9
14 changed files with 380 additions and 35 deletions

View File

@@ -221,6 +221,10 @@ class LabelPrintMixin(LabelFilterMixin):
# Label template
label = self.get_object()
# Check the label dimensions
if label.width <= 0 or label.height <= 0:
raise ValidationError('Label has invalid dimensions')
# if the plugin returns a serializer, validate the data
if serializer := plugin.get_printing_options_serializer(request, data=request.data):
serializer.is_valid(raise_exception=True)

View File

@@ -191,10 +191,38 @@ class LabelTemplate(MetadataMixin, models.Model):
return template_string.render(context)
def context(self, request):
"""Provides context data to the template."""
def generate_page_style(self, **kwargs):
"""Generate @page style for the label template.
This is inserted at the top of the style block for a given label
"""
width = kwargs.get('width', self.width)
height = kwargs.get('height', self.height)
margin = kwargs.get('margin', 0)
return f"""
@page {{
size: {width}mm {height}mm;
margin: {margin}mm;
}}
"""
def context(self, request, **kwargs):
"""Provides context data to the template.
Arguments:
request: The HTTP request object
kwargs: Additional keyword arguments
"""
context = self.get_context_data(request)
# By default, each label is supplied with '@page' data
# However, it can be excluded, e.g. when rendering a label sheet
if kwargs.get('insert_page_style', True):
context['page_style'] = self.generate_page_style()
# Add "basic" context data which gets passed to every label
context['base_url'] = get_base_url(request=request)
context['date'] = datetime.datetime.now().date()
@@ -213,18 +241,31 @@ class LabelTemplate(MetadataMixin, models.Model):
return context
def render_as_string(self, request, **kwargs):
"""Render the label to a HTML string.
def render_as_string(self, request, target_object=None, **kwargs):
"""Render the label to a HTML string"""
Useful for debug mode (viewing generated code)
"""
return render_to_string(self.template_name, self.context(request), request)
if target_object:
self.object_to_print = target_object
def render(self, request, **kwargs):
context = self.context(request, **kwargs)
return render_to_string(
self.template_name,
context,
request
)
def render(self, request, target_object=None, **kwargs):
"""Render the label template to a PDF file.
Uses django-weasyprint plugin to render HTML template
"""
if target_object:
self.object_to_print = target_object
context = self.context(request, **kwargs)
wp = WeasyprintLabelMixin(
request,
self.template_name,
@@ -235,7 +276,7 @@ class LabelTemplate(MetadataMixin, models.Model):
)
return wp.render_to_response(
self.context(request),
context,
**kwargs
)

View File

@@ -16,9 +16,9 @@ Refer to the documentation for a full list of available template variables.
}
.qr {
position: absolute;
height: 28mm;
width: 28mm;
position: relative;
top: 0mm;
right: 0mm;
float: right;

View File

@@ -4,15 +4,18 @@
<head>
<style>
@page {
{% localize off %}
size: {{ width }}mm {{ height }}mm;
{% endlocalize %}
{% block margin %}
margin: 0mm;
{% endblock margin %}
}
{% block page_style %}
{% if page_style %}
/* @page styling */
{% localize off %}
{{ page_style }}
{% endlocalize %}
{% endif %}
{% endblock page_style %}
{% block body_style %}
/* body styling */
body {
font-family: Arial, Helvetica, sans-serif;
margin: 0mm;
@@ -21,20 +24,27 @@
page-break-before: always;
page-break-after: always;
}
{% endblock body_style %}
img {
display: inline-block;
image-rendering: pixelated;
}
/* Global content wrapper div which takes up entire page area */
.content {
width: 100%;
{% localize off %}
width: {{ width }}mm;
height: {{ height }}mm;
{% endlocalize %}
break-after: always;
position: relative;
left: 0mm;
top: 0mm;
}
{% block style %}
/* User-defined styles can go here */
/* User-defined styles can go here, and override any styles defined above */
{% endblock style %}
</style>

View File

@@ -5,7 +5,7 @@
{% block style %}
.qr {
position: fixed;
position: absolute;
left: 0mm;
top: 0mm;
{% localize off %}
@@ -16,7 +16,7 @@
.part {
font-family: Arial, Helvetica, sans-serif;
display: inline;
display: flex;
position: absolute;
{% localize off %}
left: {{ height }}mm;

View File

@@ -5,7 +5,7 @@
{% block style %}
.qr {
position: fixed;
position: absolute;
left: 0mm;
top: 0mm;
{% localize off %}
@@ -16,7 +16,7 @@
.part {
font-family: Arial, Helvetica, sans-serif;
display: inline;
display: flex;
position: absolute;
{% localize off %}
left: {{ height }}mm;

View File

@@ -5,7 +5,7 @@
{% block style %}
.qr {
position: fixed;
position: absolute;
left: 0mm;
top: 0mm;
{% localize off %}

View File

@@ -5,7 +5,7 @@
{% block style %}
.qr {
position: fixed;
position: absolute;
left: 0mm;
top: 0mm;
{% localize off %}
@@ -17,6 +17,5 @@
{% endblock style %}
{% block content %}
<img class='qr' alt="{% trans 'QR Code' %}" src='{% qrcode qr_data %}'>
{% endblock content %}

View File

@@ -5,7 +5,7 @@
{% block style %}
.qr {
position: fixed;
position: absolute;
left: 0mm;
top: 0mm;
{% localize off %}
@@ -16,7 +16,7 @@
.loc {
font-family: Arial, Helvetica, sans-serif;
display: inline;
display: flex;
position: absolute;
{% localize off %}
left: {{ height }}mm;