From e9ed7bffc974cd988614c79867320b8c7b8fb81d Mon Sep 17 00:00:00 2001 From: Oliver Date: Sun, 28 Nov 2021 12:53:01 +1100 Subject: [PATCH 1/6] adds a "link" field to the InvenTree attachment model - Allows attachments to also serve as links to external files --- InvenTree/InvenTree/models.py | 15 ++++++-- .../migrations/0033_auto_20211128_0151.py | 25 +++++++++++++ .../migrations/0053_auto_20211128_0151.py | 35 +++++++++++++++++++ .../migrations/0075_auto_20211128_0151.py | 25 +++++++++++++ .../migrations/0070_auto_20211128_0151.py | 25 +++++++++++++ 5 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 InvenTree/build/migrations/0033_auto_20211128_0151.py create mode 100644 InvenTree/order/migrations/0053_auto_20211128_0151.py create mode 100644 InvenTree/part/migrations/0075_auto_20211128_0151.py create mode 100644 InvenTree/stock/migrations/0070_auto_20211128_0151.py diff --git a/InvenTree/InvenTree/models.py b/InvenTree/InvenTree/models.py index 0f8350f84f..4e7ed89e63 100644 --- a/InvenTree/InvenTree/models.py +++ b/InvenTree/InvenTree/models.py @@ -21,7 +21,8 @@ from django.dispatch import receiver from mptt.models import MPTTModel, TreeForeignKey from mptt.exceptions import InvalidMove -from .validators import validate_tree_name +from InvenTree.fields import InvenTreeURLField +from InvenTree.validators import validate_tree_name logger = logging.getLogger('inventree') @@ -89,6 +90,8 @@ class ReferenceIndexingMixin(models.Model): class InvenTreeAttachment(models.Model): """ Provides an abstracted class for managing file attachments. + An attachment can be either an uploaded file, or an external URL + Attributes: attachment: File comment: String descriptor for the attachment @@ -107,7 +110,15 @@ class InvenTreeAttachment(models.Model): return os.path.basename(self.attachment.name) attachment = models.FileField(upload_to=rename_attachment, verbose_name=_('Attachment'), - help_text=_('Select file to attach')) + help_text=_('Select file to attach'), + blank=True, null=True + ) + + link = InvenTreeURLField( + blank=True, null=True, + verbose_name=_('Link'), + help_text=_('Link to external URL') + ) comment = models.CharField(blank=True, max_length=100, verbose_name=_('Comment'), help_text=_('File comment')) diff --git a/InvenTree/build/migrations/0033_auto_20211128_0151.py b/InvenTree/build/migrations/0033_auto_20211128_0151.py new file mode 100644 index 0000000000..db8df848ce --- /dev/null +++ b/InvenTree/build/migrations/0033_auto_20211128_0151.py @@ -0,0 +1,25 @@ +# Generated by Django 3.2.5 on 2021-11-28 01:51 + +import InvenTree.fields +import InvenTree.models +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('build', '0032_auto_20211014_0632'), + ] + + operations = [ + migrations.AddField( + model_name='buildorderattachment', + name='link', + field=InvenTree.fields.InvenTreeURLField(blank=True, help_text='Link to external URL', null=True, verbose_name='Link'), + ), + migrations.AlterField( + model_name='buildorderattachment', + name='attachment', + field=models.FileField(blank=True, help_text='Select file to attach', null=True, upload_to=InvenTree.models.rename_attachment, verbose_name='Attachment'), + ), + ] diff --git a/InvenTree/order/migrations/0053_auto_20211128_0151.py b/InvenTree/order/migrations/0053_auto_20211128_0151.py new file mode 100644 index 0000000000..bbe029b4af --- /dev/null +++ b/InvenTree/order/migrations/0053_auto_20211128_0151.py @@ -0,0 +1,35 @@ +# Generated by Django 3.2.5 on 2021-11-28 01:51 + +import InvenTree.fields +import InvenTree.models +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('order', '0052_auto_20211014_0631'), + ] + + operations = [ + migrations.AddField( + model_name='purchaseorderattachment', + name='link', + field=InvenTree.fields.InvenTreeURLField(blank=True, help_text='Link to external URL', null=True, verbose_name='Link'), + ), + migrations.AddField( + model_name='salesorderattachment', + name='link', + field=InvenTree.fields.InvenTreeURLField(blank=True, help_text='Link to external URL', null=True, verbose_name='Link'), + ), + migrations.AlterField( + model_name='purchaseorderattachment', + name='attachment', + field=models.FileField(blank=True, help_text='Select file to attach', null=True, upload_to=InvenTree.models.rename_attachment, verbose_name='Attachment'), + ), + migrations.AlterField( + model_name='salesorderattachment', + name='attachment', + field=models.FileField(blank=True, help_text='Select file to attach', null=True, upload_to=InvenTree.models.rename_attachment, verbose_name='Attachment'), + ), + ] diff --git a/InvenTree/part/migrations/0075_auto_20211128_0151.py b/InvenTree/part/migrations/0075_auto_20211128_0151.py new file mode 100644 index 0000000000..d484a7adce --- /dev/null +++ b/InvenTree/part/migrations/0075_auto_20211128_0151.py @@ -0,0 +1,25 @@ +# Generated by Django 3.2.5 on 2021-11-28 01:51 + +import InvenTree.fields +import InvenTree.models +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('part', '0074_partcategorystar'), + ] + + operations = [ + migrations.AddField( + model_name='partattachment', + name='link', + field=InvenTree.fields.InvenTreeURLField(blank=True, help_text='Link to external URL', null=True, verbose_name='Link'), + ), + migrations.AlterField( + model_name='partattachment', + name='attachment', + field=models.FileField(blank=True, help_text='Select file to attach', null=True, upload_to=InvenTree.models.rename_attachment, verbose_name='Attachment'), + ), + ] diff --git a/InvenTree/stock/migrations/0070_auto_20211128_0151.py b/InvenTree/stock/migrations/0070_auto_20211128_0151.py new file mode 100644 index 0000000000..a2f6ef322d --- /dev/null +++ b/InvenTree/stock/migrations/0070_auto_20211128_0151.py @@ -0,0 +1,25 @@ +# Generated by Django 3.2.5 on 2021-11-28 01:51 + +import InvenTree.fields +import InvenTree.models +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('stock', '0069_auto_20211109_2347'), + ] + + operations = [ + migrations.AddField( + model_name='stockitemattachment', + name='link', + field=InvenTree.fields.InvenTreeURLField(blank=True, help_text='Link to external URL', null=True, verbose_name='Link'), + ), + migrations.AlterField( + model_name='stockitemattachment', + name='attachment', + field=models.FileField(blank=True, help_text='Select file to attach', null=True, upload_to=InvenTree.models.rename_attachment, verbose_name='Attachment'), + ), + ] From 6582fd3d04392bc301816fe514cbb64e0915b061 Mon Sep 17 00:00:00 2001 From: Oliver Date: Sun, 28 Nov 2021 12:57:37 +1100 Subject: [PATCH 2/6] Add 'link' field to attachment serializer --- InvenTree/build/serializers.py | 1 + InvenTree/order/serializers.py | 2 ++ InvenTree/part/serializers.py | 1 + InvenTree/stock/serializers.py | 1 + 4 files changed, 5 insertions(+) diff --git a/InvenTree/build/serializers.py b/InvenTree/build/serializers.py index 4d3680ea98..12f57f980a 100644 --- a/InvenTree/build/serializers.py +++ b/InvenTree/build/serializers.py @@ -525,6 +525,7 @@ class BuildAttachmentSerializer(InvenTreeAttachmentSerializer): 'pk', 'build', 'attachment', + 'link', 'filename', 'comment', 'upload_date', diff --git a/InvenTree/order/serializers.py b/InvenTree/order/serializers.py index 983cd01a63..a3674afb59 100644 --- a/InvenTree/order/serializers.py +++ b/InvenTree/order/serializers.py @@ -386,6 +386,7 @@ class POAttachmentSerializer(InvenTreeAttachmentSerializer): 'pk', 'order', 'attachment', + 'link', 'filename', 'comment', 'upload_date', @@ -607,6 +608,7 @@ class SOAttachmentSerializer(InvenTreeAttachmentSerializer): 'order', 'attachment', 'filename', + 'link', 'comment', 'upload_date', ] diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index 388faf1ca2..189665fb32 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -85,6 +85,7 @@ class PartAttachmentSerializer(InvenTreeAttachmentSerializer): 'part', 'attachment', 'filename', + 'link', 'comment', 'upload_date', ] diff --git a/InvenTree/stock/serializers.py b/InvenTree/stock/serializers.py index 840eb4793e..39ebd13acb 100644 --- a/InvenTree/stock/serializers.py +++ b/InvenTree/stock/serializers.py @@ -432,6 +432,7 @@ class StockItemAttachmentSerializer(InvenTree.serializers.InvenTreeAttachmentSer 'stock_item', 'attachment', 'filename', + 'link', 'comment', 'upload_date', 'user', From 0949bac175a1763404c59f688a677f2655b03dc3 Mon Sep 17 00:00:00 2001 From: Oliver Date: Sun, 28 Nov 2021 14:21:09 +1100 Subject: [PATCH 3/6] serializer fixes --- InvenTree/InvenTree/models.py | 10 ++++-- InvenTree/InvenTree/serializers.py | 56 +++++++++++++++++++++--------- InvenTree/build/serializers.py | 2 -- InvenTree/order/serializers.py | 4 --- InvenTree/part/serializers.py | 2 -- InvenTree/stock/serializers.py | 2 -- 6 files changed, 48 insertions(+), 28 deletions(-) diff --git a/InvenTree/InvenTree/models.py b/InvenTree/InvenTree/models.py index 4e7ed89e63..52c5384b63 100644 --- a/InvenTree/InvenTree/models.py +++ b/InvenTree/InvenTree/models.py @@ -107,7 +107,10 @@ class InvenTreeAttachment(models.Model): return "attachments" def __str__(self): - return os.path.basename(self.attachment.name) + if self.attachment is not None: + return os.path.basename(self.attachment.name) + else: + return str(self.link) attachment = models.FileField(upload_to=rename_attachment, verbose_name=_('Attachment'), help_text=_('Select file to attach'), @@ -134,7 +137,10 @@ class InvenTreeAttachment(models.Model): @property def basename(self): - return os.path.basename(self.attachment.name) + if self.attachment: + return os.path.basename(self.attachment.name) + else: + return None @basename.setter def basename(self, fn): diff --git a/InvenTree/InvenTree/serializers.py b/InvenTree/InvenTree/serializers.py index 90cc857cdd..c236bad603 100644 --- a/InvenTree/InvenTree/serializers.py +++ b/InvenTree/InvenTree/serializers.py @@ -239,22 +239,6 @@ class InvenTreeModelSerializer(serializers.ModelSerializer): return data -class InvenTreeAttachmentSerializer(InvenTreeModelSerializer): - """ - Special case of an InvenTreeModelSerializer, which handles an "attachment" model. - - The only real addition here is that we support "renaming" of the attachment file. - """ - - # The 'filename' field must be present in the serializer - filename = serializers.CharField( - label=_('Filename'), - required=False, - source='basename', - allow_blank=False, - ) - - class InvenTreeAttachmentSerializerField(serializers.FileField): """ Override the DRF native FileField serializer, @@ -284,6 +268,46 @@ class InvenTreeAttachmentSerializerField(serializers.FileField): return os.path.join(str(settings.MEDIA_URL), str(value)) +class InvenTreeAttachmentSerializer(InvenTreeModelSerializer): + """ + Special case of an InvenTreeModelSerializer, which handles an "attachment" model. + + The only real addition here is that we support "renaming" of the attachment file. + """ + + def validate(self, data): + """ + Validation for an attachment. Either a file or external link must be provided + """ + + data = super().validate(data) + + attachment = data.get('attachment', None) + link = data.get('link', None) + + if not attachment and not link: + raise ValidationError({ + 'attachment': _('Missing file'), + 'link': _('Missing external link'), + }) + + return data + + attachment = InvenTreeAttachmentSerializerField( + required=False, + allow_null=False, + ) + + # The 'filename' field must be present in the serializer + filename = serializers.CharField( + label=_('Filename'), + required=False, + source='basename', + allow_blank=False, + ) + + + class InvenTreeImageSerializerField(serializers.ImageField): """ Custom image serializer. diff --git a/InvenTree/build/serializers.py b/InvenTree/build/serializers.py index 12f57f980a..338e4a38b3 100644 --- a/InvenTree/build/serializers.py +++ b/InvenTree/build/serializers.py @@ -516,8 +516,6 @@ class BuildAttachmentSerializer(InvenTreeAttachmentSerializer): Serializer for a BuildAttachment """ - attachment = InvenTreeAttachmentSerializerField(required=True) - class Meta: model = BuildOrderAttachment diff --git a/InvenTree/order/serializers.py b/InvenTree/order/serializers.py index a3674afb59..c1356d78c9 100644 --- a/InvenTree/order/serializers.py +++ b/InvenTree/order/serializers.py @@ -377,8 +377,6 @@ class POAttachmentSerializer(InvenTreeAttachmentSerializer): Serializers for the PurchaseOrderAttachment model """ - attachment = InvenTreeAttachmentSerializerField(required=True) - class Meta: model = PurchaseOrderAttachment @@ -598,8 +596,6 @@ class SOAttachmentSerializer(InvenTreeAttachmentSerializer): Serializers for the SalesOrderAttachment model """ - attachment = InvenTreeAttachmentSerializerField(required=True) - class Meta: model = SalesOrderAttachment diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index 189665fb32..1be81c16ba 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -75,8 +75,6 @@ class PartAttachmentSerializer(InvenTreeAttachmentSerializer): Serializer for the PartAttachment class """ - attachment = InvenTreeAttachmentSerializerField(required=True) - class Meta: model = PartAttachment diff --git a/InvenTree/stock/serializers.py b/InvenTree/stock/serializers.py index 39ebd13acb..c74d674275 100644 --- a/InvenTree/stock/serializers.py +++ b/InvenTree/stock/serializers.py @@ -420,8 +420,6 @@ class StockItemAttachmentSerializer(InvenTree.serializers.InvenTreeAttachmentSer user_detail = InvenTree.serializers.UserSerializerBrief(source='user', read_only=True) - attachment = InvenTree.serializers.InvenTreeAttachmentSerializerField(required=True) - # TODO: Record the uploading user when creating or updating an attachment! class Meta: From 4ee55847f12030e5179fd6bd517b4164fefa34a2 Mon Sep 17 00:00:00 2001 From: Oliver Date: Sun, 28 Nov 2021 14:21:20 +1100 Subject: [PATCH 4/6] Refactor attachment tables --- InvenTree/build/templates/build/detail.html | 54 ++------ .../order/purchase_order_detail.html | 51 ++----- .../templates/order/sales_order_detail.html | 52 ++----- InvenTree/part/templates/part/detail.html | 57 ++------ InvenTree/stock/templates/stock/item.html | 55 ++------ InvenTree/templates/attachment_button.html | 3 + .../templates/js/translated/attachment.js | 131 +++++++++++++----- 7 files changed, 143 insertions(+), 260 deletions(-) diff --git a/InvenTree/build/templates/build/detail.html b/InvenTree/build/templates/build/detail.html index 31e9f38080..8479c2819f 100644 --- a/InvenTree/build/templates/build/detail.html +++ b/InvenTree/build/templates/build/detail.html @@ -431,53 +431,17 @@ enableDragAndDrop( } ); -// Callback for creating a new attachment -$('#new-attachment').click(function() { - - constructForm('{% url "api-build-attachment-list" %}', { - fields: { - attachment: {}, - comment: {}, - build: { - value: {{ build.pk }}, - hidden: true, - } - }, - method: 'POST', - onSuccess: reloadAttachmentTable, - title: '{% trans "Add Attachment" %}', - }); -}); - -loadAttachmentTable( - '{% url "api-build-attachment-list" %}', - { - filters: { - build: {{ build.pk }}, - }, - onEdit: function(pk) { - var url = `/api/build/attachment/${pk}/`; - - constructForm(url, { - fields: { - filename: {}, - comment: {}, - }, - onSuccess: reloadAttachmentTable, - title: '{% trans "Edit Attachment" %}', - }); - }, - onDelete: function(pk) { - - constructForm(`/api/build/attachment/${pk}/`, { - method: 'DELETE', - confirmMessage: '{% trans "Confirm Delete Operation" %}', - title: '{% trans "Delete Attachment" %}', - onSuccess: reloadAttachmentTable, - }); +loadAttachmentTable('{% url "api-build-attachment-list" %}', { + filters: { + build: {{ build.pk }}, + }, + fields: { + build: { + value: {{ build.pk }}, + hidden: true, } } -); +}); $('#edit-notes').click(function() { constructForm('{% url "api-build-detail" build.pk %}', { diff --git a/InvenTree/order/templates/order/purchase_order_detail.html b/InvenTree/order/templates/order/purchase_order_detail.html index 257707347a..3a6ea090d5 100644 --- a/InvenTree/order/templates/order/purchase_order_detail.html +++ b/InvenTree/order/templates/order/purchase_order_detail.html @@ -124,51 +124,16 @@ } ); - loadAttachmentTable( - '{% url "api-po-attachment-list" %}', - { - filters: { - order: {{ order.pk }}, - }, - onEdit: function(pk) { - var url = `/api/order/po/attachment/${pk}/`; - - constructForm(url, { - fields: { - filename: {}, - comment: {}, - }, - onSuccess: reloadAttachmentTable, - title: '{% trans "Edit Attachment" %}', - }); - }, - onDelete: function(pk) { - - constructForm(`/api/order/po/attachment/${pk}/`, { - method: 'DELETE', - confirmMessage: '{% trans "Confirm Delete Operation" %}', - title: '{% trans "Delete Attachment" %}', - onSuccess: reloadAttachmentTable, - }); + loadAttachmentTable('{% url "api-po-attachment-list" %}', { + filters: { + order: {{ order.pk }}, + }, + fields: { + order: { + value: {{ order.pk }}, + hidden: true, } } - ); - - $("#new-attachment").click(function() { - - constructForm('{% url "api-po-attachment-list" %}', { - method: 'POST', - fields: { - attachment: {}, - comment: {}, - order: { - value: {{ order.pk }}, - hidden: true, - }, - }, - reload: true, - title: '{% trans "Add Attachment" %}', - }); }); loadStockTable($("#stock-table"), { diff --git a/InvenTree/order/templates/order/sales_order_detail.html b/InvenTree/order/templates/order/sales_order_detail.html index 887ecd390c..1cf2ce06cc 100644 --- a/InvenTree/order/templates/order/sales_order_detail.html +++ b/InvenTree/order/templates/order/sales_order_detail.html @@ -110,55 +110,21 @@ }, label: 'attachment', success: function(data, status, xhr) { - location.reload(); + reloadAttachmentTable(); } } ); - loadAttachmentTable( - '{% url "api-so-attachment-list" %}', - { - filters: { - order: {{ order.pk }}, + loadAttachmentTable('{% url "api-so-attachment-list" %}', { + filters: { + order: {{ order.pk }}, + }, + fields: { + order: { + value: {{ order.pk }}, + hidden: true, }, - onEdit: function(pk) { - var url = `/api/order/so/attachment/${pk}/`; - - constructForm(url, { - fields: { - filename: {}, - comment: {}, - }, - onSuccess: reloadAttachmentTable, - title: '{% trans "Edit Attachment" %}', - }); - }, - onDelete: function(pk) { - constructForm(`/api/order/so/attachment/${pk}/`, { - method: 'DELETE', - confirmMessage: '{% trans "Confirm Delete Operation" %}', - title: '{% trans "Delete Attachment" %}', - onSuccess: reloadAttachmentTable, - }); - } } - ); - - $("#new-attachment").click(function() { - - constructForm('{% url "api-so-attachment-list" %}', { - method: 'POST', - fields: { - attachment: {}, - comment: {}, - order: { - value: {{ order.pk }}, - hidden: true - } - }, - onSuccess: reloadAttachmentTable, - title: '{% trans "Add Attachment" %}' - }); }); loadBuildTable($("#builds-table"), { diff --git a/InvenTree/part/templates/part/detail.html b/InvenTree/part/templates/part/detail.html index a737bfa6fc..4cf6f5e824 100644 --- a/InvenTree/part/templates/part/detail.html +++ b/InvenTree/part/templates/part/detail.html @@ -999,36 +999,17 @@ }); onPanelLoad("part-attachments", function() { - loadAttachmentTable( - '{% url "api-part-attachment-list" %}', - { - filters: { - part: {{ part.pk }}, - }, - onEdit: function(pk) { - var url = `/api/part/attachment/${pk}/`; - - constructForm(url, { - fields: { - filename: {}, - comment: {}, - }, - title: '{% trans "Edit Attachment" %}', - onSuccess: reloadAttachmentTable, - }); - }, - onDelete: function(pk) { - var url = `/api/part/attachment/${pk}/`; - - constructForm(url, { - method: 'DELETE', - confirmMessage: '{% trans "Confirm Delete Operation" %}', - title: '{% trans "Delete Attachment" %}', - onSuccess: reloadAttachmentTable, - }); + loadAttachmentTable('{% url "api-part-attachment-list" %}', { + filters: { + part: {{ part.pk }}, + }, + fields: { + part: { + value: {{ part.pk }}, + hidden: true } } - ); + }); enableDragAndDrop( '#attachment-dropzone', @@ -1043,26 +1024,6 @@ } } ); - - $("#new-attachment").click(function() { - - constructForm( - '{% url "api-part-attachment-list" %}', - { - method: 'POST', - fields: { - attachment: {}, - comment: {}, - part: { - value: {{ part.pk }}, - hidden: true, - } - }, - onSuccess: reloadAttachmentTable, - title: '{% trans "Add Attachment" %}', - } - ) - }); }); diff --git a/InvenTree/stock/templates/stock/item.html b/InvenTree/stock/templates/stock/item.html index 9bafc2633c..9cc6d85aeb 100644 --- a/InvenTree/stock/templates/stock/item.html +++ b/InvenTree/stock/templates/stock/item.html @@ -221,55 +221,16 @@ } ); - loadAttachmentTable( - '{% url "api-stock-attachment-list" %}', - { - filters: { - stock_item: {{ item.pk }}, - }, - onEdit: function(pk) { - var url = `/api/stock/attachment/${pk}/`; - - constructForm(url, { - fields: { - filename: {}, - comment: {}, - }, - title: '{% trans "Edit Attachment" %}', - onSuccess: reloadAttachmentTable - }); - }, - onDelete: function(pk) { - var url = `/api/stock/attachment/${pk}/`; - - constructForm(url, { - method: 'DELETE', - confirmMessage: '{% trans "Confirm Delete Operation" %}', - title: '{% trans "Delete Attachment" %}', - onSuccess: reloadAttachmentTable, - }); + loadAttachmentTable('{% url "api-stock-attachment-list" %}', { + filters: { + stock_item: {{ item.pk }}, + }, + fields: { + stock_item: { + value: {{ item.pk }}, + hidden: true, } } - ); - - $("#new-attachment").click(function() { - - constructForm( - '{% url "api-stock-attachment-list" %}', - { - method: 'POST', - fields: { - attachment: {}, - comment: {}, - stock_item: { - value: {{ item.pk }}, - hidden: true, - }, - }, - reload: true, - title: '{% trans "Add Attachment" %}', - } - ); }); loadStockTestResultsTable( diff --git a/InvenTree/templates/attachment_button.html b/InvenTree/templates/attachment_button.html index e1561010c0..d220f4829d 100644 --- a/InvenTree/templates/attachment_button.html +++ b/InvenTree/templates/attachment_button.html @@ -1,5 +1,8 @@ {% load i18n %} + \ No newline at end of file diff --git a/InvenTree/templates/js/translated/attachment.js b/InvenTree/templates/js/translated/attachment.js index 5ff5786588..87f2fbdcc0 100644 --- a/InvenTree/templates/js/translated/attachment.js +++ b/InvenTree/templates/js/translated/attachment.js @@ -6,10 +6,57 @@ */ /* exported + addAttachmentButtonCallbacks, loadAttachmentTable, reloadAttachmentTable, */ + +/* + * Add callbacks to buttons for creating new attachments. + * + * Note: Attachments can also be external links! + */ +function addAttachmentButtonCallbacks(url, fields={}) { + + // Callback for 'new attachment' button + $('#new-attachment').click(function() { + + var file_fields = { + attachment: {}, + comment: {}, + }; + + Object.assign(file_fields, fields); + + constructForm(url, { + fields: file_fields, + method: 'POST', + onSuccess: reloadAttachmentTable, + title: '{% trans "Add Attachment" %}', + }); + }); + + // Callback for 'new link' button + $('#new-attachment-link').click(function() { + + var link_fields = { + link: {}, + comment: {}, + }; + + Object.assign(link_fields, fields); + + constructForm(url, { + fields: link_fields, + method: 'POST', + onSuccess: reloadAttachmentTable, + title: '{% trans "Add Link" %}', + }); + }); +} + + function reloadAttachmentTable() { $('#attachment-table').bootstrapTable('refresh'); @@ -20,6 +67,8 @@ function loadAttachmentTable(url, options) { var table = options.table || '#attachment-table'; + addAttachmentButtonCallbacks(url, options.fields || {}); + $(table).inventreeTable({ url: url, name: options.name || 'attachments', @@ -34,56 +83,70 @@ function loadAttachmentTable(url, options) { $(table).find('.button-attachment-edit').click(function() { var pk = $(this).attr('pk'); - if (options.onEdit) { - options.onEdit(pk); - } + constructForm(`${url}${pk}/`, { + fields: { + link: {}, + comment: {}, + }, + onSuccess: reloadAttachmentTable, + title: '{% trans "Edit Attachment" %}', + }); }); // Add callback for 'delete' button $(table).find('.button-attachment-delete').click(function() { var pk = $(this).attr('pk'); - if (options.onDelete) { - options.onDelete(pk); - } + constructForm(`${url}${pk}/`, { + method: 'DELETE', + confirmMessage: '{% trans "Confirm Delete" %}', + title: '{% trans "Delete Attachment" %}', + onSuccess: reloadAttachmentTable, + }); }); }, columns: [ { field: 'attachment', - title: '{% trans "File" %}', - formatter: function(value) { + title: '{% trans "Attachment" %}', + formatter: function(value, row) { - var icon = 'fa-file-alt'; + if (row.attachment) { + var icon = 'fa-file-alt'; - var fn = value.toLowerCase(); + var fn = value.toLowerCase(); - if (fn.endsWith('.csv')) { - icon = 'fa-file-csv'; - } else if (fn.endsWith('.pdf')) { - icon = 'fa-file-pdf'; - } else if (fn.endsWith('.xls') || fn.endsWith('.xlsx')) { - icon = 'fa-file-excel'; - } else if (fn.endsWith('.doc') || fn.endsWith('.docx')) { - icon = 'fa-file-word'; - } else if (fn.endsWith('.zip') || fn.endsWith('.7z')) { - icon = 'fa-file-archive'; + if (fn.endsWith('.csv')) { + icon = 'fa-file-csv'; + } else if (fn.endsWith('.pdf')) { + icon = 'fa-file-pdf'; + } else if (fn.endsWith('.xls') || fn.endsWith('.xlsx')) { + icon = 'fa-file-excel'; + } else if (fn.endsWith('.doc') || fn.endsWith('.docx')) { + icon = 'fa-file-word'; + } else if (fn.endsWith('.zip') || fn.endsWith('.7z')) { + icon = 'fa-file-archive'; + } else { + var images = ['.png', '.jpg', '.bmp', '.gif', '.svg', '.tif']; + + images.forEach(function(suffix) { + if (fn.endsWith(suffix)) { + icon = 'fa-file-image'; + } + }); + } + + var split = value.split('/'); + var filename = split[split.length - 1]; + + var html = ` ${filename}`; + + return renderLink(html, value); + } else if (row.link) { + return renderLink(row.link, row.link); } else { - var images = ['.png', '.jpg', '.bmp', '.gif', '.svg', '.tif']; - - images.forEach(function(suffix) { - if (fn.endsWith(suffix)) { - icon = 'fa-file-image'; - } - }); + return '-'; } - - var split = value.split('/'); - var filename = split[split.length - 1]; - - var html = ` ${filename}`; - - return renderLink(html, value); } }, { From 78309c19156609b4bb3462333231155d7b60755b Mon Sep 17 00:00:00 2001 From: Oliver Date: Sun, 28 Nov 2021 22:29:01 +1100 Subject: [PATCH 5/6] Move validation to the model class --- InvenTree/InvenTree/models.py | 11 +++++++++++ InvenTree/InvenTree/serializers.py | 18 ------------------ .../templates/js/translated/attachment.js | 6 ++++++ 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/InvenTree/InvenTree/models.py b/InvenTree/InvenTree/models.py index 52c5384b63..1b130cf599 100644 --- a/InvenTree/InvenTree/models.py +++ b/InvenTree/InvenTree/models.py @@ -98,6 +98,7 @@ class InvenTreeAttachment(models.Model): user: User associated with file upload upload_date: Date the file was uploaded """ + def getSubdir(self): """ Return the subdirectory under which attachments should be stored. @@ -106,6 +107,16 @@ class InvenTreeAttachment(models.Model): return "attachments" + def save(self, *args, **kwargs): + # Either 'attachment' or 'link' must be specified! + if not self.attachment and not self.link: + raise ValidationError({ + 'attachment': _('Missing file'), + 'link': _('Missing external link'), + }) + + super().save(*args, **kwargs) + def __str__(self): if self.attachment is not None: return os.path.basename(self.attachment.name) diff --git a/InvenTree/InvenTree/serializers.py b/InvenTree/InvenTree/serializers.py index c236bad603..36d344c867 100644 --- a/InvenTree/InvenTree/serializers.py +++ b/InvenTree/InvenTree/serializers.py @@ -275,24 +275,6 @@ class InvenTreeAttachmentSerializer(InvenTreeModelSerializer): The only real addition here is that we support "renaming" of the attachment file. """ - def validate(self, data): - """ - Validation for an attachment. Either a file or external link must be provided - """ - - data = super().validate(data) - - attachment = data.get('attachment', None) - link = data.get('link', None) - - if not attachment and not link: - raise ValidationError({ - 'attachment': _('Missing file'), - 'link': _('Missing external link'), - }) - - return data - attachment = InvenTreeAttachmentSerializerField( required=False, allow_null=False, diff --git a/InvenTree/templates/js/translated/attachment.js b/InvenTree/templates/js/translated/attachment.js index 87f2fbdcc0..fe8a0580c1 100644 --- a/InvenTree/templates/js/translated/attachment.js +++ b/InvenTree/templates/js/translated/attachment.js @@ -88,6 +88,12 @@ function loadAttachmentTable(url, options) { link: {}, comment: {}, }, + processResults: function(data, fields, opts) { + // Remove the "link" field if the attachment is a file! + if (data.attachment) { + delete opts.fields.link; + } + }, onSuccess: reloadAttachmentTable, title: '{% trans "Edit Attachment" %}', }); From 32be39774ff75e2c011f7f9a0b85a12950d0d994 Mon Sep 17 00:00:00 2001 From: Oliver Date: Sun, 28 Nov 2021 22:36:47 +1100 Subject: [PATCH 6/6] PEP fixes --- InvenTree/InvenTree/models.py | 2 +- InvenTree/InvenTree/serializers.py | 1 - InvenTree/build/serializers.py | 2 +- InvenTree/order/serializers.py | 1 - InvenTree/templates/js/translated/attachment.js | 3 ++- 5 files changed, 4 insertions(+), 5 deletions(-) diff --git a/InvenTree/InvenTree/models.py b/InvenTree/InvenTree/models.py index 1b130cf599..0448a69781 100644 --- a/InvenTree/InvenTree/models.py +++ b/InvenTree/InvenTree/models.py @@ -116,7 +116,7 @@ class InvenTreeAttachment(models.Model): }) super().save(*args, **kwargs) - + def __str__(self): if self.attachment is not None: return os.path.basename(self.attachment.name) diff --git a/InvenTree/InvenTree/serializers.py b/InvenTree/InvenTree/serializers.py index 36d344c867..3785cfb292 100644 --- a/InvenTree/InvenTree/serializers.py +++ b/InvenTree/InvenTree/serializers.py @@ -289,7 +289,6 @@ class InvenTreeAttachmentSerializer(InvenTreeModelSerializer): ) - class InvenTreeImageSerializerField(serializers.ImageField): """ Custom image serializer. diff --git a/InvenTree/build/serializers.py b/InvenTree/build/serializers.py index 338e4a38b3..35a3bb3baa 100644 --- a/InvenTree/build/serializers.py +++ b/InvenTree/build/serializers.py @@ -16,7 +16,7 @@ from rest_framework import serializers from rest_framework.serializers import ValidationError from InvenTree.serializers import InvenTreeModelSerializer, InvenTreeAttachmentSerializer -from InvenTree.serializers import InvenTreeAttachmentSerializerField, UserSerializerBrief +from InvenTree.serializers import UserSerializerBrief import InvenTree.helpers from InvenTree.serializers import InvenTreeDecimalField diff --git a/InvenTree/order/serializers.py b/InvenTree/order/serializers.py index c1356d78c9..0528d57596 100644 --- a/InvenTree/order/serializers.py +++ b/InvenTree/order/serializers.py @@ -24,7 +24,6 @@ from InvenTree.serializers import InvenTreeAttachmentSerializer from InvenTree.serializers import InvenTreeModelSerializer from InvenTree.serializers import InvenTreeDecimalField from InvenTree.serializers import InvenTreeMoneySerializer -from InvenTree.serializers import InvenTreeAttachmentSerializerField from InvenTree.status_codes import StockStatus from part.serializers import PartBriefSerializer diff --git a/InvenTree/templates/js/translated/attachment.js b/InvenTree/templates/js/translated/attachment.js index fe8a0580c1..5c5af5682f 100644 --- a/InvenTree/templates/js/translated/attachment.js +++ b/InvenTree/templates/js/translated/attachment.js @@ -149,7 +149,8 @@ function loadAttachmentTable(url, options) { return renderLink(html, value); } else if (row.link) { - return renderLink(row.link, row.link); + var html = ` ${row.link}`; + return renderLink(html, row.link); } else { return '-'; }