mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge branch 'inventree:master' into matmair/issue2279
This commit is contained in:
commit
e34de8204a
@ -21,7 +21,8 @@ from django.dispatch import receiver
|
|||||||
from mptt.models import MPTTModel, TreeForeignKey
|
from mptt.models import MPTTModel, TreeForeignKey
|
||||||
from mptt.exceptions import InvalidMove
|
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')
|
logger = logging.getLogger('inventree')
|
||||||
@ -89,12 +90,15 @@ class ReferenceIndexingMixin(models.Model):
|
|||||||
class InvenTreeAttachment(models.Model):
|
class InvenTreeAttachment(models.Model):
|
||||||
""" Provides an abstracted class for managing file attachments.
|
""" Provides an abstracted class for managing file attachments.
|
||||||
|
|
||||||
|
An attachment can be either an uploaded file, or an external URL
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
attachment: File
|
attachment: File
|
||||||
comment: String descriptor for the attachment
|
comment: String descriptor for the attachment
|
||||||
user: User associated with file upload
|
user: User associated with file upload
|
||||||
upload_date: Date the file was uploaded
|
upload_date: Date the file was uploaded
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def getSubdir(self):
|
def getSubdir(self):
|
||||||
"""
|
"""
|
||||||
Return the subdirectory under which attachments should be stored.
|
Return the subdirectory under which attachments should be stored.
|
||||||
@ -103,11 +107,32 @@ class InvenTreeAttachment(models.Model):
|
|||||||
|
|
||||||
return "attachments"
|
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):
|
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'),
|
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'))
|
comment = models.CharField(blank=True, max_length=100, verbose_name=_('Comment'), help_text=_('File comment'))
|
||||||
|
|
||||||
@ -123,7 +148,10 @@ class InvenTreeAttachment(models.Model):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def basename(self):
|
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
|
@basename.setter
|
||||||
def basename(self, fn):
|
def basename(self, fn):
|
||||||
|
@ -239,22 +239,6 @@ class InvenTreeModelSerializer(serializers.ModelSerializer):
|
|||||||
return data
|
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):
|
class InvenTreeAttachmentSerializerField(serializers.FileField):
|
||||||
"""
|
"""
|
||||||
Override the DRF native FileField serializer,
|
Override the DRF native FileField serializer,
|
||||||
@ -284,6 +268,27 @@ class InvenTreeAttachmentSerializerField(serializers.FileField):
|
|||||||
return os.path.join(str(settings.MEDIA_URL), str(value))
|
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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
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):
|
class InvenTreeImageSerializerField(serializers.ImageField):
|
||||||
"""
|
"""
|
||||||
Custom image serializer.
|
Custom image serializer.
|
||||||
|
25
InvenTree/build/migrations/0033_auto_20211128_0151.py
Normal file
25
InvenTree/build/migrations/0033_auto_20211128_0151.py
Normal file
@ -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'),
|
||||||
|
),
|
||||||
|
]
|
@ -16,7 +16,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 InvenTreeAttachmentSerializerField, UserSerializerBrief
|
from InvenTree.serializers import UserSerializerBrief
|
||||||
|
|
||||||
import InvenTree.helpers
|
import InvenTree.helpers
|
||||||
from InvenTree.serializers import InvenTreeDecimalField
|
from InvenTree.serializers import InvenTreeDecimalField
|
||||||
@ -516,8 +516,6 @@ class BuildAttachmentSerializer(InvenTreeAttachmentSerializer):
|
|||||||
Serializer for a BuildAttachment
|
Serializer for a BuildAttachment
|
||||||
"""
|
"""
|
||||||
|
|
||||||
attachment = InvenTreeAttachmentSerializerField(required=True)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = BuildOrderAttachment
|
model = BuildOrderAttachment
|
||||||
|
|
||||||
@ -525,6 +523,7 @@ class BuildAttachmentSerializer(InvenTreeAttachmentSerializer):
|
|||||||
'pk',
|
'pk',
|
||||||
'build',
|
'build',
|
||||||
'attachment',
|
'attachment',
|
||||||
|
'link',
|
||||||
'filename',
|
'filename',
|
||||||
'comment',
|
'comment',
|
||||||
'upload_date',
|
'upload_date',
|
||||||
|
@ -431,53 +431,17 @@ enableDragAndDrop(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Callback for creating a new attachment
|
loadAttachmentTable('{% url "api-build-attachment-list" %}', {
|
||||||
$('#new-attachment').click(function() {
|
filters: {
|
||||||
|
build: {{ build.pk }},
|
||||||
constructForm('{% url "api-build-attachment-list" %}', {
|
},
|
||||||
fields: {
|
fields: {
|
||||||
attachment: {},
|
build: {
|
||||||
comment: {},
|
value: {{ build.pk }},
|
||||||
build: {
|
hidden: true,
|
||||||
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,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
$('#edit-notes').click(function() {
|
$('#edit-notes').click(function() {
|
||||||
constructForm('{% url "api-build-detail" build.pk %}', {
|
constructForm('{% url "api-build-detail" build.pk %}', {
|
||||||
|
35
InvenTree/order/migrations/0053_auto_20211128_0151.py
Normal file
35
InvenTree/order/migrations/0053_auto_20211128_0151.py
Normal file
@ -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'),
|
||||||
|
),
|
||||||
|
]
|
@ -24,7 +24,6 @@ from InvenTree.serializers import InvenTreeAttachmentSerializer
|
|||||||
from InvenTree.serializers import InvenTreeModelSerializer
|
from InvenTree.serializers import InvenTreeModelSerializer
|
||||||
from InvenTree.serializers import InvenTreeDecimalField
|
from InvenTree.serializers import InvenTreeDecimalField
|
||||||
from InvenTree.serializers import InvenTreeMoneySerializer
|
from InvenTree.serializers import InvenTreeMoneySerializer
|
||||||
from InvenTree.serializers import InvenTreeAttachmentSerializerField
|
|
||||||
from InvenTree.status_codes import StockStatus
|
from InvenTree.status_codes import StockStatus
|
||||||
|
|
||||||
from part.serializers import PartBriefSerializer
|
from part.serializers import PartBriefSerializer
|
||||||
@ -377,8 +376,6 @@ class POAttachmentSerializer(InvenTreeAttachmentSerializer):
|
|||||||
Serializers for the PurchaseOrderAttachment model
|
Serializers for the PurchaseOrderAttachment model
|
||||||
"""
|
"""
|
||||||
|
|
||||||
attachment = InvenTreeAttachmentSerializerField(required=True)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PurchaseOrderAttachment
|
model = PurchaseOrderAttachment
|
||||||
|
|
||||||
@ -386,6 +383,7 @@ class POAttachmentSerializer(InvenTreeAttachmentSerializer):
|
|||||||
'pk',
|
'pk',
|
||||||
'order',
|
'order',
|
||||||
'attachment',
|
'attachment',
|
||||||
|
'link',
|
||||||
'filename',
|
'filename',
|
||||||
'comment',
|
'comment',
|
||||||
'upload_date',
|
'upload_date',
|
||||||
@ -597,8 +595,6 @@ class SOAttachmentSerializer(InvenTreeAttachmentSerializer):
|
|||||||
Serializers for the SalesOrderAttachment model
|
Serializers for the SalesOrderAttachment model
|
||||||
"""
|
"""
|
||||||
|
|
||||||
attachment = InvenTreeAttachmentSerializerField(required=True)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SalesOrderAttachment
|
model = SalesOrderAttachment
|
||||||
|
|
||||||
@ -607,6 +603,7 @@ class SOAttachmentSerializer(InvenTreeAttachmentSerializer):
|
|||||||
'order',
|
'order',
|
||||||
'attachment',
|
'attachment',
|
||||||
'filename',
|
'filename',
|
||||||
|
'link',
|
||||||
'comment',
|
'comment',
|
||||||
'upload_date',
|
'upload_date',
|
||||||
]
|
]
|
||||||
|
@ -124,51 +124,16 @@
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
loadAttachmentTable(
|
loadAttachmentTable('{% url "api-po-attachment-list" %}', {
|
||||||
'{% url "api-po-attachment-list" %}',
|
filters: {
|
||||||
{
|
order: {{ order.pk }},
|
||||||
filters: {
|
},
|
||||||
order: {{ order.pk }},
|
fields: {
|
||||||
},
|
order: {
|
||||||
onEdit: function(pk) {
|
value: {{ order.pk }},
|
||||||
var url = `/api/order/po/attachment/${pk}/`;
|
hidden: true,
|
||||||
|
|
||||||
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,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
|
||||||
$("#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"), {
|
loadStockTable($("#stock-table"), {
|
||||||
|
@ -110,55 +110,21 @@
|
|||||||
},
|
},
|
||||||
label: 'attachment',
|
label: 'attachment',
|
||||||
success: function(data, status, xhr) {
|
success: function(data, status, xhr) {
|
||||||
location.reload();
|
reloadAttachmentTable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
loadAttachmentTable(
|
loadAttachmentTable('{% url "api-so-attachment-list" %}', {
|
||||||
'{% url "api-so-attachment-list" %}',
|
filters: {
|
||||||
{
|
order: {{ order.pk }},
|
||||||
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"), {
|
loadBuildTable($("#builds-table"), {
|
||||||
|
25
InvenTree/part/migrations/0075_auto_20211128_0151.py
Normal file
25
InvenTree/part/migrations/0075_auto_20211128_0151.py
Normal file
@ -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'),
|
||||||
|
),
|
||||||
|
]
|
@ -75,8 +75,6 @@ class PartAttachmentSerializer(InvenTreeAttachmentSerializer):
|
|||||||
Serializer for the PartAttachment class
|
Serializer for the PartAttachment class
|
||||||
"""
|
"""
|
||||||
|
|
||||||
attachment = InvenTreeAttachmentSerializerField(required=True)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PartAttachment
|
model = PartAttachment
|
||||||
|
|
||||||
@ -85,6 +83,7 @@ class PartAttachmentSerializer(InvenTreeAttachmentSerializer):
|
|||||||
'part',
|
'part',
|
||||||
'attachment',
|
'attachment',
|
||||||
'filename',
|
'filename',
|
||||||
|
'link',
|
||||||
'comment',
|
'comment',
|
||||||
'upload_date',
|
'upload_date',
|
||||||
]
|
]
|
||||||
|
@ -999,36 +999,17 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
onPanelLoad("part-attachments", function() {
|
onPanelLoad("part-attachments", function() {
|
||||||
loadAttachmentTable(
|
loadAttachmentTable('{% url "api-part-attachment-list" %}', {
|
||||||
'{% url "api-part-attachment-list" %}',
|
filters: {
|
||||||
{
|
part: {{ part.pk }},
|
||||||
filters: {
|
},
|
||||||
part: {{ part.pk }},
|
fields: {
|
||||||
},
|
part: {
|
||||||
onEdit: function(pk) {
|
value: {{ part.pk }},
|
||||||
var url = `/api/part/attachment/${pk}/`;
|
hidden: true
|
||||||
|
|
||||||
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,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
enableDragAndDrop(
|
enableDragAndDrop(
|
||||||
'#attachment-dropzone',
|
'#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" %}',
|
|
||||||
}
|
|
||||||
)
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
25
InvenTree/stock/migrations/0070_auto_20211128_0151.py
Normal file
25
InvenTree/stock/migrations/0070_auto_20211128_0151.py
Normal file
@ -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'),
|
||||||
|
),
|
||||||
|
]
|
@ -420,8 +420,6 @@ class StockItemAttachmentSerializer(InvenTree.serializers.InvenTreeAttachmentSer
|
|||||||
|
|
||||||
user_detail = InvenTree.serializers.UserSerializerBrief(source='user', read_only=True)
|
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!
|
# TODO: Record the uploading user when creating or updating an attachment!
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -432,6 +430,7 @@ class StockItemAttachmentSerializer(InvenTree.serializers.InvenTreeAttachmentSer
|
|||||||
'stock_item',
|
'stock_item',
|
||||||
'attachment',
|
'attachment',
|
||||||
'filename',
|
'filename',
|
||||||
|
'link',
|
||||||
'comment',
|
'comment',
|
||||||
'upload_date',
|
'upload_date',
|
||||||
'user',
|
'user',
|
||||||
|
@ -221,55 +221,16 @@
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
loadAttachmentTable(
|
loadAttachmentTable('{% url "api-stock-attachment-list" %}', {
|
||||||
'{% url "api-stock-attachment-list" %}',
|
filters: {
|
||||||
{
|
stock_item: {{ item.pk }},
|
||||||
filters: {
|
},
|
||||||
stock_item: {{ item.pk }},
|
fields: {
|
||||||
},
|
stock_item: {
|
||||||
onEdit: function(pk) {
|
value: {{ item.pk }},
|
||||||
var url = `/api/stock/attachment/${pk}/`;
|
hidden: true,
|
||||||
|
|
||||||
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,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
|
||||||
$("#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(
|
loadStockTestResultsTable(
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
|
<button type='button' class='btn btn-outline-success' id='new-attachment-link'>
|
||||||
|
<span class='fas fa-link'></span> {% trans "Add Link" %}
|
||||||
|
</button>
|
||||||
<button type='button' class='btn btn-success' id='new-attachment'>
|
<button type='button' class='btn btn-success' id='new-attachment'>
|
||||||
<span class='fas fa-plus-circle'></span> {% trans "Add Attachment" %}
|
<span class='fas fa-plus-circle'></span> {% trans "Add Attachment" %}
|
||||||
</button>
|
</button>
|
@ -6,10 +6,57 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/* exported
|
/* exported
|
||||||
|
addAttachmentButtonCallbacks,
|
||||||
loadAttachmentTable,
|
loadAttachmentTable,
|
||||||
reloadAttachmentTable,
|
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() {
|
function reloadAttachmentTable() {
|
||||||
|
|
||||||
$('#attachment-table').bootstrapTable('refresh');
|
$('#attachment-table').bootstrapTable('refresh');
|
||||||
@ -20,6 +67,8 @@ function loadAttachmentTable(url, options) {
|
|||||||
|
|
||||||
var table = options.table || '#attachment-table';
|
var table = options.table || '#attachment-table';
|
||||||
|
|
||||||
|
addAttachmentButtonCallbacks(url, options.fields || {});
|
||||||
|
|
||||||
$(table).inventreeTable({
|
$(table).inventreeTable({
|
||||||
url: url,
|
url: url,
|
||||||
name: options.name || 'attachments',
|
name: options.name || 'attachments',
|
||||||
@ -34,56 +83,77 @@ function loadAttachmentTable(url, options) {
|
|||||||
$(table).find('.button-attachment-edit').click(function() {
|
$(table).find('.button-attachment-edit').click(function() {
|
||||||
var pk = $(this).attr('pk');
|
var pk = $(this).attr('pk');
|
||||||
|
|
||||||
if (options.onEdit) {
|
constructForm(`${url}${pk}/`, {
|
||||||
options.onEdit(pk);
|
fields: {
|
||||||
}
|
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" %}',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add callback for 'delete' button
|
// Add callback for 'delete' button
|
||||||
$(table).find('.button-attachment-delete').click(function() {
|
$(table).find('.button-attachment-delete').click(function() {
|
||||||
var pk = $(this).attr('pk');
|
var pk = $(this).attr('pk');
|
||||||
|
|
||||||
if (options.onDelete) {
|
constructForm(`${url}${pk}/`, {
|
||||||
options.onDelete(pk);
|
method: 'DELETE',
|
||||||
}
|
confirmMessage: '{% trans "Confirm Delete" %}',
|
||||||
|
title: '{% trans "Delete Attachment" %}',
|
||||||
|
onSuccess: reloadAttachmentTable,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
field: 'attachment',
|
field: 'attachment',
|
||||||
title: '{% trans "File" %}',
|
title: '{% trans "Attachment" %}',
|
||||||
formatter: function(value) {
|
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')) {
|
if (fn.endsWith('.csv')) {
|
||||||
icon = 'fa-file-csv';
|
icon = 'fa-file-csv';
|
||||||
} else if (fn.endsWith('.pdf')) {
|
} else if (fn.endsWith('.pdf')) {
|
||||||
icon = 'fa-file-pdf';
|
icon = 'fa-file-pdf';
|
||||||
} else if (fn.endsWith('.xls') || fn.endsWith('.xlsx')) {
|
} else if (fn.endsWith('.xls') || fn.endsWith('.xlsx')) {
|
||||||
icon = 'fa-file-excel';
|
icon = 'fa-file-excel';
|
||||||
} else if (fn.endsWith('.doc') || fn.endsWith('.docx')) {
|
} else if (fn.endsWith('.doc') || fn.endsWith('.docx')) {
|
||||||
icon = 'fa-file-word';
|
icon = 'fa-file-word';
|
||||||
} else if (fn.endsWith('.zip') || fn.endsWith('.7z')) {
|
} else if (fn.endsWith('.zip') || fn.endsWith('.7z')) {
|
||||||
icon = 'fa-file-archive';
|
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 = `<span class='fas ${icon}'></span> ${filename}`;
|
||||||
|
|
||||||
|
return renderLink(html, value);
|
||||||
|
} else if (row.link) {
|
||||||
|
var html = `<span class='fas fa-link'></span> ${row.link}`;
|
||||||
|
return renderLink(html, row.link);
|
||||||
} else {
|
} else {
|
||||||
var images = ['.png', '.jpg', '.bmp', '.gif', '.svg', '.tif'];
|
return '-';
|
||||||
|
|
||||||
images.forEach(function(suffix) {
|
|
||||||
if (fn.endsWith(suffix)) {
|
|
||||||
icon = 'fa-file-image';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var split = value.split('/');
|
|
||||||
var filename = split[split.length - 1];
|
|
||||||
|
|
||||||
var html = `<span class='fas ${icon}'></span> ${filename}`;
|
|
||||||
|
|
||||||
return renderLink(html, value);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user