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:
parent
403655e3d2
commit
a816c14b95
@ -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.
|
||||||
|
@ -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' %}",
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -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 %}
|
|
@ -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)
|
||||||
|
@ -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'),
|
||||||
]
|
]
|
||||||
|
@ -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')
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user