From 45d50fc6186e85f18f9264074fe03235ee294ec7 Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 16 Feb 2023 09:52:13 +1100 Subject: [PATCH] Company attachment (#4346) * Adds new model, API, serializers, etc - Refactor InvenTreeAttachmentSerializer class - Reduces code duplication * Update front end * Increment API version --- InvenTree/InvenTree/api_version.py | 5 ++- InvenTree/InvenTree/serializers.py | 21 +++++++++ InvenTree/build/serializers.py | 16 +------ InvenTree/company/api.py | 35 +++++++++++++-- .../migrations/0054_companyattachment.py | 33 ++++++++++++++ InvenTree/company/models.py | 19 ++++++++ InvenTree/company/serializers.py | 34 +++++++-------- .../company/templates/company/detail.html | 43 +++++++++++++++++++ .../company/templates/company/sidebar.html | 2 + .../company/supplier_part_navbar.html | 33 -------------- InvenTree/order/serializers.py | 32 ++------------ InvenTree/part/serializers.py | 16 +------ InvenTree/stock/serializers.py | 18 +------- InvenTree/users/models.py | 2 + 14 files changed, 182 insertions(+), 127 deletions(-) create mode 100644 InvenTree/company/migrations/0054_companyattachment.py delete mode 100644 InvenTree/company/templates/company/supplier_part_navbar.html diff --git a/InvenTree/InvenTree/api_version.py b/InvenTree/InvenTree/api_version.py index 7f207ec394..155b065b3c 100644 --- a/InvenTree/InvenTree/api_version.py +++ b/InvenTree/InvenTree/api_version.py @@ -2,11 +2,14 @@ # InvenTree API version -INVENTREE_API_VERSION = 94 +INVENTREE_API_VERSION = 95 """ Increment this API version number whenever there is a significant change to the API that any clients need to know about +v95 -> 2023-02-16 : https://github.com/inventree/InvenTree/pull/4346 + - Adds "CompanyAttachment" model (and associated API endpoints) + v94 -> 2023-02-10 : https://github.com/inventree/InvenTree/pull/4327 - Adds API endpoints for the "Group" auth model diff --git a/InvenTree/InvenTree/serializers.py b/InvenTree/InvenTree/serializers.py index a7d46745ae..39f9aecd4c 100644 --- a/InvenTree/InvenTree/serializers.py +++ b/InvenTree/InvenTree/serializers.py @@ -282,6 +282,25 @@ class InvenTreeAttachmentSerializer(InvenTreeModelSerializer): The only real addition here is that we support "renaming" of the attachment file. """ + @staticmethod + def attachment_fields(extra_fields=None): + """Default set of fields for an attachment serializer""" + fields = [ + 'pk', + 'attachment', + 'filename', + 'link', + 'comment', + 'upload_date', + 'user', + 'user_detail', + ] + + if extra_fields: + fields += extra_fields + + return fields + user_detail = UserSerializer(source='user', read_only=True, many=False) attachment = InvenTreeAttachmentSerializerField( @@ -297,6 +316,8 @@ class InvenTreeAttachmentSerializer(InvenTreeModelSerializer): allow_blank=False, ) + upload_date = serializers.DateField(read_only=True) + class InvenTreeImageSerializerField(serializers.ImageField): """Custom image serializer. diff --git a/InvenTree/build/serializers.py b/InvenTree/build/serializers.py index 1f2844bda8..78d3dc98ae 100644 --- a/InvenTree/build/serializers.py +++ b/InvenTree/build/serializers.py @@ -929,18 +929,6 @@ class BuildAttachmentSerializer(InvenTreeAttachmentSerializer): """Serializer metaclass""" model = BuildOrderAttachment - fields = [ - 'pk', + fields = InvenTreeAttachmentSerializer.attachment_fields([ 'build', - 'attachment', - 'link', - 'filename', - 'comment', - 'upload_date', - 'user', - 'user_detail', - ] - - read_only_fields = [ - 'upload_date', - ] + ]) diff --git a/InvenTree/company/api.py b/InvenTree/company/api.py index 7095b2fff8..13c0901e43 100644 --- a/InvenTree/company/api.py +++ b/InvenTree/company/api.py @@ -15,10 +15,10 @@ from InvenTree.mixins import (ListCreateAPI, RetrieveUpdateAPI, RetrieveUpdateDestroyAPI) from plugin.serializers import MetadataSerializer -from .models import (Company, ManufacturerPart, ManufacturerPartAttachment, - ManufacturerPartParameter, SupplierPart, - SupplierPriceBreak) -from .serializers import (CompanySerializer, +from .models import (Company, CompanyAttachment, ManufacturerPart, + ManufacturerPartAttachment, ManufacturerPartParameter, + SupplierPart, SupplierPriceBreak) +from .serializers import (CompanyAttachmentSerializer, CompanySerializer, ManufacturerPartAttachmentSerializer, ManufacturerPartParameterSerializer, ManufacturerPartSerializer, SupplierPartSerializer, @@ -96,6 +96,28 @@ class CompanyMetadata(RetrieveUpdateAPI): queryset = Company.objects.all() +class CompanyAttachmentList(AttachmentMixin, ListCreateDestroyAPIView): + """API endpoint for the CompanyAttachment model""" + + queryset = CompanyAttachment.objects.all() + serializer_class = CompanyAttachmentSerializer + + filter_backends = [ + DjangoFilterBackend, + ] + + filterset_fields = [ + 'company', + ] + + +class CompanyAttachmentDetail(AttachmentMixin, RetrieveUpdateDestroyAPI): + """Detail endpoint for CompanyAttachment model.""" + + queryset = CompanyAttachment.objects.all() + serializer_class = CompanyAttachmentSerializer + + class ManufacturerPartFilter(rest_filters.FilterSet): """Custom API filters for the ManufacturerPart list endpoint.""" @@ -521,6 +543,11 @@ company_api_urls = [ re_path(r'^.*$', CompanyDetail.as_view(), name='api-company-detail'), ])), + re_path(r'^attachment/', include([ + re_path(r'^(?P\d+)/', CompanyAttachmentDetail.as_view(), name='api-company-attachment-detail'), + re_path(r'^$', CompanyAttachmentList.as_view(), name='api-company-attachment-list'), + ])), + re_path(r'^.*$', CompanyList.as_view(), name='api-company-list'), ] diff --git a/InvenTree/company/migrations/0054_companyattachment.py b/InvenTree/company/migrations/0054_companyattachment.py new file mode 100644 index 0000000000..44a415fce3 --- /dev/null +++ b/InvenTree/company/migrations/0054_companyattachment.py @@ -0,0 +1,33 @@ +# Generated by Django 3.2.16 on 2023-02-15 12:55 + +import InvenTree.fields +import InvenTree.models +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), + ('company', '0053_supplierpart_updated'), + ] + + operations = [ + migrations.CreateModel( + name='CompanyAttachment', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('attachment', models.FileField(blank=True, help_text='Select file to attach', null=True, upload_to=InvenTree.models.rename_attachment, verbose_name='Attachment')), + ('link', InvenTree.fields.InvenTreeURLField(blank=True, help_text='Link to external URL', null=True, verbose_name='Link')), + ('comment', models.CharField(blank=True, help_text='File comment', max_length=100, verbose_name='Comment')), + ('upload_date', models.DateField(auto_now_add=True, null=True, verbose_name='upload date')), + ('company', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='attachments', to='company.company', verbose_name='Company')), + ('user', models.ForeignKey(blank=True, help_text='User', null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='User')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/InvenTree/company/models.py b/InvenTree/company/models.py index 6add14d125..3227af4707 100644 --- a/InvenTree/company/models.py +++ b/InvenTree/company/models.py @@ -205,6 +205,25 @@ class Company(MetadataMixin, models.Model): return stock.objects.filter(Q(supplier_part__supplier=self.id) | Q(supplier_part__manufacturer_part__manufacturer=self.id)).all() +class CompanyAttachment(InvenTreeAttachment): + """Model for storing file or URL attachments against a Company object""" + + @staticmethod + def get_api_url(): + """Return the API URL associated with this model""" + return reverse('api-company-attachment-list') + + def getSubdir(self): + """Return the subdirectory where these attachments are uploaded""" + return os.path.join('company_files', str(self.company.pk)) + + company = models.ForeignKey( + Company, on_delete=models.CASCADE, + verbose_name=_('Company'), + related_name='attachments', + ) + + class Contact(models.Model): """A Contact represents a person who works at a particular company. A Company may have zero or more associated Contact objects. diff --git a/InvenTree/company/serializers.py b/InvenTree/company/serializers.py index 52d4f55921..79c8da357f 100644 --- a/InvenTree/company/serializers.py +++ b/InvenTree/company/serializers.py @@ -17,9 +17,9 @@ from InvenTree.serializers import (InvenTreeAttachmentSerializer, InvenTreeMoneySerializer, RemoteImageMixin) from part.serializers import PartBriefSerializer -from .models import (Company, ManufacturerPart, ManufacturerPartAttachment, - ManufacturerPartParameter, SupplierPart, - SupplierPriceBreak) +from .models import (Company, CompanyAttachment, ManufacturerPart, + ManufacturerPartAttachment, ManufacturerPartParameter, + SupplierPart, SupplierPriceBreak) class CompanyBriefSerializer(InvenTreeModelSerializer): @@ -126,6 +126,18 @@ class CompanySerializer(RemoteImageMixin, InvenTreeModelSerializer): return self.instance +class CompanyAttachmentSerializer(InvenTreeAttachmentSerializer): + """Serializer for the CompanyAttachment class""" + + class Meta: + """Metaclass defines serializer options""" + model = CompanyAttachment + + fields = InvenTreeAttachmentSerializer.attachment_fields([ + 'company', + ]) + + class ManufacturerPartSerializer(InvenTreeModelSerializer): """Serializer for ManufacturerPart object.""" @@ -179,21 +191,9 @@ class ManufacturerPartAttachmentSerializer(InvenTreeAttachmentSerializer): model = ManufacturerPartAttachment - fields = [ - 'pk', + fields = InvenTreeAttachmentSerializer.attachment_fields([ 'manufacturer_part', - 'attachment', - 'filename', - 'link', - 'comment', - 'upload_date', - 'user', - 'user_detail', - ] - - read_only_fields = [ - 'upload_date', - ] + ]) class ManufacturerPartParameterSerializer(InvenTreeModelSerializer): diff --git a/InvenTree/company/templates/company/detail.html b/InvenTree/company/templates/company/detail.html index e77f81a0f8..49780c56ff 100644 --- a/InvenTree/company/templates/company/detail.html +++ b/InvenTree/company/templates/company/detail.html @@ -194,11 +194,54 @@ +
+
+
+

{% trans "Attachments" %}

+ {% include "spacer.html" %} +
+ {% include "attachment_button.html" %} +
+
+
+
+ {% include "attachment_table.html" %} +
+
+ {% endblock %} {% block js_ready %} {{ block.super }} + onPanelLoad("attachments", function() { + loadAttachmentTable('{% url "api-company-attachment-list" %}', { + filters: { + company: {{ company.pk }}, + }, + fields: { + company: { + value: {{ company.pk }}, + hidden: true + } + } + }); + + enableDragAndDrop( + '#attachment-dropzone', + '{% url "api-company-attachment-list" %}', + { + data: { + company: {{ company.id }}, + }, + label: 'attachment', + success: function(data, status, xhr) { + reloadAttachmentTable(); + } + } + ); + }); + onPanelLoad('company-notes', function() { setupNotesField( diff --git a/InvenTree/company/templates/company/sidebar.html b/InvenTree/company/templates/company/sidebar.html index 0063853732..ad40c03a0b 100644 --- a/InvenTree/company/templates/company/sidebar.html +++ b/InvenTree/company/templates/company/sidebar.html @@ -24,3 +24,5 @@ {% endif %} {% trans "Notes" as text %} {% include "sidebar_item.html" with label='company-notes' text=text icon="fa-clipboard" %} +{% trans "Attachments" as text %} +{% include "sidebar_item.html" with label='attachments' text=text icon="fa-paperclip" %} diff --git a/InvenTree/company/templates/company/supplier_part_navbar.html b/InvenTree/company/templates/company/supplier_part_navbar.html deleted file mode 100644 index d83338f1e0..0000000000 --- a/InvenTree/company/templates/company/supplier_part_navbar.html +++ /dev/null @@ -1,33 +0,0 @@ -{% load i18n %} -{% load inventree_extras %} - - diff --git a/InvenTree/order/serializers.py b/InvenTree/order/serializers.py index f240f29de1..fe960e7cf0 100644 --- a/InvenTree/order/serializers.py +++ b/InvenTree/order/serializers.py @@ -644,21 +644,9 @@ class PurchaseOrderAttachmentSerializer(InvenTreeAttachmentSerializer): model = order.models.PurchaseOrderAttachment - fields = [ - 'pk', + fields = InvenTreeAttachmentSerializer.attachment_fields([ 'order', - 'attachment', - 'link', - 'filename', - 'comment', - 'upload_date', - 'user', - 'user_detail', - ] - - read_only_fields = [ - 'upload_date', - ] + ]) class SalesOrderSerializer(AbstractOrderSerializer, InvenTreeModelSerializer): @@ -1416,18 +1404,6 @@ class SalesOrderAttachmentSerializer(InvenTreeAttachmentSerializer): model = order.models.SalesOrderAttachment - fields = [ - 'pk', + fields = InvenTreeAttachmentSerializer.attachment_fields([ 'order', - 'attachment', - 'filename', - 'link', - 'comment', - 'upload_date', - 'user', - 'user_detail', - ] - - read_only_fields = [ - 'upload_date', - ] + ]) diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index 9745275407..ad065bc5e1 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -106,21 +106,9 @@ class PartAttachmentSerializer(InvenTreeAttachmentSerializer): """Metaclass defining serializer fields""" model = PartAttachment - fields = [ - 'pk', + fields = InvenTreeAttachmentSerializer.attachment_fields([ 'part', - 'attachment', - 'filename', - 'link', - 'comment', - 'upload_date', - 'user', - 'user_detail', - ] - - read_only_fields = [ - 'upload_date', - ] + ]) class PartTestTemplateSerializer(InvenTreeModelSerializer): diff --git a/InvenTree/stock/serializers.py b/InvenTree/stock/serializers.py index 9708ed15d6..1a3b14af23 100644 --- a/InvenTree/stock/serializers.py +++ b/InvenTree/stock/serializers.py @@ -622,23 +622,9 @@ class StockItemAttachmentSerializer(InvenTree.serializers.InvenTreeAttachmentSer model = StockItemAttachment - fields = [ - 'pk', + fields = InvenTree.serializers.InvenTreeAttachmentSerializer.attachment_fields([ 'stock_item', - 'attachment', - 'filename', - 'link', - 'comment', - 'upload_date', - 'user', - 'user_detail', - ] - - read_only_fields = [ - 'upload_date', - 'user', - 'user_detail' - ] + ]) class StockItemTestResultSerializer(InvenTree.serializers.InvenTreeModelSerializer): diff --git a/InvenTree/users/models.py b/InvenTree/users/models.py index b754ae65e9..985b1228bd 100644 --- a/InvenTree/users/models.py +++ b/InvenTree/users/models.py @@ -130,6 +130,7 @@ class RuleSet(models.Model): ], 'purchase_order': [ 'company_company', + 'company_companyattachment', 'company_manufacturerpart', 'company_manufacturerpartparameter', 'company_supplierpart', @@ -142,6 +143,7 @@ class RuleSet(models.Model): ], 'sales_order': [ 'company_company', + 'company_companyattachment', 'order_salesorder', 'order_salesorderallocation', 'order_salesorderattachment',