PEP fixes for /stock

This commit is contained in:
Oliver Walters 2019-04-14 09:23:24 +10:00
parent c3312ac935
commit 76b0d17b11
5 changed files with 93 additions and 49 deletions

View File

@ -1,11 +1,8 @@
from django_filters.rest_framework import FilterSet, DjangoFilterBackend from django_filters.rest_framework import FilterSet, DjangoFilterBackend
from django_filters import NumberFilter from django_filters import NumberFilter
from rest_framework import generics, permissions, response, filters
from django.conf.urls import url, include from django.conf.urls import url, include
# from InvenTree.models import FilterChildren
from .models import StockLocation, StockItem from .models import StockLocation, StockItem
from .models import StockItemTracking from .models import StockItemTracking
@ -19,8 +16,8 @@ from InvenTree.serializers import DraftRUDView
from rest_framework.serializers import ValidationError from rest_framework.serializers import ValidationError
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework import authentication, permissions from rest_framework import generics, response, filters, permissions
from django.contrib.auth.models import User
class StockCategoryTree(TreeSerializer): class StockCategoryTree(TreeSerializer):
title = 'Stock' title = 'Stock'
@ -69,26 +66,26 @@ class StockStocktake(APIView):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
if not 'action' in request.data: if 'action' not in request.data:
raise ValidationError({'action': 'Stocktake action must be provided'}) raise ValidationError({'action': 'Stocktake action must be provided'})
action = request.data['action'] action = request.data['action']
ACTIONS = ['stocktake', 'remove', 'add'] ACTIONS = ['stocktake', 'remove', 'add']
if not action in ACTIONS: if action not in ACTIONS:
raise ValidationError({'action': 'Action must be one of ' + ','.join(ACTIONS)}) raise ValidationError({'action': 'Action must be one of ' + ','.join(ACTIONS)})
if not 'items[]' in request.data: elif 'items[]' not in request.data:
raise ValidationError({'items[]:' 'Request must contain list of items'}) raise ValidationError({'items[]:' 'Request must contain list of items'})
items = [] items = []
# Ensure each entry is valid # Ensure each entry is valid
for entry in request.data['items[]']: for entry in request.data['items[]']:
if not 'pk' in entry: if 'pk' not in entry:
raise ValidationError({'pk': 'Each entry must contain pk field'}) raise ValidationError({'pk': 'Each entry must contain pk field'})
if not 'quantity' in entry: elif 'quantity' not in entry:
raise ValidationError({'quantity': 'Each entry must contain quantity field'}) raise ValidationError({'quantity': 'Each entry must contain quantity field'})
item = {} item = {}
@ -112,7 +109,6 @@ class StockStocktake(APIView):
if 'notes' in request.data: if 'notes' in request.data:
notes = request.data['notes'] notes = request.data['notes']
for item in items: for item in items:
quantity = int(item['quantity']) quantity = int(item['quantity'])
@ -136,7 +132,7 @@ class StockMove(APIView):
data = request.data data = request.data
if not u'location' in data: if u'location' not in data:
raise ValidationError({'location': 'Destination must be specified'}) raise ValidationError({'location': 'Destination must be specified'})
loc_id = data.get(u'location') loc_id = data.get(u'location')
@ -146,7 +142,7 @@ class StockMove(APIView):
except StockLocation.DoesNotExist: except StockLocation.DoesNotExist:
raise ValidationError({'location': 'Location does not exist'}) raise ValidationError({'location': 'Location does not exist'})
if not u'parts[]' in data: if u'parts[]' not in data:
raise ValidationError({'parts[]': 'Parts list must be specified'}) raise ValidationError({'parts[]': 'Parts list must be specified'})
part_list = data.get(u'parts[]') part_list = data.get(u'parts[]')
@ -160,7 +156,7 @@ class StockMove(APIView):
part = StockItem.objects.get(pk=pid) part = StockItem.objects.get(pk=pid)
parts.append(part) parts.append(part)
except StockItem.DoesNotExist: except StockItem.DoesNotExist:
errors.append({'part': 'Part {id} does not exist'.format(id=part_id)}) errors.append({'part': 'Part {id} does not exist'.format(id=pid)})
if len(errors) > 0: if len(errors) > 0:
raise ValidationError(errors) raise ValidationError(errors)
@ -227,7 +223,7 @@ class StockList(generics.ListCreateAPIView):
'supplier_part', 'supplier_part',
'customer', 'customer',
'belongs_to', 'belongs_to',
#'status', # 'status' TODO - There are some issues filtering based on an enumeration field
] ]
@ -250,7 +246,7 @@ class StockTrackingList(generics.ListCreateAPIView):
queryset = StockItemTracking.objects.all() queryset = StockItemTracking.objects.all()
serializer_class = StockTrackingSerializer serializer_class = StockTrackingSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly,] permission_classes = [permissions.IsAuthenticatedOrReadOnly]
filter_backends = [ filter_backends = [
DjangoFilterBackend, DjangoFilterBackend,
@ -275,7 +271,6 @@ class StockTrackingList(generics.ListCreateAPIView):
] ]
class LocationDetail(generics.RetrieveUpdateDestroyAPIView): class LocationDetail(generics.RetrieveUpdateDestroyAPIView):
""" """
@ -303,9 +298,7 @@ location_endpoints = [
url(r'^$', LocationDetail.as_view(), name='stocklocation-detail'), url(r'^$', LocationDetail.as_view(), name='stocklocation-detail'),
] ]
stock_api_urls = [ stock_api_urls = [
url(r'location/?', StockLocationList.as_view(), name='api-location-list'), url(r'location/?', StockLocationList.as_view(), name='api-location-list'),
url(r'location/(?P<pk>\d+)/', include(location_endpoints)), url(r'location/(?P<pk>\d+)/', include(location_endpoints)),
@ -322,4 +315,4 @@ stock_api_urls = [
url(r'^(?P<pk>\d+)/', include(stock_endpoints)), url(r'^(?P<pk>\d+)/', include(stock_endpoints)),
url(r'^.*$', StockList.as_view(), name='api-stock-list'), url(r'^.*$', StockList.as_view(), name='api-stock-list'),
] ]

View File

@ -67,4 +67,4 @@ class EditStockItemForm(HelperForm):
'batch', 'batch',
'status', 'status',
'notes' 'notes'
] ]

View File

@ -69,12 +69,11 @@ class StockItem(models.Model):
if add_note: if add_note:
# This StockItem is being saved for the first time # This StockItem is being saved for the first time
self.add_transaction_note( self.add_transaction_note(
'Created stock item', 'Created stock item',
None, None,
system=True system=True
) )
def clean(self): def clean(self):
# The 'supplier_part' field must point to the same part! # The 'supplier_part' field must point to the same part!
@ -227,8 +226,10 @@ class StockItem(models.Model):
if location.pk == self.location.pk: if location.pk == self.location.pk:
return False # raise forms.ValidationError("Cannot move item to its current location") return False # raise forms.ValidationError("Cannot move item to its current location")
msg = "Moved to {loc} (from {src})".format(loc=location.name, msg = "Moved to {loc} (from {src})".format(
src=self.location.name) loc=location.name,
src=self.location.name
)
self.location = location self.location = location
self.save() self.save()
@ -240,7 +241,6 @@ class StockItem(models.Model):
return True return True
@transaction.atomic @transaction.atomic
def stocktake(self, count, user, notes=''): def stocktake(self, count, user, notes=''):
""" Perform item stocktake. """ Perform item stocktake.

View File

@ -4,9 +4,13 @@ from .models import StockItem, StockLocation
from .models import StockItemTracking from .models import StockItemTracking
from part.serializers import PartBriefSerializer from part.serializers import PartBriefSerializer
from InvenTree.serializers import UserSerializer, UserSerializerBrief from InvenTree.serializers import UserSerializerBrief
class LocationBriefSerializer(serializers.ModelSerializer): class LocationBriefSerializer(serializers.ModelSerializer):
"""
Provides a brief serializer for a StockLocation object
"""
url = serializers.CharField(source='get_absolute_url', read_only=True) url = serializers.CharField(source='get_absolute_url', read_only=True)
@ -49,8 +53,12 @@ class StockTrackingSerializer(serializers.ModelSerializer):
class StockItemSerializer(serializers.ModelSerializer): class StockItemSerializer(serializers.ModelSerializer):
""" Serializer for a StockItem
""" """
Serializer for a StockItem
- Includes serialization for the linked part
- Includes serialization for the item location
"""
url = serializers.CharField(source='get_absolute_url', read_only=True) url = serializers.CharField(source='get_absolute_url', read_only=True)
part = PartBriefSerializer(many=False, read_only=True) part = PartBriefSerializer(many=False, read_only=True)
@ -66,17 +74,11 @@ class StockItemSerializer(serializers.ModelSerializer):
'supplier_part', 'supplier_part',
'location', 'location',
'in_stock', 'in_stock',
#'belongs_to',
#'customer',
'quantity', 'quantity',
'serial', 'serial',
'batch', 'batch',
'status', 'status',
'notes', 'notes',
#'updated',
#'stocktake_date',
#'stocktake_user',
#'review_needed',
] ]
""" These fields are read-only in this context. """ These fields are read-only in this context.
@ -86,7 +88,8 @@ class StockItemSerializer(serializers.ModelSerializer):
'stocktake_date', 'stocktake_date',
'stocktake_user', 'stocktake_user',
'updated', 'updated',
#'quantity', 'quantity',
'in_stock'
] ]
@ -106,10 +109,10 @@ class LocationSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = StockLocation model = StockLocation
fields = [ fields = [
'pk', 'pk',
'url', 'url',
'name', 'name',
'description', 'description',
'parent', 'parent',
'pathstring' 'pathstring'
] ]

View File

@ -10,15 +10,17 @@ from InvenTree.views import AjaxUpdateView, AjaxDeleteView, AjaxCreateView
from part.models import Part from part.models import Part
from .models import StockItem, StockLocation from .models import StockItem, StockLocation
import datetime
from .forms import EditStockLocationForm from .forms import EditStockLocationForm
from .forms import CreateStockItemForm from .forms import CreateStockItemForm
from .forms import EditStockItemForm from .forms import EditStockItemForm
from .forms import MoveStockItemForm from .forms import MoveStockItemForm
from .forms import StocktakeForm from .forms import StocktakeForm
class StockIndex(ListView): class StockIndex(ListView):
"""
StockIndex view loads all StockLocation and StockItem object
"""
model = StockItem model = StockItem
template_name = 'stock/location.html' template_name = 'stock/location.html'
context_obect_name = 'locations' context_obect_name = 'locations'
@ -36,6 +38,10 @@ class StockIndex(ListView):
class StockLocationDetail(DetailView): class StockLocationDetail(DetailView):
"""
Detailed view of a single StockLocation object
"""
context_object_name = 'location' context_object_name = 'location'
template_name = 'stock/location.html' template_name = 'stock/location.html'
queryset = StockLocation.objects.all() queryset = StockLocation.objects.all()
@ -43,6 +49,10 @@ class StockLocationDetail(DetailView):
class StockItemDetail(DetailView): class StockItemDetail(DetailView):
"""
Detailed view of a single StockItem object
"""
context_object_name = 'item' context_object_name = 'item'
template_name = 'stock/item.html' template_name = 'stock/item.html'
queryset = StockItem.objects.all() queryset = StockItem.objects.all()
@ -50,6 +60,11 @@ class StockItemDetail(DetailView):
class StockLocationEdit(AjaxUpdateView): class StockLocationEdit(AjaxUpdateView):
"""
View for editing details of a StockLocation.
This view is used with the EditStockLocationForm to deliver a modal form to the web view
"""
model = StockLocation model = StockLocation
form_class = EditStockLocationForm form_class = EditStockLocationForm
template_name = 'stock/location_edit.html' template_name = 'stock/location_edit.html'
@ -59,6 +74,10 @@ class StockLocationEdit(AjaxUpdateView):
class StockItemEdit(AjaxUpdateView): class StockItemEdit(AjaxUpdateView):
"""
View for editing details of a single StockItem
"""
model = StockItem model = StockItem
form_class = EditStockItemForm form_class = EditStockItemForm
template_name = 'stock/item_edit.html' template_name = 'stock/item_edit.html'
@ -68,6 +87,11 @@ class StockItemEdit(AjaxUpdateView):
class StockLocationCreate(AjaxCreateView): class StockLocationCreate(AjaxCreateView):
"""
View for creating a new StockLocation
A parent location (another StockLocation object) can be passed as a query parameter
"""
model = StockLocation model = StockLocation
form_class = EditStockLocationForm form_class = EditStockLocationForm
template_name = 'stock/location_create.html' template_name = 'stock/location_create.html'
@ -87,6 +111,13 @@ class StockLocationCreate(AjaxCreateView):
class StockItemCreate(AjaxCreateView): class StockItemCreate(AjaxCreateView):
"""
View for creating a new StockItem
Parameters can be pre-filled by passing query items:
- part: The part of which the new StockItem is an instance
- location: The location of the new StockItem
"""
model = StockItem model = StockItem
form_class = CreateStockItemForm form_class = CreateStockItemForm
template_name = 'stock/item_create.html' template_name = 'stock/item_create.html'
@ -114,6 +145,11 @@ class StockItemCreate(AjaxCreateView):
class StockLocationDelete(AjaxDeleteView): class StockLocationDelete(AjaxDeleteView):
"""
View to delete a StockLocation
Presents a deletion confirmation form to the user
"""
model = StockLocation model = StockLocation
success_url = '/stock' success_url = '/stock'
template_name = 'stock/location_delete.html' template_name = 'stock/location_delete.html'
@ -122,6 +158,11 @@ class StockLocationDelete(AjaxDeleteView):
class StockItemDelete(AjaxDeleteView): class StockItemDelete(AjaxDeleteView):
"""
View to delete a StockItem
Presents a deletion confirmation form to the user
"""
model = StockItem model = StockItem
success_url = '/stock/' success_url = '/stock/'
template_name = 'stock/item_delete.html' template_name = 'stock/item_delete.html'
@ -130,6 +171,11 @@ class StockItemDelete(AjaxDeleteView):
class StockItemMove(AjaxUpdateView): class StockItemMove(AjaxUpdateView):
"""
View to move a StockItem from one location to another
Performs some data validation to prevent illogical stock moves
"""
model = StockItem model = StockItem
template_name = 'modal_form.html' template_name = 'modal_form.html'
context_object_name = 'item' context_object_name = 'item'
@ -156,7 +202,6 @@ class StockItemMove(AjaxUpdateView):
form.errors['location'] = ['Cannot move to an empty location'] form.errors['location'] = ['Cannot move to an empty location']
except StockLocation.DoesNotExist: except StockLocation.DoesNotExist:
loc_path = ''
form.errors['location'] = ['Location does not exist'] form.errors['location'] = ['Location does not exist']
data = { data = {
@ -167,6 +212,11 @@ class StockItemMove(AjaxUpdateView):
class StockItemStocktake(AjaxUpdateView): class StockItemStocktake(AjaxUpdateView):
"""
View to perform stocktake on a single StockItem
Updates the quantity, which will also create a new StockItemTracking item
"""
model = StockItem model = StockItem
template_name = 'modal_form.html' template_name = 'modal_form.html'
context_object_name = 'item' context_object_name = 'item'
@ -188,5 +238,3 @@ class StockItemStocktake(AjaxUpdateView):
} }
return self.renderJsonResponse(request, form, data) return self.renderJsonResponse(request, form, data)