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
|
||||
"""
|
||||
|
||||
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
|
||||
has_ipn = rest_filters.BooleanFilter(label='Has IPN', method='filter_has_ipn')
|
||||
|
||||
def filter_has_ipn(self, queryset, name, value):
|
||||
"""Filter by whether the Part has an IPN (internal part number) or not"""
|
||||
value = str2bool(value)
|
||||
|
||||
if value:
|
||||
queryset = queryset.exclude(IPN='')
|
||||
if str2bool(value):
|
||||
return queryset.exclude(IPN='')
|
||||
else:
|
||||
queryset = queryset.filter(IPN='')
|
||||
|
||||
return queryset
|
||||
return queryset.filter(IPN='')
|
||||
|
||||
# Regex filter for name
|
||||
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):
|
||||
"""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
|
||||
queryset = queryset.exclude(minimum_stock=0)
|
||||
# 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:
|
||||
# Filter items which have an 'in_stock' level higher than 'minimum_stock'
|
||||
queryset = queryset.filter(Q(in_stock__gte=F('minimum_stock')))
|
||||
|
||||
return queryset
|
||||
return queryset.filter(Q(in_stock__gte=F('minimum_stock')))
|
||||
|
||||
# has_stock filter
|
||||
has_stock = rest_filters.BooleanFilter(label='Has stock', method='filter_has_stock')
|
||||
|
||||
def filter_has_stock(self, queryset, name, value):
|
||||
"""Filter by whether the Part has any stock"""
|
||||
value = str2bool(value)
|
||||
|
||||
if value:
|
||||
queryset = queryset.filter(Q(in_stock__gt=0))
|
||||
if str2bool(value):
|
||||
return queryset.filter(Q(in_stock__gt=0))
|
||||
else:
|
||||
queryset = queryset.filter(Q(in_stock__lte=0))
|
||||
|
||||
return queryset
|
||||
return queryset.filter(Q(in_stock__lte=0))
|
||||
|
||||
# unallocated_stock filter
|
||||
unallocated_stock = rest_filters.BooleanFilter(label='Unallocated stock', method='filter_unallocated_stock')
|
||||
|
||||
def filter_unallocated_stock(self, queryset, name, value):
|
||||
"""Filter by whether the Part has unallocated stock"""
|
||||
value = str2bool(value)
|
||||
|
||||
if value:
|
||||
queryset = queryset.filter(Q(unallocated_stock__gt=0))
|
||||
if str2bool(value):
|
||||
return queryset.filter(Q(unallocated_stock__gt=0))
|
||||
else:
|
||||
queryset = queryset.filter(Q(unallocated_stock__lte=0))
|
||||
|
||||
return queryset
|
||||
return queryset.filter(Q(unallocated_stock__lte=0))
|
||||
|
||||
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)
|
||||
|
||||
queryset = queryset.exclude(id__in=children)
|
||||
|
||||
return queryset
|
||||
return queryset.exclude(id__in=children)
|
||||
|
||||
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"""
|
||||
|
||||
descendants = part.get_descendants(include_self=False)
|
||||
queryset = queryset.filter(id__in=descendants)
|
||||
|
||||
return queryset
|
||||
return queryset.filter(id__in=descendants)
|
||||
|
||||
variant_of = rest_filters.ModelChoiceFilter(label='Variant Of', queryset=Part.objects.all(), method='filter_variant_of')
|
||||
|
||||
def filter_variant_of(self, queryset, name, part):
|
||||
"""Limit queryset to direct children (variants) of the specified part"""
|
||||
|
||||
queryset = queryset.filter(id__in=part.get_children())
|
||||
return queryset
|
||||
return queryset.filter(id__in=part.get_children())
|
||||
|
||||
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"""
|
||||
|
||||
bom_parts = part.get_parts_in_bom()
|
||||
queryset = queryset.filter(id__in=[p.pk for p in bom_parts])
|
||||
return queryset
|
||||
return queryset.filter(id__in=[p.pk for p in bom_parts])
|
||||
|
||||
has_pricing = rest_filters.BooleanFilter(label="Has Pricing", method="filter_has_pricing")
|
||||
|
||||
def filter_has_pricing(self, queryset, name, value):
|
||||
"""Filter the queryset based on whether pricing information is available for the sub_part"""
|
||||
|
||||
value = str2bool(value)
|
||||
|
||||
q_a = Q(pricing_data=None)
|
||||
q_b = Q(pricing_data__overall_min=None, pricing_data__overall_max=None)
|
||||
|
||||
if value:
|
||||
queryset = queryset.exclude(q_a | q_b)
|
||||
if str2bool(value):
|
||||
return queryset.exclude(q_a | q_b)
|
||||
else:
|
||||
queryset = queryset.filter(q_a | q_b)
|
||||
|
||||
return queryset
|
||||
return queryset.filter(q_a | q_b)
|
||||
|
||||
stocktake = rest_filters.BooleanFilter(label="Has stocktake", method='filter_has_stocktake')
|
||||
|
||||
def filter_has_stocktake(self, queryset, name, value):
|
||||
"""Filter the queryset based on whether stocktake data is available"""
|
||||
|
||||
value = str2bool(value)
|
||||
|
||||
if (value):
|
||||
queryset = queryset.exclude(last_stocktake=None)
|
||||
if str2bool(value):
|
||||
return queryset.exclude(last_stocktake=None)
|
||||
else:
|
||||
queryset = queryset.filter(last_stocktake=None)
|
||||
|
||||
return queryset
|
||||
return queryset.filter(last_stocktake=None)
|
||||
|
||||
is_template = rest_filters.BooleanFilter()
|
||||
|
||||
@ -1259,6 +1247,7 @@ class PartList(PartMixin, APIDownloadMixin, ListCreateAPI):
|
||||
'unallocated_stock',
|
||||
'category',
|
||||
'last_stocktake',
|
||||
'units',
|
||||
]
|
||||
|
||||
# Default ordering
|
||||
|
@ -20,21 +20,11 @@ from taggit.serializers import TagListSerializerField
|
||||
import common.models
|
||||
import company.models
|
||||
import InvenTree.helpers
|
||||
import InvenTree.serializers
|
||||
import InvenTree.status
|
||||
import part.filters
|
||||
import part.tasks
|
||||
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.tasks import offload_task
|
||||
|
||||
@ -48,7 +38,7 @@ from .models import (BomItem, BomItemSubstitute, Part, PartAttachment,
|
||||
logger = logging.getLogger("inventree")
|
||||
|
||||
|
||||
class CategorySerializer(InvenTreeModelSerializer):
|
||||
class CategorySerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||
"""Serializer for PartCategory."""
|
||||
|
||||
class Meta:
|
||||
@ -94,7 +84,7 @@ class CategorySerializer(InvenTreeModelSerializer):
|
||||
starred = serializers.SerializerMethodField()
|
||||
|
||||
|
||||
class CategoryTree(InvenTreeModelSerializer):
|
||||
class CategoryTree(InvenTree.serializers.InvenTreeModelSerializer):
|
||||
"""Serializer for PartCategory tree."""
|
||||
|
||||
class Meta:
|
||||
@ -108,19 +98,19 @@ class CategoryTree(InvenTreeModelSerializer):
|
||||
]
|
||||
|
||||
|
||||
class PartAttachmentSerializer(InvenTreeAttachmentSerializer):
|
||||
class PartAttachmentSerializer(InvenTree.serializers.InvenTreeAttachmentSerializer):
|
||||
"""Serializer for the PartAttachment class."""
|
||||
|
||||
class Meta:
|
||||
"""Metaclass defining serializer fields"""
|
||||
model = PartAttachment
|
||||
|
||||
fields = InvenTreeAttachmentSerializer.attachment_fields([
|
||||
fields = InvenTree.serializers.InvenTreeAttachmentSerializer.attachment_fields([
|
||||
'part',
|
||||
])
|
||||
|
||||
|
||||
class PartTestTemplateSerializer(InvenTreeModelSerializer):
|
||||
class PartTestTemplateSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||
"""Serializer for the PartTestTemplate class."""
|
||||
|
||||
class Meta:
|
||||
@ -141,7 +131,7 @@ class PartTestTemplateSerializer(InvenTreeModelSerializer):
|
||||
key = serializers.CharField(read_only=True)
|
||||
|
||||
|
||||
class PartSalePriceSerializer(InvenTreeModelSerializer):
|
||||
class PartSalePriceSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||
"""Serializer for sale prices for Part model."""
|
||||
|
||||
class Meta:
|
||||
@ -155,14 +145,14 @@ class PartSalePriceSerializer(InvenTreeModelSerializer):
|
||||
'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."""
|
||||
|
||||
class Meta:
|
||||
@ -176,13 +166,13 @@ class PartInternalPriceSerializer(InvenTreeModelSerializer):
|
||||
'price_currency',
|
||||
]
|
||||
|
||||
quantity = InvenTreeDecimalField()
|
||||
quantity = InvenTree.serializers.InvenTreeDecimalField()
|
||||
|
||||
price = InvenTreeMoneySerializer(
|
||||
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 PartThumbSerializer(serializers.Serializer):
|
||||
@ -195,7 +185,7 @@ class PartThumbSerializer(serializers.Serializer):
|
||||
count = serializers.IntegerField(read_only=True)
|
||||
|
||||
|
||||
class PartThumbSerializerUpdate(InvenTreeModelSerializer):
|
||||
class PartThumbSerializerUpdate(InvenTree.serializers.InvenTreeModelSerializer):
|
||||
"""Serializer for updating Part thumbnail."""
|
||||
|
||||
class Meta:
|
||||
@ -212,10 +202,10 @@ class PartThumbSerializerUpdate(InvenTreeModelSerializer):
|
||||
raise serializers.ValidationError("File is not an image")
|
||||
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."""
|
||||
|
||||
class Meta:
|
||||
@ -229,7 +219,7 @@ class PartParameterTemplateSerializer(InvenTreeModelSerializer):
|
||||
]
|
||||
|
||||
|
||||
class PartParameterSerializer(InvenTreeModelSerializer):
|
||||
class PartParameterSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||
"""JSON serializers for the PartParameter model."""
|
||||
|
||||
class Meta:
|
||||
@ -260,7 +250,7 @@ class PartParameterSerializer(InvenTreeModelSerializer):
|
||||
template_detail = PartParameterTemplateSerializer(source='template', many=False, read_only=True)
|
||||
|
||||
|
||||
class PartBriefSerializer(InvenTreeModelSerializer):
|
||||
class PartBriefSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||
"""Serializer for Part (brief detail)"""
|
||||
|
||||
class Meta:
|
||||
@ -295,8 +285,8 @@ class PartBriefSerializer(InvenTreeModelSerializer):
|
||||
thumbnail = serializers.CharField(source='get_thumbnail_url', read_only=True)
|
||||
|
||||
# Pricing fields
|
||||
pricing_min = 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_min = InvenTree.serializers.InvenTreeMoneySerializer(source='pricing_data.overall_min', 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):
|
||||
@ -406,7 +396,7 @@ class InitialSupplierSerializer(serializers.Serializer):
|
||||
return data
|
||||
|
||||
|
||||
class PartSerializer(RemoteImageMixin, InvenTreeTagModelSerializer):
|
||||
class PartSerializer(InvenTree.serializers.RemoteImageMixin, InvenTree.serializers.InvenTreeTagModelSerializer):
|
||||
"""Serializer for complete detail information of a part.
|
||||
|
||||
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)
|
||||
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)
|
||||
starred = serializers.SerializerMethodField()
|
||||
|
||||
@ -615,8 +605,8 @@ class PartSerializer(RemoteImageMixin, InvenTreeTagModelSerializer):
|
||||
category = serializers.PrimaryKeyRelatedField(queryset=PartCategory.objects.all())
|
||||
|
||||
# Pricing fields
|
||||
pricing_min = 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_min = InvenTree.serializers.InvenTreeMoneySerializer(source='pricing_data.overall_min', allow_null=True, read_only=True)
|
||||
pricing_max = InvenTree.serializers.InvenTreeMoneySerializer(source='pricing_data.overall_max', allow_null=True, read_only=True)
|
||||
|
||||
parameters = PartParameterSerializer(
|
||||
many=True,
|
||||
@ -771,7 +761,7 @@ class PartSerializer(RemoteImageMixin, InvenTreeTagModelSerializer):
|
||||
return self.instance
|
||||
|
||||
|
||||
class PartStocktakeSerializer(InvenTreeModelSerializer):
|
||||
class PartStocktakeSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||
"""Serializer for the PartStocktake model"""
|
||||
|
||||
class Meta:
|
||||
@ -800,13 +790,13 @@ class PartStocktakeSerializer(InvenTreeModelSerializer):
|
||||
|
||||
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_currency = InvenTreeCurrencySerializer()
|
||||
cost_min = InvenTree.serializers.InvenTreeMoneySerializer(allow_null=True)
|
||||
cost_min_currency = InvenTree.serializers.InvenTreeCurrencySerializer()
|
||||
|
||||
cost_max = InvenTreeMoneySerializer(allow_null=True)
|
||||
cost_max_currency = InvenTreeCurrencySerializer()
|
||||
cost_max = InvenTree.serializers.InvenTreeMoneySerializer(allow_null=True)
|
||||
cost_max_currency = InvenTree.serializers.InvenTreeCurrencySerializer()
|
||||
|
||||
def save(self):
|
||||
"""Called when this serializer is saved"""
|
||||
@ -820,7 +810,7 @@ class PartStocktakeSerializer(InvenTreeModelSerializer):
|
||||
super().save()
|
||||
|
||||
|
||||
class PartStocktakeReportSerializer(InvenTreeModelSerializer):
|
||||
class PartStocktakeReportSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||
"""Serializer for stocktake report class"""
|
||||
|
||||
class Meta:
|
||||
@ -836,9 +826,9 @@ class PartStocktakeReportSerializer(InvenTreeModelSerializer):
|
||||
'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):
|
||||
@ -906,7 +896,7 @@ class PartStocktakeReportGenerateSerializer(serializers.Serializer):
|
||||
)
|
||||
|
||||
|
||||
class PartPricingSerializer(InvenTreeModelSerializer):
|
||||
class PartPricingSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||
"""Serializer for Part pricing information"""
|
||||
|
||||
class Meta:
|
||||
@ -942,29 +932,29 @@ class PartPricingSerializer(InvenTreeModelSerializer):
|
||||
scheduled_for_update = serializers.BooleanField(read_only=True)
|
||||
|
||||
# Custom serializers
|
||||
bom_cost_min = InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
||||
bom_cost_max = InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
||||
bom_cost_min = InvenTree.serializers.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_max = InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
||||
purchase_cost_min = InvenTree.serializers.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_max = InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
||||
internal_cost_min = InvenTree.serializers.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_max = InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
||||
supplier_price_min = InvenTree.serializers.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_max = InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
||||
variant_cost_min = InvenTree.serializers.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_max = InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
||||
overall_min = InvenTree.serializers.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_max = InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
||||
sale_price_min = InvenTree.serializers.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_max = InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
||||
sale_history_min = InvenTree.serializers.InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
||||
sale_history_max = InvenTree.serializers.InvenTreeMoneySerializer(allow_null=True, read_only=True)
|
||||
|
||||
update = serializers.BooleanField(
|
||||
write_only=True,
|
||||
@ -984,7 +974,7 @@ class PartPricingSerializer(InvenTreeModelSerializer):
|
||||
pricing.update_pricing()
|
||||
|
||||
|
||||
class PartRelationSerializer(InvenTreeModelSerializer):
|
||||
class PartRelationSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||
"""Serializer for a PartRelated model."""
|
||||
|
||||
class Meta:
|
||||
@ -1002,7 +992,7 @@ class PartRelationSerializer(InvenTreeModelSerializer):
|
||||
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."""
|
||||
|
||||
class Meta:
|
||||
@ -1020,7 +1010,7 @@ class PartStarSerializer(InvenTreeModelSerializer):
|
||||
username = serializers.CharField(source='user.username', read_only=True)
|
||||
|
||||
|
||||
class BomItemSubstituteSerializer(InvenTreeModelSerializer):
|
||||
class BomItemSubstituteSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||
"""Serializer for the BomItemSubstitute class."""
|
||||
|
||||
class Meta:
|
||||
@ -1036,7 +1026,7 @@ class BomItemSubstituteSerializer(InvenTreeModelSerializer):
|
||||
part_detail = PartBriefSerializer(source='part', read_only=True, many=False)
|
||||
|
||||
|
||||
class BomItemSerializer(InvenTreeModelSerializer):
|
||||
class BomItemSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||
"""Serializer for BomItem object."""
|
||||
|
||||
class Meta:
|
||||
@ -1087,7 +1077,7 @@ class BomItemSerializer(InvenTreeModelSerializer):
|
||||
if sub_part_detail is not True:
|
||||
self.fields.pop('sub_part_detail')
|
||||
|
||||
quantity = InvenTreeDecimalField(required=True)
|
||||
quantity = InvenTree.serializers.InvenTreeDecimalField(required=True)
|
||||
|
||||
def validate_quantity(self, quantity):
|
||||
"""Perform validation for the BomItem quantity field"""
|
||||
@ -1109,8 +1099,8 @@ class BomItemSerializer(InvenTreeModelSerializer):
|
||||
on_order = serializers.FloatField(read_only=True)
|
||||
|
||||
# Cached pricing fields
|
||||
pricing_min = 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_min = InvenTree.serializers.InvenTreeMoneySerializer(source='sub_part.pricing.overall_min', 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
|
||||
available_stock = serializers.FloatField(read_only=True)
|
||||
@ -1212,7 +1202,7 @@ class BomItemSerializer(InvenTreeModelSerializer):
|
||||
return queryset
|
||||
|
||||
|
||||
class CategoryParameterTemplateSerializer(InvenTreeModelSerializer):
|
||||
class CategoryParameterTemplateSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||
"""Serializer for the PartCategoryParameterTemplate model."""
|
||||
|
||||
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."""
|
||||
|
||||
TARGET_MODEL = BomItem
|
||||
@ -1333,7 +1323,7 @@ class BomImportUploadSerializer(DataFileUploadSerializer):
|
||||
part.bom_items.all().delete()
|
||||
|
||||
|
||||
class BomImportExtractSerializer(DataFileExtractSerializer):
|
||||
class BomImportExtractSerializer(InvenTree.serializers.DataFileExtractSerializer):
|
||||
"""Serializer class for exatracting BOM data from an uploaded file.
|
||||
|
||||
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({
|
||||
sortName: 'category',
|
||||
field: 'category_detail',
|
||||
|
@ -626,6 +626,11 @@ function getPartTableFilters() {
|
||||
type: 'bool',
|
||||
title: '{% trans "Component" %}',
|
||||
},
|
||||
has_units: {
|
||||
type: 'bool',
|
||||
title: '{% trans "Has Units" %}',
|
||||
description: '{% trans "Part has defined units" %}',
|
||||
},
|
||||
has_ipn: {
|
||||
type: 'bool',
|
||||
title: '{% trans "Has IPN" %}',
|
||||
|
Reference in New Issue
Block a user