mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-16 12:05:53 +00:00
Display part units column in part table
- Also allow ordering by part units - Allow filtering to show parts which have defined units
This commit is contained in:
@ -804,19 +804,31 @@ class PartFilter(rest_filters.FilterSet):
|
|||||||
Uses the django_filters extension framework
|
Uses the django_filters extension framework
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Metaclass options for this filter set"""
|
||||||
|
model = Part
|
||||||
|
fields = []
|
||||||
|
|
||||||
|
has_units = rest_filters.BooleanFilter(label='Has units', method='filter_has_units')
|
||||||
|
|
||||||
|
def filter_has_units(self, queryset, name, value):
|
||||||
|
"""Filter by whether the Part has units or not"""
|
||||||
|
|
||||||
|
if str2bool(value):
|
||||||
|
return queryset.exclude(units='')
|
||||||
|
else:
|
||||||
|
return queryset.filter(units='')
|
||||||
|
|
||||||
# Filter by parts which have (or not) an IPN value
|
# Filter by parts which have (or not) an IPN value
|
||||||
has_ipn = rest_filters.BooleanFilter(label='Has IPN', method='filter_has_ipn')
|
has_ipn = rest_filters.BooleanFilter(label='Has IPN', method='filter_has_ipn')
|
||||||
|
|
||||||
def filter_has_ipn(self, queryset, name, value):
|
def filter_has_ipn(self, queryset, name, value):
|
||||||
"""Filter by whether the Part has an IPN (internal part number) or not"""
|
"""Filter by whether the Part has an IPN (internal part number) or not"""
|
||||||
value = str2bool(value)
|
|
||||||
|
|
||||||
if value:
|
if str2bool(value):
|
||||||
queryset = queryset.exclude(IPN='')
|
return queryset.exclude(IPN='')
|
||||||
else:
|
else:
|
||||||
queryset = queryset.filter(IPN='')
|
return queryset.filter(IPN='')
|
||||||
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
# Regex filter for name
|
# Regex filter for name
|
||||||
name_regex = rest_filters.CharFilter(label='Filter by name (regex)', field_name='name', lookup_expr='iregex')
|
name_regex = rest_filters.CharFilter(label='Filter by name (regex)', field_name='name', lookup_expr='iregex')
|
||||||
@ -836,46 +848,36 @@ class PartFilter(rest_filters.FilterSet):
|
|||||||
|
|
||||||
def filter_low_stock(self, queryset, name, value):
|
def filter_low_stock(self, queryset, name, value):
|
||||||
"""Filter by "low stock" status."""
|
"""Filter by "low stock" status."""
|
||||||
value = str2bool(value)
|
|
||||||
|
|
||||||
if value:
|
if str2bool(value):
|
||||||
# Ignore any parts which do not have a specified 'minimum_stock' level
|
# Ignore any parts which do not have a specified 'minimum_stock' level
|
||||||
queryset = queryset.exclude(minimum_stock=0)
|
|
||||||
# Filter items which have an 'in_stock' level lower than 'minimum_stock'
|
# Filter items which have an 'in_stock' level lower than 'minimum_stock'
|
||||||
queryset = queryset.filter(Q(in_stock__lt=F('minimum_stock')))
|
return queryset.exclude(minimum_stock=0).filter(Q(in_stock__lt=F('minimum_stock')))
|
||||||
else:
|
else:
|
||||||
# Filter items which have an 'in_stock' level higher than 'minimum_stock'
|
# Filter items which have an 'in_stock' level higher than 'minimum_stock'
|
||||||
queryset = queryset.filter(Q(in_stock__gte=F('minimum_stock')))
|
return queryset.filter(Q(in_stock__gte=F('minimum_stock')))
|
||||||
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
# has_stock filter
|
# has_stock filter
|
||||||
has_stock = rest_filters.BooleanFilter(label='Has stock', method='filter_has_stock')
|
has_stock = rest_filters.BooleanFilter(label='Has stock', method='filter_has_stock')
|
||||||
|
|
||||||
def filter_has_stock(self, queryset, name, value):
|
def filter_has_stock(self, queryset, name, value):
|
||||||
"""Filter by whether the Part has any stock"""
|
"""Filter by whether the Part has any stock"""
|
||||||
value = str2bool(value)
|
|
||||||
|
|
||||||
if value:
|
if str2bool(value):
|
||||||
queryset = queryset.filter(Q(in_stock__gt=0))
|
return queryset.filter(Q(in_stock__gt=0))
|
||||||
else:
|
else:
|
||||||
queryset = queryset.filter(Q(in_stock__lte=0))
|
return queryset.filter(Q(in_stock__lte=0))
|
||||||
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
# unallocated_stock filter
|
# unallocated_stock filter
|
||||||
unallocated_stock = rest_filters.BooleanFilter(label='Unallocated stock', method='filter_unallocated_stock')
|
unallocated_stock = rest_filters.BooleanFilter(label='Unallocated stock', method='filter_unallocated_stock')
|
||||||
|
|
||||||
def filter_unallocated_stock(self, queryset, name, value):
|
def filter_unallocated_stock(self, queryset, name, value):
|
||||||
"""Filter by whether the Part has unallocated stock"""
|
"""Filter by whether the Part has unallocated stock"""
|
||||||
value = str2bool(value)
|
|
||||||
|
|
||||||
if value:
|
if str2bool(value):
|
||||||
queryset = queryset.filter(Q(unallocated_stock__gt=0))
|
return queryset.filter(Q(unallocated_stock__gt=0))
|
||||||
else:
|
else:
|
||||||
queryset = queryset.filter(Q(unallocated_stock__lte=0))
|
return queryset.filter(Q(unallocated_stock__lte=0))
|
||||||
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
convert_from = rest_filters.ModelChoiceFilter(label="Can convert from", queryset=Part.objects.all(), method='filter_convert_from')
|
convert_from = rest_filters.ModelChoiceFilter(label="Can convert from", queryset=Part.objects.all(), method='filter_convert_from')
|
||||||
|
|
||||||
@ -894,9 +896,7 @@ class PartFilter(rest_filters.FilterSet):
|
|||||||
|
|
||||||
children = part.get_descendants(include_self=True)
|
children = part.get_descendants(include_self=True)
|
||||||
|
|
||||||
queryset = queryset.exclude(id__in=children)
|
return queryset.exclude(id__in=children)
|
||||||
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
ancestor = rest_filters.ModelChoiceFilter(label='Ancestor', queryset=Part.objects.all(), method='filter_ancestor')
|
ancestor = rest_filters.ModelChoiceFilter(label='Ancestor', queryset=Part.objects.all(), method='filter_ancestor')
|
||||||
|
|
||||||
@ -904,17 +904,14 @@ class PartFilter(rest_filters.FilterSet):
|
|||||||
"""Limit queryset to descendants of the specified ancestor part"""
|
"""Limit queryset to descendants of the specified ancestor part"""
|
||||||
|
|
||||||
descendants = part.get_descendants(include_self=False)
|
descendants = part.get_descendants(include_self=False)
|
||||||
queryset = queryset.filter(id__in=descendants)
|
return queryset.filter(id__in=descendants)
|
||||||
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
variant_of = rest_filters.ModelChoiceFilter(label='Variant Of', queryset=Part.objects.all(), method='filter_variant_of')
|
variant_of = rest_filters.ModelChoiceFilter(label='Variant Of', queryset=Part.objects.all(), method='filter_variant_of')
|
||||||
|
|
||||||
def filter_variant_of(self, queryset, name, part):
|
def filter_variant_of(self, queryset, name, part):
|
||||||
"""Limit queryset to direct children (variants) of the specified part"""
|
"""Limit queryset to direct children (variants) of the specified part"""
|
||||||
|
|
||||||
queryset = queryset.filter(id__in=part.get_children())
|
return queryset.filter(id__in=part.get_children())
|
||||||
return queryset
|
|
||||||
|
|
||||||
in_bom_for = rest_filters.ModelChoiceFilter(label='In BOM Of', queryset=Part.objects.all(), method='filter_in_bom')
|
in_bom_for = rest_filters.ModelChoiceFilter(label='In BOM Of', queryset=Part.objects.all(), method='filter_in_bom')
|
||||||
|
|
||||||
@ -922,39 +919,30 @@ class PartFilter(rest_filters.FilterSet):
|
|||||||
"""Limit queryset to parts in the BOM for the specified part"""
|
"""Limit queryset to parts in the BOM for the specified part"""
|
||||||
|
|
||||||
bom_parts = part.get_parts_in_bom()
|
bom_parts = part.get_parts_in_bom()
|
||||||
queryset = queryset.filter(id__in=[p.pk for p in bom_parts])
|
return queryset.filter(id__in=[p.pk for p in bom_parts])
|
||||||
return queryset
|
|
||||||
|
|
||||||
has_pricing = rest_filters.BooleanFilter(label="Has Pricing", method="filter_has_pricing")
|
has_pricing = rest_filters.BooleanFilter(label="Has Pricing", method="filter_has_pricing")
|
||||||
|
|
||||||
def filter_has_pricing(self, queryset, name, value):
|
def filter_has_pricing(self, queryset, name, value):
|
||||||
"""Filter the queryset based on whether pricing information is available for the sub_part"""
|
"""Filter the queryset based on whether pricing information is available for the sub_part"""
|
||||||
|
|
||||||
value = str2bool(value)
|
|
||||||
|
|
||||||
q_a = Q(pricing_data=None)
|
q_a = Q(pricing_data=None)
|
||||||
q_b = Q(pricing_data__overall_min=None, pricing_data__overall_max=None)
|
q_b = Q(pricing_data__overall_min=None, pricing_data__overall_max=None)
|
||||||
|
|
||||||
if value:
|
if str2bool(value):
|
||||||
queryset = queryset.exclude(q_a | q_b)
|
return queryset.exclude(q_a | q_b)
|
||||||
else:
|
else:
|
||||||
queryset = queryset.filter(q_a | q_b)
|
return queryset.filter(q_a | q_b)
|
||||||
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
stocktake = rest_filters.BooleanFilter(label="Has stocktake", method='filter_has_stocktake')
|
stocktake = rest_filters.BooleanFilter(label="Has stocktake", method='filter_has_stocktake')
|
||||||
|
|
||||||
def filter_has_stocktake(self, queryset, name, value):
|
def filter_has_stocktake(self, queryset, name, value):
|
||||||
"""Filter the queryset based on whether stocktake data is available"""
|
"""Filter the queryset based on whether stocktake data is available"""
|
||||||
|
|
||||||
value = str2bool(value)
|
if str2bool(value):
|
||||||
|
return queryset.exclude(last_stocktake=None)
|
||||||
if (value):
|
|
||||||
queryset = queryset.exclude(last_stocktake=None)
|
|
||||||
else:
|
else:
|
||||||
queryset = queryset.filter(last_stocktake=None)
|
return queryset.filter(last_stocktake=None)
|
||||||
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
is_template = rest_filters.BooleanFilter()
|
is_template = rest_filters.BooleanFilter()
|
||||||
|
|
||||||
@ -1259,6 +1247,7 @@ class PartList(PartMixin, APIDownloadMixin, ListCreateAPI):
|
|||||||
'unallocated_stock',
|
'unallocated_stock',
|
||||||
'category',
|
'category',
|
||||||
'last_stocktake',
|
'last_stocktake',
|
||||||
|
'units',
|
||||||
]
|
]
|
||||||
|
|
||||||
# Default ordering
|
# Default ordering
|
||||||
|
@ -20,21 +20,11 @@ from taggit.serializers import TagListSerializerField
|
|||||||
import common.models
|
import common.models
|
||||||
import company.models
|
import company.models
|
||||||
import InvenTree.helpers
|
import InvenTree.helpers
|
||||||
|
import InvenTree.serializers
|
||||||
import InvenTree.status
|
import InvenTree.status
|
||||||
import part.filters
|
import part.filters
|
||||||
import part.tasks
|
import part.tasks
|
||||||
import stock.models
|
import stock.models
|
||||||
from InvenTree.serializers import (DataFileExtractSerializer,
|
|
||||||
DataFileUploadSerializer,
|
|
||||||
InvenTreeAttachmentSerializer,
|
|
||||||
InvenTreeAttachmentSerializerField,
|
|
||||||
InvenTreeCurrencySerializer,
|
|
||||||
InvenTreeDecimalField,
|
|
||||||
InvenTreeImageSerializerField,
|
|
||||||
InvenTreeModelSerializer,
|
|
||||||
InvenTreeMoneySerializer,
|
|
||||||
InvenTreeTagModelSerializer,
|
|
||||||
RemoteImageMixin, UserSerializer)
|
|
||||||
from InvenTree.status_codes import BuildStatus
|
from InvenTree.status_codes import BuildStatus
|
||||||
from InvenTree.tasks import offload_task
|
from InvenTree.tasks import offload_task
|
||||||
|
|
||||||
@ -48,7 +38,7 @@ from .models import (BomItem, BomItemSubstitute, Part, PartAttachment,
|
|||||||
logger = logging.getLogger("inventree")
|
logger = logging.getLogger("inventree")
|
||||||
|
|
||||||
|
|
||||||
class CategorySerializer(InvenTreeModelSerializer):
|
class CategorySerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||||
"""Serializer for PartCategory."""
|
"""Serializer for PartCategory."""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -94,7 +84,7 @@ class CategorySerializer(InvenTreeModelSerializer):
|
|||||||
starred = serializers.SerializerMethodField()
|
starred = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
|
||||||
class CategoryTree(InvenTreeModelSerializer):
|
class CategoryTree(InvenTree.serializers.InvenTreeModelSerializer):
|
||||||
"""Serializer for PartCategory tree."""
|
"""Serializer for PartCategory tree."""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -108,19 +98,19 @@ class CategoryTree(InvenTreeModelSerializer):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class PartAttachmentSerializer(InvenTreeAttachmentSerializer):
|
class PartAttachmentSerializer(InvenTree.serializers.InvenTreeAttachmentSerializer):
|
||||||
"""Serializer for the PartAttachment class."""
|
"""Serializer for the PartAttachment class."""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Metaclass defining serializer fields"""
|
"""Metaclass defining serializer fields"""
|
||||||
model = PartAttachment
|
model = PartAttachment
|
||||||
|
|
||||||
fields = InvenTreeAttachmentSerializer.attachment_fields([
|
fields = InvenTree.serializers.InvenTreeAttachmentSerializer.attachment_fields([
|
||||||
'part',
|
'part',
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
class PartTestTemplateSerializer(InvenTreeModelSerializer):
|
class PartTestTemplateSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||||
"""Serializer for the PartTestTemplate class."""
|
"""Serializer for the PartTestTemplate class."""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -141,7 +131,7 @@ class PartTestTemplateSerializer(InvenTreeModelSerializer):
|
|||||||
key = serializers.CharField(read_only=True)
|
key = serializers.CharField(read_only=True)
|
||||||
|
|
||||||
|
|
||||||
class PartSalePriceSerializer(InvenTreeModelSerializer):
|
class PartSalePriceSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||||
"""Serializer for sale prices for Part model."""
|
"""Serializer for sale prices for Part model."""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -155,14 +145,14 @@ class PartSalePriceSerializer(InvenTreeModelSerializer):
|
|||||||
'price_currency',
|
'price_currency',
|
||||||
]
|
]
|
||||||
|
|
||||||
quantity = InvenTreeDecimalField()
|
quantity = InvenTree.serializers.InvenTreeDecimalField()
|
||||||
|
|
||||||
price = InvenTreeMoneySerializer(allow_null=True)
|
price = InvenTree.serializers.InvenTreeMoneySerializer(allow_null=True)
|
||||||
|
|
||||||
price_currency = InvenTreeCurrencySerializer(help_text=_('Purchase currency of this stock item'))
|
price_currency = InvenTree.serializers.InvenTreeCurrencySerializer(help_text=_('Purchase currency of this stock item'))
|
||||||
|
|
||||||
|
|
||||||
class PartInternalPriceSerializer(InvenTreeModelSerializer):
|
class PartInternalPriceSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||||
"""Serializer for internal prices for Part model."""
|
"""Serializer for internal prices for Part model."""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -176,13 +166,13 @@ class PartInternalPriceSerializer(InvenTreeModelSerializer):
|
|||||||
'price_currency',
|
'price_currency',
|
||||||
]
|
]
|
||||||
|
|
||||||
quantity = InvenTreeDecimalField()
|
quantity = InvenTree.serializers.InvenTreeDecimalField()
|
||||||
|
|
||||||
price = InvenTreeMoneySerializer(
|
price = InvenTree.serializers.InvenTreeMoneySerializer(
|
||||||
allow_null=True
|
allow_null=True
|
||||||
)
|
)
|
||||||
|
|
||||||
price_currency = InvenTreeCurrencySerializer(help_text=_('Purchase currency of this stock item'))
|
price_currency = InvenTree.serializers.InvenTreeCurrencySerializer(help_text=_('Purchase currency of this stock item'))
|
||||||
|
|
||||||
|
|
||||||
class PartThumbSerializer(serializers.Serializer):
|
class PartThumbSerializer(serializers.Serializer):
|
||||||
@ -195,7 +185,7 @@ class PartThumbSerializer(serializers.Serializer):
|
|||||||
count = serializers.IntegerField(read_only=True)
|
count = serializers.IntegerField(read_only=True)
|
||||||
|
|
||||||
|
|
||||||
class PartThumbSerializerUpdate(InvenTreeModelSerializer):
|
class PartThumbSerializerUpdate(InvenTree.serializers.InvenTreeModelSerializer):
|
||||||
"""Serializer for updating Part thumbnail."""
|
"""Serializer for updating Part thumbnail."""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -212,10 +202,10 @@ class PartThumbSerializerUpdate(InvenTreeModelSerializer):
|
|||||||
raise serializers.ValidationError("File is not an image")
|
raise serializers.ValidationError("File is not an image")
|
||||||
return value
|
return value
|
||||||
|
|
||||||
image = InvenTreeAttachmentSerializerField(required=True)
|
image = InvenTree.serializers.InvenTreeAttachmentSerializerField(required=True)
|
||||||
|
|
||||||
|
|
||||||
class PartParameterTemplateSerializer(InvenTreeModelSerializer):
|
class PartParameterTemplateSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||||
"""JSON serializer for the PartParameterTemplate model."""
|
"""JSON serializer for the PartParameterTemplate model."""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -229,7 +219,7 @@ class PartParameterTemplateSerializer(InvenTreeModelSerializer):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class PartParameterSerializer(InvenTreeModelSerializer):
|
class PartParameterSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||||
"""JSON serializers for the PartParameter model."""
|
"""JSON serializers for the PartParameter model."""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -260,7 +250,7 @@ class PartParameterSerializer(InvenTreeModelSerializer):
|
|||||||
template_detail = PartParameterTemplateSerializer(source='template', many=False, read_only=True)
|
template_detail = PartParameterTemplateSerializer(source='template', many=False, read_only=True)
|
||||||
|
|
||||||
|
|
||||||
class PartBriefSerializer(InvenTreeModelSerializer):
|
class PartBriefSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||||
"""Serializer for Part (brief detail)"""
|
"""Serializer for Part (brief detail)"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -295,8 +285,8 @@ class PartBriefSerializer(InvenTreeModelSerializer):
|
|||||||
thumbnail = serializers.CharField(source='get_thumbnail_url', read_only=True)
|
thumbnail = serializers.CharField(source='get_thumbnail_url', read_only=True)
|
||||||
|
|
||||||
# Pricing fields
|
# Pricing fields
|
||||||
pricing_min = InvenTreeMoneySerializer(source='pricing_data.overall_min', allow_null=True, read_only=True)
|
pricing_min = InvenTree.serializers.InvenTreeMoneySerializer(source='pricing_data.overall_min', allow_null=True, read_only=True)
|
||||||
pricing_max = InvenTreeMoneySerializer(source='pricing_data.overall_max', allow_null=True, read_only=True)
|
pricing_max = InvenTree.serializers.InvenTreeMoneySerializer(source='pricing_data.overall_max', allow_null=True, read_only=True)
|
||||||
|
|
||||||
|
|
||||||
class DuplicatePartSerializer(serializers.Serializer):
|
class DuplicatePartSerializer(serializers.Serializer):
|
||||||
@ -406,7 +396,7 @@ class InitialSupplierSerializer(serializers.Serializer):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
class PartSerializer(RemoteImageMixin, InvenTreeTagModelSerializer):
|
class PartSerializer(InvenTree.serializers.RemoteImageMixin, InvenTree.serializers.InvenTreeTagModelSerializer):
|
||||||
"""Serializer for complete detail information of a part.
|
"""Serializer for complete detail information of a part.
|
||||||
|
|
||||||
Used when displaying all details of a single component.
|
Used when displaying all details of a single component.
|
||||||
@ -607,7 +597,7 @@ class PartSerializer(RemoteImageMixin, InvenTreeTagModelSerializer):
|
|||||||
stock_item_count = serializers.IntegerField(read_only=True)
|
stock_item_count = serializers.IntegerField(read_only=True)
|
||||||
suppliers = serializers.IntegerField(read_only=True)
|
suppliers = serializers.IntegerField(read_only=True)
|
||||||
|
|
||||||
image = InvenTreeImageSerializerField(required=False, allow_null=True)
|
image = InvenTree.serializers.InvenTreeImageSerializerField(required=False, allow_null=True)
|
||||||
thumbnail = serializers.CharField(source='get_thumbnail_url', read_only=True)
|
thumbnail = serializers.CharField(source='get_thumbnail_url', read_only=True)
|
||||||
starred = serializers.SerializerMethodField()
|
starred = serializers.SerializerMethodField()
|
||||||
|
|
||||||
@ -615,8 +605,8 @@ class PartSerializer(RemoteImageMixin, InvenTreeTagModelSerializer):
|
|||||||
category = serializers.PrimaryKeyRelatedField(queryset=PartCategory.objects.all())
|
category = serializers.PrimaryKeyRelatedField(queryset=PartCategory.objects.all())
|
||||||
|
|
||||||
# Pricing fields
|
# Pricing fields
|
||||||
pricing_min = InvenTreeMoneySerializer(source='pricing_data.overall_min', allow_null=True, read_only=True)
|
pricing_min = InvenTree.serializers.InvenTreeMoneySerializer(source='pricing_data.overall_min', allow_null=True, read_only=True)
|
||||||
pricing_max = InvenTreeMoneySerializer(source='pricing_data.overall_max', allow_null=True, read_only=True)
|
pricing_max = InvenTree.serializers.InvenTreeMoneySerializer(source='pricing_data.overall_max', allow_null=True, read_only=True)
|
||||||
|
|
||||||
parameters = PartParameterSerializer(
|
parameters = PartParameterSerializer(
|
||||||
many=True,
|
many=True,
|
||||||
@ -771,7 +761,7 @@ class PartSerializer(RemoteImageMixin, InvenTreeTagModelSerializer):
|
|||||||
return self.instance
|
return self.instance
|
||||||
|
|
||||||
|
|
||||||
class PartStocktakeSerializer(InvenTreeModelSerializer):
|
class PartStocktakeSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||||
"""Serializer for the PartStocktake model"""
|
"""Serializer for the PartStocktake model"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -800,13 +790,13 @@ class PartStocktakeSerializer(InvenTreeModelSerializer):
|
|||||||
|
|
||||||
quantity = serializers.FloatField()
|
quantity = serializers.FloatField()
|
||||||
|
|
||||||
user_detail = UserSerializer(source='user', read_only=True, many=False)
|
user_detail = InvenTree.serializers.UserSerializer(source='user', read_only=True, many=False)
|
||||||
|
|
||||||
cost_min = InvenTreeMoneySerializer(allow_null=True)
|
cost_min = InvenTree.serializers.InvenTreeMoneySerializer(allow_null=True)
|
||||||
cost_min_currency = InvenTreeCurrencySerializer()
|
cost_min_currency = InvenTree.serializers.InvenTreeCurrencySerializer()
|
||||||
|
|
||||||
cost_max = InvenTreeMoneySerializer(allow_null=True)
|
cost_max = InvenTree.serializers.InvenTreeMoneySerializer(allow_null=True)
|
||||||
cost_max_currency = InvenTreeCurrencySerializer()
|
cost_max_currency = InvenTree.serializers.InvenTreeCurrencySerializer()
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
"""Called when this serializer is saved"""
|
"""Called when this serializer is saved"""
|
||||||
@ -820,7 +810,7 @@ class PartStocktakeSerializer(InvenTreeModelSerializer):
|
|||||||
super().save()
|
super().save()
|
||||||
|
|
||||||
|
|
||||||
class PartStocktakeReportSerializer(InvenTreeModelSerializer):
|
class PartStocktakeReportSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||||
"""Serializer for stocktake report class"""
|
"""Serializer for stocktake report class"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -836,9 +826,9 @@ class PartStocktakeReportSerializer(InvenTreeModelSerializer):
|
|||||||
'user_detail',
|
'user_detail',
|
||||||
]
|
]
|
||||||
|
|
||||||
user_detail = UserSerializer(source='user', read_only=True, many=False)
|
user_detail = InvenTree.serializers.UserSerializer(source='user', read_only=True, many=False)
|
||||||
|
|
||||||
report = InvenTreeAttachmentSerializerField(read_only=True)
|
report = InvenTree.serializers.InvenTreeAttachmentSerializerField(read_only=True)
|
||||||
|
|
||||||
|
|
||||||
class PartStocktakeReportGenerateSerializer(serializers.Serializer):
|
class PartStocktakeReportGenerateSerializer(serializers.Serializer):
|
||||||
@ -906,7 +896,7 @@ class PartStocktakeReportGenerateSerializer(serializers.Serializer):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class PartPricingSerializer(InvenTreeModelSerializer):
|
class PartPricingSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||||
"""Serializer for Part pricing information"""
|
"""Serializer for Part pricing information"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -942,29 +932,29 @@ class PartPricingSerializer(InvenTreeModelSerializer):
|
|||||||
scheduled_for_update = serializers.BooleanField(read_only=True)
|
scheduled_for_update = serializers.BooleanField(read_only=True)
|
||||||
|
|
||||||
# Custom serializers
|
# Custom serializers
|
||||||
bom_cost_min = InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
bom_cost_min = InvenTree.serializers.InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
||||||
bom_cost_max = InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
bom_cost_max = InvenTree.serializers.InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
||||||
|
|
||||||
purchase_cost_min = InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
purchase_cost_min = InvenTree.serializers.InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
||||||
purchase_cost_max = InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
purchase_cost_max = InvenTree.serializers.InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
||||||
|
|
||||||
internal_cost_min = InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
internal_cost_min = InvenTree.serializers.InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
||||||
internal_cost_max = InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
internal_cost_max = InvenTree.serializers.InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
||||||
|
|
||||||
supplier_price_min = InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
supplier_price_min = InvenTree.serializers.InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
||||||
supplier_price_max = InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
supplier_price_max = InvenTree.serializers.InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
||||||
|
|
||||||
variant_cost_min = InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
variant_cost_min = InvenTree.serializers.InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
||||||
variant_cost_max = InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
variant_cost_max = InvenTree.serializers.InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
||||||
|
|
||||||
overall_min = InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
overall_min = InvenTree.serializers.InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
||||||
overall_max = InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
overall_max = InvenTree.serializers.InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
||||||
|
|
||||||
sale_price_min = InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
sale_price_min = InvenTree.serializers.InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
||||||
sale_price_max = InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
sale_price_max = InvenTree.serializers.InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
||||||
|
|
||||||
sale_history_min = InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
sale_history_min = InvenTree.serializers.InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
||||||
sale_history_max = InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
sale_history_max = InvenTree.serializers.InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
||||||
|
|
||||||
update = serializers.BooleanField(
|
update = serializers.BooleanField(
|
||||||
write_only=True,
|
write_only=True,
|
||||||
@ -984,7 +974,7 @@ class PartPricingSerializer(InvenTreeModelSerializer):
|
|||||||
pricing.update_pricing()
|
pricing.update_pricing()
|
||||||
|
|
||||||
|
|
||||||
class PartRelationSerializer(InvenTreeModelSerializer):
|
class PartRelationSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||||
"""Serializer for a PartRelated model."""
|
"""Serializer for a PartRelated model."""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -1002,7 +992,7 @@ class PartRelationSerializer(InvenTreeModelSerializer):
|
|||||||
part_2_detail = PartSerializer(source='part_2', read_only=True, many=False)
|
part_2_detail = PartSerializer(source='part_2', read_only=True, many=False)
|
||||||
|
|
||||||
|
|
||||||
class PartStarSerializer(InvenTreeModelSerializer):
|
class PartStarSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||||
"""Serializer for a PartStar object."""
|
"""Serializer for a PartStar object."""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -1020,7 +1010,7 @@ class PartStarSerializer(InvenTreeModelSerializer):
|
|||||||
username = serializers.CharField(source='user.username', read_only=True)
|
username = serializers.CharField(source='user.username', read_only=True)
|
||||||
|
|
||||||
|
|
||||||
class BomItemSubstituteSerializer(InvenTreeModelSerializer):
|
class BomItemSubstituteSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||||
"""Serializer for the BomItemSubstitute class."""
|
"""Serializer for the BomItemSubstitute class."""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -1036,7 +1026,7 @@ class BomItemSubstituteSerializer(InvenTreeModelSerializer):
|
|||||||
part_detail = PartBriefSerializer(source='part', read_only=True, many=False)
|
part_detail = PartBriefSerializer(source='part', read_only=True, many=False)
|
||||||
|
|
||||||
|
|
||||||
class BomItemSerializer(InvenTreeModelSerializer):
|
class BomItemSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||||
"""Serializer for BomItem object."""
|
"""Serializer for BomItem object."""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -1087,7 +1077,7 @@ class BomItemSerializer(InvenTreeModelSerializer):
|
|||||||
if sub_part_detail is not True:
|
if sub_part_detail is not True:
|
||||||
self.fields.pop('sub_part_detail')
|
self.fields.pop('sub_part_detail')
|
||||||
|
|
||||||
quantity = InvenTreeDecimalField(required=True)
|
quantity = InvenTree.serializers.InvenTreeDecimalField(required=True)
|
||||||
|
|
||||||
def validate_quantity(self, quantity):
|
def validate_quantity(self, quantity):
|
||||||
"""Perform validation for the BomItem quantity field"""
|
"""Perform validation for the BomItem quantity field"""
|
||||||
@ -1109,8 +1099,8 @@ class BomItemSerializer(InvenTreeModelSerializer):
|
|||||||
on_order = serializers.FloatField(read_only=True)
|
on_order = serializers.FloatField(read_only=True)
|
||||||
|
|
||||||
# Cached pricing fields
|
# Cached pricing fields
|
||||||
pricing_min = InvenTreeMoneySerializer(source='sub_part.pricing.overall_min', allow_null=True, read_only=True)
|
pricing_min = InvenTree.serializers.InvenTreeMoneySerializer(source='sub_part.pricing.overall_min', allow_null=True, read_only=True)
|
||||||
pricing_max = InvenTreeMoneySerializer(source='sub_part.pricing.overall_max', allow_null=True, read_only=True)
|
pricing_max = InvenTree.serializers.InvenTreeMoneySerializer(source='sub_part.pricing.overall_max', allow_null=True, read_only=True)
|
||||||
|
|
||||||
# Annotated fields for available stock
|
# Annotated fields for available stock
|
||||||
available_stock = serializers.FloatField(read_only=True)
|
available_stock = serializers.FloatField(read_only=True)
|
||||||
@ -1212,7 +1202,7 @@ class BomItemSerializer(InvenTreeModelSerializer):
|
|||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
class CategoryParameterTemplateSerializer(InvenTreeModelSerializer):
|
class CategoryParameterTemplateSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||||
"""Serializer for the PartCategoryParameterTemplate model."""
|
"""Serializer for the PartCategoryParameterTemplate model."""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -1297,7 +1287,7 @@ class PartCopyBOMSerializer(serializers.Serializer):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class BomImportUploadSerializer(DataFileUploadSerializer):
|
class BomImportUploadSerializer(InvenTree.serializers.DataFileUploadSerializer):
|
||||||
"""Serializer for uploading a file and extracting data from it."""
|
"""Serializer for uploading a file and extracting data from it."""
|
||||||
|
|
||||||
TARGET_MODEL = BomItem
|
TARGET_MODEL = BomItem
|
||||||
@ -1333,7 +1323,7 @@ class BomImportUploadSerializer(DataFileUploadSerializer):
|
|||||||
part.bom_items.all().delete()
|
part.bom_items.all().delete()
|
||||||
|
|
||||||
|
|
||||||
class BomImportExtractSerializer(DataFileExtractSerializer):
|
class BomImportExtractSerializer(InvenTree.serializers.DataFileExtractSerializer):
|
||||||
"""Serializer class for exatracting BOM data from an uploaded file.
|
"""Serializer class for exatracting BOM data from an uploaded file.
|
||||||
|
|
||||||
The parent class DataFileExtractSerializer does most of the heavy lifting here.
|
The parent class DataFileExtractSerializer does most of the heavy lifting here.
|
||||||
|
@ -2038,6 +2038,12 @@ function loadPartTable(table, url, options={}) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
columns.push({
|
||||||
|
field: 'units',
|
||||||
|
title: '{% trans "Units" %}',
|
||||||
|
sortable: true,
|
||||||
|
});
|
||||||
|
|
||||||
columns.push({
|
columns.push({
|
||||||
sortName: 'category',
|
sortName: 'category',
|
||||||
field: 'category_detail',
|
field: 'category_detail',
|
||||||
|
@ -626,6 +626,11 @@ function getPartTableFilters() {
|
|||||||
type: 'bool',
|
type: 'bool',
|
||||||
title: '{% trans "Component" %}',
|
title: '{% trans "Component" %}',
|
||||||
},
|
},
|
||||||
|
has_units: {
|
||||||
|
type: 'bool',
|
||||||
|
title: '{% trans "Has Units" %}',
|
||||||
|
description: '{% trans "Part has defined units" %}',
|
||||||
|
},
|
||||||
has_ipn: {
|
has_ipn: {
|
||||||
type: 'bool',
|
type: 'bool',
|
||||||
title: '{% trans "Has IPN" %}',
|
title: '{% trans "Has IPN" %}',
|
||||||
|
Reference in New Issue
Block a user