mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-31 13:15:43 +00:00 
			
		
		
		
	| @@ -3,7 +3,7 @@ from __future__ import unicode_literals | |||||||
|  |  | ||||||
| from django.contrib import admin | from django.contrib import admin | ||||||
|  |  | ||||||
| from .models import StockItemLabel, StockLocationLabel | from .models import StockItemLabel, StockLocationLabel, PartLabel | ||||||
|  |  | ||||||
|  |  | ||||||
| class LabelAdmin(admin.ModelAdmin): | class LabelAdmin(admin.ModelAdmin): | ||||||
| @@ -13,3 +13,4 @@ class LabelAdmin(admin.ModelAdmin): | |||||||
|  |  | ||||||
| admin.site.register(StockItemLabel, LabelAdmin) | admin.site.register(StockItemLabel, LabelAdmin) | ||||||
| admin.site.register(StockLocationLabel, LabelAdmin) | admin.site.register(StockLocationLabel, LabelAdmin) | ||||||
|  | admin.site.register(PartLabel, LabelAdmin) | ||||||
|   | |||||||
| @@ -15,9 +15,10 @@ import InvenTree.helpers | |||||||
| import common.models | import common.models | ||||||
|  |  | ||||||
| from stock.models import StockItem, StockLocation | from stock.models import StockItem, StockLocation | ||||||
|  | from part.models import Part | ||||||
|  |  | ||||||
| from .models import StockItemLabel, StockLocationLabel | from .models import StockItemLabel, StockLocationLabel, PartLabel | ||||||
| from .serializers import StockItemLabelSerializer, StockLocationLabelSerializer | from .serializers import StockItemLabelSerializer, StockLocationLabelSerializer, PartLabelSerializer | ||||||
|  |  | ||||||
|  |  | ||||||
| class LabelListView(generics.ListAPIView): | class LabelListView(generics.ListAPIView): | ||||||
| @@ -132,6 +133,7 @@ class StockItemLabelMixin: | |||||||
|         for key in ['item', 'item[]', 'items', 'items[]']: |         for key in ['item', 'item[]', 'items', 'items[]']: | ||||||
|             if key in params: |             if key in params: | ||||||
|                 items = params.getlist(key, []) |                 items = params.getlist(key, []) | ||||||
|  |                 break | ||||||
|  |  | ||||||
|         valid_ids = [] |         valid_ids = [] | ||||||
|  |  | ||||||
| @@ -376,6 +378,112 @@ class StockLocationLabelPrint(generics.RetrieveAPIView, StockLocationLabelMixin, | |||||||
|         return self.print(request, locations) |         return self.print(request, locations) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PartLabelMixin: | ||||||
|  |     """ | ||||||
|  |     Mixin for extracting Part objects from query parameters | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     def get_parts(self): | ||||||
|  |         """ | ||||||
|  |         Return a list of requested Part objects | ||||||
|  |         """ | ||||||
|  |  | ||||||
|  |         parts = [] | ||||||
|  |  | ||||||
|  |         params = self.request.query_params | ||||||
|  |  | ||||||
|  |         for key in ['part', 'part[]', 'parts', 'parts[]']: | ||||||
|  |             if key in params: | ||||||
|  |                 parts = params.getlist(key, []) | ||||||
|  |                 break | ||||||
|  |                  | ||||||
|  |         valid_ids = [] | ||||||
|  |  | ||||||
|  |         for part in parts: | ||||||
|  |             try: | ||||||
|  |                 valid_ids.append(int(part)) | ||||||
|  |             except (ValueError): | ||||||
|  |                 pass | ||||||
|  |  | ||||||
|  |         # List of Part objects which match provided values | ||||||
|  |         return Part.objects.filter(pk__in=valid_ids) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PartLabelList(LabelListView, PartLabelMixin): | ||||||
|  |     """ | ||||||
|  |     API endpoint for viewing list of PartLabel objects | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     queryset = PartLabel.objects.all() | ||||||
|  |     serializer_class = PartLabelSerializer | ||||||
|  |  | ||||||
|  |     def filter_queryset(self, queryset): | ||||||
|  |  | ||||||
|  |         queryset = super().filter_queryset(queryset) | ||||||
|  |  | ||||||
|  |         parts = self.get_parts() | ||||||
|  |  | ||||||
|  |         if len(parts) > 0: | ||||||
|  |  | ||||||
|  |             valid_label_ids = set() | ||||||
|  |  | ||||||
|  |             for label in queryset.all(): | ||||||
|  |  | ||||||
|  |                 matches = True | ||||||
|  |  | ||||||
|  |                 try: | ||||||
|  |                     filters = InvenTree.helpers.validateFilterString(label.filters) | ||||||
|  |                 except ValidationError: | ||||||
|  |                     continue | ||||||
|  |  | ||||||
|  |                 for part in parts: | ||||||
|  |  | ||||||
|  |                     part_query = Part.objects.filter(pk=part.pk) | ||||||
|  |  | ||||||
|  |                     try: | ||||||
|  |                         if not part_query.filter(**filters).exists(): | ||||||
|  |                             matches = False | ||||||
|  |                             break | ||||||
|  |                     except FieldError: | ||||||
|  |                         matches = False | ||||||
|  |                         break | ||||||
|  |  | ||||||
|  |                 if matches: | ||||||
|  |                     valid_label_ids.add(label.pk) | ||||||
|  |  | ||||||
|  |             # Reduce queryset to only valid matches | ||||||
|  |             queryset = queryset.filter(pk__in=[pk for pk in valid_label_ids]) | ||||||
|  |  | ||||||
|  |         return queryset | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PartLabelDetail(generics.RetrieveUpdateDestroyAPIView): | ||||||
|  |     """ | ||||||
|  |     API endpoint for a single PartLabel object | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     queryset = PartLabel.objects.all() | ||||||
|  |     serializer_class = PartLabelSerializer | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PartLabelPrint(generics.RetrieveAPIView, PartLabelMixin, LabelPrintMixin): | ||||||
|  |     """ | ||||||
|  |     API endpoint for printing a PartLabel object | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     queryset = PartLabel.objects.all() | ||||||
|  |     serializer_class = PartLabelSerializer | ||||||
|  |  | ||||||
|  |     def get(self, request, *args, **kwargs): | ||||||
|  |         """ | ||||||
|  |         Check if valid part(s) have been provided | ||||||
|  |         """ | ||||||
|  |  | ||||||
|  |         parts = self.get_parts() | ||||||
|  |  | ||||||
|  |         return self.print(request, parts) | ||||||
|  |  | ||||||
|  |  | ||||||
| label_api_urls = [ | label_api_urls = [ | ||||||
|  |  | ||||||
|     # Stock item labels |     # Stock item labels | ||||||
| @@ -401,4 +509,16 @@ label_api_urls = [ | |||||||
|         # List view |         # List view | ||||||
|         url(r'^.*$', StockLocationLabelList.as_view(), name='api-stocklocation-label-list'), |         url(r'^.*$', StockLocationLabelList.as_view(), name='api-stocklocation-label-list'), | ||||||
|     ])), |     ])), | ||||||
|  |  | ||||||
|  |     # Part labels | ||||||
|  |     url(r'^part/', include([ | ||||||
|  |         # Detail views | ||||||
|  |         url(r'^(?P<pk>\d+)/', include([ | ||||||
|  |             url(r'^print/', PartLabelPrint.as_view(), name='api-part-label-print'), | ||||||
|  |             url(r'^.*$', PartLabelDetail.as_view(), name='api-part-label-detail'), | ||||||
|  |         ])), | ||||||
|  |  | ||||||
|  |         # List view | ||||||
|  |         url(r'^.*$', PartLabelList.as_view(), name='api-part-label-list'), | ||||||
|  |     ])), | ||||||
| ] | ] | ||||||
|   | |||||||
| @@ -37,6 +37,7 @@ class LabelConfig(AppConfig): | |||||||
|         if canAppAccessDatabase(): |         if canAppAccessDatabase(): | ||||||
|             self.create_stock_item_labels() |             self.create_stock_item_labels() | ||||||
|             self.create_stock_location_labels() |             self.create_stock_location_labels() | ||||||
|  |             self.create_part_labels() | ||||||
|  |  | ||||||
|     def create_stock_item_labels(self): |     def create_stock_item_labels(self): | ||||||
|         """ |         """ | ||||||
| @@ -65,7 +66,7 @@ class LabelConfig(AppConfig): | |||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         if not os.path.exists(dst_dir): |         if not os.path.exists(dst_dir): | ||||||
|             logger.info(f"Creating missing directory: '{dst_dir}'") |             logger.info(f"Creating required directory: '{dst_dir}'") | ||||||
|             os.makedirs(dst_dir, exist_ok=True) |             os.makedirs(dst_dir, exist_ok=True) | ||||||
|  |  | ||||||
|         labels = [ |         labels = [ | ||||||
| @@ -109,7 +110,6 @@ class LabelConfig(AppConfig): | |||||||
|                 logger.info(f"Copying label template '{dst_file}'") |                 logger.info(f"Copying label template '{dst_file}'") | ||||||
|                 shutil.copyfile(src_file, dst_file) |                 shutil.copyfile(src_file, dst_file) | ||||||
|  |  | ||||||
|             try: |  | ||||||
|             # Check if a label matching the template already exists |             # Check if a label matching the template already exists | ||||||
|             if StockItemLabel.objects.filter(label=filename).exists(): |             if StockItemLabel.objects.filter(label=filename).exists(): | ||||||
|                 continue |                 continue | ||||||
| @@ -125,8 +125,6 @@ class LabelConfig(AppConfig): | |||||||
|                 width=label['width'], |                 width=label['width'], | ||||||
|                 height=label['height'], |                 height=label['height'], | ||||||
|             ) |             ) | ||||||
|             except: |  | ||||||
|                 pass |  | ||||||
|  |  | ||||||
|     def create_stock_location_labels(self): |     def create_stock_location_labels(self): | ||||||
|         """ |         """ | ||||||
| @@ -155,7 +153,7 @@ class LabelConfig(AppConfig): | |||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         if not os.path.exists(dst_dir): |         if not os.path.exists(dst_dir): | ||||||
|             logger.info(f"Creating missing directory: '{dst_dir}'") |             logger.info(f"Creating required directory: '{dst_dir}'") | ||||||
|             os.makedirs(dst_dir, exist_ok=True) |             os.makedirs(dst_dir, exist_ok=True) | ||||||
|  |  | ||||||
|         labels = [ |         labels = [ | ||||||
| @@ -206,7 +204,6 @@ class LabelConfig(AppConfig): | |||||||
|                 logger.info(f"Copying label template '{dst_file}'") |                 logger.info(f"Copying label template '{dst_file}'") | ||||||
|                 shutil.copyfile(src_file, dst_file) |                 shutil.copyfile(src_file, dst_file) | ||||||
|  |  | ||||||
|             try: |  | ||||||
|             # Check if a label matching the template already exists |             # Check if a label matching the template already exists | ||||||
|             if StockLocationLabel.objects.filter(label=filename).exists(): |             if StockLocationLabel.objects.filter(label=filename).exists(): | ||||||
|                 continue |                 continue | ||||||
| @@ -222,5 +219,88 @@ class LabelConfig(AppConfig): | |||||||
|                 width=label['width'], |                 width=label['width'], | ||||||
|                 height=label['height'], |                 height=label['height'], | ||||||
|             ) |             ) | ||||||
|  |  | ||||||
|  |     def create_part_labels(self): | ||||||
|  |         """ | ||||||
|  |         Create database entries for the default PartLabel templates, | ||||||
|  |         if they do not already exist. | ||||||
|  |         """ | ||||||
|  |  | ||||||
|  |         try: | ||||||
|  |             from .models import PartLabel | ||||||
|         except: |         except: | ||||||
|                 pass |             # Database might not yet be ready | ||||||
|  |             return | ||||||
|  |  | ||||||
|  |         src_dir = os.path.join( | ||||||
|  |             os.path.dirname(os.path.realpath(__file__)), | ||||||
|  |             'templates', | ||||||
|  |             'label', | ||||||
|  |             'part', | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         dst_dir = os.path.join( | ||||||
|  |             settings.MEDIA_ROOT, | ||||||
|  |             'label', | ||||||
|  |             'inventree', | ||||||
|  |             'part', | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         if not os.path.exists(dst_dir): | ||||||
|  |             logger.info(f"Creating required directory: '{dst_dir}'") | ||||||
|  |             os.makedirs(dst_dir, exist_ok=True) | ||||||
|  |  | ||||||
|  |         labels = [ | ||||||
|  |             { | ||||||
|  |                 'file': 'part_label.html', | ||||||
|  |                 'name': 'Part Label', | ||||||
|  |                 'description': 'Simple part label', | ||||||
|  |                 'width': 70, | ||||||
|  |                 'height': 24, | ||||||
|  |             }, | ||||||
|  |         ] | ||||||
|  |  | ||||||
|  |         for label in labels: | ||||||
|  |  | ||||||
|  |             filename = os.path.join( | ||||||
|  |                 'label', | ||||||
|  |                 'inventree', | ||||||
|  |                 'part', | ||||||
|  |                 label['file'] | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |             src_file = os.path.join(src_dir, label['file']) | ||||||
|  |             dst_file = os.path.join(settings.MEDIA_ROOT, filename) | ||||||
|  |  | ||||||
|  |             to_copy = False | ||||||
|  |  | ||||||
|  |             if os.path.exists(dst_file): | ||||||
|  |                 # File already exists - let's see if it is the "same" | ||||||
|  |  | ||||||
|  |                 if not hashFile(dst_file) == hashFile(src_file): | ||||||
|  |                     logger.info(f"Hash differs for '{filename}'") | ||||||
|  |                     to_copy = True | ||||||
|  |  | ||||||
|  |             else: | ||||||
|  |                 logger.info(f"Label template '{filename}' is not present") | ||||||
|  |                 to_copy = True | ||||||
|  |  | ||||||
|  |             if to_copy: | ||||||
|  |                 logger.info(f"Copying label template '{dst_file}'") | ||||||
|  |                 shutil.copyfile(src_file, dst_file) | ||||||
|  |  | ||||||
|  |             # Check if a label matching the template already exists | ||||||
|  |             if PartLabel.objects.filter(label=filename).exists(): | ||||||
|  |                 continue | ||||||
|  |  | ||||||
|  |             logger.info(f"Creating entry for PartLabel '{label['name']}'") | ||||||
|  |  | ||||||
|  |             PartLabel.objects.create( | ||||||
|  |                 name=label['name'], | ||||||
|  |                 description=label['description'], | ||||||
|  |                 label=filename, | ||||||
|  |                 filters='', | ||||||
|  |                 enabled=True, | ||||||
|  |                 width=label['width'], | ||||||
|  |                 height=label['height'], | ||||||
|  |             ) | ||||||
|   | |||||||
							
								
								
									
										37
									
								
								InvenTree/label/migrations/0008_auto_20210708_2106.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								InvenTree/label/migrations/0008_auto_20210708_2106.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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'), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
| @@ -25,6 +25,8 @@ from InvenTree.helpers import validateFilterString, normalize | |||||||
|  |  | ||||||
| import common.models | import common.models | ||||||
| import stock.models | import stock.models | ||||||
|  | import part.models | ||||||
|  |  | ||||||
|  |  | ||||||
| try: | try: | ||||||
|     from django_weasyprint import WeasyTemplateResponseMixin |     from django_weasyprint import WeasyTemplateResponseMixin | ||||||
| @@ -59,6 +61,13 @@ def validate_stock_location_filters(filters): | |||||||
|     return filters |     return filters | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def validate_part_filters(filters): | ||||||
|  |  | ||||||
|  |     filters = validateFilterString(filters, model=part.models.Part) | ||||||
|  |  | ||||||
|  |     return filters | ||||||
|  |  | ||||||
|  |  | ||||||
| class WeasyprintLabelMixin(WeasyTemplateResponseMixin): | class WeasyprintLabelMixin(WeasyTemplateResponseMixin): | ||||||
|     """ |     """ | ||||||
|     Class for rendering a label to a PDF |     Class for rendering a label to a PDF | ||||||
| @@ -246,10 +255,11 @@ class StockItemLabel(LabelTemplate): | |||||||
|  |  | ||||||
|     filters = models.CharField( |     filters = models.CharField( | ||||||
|         blank=True, max_length=250, |         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'), |         verbose_name=_('Filters'), | ||||||
|         validators=[ |         validators=[ | ||||||
|             validate_stock_item_filters] |             validate_stock_item_filters | ||||||
|  |         ] | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     def matches_stock_item(self, item): |     def matches_stock_item(self, item): | ||||||
| @@ -335,3 +345,57 @@ class StockLocationLabel(LabelTemplate): | |||||||
|             'location': location, |             'location': location, | ||||||
|             'qr_data': location.format_barcode(brief=True), |             '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), | ||||||
|  |         } | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ from __future__ import unicode_literals | |||||||
| from InvenTree.serializers import InvenTreeModelSerializer | from InvenTree.serializers import InvenTreeModelSerializer | ||||||
| from InvenTree.serializers import InvenTreeAttachmentSerializerField | from InvenTree.serializers import InvenTreeAttachmentSerializerField | ||||||
|  |  | ||||||
| from .models import StockItemLabel, StockLocationLabel | from .models import StockItemLabel, StockLocationLabel, PartLabel | ||||||
|  |  | ||||||
|  |  | ||||||
| class StockItemLabelSerializer(InvenTreeModelSerializer): | class StockItemLabelSerializer(InvenTreeModelSerializer): | ||||||
| @@ -43,3 +43,22 @@ class StockLocationLabelSerializer(InvenTreeModelSerializer): | |||||||
|             'filters', |             'filters', | ||||||
|             'enabled', |             'enabled', | ||||||
|         ] |         ] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PartLabelSerializer(InvenTreeModelSerializer): | ||||||
|  |     """ | ||||||
|  |     Serializes a PartLabel object | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     label = InvenTreeAttachmentSerializerField(required=True) | ||||||
|  |  | ||||||
|  |     class Meta: | ||||||
|  |         model = PartLabel | ||||||
|  |         fields = [ | ||||||
|  |             'pk', | ||||||
|  |             'name', | ||||||
|  |             'description', | ||||||
|  |             'label', | ||||||
|  |             'filters', | ||||||
|  |             'enabled', | ||||||
|  |         ] | ||||||
|   | |||||||
							
								
								
									
										33
									
								
								InvenTree/label/templates/label/part/part_label.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								InvenTree/label/templates/label/part/part_label.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | |||||||
|  | {% extends "label/label_base.html" %} | ||||||
|  |  | ||||||
|  | {% load barcode %} | ||||||
|  |  | ||||||
|  | {% block style %} | ||||||
|  |  | ||||||
|  | .qr { | ||||||
|  |     position: fixed; | ||||||
|  |     left: 0mm; | ||||||
|  |     top: 0mm; | ||||||
|  |     height: {{ height }}mm; | ||||||
|  |     width: {{ height }}mm; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .part { | ||||||
|  |     font-family: Arial, Helvetica, sans-serif; | ||||||
|  |     display: inline; | ||||||
|  |     position: absolute; | ||||||
|  |     left: {{ height }}mm; | ||||||
|  |     top: 2mm; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block content %} | ||||||
|  |  | ||||||
|  | <img class='qr' src='{% qrcode qr_data %}'> | ||||||
|  |  | ||||||
|  | <div class='part'> | ||||||
|  |     {{ part.full_name }} | ||||||
|  | </div> | ||||||
|  |  | ||||||
|  | {% endblock %} | ||||||
| @@ -268,6 +268,10 @@ | |||||||
|         ); |         ); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  |     $('#print-label').click(function() { | ||||||
|  |         printPartLabels([{{ part.pk }}]); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|     $("#part-count").click(function() { |     $("#part-count").click(function() { | ||||||
|         launchModalForm("/stock/adjust/", { |         launchModalForm("/stock/adjust/", { | ||||||
|             data: { |             data: { | ||||||
|   | |||||||
| @@ -105,6 +105,61 @@ function printStockLocationLabels(locations, options={}) { | |||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | function printPartLabels(parts, options={}) { | ||||||
|  |     /** | ||||||
|  |      * Print labels for the provided parts | ||||||
|  |      */ | ||||||
|  |  | ||||||
|  |     if (parts.length == 0) { | ||||||
|  |         showAlertDialog( | ||||||
|  |             '{% trans "Select Parts" %}', | ||||||
|  |             '{% trans "Part(s) must be selected before printing labels" %}', | ||||||
|  |         ); | ||||||
|  |  | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Request available labels from the server | ||||||
|  |     inventreeGet( | ||||||
|  |         '{% url "api-part-label-list" %}', | ||||||
|  |         { | ||||||
|  |             enabled: true, | ||||||
|  |             parts: parts, | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             success: function(response) { | ||||||
|  |  | ||||||
|  |                 if (response.length == 0) { | ||||||
|  |                     showAlertDialog( | ||||||
|  |                         '{% trans "No Labels Found" %}', | ||||||
|  |                         '{% trans "No labels found which match the selected part(s)" %}', | ||||||
|  |                     ); | ||||||
|  |  | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 // Select label to print | ||||||
|  |                 selectLabel( | ||||||
|  |                     response, | ||||||
|  |                     parts, | ||||||
|  |                     { | ||||||
|  |                         success: function(pk) { | ||||||
|  |                             var url = `/api/label/part/${pk}/print/?`; | ||||||
|  |  | ||||||
|  |                             parts.forEach(function(part) { | ||||||
|  |                                 url += `parts[]=${part}&`; | ||||||
|  |                             }); | ||||||
|  |  | ||||||
|  |                             window.location.href = url; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 ); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
| function selectLabel(labels, items, options={}) { | function selectLabel(labels, items, options={}) { | ||||||
|     /** |     /** | ||||||
|      * Present the user with the available labels, |      * Present the user with the available labels, | ||||||
|   | |||||||
| @@ -87,6 +87,7 @@ class RuleSet(models.Model): | |||||||
|             'company_supplierpart', |             'company_supplierpart', | ||||||
|             'company_manufacturerpart', |             'company_manufacturerpart', | ||||||
|             'company_manufacturerpartparameter', |             'company_manufacturerpartparameter', | ||||||
|  |             'label_partlabel', | ||||||
|         ], |         ], | ||||||
|         'stock_location': [ |         'stock_location': [ | ||||||
|             'stock_stocklocation', |             'stock_stocklocation', | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user