Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Converting more forms to the API #2931

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion InvenTree/InvenTree/api_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@


# InvenTree API version
INVENTREE_API_VERSION = 43
INVENTREE_API_VERSION = 44

"""
Increment this API version number whenever there is a significant change to the API that any clients need to know about

v44 -> 2022-05-04 : https://github.com/inventree/InvenTree/pull/2931
- Converting more server-side rendered forms to the API
- Exposes more core functionality to API endpoints

v43 -> 2022-04-26 : https://github.com/inventree/InvenTree/pull/2875
- Adds API detail endpoint for PartSalePrice model
- Adds API detail endpoint for PartInternalPrice model
Expand Down
20 changes: 20 additions & 0 deletions InvenTree/InvenTree/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
from rest_framework.utils import model_meta
from rest_framework.fields import empty

from InvenTree.helpers import str2bool

import users.models


Expand Down Expand Up @@ -37,6 +39,22 @@ def determine_metadata(self, request, view):

metadata = super().determine_metadata(request, view)

"""
Custom context information to pass through to the OPTIONS endpoint,
if the "context=True" is supplied to the OPTIONS requst

Serializer class can supply context data by defining a get_context_data() method (no arguments)
"""

context = {}

if str2bool(request.query_params.get('context', False)):

if hasattr(self.serializer, 'get_context_data'):
context = self.serializer.get_context_data()

metadata['context'] = context

user = request.user

if user is None:
Expand Down Expand Up @@ -99,6 +117,8 @@ def get_serializer_info(self, serializer):
to any fields whose Meta.model specifies a default value
"""

self.serializer = serializer

serializer_info = super().get_serializer_info(serializer)

model_class = None
Expand Down
97 changes: 20 additions & 77 deletions InvenTree/build/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,14 +233,8 @@ def get_serializer_context(self):
return ctx


class BuildOutputCreate(generics.CreateAPIView):
"""
API endpoint for creating new build output(s)
"""

queryset = Build.objects.none()

serializer_class = build.serializers.BuildOutputCreateSerializer
class BuildOrderContextMixin:
""" Mixin class which adds build order as serializer context variable """

def get_serializer_context(self):
ctx = super().get_serializer_context()
Expand All @@ -256,30 +250,27 @@ def get_serializer_context(self):
return ctx


class BuildOutputComplete(generics.CreateAPIView):
class BuildOutputCreate(BuildOrderContextMixin, generics.CreateAPIView):
"""
API endpoint for completing build outputs
API endpoint for creating new build output(s)
"""

queryset = Build.objects.none()

serializer_class = build.serializers.BuildOutputCompleteSerializer
serializer_class = build.serializers.BuildOutputCreateSerializer

def get_serializer_context(self):
ctx = super().get_serializer_context()

ctx['request'] = self.request
ctx['to_complete'] = True
class BuildOutputComplete(BuildOrderContextMixin, generics.CreateAPIView):
"""
API endpoint for completing build outputs
"""

try:
ctx['build'] = Build.objects.get(pk=self.kwargs.get('pk', None))
except:
pass
queryset = Build.objects.none()

return ctx
serializer_class = build.serializers.BuildOutputCompleteSerializer


class BuildOutputDelete(generics.CreateAPIView):
class BuildOutputDelete(BuildOrderContextMixin, generics.CreateAPIView):
"""
API endpoint for deleting multiple build outputs
"""
Expand All @@ -288,20 +279,8 @@ class BuildOutputDelete(generics.CreateAPIView):

serializer_class = build.serializers.BuildOutputDeleteSerializer

def get_serializer_context(self):
ctx = super().get_serializer_context()

ctx['request'] = self.request

try:
ctx['build'] = Build.objects.get(pk=self.kwargs.get('pk', None))
except:
pass

return ctx


class BuildFinish(generics.CreateAPIView):
class BuildFinish(BuildOrderContextMixin, generics.CreateAPIView):
"""
API endpoint for marking a build as finished (completed)
"""
Expand All @@ -310,20 +289,8 @@ class BuildFinish(generics.CreateAPIView):

serializer_class = build.serializers.BuildCompleteSerializer

def get_serializer_context(self):
ctx = super().get_serializer_context()

ctx['request'] = self.request

try:
ctx['build'] = Build.objects.get(pk=self.kwargs.get('pk', None))
except:
pass

return ctx


class BuildAutoAllocate(generics.CreateAPIView):
class BuildAutoAllocate(BuildOrderContextMixin, generics.CreateAPIView):
"""
API endpoint for 'automatically' allocating stock against a build order.

Expand All @@ -337,24 +304,8 @@ class BuildAutoAllocate(generics.CreateAPIView):

serializer_class = build.serializers.BuildAutoAllocationSerializer

def get_serializer_context(self):
"""
Provide the Build object to the serializer context
"""

context = super().get_serializer_context()

try:
context['build'] = Build.objects.get(pk=self.kwargs.get('pk', None))
except:
pass

context['request'] = self.request

return context


class BuildAllocate(generics.CreateAPIView):
class BuildAllocate(BuildOrderContextMixin, generics.CreateAPIView):
"""
API endpoint to allocate stock items to a build order

Expand All @@ -370,21 +321,12 @@ class BuildAllocate(generics.CreateAPIView):

serializer_class = build.serializers.BuildAllocationSerializer

def get_serializer_context(self):
"""
Provide the Build object to the serializer context
"""

context = super().get_serializer_context()

try:
context['build'] = Build.objects.get(pk=self.kwargs.get('pk', None))
except:
pass

context['request'] = self.request
class BuildCancel(BuildOrderContextMixin, generics.CreateAPIView):
""" API endpoint for cancelling a BuildOrder """

return context
queryset = Build.objects.all()
serializer_class = build.serializers.BuildCancelSerializer


class BuildItemDetail(generics.RetrieveUpdateDestroyAPIView):
Expand Down Expand Up @@ -527,6 +469,7 @@ class BuildAttachmentDetail(generics.RetrieveUpdateDestroyAPIView, AttachmentMix
re_path(r'^create-output/', BuildOutputCreate.as_view(), name='api-build-output-create'),
re_path(r'^delete-outputs/', BuildOutputDelete.as_view(), name='api-build-output-delete'),
re_path(r'^finish/', BuildFinish.as_view(), name='api-build-finish'),
re_path(r'^cancel/', BuildCancel.as_view(), name='api-build-cancel'),
re_path(r'^unallocate/', BuildUnallocate.as_view(), name='api-build-unallocate'),
re_path(r'^.*$', BuildDetail.as_view(), name='api-build-detail'),
])),
Expand Down
19 changes: 0 additions & 19 deletions InvenTree/build/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,3 @@
# -*- coding: utf-8 -*-

from __future__ import unicode_literals

from django.utils.translation import gettext_lazy as _
from django import forms

from InvenTree.forms import HelperForm

from .models import Build


class CancelBuildForm(HelperForm):
""" Form for cancelling a build """

confirm_cancel = forms.BooleanField(required=False, label=_('Confirm cancel'), help_text=_('Confirm build cancellation'))

class Meta:
model = Build
fields = [
'confirm_cancel'
]
49 changes: 46 additions & 3 deletions InvenTree/build/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,16 @@ def complete_outputs(self):

return outputs

@property
def complete_count(self):

quantity = 0

for output in self.complete_outputs:
quantity += output.quantity

return quantity

@property
def incomplete_outputs(self):
"""
Expand Down Expand Up @@ -588,16 +598,31 @@ def complete_build(self, user):
trigger_event('build.completed', id=self.pk)

@transaction.atomic
def cancelBuild(self, user):
def cancel_build(self, user, **kwargs):
""" Mark the Build as CANCELLED

- Delete any pending BuildItem objects (but do not remove items from stock)
- Set build status to CANCELLED
- Save the Build object
"""

for item in self.allocated_stock.all():
item.delete()
remove_allocated_stock = kwargs.get('remove_allocated_stock', False)
remove_incomplete_outputs = kwargs.get('remove_incomplete_outputs', False)

# Handle stock allocations
for build_item in self.allocated_stock.all():

if remove_allocated_stock:
build_item.complete_allocation(user)

build_item.delete()

# Remove incomplete outputs (if required)
if remove_incomplete_outputs:
outputs = self.build_outputs.filter(is_building=True)

for output in outputs:
output.delete()

# Date of 'completion' is the date the build was cancelled
self.completion_date = datetime.now().date()
Expand Down Expand Up @@ -1025,6 +1050,24 @@ def is_fully_allocated(self, output):
# All parts must be fully allocated!
return True

def is_partially_allocated(self, output):
"""
Returns True if the particular build output is (at least) partially allocated
"""

# If output is not specified, we are talking about "untracked" items
if output is None:
bom_items = self.untracked_bom_items
else:
bom_items = self.tracked_bom_items

for bom_item in bom_items:

if self.allocated_quantity(bom_item, output) > 0:
return True

return False

def are_untracked_parts_allocated(self):
"""
Returns True if the un-tracked parts are fully allocated for this BuildOrder
Expand Down
46 changes: 46 additions & 0 deletions InvenTree/build/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,52 @@ def save(self):
)


class BuildCancelSerializer(serializers.Serializer):

class Meta:
fields = [
'remove_allocated_stock',
'remove_incomplete_outputs',
]

def get_context_data(self):

build = self.context['build']

return {
'has_allocated_stock': build.is_partially_allocated(None),
'incomplete_outputs': build.incomplete_count,
'completed_outputs': build.complete_count,
}

remove_allocated_stock = serializers.BooleanField(
label=_('Remove Allocated Stock'),
help_text=_('Subtract any stock which has already been allocated to this build'),
required=False,
default=False,
)

remove_incomplete_outputs = serializers.BooleanField(
label=_('Remove Incomplete Outputs'),
help_text=_('Delete any build outputs which have not been completed'),
required=False,
default=False,
)

def save(self):

build = self.context['build']
request = self.context['request']

data = self.validated_data

build.cancel_build(
request.user,
remove_allocated_stock=data.get('remove_unallocated_stock', False),
remove_incomplete_outputs=data.get('remove_incomplete_outputs', False),
)


class BuildCompleteSerializer(serializers.Serializer):
"""
DRF serializer for marking a BuildOrder as complete
Expand Down
Loading