mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-28 11:36:44 +00:00
parent
b1930404bd
commit
d884e62be1
@ -46,7 +46,7 @@ class InvenTreeAPITestCase(APITestCase):
|
|||||||
self.user.is_staff = True
|
self.user.is_staff = True
|
||||||
|
|
||||||
self.user.save()
|
self.user.save()
|
||||||
|
|
||||||
for role in self.roles:
|
for role in self.roles:
|
||||||
self.assignRole(role)
|
self.assignRole(role)
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ class InvenTreeModelMoneyField(ModelMoneyField):
|
|||||||
"""
|
"""
|
||||||
Custom MoneyField for clean migrations while using dynamic currency settings
|
Custom MoneyField for clean migrations while using dynamic currency settings
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
# detect if creating migration
|
# detect if creating migration
|
||||||
if 'migrate' in sys.argv or 'makemigrations' in sys.argv:
|
if 'migrate' in sys.argv or 'makemigrations' in sys.argv:
|
||||||
|
@ -38,7 +38,7 @@ class InvenTreeOrderingFilter(OrderingFilter):
|
|||||||
ordering = []
|
ordering = []
|
||||||
|
|
||||||
for field in ordering_initial:
|
for field in ordering_initial:
|
||||||
|
|
||||||
reverse = field.startswith('-')
|
reverse = field.startswith('-')
|
||||||
|
|
||||||
if reverse:
|
if reverse:
|
||||||
@ -52,7 +52,7 @@ class InvenTreeOrderingFilter(OrderingFilter):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
Potentially, a single field could be "aliased" to multiple field,
|
Potentially, a single field could be "aliased" to multiple field,
|
||||||
|
|
||||||
(For example to enforce a particular ordering sequence)
|
(For example to enforce a particular ordering sequence)
|
||||||
|
|
||||||
e.g. to filter first by the integer value...
|
e.g. to filter first by the integer value...
|
||||||
|
@ -36,7 +36,7 @@ class Command(BaseCommand):
|
|||||||
img = model.image
|
img = model.image
|
||||||
url = img.thumbnail.name
|
url = img.thumbnail.name
|
||||||
loc = os.path.join(settings.MEDIA_ROOT, url)
|
loc = os.path.join(settings.MEDIA_ROOT, url)
|
||||||
|
|
||||||
if not os.path.exists(loc):
|
if not os.path.exists(loc):
|
||||||
logger.info(f"Generating thumbnail image for '{img}'")
|
logger.info(f"Generating thumbnail image for '{img}'")
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ class InvenTreeMetadata(SimpleMetadata):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def determine_metadata(self, request, view):
|
def determine_metadata(self, request, view):
|
||||||
|
|
||||||
self.request = request
|
self.request = request
|
||||||
self.view = view
|
self.view = view
|
||||||
|
|
||||||
@ -98,7 +98,7 @@ class InvenTreeMetadata(SimpleMetadata):
|
|||||||
Override get_serializer_info so that we can add 'default' values
|
Override get_serializer_info so that we can add 'default' values
|
||||||
to any fields whose Meta.model specifies a default value
|
to any fields whose Meta.model specifies a default value
|
||||||
"""
|
"""
|
||||||
|
|
||||||
serializer_info = super().get_serializer_info(serializer)
|
serializer_info = super().get_serializer_info(serializer)
|
||||||
|
|
||||||
model_class = None
|
model_class = None
|
||||||
@ -174,7 +174,7 @@ class InvenTreeMetadata(SimpleMetadata):
|
|||||||
# Extract extra information if an instance is available
|
# Extract extra information if an instance is available
|
||||||
if hasattr(serializer, 'instance'):
|
if hasattr(serializer, 'instance'):
|
||||||
instance = serializer.instance
|
instance = serializer.instance
|
||||||
|
|
||||||
if instance is None and model_class is not None:
|
if instance is None and model_class is not None:
|
||||||
# Attempt to find the instance based on kwargs lookup
|
# Attempt to find the instance based on kwargs lookup
|
||||||
kwargs = getattr(self.view, 'kwargs', None)
|
kwargs = getattr(self.view, 'kwargs', None)
|
||||||
@ -240,7 +240,7 @@ class InvenTreeMetadata(SimpleMetadata):
|
|||||||
|
|
||||||
# Introspect writable related fields
|
# Introspect writable related fields
|
||||||
if field_info['type'] == 'field' and not field_info['read_only']:
|
if field_info['type'] == 'field' and not field_info['read_only']:
|
||||||
|
|
||||||
# If the field is a PrimaryKeyRelatedField, we can extract the model from the queryset
|
# If the field is a PrimaryKeyRelatedField, we can extract the model from the queryset
|
||||||
if isinstance(field, serializers.PrimaryKeyRelatedField):
|
if isinstance(field, serializers.PrimaryKeyRelatedField):
|
||||||
model = field.queryset.model
|
model = field.queryset.model
|
||||||
|
@ -66,7 +66,7 @@ class InvenTreeMoneySerializer(MoneyField):
|
|||||||
|
|
||||||
if currency and amount is not None and not isinstance(amount, MONEY_CLASSES) and amount is not empty:
|
if currency and amount is not None and not isinstance(amount, MONEY_CLASSES) and amount is not empty:
|
||||||
return Money(amount, currency)
|
return Money(amount, currency)
|
||||||
|
|
||||||
return amount
|
return amount
|
||||||
|
|
||||||
|
|
||||||
|
@ -106,7 +106,7 @@ def offload_task(taskname, *args, force_sync=False, **kwargs):
|
|||||||
except NameError:
|
except NameError:
|
||||||
logger.warning(f"WARNING: '{taskname}' not started - No function named '{func}'")
|
logger.warning(f"WARNING: '{taskname}' not started - No function named '{func}'")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Workers are not running: run it as synchronous task
|
# Workers are not running: run it as synchronous task
|
||||||
_func(*args, **kwargs)
|
_func(*args, **kwargs)
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ from base64 import b64encode
|
|||||||
class HTMLAPITests(TestCase):
|
class HTMLAPITests(TestCase):
|
||||||
"""
|
"""
|
||||||
Test that we can access the REST API endpoints via the HTML interface.
|
Test that we can access the REST API endpoints via the HTML interface.
|
||||||
|
|
||||||
History: Discovered on 2021-06-28 a bug in InvenTreeModelSerializer,
|
History: Discovered on 2021-06-28 a bug in InvenTreeModelSerializer,
|
||||||
which raised an AssertionError when using the HTML API interface,
|
which raised an AssertionError when using the HTML API interface,
|
||||||
while the regular JSON interface continued to work as expected.
|
while the regular JSON interface continued to work as expected.
|
||||||
@ -280,7 +280,7 @@ class APITests(InvenTreeAPITestCase):
|
|||||||
"""
|
"""
|
||||||
Tests for detail API endpoint actions
|
Tests for detail API endpoint actions
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.basicAuth()
|
self.basicAuth()
|
||||||
|
|
||||||
url = reverse('api-part-detail', kwargs={'pk': 1})
|
url = reverse('api-part-detail', kwargs={'pk': 1})
|
||||||
|
@ -77,7 +77,7 @@ apipatterns = [
|
|||||||
settings_urls = [
|
settings_urls = [
|
||||||
|
|
||||||
url(r'^i18n/?', include('django.conf.urls.i18n')),
|
url(r'^i18n/?', include('django.conf.urls.i18n')),
|
||||||
|
|
||||||
url(r'^appearance/?', AppearanceSelectView.as_view(), name='settings-appearance'),
|
url(r'^appearance/?', AppearanceSelectView.as_view(), name='settings-appearance'),
|
||||||
url(r'^currencies-refresh/', CurrencyRefreshView.as_view(), name='settings-currencies-refresh'),
|
url(r'^currencies-refresh/', CurrencyRefreshView.as_view(), name='settings-currencies-refresh'),
|
||||||
|
|
||||||
|
@ -120,10 +120,10 @@ def isInvenTreeDevelopmentVersion():
|
|||||||
def inventreeDocsVersion():
|
def inventreeDocsVersion():
|
||||||
"""
|
"""
|
||||||
Return the version string matching the latest documentation.
|
Return the version string matching the latest documentation.
|
||||||
|
|
||||||
Development -> "latest"
|
Development -> "latest"
|
||||||
Release -> "major.minor.sub" e.g. "0.5.2"
|
Release -> "major.minor.sub" e.g. "0.5.2"
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if isInvenTreeDevelopmentVersion():
|
if isInvenTreeDevelopmentVersion():
|
||||||
|
@ -198,7 +198,7 @@ class BuildUnallocate(generics.CreateAPIView):
|
|||||||
queryset = Build.objects.none()
|
queryset = Build.objects.none()
|
||||||
|
|
||||||
serializer_class = BuildUnallocationSerializer
|
serializer_class = BuildUnallocationSerializer
|
||||||
|
|
||||||
def get_serializer_context(self):
|
def get_serializer_context(self):
|
||||||
|
|
||||||
ctx = super().get_serializer_context()
|
ctx = super().get_serializer_context()
|
||||||
@ -231,7 +231,7 @@ class BuildComplete(generics.CreateAPIView):
|
|||||||
ctx['build'] = Build.objects.get(pk=self.kwargs.get('pk', None))
|
ctx['build'] = Build.objects.get(pk=self.kwargs.get('pk', None))
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
|
|
||||||
@ -296,7 +296,7 @@ class BuildItemList(generics.ListCreateAPIView):
|
|||||||
kwargs['location_detail'] = str2bool(params.get('location_detail', False))
|
kwargs['location_detail'] = str2bool(params.get('location_detail', False))
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return self.serializer_class(*args, **kwargs)
|
return self.serializer_class(*args, **kwargs)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
@ -66,7 +66,7 @@ def get_next_build_number():
|
|||||||
attempts.add(reference)
|
attempts.add(reference)
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
|
|
||||||
return reference
|
return reference
|
||||||
|
|
||||||
|
|
||||||
@ -94,13 +94,13 @@ class Build(MPTTModel, ReferenceIndexingMixin):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
OVERDUE_FILTER = Q(status__in=BuildStatus.ACTIVE_CODES) & ~Q(target_date=None) & Q(target_date__lte=datetime.now().date())
|
OVERDUE_FILTER = Q(status__in=BuildStatus.ACTIVE_CODES) & ~Q(target_date=None) & Q(target_date__lte=datetime.now().date())
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_api_url():
|
def get_api_url():
|
||||||
return reverse('api-build-list')
|
return reverse('api-build-list')
|
||||||
|
|
||||||
def api_instance_filters(self):
|
def api_instance_filters(self):
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'parent': {
|
'parent': {
|
||||||
'exclude_tree': self.pk,
|
'exclude_tree': self.pk,
|
||||||
@ -1178,7 +1178,7 @@ class BuildItem(models.Model):
|
|||||||
bom_item = PartModels.BomItem.objects.get(part=self.build.part, sub_part=ancestor)
|
bom_item = PartModels.BomItem.objects.get(part=self.build.part, sub_part=ancestor)
|
||||||
except PartModels.BomItem.DoesNotExist:
|
except PartModels.BomItem.DoesNotExist:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# A matching BOM item has been found!
|
# A matching BOM item has been found!
|
||||||
if idx == 0 or bom_item.allow_variants:
|
if idx == 0 or bom_item.allow_variants:
|
||||||
bom_item_valid = True
|
bom_item_valid = True
|
||||||
@ -1234,7 +1234,7 @@ class BuildItem(models.Model):
|
|||||||
thumb_url = self.stock_item.part.image.thumbnail.url
|
thumb_url = self.stock_item.part.image.thumbnail.url
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if thumb_url is None and self.bom_item and self.bom_item.sub_part:
|
if thumb_url is None and self.bom_item and self.bom_item.sub_part:
|
||||||
try:
|
try:
|
||||||
thumb_url = self.bom_item.sub_part.image.thumbnail.url
|
thumb_url = self.bom_item.sub_part.image.thumbnail.url
|
||||||
|
@ -309,7 +309,7 @@ class BuildAllocationItemSerializer(serializers.Serializer):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def validate_bom_item(self, bom_item):
|
def validate_bom_item(self, bom_item):
|
||||||
|
|
||||||
# TODO: Fix this validation - allow for variants and substitutes!
|
# TODO: Fix this validation - allow for variants and substitutes!
|
||||||
|
|
||||||
build = self.context['build']
|
build = self.context['build']
|
||||||
@ -332,7 +332,7 @@ class BuildAllocationItemSerializer(serializers.Serializer):
|
|||||||
|
|
||||||
if not stock_item.in_stock:
|
if not stock_item.in_stock:
|
||||||
raise ValidationError(_("Item must be in stock"))
|
raise ValidationError(_("Item must be in stock"))
|
||||||
|
|
||||||
return stock_item
|
return stock_item
|
||||||
|
|
||||||
quantity = serializers.DecimalField(
|
quantity = serializers.DecimalField(
|
||||||
@ -398,7 +398,7 @@ class BuildAllocationItemSerializer(serializers.Serializer):
|
|||||||
|
|
||||||
# Output *cannot* be set for un-tracked parts
|
# Output *cannot* be set for un-tracked parts
|
||||||
if output is not None and not bom_item.sub_part.trackable:
|
if output is not None and not bom_item.sub_part.trackable:
|
||||||
|
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'output': _('Build output cannot be specified for allocation of untracked parts')
|
'output': _('Build output cannot be specified for allocation of untracked parts')
|
||||||
})
|
})
|
||||||
@ -422,14 +422,14 @@ class BuildAllocationSerializer(serializers.Serializer):
|
|||||||
"""
|
"""
|
||||||
Validation
|
Validation
|
||||||
"""
|
"""
|
||||||
|
|
||||||
super().validate(data)
|
super().validate(data)
|
||||||
|
|
||||||
items = data.get('items', [])
|
items = data.get('items', [])
|
||||||
|
|
||||||
if len(items) == 0:
|
if len(items) == 0:
|
||||||
raise ValidationError(_('Allocation items must be provided'))
|
raise ValidationError(_('Allocation items must be provided'))
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
|
@ -73,7 +73,7 @@ class GlobalSettingsDetail(generics.RetrieveUpdateAPIView):
|
|||||||
permission_classes = [
|
permission_classes = [
|
||||||
GlobalSettingsPermissions,
|
GlobalSettingsPermissions,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class UserSettingsList(SettingsList):
|
class UserSettingsList(SettingsList):
|
||||||
"""
|
"""
|
||||||
@ -124,7 +124,7 @@ class UserSettingsDetail(generics.RetrieveUpdateAPIView):
|
|||||||
|
|
||||||
queryset = common.models.InvenTreeUserSetting.objects.all()
|
queryset = common.models.InvenTreeUserSetting.objects.all()
|
||||||
serializer_class = common.serializers.UserSettingsSerializer
|
serializer_class = common.serializers.UserSettingsSerializer
|
||||||
|
|
||||||
permission_classes = [
|
permission_classes = [
|
||||||
UserSettingsPermissions,
|
UserSettingsPermissions,
|
||||||
]
|
]
|
||||||
|
@ -12,7 +12,7 @@ class CommonConfig(AppConfig):
|
|||||||
name = 'common'
|
name = 'common'
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
|
|
||||||
self.clear_restart_flag()
|
self.clear_restart_flag()
|
||||||
|
|
||||||
def clear_restart_flag(self):
|
def clear_restart_flag(self):
|
||||||
@ -22,7 +22,7 @@ class CommonConfig(AppConfig):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
import common.models
|
import common.models
|
||||||
|
|
||||||
if common.models.InvenTreeSetting.get_setting('SERVER_RESTART_REQUIRED'):
|
if common.models.InvenTreeSetting.get_setting('SERVER_RESTART_REQUIRED'):
|
||||||
logger.info("Clearing SERVER_RESTART_REQUIRED flag")
|
logger.info("Clearing SERVER_RESTART_REQUIRED flag")
|
||||||
common.models.InvenTreeSetting.set_setting('SERVER_RESTART_REQUIRED', False, None)
|
common.models.InvenTreeSetting.set_setting('SERVER_RESTART_REQUIRED', False, None)
|
||||||
|
@ -487,7 +487,7 @@ class BaseInvenTreeSetting(models.Model):
|
|||||||
|
|
||||||
elif self.is_int():
|
elif self.is_int():
|
||||||
return 'integer'
|
return 'integer'
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return 'string'
|
return 'string'
|
||||||
|
|
||||||
|
@ -170,7 +170,7 @@ class ManufacturerPartParameterList(generics.ListCreateAPIView):
|
|||||||
|
|
||||||
queryset = ManufacturerPartParameter.objects.all()
|
queryset = ManufacturerPartParameter.objects.all()
|
||||||
serializer_class = ManufacturerPartParameterSerializer
|
serializer_class = ManufacturerPartParameterSerializer
|
||||||
|
|
||||||
def get_serializer(self, *args, **kwargs):
|
def get_serializer(self, *args, **kwargs):
|
||||||
|
|
||||||
# Do we wish to include any extra detail?
|
# Do we wish to include any extra detail?
|
||||||
|
@ -477,7 +477,7 @@ class SupplierPart(models.Model):
|
|||||||
return reverse('supplier-part-detail', kwargs={'pk': self.id})
|
return reverse('supplier-part-detail', kwargs={'pk': self.id})
|
||||||
|
|
||||||
def api_instance_filters(self):
|
def api_instance_filters(self):
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'manufacturer_part': {
|
'manufacturer_part': {
|
||||||
'part': self.part.pk
|
'part': self.part.pk
|
||||||
|
@ -187,7 +187,7 @@ class SupplierPartSerializer(InvenTreeModelSerializer):
|
|||||||
part_detail = kwargs.pop('part_detail', True)
|
part_detail = kwargs.pop('part_detail', True)
|
||||||
supplier_detail = kwargs.pop('supplier_detail', True)
|
supplier_detail = kwargs.pop('supplier_detail', True)
|
||||||
manufacturer_detail = kwargs.pop('manufacturer_detail', True)
|
manufacturer_detail = kwargs.pop('manufacturer_detail', True)
|
||||||
|
|
||||||
prettify = kwargs.pop('pretty', False)
|
prettify = kwargs.pop('pretty', False)
|
||||||
|
|
||||||
super(SupplierPartSerializer, self).__init__(*args, **kwargs)
|
super(SupplierPartSerializer, self).__init__(*args, **kwargs)
|
||||||
|
@ -202,7 +202,7 @@ class ManufacturerTest(InvenTreeAPITestCase):
|
|||||||
data = {
|
data = {
|
||||||
'MPN': 'MPN-TEST-123',
|
'MPN': 'MPN-TEST-123',
|
||||||
}
|
}
|
||||||
|
|
||||||
response = self.client.patch(url, data, format='json')
|
response = self.client.patch(url, data, format='json')
|
||||||
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
@ -29,7 +29,7 @@ company_urls = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
manufacturer_part_urls = [
|
manufacturer_part_urls = [
|
||||||
|
|
||||||
url(r'^(?P<pk>\d+)/', views.ManufacturerPartDetail.as_view(template_name='company/manufacturer_part.html'), name='manufacturer-part-detail'),
|
url(r'^(?P<pk>\d+)/', views.ManufacturerPartDetail.as_view(template_name='company/manufacturer_part.html'), name='manufacturer-part-detail'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -399,7 +399,7 @@ class PartLabelMixin:
|
|||||||
if key in params:
|
if key in params:
|
||||||
parts = params.getlist(key, [])
|
parts = params.getlist(key, [])
|
||||||
break
|
break
|
||||||
|
|
||||||
valid_ids = []
|
valid_ids = []
|
||||||
|
|
||||||
for part in parts:
|
for part in parts:
|
||||||
|
@ -186,7 +186,7 @@ class LabelTemplate(models.Model):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
template_string = Template(self.filename_pattern)
|
template_string = Template(self.filename_pattern)
|
||||||
|
|
||||||
ctx = self.context(request)
|
ctx = self.context(request)
|
||||||
|
|
||||||
context = Context(ctx)
|
context = Context(ctx)
|
||||||
|
@ -49,7 +49,7 @@ class POList(generics.ListCreateAPIView):
|
|||||||
"""
|
"""
|
||||||
serializer = self.get_serializer(data=request.data)
|
serializer = self.get_serializer(data=request.data)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
item = serializer.save()
|
item = serializer.save()
|
||||||
item.created_by = request.user
|
item.created_by = request.user
|
||||||
item.save()
|
item.save()
|
||||||
@ -404,7 +404,7 @@ class SOList(generics.ListCreateAPIView):
|
|||||||
"""
|
"""
|
||||||
serializer = self.get_serializer(data=request.data)
|
serializer = self.get_serializer(data=request.data)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
item = serializer.save()
|
item = serializer.save()
|
||||||
item.created_by = request.user
|
item.created_by = request.user
|
||||||
item.save()
|
item.save()
|
||||||
|
@ -772,7 +772,7 @@ class PurchaseOrderLineItem(OrderLineItem):
|
|||||||
def get_base_part(self):
|
def get_base_part(self):
|
||||||
"""
|
"""
|
||||||
Return the base part.Part object for the line item
|
Return the base part.Part object for the line item
|
||||||
|
|
||||||
Note: Returns None if the SupplierPart is not set!
|
Note: Returns None if the SupplierPart is not set!
|
||||||
"""
|
"""
|
||||||
if self.part is None:
|
if self.part is None:
|
||||||
|
@ -553,10 +553,10 @@ class SOLineItemSerializer(InvenTreeModelSerializer):
|
|||||||
allocations = SalesOrderAllocationSerializer(many=True, read_only=True, location_detail=True)
|
allocations = SalesOrderAllocationSerializer(many=True, read_only=True, location_detail=True)
|
||||||
|
|
||||||
quantity = InvenTreeDecimalField()
|
quantity = InvenTreeDecimalField()
|
||||||
|
|
||||||
allocated = serializers.FloatField(source='allocated_quantity', read_only=True)
|
allocated = serializers.FloatField(source='allocated_quantity', read_only=True)
|
||||||
fulfilled = serializers.FloatField(source='fulfilled_quantity', read_only=True)
|
fulfilled = serializers.FloatField(source='fulfilled_quantity', read_only=True)
|
||||||
|
|
||||||
sale_price = InvenTreeMoneySerializer(
|
sale_price = InvenTreeMoneySerializer(
|
||||||
allow_null=True
|
allow_null=True
|
||||||
)
|
)
|
||||||
|
@ -228,7 +228,7 @@ class PurchaseOrderReceiveTest(OrderTest):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
|
||||||
self.assignRole('purchase_order.add')
|
self.assignRole('purchase_order.add')
|
||||||
|
|
||||||
self.url = reverse('api-po-receive', kwargs={'pk': 1})
|
self.url = reverse('api-po-receive', kwargs={'pk': 1})
|
||||||
|
@ -406,7 +406,7 @@ class PurchaseOrderUpload(FileManagementFormView):
|
|||||||
|
|
||||||
def done(self, form_list, **kwargs):
|
def done(self, form_list, **kwargs):
|
||||||
""" Once all the data is in, process it to add PurchaseOrderLineItem instances to the order """
|
""" Once all the data is in, process it to add PurchaseOrderLineItem instances to the order """
|
||||||
|
|
||||||
order = self.get_order()
|
order = self.get_order()
|
||||||
items = self.get_clean_items()
|
items = self.get_clean_items()
|
||||||
|
|
||||||
@ -432,7 +432,7 @@ class PurchaseOrderUpload(FileManagementFormView):
|
|||||||
except IntegrityError:
|
except IntegrityError:
|
||||||
# PurchaseOrderLineItem already exists
|
# PurchaseOrderLineItem already exists
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return HttpResponseRedirect(reverse('po-detail', kwargs={'pk': self.kwargs['pk']}))
|
return HttpResponseRedirect(reverse('po-detail', kwargs={'pk': self.kwargs['pk']}))
|
||||||
|
|
||||||
|
|
||||||
@ -449,7 +449,7 @@ class SalesOrderExport(AjaxView):
|
|||||||
role_required = 'sales_order.view'
|
role_required = 'sales_order.view'
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
|
|
||||||
order = get_object_or_404(SalesOrder, pk=self.kwargs.get('pk', None))
|
order = get_object_or_404(SalesOrder, pk=self.kwargs.get('pk', None))
|
||||||
|
|
||||||
export_format = request.GET.get('format', 'csv')
|
export_format = request.GET.get('format', 'csv')
|
||||||
|
@ -169,7 +169,7 @@ class CategoryDetail(generics.RetrieveUpdateDestroyAPIView):
|
|||||||
"""
|
"""
|
||||||
API endpoint for detail view of a single PartCategory object
|
API endpoint for detail view of a single PartCategory object
|
||||||
"""
|
"""
|
||||||
|
|
||||||
serializer_class = part_serializers.CategorySerializer
|
serializer_class = part_serializers.CategorySerializer
|
||||||
queryset = PartCategory.objects.all()
|
queryset = PartCategory.objects.all()
|
||||||
|
|
||||||
@ -222,7 +222,7 @@ class CategoryParameterList(generics.ListAPIView):
|
|||||||
|
|
||||||
if category is not None:
|
if category is not None:
|
||||||
try:
|
try:
|
||||||
|
|
||||||
category = PartCategory.objects.get(pk=category)
|
category = PartCategory.objects.get(pk=category)
|
||||||
|
|
||||||
fetch_parent = str2bool(params.get('fetch_parent', True))
|
fetch_parent = str2bool(params.get('fetch_parent', True))
|
||||||
@ -734,7 +734,7 @@ class PartList(generics.ListCreateAPIView):
|
|||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'initial_stock_quantity': [_('Must be a valid quantity')],
|
'initial_stock_quantity': [_('Must be a valid quantity')],
|
||||||
})
|
})
|
||||||
|
|
||||||
initial_stock_location = request.data.get('initial_stock_location', None)
|
initial_stock_location = request.data.get('initial_stock_location', None)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -850,7 +850,7 @@ class PartList(generics.ListCreateAPIView):
|
|||||||
id_values.append(val)
|
id_values.append(val)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
queryset = queryset.exclude(pk__in=id_values)
|
queryset = queryset.exclude(pk__in=id_values)
|
||||||
|
|
||||||
# Exclude part variant tree?
|
# Exclude part variant tree?
|
||||||
@ -1096,7 +1096,7 @@ class BomFilter(rest_filters.FilterSet):
|
|||||||
queryset = queryset.filter(pk__in=pks)
|
queryset = queryset.filter(pk__in=pks)
|
||||||
else:
|
else:
|
||||||
queryset = queryset.exclude(pk__in=pks)
|
queryset = queryset.exclude(pk__in=pks)
|
||||||
|
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
# Filters for linked 'part'
|
# Filters for linked 'part'
|
||||||
@ -1257,7 +1257,7 @@ class BomList(generics.ListCreateAPIView):
|
|||||||
queryset = self.annotate_pricing(queryset)
|
queryset = self.annotate_pricing(queryset)
|
||||||
|
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
def include_pricing(self):
|
def include_pricing(self):
|
||||||
"""
|
"""
|
||||||
Determine if pricing information should be included in the response
|
Determine if pricing information should be included in the response
|
||||||
@ -1291,7 +1291,7 @@ class BomList(generics.ListCreateAPIView):
|
|||||||
|
|
||||||
# Get default currency from settings
|
# Get default currency from settings
|
||||||
default_currency = InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY')
|
default_currency = InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY')
|
||||||
|
|
||||||
if price:
|
if price:
|
||||||
if currency and default_currency:
|
if currency and default_currency:
|
||||||
try:
|
try:
|
||||||
@ -1381,7 +1381,7 @@ class BomItemSubstituteList(generics.ListCreateAPIView):
|
|||||||
|
|
||||||
serializer_class = part_serializers.BomItemSubstituteSerializer
|
serializer_class = part_serializers.BomItemSubstituteSerializer
|
||||||
queryset = BomItemSubstitute.objects.all()
|
queryset = BomItemSubstitute.objects.all()
|
||||||
|
|
||||||
filter_backends = [
|
filter_backends = [
|
||||||
DjangoFilterBackend,
|
DjangoFilterBackend,
|
||||||
filters.SearchFilter,
|
filters.SearchFilter,
|
||||||
|
@ -172,7 +172,7 @@ def ExportBom(part, fmt='csv', cascade=False, max_levels=None, parameter_data=Fa
|
|||||||
|
|
||||||
# Filter manufacturer parts
|
# Filter manufacturer parts
|
||||||
manufacturer_parts = ManufacturerPart.objects.filter(part__pk=b_part.pk).prefetch_related('supplier_parts')
|
manufacturer_parts = ManufacturerPart.objects.filter(part__pk=b_part.pk).prefetch_related('supplier_parts')
|
||||||
|
|
||||||
for mp_idx, mp_part in enumerate(manufacturer_parts):
|
for mp_idx, mp_part in enumerate(manufacturer_parts):
|
||||||
|
|
||||||
# Extract the "name" field of the Manufacturer (Company)
|
# Extract the "name" field of the Manufacturer (Company)
|
||||||
@ -190,7 +190,7 @@ def ExportBom(part, fmt='csv', cascade=False, max_levels=None, parameter_data=Fa
|
|||||||
# Generate a column name for this manufacturer
|
# Generate a column name for this manufacturer
|
||||||
k_man = f'{_("Manufacturer")}_{mp_idx}'
|
k_man = f'{_("Manufacturer")}_{mp_idx}'
|
||||||
k_mpn = f'{_("MPN")}_{mp_idx}'
|
k_mpn = f'{_("MPN")}_{mp_idx}'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
manufacturer_cols[k_man].update({bom_idx: manufacturer_name})
|
manufacturer_cols[k_man].update({bom_idx: manufacturer_name})
|
||||||
manufacturer_cols[k_mpn].update({bom_idx: manufacturer_mpn})
|
manufacturer_cols[k_mpn].update({bom_idx: manufacturer_mpn})
|
||||||
@ -200,7 +200,7 @@ def ExportBom(part, fmt='csv', cascade=False, max_levels=None, parameter_data=Fa
|
|||||||
|
|
||||||
# We wish to include supplier data for this manufacturer part
|
# We wish to include supplier data for this manufacturer part
|
||||||
if supplier_data:
|
if supplier_data:
|
||||||
|
|
||||||
for sp_idx, sp_part in enumerate(mp_part.supplier_parts.all()):
|
for sp_idx, sp_part in enumerate(mp_part.supplier_parts.all()):
|
||||||
|
|
||||||
supplier_parts_used.add(sp_part)
|
supplier_parts_used.add(sp_part)
|
||||||
|
@ -2118,7 +2118,7 @@ class Part(MPTTModel):
|
|||||||
"""
|
"""
|
||||||
Returns True if the total stock for this part is less than the minimum stock level
|
Returns True if the total stock for this part is less than the minimum stock level
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return self.get_stock_count() < self.minimum_stock
|
return self.get_stock_count() < self.minimum_stock
|
||||||
|
|
||||||
|
|
||||||
@ -2155,7 +2155,7 @@ class PartSellPriceBreak(common.models.PriceBreak):
|
|||||||
"""
|
"""
|
||||||
Represents a price break for selling this part
|
Represents a price break for selling this part
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_api_url():
|
def get_api_url():
|
||||||
return reverse('api-part-sale-price-list')
|
return reverse('api-part-sale-price-list')
|
||||||
|
@ -446,9 +446,9 @@ class BomItemSerializer(InvenTreeModelSerializer):
|
|||||||
purchase_price_min = MoneyField(max_digits=10, decimal_places=6, read_only=True)
|
purchase_price_min = MoneyField(max_digits=10, decimal_places=6, read_only=True)
|
||||||
|
|
||||||
purchase_price_max = MoneyField(max_digits=10, decimal_places=6, read_only=True)
|
purchase_price_max = MoneyField(max_digits=10, decimal_places=6, read_only=True)
|
||||||
|
|
||||||
purchase_price_avg = serializers.SerializerMethodField()
|
purchase_price_avg = serializers.SerializerMethodField()
|
||||||
|
|
||||||
purchase_price_range = serializers.SerializerMethodField()
|
purchase_price_range = serializers.SerializerMethodField()
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@ -520,7 +520,7 @@ class BomItemSerializer(InvenTreeModelSerializer):
|
|||||||
|
|
||||||
def get_purchase_price_avg(self, obj):
|
def get_purchase_price_avg(self, obj):
|
||||||
""" Return purchase price average """
|
""" Return purchase price average """
|
||||||
|
|
||||||
try:
|
try:
|
||||||
purchase_price_avg = obj.purchase_price_avg
|
purchase_price_avg = obj.purchase_price_avg
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
|
@ -62,7 +62,7 @@ def notify_low_stock(part: part.models.Part):
|
|||||||
def notify_low_stock_if_required(part: part.models.Part):
|
def notify_low_stock_if_required(part: part.models.Part):
|
||||||
"""
|
"""
|
||||||
Check if the stock quantity has fallen below the minimum threshold of part.
|
Check if the stock quantity has fallen below the minimum threshold of part.
|
||||||
|
|
||||||
If true, notify the users who have subscribed to the part
|
If true, notify the users who have subscribed to the part
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -236,7 +236,7 @@ def settings_value(key, *args, **kwargs):
|
|||||||
|
|
||||||
if 'user' in kwargs:
|
if 'user' in kwargs:
|
||||||
return InvenTreeUserSetting.get_setting(key, user=kwargs['user'])
|
return InvenTreeUserSetting.get_setting(key, user=kwargs['user'])
|
||||||
|
|
||||||
return InvenTreeSetting.get_setting(key)
|
return InvenTreeSetting.get_setting(key)
|
||||||
|
|
||||||
|
|
||||||
@ -384,7 +384,7 @@ def keyvalue(dict, key):
|
|||||||
def call_method(obj, method_name, *args):
|
def call_method(obj, method_name, *args):
|
||||||
"""
|
"""
|
||||||
enables calling model methods / functions from templates with arguments
|
enables calling model methods / functions from templates with arguments
|
||||||
|
|
||||||
usage:
|
usage:
|
||||||
{% call_method model_object 'fnc_name' argument1 %}
|
{% call_method model_object 'fnc_name' argument1 %}
|
||||||
"""
|
"""
|
||||||
|
@ -542,7 +542,7 @@ class PartAPITest(InvenTreeAPITestCase):
|
|||||||
# Check that there is a new manufacturer part *and* a new supplier part
|
# Check that there is a new manufacturer part *and* a new supplier part
|
||||||
self.assertEqual(new_part.supplier_parts.count(), 1)
|
self.assertEqual(new_part.supplier_parts.count(), 1)
|
||||||
self.assertEqual(new_part.manufacturer_parts.count(), 1)
|
self.assertEqual(new_part.manufacturer_parts.count(), 1)
|
||||||
|
|
||||||
def test_strange_chars(self):
|
def test_strange_chars(self):
|
||||||
"""
|
"""
|
||||||
Test that non-standard ASCII chars are accepted
|
Test that non-standard ASCII chars are accepted
|
||||||
@ -911,7 +911,7 @@ class BomItemTest(InvenTreeAPITestCase):
|
|||||||
|
|
||||||
# How many BOM items currently exist in the database?
|
# How many BOM items currently exist in the database?
|
||||||
n = BomItem.objects.count()
|
n = BomItem.objects.count()
|
||||||
|
|
||||||
url = reverse('api-bom-list')
|
url = reverse('api-bom-list')
|
||||||
response = self.get(url, expected_code=200)
|
response = self.get(url, expected_code=200)
|
||||||
self.assertEqual(len(response.data), n)
|
self.assertEqual(len(response.data), n)
|
||||||
@ -962,7 +962,7 @@ class BomItemTest(InvenTreeAPITestCase):
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.post(url, data, expected_code=201)
|
self.post(url, data, expected_code=201)
|
||||||
|
|
||||||
# Now try to create a BomItem which references itself
|
# Now try to create a BomItem which references itself
|
||||||
data['part'] = 100
|
data['part'] = 100
|
||||||
data['sub_part'] = 100
|
data['sub_part'] = 100
|
||||||
@ -1003,7 +1003,7 @@ class BomItemTest(InvenTreeAPITestCase):
|
|||||||
|
|
||||||
# Now we will create some variant parts and stock
|
# Now we will create some variant parts and stock
|
||||||
for ii in range(5):
|
for ii in range(5):
|
||||||
|
|
||||||
# Create a variant part!
|
# Create a variant part!
|
||||||
variant = Part.objects.create(
|
variant = Part.objects.create(
|
||||||
name=f"Variant_{ii}",
|
name=f"Variant_{ii}",
|
||||||
|
@ -153,7 +153,7 @@ class BomItemTest(TestCase):
|
|||||||
subs = []
|
subs = []
|
||||||
|
|
||||||
for ii in range(5):
|
for ii in range(5):
|
||||||
|
|
||||||
# Create a new part
|
# Create a new part
|
||||||
sub_part = Part.objects.create(
|
sub_part = Part.objects.create(
|
||||||
name=f"Orphan {ii}",
|
name=f"Orphan {ii}",
|
||||||
@ -181,7 +181,7 @@ class BomItemTest(TestCase):
|
|||||||
|
|
||||||
# There should be now 5 substitute parts available
|
# There should be now 5 substitute parts available
|
||||||
self.assertEqual(bom_item.substitutes.count(), 5)
|
self.assertEqual(bom_item.substitutes.count(), 5)
|
||||||
|
|
||||||
# Try to create a substitute which points to the same sub-part (should fail)
|
# Try to create a substitute which points to the same sub-part (should fail)
|
||||||
with self.assertRaises(django_exceptions.ValidationError):
|
with self.assertRaises(django_exceptions.ValidationError):
|
||||||
BomItemSubstitute.objects.create(
|
BomItemSubstitute.objects.create(
|
||||||
|
@ -370,7 +370,7 @@ class PartSubscriptionTests(TestCase):
|
|||||||
|
|
||||||
# electronics / IC / MCU
|
# electronics / IC / MCU
|
||||||
self.category = PartCategory.objects.get(pk=4)
|
self.category = PartCategory.objects.get(pk=4)
|
||||||
|
|
||||||
self.part = Part.objects.create(
|
self.part = Part.objects.create(
|
||||||
category=self.category,
|
category=self.category,
|
||||||
name='STM32F103',
|
name='STM32F103',
|
||||||
@ -382,7 +382,7 @@ class PartSubscriptionTests(TestCase):
|
|||||||
"""
|
"""
|
||||||
Test basic subscription against a part
|
Test basic subscription against a part
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# First check that the user is *not* subscribed to the part
|
# First check that the user is *not* subscribed to the part
|
||||||
self.assertFalse(self.part.is_starred_by(self.user))
|
self.assertFalse(self.part.is_starred_by(self.user))
|
||||||
|
|
||||||
@ -450,7 +450,7 @@ class PartSubscriptionTests(TestCase):
|
|||||||
"""
|
"""
|
||||||
Check that a parent category can be subscribed to
|
Check that a parent category can be subscribed to
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Top-level "electronics" category
|
# Top-level "electronics" category
|
||||||
cat = PartCategory.objects.get(pk=1)
|
cat = PartCategory.objects.get(pk=1)
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ part_detail_urls = [
|
|||||||
url(r'^bom-export/?', views.BomExport.as_view(), name='bom-export'),
|
url(r'^bom-export/?', views.BomExport.as_view(), name='bom-export'),
|
||||||
url(r'^bom-download/?', views.BomDownload.as_view(), name='bom-download'),
|
url(r'^bom-download/?', views.BomDownload.as_view(), name='bom-download'),
|
||||||
url(r'^validate-bom/', views.BomValidate.as_view(), name='bom-validate'),
|
url(r'^validate-bom/', views.BomValidate.as_view(), name='bom-validate'),
|
||||||
|
|
||||||
url(r'^pricing/', views.PartPricing.as_view(), name='part-pricing'),
|
url(r'^pricing/', views.PartPricing.as_view(), name='part-pricing'),
|
||||||
|
|
||||||
url(r'^bom-upload/?', views.BomUpload.as_view(), name='upload-bom'),
|
url(r'^bom-upload/?', views.BomUpload.as_view(), name='upload-bom'),
|
||||||
|
@ -459,7 +459,7 @@ class PartDetail(InvenTreeRoleMixin, DetailView):
|
|||||||
part = self.get_object()
|
part = self.get_object()
|
||||||
|
|
||||||
ctx = part.get_context_data(self.request)
|
ctx = part.get_context_data(self.request)
|
||||||
|
|
||||||
context.update(**ctx)
|
context.update(**ctx)
|
||||||
|
|
||||||
# Pricing information
|
# Pricing information
|
||||||
@ -1056,7 +1056,7 @@ class BomUpload(InvenTreeRoleMixin, FileManagementFormView):
|
|||||||
matches = sorted(matches, key=lambda item: item['match'], reverse=True)
|
matches = sorted(matches, key=lambda item: item['match'], reverse=True)
|
||||||
|
|
||||||
part_options = [m['part'] for m in matches]
|
part_options = [m['part'] for m in matches]
|
||||||
|
|
||||||
# Supply list of part options for each row, sorted by how closely they match the part name
|
# Supply list of part options for each row, sorted by how closely they match the part name
|
||||||
row['item_options'] = part_options
|
row['item_options'] = part_options
|
||||||
|
|
||||||
@ -1520,11 +1520,11 @@ class CategoryDetail(InvenTreeRoleMixin, DetailView):
|
|||||||
|
|
||||||
# Prefetch parts parameters
|
# Prefetch parts parameters
|
||||||
parts_parameters = category.prefetch_parts_parameters(cascade=cascade)
|
parts_parameters = category.prefetch_parts_parameters(cascade=cascade)
|
||||||
|
|
||||||
# Get table headers (unique parameters names)
|
# Get table headers (unique parameters names)
|
||||||
context['headers'] = category.get_unique_parameters(cascade=cascade,
|
context['headers'] = category.get_unique_parameters(cascade=cascade,
|
||||||
prefetch=parts_parameters)
|
prefetch=parts_parameters)
|
||||||
|
|
||||||
# Insert part information
|
# Insert part information
|
||||||
context['headers'].insert(0, 'description')
|
context['headers'].insert(0, 'description')
|
||||||
context['headers'].insert(0, 'part')
|
context['headers'].insert(0, 'part')
|
||||||
|
@ -247,7 +247,7 @@ class ReportTemplateBase(ReportBase):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
template_string = Template(self.filename_pattern)
|
template_string = Template(self.filename_pattern)
|
||||||
|
|
||||||
ctx = self.context(request)
|
ctx = self.context(request)
|
||||||
|
|
||||||
context = Context(ctx)
|
context = Context(ctx)
|
||||||
|
@ -106,7 +106,7 @@ class ReportTest(InvenTreeAPITestCase):
|
|||||||
# Filter by "enabled" status
|
# Filter by "enabled" status
|
||||||
response = self.get(url, {'enabled': True})
|
response = self.get(url, {'enabled': True})
|
||||||
self.assertEqual(len(response.data), n)
|
self.assertEqual(len(response.data), n)
|
||||||
|
|
||||||
response = self.get(url, {'enabled': False})
|
response = self.get(url, {'enabled': False})
|
||||||
self.assertEqual(len(response.data), 0)
|
self.assertEqual(len(response.data), 0)
|
||||||
|
|
||||||
@ -118,7 +118,7 @@ class ReportTest(InvenTreeAPITestCase):
|
|||||||
# Filter by "enabled" status
|
# Filter by "enabled" status
|
||||||
response = self.get(url, {'enabled': True})
|
response = self.get(url, {'enabled': True})
|
||||||
self.assertEqual(len(response.data), 0)
|
self.assertEqual(len(response.data), 0)
|
||||||
|
|
||||||
response = self.get(url, {'enabled': False})
|
response = self.get(url, {'enabled': False})
|
||||||
self.assertEqual(len(response.data), n)
|
self.assertEqual(len(response.data), n)
|
||||||
|
|
||||||
@ -199,7 +199,7 @@ class BuildReportTest(ReportTest):
|
|||||||
build = Build.objects.first()
|
build = Build.objects.first()
|
||||||
|
|
||||||
response = self.get(url, {'build': build.pk})
|
response = self.get(url, {'build': build.pk})
|
||||||
|
|
||||||
self.assertEqual(type(response), StreamingHttpResponse)
|
self.assertEqual(type(response), StreamingHttpResponse)
|
||||||
|
|
||||||
headers = response.headers
|
headers = response.headers
|
||||||
|
@ -91,7 +91,7 @@ class StockDetail(generics.RetrieveUpdateDestroyAPIView):
|
|||||||
Instead of "deleting" the StockItem
|
Instead of "deleting" the StockItem
|
||||||
(which may take a long time)
|
(which may take a long time)
|
||||||
we instead schedule it for deletion at a later date.
|
we instead schedule it for deletion at a later date.
|
||||||
|
|
||||||
The background worker will delete these in the future
|
The background worker will delete these in the future
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -134,7 +134,7 @@ class StockAdjustView(generics.CreateAPIView):
|
|||||||
queryset = StockItem.objects.none()
|
queryset = StockItem.objects.none()
|
||||||
|
|
||||||
def get_serializer_context(self):
|
def get_serializer_context(self):
|
||||||
|
|
||||||
context = super().get_serializer_context()
|
context = super().get_serializer_context()
|
||||||
|
|
||||||
context['request'] = self.request
|
context['request'] = self.request
|
||||||
@ -348,7 +348,7 @@ class StockFilter(rest_filters.FilterSet):
|
|||||||
queryset = queryset.exclude(customer=None)
|
queryset = queryset.exclude(customer=None)
|
||||||
else:
|
else:
|
||||||
queryset = queryset.filter(customer=None)
|
queryset = queryset.filter(customer=None)
|
||||||
|
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
depleted = rest_filters.BooleanFilter(label='Depleted', method='filter_depleted')
|
depleted = rest_filters.BooleanFilter(label='Depleted', method='filter_depleted')
|
||||||
@ -437,7 +437,7 @@ class StockList(generics.ListCreateAPIView):
|
|||||||
})
|
})
|
||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
|
|
||||||
# Create an initial stock item
|
# Create an initial stock item
|
||||||
item = serializer.save()
|
item = serializer.save()
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@ class ConvertStockItemForm(HelperForm):
|
|||||||
class CreateStockItemForm(HelperForm):
|
class CreateStockItemForm(HelperForm):
|
||||||
"""
|
"""
|
||||||
Form for creating a new StockItem
|
Form for creating a new StockItem
|
||||||
|
|
||||||
TODO: Migrate this form to the modern API forms interface
|
TODO: Migrate this form to the modern API forms interface
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -143,7 +143,7 @@ class CreateStockItemForm(HelperForm):
|
|||||||
class SerializeStockForm(HelperForm):
|
class SerializeStockForm(HelperForm):
|
||||||
"""
|
"""
|
||||||
Form for serializing a StockItem.
|
Form for serializing a StockItem.
|
||||||
|
|
||||||
TODO: Migrate this form to the modern API forms interface
|
TODO: Migrate this form to the modern API forms interface
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -272,7 +272,7 @@ class StockItem(MPTTModel):
|
|||||||
add_note = kwargs.pop('add_note', True)
|
add_note = kwargs.pop('add_note', True)
|
||||||
|
|
||||||
notes = kwargs.pop('notes', '')
|
notes = kwargs.pop('notes', '')
|
||||||
|
|
||||||
if self.pk:
|
if self.pk:
|
||||||
# StockItem has already been saved
|
# StockItem has already been saved
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ class StockItemSerializerBrief(InvenTree.serializers.InvenTreeModelSerializer):
|
|||||||
|
|
||||||
location_name = serializers.CharField(source='location', read_only=True)
|
location_name = serializers.CharField(source='location', read_only=True)
|
||||||
part_name = serializers.CharField(source='part.full_name', read_only=True)
|
part_name = serializers.CharField(source='part.full_name', read_only=True)
|
||||||
|
|
||||||
quantity = InvenTreeDecimalField()
|
quantity = InvenTreeDecimalField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -615,7 +615,7 @@ class StockCountSerializer(StockAdjustmentSerializer):
|
|||||||
|
|
||||||
stock_item = item['pk']
|
stock_item = item['pk']
|
||||||
quantity = item['quantity']
|
quantity = item['quantity']
|
||||||
|
|
||||||
stock_item.stocktake(
|
stock_item.stocktake(
|
||||||
quantity,
|
quantity,
|
||||||
request.user,
|
request.user,
|
||||||
@ -654,7 +654,7 @@ class StockRemoveSerializer(StockAdjustmentSerializer):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
|
|
||||||
request = self.context['request']
|
request = self.context['request']
|
||||||
|
|
||||||
data = self.validated_data
|
data = self.validated_data
|
||||||
@ -707,7 +707,7 @@ class StockTransferSerializer(StockAdjustmentSerializer):
|
|||||||
request = self.context['request']
|
request = self.context['request']
|
||||||
|
|
||||||
data = self.validated_data
|
data = self.validated_data
|
||||||
|
|
||||||
items = data['items']
|
items = data['items']
|
||||||
notes = data.get('notes', '')
|
notes = data.get('notes', '')
|
||||||
location = data['location']
|
location = data['location']
|
||||||
|
@ -368,7 +368,7 @@ class StockItemTest(StockAPITestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.assertIn('Quantity is required', str(response.data))
|
self.assertIn('Quantity is required', str(response.data))
|
||||||
|
|
||||||
# POST with quantity and part and location
|
# POST with quantity and part and location
|
||||||
response = self.post(
|
response = self.post(
|
||||||
self.list_url,
|
self.list_url,
|
||||||
|
@ -11,7 +11,7 @@ location_urls = [
|
|||||||
url(r'^(?P<pk>\d+)/', include([
|
url(r'^(?P<pk>\d+)/', include([
|
||||||
url(r'^delete/?', views.StockLocationDelete.as_view(), name='stock-location-delete'),
|
url(r'^delete/?', views.StockLocationDelete.as_view(), name='stock-location-delete'),
|
||||||
url(r'^qr_code/?', views.StockLocationQRCode.as_view(), name='stock-location-qr'),
|
url(r'^qr_code/?', views.StockLocationQRCode.as_view(), name='stock-location-qr'),
|
||||||
|
|
||||||
# Anything else
|
# Anything else
|
||||||
url('^.*$', views.StockLocationDetail.as_view(), name='stock-location-detail'),
|
url('^.*$', views.StockLocationDetail.as_view(), name='stock-location-detail'),
|
||||||
])),
|
])),
|
||||||
|
@ -543,7 +543,7 @@ class StockItemInstall(AjaxUpdateView):
|
|||||||
- Items must be in BOM of stock item
|
- Items must be in BOM of stock item
|
||||||
- Items must be serialized
|
- Items must be serialized
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Filter items in stock
|
# Filter items in stock
|
||||||
items = StockItem.objects.filter(StockItem.IN_STOCK_FILTER)
|
items = StockItem.objects.filter(StockItem.IN_STOCK_FILTER)
|
||||||
|
|
||||||
@ -900,7 +900,7 @@ class StockItemEdit(AjaxUpdateView):
|
|||||||
item.save(user=self.request.user)
|
item.save(user=self.request.user)
|
||||||
|
|
||||||
return item
|
return item
|
||||||
|
|
||||||
|
|
||||||
class StockItemConvert(AjaxUpdateView):
|
class StockItemConvert(AjaxUpdateView):
|
||||||
"""
|
"""
|
||||||
|
@ -105,7 +105,7 @@ for filename in pathlib.Path(js_dynamic_dir).rglob('*.js'):
|
|||||||
data = js_file.readlines()
|
data = js_file.readlines()
|
||||||
|
|
||||||
pattern = r'{% trans '
|
pattern = r'{% trans '
|
||||||
|
|
||||||
err_count = 0
|
err_count = 0
|
||||||
|
|
||||||
for idx, line in enumerate(data):
|
for idx, line in enumerate(data):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user