2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-06-17 20:45:44 +00:00

Build line labels (#5034)

* Adds BuildLineLabel model

- New type of label for printing against BuildLine objects

* Add serializer for new model

* Add API endpoints for new label type

* Add hooks to BuildLine table

* Create default label

- Create an example BuildLineLabel object

* Add admin integration

* Fix js code

* Use two-tiered template

- Allows base template to be updated

* Improve default label

* Add docs pages for labels

* Update nav

* Documentation for new label

* Add permission role

* Bump API version
This commit is contained in:
Oliver
2023-06-14 13:07:18 +10:00
committed by GitHub
parent a3940cfc41
commit 8d16abcefb
19 changed files with 515 additions and 142 deletions

View File

@ -2,11 +2,14 @@
# InvenTree API version
INVENTREE_API_VERSION = 121
INVENTREE_API_VERSION = 122
"""
Increment this API version number whenever there is a significant change to the API that any clients need to know about
v122 -> 2023-06-14 : https://github.com/inventree/InvenTree/pull/5034
- Adds new BuildLineLabel label type
v121 -> 2023-06-14 : https://github.com/inventree/InvenTree/pull/4808
- Adds "ProjectCode" link to Build model
@ -14,9 +17,6 @@ v120 -> 2023-06-07 : https://github.com/inventree/InvenTree/pull/4855
- Major overhaul of the build order API
- Adds new BuildLine model
v120 -> 2023-06-12 : https://github.com/inventree/InvenTree/pull/4804
- Adds 'project_code' field to build order API endpoints
v119 -> 2023-06-01 : https://github.com/inventree/InvenTree/pull/4898
- Add Metadata to: Part test templates, Part parameters, Part category parameter templates, BOM item substitute, Related Parts, Stock item test result

View File

@ -13,3 +13,4 @@ class LabelAdmin(admin.ModelAdmin):
admin.site.register(label.models.StockItemLabel, LabelAdmin)
admin.site.register(label.models.StockLocationLabel, LabelAdmin)
admin.site.register(label.models.PartLabel, LabelAdmin)
admin.site.register(label.models.BuildLineLabel, LabelAdmin)

View File

@ -10,6 +10,7 @@ from django.views.decorators.cache import cache_page, never_cache
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.exceptions import NotFound
import build.models
import common.models
import InvenTree.helpers
import label.models
@ -368,6 +369,31 @@ class PartLabelPrint(PartLabelMixin, LabelPrintMixin, RetrieveAPI):
pass
class BuildLineLabelMixin:
"""Mixin class for BuildLineLabel endpoints"""
queryset = label.models.BuildLineLabel.objects.all()
serializer_class = label.serializers.BuildLineLabelSerializer
ITEM_MODEL = build.models.BuildLine
ITEM_KEY = 'line'
class BuildLineLabelList(BuildLineLabelMixin, LabelListView):
"""API endpoint for viewing a list of BuildLineLabel objects"""
pass
class BuildLineLabelDetail(BuildLineLabelMixin, RetrieveUpdateDestroyAPI):
"""API endpoint for a single BuildLineLabel object"""
pass
class BuildLineLabelPrint(BuildLineLabelMixin, LabelPrintMixin, RetrieveAPI):
"""API endpoint for printing a BuildLineLabel object"""
pass
label_api_urls = [
# Stock item labels
@ -408,4 +434,17 @@ label_api_urls = [
# List view
re_path(r'^.*$', PartLabelList.as_view(), name='api-part-label-list'),
])),
# BuildLine labels
re_path(r'^buildline/', include([
# Detail views
path(r'<int:pk>/', include([
re_path(r'^print/', BuildLineLabelPrint.as_view(), name='api-buildline-label-print'),
re_path(r'^metadata/', MetadataView.as_view(), {'model': label.models.BuildLineLabel}, name='api-buildline-label-metadata'),
re_path(r'^.*$', BuildLineLabelDetail.as_view(), name='api-buildline-label-detail'),
])),
# List view
re_path(r'^.*$', BuildLineLabelList.as_view(), name='api-buildline-label-list'),
])),
]

View File

@ -46,12 +46,12 @@ class LabelConfig(AppConfig):
def create_labels(self):
"""Create all default templates."""
# Test if models are ready
from .models import PartLabel, StockItemLabel, StockLocationLabel
assert bool(StockLocationLabel is not None)
import label.models
assert bool(label.models.StockLocationLabel is not None)
# Create the categories
self.create_labels_category(
StockItemLabel,
label.models.StockItemLabel,
'stockitem',
[
{
@ -65,7 +65,7 @@ class LabelConfig(AppConfig):
)
self.create_labels_category(
StockLocationLabel,
label.models.StockLocationLabel,
'stocklocation',
[
{
@ -86,7 +86,7 @@ class LabelConfig(AppConfig):
)
self.create_labels_category(
PartLabel,
label.models.PartLabel,
'part',
[
{
@ -106,6 +106,20 @@ class LabelConfig(AppConfig):
]
)
self.create_labels_category(
label.models.BuildLineLabel,
'buildline',
[
{
'file': 'buildline_label.html',
'name': 'Build Line Label',
'description': 'Example build line label',
'width': 125,
'height': 48,
},
]
)
def create_labels_category(self, model, ref_name, labels):
"""Create folder and database entries for the default templates, if they do not already exist."""
# Create root dir for templates

View File

@ -0,0 +1,33 @@
# Generated by Django 3.2.19 on 2023-06-13 11:10
import django.core.validators
from django.db import migrations, models
import label.models
class Migration(migrations.Migration):
dependencies = [
('label', '0009_auto_20230317_0816'),
]
operations = [
migrations.CreateModel(
name='BuildLineLabel',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('metadata', models.JSONField(blank=True, help_text='JSON metadata field, for use by external plugins', null=True, verbose_name='Plugin Metadata')),
('name', models.CharField(help_text='Label name', max_length=100, verbose_name='Name')),
('description', models.CharField(blank=True, help_text='Label description', max_length=250, null=True, verbose_name='Description')),
('label', models.FileField(help_text='Label template file', unique=True, upload_to=label.models.rename_label, validators=[django.core.validators.FileExtensionValidator(allowed_extensions=['html'])], verbose_name='Label')),
('enabled', models.BooleanField(default=True, help_text='Label template is enabled', verbose_name='Enabled')),
('width', models.FloatField(default=50, help_text='Label width, specified in mm', validators=[django.core.validators.MinValueValidator(2)], verbose_name='Width [mm]')),
('height', models.FloatField(default=20, help_text='Label height, specified in mm', validators=[django.core.validators.MinValueValidator(2)], verbose_name='Height [mm]')),
('filename_pattern', models.CharField(default='label.pdf', help_text='Pattern for generating label filenames', max_length=100, verbose_name='Filename Pattern')),
('filters', models.CharField(blank=True, help_text='Query filters (comma-separated list of key=value pairs)', max_length=250, validators=[label.models.validate_build_line_filters], verbose_name='Filters')),
],
options={
'abstract': False,
},
),
]

View File

@ -13,6 +13,7 @@ from django.template.loader import render_to_string
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
import build.models
import part.models
import stock.models
from InvenTree.helpers import normalize, validateFilterString
@ -59,6 +60,13 @@ def validate_part_filters(filters):
return filters
def validate_build_line_filters(filters):
"""Validate query filters for the BuildLine model"""
filters = validateFilterString(filters, model=build.models.BuildLine)
return filters
class WeasyprintLabelMixin(WeasyTemplateResponseMixin):
"""Class for rendering a label to a PDF."""
@ -330,3 +338,38 @@ class PartLabel(LabelTemplate):
'qr_url': request.build_absolute_uri(part.get_absolute_url()),
'parameters': part.parameters_map(),
}
class BuildLineLabel(LabelTemplate):
"""Template for printing labels against BuildLine objects"""
@staticmethod
def get_api_url():
"""Return the API URL associated with the BuildLineLabel model"""
return reverse('api-buildline-label-list')
SUBDIR = 'buildline'
filters = models.CharField(
blank=True, max_length=250,
help_text=_('Query filters (comma-separated list of key=value pairs)'),
verbose_name=_('Filters'),
validators=[
validate_build_line_filters
]
)
def get_context_data(self, request):
"""Generate context data for each provided BuildLine object."""
build_line = self.object_to_print
return {
'build_line': build_line,
'build': build_line.build,
'bom_item': build_line.bom_item,
'part': build_line.bom_item.sub_part,
'quantity': build_line.quantity,
'allocated_quantity': build_line.allocated_quantity,
'allocations': build_line.allocations,
}

View File

@ -52,3 +52,13 @@ class PartLabelSerializer(LabelSerializerBase):
model = label.models.PartLabel
fields = LabelSerializerBase.label_fields()
class BuildLineLabelSerializer(LabelSerializerBase):
"""Serializes a BuildLineLabel object"""
class Meta:
"""Metaclass options."""
model = label.models.BuildLineLabel
fields = LabelSerializerBase.label_fields()

View File

@ -0,0 +1,3 @@
{% extends "label/buildline/buildline_label_base.html" %}
<!-- Refer to the buildline_label_base template for further information -->

View File

@ -0,0 +1,74 @@
{% extends "label/label_base.html" %}
{% load barcode report %}
{% load inventree_extras %}
<!--
This is an example template for printing labels against BuildLine objects.
Refer to the documentation for a full list of available template variables.
-->
{% block style %}
{{ block.super }}
.label {
margin: 1mm;
}
.qr {
height: 28mm;
width: 28mm;
position: relative;
top: 0mm;
right: 0mm;
float: right;
}
.label-table {
width: 100%;
border-collapse: collapse;
border: 1pt solid black;
}
.label-table tr {
width: 100%;
border-bottom: 1pt solid black;
padding: 2.5mm;
}
.label-table td {
padding: 3mm;
}
{% endblock style %}
{% block content %}
<div class='label'>
<table class='label-table'>
<tr>
<td>
<b>Build Order:</b> {{ build.reference }}<br>
<b>Build Qty:</b> {% decimal build.quantity %}<br>
</td>
<td>
<img class='qr' alt='build qr' src='{% qrcode build.barcode %}'>
</td>
</tr>
<tr>
<td>
<b>Part:</b> {{ part.name }}<br>
{% if part.IPN %}
<b>IPN:</b> {{ part.IPN }}<br>
{% endif %}
<b>Qty / Unit:</b> {% decimal bom_item.quantity %} {% if part.units %}[{{ part.units }}]{% endif %}<br>
<b>Qty Total:</b> {% decimal quantity %} {% if part.units %}[{{ part.units }}]{% endif %}
</td>
<td>
<img class='qr' alt='part qr' src='{% qrcode part.barcode %}'>
</td>
</tr>
</table>
</div>
{% endblock content %}

View File

@ -2349,8 +2349,17 @@ function loadBuildLineTable(table, build_id, options={}) {
let filters = loadTableFilters('buildlines', params);
let filterTarget = options.filterTarget || '#filter-list-buildlines';
setupFilterList('buildlines', $(table), filterTarget);
// If data is passed directly to this function, do not request data from the server
// If data is passed directly to this function, do not setup filters
if (!options.data) {
setupFilterList('buildlines', $(table), filterTarget, {
labels: {
url: '{% url "api-buildline-label-list" %}',
key: 'line',
},
singular_name: '{% trans "build line" %}',
plural_name: '{% trans "build lines" %}',
});
}
let table_options = {
name: name,

View File

@ -136,6 +136,7 @@ class RuleSet(models.Model):
'stock_stockitem',
'stock_stocklocation',
'report_buildreport',
'label_buildlinelabel',
],
'purchase_order': [
'company_company',