From 14132a6efac478f66ad071b13b2ffa4d67a243e8 Mon Sep 17 00:00:00 2001
From: Oliver Walters <oliver.henry.walters@gmail.com>
Date: Thu, 7 May 2020 09:57:54 +1000
Subject: [PATCH] Add views / models / etc etc to support StockItem attachment

---
 InvenTree/part/views.py                       |  2 +-
 InvenTree/stock/forms.py                      | 16 +++-
 InvenTree/stock/models.py                     |  2 +
 .../templates/stock}/attachment_delete.html   |  0
 .../templates/stock/item_attachments.html     | 82 +++++++++++++++++++
 InvenTree/stock/templates/stock/tabs.html     |  3 +
 InvenTree/stock/urls.py                       |  7 ++
 InvenTree/stock/views.py                      | 75 ++++++++++++++++-
 InvenTree/templates/attachment_delete.html    |  7 ++
 9 files changed, 191 insertions(+), 3 deletions(-)
 rename InvenTree/{part/templates/part => stock/templates/stock}/attachment_delete.html (100%)
 create mode 100644 InvenTree/stock/templates/stock/item_attachments.html
 create mode 100644 InvenTree/templates/attachment_delete.html

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' %}
+
+<hr>
+<h4>{% trans "Stock Item Attachments" %}</h4>
+
+
+<div id='attachment-buttons'>
+    <div class="btn-group">
+        <button type='button' class='btn btn-success' id='new-attachment'>{% trans "Add Attachment" %}</button>
+    </div>
+</div>
+
+<table class='table table-striped table-condensed' data-toolbar='#attachment-buttons' id='attachment-table'>
+    <thead>
+        <tr>
+            <th data-field='file' data-searchable='true'>{% trans "File" %}</th>
+            <th data-field='comment' data-searchable='true'>{% trans "Comment" %}</th>
+            <th></th>
+        </tr>
+    </thead>
+    <tbody>
+        {% for attachment in item.attachments.all %}
+        <tr>
+            <td><a href='/media/{{ attachment.attachment }}'>{{ attachment.basename }}</a></td>
+            <td>{{ attachment.comment }}</td>
+            <td>
+                <div class='btn-group' style='float: right;'>    
+                    <button type='button' class='btn btn-default btn-glyph attachment-edit-button' url="{% url 'stock-item-attachment-edit' attachment.id %}" data-toggle='tooltip' title='{% trans "Edit attachment" %}'>
+                        <span class='fas fa-edit'/>
+                    </button>
+                    <button type='button' class='btn btn-default btn-glyph attachment-delete-button' url="{% url 'stock-item-attachment-delete' attachment.id %}" data-toggle='tooltip' title='{% trans "Delete attachment" %}'>
+                        <span class='fas fa-trash-alt icon-red'/>
+                    </button>
+                </div>
+            </td>
+        </tr>
+        {% endfor %}
+    </tbody>
+</table>
+
+{% 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 @@
     <li{% ifequal tab 'notes' %} class='active'{% endifequal %}>
         <a href="{% url 'stock-item-notes' item.id %}">{% trans "Notes" %}{% if item.notes %} <span class='fas fa-info-circle'></span>{% endif %}</a>
     </li>
+    <li{% if tab == 'attachments' %} class='active'{% endif %}>
+        <a href="{% url 'stock-item-attachments' item.id %}">{% trans "Attachments" %}{% if item.attachments.count > 0 %}<span class='badge'>{{ item.attachments.count }}</span>{% endif %}</a>
+    </li>
 </ul>
\ 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<pk>\d+)/edit/', views.StockItemAttachmentEdit.as_view(), name='stock-item-attachment-edit'),
+        url(r'^(?P<pk>\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?" %}
+<br>
+{% endblock %}
\ No newline at end of file