mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-11-04 07:05:41 +00:00 
			
		
		
		
	Default location column (#7587)
* Add "default_location_detail" serializer to part API * Add column to CUI table * Implement in PUI part table * Update API version
This commit is contained in:
		@@ -1,12 +1,15 @@
 | 
				
			|||||||
"""InvenTree API version information."""
 | 
					"""InvenTree API version information."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# InvenTree API version
 | 
					# InvenTree API version
 | 
				
			||||||
INVENTREE_API_VERSION = 213
 | 
					INVENTREE_API_VERSION = 214
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
 | 
					"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
INVENTREE_API_TEXT = """
 | 
					INVENTREE_API_TEXT = """
 | 
				
			||||||
 | 
					v214 - 2024-07-08 : https://github.com/inventree/InvenTree/pull/7587
 | 
				
			||||||
 | 
					    - Adds "default_location_detail" field to the Part API
 | 
				
			||||||
 | 
					
 | 
				
			||||||
v213 - 2024-07-06 : https://github.com/inventree/InvenTree/pull/7527
 | 
					v213 - 2024-07-06 : https://github.com/inventree/InvenTree/pull/7527
 | 
				
			||||||
    - Adds 'locked' field to Part API
 | 
					    - Adds 'locked' field to Part API
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,6 +10,7 @@ from sql_util.utils import SubqueryCount
 | 
				
			|||||||
from taggit.serializers import TagListSerializerField
 | 
					from taggit.serializers import TagListSerializerField
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import part.filters
 | 
					import part.filters
 | 
				
			||||||
 | 
					import part.serializers as part_serializers
 | 
				
			||||||
from importer.mixins import DataImportExportSerializerMixin
 | 
					from importer.mixins import DataImportExportSerializerMixin
 | 
				
			||||||
from importer.registry import register_importer
 | 
					from importer.registry import register_importer
 | 
				
			||||||
from InvenTree.serializers import (
 | 
					from InvenTree.serializers import (
 | 
				
			||||||
@@ -22,7 +23,6 @@ from InvenTree.serializers import (
 | 
				
			|||||||
    NotesFieldMixin,
 | 
					    NotesFieldMixin,
 | 
				
			||||||
    RemoteImageMixin,
 | 
					    RemoteImageMixin,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from part.serializers import PartBriefSerializer
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .models import (
 | 
					from .models import (
 | 
				
			||||||
    Address,
 | 
					    Address,
 | 
				
			||||||
@@ -254,7 +254,9 @@ class ManufacturerPartSerializer(
 | 
				
			|||||||
        if prettify is not True:
 | 
					        if prettify is not True:
 | 
				
			||||||
            self.fields.pop('pretty_name', None)
 | 
					            self.fields.pop('pretty_name', None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    part_detail = PartBriefSerializer(source='part', many=False, read_only=True)
 | 
					    part_detail = part_serializers.PartBriefSerializer(
 | 
				
			||||||
 | 
					        source='part', many=False, read_only=True
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    manufacturer_detail = CompanyBriefSerializer(
 | 
					    manufacturer_detail = CompanyBriefSerializer(
 | 
				
			||||||
        source='manufacturer', many=False, read_only=True
 | 
					        source='manufacturer', many=False, read_only=True
 | 
				
			||||||
@@ -387,7 +389,9 @@ class SupplierPartSerializer(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    pack_quantity_native = serializers.FloatField(read_only=True)
 | 
					    pack_quantity_native = serializers.FloatField(read_only=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    part_detail = PartBriefSerializer(source='part', many=False, read_only=True)
 | 
					    part_detail = part_serializers.PartBriefSerializer(
 | 
				
			||||||
 | 
					        source='part', many=False, read_only=True
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    supplier_detail = CompanyBriefSerializer(
 | 
					    supplier_detail = CompanyBriefSerializer(
 | 
				
			||||||
        source='supplier', many=False, read_only=True
 | 
					        source='supplier', many=False, read_only=True
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1204,6 +1204,7 @@ class PartMixin:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            kwargs['parameters'] = str2bool(params.get('parameters', None))
 | 
					            kwargs['parameters'] = str2bool(params.get('parameters', None))
 | 
				
			||||||
            kwargs['category_detail'] = str2bool(params.get('category_detail', False))
 | 
					            kwargs['category_detail'] = str2bool(params.get('category_detail', False))
 | 
				
			||||||
 | 
					            kwargs['location_detail'] = str2bool(params.get('location_detail', False))
 | 
				
			||||||
            kwargs['path_detail'] = str2bool(params.get('path_detail', False))
 | 
					            kwargs['path_detail'] = str2bool(params.get('path_detail', False))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        except AttributeError:
 | 
					        except AttributeError:
 | 
				
			||||||
@@ -1354,6 +1355,7 @@ class PartList(PartMixin, DataExportViewMixin, ListCreateAPI):
 | 
				
			|||||||
        'total_in_stock',
 | 
					        'total_in_stock',
 | 
				
			||||||
        'unallocated_stock',
 | 
					        'unallocated_stock',
 | 
				
			||||||
        'category',
 | 
					        'category',
 | 
				
			||||||
 | 
					        'default_location',
 | 
				
			||||||
        'last_stocktake',
 | 
					        'last_stocktake',
 | 
				
			||||||
        'units',
 | 
					        'units',
 | 
				
			||||||
        'pricing_min',
 | 
					        'pricing_min',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -591,6 +591,21 @@ class InitialSupplierSerializer(serializers.Serializer):
 | 
				
			|||||||
        return data
 | 
					        return data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DefaultLocationSerializer(InvenTree.serializers.InvenTreeModelSerializer):
 | 
				
			||||||
 | 
					    """Brief serializer for a StockLocation object.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Defined here, rather than stock.serializers, to negotiate circular imports.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class Meta:
 | 
				
			||||||
 | 
					        """Metaclass options."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        import stock.models as stock_models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        model = stock_models.StockLocation
 | 
				
			||||||
 | 
					        fields = ['pk', 'name', 'pathstring']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@register_importer()
 | 
					@register_importer()
 | 
				
			||||||
class PartSerializer(
 | 
					class PartSerializer(
 | 
				
			||||||
    DataImportExportSerializerMixin,
 | 
					    DataImportExportSerializerMixin,
 | 
				
			||||||
@@ -623,6 +638,7 @@ class PartSerializer(
 | 
				
			|||||||
            'creation_user',
 | 
					            'creation_user',
 | 
				
			||||||
            'default_expiry',
 | 
					            'default_expiry',
 | 
				
			||||||
            'default_location',
 | 
					            'default_location',
 | 
				
			||||||
 | 
					            'default_location_detail',
 | 
				
			||||||
            'default_supplier',
 | 
					            'default_supplier',
 | 
				
			||||||
            'description',
 | 
					            'description',
 | 
				
			||||||
            'full_name',
 | 
					            'full_name',
 | 
				
			||||||
@@ -687,6 +703,7 @@ class PartSerializer(
 | 
				
			|||||||
        """
 | 
					        """
 | 
				
			||||||
        self.starred_parts = kwargs.pop('starred_parts', [])
 | 
					        self.starred_parts = kwargs.pop('starred_parts', [])
 | 
				
			||||||
        category_detail = kwargs.pop('category_detail', False)
 | 
					        category_detail = kwargs.pop('category_detail', False)
 | 
				
			||||||
 | 
					        location_detail = kwargs.pop('location_detail', False)
 | 
				
			||||||
        parameters = kwargs.pop('parameters', False)
 | 
					        parameters = kwargs.pop('parameters', False)
 | 
				
			||||||
        create = kwargs.pop('create', False)
 | 
					        create = kwargs.pop('create', False)
 | 
				
			||||||
        pricing = kwargs.pop('pricing', True)
 | 
					        pricing = kwargs.pop('pricing', True)
 | 
				
			||||||
@@ -697,6 +714,9 @@ class PartSerializer(
 | 
				
			|||||||
        if not category_detail:
 | 
					        if not category_detail:
 | 
				
			||||||
            self.fields.pop('category_detail', None)
 | 
					            self.fields.pop('category_detail', None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not location_detail:
 | 
				
			||||||
 | 
					            self.fields.pop('default_location_detail', None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if not parameters:
 | 
					        if not parameters:
 | 
				
			||||||
            self.fields.pop('parameters', None)
 | 
					            self.fields.pop('parameters', None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -740,6 +760,8 @@ class PartSerializer(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        Performing database queries as efficiently as possible, to reduce database trips.
 | 
					        Performing database queries as efficiently as possible, to reduce database trips.
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
 | 
					        queryset = queryset.prefetch_related('category', 'default_location')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Annotate with the total number of stock items
 | 
					        # Annotate with the total number of stock items
 | 
				
			||||||
        queryset = queryset.annotate(stock_item_count=SubqueryCount('stock_items'))
 | 
					        queryset = queryset.annotate(stock_item_count=SubqueryCount('stock_items'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -833,6 +855,10 @@ class PartSerializer(
 | 
				
			|||||||
        child=serializers.DictField(), source='category.get_path', read_only=True
 | 
					        child=serializers.DictField(), source='category.get_path', read_only=True
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    default_location_detail = DefaultLocationSerializer(
 | 
				
			||||||
 | 
					        source='default_location', many=False, read_only=True
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    category_name = serializers.CharField(
 | 
					    category_name = serializers.CharField(
 | 
				
			||||||
        source='category.name', read_only=True, label=_('Category Name')
 | 
					        source='category.name', read_only=True, label=_('Category Name')
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,19 +17,19 @@ from taggit.serializers import TagListSerializerField
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import build.models
 | 
					import build.models
 | 
				
			||||||
import company.models
 | 
					import company.models
 | 
				
			||||||
 | 
					import company.serializers as company_serializers
 | 
				
			||||||
import InvenTree.helpers
 | 
					import InvenTree.helpers
 | 
				
			||||||
import InvenTree.serializers
 | 
					import InvenTree.serializers
 | 
				
			||||||
import order.models
 | 
					import order.models
 | 
				
			||||||
import part.filters as part_filters
 | 
					import part.filters as part_filters
 | 
				
			||||||
import part.models as part_models
 | 
					import part.models as part_models
 | 
				
			||||||
 | 
					import part.serializers as part_serializers
 | 
				
			||||||
import stock.filters
 | 
					import stock.filters
 | 
				
			||||||
import stock.status_codes
 | 
					import stock.status_codes
 | 
				
			||||||
from common.settings import get_global_setting
 | 
					from common.settings import get_global_setting
 | 
				
			||||||
from company.serializers import SupplierPartSerializer
 | 
					 | 
				
			||||||
from importer.mixins import DataImportExportSerializerMixin
 | 
					from importer.mixins import DataImportExportSerializerMixin
 | 
				
			||||||
from importer.registry import register_importer
 | 
					from importer.registry import register_importer
 | 
				
			||||||
from InvenTree.serializers import InvenTreeCurrencySerializer, InvenTreeDecimalField
 | 
					from InvenTree.serializers import InvenTreeCurrencySerializer, InvenTreeDecimalField
 | 
				
			||||||
from part.serializers import PartBriefSerializer, PartTestTemplateSerializer
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .models import (
 | 
					from .models import (
 | 
				
			||||||
    StockItem,
 | 
					    StockItem,
 | 
				
			||||||
@@ -233,7 +233,9 @@ class StockItemTestResultSerializer(
 | 
				
			|||||||
        label=_('Test template for this result'),
 | 
					        label=_('Test template for this result'),
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    template_detail = PartTestTemplateSerializer(source='template', read_only=True)
 | 
					    template_detail = part_serializers.PartTestTemplateSerializer(
 | 
				
			||||||
 | 
					        source='template', read_only=True
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    attachment = InvenTree.serializers.InvenTreeAttachmentSerializerField(
 | 
					    attachment = InvenTree.serializers.InvenTreeAttachmentSerializerField(
 | 
				
			||||||
        required=False
 | 
					        required=False
 | 
				
			||||||
@@ -560,7 +562,7 @@ class StockItemSerializer(
 | 
				
			|||||||
    sku = serializers.CharField(source='supplier_part.SKU', read_only=True)
 | 
					    sku = serializers.CharField(source='supplier_part.SKU', read_only=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Optional detail fields, which can be appended via query parameters
 | 
					    # Optional detail fields, which can be appended via query parameters
 | 
				
			||||||
    supplier_part_detail = SupplierPartSerializer(
 | 
					    supplier_part_detail = company_serializers.SupplierPartSerializer(
 | 
				
			||||||
        source='supplier_part',
 | 
					        source='supplier_part',
 | 
				
			||||||
        supplier_detail=False,
 | 
					        supplier_detail=False,
 | 
				
			||||||
        manufacturer_detail=False,
 | 
					        manufacturer_detail=False,
 | 
				
			||||||
@@ -568,7 +570,9 @@ class StockItemSerializer(
 | 
				
			|||||||
        many=False,
 | 
					        many=False,
 | 
				
			||||||
        read_only=True,
 | 
					        read_only=True,
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    part_detail = PartBriefSerializer(source='part', many=False, read_only=True)
 | 
					    part_detail = part_serializers.PartBriefSerializer(
 | 
				
			||||||
 | 
					        source='part', many=False, read_only=True
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    location_detail = LocationBriefSerializer(
 | 
					    location_detail = LocationBriefSerializer(
 | 
				
			||||||
        source='location', many=False, read_only=True
 | 
					        source='location', many=False, read_only=True
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2276,6 +2276,7 @@ function loadPartTable(table, url, options={}) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // Ensure category detail is included
 | 
					    // Ensure category detail is included
 | 
				
			||||||
    options.params['category_detail'] = true;
 | 
					    options.params['category_detail'] = true;
 | 
				
			||||||
 | 
					    options.params['location_detail'] = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let filters = {};
 | 
					    let filters = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -2389,6 +2390,19 @@ function loadPartTable(table, url, options={}) {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    columns.push({
 | 
				
			||||||
 | 
					        field: 'default_location',
 | 
				
			||||||
 | 
					        title: '{% trans "Default Location" %}',
 | 
				
			||||||
 | 
					        sortable: true,
 | 
				
			||||||
 | 
					        formatter: function(value, row) {
 | 
				
			||||||
 | 
					            if (row.default_location && row.default_location_detail) {
 | 
				
			||||||
 | 
					                let text = shortenString(row.default_location_detail.pathstring);
 | 
				
			||||||
 | 
					                return withTitle(renderLink(text, `/stock/location/${row.default_location}/`), row.default_location_detail.pathstring);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                return '-';
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    columns.push({
 | 
					    columns.push({
 | 
				
			||||||
        field: 'total_in_stock',
 | 
					        field: 'total_in_stock',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -44,6 +44,11 @@ function partTableColumns(): TableColumn[] {
 | 
				
			|||||||
      sortable: true,
 | 
					      sortable: true,
 | 
				
			||||||
      render: (record: any) => record.category_detail?.pathstring
 | 
					      render: (record: any) => record.category_detail?.pathstring
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      accessor: 'default_location',
 | 
				
			||||||
 | 
					      sortable: true,
 | 
				
			||||||
 | 
					      render: (record: any) => record.default_location_detail?.pathstring
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      accessor: 'total_in_stock',
 | 
					      accessor: 'total_in_stock',
 | 
				
			||||||
      sortable: true,
 | 
					      sortable: true,
 | 
				
			||||||
@@ -327,7 +332,8 @@ export function PartListTable({ props }: { props: InvenTreeTableProps }) {
 | 
				
			|||||||
          tableActions: tableActions,
 | 
					          tableActions: tableActions,
 | 
				
			||||||
          params: {
 | 
					          params: {
 | 
				
			||||||
            ...props.params,
 | 
					            ...props.params,
 | 
				
			||||||
            category_detail: true
 | 
					            category_detail: true,
 | 
				
			||||||
 | 
					            location_detail: true
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }}
 | 
					        }}
 | 
				
			||||||
      />
 | 
					      />
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user