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 %} + + + + + + +{% endfor %} +
+ {% 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 }} +
+ {% 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 @@
- {% if 0 %} - {% endif %}
@@ -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 %}