diff --git a/InvenTree/InvenTree/views.py b/InvenTree/InvenTree/views.py
index 09b686578a..95ad33cba1 100644
--- a/InvenTree/InvenTree/views.py
+++ b/InvenTree/InvenTree/views.py
@@ -164,6 +164,8 @@ class AjaxMixin(object):
if form:
context['form'] = form
+ else:
+ context['form'] = None
data['title'] = self.ajax_form_title
diff --git a/InvenTree/company/templates/company/detail_part.html b/InvenTree/company/templates/company/detail_part.html
index 16d564a9db..f1e5ab8632 100644
--- a/InvenTree/company/templates/company/detail_part.html
+++ b/InvenTree/company/templates/company/detail_part.html
@@ -15,6 +15,7 @@
@@ -98,6 +99,23 @@
url: "{% url 'api-part-supplier-list' %}"
});
+ $("#multi-part-delete").click(function() {
+ var selections = $("#part-table").bootstrapTable("getSelections");
+
+ var parts = [];
+
+ selections.forEach(function(item) {
+ parts.push(item.pk);
+ });
+
+ launchModalForm("{% url 'supplier-part-delete' %}", {
+ data: {
+ parts: parts,
+ },
+ reload: true,
+ });
+ });
+
$("#multi-part-order").click(function() {
var selections = $("#part-table").bootstrapTable("getSelections");
diff --git a/InvenTree/company/templates/company/partdelete.html b/InvenTree/company/templates/company/partdelete.html
index ba6b78f841..8eed487697 100644
--- a/InvenTree/company/templates/company/partdelete.html
+++ b/InvenTree/company/templates/company/partdelete.html
@@ -1,5 +1,28 @@
{% extends "modal_delete_form.html" %}
+{% load i18n %}
{% block pre_form_content %}
-Are you sure you want to delete this supplier part?
+{% trans "Are you sure you want to delete the following Supplier Parts?" %}
+
+
+{% endblock %}
+
+{% block form_data %}
+
+{% for part in parts %}
+
+
+
+
+ {% include "hover_image.html" with image=part.supplier.image %}
+ {{ part.supplier.name }}
+ |
+
+ {% include "hover_image.html" with image=part.part.image %}
+ {{ part.part.full_name }}
+ |
+
+{% endfor %}
+
+
{% endblock %}
\ No newline at end of file
diff --git a/InvenTree/company/templates/company/partdetail.html b/InvenTree/company/templates/company/partdetail.html
index 77d71ea77c..b6b12b114d 100644
--- a/InvenTree/company/templates/company/partdetail.html
+++ b/InvenTree/company/templates/company/partdetail.html
@@ -130,10 +130,10 @@ InvenTree | {{ company.name }} - Parts
$('#delete-part').click(function() {
launchModalForm(
- "{% url 'supplier-part-delete' part.id %}",
- {
- redirect: "{% url 'company-index' %}"
- }
+ "{% url 'supplier-part-delete' %}?part={{ part.id }}",
+ {
+ redirect: "{% url 'company-detail-parts' part.supplier.id %}"
+ }
);
});
diff --git a/InvenTree/company/test_views.py b/InvenTree/company/test_views.py
new file mode 100644
index 0000000000..b9bb69e503
--- /dev/null
+++ b/InvenTree/company/test_views.py
@@ -0,0 +1,64 @@
+""" Unit tests for Company views (see views.py) """
+
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.test import TestCase
+from django.urls import reverse
+from django.contrib.auth import get_user_model
+
+from .models import SupplierPart
+
+
+class CompanyViewTest(TestCase):
+
+ fixtures = [
+ 'category',
+ 'part',
+ 'location',
+ 'company',
+ 'supplier_part',
+ ]
+
+ def setUp(self):
+ super().setUp()
+
+ # Create a user
+ User = get_user_model()
+ User.objects.create_user('username', 'user@email.com', 'password')
+
+ self.client.login(username='username', password='password')
+
+ def test_company_index(self):
+ """ Test the company index """
+
+ response = self.client.get(reverse('company-index'))
+ self.assertEqual(response.status_code, 200)
+
+ def test_supplier_part_delete(self):
+ """ Test the SupplierPartDelete view """
+
+ url = reverse('supplier-part-delete')
+
+ # Get form using 'part' argument
+ response = self.client.get(url, {'part': '1'}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
+ self.assertEqual(response.status_code, 200)
+
+ # Get form using 'parts' argument
+ response = self.client.get(url + '?parts[]=1&parts[]=2', HTTP_X_REQUESTED_WITH='XMLHttpRequest')
+ self.assertEqual(response.status_code, 200)
+
+ # POST to delete two parts
+ n = SupplierPart.objects.count()
+ response = self.client.post(
+ url,
+ {
+ 'supplier-part-2': 'supplier-part-2',
+ 'supplier-part-3': 'supplier-part-3',
+ 'confirm_delete': True
+ },
+ HTTP_X_REQUESTED_WITH='XMLHttpRequest')
+
+ self.assertEqual(response.status_code, 200)
+
+ self.assertEqual(n - 2, SupplierPart.objects.count())
diff --git a/InvenTree/company/urls.py b/InvenTree/company/urls.py
index de4a5e7c5f..55575eb65d 100644
--- a/InvenTree/company/urls.py
+++ b/InvenTree/company/urls.py
@@ -47,7 +47,6 @@ price_break_urls = [
supplier_part_detail_urls = [
url(r'edit/?', views.SupplierPartEdit.as_view(), name='supplier-part-edit'),
- url(r'delete/?', views.SupplierPartDelete.as_view(), name='supplier-part-delete'),
url('^.*$', views.SupplierPartDetail.as_view(), name='supplier-part-detail'),
]
@@ -55,5 +54,7 @@ supplier_part_detail_urls = [
supplier_part_urls = [
url(r'^new/?', views.SupplierPartCreate.as_view(), name='supplier-part-create'),
+ url(r'delete/', views.SupplierPartDelete.as_view(), name='supplier-part-delete'),
+
url(r'^(?P\d+)/', include(supplier_part_detail_urls)),
]
diff --git a/InvenTree/company/views.py b/InvenTree/company/views.py
index e69f7691cf..921b49aba2 100644
--- a/InvenTree/company/views.py
+++ b/InvenTree/company/views.py
@@ -12,6 +12,7 @@ from django.forms import HiddenInput
from InvenTree.views import AjaxCreateView, AjaxUpdateView, AjaxDeleteView
from InvenTree.status_codes import OrderStatus
+from InvenTree.helpers import str2bool
from .models import Company
from .models import SupplierPart
@@ -197,12 +198,80 @@ class SupplierPartCreate(AjaxCreateView):
class SupplierPartDelete(AjaxDeleteView):
- """ Delete view for removing a SupplierPart """
- model = SupplierPart
+ """ Delete view for removing a SupplierPart.
+
+ SupplierParts can be deleted using a variety of 'selectors'.
+
+ - ?part= -> Delete a single SupplierPart object
+ - ?parts=[] -> Delete a list of SupplierPart objects
+
+ """
+
success_url = '/supplier/'
ajax_template_name = 'company/partdelete.html'
ajax_form_title = 'Delete Supplier Part'
- context_object_name = 'supplier_part'
+
+ parts = []
+
+ def get_context_data(self):
+ ctx = {}
+
+ ctx['parts'] = self.parts
+
+ return ctx
+
+ def get_parts(self):
+ """ Determine which SupplierPart object(s) the user wishes to delete.
+ """
+
+ self.parts = []
+
+ # User passes a single SupplierPart ID
+ if 'part' in self.request.GET:
+ try:
+ self.parts.append(SupplierPart.objects.get(pk=self.request.GET.get('part')))
+ except (ValueError, SupplierPart.DoesNotExist):
+ pass
+
+ elif 'parts[]' in self.request.GET:
+
+ part_id_list = self.request.GET.getlist('parts[]')
+
+ self.parts = SupplierPart.objects.filter(id__in=part_id_list)
+
+ def get(self, request, *args, **kwargs):
+ self.request = request
+ self.get_parts()
+
+ return self.renderJsonResponse(request, form=self.get_form())
+
+ def post(self, request, *args, **kwargs):
+ """ Handle the POST action for deleting supplier parts.
+ """
+
+ self.request = request
+ self.parts = []
+
+ for item in self.request.POST:
+ if item.startswith('supplier-part-'):
+ pk = item.replace('supplier-part-', '')
+
+ try:
+ self.parts.append(SupplierPart.objects.get(pk=pk))
+ except (ValueError, SupplierPart.DoesNotExist):
+ pass
+
+ confirm = str2bool(self.request.POST.get('confirm_delete', False))
+
+ data = {
+ 'form_valid': confirm,
+ }
+
+ if confirm:
+ for part in self.parts:
+ part.delete()
+
+ return self.renderJsonResponse(self.request, data=data, form=self.get_form())
class PriceBreakCreate(AjaxCreateView):
diff --git a/InvenTree/order/test_views.py b/InvenTree/order/test_views.py
index 9ef3c80276..0bed1f8449 100644
--- a/InvenTree/order/test_views.py
+++ b/InvenTree/order/test_views.py
@@ -1,5 +1,8 @@
""" Unit tests for Order views (see views.py) """
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
from django.test import TestCase
from django.urls import reverse
from django.contrib.auth import get_user_model
diff --git a/InvenTree/part/templates/part/supplier.html b/InvenTree/part/templates/part/supplier.html
index 8d21f6417c..38596ba03c 100644
--- a/InvenTree/part/templates/part/supplier.html
+++ b/InvenTree/part/templates/part/supplier.html
@@ -9,7 +9,6 @@
@@ -50,6 +48,24 @@
});
});
+ $("#supplier-part-delete").click(function() {
+
+ var selections = $("#supplier-table").bootstrapTable("getSelections");
+
+ var parts = [];
+
+ selections.forEach(function(item) {
+ parts.push(item.pk);
+ });
+
+ launchModalForm("{% url 'supplier-part-delete' %}", {
+ data: {
+ parts: parts,
+ },
+ reload: true,
+ });
+ });
+
$("#supplier-table").inventreeTable({
formatNoMatches: function() { return "No supplier parts available for {{ part.full_name }}"; },
queryParams: function(p) {
@@ -58,12 +74,9 @@
}
},
columns: [
- /*
- // TODO - Re-enable the checkbox column for performing actions on multiple supplier parts
{
checkbox: true,
},
- */
{
sortable: true,
field: 'supplier_name',
diff --git a/InvenTree/templates/modal_form.html b/InvenTree/templates/modal_form.html
index eb5334b445..98f1c49693 100644
--- a/InvenTree/templates/modal_form.html
+++ b/InvenTree/templates/modal_form.html
@@ -27,11 +27,11 @@
{% csrf_token %}
{% load crispy_forms_tags %}
+ {% block form_data %}
+ {% endblock %}
{% crispy form %}
- {% block form_data %}
- {% endblock %}
{% endblock %}