mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-28 11:36:44 +00:00
Add new global setting to control auto-upload of test reports (#3137)
* Add new global setting to control auto-upload of test reports * Adds callback to attach a copy of the test report when printing * Fix for all attachment API endpoints - The AttachmentMixin must come first! - User was not being set, as the custom 'perform_create' function was never called * Remove duplicated UserSerializer * display uploading user in attachment table * Add unit test to check the test report is automatically uploaded
This commit is contained in:
parent
2b1d8f5b79
commit
a066fcc909
@ -60,29 +60,6 @@ class InvenTreeMoneySerializer(MoneyField):
|
|||||||
return amount
|
return amount
|
||||||
|
|
||||||
|
|
||||||
class UserSerializer(serializers.ModelSerializer):
|
|
||||||
"""Serializer for User - provides all fields."""
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
"""Metaclass options."""
|
|
||||||
|
|
||||||
model = User
|
|
||||||
fields = 'all'
|
|
||||||
|
|
||||||
|
|
||||||
class UserSerializerBrief(serializers.ModelSerializer):
|
|
||||||
"""Serializer for User - provides limited information."""
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
"""Metaclass options."""
|
|
||||||
|
|
||||||
model = User
|
|
||||||
fields = [
|
|
||||||
'pk',
|
|
||||||
'username',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class InvenTreeModelSerializer(serializers.ModelSerializer):
|
class InvenTreeModelSerializer(serializers.ModelSerializer):
|
||||||
"""Inherits the standard Django ModelSerializer class, but also ensures that the underlying model class data are checked on validation."""
|
"""Inherits the standard Django ModelSerializer class, but also ensures that the underlying model class data are checked on validation."""
|
||||||
|
|
||||||
@ -218,6 +195,21 @@ class InvenTreeModelSerializer(serializers.ModelSerializer):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
class UserSerializer(InvenTreeModelSerializer):
|
||||||
|
"""Serializer for a User."""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Metaclass defines serializer fields."""
|
||||||
|
model = User
|
||||||
|
fields = [
|
||||||
|
'pk',
|
||||||
|
'username',
|
||||||
|
'first_name',
|
||||||
|
'last_name',
|
||||||
|
'email'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class ReferenceIndexingSerializerMixin():
|
class ReferenceIndexingSerializerMixin():
|
||||||
"""This serializer mixin ensures the the reference is not to big / small for the BigIntegerField."""
|
"""This serializer mixin ensures the the reference is not to big / small for the BigIntegerField."""
|
||||||
|
|
||||||
@ -239,9 +231,7 @@ class InvenTreeAttachmentSerializerField(serializers.FileField):
|
|||||||
|
|
||||||
/media/foo/bar.jpg
|
/media/foo/bar.jpg
|
||||||
|
|
||||||
Why? You can't handle the why!
|
If the server process is serving the data at 127.0.0.1,
|
||||||
|
|
||||||
Actually, if the server process is serving the data at 127.0.0.1,
|
|
||||||
but a proxy service (e.g. nginx) is then providing DNS lookup to the outside world,
|
but a proxy service (e.g. nginx) is then providing DNS lookup to the outside world,
|
||||||
then an attachment which prefixes the "address" of the internal server
|
then an attachment which prefixes the "address" of the internal server
|
||||||
will not be accessible from the outside world.
|
will not be accessible from the outside world.
|
||||||
@ -261,6 +251,8 @@ class InvenTreeAttachmentSerializer(InvenTreeModelSerializer):
|
|||||||
The only real addition here is that we support "renaming" of the attachment file.
|
The only real addition here is that we support "renaming" of the attachment file.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
user_detail = UserSerializer(source='user', read_only=True, many=False)
|
||||||
|
|
||||||
attachment = InvenTreeAttachmentSerializerField(
|
attachment = InvenTreeAttachmentSerializerField(
|
||||||
required=False,
|
required=False,
|
||||||
allow_null=False,
|
allow_null=False,
|
||||||
|
@ -413,7 +413,7 @@ class BuildItemList(generics.ListCreateAPIView):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class BuildAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
|
class BuildAttachmentList(AttachmentMixin, generics.ListCreateAPIView):
|
||||||
"""API endpoint for listing (and creating) BuildOrderAttachment objects."""
|
"""API endpoint for listing (and creating) BuildOrderAttachment objects."""
|
||||||
|
|
||||||
queryset = BuildOrderAttachment.objects.all()
|
queryset = BuildOrderAttachment.objects.all()
|
||||||
@ -428,7 +428,7 @@ class BuildAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class BuildAttachmentDetail(generics.RetrieveUpdateDestroyAPIView, AttachmentMixin):
|
class BuildAttachmentDetail(AttachmentMixin, generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""Detail endpoint for a BuildOrderAttachment object."""
|
"""Detail endpoint for a BuildOrderAttachment object."""
|
||||||
|
|
||||||
queryset = BuildOrderAttachment.objects.all()
|
queryset = BuildOrderAttachment.objects.all()
|
||||||
|
@ -11,7 +11,7 @@ from rest_framework import serializers
|
|||||||
from rest_framework.serializers import ValidationError
|
from rest_framework.serializers import ValidationError
|
||||||
|
|
||||||
from InvenTree.serializers import InvenTreeModelSerializer, InvenTreeAttachmentSerializer
|
from InvenTree.serializers import InvenTreeModelSerializer, InvenTreeAttachmentSerializer
|
||||||
from InvenTree.serializers import UserSerializerBrief, ReferenceIndexingSerializerMixin
|
from InvenTree.serializers import ReferenceIndexingSerializerMixin, UserSerializer
|
||||||
|
|
||||||
import InvenTree.helpers
|
import InvenTree.helpers
|
||||||
from InvenTree.helpers import extract_serial_numbers
|
from InvenTree.helpers import extract_serial_numbers
|
||||||
@ -40,7 +40,7 @@ class BuildSerializer(ReferenceIndexingSerializerMixin, InvenTreeModelSerializer
|
|||||||
|
|
||||||
overdue = serializers.BooleanField(required=False, read_only=True)
|
overdue = serializers.BooleanField(required=False, read_only=True)
|
||||||
|
|
||||||
issued_by_detail = UserSerializerBrief(source='issued_by', read_only=True)
|
issued_by_detail = UserSerializer(source='issued_by', read_only=True)
|
||||||
|
|
||||||
responsible_detail = OwnerSerializer(source='responsible', read_only=True)
|
responsible_detail = OwnerSerializer(source='responsible', read_only=True)
|
||||||
|
|
||||||
@ -860,6 +860,8 @@ class BuildAttachmentSerializer(InvenTreeAttachmentSerializer):
|
|||||||
'filename',
|
'filename',
|
||||||
'comment',
|
'comment',
|
||||||
'upload_date',
|
'upload_date',
|
||||||
|
'user',
|
||||||
|
'user_detail',
|
||||||
]
|
]
|
||||||
|
|
||||||
read_only_fields = [
|
read_only_fields = [
|
||||||
|
@ -965,12 +965,19 @@ class InvenTreeSetting(BaseInvenTreeSetting):
|
|||||||
},
|
},
|
||||||
|
|
||||||
'REPORT_ENABLE_TEST_REPORT': {
|
'REPORT_ENABLE_TEST_REPORT': {
|
||||||
'name': _('Test Reports'),
|
'name': _('Enable Test Reports'),
|
||||||
'description': _('Enable generation of test reports'),
|
'description': _('Enable generation of test reports'),
|
||||||
'default': True,
|
'default': True,
|
||||||
'validator': bool,
|
'validator': bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'REPORT_ATTACH_TEST_REPORT': {
|
||||||
|
'name': _('Attach Test Reports'),
|
||||||
|
'description': _('When printing a Test Report, attach a copy of the Test Report to the associated Stock Item'),
|
||||||
|
'default': False,
|
||||||
|
'validator': bool,
|
||||||
|
},
|
||||||
|
|
||||||
'STOCK_BATCH_CODE_TEMPLATE': {
|
'STOCK_BATCH_CODE_TEMPLATE': {
|
||||||
'name': _('Batch Code Template'),
|
'name': _('Batch Code Template'),
|
||||||
'description': _('Template for generating default batch codes for stock items'),
|
'description': _('Template for generating default batch codes for stock items'),
|
||||||
|
@ -158,6 +158,8 @@ class ManufacturerPartAttachmentSerializer(InvenTreeAttachmentSerializer):
|
|||||||
'link',
|
'link',
|
||||||
'comment',
|
'comment',
|
||||||
'upload_date',
|
'upload_date',
|
||||||
|
'user',
|
||||||
|
'user_detail',
|
||||||
]
|
]
|
||||||
|
|
||||||
read_only_fields = [
|
read_only_fields = [
|
||||||
|
@ -527,7 +527,7 @@ class PurchaseOrderExtraLineDetail(generics.RetrieveUpdateDestroyAPIView):
|
|||||||
serializer_class = serializers.PurchaseOrderExtraLineSerializer
|
serializer_class = serializers.PurchaseOrderExtraLineSerializer
|
||||||
|
|
||||||
|
|
||||||
class SalesOrderAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
|
class SalesOrderAttachmentList(AttachmentMixin, generics.ListCreateAPIView):
|
||||||
"""API endpoint for listing (and creating) a SalesOrderAttachment (file upload)"""
|
"""API endpoint for listing (and creating) a SalesOrderAttachment (file upload)"""
|
||||||
|
|
||||||
queryset = models.SalesOrderAttachment.objects.all()
|
queryset = models.SalesOrderAttachment.objects.all()
|
||||||
@ -542,7 +542,7 @@ class SalesOrderAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class SalesOrderAttachmentDetail(generics.RetrieveUpdateDestroyAPIView, AttachmentMixin):
|
class SalesOrderAttachmentDetail(AttachmentMixin, generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""Detail endpoint for SalesOrderAttachment."""
|
"""Detail endpoint for SalesOrderAttachment."""
|
||||||
|
|
||||||
queryset = models.SalesOrderAttachment.objects.all()
|
queryset = models.SalesOrderAttachment.objects.all()
|
||||||
@ -1056,7 +1056,7 @@ class SalesOrderShipmentComplete(generics.CreateAPIView):
|
|||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
|
|
||||||
class PurchaseOrderAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
|
class PurchaseOrderAttachmentList(AttachmentMixin, generics.ListCreateAPIView):
|
||||||
"""API endpoint for listing (and creating) a PurchaseOrderAttachment (file upload)"""
|
"""API endpoint for listing (and creating) a PurchaseOrderAttachment (file upload)"""
|
||||||
|
|
||||||
queryset = models.PurchaseOrderAttachment.objects.all()
|
queryset = models.PurchaseOrderAttachment.objects.all()
|
||||||
@ -1071,7 +1071,7 @@ class PurchaseOrderAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class PurchaseOrderAttachmentDetail(generics.RetrieveUpdateDestroyAPIView, AttachmentMixin):
|
class PurchaseOrderAttachmentDetail(AttachmentMixin, generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""Detail endpoint for a PurchaseOrderAttachment."""
|
"""Detail endpoint for a PurchaseOrderAttachment."""
|
||||||
|
|
||||||
queryset = models.PurchaseOrderAttachment.objects.all()
|
queryset = models.PurchaseOrderAttachment.objects.all()
|
||||||
|
@ -630,6 +630,8 @@ class PurchaseOrderAttachmentSerializer(InvenTreeAttachmentSerializer):
|
|||||||
'filename',
|
'filename',
|
||||||
'comment',
|
'comment',
|
||||||
'upload_date',
|
'upload_date',
|
||||||
|
'user',
|
||||||
|
'user_detail',
|
||||||
]
|
]
|
||||||
|
|
||||||
read_only_fields = [
|
read_only_fields = [
|
||||||
@ -1348,6 +1350,8 @@ class SalesOrderAttachmentSerializer(InvenTreeAttachmentSerializer):
|
|||||||
'link',
|
'link',
|
||||||
'comment',
|
'comment',
|
||||||
'upload_date',
|
'upload_date',
|
||||||
|
'user',
|
||||||
|
'user_detail',
|
||||||
]
|
]
|
||||||
|
|
||||||
read_only_fields = [
|
read_only_fields = [
|
||||||
|
@ -302,7 +302,7 @@ class PartInternalPriceList(generics.ListCreateAPIView):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class PartAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
|
class PartAttachmentList(AttachmentMixin, generics.ListCreateAPIView):
|
||||||
"""API endpoint for listing (and creating) a PartAttachment (file upload)."""
|
"""API endpoint for listing (and creating) a PartAttachment (file upload)."""
|
||||||
|
|
||||||
queryset = PartAttachment.objects.all()
|
queryset = PartAttachment.objects.all()
|
||||||
@ -317,7 +317,7 @@ class PartAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class PartAttachmentDetail(generics.RetrieveUpdateDestroyAPIView, AttachmentMixin):
|
class PartAttachmentDetail(AttachmentMixin, generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""Detail endpoint for PartAttachment model."""
|
"""Detail endpoint for PartAttachment model."""
|
||||||
|
|
||||||
queryset = PartAttachment.objects.all()
|
queryset = PartAttachment.objects.all()
|
||||||
|
@ -94,6 +94,8 @@ class PartAttachmentSerializer(InvenTreeAttachmentSerializer):
|
|||||||
'link',
|
'link',
|
||||||
'comment',
|
'comment',
|
||||||
'upload_date',
|
'upload_date',
|
||||||
|
'user',
|
||||||
|
'user_detail',
|
||||||
]
|
]
|
||||||
|
|
||||||
read_only_fields = [
|
read_only_fields = [
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
"""API functionality for the 'report' app"""
|
"""API functionality for the 'report' app"""
|
||||||
|
|
||||||
from django.core.exceptions import FieldError, ValidationError
|
from django.core.exceptions import FieldError, ValidationError
|
||||||
|
from django.core.files.base import ContentFile
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.template.exceptions import TemplateDoesNotExist
|
from django.template.exceptions import TemplateDoesNotExist
|
||||||
from django.urls import include, path, re_path
|
from django.urls import include, path, re_path
|
||||||
@ -15,7 +16,7 @@ import common.models
|
|||||||
import InvenTree.helpers
|
import InvenTree.helpers
|
||||||
import order.models
|
import order.models
|
||||||
import part.models
|
import part.models
|
||||||
from stock.models import StockItem
|
from stock.models import StockItem, StockItemAttachment
|
||||||
|
|
||||||
from .models import (BillOfMaterialsReport, BuildReport, PurchaseOrderReport,
|
from .models import (BillOfMaterialsReport, BuildReport, PurchaseOrderReport,
|
||||||
SalesOrderReport, TestReport)
|
SalesOrderReport, TestReport)
|
||||||
@ -158,6 +159,18 @@ class PartReportMixin:
|
|||||||
class ReportPrintMixin:
|
class ReportPrintMixin:
|
||||||
"""Mixin for printing reports."""
|
"""Mixin for printing reports."""
|
||||||
|
|
||||||
|
def report_callback(self, object, report, request):
|
||||||
|
"""Callback function for each object/report combination.
|
||||||
|
|
||||||
|
Allows functionality to be performed before returning the consolidated PDF
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
object: The model instance to be printed
|
||||||
|
report: The individual PDF file object
|
||||||
|
request: The request instance associated with this print call
|
||||||
|
"""
|
||||||
|
...
|
||||||
|
|
||||||
def print(self, request, items_to_print):
|
def print(self, request, items_to_print):
|
||||||
"""Print this report template against a number of pre-validated items."""
|
"""Print this report template against a number of pre-validated items."""
|
||||||
if len(items_to_print) == 0:
|
if len(items_to_print) == 0:
|
||||||
@ -182,12 +195,16 @@ class ReportPrintMixin:
|
|||||||
report.object_to_print = item
|
report.object_to_print = item
|
||||||
|
|
||||||
report_name = report.generate_filename(request)
|
report_name = report.generate_filename(request)
|
||||||
|
output = report.render(request)
|
||||||
|
|
||||||
|
# Run report callback for each generated report
|
||||||
|
self.report_callback(item, output, request)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if debug_mode:
|
if debug_mode:
|
||||||
outputs.append(report.render_as_string(request))
|
outputs.append(report.render_as_string(request))
|
||||||
else:
|
else:
|
||||||
outputs.append(report.render(request))
|
outputs.append(output)
|
||||||
except TemplateDoesNotExist as e:
|
except TemplateDoesNotExist as e:
|
||||||
template = str(e)
|
template = str(e)
|
||||||
if not template:
|
if not template:
|
||||||
@ -326,6 +343,22 @@ class StockItemTestReportPrint(generics.RetrieveAPIView, StockItemReportMixin, R
|
|||||||
queryset = TestReport.objects.all()
|
queryset = TestReport.objects.all()
|
||||||
serializer_class = TestReportSerializer
|
serializer_class = TestReportSerializer
|
||||||
|
|
||||||
|
def report_callback(self, item, report, request):
|
||||||
|
"""Callback to (optionally) save a copy of the generated report"""
|
||||||
|
|
||||||
|
if common.models.InvenTreeSetting.get_setting('REPORT_ATTACH_TEST_REPORT'):
|
||||||
|
|
||||||
|
# Construct a PDF file object
|
||||||
|
pdf = report.get_document().write_pdf()
|
||||||
|
pdf_content = ContentFile(pdf, "test_report.pdf")
|
||||||
|
|
||||||
|
StockItemAttachment.objects.create(
|
||||||
|
attachment=pdf_content,
|
||||||
|
stock_item=item,
|
||||||
|
user=request.user,
|
||||||
|
comment=_("Test report")
|
||||||
|
)
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
"""Check if valid stock item(s) have been provided."""
|
"""Check if valid stock item(s) have been provided."""
|
||||||
items = self.get_items()
|
items = self.get_items()
|
||||||
|
@ -9,9 +9,9 @@ from django.urls import reverse
|
|||||||
|
|
||||||
import report.models as report_models
|
import report.models as report_models
|
||||||
from build.models import Build
|
from build.models import Build
|
||||||
from common.models import InvenTreeUserSetting
|
from common.models import InvenTreeSetting, InvenTreeUserSetting
|
||||||
from InvenTree.api_tester import InvenTreeAPITestCase
|
from InvenTree.api_tester import InvenTreeAPITestCase
|
||||||
from stock.models import StockItem
|
from stock.models import StockItem, StockItemAttachment
|
||||||
|
|
||||||
|
|
||||||
class ReportTest(InvenTreeAPITestCase):
|
class ReportTest(InvenTreeAPITestCase):
|
||||||
@ -141,15 +141,28 @@ class TestReportTest(ReportTest):
|
|||||||
# Now print with a valid StockItem
|
# Now print with a valid StockItem
|
||||||
item = StockItem.objects.first()
|
item = StockItem.objects.first()
|
||||||
|
|
||||||
response = self.get(url, {'item': item.pk})
|
response = self.get(url, {'item': item.pk}, expected_code=200)
|
||||||
|
|
||||||
# Response should be a StreamingHttpResponse (PDF file)
|
# Response should be a StreamingHttpResponse (PDF file)
|
||||||
self.assertEqual(type(response), StreamingHttpResponse)
|
self.assertEqual(type(response), StreamingHttpResponse)
|
||||||
|
|
||||||
headers = response.headers
|
headers = response.headers
|
||||||
|
|
||||||
self.assertEqual(headers['Content-Type'], 'application/pdf')
|
self.assertEqual(headers['Content-Type'], 'application/pdf')
|
||||||
|
|
||||||
|
# By default, this should *not* have created an attachment against this stockitem
|
||||||
|
self.assertFalse(StockItemAttachment.objects.filter(stock_item=item).exists())
|
||||||
|
|
||||||
|
# Change the setting, now the test report should be attached automatically
|
||||||
|
InvenTreeSetting.set_setting('REPORT_ATTACH_TEST_REPORT', True, None)
|
||||||
|
|
||||||
|
response = self.get(url, {'item': item.pk}, expected_code=200)
|
||||||
|
headers = response.headers
|
||||||
|
self.assertEqual(headers['Content-Type'], 'application/pdf')
|
||||||
|
|
||||||
|
# Check that a report has been uploaded
|
||||||
|
attachment = StockItemAttachment.objects.filter(stock_item=item).first()
|
||||||
|
self.assertIsNotNone(attachment)
|
||||||
|
|
||||||
|
|
||||||
class BuildReportTest(ReportTest):
|
class BuildReportTest(ReportTest):
|
||||||
"""Unit test class for the BuildReport model"""
|
"""Unit test class for the BuildReport model"""
|
||||||
|
@ -1043,7 +1043,7 @@ class StockList(APIDownloadMixin, generics.ListCreateAPIView):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class StockAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
|
class StockAttachmentList(AttachmentMixin, generics.ListCreateAPIView):
|
||||||
"""API endpoint for listing (and creating) a StockItemAttachment (file upload)."""
|
"""API endpoint for listing (and creating) a StockItemAttachment (file upload)."""
|
||||||
|
|
||||||
queryset = StockItemAttachment.objects.all()
|
queryset = StockItemAttachment.objects.all()
|
||||||
@ -1060,7 +1060,7 @@ class StockAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class StockAttachmentDetail(generics.RetrieveUpdateDestroyAPIView, AttachmentMixin):
|
class StockAttachmentDetail(AttachmentMixin, generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""Detail endpoint for StockItemAttachment."""
|
"""Detail endpoint for StockItemAttachment."""
|
||||||
|
|
||||||
queryset = StockItemAttachment.objects.all()
|
queryset = StockItemAttachment.objects.all()
|
||||||
|
@ -20,9 +20,9 @@ def delete_scheduled(apps, schema_editor):
|
|||||||
|
|
||||||
items = StockItem.objects.filter(scheduled_for_deletion=True)
|
items = StockItem.objects.filter(scheduled_for_deletion=True)
|
||||||
|
|
||||||
logger.info(f"Removing {items.count()} stock items scheduled for deletion")
|
if items.count() > 0:
|
||||||
|
logger.info(f"Removing {items.count()} stock items scheduled for deletion")
|
||||||
items.delete()
|
items.delete()
|
||||||
|
|
||||||
Task = apps.get_model('django_q', 'schedule')
|
Task = apps.get_model('django_q', 'schedule')
|
||||||
|
|
||||||
|
@ -549,19 +549,6 @@ class LocationSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
|||||||
class StockItemAttachmentSerializer(InvenTree.serializers.InvenTreeAttachmentSerializer):
|
class StockItemAttachmentSerializer(InvenTree.serializers.InvenTreeAttachmentSerializer):
|
||||||
"""Serializer for StockItemAttachment model."""
|
"""Serializer for StockItemAttachment model."""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
"""Add detail fields."""
|
|
||||||
user_detail = kwargs.pop('user_detail', False)
|
|
||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
if user_detail is not True:
|
|
||||||
self.fields.pop('user_detail')
|
|
||||||
|
|
||||||
user_detail = InvenTree.serializers.UserSerializerBrief(source='user', read_only=True)
|
|
||||||
|
|
||||||
# TODO: Record the uploading user when creating or updating an attachment!
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Metaclass options."""
|
"""Metaclass options."""
|
||||||
|
|
||||||
@ -589,7 +576,7 @@ class StockItemAttachmentSerializer(InvenTree.serializers.InvenTreeAttachmentSer
|
|||||||
class StockItemTestResultSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
class StockItemTestResultSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||||
"""Serializer for the StockItemTestResult model."""
|
"""Serializer for the StockItemTestResult model."""
|
||||||
|
|
||||||
user_detail = InvenTree.serializers.UserSerializerBrief(source='user', read_only=True)
|
user_detail = InvenTree.serializers.UserSerializer(source='user', read_only=True)
|
||||||
|
|
||||||
key = serializers.CharField(read_only=True)
|
key = serializers.CharField(read_only=True)
|
||||||
|
|
||||||
@ -650,7 +637,7 @@ class StockTrackingSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
|||||||
|
|
||||||
item_detail = StockItemSerializerBrief(source='item', many=False, read_only=True)
|
item_detail = StockItemSerializerBrief(source='item', many=False, read_only=True)
|
||||||
|
|
||||||
user_detail = InvenTree.serializers.UserSerializerBrief(source='user', many=False, read_only=True)
|
user_detail = InvenTree.serializers.UserSerializer(source='user', many=False, read_only=True)
|
||||||
|
|
||||||
deltas = serializers.JSONField(read_only=True)
|
deltas = serializers.JSONField(read_only=True)
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
{% include "InvenTree/settings/setting.html" with key="REPORT_DEFAULT_PAGE_SIZE" icon="fa-print" %}
|
{% include "InvenTree/settings/setting.html" with key="REPORT_DEFAULT_PAGE_SIZE" icon="fa-print" %}
|
||||||
{% include "InvenTree/settings/setting.html" with key="REPORT_DEBUG_MODE" icon="fa-laptop-code" %}
|
{% include "InvenTree/settings/setting.html" with key="REPORT_DEBUG_MODE" icon="fa-laptop-code" %}
|
||||||
{% include "InvenTree/settings/setting.html" with key="REPORT_ENABLE_TEST_REPORT" icon="fa-vial" %}
|
{% include "InvenTree/settings/setting.html" with key="REPORT_ENABLE_TEST_REPORT" icon="fa-vial" %}
|
||||||
|
{% include "InvenTree/settings/setting.html" with key="REPORT_ATTACH_TEST_REPORT" icon="fa-file-upload" %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
@ -244,8 +244,14 @@ function loadAttachmentTable(url, options) {
|
|||||||
{
|
{
|
||||||
field: 'upload_date',
|
field: 'upload_date',
|
||||||
title: '{% trans "Upload Date" %}',
|
title: '{% trans "Upload Date" %}',
|
||||||
formatter: function(value) {
|
formatter: function(value, row) {
|
||||||
return renderDate(value);
|
var html = renderDate(value);
|
||||||
|
|
||||||
|
if (row.user_detail) {
|
||||||
|
html += `<span class='badge bg-dark rounded-pill float-right'>${row.user_detail.username}</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -10,8 +10,9 @@ from rest_framework.authtoken.models import Token
|
|||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
|
from InvenTree.serializers import UserSerializer
|
||||||
from users.models import Owner, RuleSet, check_user_role
|
from users.models import Owner, RuleSet, check_user_role
|
||||||
from users.serializers import OwnerSerializer, UserSerializer
|
from users.serializers import OwnerSerializer
|
||||||
|
|
||||||
|
|
||||||
class OwnerList(generics.ListAPIView):
|
class OwnerList(generics.ListAPIView):
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
"""DRF API serializers for the 'users' app"""
|
"""DRF API serializers for the 'users' app"""
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
@ -9,19 +8,6 @@ from InvenTree.serializers import InvenTreeModelSerializer
|
|||||||
from .models import Owner
|
from .models import Owner
|
||||||
|
|
||||||
|
|
||||||
class UserSerializer(InvenTreeModelSerializer):
|
|
||||||
"""Serializer for a User."""
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
"""Metaclass defines serializer fields."""
|
|
||||||
model = User
|
|
||||||
fields = ('pk',
|
|
||||||
'username',
|
|
||||||
'first_name',
|
|
||||||
'last_name',
|
|
||||||
'email',)
|
|
||||||
|
|
||||||
|
|
||||||
class OwnerSerializer(InvenTreeModelSerializer):
|
class OwnerSerializer(InvenTreeModelSerializer):
|
||||||
"""Serializer for an "Owner" (either a "user" or a "group")"""
|
"""Serializer for an "Owner" (either a "user" or a "group")"""
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user