diff --git a/InvenTree/label/migrations/0008_auto_20210708_2106.py b/InvenTree/label/migrations/0008_auto_20210708_2106.py new file mode 100644 index 0000000000..ea57526909 --- /dev/null +++ b/InvenTree/label/migrations/0008_auto_20210708_2106.py @@ -0,0 +1,37 @@ +# Generated by Django 3.2.4 on 2021-07-08 11:06 + +import django.core.validators +from django.db import migrations, models +import label.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('label', '0007_auto_20210513_1327'), + ] + + operations = [ + migrations.CreateModel( + name='PartLabel', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('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='Part query filters (comma-separated value of key=value pairs)', max_length=250, validators=[label.models.validate_part_filters], verbose_name='Filters')), + ], + options={ + 'abstract': False, + }, + ), + migrations.AlterField( + model_name='stockitemlabel', + name='filters', + field=models.CharField(blank=True, help_text='Query filters (comma-separated list of key=value pairs),', max_length=250, validators=[label.models.validate_stock_item_filters], verbose_name='Filters'), + ), + ] diff --git a/InvenTree/label/models.py b/InvenTree/label/models.py index 8a6684d7e3..cfa658914d 100644 --- a/InvenTree/label/models.py +++ b/InvenTree/label/models.py @@ -12,6 +12,7 @@ import datetime from django.conf import settings from django.db import models +from django.db.models.fields import Field from django.urls import reverse from django.core.validators import FileExtensionValidator, MinValueValidator from django.core.exceptions import ValidationError, FieldError @@ -25,6 +26,8 @@ from InvenTree.helpers import validateFilterString, normalize import common.models import stock.models +import part.models + try: from django_weasyprint import WeasyTemplateResponseMixin @@ -59,6 +62,13 @@ def validate_stock_location_filters(filters): return filters +def validate_part_filters(filters): + + filters = validateFilterString(filters, model=part.models.Part) + + return filters + + class WeasyprintLabelMixin(WeasyTemplateResponseMixin): """ Class for rendering a label to a PDF @@ -246,10 +256,11 @@ class StockItemLabel(LabelTemplate): filters = models.CharField( blank=True, max_length=250, - help_text=_('Query filters (comma-separated list of key=value pairs'), + help_text=_('Query filters (comma-separated list of key=value pairs),'), verbose_name=_('Filters'), validators=[ - validate_stock_item_filters] + validate_stock_item_filters + ] ) def matches_stock_item(self, item): @@ -335,3 +346,57 @@ class StockLocationLabel(LabelTemplate): 'location': location, 'qr_data': location.format_barcode(brief=True), } + + +class PartLabel(LabelTemplate): + """ + Template for printing Part labels + """ + + @staticmethod + def get_api_url(): + return reverse('api-part-label-list') + + SUBDIR = 'part' + + filters = models.CharField( + blank=True, max_length=250, + help_text=_('Part query filters (comma-separated value of key=value pairs)'), + verbose_name=_('Filters'), + validators=[ + validate_part_filters + ] + ) + + def matches_part(self, part): + """ + Test if this label template matches a given Part object + """ + + try: + filters = validateFilterString(self.filters) + parts = part.models.Part.objects.filter(**filters) + except (ValidationError, FieldError): + return False + + parts = parts.filter(pk=part.pk) + + return parts.exists() + + def get_context_data(self, request): + """ + Generate context data for each provided Part object + """ + + part = self.object_to_print + + return { + 'part': part, + 'category': part.category, + 'name': part.name, + 'description': part.description, + 'IPN': part.IPN, + 'revision': part.revision, + 'qr_data': part.format_barcode(brief=True), + 'qr_url': part.format_barcode(url=True, request=request), + }