mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Adds model for BuildReport
- List / Detail / Print
This commit is contained in:
parent
e8fd336612
commit
a349e77866
@ -5,6 +5,7 @@ from django.contrib import admin
|
|||||||
|
|
||||||
from .models import ReportSnippet, ReportAsset
|
from .models import ReportSnippet, ReportAsset
|
||||||
from .models import TestReport
|
from .models import TestReport
|
||||||
|
from .models import BuildReport
|
||||||
from .models import BillOfMaterialsReport
|
from .models import BillOfMaterialsReport
|
||||||
|
|
||||||
|
|
||||||
@ -27,4 +28,5 @@ admin.site.register(ReportSnippet, ReportSnippetAdmin)
|
|||||||
admin.site.register(ReportAsset, ReportAssetAdmin)
|
admin.site.register(ReportAsset, ReportAssetAdmin)
|
||||||
|
|
||||||
admin.site.register(TestReport, ReportTemplateAdmin)
|
admin.site.register(TestReport, ReportTemplateAdmin)
|
||||||
|
admin.site.register(BuildReport, ReportTemplateAdmin)
|
||||||
admin.site.register(BillOfMaterialsReport, ReportTemplateAdmin)
|
admin.site.register(BillOfMaterialsReport, ReportTemplateAdmin)
|
||||||
|
@ -16,12 +16,15 @@ import InvenTree.helpers
|
|||||||
|
|
||||||
from stock.models import StockItem
|
from stock.models import StockItem
|
||||||
|
|
||||||
|
import build.models
|
||||||
import part.models
|
import part.models
|
||||||
|
|
||||||
from .models import TestReport
|
from .models import TestReport
|
||||||
|
from .models import BuildReport
|
||||||
from .models import BillOfMaterialsReport
|
from .models import BillOfMaterialsReport
|
||||||
|
|
||||||
from .serializers import TestReportSerializer
|
from .serializers import TestReportSerializer
|
||||||
|
from .serializers import BuildReportSerializer
|
||||||
from .serializers import BOMReportSerializer
|
from .serializers import BOMReportSerializer
|
||||||
|
|
||||||
|
|
||||||
@ -81,6 +84,39 @@ class StockItemReportMixin:
|
|||||||
return valid_items
|
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:
|
class PartReportMixin:
|
||||||
"""
|
"""
|
||||||
Mixin for extracting part items from query params
|
Mixin for extracting part items from query params
|
||||||
@ -349,7 +385,7 @@ class BOMReportDetail(generics.RetrieveUpdateDestroyAPIView):
|
|||||||
serializer_class = BOMReportSerializer
|
serializer_class = BOMReportSerializer
|
||||||
|
|
||||||
|
|
||||||
class BOMReportPrint(generics.RetrieveUpdateDestroyAPIView, PartReportMixin, ReportPrintMixin):
|
class BOMReportPrint(generics.RetrieveAPIView, PartReportMixin, ReportPrintMixin):
|
||||||
"""
|
"""
|
||||||
API endpoint for printing a BillOfMaterialReport object
|
API endpoint for printing a BillOfMaterialReport object
|
||||||
"""
|
"""
|
||||||
@ -367,8 +403,108 @@ class BOMReportPrint(generics.RetrieveUpdateDestroyAPIView, PartReportMixin, Rep
|
|||||||
return self.print(request, parts)
|
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 = [
|
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
|
# Bill of Material reports
|
||||||
url(r'bom/', include([
|
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.files.storage import FileSystemStorage
|
||||||
from django.core.validators import FileExtensionValidator
|
from django.core.validators import FileExtensionValidator
|
||||||
|
|
||||||
|
import build.models
|
||||||
|
import common.models
|
||||||
import part.models
|
import part.models
|
||||||
import stock.models
|
import stock.models
|
||||||
import common.models
|
|
||||||
|
|
||||||
from InvenTree.helpers import validateFilterString
|
from InvenTree.helpers import validateFilterString
|
||||||
|
|
||||||
@ -86,6 +87,14 @@ def validate_part_report_filters(filters):
|
|||||||
return validateFilterString(filters, model=part.models.Part)
|
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 WeasyprintReportMixin(WeasyTemplateResponseMixin):
|
||||||
"""
|
"""
|
||||||
Class for rendering a HTML template to a PDF.
|
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,
|
def get_context_data(self, request):
|
||||||
# delete the original one from the media directory
|
"""
|
||||||
if str(filename) == str(instance.snippet):
|
Custom context data for the build report
|
||||||
fullpath = os.path.join(settings.MEDIA_ROOT, path)
|
"""
|
||||||
fullpath = os.path.abspath(fullpath)
|
|
||||||
|
|
||||||
if os.path.exists(fullpath):
|
my_build = self.object_to_print
|
||||||
logger.info(f"Deleting existing snippet file: '{filename}'")
|
|
||||||
os.remove(fullpath)
|
|
||||||
|
|
||||||
return path
|
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):
|
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):
|
class ReportSnippet(models.Model):
|
||||||
"""
|
"""
|
||||||
Report template 'snippet' which can be used to make templates
|
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 InvenTree.serializers import InvenTreeAttachmentSerializerField
|
||||||
|
|
||||||
from .models import TestReport
|
from .models import TestReport
|
||||||
|
from .models import BuildReport
|
||||||
from .models import BillOfMaterialsReport
|
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):
|
class BOMReportSerializer(InvenTreeModelSerializer):
|
||||||
|
|
||||||
template = InvenTreeAttachmentSerializerField(required=True)
|
template = InvenTreeAttachmentSerializerField(required=True)
|
||||||
|
Loading…
Reference in New Issue
Block a user