diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py
index 458696b75c..89a285cf60 100644
--- a/InvenTree/part/views.py
+++ b/InvenTree/part/views.py
@@ -135,7 +135,7 @@ class PartAttachmentDelete(AjaxDeleteView):
model = PartAttachment
ajax_form_title = _("Delete Part Attachment")
- ajax_template_name = "part/attachment_delete.html"
+ ajax_template_name = "attachment_delete.html"
context_object_name = "attachment"
def get_data(self):
diff --git a/InvenTree/stock/forms.py b/InvenTree/stock/forms.py
index fef6ecc7a5..a4578440cb 100644
--- a/InvenTree/stock/forms.py
+++ b/InvenTree/stock/forms.py
@@ -13,7 +13,21 @@ from mptt.fields import TreeNodeChoiceField
from InvenTree.helpers import GetExportFormats
from InvenTree.forms import HelperForm
-from .models import StockLocation, StockItem, StockItemTracking
+from .models import StockLocation, StockItem, StockItemTracking, StockItemAttachment
+
+
+class EditStockItemAttachmentForm(HelperForm):
+ """
+ Form for creating / editing a StockItemAttachment object
+ """
+
+ class Meta:
+ model = StockItemAttachment
+ fields = [
+ 'stock_item',
+ 'attachment',
+ 'comment'
+ ]
class EditStockLocationForm(HelperForm):
diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py
index 6ab8ec07ee..ca5ae115a1 100644
--- a/InvenTree/stock/models.py
+++ b/InvenTree/stock/models.py
@@ -6,6 +6,8 @@ Stock database model definitions
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
+import os
+
from django.utils.translation import gettext_lazy as _
from django.core.exceptions import ValidationError
from django.urls import reverse
diff --git a/InvenTree/part/templates/part/attachment_delete.html b/InvenTree/stock/templates/stock/attachment_delete.html
similarity index 100%
rename from InvenTree/part/templates/part/attachment_delete.html
rename to InvenTree/stock/templates/stock/attachment_delete.html
diff --git a/InvenTree/stock/templates/stock/item_attachments.html b/InvenTree/stock/templates/stock/item_attachments.html
new file mode 100644
index 0000000000..6aeff554d0
--- /dev/null
+++ b/InvenTree/stock/templates/stock/item_attachments.html
@@ -0,0 +1,82 @@
+{% extends "stock/item_base.html" %}
+
+{% load static %}
+{% load i18n %}
+
+{% block details %}
+
+{% include "stock/tabs.html" with tab='attachments' %}
+
+
+{% trans "Stock Item Attachments" %}
+
+
+
+
+
+
+
+ {% trans "File" %} |
+ {% trans "Comment" %} |
+ |
+
+
+
+ {% for attachment in item.attachments.all %}
+
+ {{ attachment.basename }} |
+ {{ attachment.comment }} |
+
+
+
+
+
+ |
+
+ {% endfor %}
+
+
+
+{% endblock %}
+
+{% block js_ready %}
+{{ block.super }}
+
+$("#new-attachment").click(function() {
+ launchModalForm("{% url 'stock-item-attachment-create' %}?item={{ item.id }}",
+ {
+ reload: true,
+ });
+});
+
+$("#attachment-table").on('click', '.attachment-edit-button', function() {
+ var button = $(this);
+
+ launchModalForm(button.attr('url'),
+ {
+ reload: true,
+ });
+});
+
+$("#attachment-table").on('click', '.attachment-delete-button', function() {
+ var button = $(this);
+
+ launchModalForm(button.attr('url'), {
+ success: function() {
+ location.reload();
+ }
+ });
+});
+
+$("#attachment-table").inventreeTable({
+});
+
+{% endblock %}
\ No newline at end of file
diff --git a/InvenTree/stock/templates/stock/tabs.html b/InvenTree/stock/templates/stock/tabs.html
index 62ee68bc4b..5b69cac8b4 100644
--- a/InvenTree/stock/templates/stock/tabs.html
+++ b/InvenTree/stock/templates/stock/tabs.html
@@ -16,4 +16,7 @@
{% trans "Notes" %}{% if item.notes %} {% endif %}
+
+ {% trans "Attachments" %}{% if item.attachments.count > 0 %}{{ item.attachments.count }}{% endif %}
+
\ No newline at end of file
diff --git a/InvenTree/stock/urls.py b/InvenTree/stock/urls.py
index f69dc8b63f..149c0dde2f 100644
--- a/InvenTree/stock/urls.py
+++ b/InvenTree/stock/urls.py
@@ -25,6 +25,7 @@ stock_item_detail_urls = [
url(r'^add_tracking/', views.StockItemTrackingCreate.as_view(), name='stock-tracking-create'),
url(r'^children/', views.StockItemDetail.as_view(template_name='stock/item_childs.html'), name='stock-item-children'),
+ url(r'^attachments/', views.StockItemDetail.as_view(template_name='stock/item_attachments.html'), name='stock-item-attachments'),
url(r'^notes/', views.StockItemNotes.as_view(), name='stock-item-notes'),
url('^.*$', views.StockItemDetail.as_view(), name='stock-item-detail'),
@@ -50,6 +51,12 @@ stock_urls = [
url(r'^item/new/?', views.StockItemCreate.as_view(), name='stock-item-create'),
+ url(r'^item/attachment/', include([
+ url(r'^new/', views.StockItemAttachmentCreate.as_view(), name='stock-item-attachment-create'),
+ url(r'^(?P\d+)/edit/', views.StockItemAttachmentEdit.as_view(), name='stock-item-attachment-edit'),
+ url(r'^(?P\d+)/delete/', views.StockItemAttachmentDelete.as_view(), name='stock-item-attachment-delete'),
+ ])),
+
url(r'^track/', include(stock_tracking_urls)),
url(r'^adjust/?', views.StockAdjust.as_view(), name='stock-adjust'),
diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py
index b6ec8bda85..19edf8666d 100644
--- a/InvenTree/stock/views.py
+++ b/InvenTree/stock/views.py
@@ -26,7 +26,7 @@ from datetime import datetime
from company.models import Company, SupplierPart
from part.models import Part
-from .models import StockItem, StockLocation, StockItemTracking
+from .models import StockItem, StockLocation, StockItemTracking, StockItemAttachment
from .admin import StockItemResource
@@ -37,6 +37,7 @@ from .forms import AdjustStockForm
from .forms import TrackingEntryForm
from .forms import SerializeStockForm
from .forms import ExportOptionsForm
+from .forms import EditStockItemAttachmentForm
class StockIndex(ListView):
@@ -149,6 +150,78 @@ class StockLocationQRCode(QRCodeView):
return None
+class StockItemAttachmentCreate(AjaxCreateView):
+ """
+ View for adding a new attachment for a StockItem
+ """
+
+ model = StockItemAttachment
+ form_class = EditStockItemAttachmentForm
+ ajax_form_title = _("Add Stock Item Attachment")
+ ajax_template_name = "modal_form.html"
+
+ def get_data(self):
+ return {
+ 'success': _("Added attachment")
+ }
+
+ def get_initial(self):
+ """
+ Get initial data for the new StockItem attachment object.
+
+ - Client must provide a valid StockItem ID
+ """
+
+ initials = super().get_initial()
+
+ try:
+ initials['stock_item'] = StockItem.objects.get(id=self.request.GET.get('item', None))
+ except (ValueError, StockItem.DoesNotExist):
+ pass
+
+ return initials
+
+ def get_form(self):
+
+ form = super().get_form()
+ form.fields['stock_item'].widget = HiddenInput()
+
+ return form
+
+
+class StockItemAttachmentEdit(AjaxUpdateView):
+ """
+ View for editing a StockItemAttachment object.
+ """
+
+ model = StockItemAttachment
+ form_class = EditStockItemAttachmentForm
+ ajax_form_title = _("Edit Stock Item Attachment")
+
+ def get_form(self):
+
+ form = super().get_form()
+ form.fields['stock_item'].widget = HiddenInput()
+
+ return form
+
+
+class StockItemAttachmentDelete(AjaxDeleteView):
+ """
+ View for deleting a StockItemAttachment object.
+ """
+
+ model = StockItemAttachment
+ ajax_form_title = _("Delete Stock Item Attachment")
+ ajax_template_name = "attachment_delete.html"
+ context_object_name = "attachment"
+
+ def get_data(self):
+ return {
+ 'danger': _("Deleted attachment"),
+ }
+
+
class StockExportOptions(AjaxView):
""" Form for selecting StockExport options """
diff --git a/InvenTree/templates/attachment_delete.html b/InvenTree/templates/attachment_delete.html
new file mode 100644
index 0000000000..4ee7f03cb1
--- /dev/null
+++ b/InvenTree/templates/attachment_delete.html
@@ -0,0 +1,7 @@
+{% extends "modal_delete_form.html" %}
+{% load i18n %}
+
+{% block pre_form_content %}
+{% trans "Are you sure you want to delete this attachment?" %}
+
+{% endblock %}
\ No newline at end of file