mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-31 21:25:42 +00:00 
			
		
		
		
	Adds model for BuildReport
- List / Detail / Print
This commit is contained in:
		| @@ -5,6 +5,7 @@ from django.contrib import admin | ||||
|  | ||||
| from .models import ReportSnippet, ReportAsset | ||||
| from .models import TestReport | ||||
| from .models import BuildReport | ||||
| from .models import BillOfMaterialsReport | ||||
|  | ||||
|  | ||||
| @@ -27,4 +28,5 @@ admin.site.register(ReportSnippet, ReportSnippetAdmin) | ||||
| admin.site.register(ReportAsset, ReportAssetAdmin) | ||||
|  | ||||
| admin.site.register(TestReport, ReportTemplateAdmin) | ||||
| admin.site.register(BuildReport, ReportTemplateAdmin) | ||||
| admin.site.register(BillOfMaterialsReport, ReportTemplateAdmin) | ||||
|   | ||||
| @@ -16,12 +16,15 @@ import InvenTree.helpers | ||||
|  | ||||
| from stock.models import StockItem | ||||
|  | ||||
| import build.models | ||||
| import part.models | ||||
|  | ||||
| from .models import TestReport | ||||
| from .models import BuildReport | ||||
| from .models import BillOfMaterialsReport | ||||
|  | ||||
| from .serializers import TestReportSerializer | ||||
| from .serializers import BuildReportSerializer | ||||
| from .serializers import BOMReportSerializer | ||||
|  | ||||
|  | ||||
| @@ -81,6 +84,39 @@ class StockItemReportMixin: | ||||
|         return valid_items | ||||
|  | ||||
|  | ||||
| class BuildReportMixin: | ||||
|     """ | ||||
|     Mixin for extracting Build items from query params | ||||
|     """ | ||||
|  | ||||
|     def get_builds(self): | ||||
|         """ | ||||
|         Return a list of requested Build objects | ||||
|         """ | ||||
|  | ||||
|         builds = [] | ||||
|  | ||||
|         params = self.request.query_params | ||||
|  | ||||
|         if 'builds[]' in params: | ||||
|             builds = params.getlist('builds[]', []) | ||||
|         elif 'build' in params: | ||||
|             builds = [params.get('build', None)] | ||||
|  | ||||
|         if type(builds) not in [list, tuple]: | ||||
|             builds = [builds] | ||||
|  | ||||
|         valid_ids = [] | ||||
|  | ||||
|         for b in builds: | ||||
|             try: | ||||
|                 valid_ids.append(int(b)) | ||||
|             except (ValueError): | ||||
|                 continue | ||||
|  | ||||
|         return build.models.Build.objects.filter(pk__in=valid_ids) | ||||
|  | ||||
|  | ||||
| class PartReportMixin: | ||||
|     """ | ||||
|     Mixin for extracting part items from query params | ||||
| @@ -349,7 +385,7 @@ class BOMReportDetail(generics.RetrieveUpdateDestroyAPIView): | ||||
|     serializer_class = BOMReportSerializer | ||||
|  | ||||
|  | ||||
| class BOMReportPrint(generics.RetrieveUpdateDestroyAPIView, PartReportMixin, ReportPrintMixin): | ||||
| class BOMReportPrint(generics.RetrieveAPIView, PartReportMixin, ReportPrintMixin): | ||||
|     """ | ||||
|     API endpoint for printing a BillOfMaterialReport object | ||||
|     """ | ||||
| @@ -367,8 +403,108 @@ class BOMReportPrint(generics.RetrieveUpdateDestroyAPIView, PartReportMixin, Rep | ||||
|         return self.print(request, parts) | ||||
|  | ||||
|  | ||||
| class BuildReportList(ReportListView, BuildReportMixin): | ||||
|     """ | ||||
|     API endpoint for viewing a list of BuildReport objects. | ||||
|  | ||||
|     Can be filtered by: | ||||
|  | ||||
|     - enabled: Filter by enabled / disabled status | ||||
|     - build: Filter by a single build | ||||
|     - builds[]: Filter by a list of builds | ||||
|     """ | ||||
|  | ||||
|     queryset = BuildReport.objects.all() | ||||
|     serializer_class = BuildReportSerializer | ||||
|  | ||||
|     def filter_queryset(self, queryset): | ||||
|  | ||||
|         queryset = super().filter_queryset(queryset) | ||||
|  | ||||
|         # List of Build objects to match against | ||||
|         builds = self.get_builds() | ||||
|  | ||||
|         if len(builds) > 0: | ||||
|             """ | ||||
|             We wish to filter by Build(s) | ||||
|  | ||||
|             We need to compare the 'filters' string of each report, | ||||
|             and see if it matches against each of the specified parts | ||||
|              | ||||
|             # TODO: This code needs some refactoring! | ||||
|             """ | ||||
|  | ||||
|             valid_build_ids = set() | ||||
|  | ||||
|             for report in queryset.all(): | ||||
|  | ||||
|                 matches = True | ||||
|  | ||||
|                 try: | ||||
|                     filters = InvenTree.helpers.validateFilterString(report.filters) | ||||
|                 except ValidationError: | ||||
|                     continue | ||||
|  | ||||
|                 for b in builds: | ||||
|                     build_query = build.models.Build.objects.filter(pk=b.pk) | ||||
|  | ||||
|                     try: | ||||
|                         if not build_query.filter(**filters).exists(): | ||||
|                             matches = False | ||||
|                             break | ||||
|                     except FieldError: | ||||
|                         matches = False | ||||
|                         break | ||||
|  | ||||
|                 if matches: | ||||
|                     valid_build_ids.add(report.pk) | ||||
|                 else: | ||||
|                     continue | ||||
|  | ||||
|             # Reduce queryset to only valid matches | ||||
|             queryset = queryset.filter(pk__in=[pk for pk in valid_build_ids]) | ||||
|  | ||||
|         return queryset | ||||
|  | ||||
|  | ||||
| class BuildReportDetail(generics.RetrieveUpdateDestroyAPIView): | ||||
|     """ | ||||
|     API endpoint for a single BuildReport object | ||||
|     """ | ||||
|  | ||||
|     queryset = BuildReport.objects.all() | ||||
|     serializer_class = BuildReportSerializer | ||||
|  | ||||
|  | ||||
| class BuildReportPrint(generics.RetrieveAPIView, BuildReportMixin, ReportPrintMixin): | ||||
|     """ | ||||
|     API endpoint for printing a BuildReport | ||||
|     """ | ||||
|  | ||||
|     queryset = BuildReport.objects.all() | ||||
|     serializer_class = BuildReportSerializer | ||||
|  | ||||
|     def get(self, request, *ars, **kwargs): | ||||
|  | ||||
|         builds = self.get_builds() | ||||
|  | ||||
|         return self.print(request, builds) | ||||
|  | ||||
|  | ||||
| report_api_urls = [ | ||||
|  | ||||
|     # Build reports | ||||
|     url(r'build/', include([ | ||||
|         # Detail views | ||||
|         url(r'^(?P<pk>\d+)/', include([ | ||||
|             url(r'print/?', BuildReportPrint.as_view(), name='api-build-report-print'), | ||||
|             url(r'^.*$', BuildReportDetail.as_view(), name='api-build-report-detail'), | ||||
|         ])), | ||||
|  | ||||
|         # List view | ||||
|         url(r'^.*$', BuildReportList.as_view(), name='api-build-report-list'), | ||||
|     ])), | ||||
|  | ||||
|     # Bill of Material reports | ||||
|     url(r'bom/', include([ | ||||
|  | ||||
|   | ||||
							
								
								
									
										30
									
								
								InvenTree/report/migrations/0012_buildreport.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								InvenTree/report/migrations/0012_buildreport.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| # Generated by Django 3.0.7 on 2021-02-15 21:08 | ||||
|  | ||||
| import django.core.validators | ||||
| from django.db import migrations, models | ||||
| import report.models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('report', '0011_auto_20210212_2024'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.CreateModel( | ||||
|             name='BuildReport', | ||||
|             fields=[ | ||||
|                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||||
|                 ('name', models.CharField(help_text='Template name', max_length=100, verbose_name='Name')), | ||||
|                 ('template', models.FileField(help_text='Report template file', upload_to=report.models.rename_template, validators=[django.core.validators.FileExtensionValidator(allowed_extensions=['html', 'htm'])], verbose_name='Template')), | ||||
|                 ('description', models.CharField(help_text='Report template description', max_length=250, verbose_name='Description')), | ||||
|                 ('revision', models.PositiveIntegerField(default=1, editable=False, help_text='Report revision number (auto-increments)', verbose_name='Revision')), | ||||
|                 ('enabled', models.BooleanField(default=True, help_text='Report template is enabled', verbose_name='Enabled')), | ||||
|                 ('filters', models.CharField(blank=True, help_text='Build query filters (comma-separated list of key=value pairs', max_length=250, validators=[report.models.validate_build_report_filters], verbose_name='Build Filters')), | ||||
|             ], | ||||
|             options={ | ||||
|                 'abstract': False, | ||||
|             }, | ||||
|         ), | ||||
|     ] | ||||
| @@ -20,9 +20,10 @@ from django.template.loader import render_to_string | ||||
| from django.core.files.storage import FileSystemStorage | ||||
| from django.core.validators import FileExtensionValidator | ||||
|  | ||||
| import build.models | ||||
| import common.models | ||||
| import part.models | ||||
| import stock.models | ||||
| import common.models | ||||
|  | ||||
| from InvenTree.helpers import validateFilterString | ||||
|  | ||||
| @@ -86,6 +87,14 @@ def validate_part_report_filters(filters): | ||||
|     return validateFilterString(filters, model=part.models.Part) | ||||
|  | ||||
|  | ||||
| def validate_build_report_filters(filters): | ||||
|     """ | ||||
|     Validate filter string against Build model | ||||
|     """ | ||||
|  | ||||
|     return validateFilterString(filters, model=build.models.Build) | ||||
|  | ||||
|  | ||||
| class WeasyprintReportMixin(WeasyTemplateResponseMixin): | ||||
|     """ | ||||
|     Class for rendering a HTML template to a PDF. | ||||
| @@ -298,23 +307,40 @@ class TestReport(ReportTemplateBase): | ||||
|         } | ||||
|  | ||||
|  | ||||
| def rename_snippet(instance, filename): | ||||
| class BuildReport(ReportTemplateBase): | ||||
|     """ | ||||
|     Build order / work order report | ||||
|     """ | ||||
|  | ||||
|     filename = os.path.basename(filename) | ||||
|     def getSubdir(self): | ||||
|         return 'build' | ||||
|  | ||||
|     path = os.path.join('report', 'snippets', filename) | ||||
|     filters = models.CharField( | ||||
|         blank=True, | ||||
|         max_length=250, | ||||
|         verbose_name=_('Build Filters'), | ||||
|         help_text=_('Build query filters (comma-separated list of key=value pairs'), | ||||
|         validators=[ | ||||
|             validate_build_report_filters, | ||||
|         ] | ||||
|     ) | ||||
|  | ||||
|     # If the snippet file is the *same* filename as the one being uploaded, | ||||
|     # delete the original one from the media directory | ||||
|     if str(filename) == str(instance.snippet): | ||||
|         fullpath = os.path.join(settings.MEDIA_ROOT, path) | ||||
|         fullpath = os.path.abspath(fullpath) | ||||
|         | ||||
|         if os.path.exists(fullpath): | ||||
|             logger.info(f"Deleting existing snippet file: '{filename}'") | ||||
|             os.remove(fullpath) | ||||
|     def get_context_data(self, request): | ||||
|         """ | ||||
|         Custom context data for the build report | ||||
|         """ | ||||
|  | ||||
|     return path | ||||
|         my_build = self.object_to_print | ||||
|  | ||||
|         if not type(my_build) == build.models.Build: | ||||
|             raise TypeError('Provided model is not a Build object') | ||||
|  | ||||
|         return { | ||||
|             'build': my_build, | ||||
|             'part': my_build.part, | ||||
|             'reference': my_build.reference, | ||||
|             'quantity': my_build.quantity, | ||||
|         } | ||||
|  | ||||
|  | ||||
| class BillOfMaterialsReport(ReportTemplateBase): | ||||
| @@ -345,6 +371,25 @@ class BillOfMaterialsReport(ReportTemplateBase): | ||||
|         } | ||||
|  | ||||
|  | ||||
| def rename_snippet(instance, filename): | ||||
|  | ||||
|     filename = os.path.basename(filename) | ||||
|  | ||||
|     path = os.path.join('report', 'snippets', filename) | ||||
|  | ||||
|     # If the snippet file is the *same* filename as the one being uploaded, | ||||
|     # delete the original one from the media directory | ||||
|     if str(filename) == str(instance.snippet): | ||||
|         fullpath = os.path.join(settings.MEDIA_ROOT, path) | ||||
|         fullpath = os.path.abspath(fullpath) | ||||
|         | ||||
|         if os.path.exists(fullpath): | ||||
|             logger.info(f"Deleting existing snippet file: '{filename}'") | ||||
|             os.remove(fullpath) | ||||
|  | ||||
|     return path | ||||
|  | ||||
|  | ||||
| class ReportSnippet(models.Model): | ||||
|     """ | ||||
|     Report template 'snippet' which can be used to make templates | ||||
|   | ||||
| @@ -5,6 +5,7 @@ from InvenTree.serializers import InvenTreeModelSerializer | ||||
| from InvenTree.serializers import InvenTreeAttachmentSerializerField | ||||
|  | ||||
| from .models import TestReport | ||||
| from .models import BuildReport | ||||
| from .models import BillOfMaterialsReport | ||||
|  | ||||
|  | ||||
| @@ -24,6 +25,22 @@ class TestReportSerializer(InvenTreeModelSerializer): | ||||
|     ] | ||||
|  | ||||
|  | ||||
| class BuildReportSerializer(InvenTreeModelSerializer): | ||||
|  | ||||
|     template = InvenTreeAttachmentSerializerField(required=True) | ||||
|  | ||||
|     class Meta: | ||||
|         model = BuildReport | ||||
|         fields = [ | ||||
|             'pk', | ||||
|             'name', | ||||
|             'description', | ||||
|             'template', | ||||
|             'filters', | ||||
|             'enabled', | ||||
|         ] | ||||
|  | ||||
|  | ||||
| class BOMReportSerializer(InvenTreeModelSerializer): | ||||
|  | ||||
|     template = InvenTreeAttachmentSerializerField(required=True) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user