From 4615fbc0d8e3fcc5256fb0d2f0335ab5bc036c02 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 12 May 2020 20:15:37 +1000 Subject: [PATCH 1/8] Abillty to filter stock list by serial number --- InvenTree/stock/api.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py index f72f2590cc..404162a300 100644 --- a/InvenTree/stock/api.py +++ b/InvenTree/stock/api.py @@ -478,6 +478,12 @@ class StockList(generics.ListCreateAPIView): if sales_order: queryset = queryset.filter(sales_order=sales_order) + + # Filter by serial number? + serial_number = params.get('serial', None) + + if serial_number is not None: + queryset = queryset.filter(serial=serial_number) in_stock = self.request.query_params.get('in_stock', None) From 0851a925fb6a60ba05b4fb0f0559bb6c94ffd8ad Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 12 May 2020 20:18:28 +1000 Subject: [PATCH 2/8] Add ability to filter by whether the part is serialized or not --- InvenTree/stock/api.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py index 404162a300..a56d4081d0 100644 --- a/InvenTree/stock/api.py +++ b/InvenTree/stock/api.py @@ -479,6 +479,17 @@ class StockList(generics.ListCreateAPIView): if sales_order: queryset = queryset.filter(sales_order=sales_order) + # Filter by "serialized" status? + serialized = params.get('serialized', None) + + if serialized is not None: + serialized = str2bool(serialized) + + if serialized: + queryset = queryset.exclude(serial=None) + else: + queryset = queryset.filter(serial=None) + # Filter by serial number? serial_number = params.get('serial', None) From d4fa7d936ee2a65438665e2b8341e3b6bf928fc5 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 12 May 2020 20:50:03 +1000 Subject: [PATCH 3/8] Add 'user' field to attachment --- InvenTree/InvenTree/models.py | 8 ++++++ .../migrations/0033_auto_20200512_1033.py | 26 +++++++++++++++++++ .../migrations/0036_partattachment_user.py | 21 +++++++++++++++ .../0037_stockitemattachment_user.py | 21 +++++++++++++++ InvenTree/stock/serializers.py | 14 +++++++++- .../templates/stock/item_attachments.html | 4 +++ InvenTree/stock/views.py | 6 +++++ 7 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 InvenTree/order/migrations/0033_auto_20200512_1033.py create mode 100644 InvenTree/part/migrations/0036_partattachment_user.py create mode 100644 InvenTree/stock/migrations/0037_stockitemattachment_user.py diff --git a/InvenTree/InvenTree/models.py b/InvenTree/InvenTree/models.py index d2a5c5daa6..103d571fd1 100644 --- a/InvenTree/InvenTree/models.py +++ b/InvenTree/InvenTree/models.py @@ -7,6 +7,7 @@ from __future__ import unicode_literals import os from django.db import models +from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType from django.utils.translation import gettext_lazy as _ @@ -55,6 +56,13 @@ class InvenTreeAttachment(models.Model): comment = models.CharField(max_length=100, help_text=_('File comment')) + user = models.ForeignKey( + User, + on_delete=models.SET_NULL, + blank=True, null=True, + help_text=_('User'), + ) + @property def basename(self): return os.path.basename(self.attachment.name) diff --git a/InvenTree/order/migrations/0033_auto_20200512_1033.py b/InvenTree/order/migrations/0033_auto_20200512_1033.py new file mode 100644 index 0000000000..2c3abbb0d0 --- /dev/null +++ b/InvenTree/order/migrations/0033_auto_20200512_1033.py @@ -0,0 +1,26 @@ +# Generated by Django 3.0.5 on 2020-05-12 10:33 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('order', '0032_auto_20200427_0044'), + ] + + operations = [ + migrations.AddField( + model_name='purchaseorderattachment', + name='user', + field=models.ForeignKey(blank=True, help_text='User', null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='salesorderattachment', + name='user', + field=models.ForeignKey(blank=True, help_text='User', null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/InvenTree/part/migrations/0036_partattachment_user.py b/InvenTree/part/migrations/0036_partattachment_user.py new file mode 100644 index 0000000000..d59bc7ffb2 --- /dev/null +++ b/InvenTree/part/migrations/0036_partattachment_user.py @@ -0,0 +1,21 @@ +# Generated by Django 3.0.5 on 2020-05-12 10:33 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('part', '0035_auto_20200406_0045'), + ] + + operations = [ + migrations.AddField( + model_name='partattachment', + name='user', + field=models.ForeignKey(blank=True, help_text='User', null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/InvenTree/stock/migrations/0037_stockitemattachment_user.py b/InvenTree/stock/migrations/0037_stockitemattachment_user.py new file mode 100644 index 0000000000..b0f87df81a --- /dev/null +++ b/InvenTree/stock/migrations/0037_stockitemattachment_user.py @@ -0,0 +1,21 @@ +# Generated by Django 3.0.5 on 2020-05-12 10:33 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('stock', '0036_stockitemattachment'), + ] + + operations = [ + migrations.AddField( + model_name='stockitemattachment', + name='user', + field=models.ForeignKey(blank=True, help_text='User', null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/InvenTree/stock/serializers.py b/InvenTree/stock/serializers.py index 69cb726f73..622f93e620 100644 --- a/InvenTree/stock/serializers.py +++ b/InvenTree/stock/serializers.py @@ -193,6 +193,16 @@ class LocationSerializer(InvenTreeModelSerializer): class StockItemAttachmentSerializer(InvenTreeModelSerializer): """ Serializer for StockItemAttachment model """ + def __init_(self, *args, **kwargs): + user_detail = kwargs.pop('user_detail', False) + + super().__init__(*args, **kwargs) + + if user_detail is not True: + self.fields.pop('user_detail') + + user_detail = UserSerializerBrief(source='user', read_only=True) + class Meta: model = StockItemAttachment @@ -200,7 +210,9 @@ class StockItemAttachmentSerializer(InvenTreeModelSerializer): 'pk', 'stock_item', 'attachment', - 'comment' + 'comment', + 'user', + 'user_detail', ] diff --git a/InvenTree/stock/templates/stock/item_attachments.html b/InvenTree/stock/templates/stock/item_attachments.html index 6aeff554d0..d8210a5c65 100644 --- a/InvenTree/stock/templates/stock/item_attachments.html +++ b/InvenTree/stock/templates/stock/item_attachments.html @@ -22,6 +22,7 @@ {% trans "File" %} {% trans "Comment" %} + {% trans "Uploaded By" %} @@ -30,6 +31,9 @@ {{ attachment.basename }} {{ attachment.comment }} + + {% if attachment.user %}{{ attachment.user.username }}{% else %}-{% endif %} +
{% endif %}
From 2369b40bbf4df7a1f3a7f00c100e7a22ab6512d3 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 12 May 2020 21:11:38 +1000 Subject: [PATCH 5/8] Ensure user gets saved when uploading attachment via API --- InvenTree/part/api.py | 15 ++++----------- InvenTree/stock/api.py | 6 ++++++ 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/InvenTree/part/api.py b/InvenTree/part/api.py index 8f9121885d..eb14e93a9a 100644 --- a/InvenTree/part/api.py +++ b/InvenTree/part/api.py @@ -296,24 +296,17 @@ class PartList(generics.ListCreateAPIView): else: return Response(data) - def create(self, request, *args, **kwargs): - """ Override the default 'create' behaviour: + def perform_create(self, serializer): + """ We wish to save the user who created this part! - Note: Implementation coped from DRF class CreateModelMixin + Note: Implementation copied from DRF class CreateModelMixin """ - serializer = self.get_serializer(data=request.data) - serializer.is_valid(raise_exception=True) - - # Record the user who created this Part object part = serializer.save() - part.creation_user = request.user + part.creation_user = self.request.user part.save() - headers = self.get_success_headers(serializer.data) - return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) - def get_queryset(self, *args, **kwargs): queryset = super().get_queryset(*args, **kwargs) diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py index a56d4081d0..2c1d4916f6 100644 --- a/InvenTree/stock/api.py +++ b/InvenTree/stock/api.py @@ -663,6 +663,12 @@ class StockAttachmentList(generics.ListCreateAPIView): 'stock_item', ] + def perform_create(self, serializer): + + attachment = serializer.save() + attachment.user = self.request.user + attachment.save() + class StockTrackingList(generics.ListCreateAPIView): """ API endpoint for list view of StockItemTracking objects. From 366d4b214390f454809b446016136843e617beaf Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 12 May 2020 21:40:42 +1000 Subject: [PATCH 6/8] Add new attechment functionality to new models - Giving the ol' refactor tractor a fresh coat of paint --- InvenTree/InvenTree/api.py | 25 +++++++++++++++++++ InvenTree/order/api.py | 17 +++---------- .../order/templates/order/po_attachments.html | 9 +++++-- .../order/templates/order/so_attachments.html | 11 +++++--- InvenTree/order/views.py | 8 ++++++ InvenTree/part/api.py | 11 ++------ .../part/templates/part/attachments.html | 9 +++++-- InvenTree/part/views.py | 5 ++++ InvenTree/stock/api.py | 15 ++--------- .../templates/stock/item_attachments.html | 6 ++--- 10 files changed, 70 insertions(+), 46 deletions(-) diff --git a/InvenTree/InvenTree/api.py b/InvenTree/InvenTree/api.py index abb6102a6f..eb87b8f77a 100644 --- a/InvenTree/InvenTree/api.py +++ b/InvenTree/InvenTree/api.py @@ -8,6 +8,9 @@ from __future__ import unicode_literals from django.utils.translation import ugettext as _ from django.http import JsonResponse +from django_filters.rest_framework import DjangoFilterBackend +from rest_framework import filters + from rest_framework import permissions from rest_framework.response import Response from rest_framework.views import APIView @@ -41,6 +44,28 @@ class InfoView(AjaxView): return JsonResponse(data) +class AttachmentMixin: + """ + Mixin for creating attachment objects, + and ensuring the user information is saved correctly. + """ + + permission_classes = [permissions.IsAuthenticated] + + filter_backends = [ + DjangoFilterBackend, + filters.OrderingFilter, + filters.SearchFilter, + ] + + def perform_create(self, serializer): + """ Save the user information when a file is uploaded """ + + attachment = serializer.save() + attachment.user = self.request.user + attachment.save() + + class ActionPluginView(APIView): """ Endpoint for running custom action plugins. diff --git a/InvenTree/order/api.py b/InvenTree/order/api.py index cb48b4c11d..dba493baab 100644 --- a/InvenTree/order/api.py +++ b/InvenTree/order/api.py @@ -12,6 +12,7 @@ from rest_framework import filters from django.conf.urls import url, include from InvenTree.helpers import str2bool +from InvenTree.api import AttachmentMixin from part.models import Part from company.models import SupplierPart @@ -200,7 +201,7 @@ class POLineItemDetail(generics.RetrieveUpdateAPIView): ] -class SOAttachmentList(generics.ListCreateAPIView): +class SOAttachmentList(generics.ListCreateAPIView, AttachmentMixin): """ API endpoint for listing (and creating) a SalesOrderAttachment (file upload) """ @@ -208,12 +209,6 @@ class SOAttachmentList(generics.ListCreateAPIView): queryset = SalesOrderAttachment.objects.all() serializer_class = SOAttachmentSerializer - filter_backends = [ - DjangoFilterBackend, - filters.OrderingFilter, - filters.SearchFilter, - ] - filter_fields = [ 'order', ] @@ -399,7 +394,7 @@ class SOLineItemDetail(generics.RetrieveUpdateAPIView): permission_classes = [permissions.IsAuthenticated] -class POAttachmentList(generics.ListCreateAPIView): +class POAttachmentList(generics.ListCreateAPIView, AttachmentMixin): """ API endpoint for listing (and creating) a PurchaseOrderAttachment (file upload) """ @@ -407,12 +402,6 @@ class POAttachmentList(generics.ListCreateAPIView): queryset = PurchaseOrderAttachment.objects.all() serializer_class = POAttachmentSerializer - filter_backends = [ - DjangoFilterBackend, - filters.OrderingFilter, - filters.SearchFilter, - ] - filter_fields = [ 'order', ] diff --git a/InvenTree/order/templates/order/po_attachments.html b/InvenTree/order/templates/order/po_attachments.html index 388b7197b6..79a0105a61 100644 --- a/InvenTree/order/templates/order/po_attachments.html +++ b/InvenTree/order/templates/order/po_attachments.html @@ -21,8 +21,9 @@ - - + + + @@ -31,6 +32,10 @@ +
{% trans "File" %}{% trans "Comment" %}{% trans "File" %}{% trans "Comment" %}{% trans "Uploaded" %}
{{ attachment.basename }} {{ attachment.comment }} + {% if attachment.upload_date %}{{ attachment.upload_date }}{% endif %} + {% if attachment.user %}{{ attachment.user.username }}{% endif %} +