Skip to content

Commit

Permalink
Merge pull request #2931 from SchrodingersGat/more-api-functionality
Browse files Browse the repository at this point in the history
Converting more forms to the API
  • Loading branch information
SchrodingersGat authored May 4, 2022
2 parents 4053a91 + 82541ed commit 1dee578
Show file tree
Hide file tree
Showing 48 changed files with 925 additions and 1,759 deletions.
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

0 comments on commit 1dee578

Please sign in to comment.