Merge pull request #118 from SchrodingersGat/travis-fix

Updated travis environment
This commit is contained in:
Oliver 2019-04-14 09:41:34 +10:00 committed by GitHub
commit a465e990b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 119 additions and 73 deletions

View File

@ -1,11 +1,18 @@
dist: xenial
language: python
python:
- 3.5
- 3.6
addons:
apt-packages:
-sqlite3
before_install:
- make setup
- make setup_ci
script:
# - make style
- make test
- make style

View File

@ -11,4 +11,4 @@ class HelperForm(forms.ModelForm):
super(forms.ModelForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.form_tag = False

View File

@ -23,4 +23,4 @@ class AuthRequiredMiddleware(object):
# Code to be executed for each request/response after
# the view is called.
return response
return response

View File

@ -3,10 +3,10 @@ from __future__ import unicode_literals
from rest_framework import serializers
from rest_framework import generics
from rest_framework import mixins
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
class Meta:
@ -33,4 +33,4 @@ class DraftRUDView(generics.RetrieveAPIView, generics.UpdateAPIView, generics.De
if ctx_data.get('_is_final', False) in [True, u'true', u'True', 1]:
super(generics.UpdateAPIView, self).perform_update(serializer)
else:
pass
pass

View File

@ -9,7 +9,6 @@ from django.views.generic import UpdateView, CreateView, DeleteView
from django.views.generic.base import TemplateView
from rest_framework import views
from django.http import JsonResponse
class TreeSerializer(views.APIView):
@ -159,9 +158,6 @@ class AjaxUpdateView(AjaxMixin, UpdateView):
return response
def get(self, request, *args, **kwargs):
response = super(UpdateView, self).get(request, *args, **kwargs)
if request.is_ajax():
form = self.form_class(instance=self.get_object())

View File

@ -16,6 +16,6 @@ class EditBuildForm(HelperForm):
'quantity',
'batch',
'notes',
# 'status',
# 'completion_date',
# 'status',
# 'completion_date',
]

View File

@ -7,11 +7,9 @@ from django.views.generic import DetailView, ListView
from part.models import Part
from .models import Build
from .forms import EditBuildForm
from InvenTree.views import AjaxView, AjaxUpdateView, AjaxCreateView
from django.http import JsonResponse
class BuildIndex(ListView):

View File

@ -31,4 +31,4 @@ class CompanyImageForm(HelperForm):
model = Company
fields = [
'image'
]
]

View File

@ -78,4 +78,4 @@ class Contact(models.Model):
role = models.CharField(max_length=100, blank=True)
company = models.ForeignKey(Company, related_name='contacts',
on_delete = models.CASCADE)
on_delete=models.CASCADE)

View File

@ -15,6 +15,7 @@ class CompanyBriefSerializer(serializers.ModelSerializer):
'name'
]
class CompanySerializer(serializers.ModelSerializer):
url = serializers.CharField(source='get_absolute_url', read_only=True)

View File

@ -3,7 +3,7 @@ from __future__ import unicode_literals
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import filters
from rest_framework import generics, permissions, mixins
from rest_framework import generics, permissions
from django.conf.urls import url, include
@ -17,6 +17,7 @@ from .serializers import CategorySerializer
from InvenTree.views import TreeSerializer
from InvenTree.serializers import DraftRUDView
class PartCategoryTree(TreeSerializer):
title = "Parts"
@ -33,7 +34,7 @@ class CategoryList(generics.ListCreateAPIView):
filter_backends = [
DjangoFilterBackend,
#filters.SearchFilter,
# filters.SearchFilter,
filters.OrderingFilter,
]
@ -61,6 +62,7 @@ class PartDetail(DraftRUDView):
permissions.IsAuthenticatedOrReadOnly,
]
class PartList(generics.ListCreateAPIView):
queryset = Part.objects.all()
@ -133,13 +135,12 @@ class SupplierPartList(generics.ListAPIView):
'supplier'
]
cat_api_urls = [
cat_api_urls = [
url(r'^$', CategoryList.as_view(), name='api-part-category-list'),
]
part_api_urls = [
url(r'^tree/?', PartCategoryTree.as_view(), name='api-part-tree'),
url(r'^category/', include(cat_api_urls)),

View File

@ -7,7 +7,6 @@ from django.utils.translation import gettext_lazy as _
from django.core.exceptions import ValidationError
from django.db import models
from django.db.models import Sum
from django.core.validators import MinValueValidator
from django.db.models.signals import pre_delete
@ -250,7 +249,6 @@ class Part(models.Model):
self.allocated_build_count,
])
@property
def stock_entries(self):
return [loc for loc in self.locations.all() if loc.in_stock]
@ -263,7 +261,6 @@ class Part(models.Model):
return sum([loc.quantity for loc in self.stock_entries])
@property
def has_bom(self):
return self.bom_count > 0

View File

@ -5,6 +5,7 @@ from .models import SupplierPart
from company.serializers import CompanyBriefSerializer
class CategorySerializer(serializers.ModelSerializer):
url = serializers.CharField(source='get_absolute_url', read_only=True)
@ -99,4 +100,4 @@ class SupplierPartSerializer(serializers.ModelSerializer):
'SKU',
'manufacturer',
'MPN',
]
]

View File

@ -1,11 +1,8 @@
from django_filters.rest_framework import FilterSet, DjangoFilterBackend
from django_filters import NumberFilter
from rest_framework import generics, permissions, response, filters
from django.conf.urls import url, include
# from InvenTree.models import FilterChildren
from .models import StockLocation, StockItem
from .models import StockItemTracking
@ -19,8 +16,8 @@ from InvenTree.serializers import DraftRUDView
from rest_framework.serializers import ValidationError
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import authentication, permissions
from django.contrib.auth.models import User
from rest_framework import generics, response, filters, permissions
class StockCategoryTree(TreeSerializer):
title = 'Stock'
@ -69,26 +66,26 @@ class StockStocktake(APIView):
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'})
action = request.data['action']
ACTIONS = ['stocktake', 'remove', 'add']
if not action in ACTIONS:
if action not in 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'})
items = []
# Ensure each entry is valid
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'})
if not 'quantity' in entry:
elif 'quantity' not in entry:
raise ValidationError({'quantity': 'Each entry must contain quantity field'})
item = {}
@ -112,7 +109,6 @@ class StockStocktake(APIView):
if 'notes' in request.data:
notes = request.data['notes']
for item in items:
quantity = int(item['quantity'])
@ -136,7 +132,7 @@ class StockMove(APIView):
data = request.data
if not u'location' in data:
if u'location' not in data:
raise ValidationError({'location': 'Destination must be specified'})
loc_id = data.get(u'location')
@ -146,7 +142,7 @@ class StockMove(APIView):
except StockLocation.DoesNotExist:
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'})
part_list = data.get(u'parts[]')
@ -160,7 +156,7 @@ class StockMove(APIView):
part = StockItem.objects.get(pk=pid)
parts.append(part)
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:
raise ValidationError(errors)
@ -227,7 +223,7 @@ class StockList(generics.ListCreateAPIView):
'supplier_part',
'customer',
'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()
serializer_class = StockTrackingSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly,]
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
filter_backends = [
DjangoFilterBackend,
@ -275,7 +271,6 @@ class StockTrackingList(generics.ListCreateAPIView):
]
class LocationDetail(generics.RetrieveUpdateDestroyAPIView):
"""
@ -303,9 +298,7 @@ location_endpoints = [
url(r'^$', LocationDetail.as_view(), name='stocklocation-detail'),
]
stock_api_urls = [
url(r'location/?', StockLocationList.as_view(), name='api-location-list'),
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'^.*$', StockList.as_view(), name='api-stock-list'),
]
]

View File

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

View File

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

View File

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

View File

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

View File

@ -8,7 +8,7 @@ clean:
rm -f .coverage
style:
flake8 InvenTree
flake8 InvenTree --ignore=C901,E501
test:
python InvenTree/manage.py check

View File

@ -1,4 +1,5 @@
Django==2.2
psycopg2>=2.8.1
pillow>=5.0.0
djangorestframework>=3.6.2
django_filter>=1.0.2