2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-28 11:36:44 +00:00

Allow build orders to be deleted via the API (#3155)

This commit is contained in:
Oliver 2022-06-08 07:45:42 +10:00 committed by GitHub
parent 403655e3d2
commit a816c14b95
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 56 additions and 27 deletions

View File

@ -1,8 +1,10 @@
"""JSON API for the Build app.""" """JSON API for the Build app."""
from django.urls import include, re_path from django.urls import include, re_path
from django.utils.translation import gettext_lazy as _
from rest_framework import filters, generics from rest_framework import filters, generics
from rest_framework.exceptions import ValidationError
from django_filters.rest_framework import DjangoFilterBackend from django_filters.rest_framework import DjangoFilterBackend
from django_filters import rest_framework as rest_filters from django_filters import rest_framework as rest_filters
@ -198,12 +200,24 @@ class BuildList(APIDownloadMixin, generics.ListCreateAPIView):
return self.serializer_class(*args, **kwargs) return self.serializer_class(*args, **kwargs)
class BuildDetail(generics.RetrieveUpdateAPIView): class BuildDetail(generics.RetrieveUpdateDestroyAPIView):
"""API endpoint for detail view of a Build object.""" """API endpoint for detail view of a Build object."""
queryset = Build.objects.all() queryset = Build.objects.all()
serializer_class = build.serializers.BuildSerializer serializer_class = build.serializers.BuildSerializer
def destroy(self, request, *args, **kwargs):
"""Only allow deletion of a BuildOrder if the build status is CANCELLED"""
build = self.get_object()
if build.status != BuildStatus.CANCELLED:
raise ValidationError({
"non_field_errors": [_("Build must be cancelled before it can be deleted")]
})
return super().destroy(request, *args, **kwargs)
class BuildUnallocate(generics.CreateAPIView): class BuildUnallocate(generics.CreateAPIView):
"""API endpoint for unallocating stock items from a build order. """API endpoint for unallocating stock items from a build order.

View File

@ -249,9 +249,11 @@ src="{% static 'img/blank_image.png' %}"
{% endif %} {% endif %}
$("#build-delete").on('click', function() { $("#build-delete").on('click', function() {
launchModalForm( constructForm(
"{% url 'build-delete' build.id %}", '{% url "api-build-detail" build.pk %}',
{ {
method: 'DELETE',
title: '{% trans "Delete Build Order" %}',
redirect: "{% url 'build-index' %}", redirect: "{% url 'build-index' %}",
} }
); );

View File

@ -1,7 +0,0 @@
{% extends "modal_delete_form.html" %}
{% load i18n %}
{% block pre_form_content %}
{% trans "Are you sure you want to delete this build?" %}
{% endblock %}

View File

@ -90,7 +90,7 @@ class BuildAPITest(InvenTreeAPITestCase):
# Required roles to access Build API endpoints # Required roles to access Build API endpoints
roles = [ roles = [
'build.change', 'build.change',
'build.add' 'build.add',
] ]
@ -268,6 +268,39 @@ class BuildTest(BuildAPITest):
self.assertEqual(bo.status, BuildStatus.CANCELLED) self.assertEqual(bo.status, BuildStatus.CANCELLED)
def test_delete(self):
"""Test that we can delete a BuildOrder via the API"""
bo = Build.objects.get(pk=1)
url = reverse('api-build-detail', kwargs={'pk': bo.pk})
# At first we do not have the required permissions
self.delete(
url,
expected_code=403,
)
self.assignRole('build.delete')
# As build is currently not 'cancelled', it cannot be deleted
self.delete(
url,
expected_code=400,
)
bo.status = BuildStatus.CANCELLED
bo.save()
# Now, we should be able to delete
self.delete(
url,
expected_code=204,
)
with self.assertRaises(Build.DoesNotExist):
Build.objects.get(pk=1)
def test_create_delete_output(self): def test_create_delete_output(self):
"""Test that we can create and delete build outputs via the API.""" """Test that we can create and delete build outputs via the API."""
bo = Build.objects.get(pk=1) bo = Build.objects.get(pk=1)

View File

@ -4,15 +4,12 @@ from django.urls import include, re_path
from . import views from . import views
build_detail_urls = [
re_path(r'^delete/', views.BuildDelete.as_view(), name='build-delete'),
re_path(r'^.*$', views.BuildDetail.as_view(), name='build-detail'),
]
build_urls = [ build_urls = [
re_path(r'^(?P<pk>\d+)/', include(build_detail_urls)), re_path(r'^(?P<pk>\d+)/', include([
re_path(r'^.*$', views.BuildDetail.as_view(), name='build-detail'),
])),
re_path(r'.*$', views.BuildIndex.as_view(), name='build-index'), re_path(r'.*$', views.BuildIndex.as_view(), name='build-index'),
] ]

View File

@ -1,11 +1,9 @@
"""Django views for interacting with Build objects.""" """Django views for interacting with Build objects."""
from django.utils.translation import gettext_lazy as _
from django.views.generic import DetailView, ListView from django.views.generic import DetailView, ListView
from .models import Build from .models import Build
from InvenTree.views import AjaxDeleteView
from InvenTree.views import InvenTreeRoleMixin from InvenTree.views import InvenTreeRoleMixin
from InvenTree.status_codes import BuildStatus from InvenTree.status_codes import BuildStatus
@ -49,11 +47,3 @@ class BuildDetail(InvenTreeRoleMixin, InvenTreePluginViewMixin, DetailView):
ctx['has_untracked_bom_items'] = build.has_untracked_bom_items() ctx['has_untracked_bom_items'] = build.has_untracked_bom_items()
return ctx return ctx
class BuildDelete(AjaxDeleteView):
"""View to delete a build."""
model = Build
ajax_template_name = 'build/delete_build.html'
ajax_form_title = _('Delete Build Order')