diff --git a/InvenTree/InvenTree/views.py b/InvenTree/InvenTree/views.py
index 6ddee71682..83a1eb05af 100644
--- a/InvenTree/InvenTree/views.py
+++ b/InvenTree/InvenTree/views.py
@@ -322,9 +322,14 @@ class AjaxCreateView(AjaxMixin, CreateView):
"""
pass
- def post_save(self, new_object, request, **kwargs):
+ def post_save(self, **kwargs):
"""
Hook for doing something with the created object after it is saved
+
+ kwargs:
+ request - The request object
+ new_object - The newly created object
+
"""
pass
@@ -356,7 +361,7 @@ class AjaxCreateView(AjaxMixin, CreateView):
self.pre_save(self.form, request)
self.object = self.form.save()
- self.post_save(self.object, request)
+ self.post_save(new_object=self.object, request=request)
# Return the PK of the newly-created object
data['pk'] = self.object.pk
@@ -411,7 +416,7 @@ class AjaxUpdateView(AjaxMixin, UpdateView):
# Include context data about the updated object
data['pk'] = obj.id
- self.post_save(obj)
+ self.post_save(new_object=obj, request=request)
try:
data['url'] = obj.get_absolute_url()
@@ -420,7 +425,7 @@ class AjaxUpdateView(AjaxMixin, UpdateView):
return self.renderJsonResponse(request, form, data)
- def post_save(self, obj, *args, **kwargs):
+ def post_save(self, **kwargs):
"""
Hook called after the form data is saved.
(Optional)
diff --git a/InvenTree/build/forms.py b/InvenTree/build/forms.py
index 6f02f2061f..1413eddc03 100644
--- a/InvenTree/build/forms.py
+++ b/InvenTree/build/forms.py
@@ -11,7 +11,7 @@ from InvenTree.forms import HelperForm
from InvenTree.fields import RoundingDecimalFormField
from django import forms
-from .models import Build, BuildItem
+from .models import Build, BuildItem, BuildOrderAttachment
from stock.models import StockLocation
@@ -164,3 +164,17 @@ class EditBuildItemForm(HelperForm):
'quantity',
'install_into',
]
+
+
+class EditBuildAttachmentForm(HelperForm):
+ """
+ Form for creating / editing a BuildAttachment object
+ """
+
+ class Meta:
+ model = BuildOrderAttachment
+ fields = [
+ 'build',
+ 'attachment',
+ 'comment'
+ ]
diff --git a/InvenTree/build/templates/build/attachments.html b/InvenTree/build/templates/build/attachments.html
new file mode 100644
index 0000000000..a2dde0868c
--- /dev/null
+++ b/InvenTree/build/templates/build/attachments.html
@@ -0,0 +1,78 @@
+{% extends "build/build_base.html" %}
+
+{% load static %}
+{% load i18n %}
+{% load markdownify %}
+
+{% block details %}
+
+{% include "build/tabs.html" with tab='attachments' %}
+
+
{% trans "Attachments" %}
+
+
+{% include "attachment_table.html" with attachments=build.attachments.all %}
+
+{% endblock %}
+
+{% block js_ready %}
+{{ block.super }}
+
+enableDragAndDrop(
+ '#attachment-dropzone',
+ '{% url "build-attachment-create" %}',
+ {
+ data: {
+ build: {{ build.id }},
+ },
+ label: 'attachment',
+ success: function(data, status, xhr) {
+ location.reload();
+ }
+ }
+);
+
+// Callback for creating a new attachment
+$('#new-attachment').click(function() {
+ launchModalForm(
+ '{% url "build-attachment-create" %}',
+ {
+ reload: true,
+ data: {
+ build: {{ build.pk }},
+ }
+ }
+ );
+});
+
+// Callback for editing an attachment
+$("#attachment-table").on('click', '.attachment-edit-button', function() {
+ var pk = $(this).attr('pk');
+
+ var url = `/build/attachment/${pk}/edit/`;
+
+ launchModalForm(
+ url,
+ {
+ reload: true,
+ }
+ );
+});
+
+// Callback for deleting an attachment
+$("#attachment-table").on('click', '.attachment-delete-button', function() {
+ var pk = $(this).attr('pk');
+
+ var url = `/build/attachment/${pk}/delete/`;
+
+ launchModalForm(
+ url,
+ {
+ reload: true,
+ }
+ );
+});
+
+$("#attachment-table").inventreeTable({});
+
+{% endblock %}
diff --git a/InvenTree/build/templates/build/tabs.html b/InvenTree/build/templates/build/tabs.html
index ca2e92f290..8f59abc97d 100644
--- a/InvenTree/build/templates/build/tabs.html
+++ b/InvenTree/build/templates/build/tabs.html
@@ -13,4 +13,7 @@
{% trans "Notes" %}{% if build.notes %} {% endif %}
+
+ {% trans "Attachments" %}
+
\ No newline at end of file
diff --git a/InvenTree/build/urls.py b/InvenTree/build/urls.py
index 109dd0b556..ff49fdd486 100644
--- a/InvenTree/build/urls.py
+++ b/InvenTree/build/urls.py
@@ -18,6 +18,7 @@ build_detail_urls = [
url(r'^notes/', views.BuildNotes.as_view(), name='build-notes'),
+ url(r'^attachments/', views.BuildDetail.as_view(template_name='build/attachments.html'), name='build-attachments'),
url(r'^output/', views.BuildDetail.as_view(template_name='build/build_output.html'), name='build-output'),
url(r'^.*$', views.BuildDetail.as_view(), name='build-detail'),
@@ -32,6 +33,12 @@ build_urls = [
url('^new/', views.BuildItemCreate.as_view(), name='build-item-create'),
])),
+ url('^attachment/', include([
+ url('^new/', views.BuildAttachmentCreate.as_view(), name='build-attachment-create'),
+ url(r'^(?P\d+)/edit/', views.BuildAttachmentEdit.as_view(), name='build-attachment-edit'),
+ url(r'^(?P\d+)/delete/', views.BuildAttachmentDelete.as_view(), name='build-attachment-delete'),
+ ])),
+
url(r'new/', views.BuildCreate.as_view(), name='build-create'),
url(r'^(?P\d+)/', include(build_detail_urls)),
diff --git a/InvenTree/build/views.py b/InvenTree/build/views.py
index 30ea31af58..a1dd802c4a 100644
--- a/InvenTree/build/views.py
+++ b/InvenTree/build/views.py
@@ -12,7 +12,7 @@ from django.forms import HiddenInput
from django.urls import reverse
from part.models import Part
-from .models import Build, BuildItem
+from .models import Build, BuildItem, BuildOrderAttachment
from . import forms
from stock.models import StockLocation, StockItem
@@ -527,12 +527,14 @@ class BuildCreate(AjaxCreateView):
'success': _('Created new build'),
}
- def post_save(self, new_object, request, **kwargs):
+ def post_save(self, **kwargs):
"""
Called immediately after a new Build object is created.
"""
- build = new_object
+ build = kwargs['new_object']
+ request = kwargs['request']
+
build.createInitialStockItem(request.user)
@@ -795,3 +797,86 @@ class BuildItemEdit(AjaxUpdateView):
form.fields[field].widget = HiddenInput()
return form
+
+
+class BuildAttachmentCreate(AjaxCreateView):
+ """
+ View for creating a BuildAttachment
+ """
+
+ model = BuildOrderAttachment
+ form_class = forms.EditBuildAttachmentForm
+ ajax_form_title = _('Add Build Order Attachment')
+ role_required = 'build.add'
+
+ def post_save(self, **kwargs):
+ self.object.user = self.request.user
+ self.object.save()
+
+ def get_data(self):
+ return {
+ 'success': _('Added attachment')
+ }
+
+ def get_initial(self):
+ """
+ Get initial data for creating an attachment
+ """
+
+ initials = super().get_initial()
+
+ try:
+ initials['build'] = Build.objects.get(pk=self.request.GET.get('build', -1))
+ except (ValueError, Build.DoesNotExist):
+ pass
+
+ return initials
+
+ def get_form(self):
+ """
+ Hide the 'build' field if specified
+ """
+
+ form = super().get_form()
+
+ form.fields['build'].widget = HiddenInput()
+
+ return form
+
+
+class BuildAttachmentEdit(AjaxUpdateView):
+ """
+ View for editing a BuildAttachment object
+ """
+
+ model = BuildOrderAttachment
+ form_class = forms.EditBuildAttachmentForm
+ ajax_form_title = _('Edit Attachment')
+ role_required = 'build.change'
+
+ def get_form(self):
+ form = super().get_form()
+ form.fields['build'].widget = HiddenInput()
+
+ return form
+
+ def get_data(self):
+ return {
+ 'success': _('Attachment updated')
+ }
+
+
+class BuildAttachmentDelete(AjaxDeleteView):
+ """
+ View for deleting a BuildAttachment
+ """
+
+ model = BuildOrderAttachment
+ ajax_form_title = _('Delete Attachment')
+ context_object_name = 'attachment'
+ role_required = 'build.delete'
+
+ def get_data(self):
+ return {
+ 'danger': _('Deleted attachment')
+ }