def filter_queryset(self, queryset): queryset = super().filter_queryset(queryset) params = self.request.query_params # Does the user wish to filter by part? part_pk = params.get('part', None) if part_pk: queryset = queryset.filter(stock_item__part=part_pk) # Filter by "tracked" status # Tracked means that the item is "installed" into a build output (stock item) tracked = params.get('tracked', None) if tracked is not None: tracked = str2bool(tracked) if tracked: queryset = queryset.exclude(install_into=None) else: queryset = queryset.filter(install_into=None) # Filter by output target output = params.get('output', None) if output: if isNull(output): queryset = queryset.filter(install_into=None) else: queryset = queryset.filter(install_into=output) return queryset
def filter_queryset(self, queryset): """ Custom filtering: - Allow filtering by "null" parent to retrieve top-level stock locations """ queryset = super().filter_queryset(queryset) params = self.request.query_params loc_id = params.get('parent', None) cascade = str2bool(params.get('cascade', False)) # Do not filter by location if loc_id is None: pass # Look for top-level locations elif isNull(loc_id): # If we allow "cascade" at the top-level, this essentially means *all* locations if not cascade: queryset = queryset.filter(parent=None) else: try: location = StockLocation.objects.get(pk=loc_id) # All sub-locations to be returned too? if cascade: parents = location.get_descendants(include_self=True) parent_ids = [p.id for p in parents] queryset = queryset.filter(parent__in=parent_ids) else: queryset = queryset.filter(parent=location) except (ValueError, StockLocation.DoesNotExist): pass # Exclude StockLocation tree exclude_tree = params.get('exclude_tree', None) if exclude_tree is not None: try: loc = StockLocation.objects.get(pk=exclude_tree) queryset = queryset.exclude(pk__in=[ subloc.pk for subloc in loc.get_descendants(include_self=True) ]) except (ValueError, StockLocation.DoesNotExist): pass return queryset
def post(self, request, *args, **kwargs): build = self.get_object() form = self.get_form() confirm = request.POST.get('confirm', False) output_id = request.POST.get('output_id', None) if output_id: # If a "null" output is provided, we are trying to unallocate "untracked" stock if isNull(output_id): output = None else: try: output = StockItem.objects.get(pk=output_id) except (ValueError, StockItem.DoesNotExist): output = None part_id = request.POST.get('part_id', None) try: part = Part.objects.get(pk=part_id) except (ValueError, Part.DoesNotExist): part = None valid = False if confirm is False: form.add_error('confirm', _('Confirm unallocation of build stock')) form.add_error(None, _('Check the confirmation box')) else: valid = True # Unallocate the entire build if not output_id: build.unallocateAll() # Unallocate a single output elif output: build.unallocateOutput(output, part=part) # Unallocate "untracked" parts else: build.unallocateUntracked(part=part) data = { 'form_valid': valid, } return self.renderJsonResponse(request, form, data)
def filter_queryset(self, queryset): """ Custom filtering: - Allow filtering by "null" parent to retrieve top-level part categories """ queryset = super().filter_queryset(queryset) params = self.request.query_params cat_id = params.get('parent', None) cascade = str2bool(params.get('cascade', False)) # Do not filter by category if cat_id is None: pass # Look for top-level categories elif isNull(cat_id): if not cascade: queryset = queryset.filter(parent=None) else: try: category = PartCategory.objects.get(pk=cat_id) if cascade: parents = category.get_descendants(include_self=True) parent_ids = [p.id for p in parents] queryset = queryset.filter(parent__in=parent_ids) else: queryset = queryset.filter(parent=category) except (ValueError, PartCategory.DoesNotExist): pass return queryset
def filter_queryset(self, queryset): queryset = super().filter_queryset(queryset) params = self.request.query_params # Does the user wish to filter by part? part_pk = params.get('part', None) if part_pk: queryset = queryset.filter(stock_item__part=part_pk) # Filter by output target output = params.get('output', None) if output: if isNull(output): queryset = queryset.filter(install_into=None) else: queryset = queryset.filter(install_into=output) return queryset
def get_queryset(self): """ Custom filtering: - Allow filtering by "null" parent to retrieve top-level stock locations """ queryset = super().get_queryset() loc_id = self.request.query_params.get('parent', None) if loc_id is not None: # Look for top-level locations if isNull(loc_id): queryset = queryset.filter(parent=None) else: try: loc_id = int(loc_id) queryset = queryset.filter(parent=loc_id) except ValueError: pass return queryset
def get_queryset(self): """ Custom filtering: - Allow filtering by "null" parent to retrieve top-level part categories """ cat_id = self.request.query_params.get('parent', None) queryset = super().get_queryset() if cat_id is not None: # Look for top-level categories if isNull(cat_id): queryset = queryset.filter(parent=None) else: try: cat_id = int(cat_id) queryset = queryset.filter(parent=cat_id) except ValueError: pass return queryset
def filter_queryset(self, queryset): """ Perform custom filtering of the queryset. We overide the DRF filter_fields here because """ params = self.request.query_params queryset = super().filter_queryset(queryset) # Filter by "uses" query - Limit to parts which use the provided part uses = params.get('uses', None) if uses: try: uses = Part.objects.get(pk=uses) queryset = queryset.filter(uses.get_used_in_filter()) except (ValueError, Part.DoesNotExist): pass # Filter by 'ancestor'? ancestor = params.get('ancestor', None) if ancestor is not None: # If an 'ancestor' part is provided, filter to match only children try: ancestor = Part.objects.get(pk=ancestor) descendants = ancestor.get_descendants(include_self=False) queryset = queryset.filter(pk__in=[d.pk for d in descendants]) except (ValueError, Part.DoesNotExist): pass # Filter by whether the part has an IPN (internal part number) defined has_ipn = params.get('has_ipn', None) if has_ipn is not None: has_ipn = str2bool(has_ipn) if has_ipn: queryset = queryset.exclude(IPN='') else: queryset = queryset.filter(IPN='') # Filter by whether the BOM has been validated (or not) bom_valid = params.get('bom_valid', None) # TODO: Querying bom_valid status may be quite expensive # TODO: (It needs to be profiled!) # TODO: It might be worth caching the bom_valid status to a database column if bom_valid is not None: bom_valid = str2bool(bom_valid) # Limit queryset to active assemblies queryset = queryset.filter(active=True, assembly=True) pks = [] for part in queryset: if part.is_bom_valid() == bom_valid: pks.append(part.pk) queryset = queryset.filter(pk__in=pks) # Filter by 'starred' parts? starred = params.get('starred', None) if starred is not None: starred = str2bool(starred) starred_parts = [ star.part.pk for star in self.request.user.starred_parts.all() ] if starred: queryset = queryset.filter(pk__in=starred_parts) else: queryset = queryset.exclude(pk__in=starred_parts) # Cascade? (Default = True) cascade = str2bool(params.get('cascade', True)) # Does the user wish to filter by category? cat_id = params.get('category', None) if cat_id is None: # No category filtering if category is not specified pass else: # Category has been specified! if isNull(cat_id): # A 'null' category is the top-level category if cascade is False: # Do not cascade, only list parts in the top-level category queryset = queryset.filter(category=None) else: try: category = PartCategory.objects.get(pk=cat_id) # If '?cascade=true' then include parts which exist in sub-categories if cascade: queryset = queryset.filter( category__in=category.getUniqueChildren()) # Just return parts directly in the requested category else: queryset = queryset.filter(category=cat_id) except (ValueError, PartCategory.DoesNotExist): pass # Annotate calculated data to the queryset # (This will be used for further filtering) queryset = part_serializers.PartSerializer.annotate_queryset(queryset) # Filter by whether the part has stock has_stock = params.get("has_stock", None) if has_stock is not None: has_stock = str2bool(has_stock) if has_stock: queryset = queryset.filter(Q(in_stock__gt=0)) else: queryset = queryset.filter(Q(in_stock__lte=0)) # If we are filtering by 'low_stock' status low_stock = params.get('low_stock', None) if low_stock is not None: low_stock = str2bool(low_stock) if low_stock: # 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'))) else: # Filter items which have an 'in_stock' level higher than 'minimum_stock' queryset = queryset.filter(Q(in_stock__gte=F('minimum_stock'))) # Filter by "parts which need stock to complete build" stock_to_build = params.get('stock_to_build', None) # TODO: This is super expensive, database query wise... # TODO: Need to figure out a cheaper way of making this filter query if stock_to_build is not None: # Filter only active parts queryset = queryset.filter(active=True) # Prefetch current active builds build_active_queryset = Build.objects.filter( status__in=BuildStatus.ACTIVE_CODES) build_active_prefetch = Prefetch('builds', queryset=build_active_queryset, to_attr='current_builds') parts = queryset.prefetch_related(build_active_prefetch) # Store parts with builds needing stock parts_need_stock = [] # Find parts with active builds # where any subpart's stock is lower than quantity being built for part in parts: if part.current_builds: builds_ids = [build.id for build in part.current_builds] total_build_quantity = build_active_queryset.filter( pk__in=builds_ids).aggregate( quantity=Sum('quantity'))['quantity'] if part.can_build < total_build_quantity: parts_need_stock.append(part.pk) queryset = queryset.filter(pk__in=parts_need_stock) # Optionally limit the maximum number of returned results # e.g. for displaying "recent part" list max_results = params.get('max_results', None) if max_results is not None: try: max_results = int(max_results) if max_results > 0: queryset = queryset[:max_results] except (ValueError): pass return queryset
def filter_queryset(self, queryset): params = self.request.query_params queryset = super().filter_queryset(queryset) # Perform basic filtering: # Note: We do not let DRF filter here, it be slow AF supplier_part = params.get('supplier_part', None) if supplier_part: queryset = queryset.filter(supplier_part=supplier_part) belongs_to = params.get('belongs_to', None) if belongs_to: queryset = queryset.filter(belongs_to=belongs_to) # Filter by batch code batch = params.get('batch', None) if batch is not None: queryset = queryset.filter(batch=batch) build = params.get('build', None) if build: queryset = queryset.filter(build=build) # Filter by 'is building' status is_building = params.get('is_building', None) if is_building: is_building = str2bool(is_building) queryset = queryset.filter(is_building=is_building) sales_order = params.get('sales_order', None) if sales_order: queryset = queryset.filter(sales_order=sales_order) purchase_order = params.get('purchase_order', None) if purchase_order is not None: queryset = queryset.filter(purchase_order=purchase_order) # Filter stock items which are installed in another (specific) stock item installed_in = params.get('installed_in', None) if installed_in: # Note: The "installed_in" field is called "belongs_to" queryset = queryset.filter(belongs_to=installed_in) # Filter stock items which are installed in another stock item installed = params.get('installed', None) if installed is not None: installed = str2bool(installed) if installed: # Exclude items which are *not* installed in another item queryset = queryset.exclude(belongs_to=None) else: # Exclude items which are instaled in another item queryset = queryset.filter(belongs_to=None) if common.settings.stock_expiry_enabled(): # Filter by 'expired' status expired = params.get('expired', None) if expired is not None: expired = str2bool(expired) if expired: queryset = queryset.filter(StockItem.EXPIRED_FILTER) else: queryset = queryset.exclude(StockItem.EXPIRED_FILTER) # Filter by 'stale' status stale = params.get('stale', None) if stale is not None: stale = str2bool(stale) # How many days to account for "staleness"? stale_days = common.models.InvenTreeSetting.get_setting('STOCK_STALE_DAYS') if stale_days > 0: stale_date = datetime.now().date() + timedelta(days=stale_days) stale_filter = StockItem.IN_STOCK_FILTER & ~Q(expiry_date=None) & Q(expiry_date__lt=stale_date) if stale: queryset = queryset.filter(stale_filter) else: queryset = queryset.exclude(stale_filter) # Filter by customer customer = params.get('customer', None) if customer: queryset = queryset.filter(customer=customer) # Filter if items have been sent to a customer (any customer) sent_to_customer = params.get('sent_to_customer', None) if sent_to_customer is not None: sent_to_customer = str2bool(sent_to_customer) if sent_to_customer: queryset = queryset.exclude(customer=None) else: queryset = queryset.filter(customer=None) # Filter by "serialized" status? serialized = params.get('serialized', None) if serialized is not None: serialized = str2bool(serialized) if serialized: queryset = queryset.exclude(serial=None) else: queryset = queryset.filter(serial=None) # Filter by serial number? serial_number = params.get('serial', None) if serial_number is not None: queryset = queryset.filter(serial=serial_number) # Filter by range of serial numbers? serial_number_gte = params.get('serial_gte', None) serial_number_lte = params.get('serial_lte', None) if serial_number_gte is not None or serial_number_lte is not None: queryset = queryset.exclude(serial=None) if serial_number_gte is not None: queryset = queryset.filter(serial__gte=serial_number_gte) if serial_number_lte is not None: queryset = queryset.filter(serial__lte=serial_number_lte) # Filter by "in_stock" status in_stock = params.get('in_stock', None) if in_stock is not None: in_stock = str2bool(in_stock) if in_stock: # Filter out parts which are not actually "in stock" queryset = queryset.filter(StockItem.IN_STOCK_FILTER) else: # Only show parts which are not in stock queryset = queryset.exclude(StockItem.IN_STOCK_FILTER) # Filter by 'allocated' patrs? allocated = params.get('allocated', None) if allocated is not None: allocated = str2bool(allocated) if allocated: # Filter StockItem with either build allocations or sales order allocations queryset = queryset.filter(Q(sales_order_allocations__isnull=False) | Q(allocations__isnull=False)) else: # Filter StockItem without build allocations or sales order allocations queryset = queryset.filter(Q(sales_order_allocations__isnull=True) & Q(allocations__isnull=True)) # Do we wish to filter by "active parts" active = params.get('active', None) if active is not None: active = str2bool(active) queryset = queryset.filter(part__active=active) # Filter by 'depleted' status depleted = params.get('depleted', None) if depleted is not None: depleted = str2bool(depleted) if depleted: queryset = queryset.filter(quantity__lte=0) else: queryset = queryset.exclude(quantity__lte=0) # Filter by internal part number ipn = params.get('IPN', None) if ipn is not None: queryset = queryset.filter(part__IPN=ipn) # Does the client wish to filter by the Part ID? part_id = params.get('part', None) if part_id: try: part = Part.objects.get(pk=part_id) # Filter by any parts "under" the given part parts = part.get_descendants(include_self=True) queryset = queryset.filter(part__in=parts) except (ValueError, Part.DoesNotExist): raise ValidationError({"part": "Invalid Part ID specified"}) # Does the client wish to filter by the 'ancestor'? anc_id = params.get('ancestor', None) if anc_id: try: ancestor = StockItem.objects.get(pk=anc_id) # Only allow items which are descendants of the specified StockItem queryset = queryset.filter(id__in=[item.pk for item in ancestor.children.all()]) except (ValueError, Part.DoesNotExist): raise ValidationError({"ancestor": "Invalid ancestor ID specified"}) # Does the client wish to filter by stock location? loc_id = params.get('location', None) cascade = str2bool(params.get('cascade', True)) if loc_id is not None: # Filter by 'null' location (i.e. top-level items) if isNull(loc_id): queryset = queryset.filter(location=None) else: try: # If '?cascade=true' then include items which exist in sub-locations if cascade: location = StockLocation.objects.get(pk=loc_id) queryset = queryset.filter(location__in=location.getUniqueChildren()) else: queryset = queryset.filter(location=loc_id) except (ValueError, StockLocation.DoesNotExist): pass # Does the client wish to filter by part category? cat_id = params.get('category', None) if cat_id: try: category = PartCategory.objects.get(pk=cat_id) queryset = queryset.filter(part__category__in=category.getUniqueChildren()) except (ValueError, PartCategory.DoesNotExist): raise ValidationError({"category": "Invalid category id specified"}) # Filter by StockItem status status = params.get('status', None) if status: queryset = queryset.filter(status=status) # Filter by supplier_part ID supplier_part_id = params.get('supplier_part', None) if supplier_part_id: queryset = queryset.filter(supplier_part=supplier_part_id) # Filter by company (either manufacturer or supplier) company = params.get('company', None) if company is not None: queryset = queryset.filter(Q(supplier_part__supplier=company) | Q(supplier_part__manufacturer=company)) # Filter by supplier supplier = params.get('supplier', None) if supplier is not None: queryset = queryset.filter(supplier_part__supplier=supplier) # Filter by manufacturer manufacturer = params.get('manufacturer', None) if manufacturer is not None: queryset = queryset.filter(supplier_part__manufacturer=manufacturer) """ Filter by the 'last updated' date of the stock item(s): - updated_before=? : Filter stock items which were last updated *before* the provided date - updated_after=? : Filter stock items which were last updated *after* the provided date """ date_fmt = '%Y-%m-%d' # ISO format date string updated_before = params.get('updated_before', None) updated_after = params.get('updated_after', None) if updated_before: try: updated_before = datetime.strptime(str(updated_before), date_fmt).date() queryset = queryset.filter(updated__lte=updated_before) print("Before:", updated_before.isoformat()) except (ValueError, TypeError): # Account for improperly formatted date string print("After before:", str(updated_before)) pass if updated_after: try: updated_after = datetime.strptime(str(updated_after), date_fmt).date() queryset = queryset.filter(updated__gte=updated_after) print("After:", updated_after.isoformat()) except (ValueError, TypeError): # Account for improperly formatted date string print("After error:", str(updated_after)) pass # Also ensure that we pre-fecth all the related items queryset = queryset.prefetch_related( 'part', 'part__category', 'location' ) queryset = queryset.order_by('part__name') return queryset
def filter_queryset(self, queryset): params = self.request.query_params # Perform basic filtering: # Note: We do not let DRF filter here, it be slow AF supplier_part = params.get('supplier_part', None) if supplier_part: queryset = queryset.filter(supplier_part=supplier_part) belongs_to = params.get('belongs_to', None) if belongs_to: queryset = queryset.filter(belongs_to=belongs_to) build = params.get('build', None) if build: queryset = queryset.filter(build=build) build_order = params.get('build_order', None) if build_order: queryset = queryset.filter(build_order=build_order) sales_order = params.get('sales_order', None) if sales_order: queryset = queryset.filter(sales_order=sales_order) # Filter by "serialized" status? serialized = params.get('serialized', None) if serialized is not None: serialized = str2bool(serialized) if serialized: queryset = queryset.exclude(serial=None) else: queryset = queryset.filter(serial=None) # Filter by serial number? serial_number = params.get('serial', None) if serial_number is not None: queryset = queryset.filter(serial=serial_number) in_stock = self.request.query_params.get('in_stock', None) if in_stock is not None: in_stock = str2bool(in_stock) if in_stock: # Filter out parts which are not actually "in stock" queryset = queryset.filter(StockItem.IN_STOCK_FILTER) else: # Only show parts which are not in stock queryset = queryset.exclude(StockItem.IN_STOCK_FILTER) # Filter by 'allocated' patrs? allocated = self.request.query_params.get('allocated', None) if allocated is not None: allocated = str2bool(allocated) if allocated: # Filter StockItem with either build allocations or sales order allocations queryset = queryset.filter( Q(sales_order_allocations__isnull=False) | Q(allocations__isnull=False)) else: # Filter StockItem without build allocations or sales order allocations queryset = queryset.filter( Q(sales_order_allocations__isnull=True) & Q(allocations__isnull=True)) # Do we wish to filter by "active parts" active = self.request.query_params.get('active', None) if active is not None: active = str2bool(active) queryset = queryset.filter(part__active=active) # Does the client wish to filter by the Part ID? part_id = self.request.query_params.get('part', None) if part_id: try: part = Part.objects.get(pk=part_id) # If the part is a Template part, select stock items for any "variant" parts under that template if part.is_template: queryset = queryset.filter(part__in=[ part.id for part in Part.objects.filter(variant_of=part_id) ]) else: queryset = queryset.filter(part=part_id) except (ValueError, Part.DoesNotExist): raise ValidationError({"part": "Invalid Part ID specified"}) # Does the client wish to filter by the 'ancestor'? anc_id = self.request.query_params.get('ancestor', None) if anc_id: try: ancestor = StockItem.objects.get(pk=anc_id) # Only allow items which are descendants of the specified StockItem queryset = queryset.filter( id__in=[item.pk for item in ancestor.children.all()]) except (ValueError, Part.DoesNotExist): raise ValidationError( {"ancestor": "Invalid ancestor ID specified"}) # Does the client wish to filter by stock location? loc_id = self.request.query_params.get('location', None) cascade = str2bool(self.request.query_params.get('cascade', True)) if loc_id is not None: # Filter by 'null' location (i.e. top-level items) if isNull(loc_id): queryset = queryset.filter(location=None) else: try: # If '?cascade=true' then include items which exist in sub-locations if cascade: location = StockLocation.objects.get(pk=loc_id) queryset = queryset.filter( location__in=location.getUniqueChildren()) else: queryset = queryset.filter(location=loc_id) except (ValueError, StockLocation.DoesNotExist): pass # Does the client wish to filter by part category? cat_id = self.request.query_params.get('category', None) if cat_id: try: category = PartCategory.objects.get(pk=cat_id) queryset = queryset.filter( part__category__in=category.getUniqueChildren()) except (ValueError, PartCategory.DoesNotExist): raise ValidationError( {"category": "Invalid category id specified"}) # Filter by StockItem status status = self.request.query_params.get('status', None) if status: queryset = queryset.filter(status=status) # Filter by supplier_part ID supplier_part_id = self.request.query_params.get('supplier_part', None) if supplier_part_id: queryset = queryset.filter(supplier_part=supplier_part_id) # Filter by company (either manufacturer or supplier) company = self.request.query_params.get('company', None) if company is not None: queryset = queryset.filter( Q(supplier_part__supplier=company) | Q(supplier_part__manufacturer=company)) # Filter by supplier supplier = self.request.query_params.get('supplier', None) if supplier is not None: queryset = queryset.filter(supplier_part__supplier=supplier) # Filter by manufacturer manufacturer = self.request.query_params.get('manufacturer', None) if manufacturer is not None: queryset = queryset.filter( supplier_part__manufacturer=manufacturer) # Also ensure that we pre-fecth all the related items queryset = queryset.prefetch_related('part', 'part__category', 'location') queryset = queryset.order_by('part__name') return queryset
def filter_queryset(self, queryset): """ Perform custom filtering of the queryset """ # Perform basic filtering queryset = super().filter_queryset(queryset) # Filter by 'starred' parts? starred = str2bool(self.request.query_params.get('starred', None)) if starred is not None: starred_parts = [ star.part.pk for star in self.request.user.starred_parts.all() ] if starred: queryset = queryset.filter(pk__in=starred_parts) else: queryset = queryset.exclude(pk__in=starred_parts) # Cascade? cascade = str2bool(self.request.query_params.get('cascade', None)) # Does the user wish to filter by category? cat_id = self.request.query_params.get('category', None) if cat_id is None: # No category filtering if category is not specified pass else: # Category has been specified! if isNull(cat_id): # A 'null' category is the top-level category if cascade is False: # Do not cascade, only list parts in the top-level category queryset = queryset.filter(category=None) else: try: category = PartCategory.objects.get(pk=cat_id) # If '?cascade=true' then include parts which exist in sub-categories if cascade: queryset = queryset.filter( category__in=category.getUniqueChildren()) # Just return parts directly in the requested category else: queryset = queryset.filter(category=cat_id) except (ValueError, PartCategory.DoesNotExist): pass # Annotate calculated data to the queryset # (This will be used for further filtering) queryset = part_serializers.PartSerializer.annotate_queryset(queryset) # Filter by whether the part has stock has_stock = self.request.query_params.get("has_stock", None) if has_stock is not None: has_stock = str2bool(has_stock) if has_stock: queryset = queryset.filter(Q(in_stock__gt=0)) else: queryset = queryset.filter(Q(in_stock__lte=0)) # If we are filtering by 'low_stock' status low_stock = self.request.query_params.get('low_stock', None) if low_stock is not None: low_stock = str2bool(low_stock) if low_stock: # 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'))) 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
def filter_queryset(self, queryset): # Start with all objects stock_list = super().filter_queryset(queryset) # Filter out parts which are not actually "in stock" stock_list = stock_list.filter(customer=None, belongs_to=None) # Do we wish to filter by "active parts" active = self.request.query_params.get('active', None) if active is not None: active = str2bool(active) stock_list = stock_list.filter(part__active=active) # Does the client wish to filter by the Part ID? part_id = self.request.query_params.get('part', None) if part_id: try: part = Part.objects.get(pk=part_id) # If the part is a Template part, select stock items for any "variant" parts under that template if part.is_template: stock_list = stock_list.filter(part__in=[ part.id for part in Part.objects.filter(variant_of=part_id) ]) else: stock_list = stock_list.filter(part=part_id) except (ValueError, Part.DoesNotExist): pass # Does the client wish to filter by the 'ancestor'? anc_id = self.request.query_params.get('ancestor', None) if anc_id: try: ancestor = StockItem.objects.get(pk=anc_id) # Only allow items which are descendants of the specified StockItem stock_list = stock_list.filter( id__in=[item.pk for item in ancestor.children.all()]) except (ValueError, Part.DoesNotExist): pass # Does the client wish to filter by stock location? loc_id = self.request.query_params.get('location', None) cascade = str2bool(self.request.query_params.get('cascade', False)) if loc_id is not None: # Filter by 'null' location (i.e. top-level items) if isNull(loc_id): stock_list = stock_list.filter(location=None) else: try: # If '?cascade=true' then include items which exist in sub-locations if cascade: location = StockLocation.objects.get(pk=loc_id) stock_list = stock_list.filter( location__in=location.getUniqueChildren()) else: stock_list = stock_list.filter(location=loc_id) except (ValueError, StockLocation.DoesNotExist): pass # Does the client wish to filter by part category? cat_id = self.request.query_params.get('category', None) if cat_id: try: category = PartCategory.objects.get(pk=cat_id) stock_list = stock_list.filter( part__category__in=category.getUniqueChildren()) except (ValueError, PartCategory.DoesNotExist): pass # Filter by StockItem status status = self.request.query_params.get('status', None) if status: stock_list = stock_list.filter(status=status) # Filter by supplier_part ID supplier_part_id = self.request.query_params.get('supplier_part', None) if supplier_part_id: stock_list = stock_list.filter(supplier_part=supplier_part_id) # Filter by company (either manufacturer or supplier) company = self.request.query_params.get('company', None) if company is not None: stock_list = stock_list.filter( Q(supplier_part__supplier=company) | Q(supplier_part__manufacturer=company)) # Filter by supplier supplier = self.request.query_params.get('supplier', None) if supplier is not None: stock_list = stock_list.filter(supplier_part__supplier=supplier) # Filter by manufacturer manufacturer = self.request.query_params.get('manufacturer', None) if manufacturer is not None: stock_list = stock_list.filter( supplier_part__manufacturer=manufacturer) # Also ensure that we pre-fecth all the related items stock_list = stock_list.prefetch_related('part', 'part__category', 'location') stock_list = stock_list.order_by('part__name') return stock_list
def filter_queryset(self, queryset): """ Custom filtering for the StockItem queryset """ params = self.request.query_params queryset = super().filter_queryset(queryset) supplier_part = params.get('supplier_part', None) if supplier_part: queryset = queryset.filter(supplier_part=supplier_part) belongs_to = params.get('belongs_to', None) if belongs_to: queryset = queryset.filter(belongs_to=belongs_to) build = params.get('build', None) if build: queryset = queryset.filter(build=build) sales_order = params.get('sales_order', None) if sales_order: queryset = queryset.filter(sales_order=sales_order) purchase_order = params.get('purchase_order', None) if purchase_order is not None: queryset = queryset.filter(purchase_order=purchase_order) # Filter stock items which are installed in another (specific) stock item installed_in = params.get('installed_in', None) if installed_in: # Note: The "installed_in" field is called "belongs_to" queryset = queryset.filter(belongs_to=installed_in) if common.settings.stock_expiry_enabled(): # Filter by 'expired' status expired = params.get('expired', None) if expired is not None: expired = str2bool(expired) if expired: queryset = queryset.filter(StockItem.EXPIRED_FILTER) else: queryset = queryset.exclude(StockItem.EXPIRED_FILTER) # Filter by 'stale' status stale = params.get('stale', None) if stale is not None: stale = str2bool(stale) # How many days to account for "staleness"? stale_days = common.models.InvenTreeSetting.get_setting('STOCK_STALE_DAYS') if stale_days > 0: stale_date = datetime.now().date() + timedelta(days=stale_days) stale_filter = StockItem.IN_STOCK_FILTER & ~Q(expiry_date=None) & Q(expiry_date__lt=stale_date) if stale: queryset = queryset.filter(stale_filter) else: queryset = queryset.exclude(stale_filter) # Filter by customer customer = params.get('customer', None) if customer: queryset = queryset.filter(customer=customer) # Exclude stock item tree exclude_tree = params.get('exclude_tree', None) if exclude_tree is not None: try: item = StockItem.objects.get(pk=exclude_tree) queryset = queryset.exclude( pk__in=[it.pk for it in item.get_descendants(include_self=True)] ) except (ValueError, StockItem.DoesNotExist): pass # Filter by 'allocated' parts? allocated = params.get('allocated', None) if allocated is not None: allocated = str2bool(allocated) if allocated: # Filter StockItem with either build allocations or sales order allocations queryset = queryset.filter(Q(sales_order_allocations__isnull=False) | Q(allocations__isnull=False)) else: # Filter StockItem without build allocations or sales order allocations queryset = queryset.filter(Q(sales_order_allocations__isnull=True) & Q(allocations__isnull=True)) # Exclude StockItems which are already allocated to a particular SalesOrder exclude_so_allocation = params.get('exclude_so_allocation', None) if exclude_so_allocation is not None: try: order = SalesOrder.objects.get(pk=exclude_so_allocation) # Grab all the active SalesOrderAllocations for this order allocations = SalesOrderAllocation.objects.filter( line__pk__in=[ line.pk for line in order.lines.all() ] ) # Exclude any stock item which is already allocated to the sales order queryset = queryset.exclude( pk__in=[ a.item.pk for a in allocations ] ) except (ValueError, SalesOrder.DoesNotExist): pass # Does the client wish to filter by the Part ID? part_id = params.get('part', None) if part_id: try: part = Part.objects.get(pk=part_id) # Do we wish to filter *just* for this part, or also for parts *under* this one? include_variants = str2bool(params.get('include_variants', True)) if include_variants: # Filter by any parts "under" the given part parts = part.get_descendants(include_self=True) queryset = queryset.filter(part__in=parts) else: queryset = queryset.filter(part=part) except (ValueError, Part.DoesNotExist): raise ValidationError({"part": "Invalid Part ID specified"}) # Does the client wish to filter by the 'ancestor'? anc_id = params.get('ancestor', None) if anc_id: try: ancestor = StockItem.objects.get(pk=anc_id) # Only allow items which are descendants of the specified StockItem queryset = queryset.filter(id__in=[item.pk for item in ancestor.children.all()]) except (ValueError, Part.DoesNotExist): raise ValidationError({"ancestor": "Invalid ancestor ID specified"}) # Does the client wish to filter by stock location? loc_id = params.get('location', None) cascade = str2bool(params.get('cascade', True)) if loc_id is not None: # Filter by 'null' location (i.e. top-level items) if isNull(loc_id) and not cascade: queryset = queryset.filter(location=None) else: try: # If '?cascade=true' then include items which exist in sub-locations if cascade: location = StockLocation.objects.get(pk=loc_id) queryset = queryset.filter(location__in=location.getUniqueChildren()) else: queryset = queryset.filter(location=loc_id) except (ValueError, StockLocation.DoesNotExist): pass # Does the client wish to filter by part category? cat_id = params.get('category', None) if cat_id: try: category = PartCategory.objects.get(pk=cat_id) queryset = queryset.filter(part__category__in=category.getUniqueChildren()) except (ValueError, PartCategory.DoesNotExist): raise ValidationError({"category": "Invalid category id specified"}) # Does the client wish to filter by BomItem bom_item_id = params.get('bom_item', None) if bom_item_id is not None: try: bom_item = BomItem.objects.get(pk=bom_item_id) queryset = queryset.filter(bom_item.get_stock_filter()) except (ValueError, BomItem.DoesNotExist): pass # Filter by StockItem status status = params.get('status', None) if status: queryset = queryset.filter(status=status) # Filter by supplier_part ID supplier_part_id = params.get('supplier_part', None) if supplier_part_id: queryset = queryset.filter(supplier_part=supplier_part_id) # Filter by company (either manufacturer or supplier) company = params.get('company', None) if company is not None: queryset = queryset.filter(Q(supplier_part__supplier=company) | Q(supplier_part__manufacturer_part__manufacturer=company)) # Filter by supplier supplier = params.get('supplier', None) if supplier is not None: queryset = queryset.filter(supplier_part__supplier=supplier) # Filter by manufacturer manufacturer = params.get('manufacturer', None) if manufacturer is not None: queryset = queryset.filter(supplier_part__manufacturer_part__manufacturer=manufacturer) # Optionally, limit the maximum number of returned results max_results = params.get('max_results', None) if max_results is not None: try: max_results = int(max_results) if max_results > 0: queryset = queryset[:max_results] except (ValueError): pass # Also ensure that we pre-fecth all the related items queryset = queryset.prefetch_related( 'part', 'part__category', 'location' ) return queryset
def filter_queryset(self, queryset): params = self.request.query_params queryset = super().filter_queryset(queryset) # Perform basic filtering: # Note: We do not let DRF filter here, it be slow AF supplier_part = params.get('supplier_part', None) if supplier_part: queryset = queryset.filter(supplier_part=supplier_part) belongs_to = params.get('belongs_to', None) if belongs_to: queryset = queryset.filter(belongs_to=belongs_to) # Filter by batch code batch = params.get('batch', None) if batch is not None: queryset = queryset.filter(batch=batch) build = params.get('build', None) if build: queryset = queryset.filter(build=build) build_order = params.get('build_order', None) if build_order: queryset = queryset.filter(build_order=build_order) sales_order = params.get('sales_order', None) if sales_order: queryset = queryset.filter(sales_order=sales_order) purchase_order = params.get('purchase_order', None) if purchase_order is not None: queryset = queryset.filter(purchase_order=purchase_order) # Filter stock items which are installed in another (specific) stock item installed_in = params.get('installed_in', None) if installed_in: # Note: The "installed_in" field is called "belongs_to" queryset = queryset.filter(belongs_to=installed_in) # Filter stock items which are installed in another stock item installed = params.get('installed', None) if installed is not None: installed = str2bool(installed) if installed: # Exclude items which are *not* installed in another item queryset = queryset.exclude(belongs_to=None) else: # Exclude items which are instaled in another item queryset = queryset.filter(belongs_to=None) # Filter by customer customer = params.get('customer', None) if customer: queryset = queryset.filter(customer=customer) # Filter if items have been sent to a customer (any customer) sent_to_customer = params.get('sent_to_customer', None) if sent_to_customer is not None: sent_to_customer = str2bool(sent_to_customer) if sent_to_customer: queryset = queryset.exclude(customer=None) else: queryset = queryset.filter(customer=None) # Filter by "serialized" status? serialized = params.get('serialized', None) if serialized is not None: serialized = str2bool(serialized) if serialized: queryset = queryset.exclude(serial=None) else: queryset = queryset.filter(serial=None) # Filter by serial number? serial_number = params.get('serial', None) if serial_number is not None: queryset = queryset.filter(serial=serial_number) # Filter by range of serial numbers? serial_number_gte = params.get('serial_gte', None) serial_number_lte = params.get('serial_lte', None) if serial_number_gte is not None or serial_number_lte is not None: queryset = queryset.exclude(serial=None) if serial_number_gte is not None: queryset = queryset.filter(serial__gte=serial_number_gte) if serial_number_lte is not None: queryset = queryset.filter(serial__lte=serial_number_lte) # Filter by "in_stock" status in_stock = params.get('in_stock', None) if in_stock is not None: in_stock = str2bool(in_stock) if in_stock: # Filter out parts which are not actually "in stock" queryset = queryset.filter(StockItem.IN_STOCK_FILTER) else: # Only show parts which are not in stock queryset = queryset.exclude(StockItem.IN_STOCK_FILTER) # Filter by 'allocated' patrs? allocated = params.get('allocated', None) if allocated is not None: allocated = str2bool(allocated) if allocated: # Filter StockItem with either build allocations or sales order allocations queryset = queryset.filter(Q(sales_order_allocations__isnull=False) | Q(allocations__isnull=False)) else: # Filter StockItem without build allocations or sales order allocations queryset = queryset.filter(Q(sales_order_allocations__isnull=True) & Q(allocations__isnull=True)) # Do we wish to filter by "active parts" active = self.request.query_params.get('active', None) if active is not None: active = str2bool(active) queryset = queryset.filter(part__active=active) # Filter by 'depleted' status depleted = params.get('depleted', None) if depleted is not None: depleted = str2bool(depleted) if depleted: queryset = queryset.filter(quantity__lte=0) else: queryset = queryset.exclude(quantity__lte=0) # Filter by internal part number IPN = params.get('IPN', None) if IPN is not None: queryset = queryset.filter(part__IPN=IPN) # Does the client wish to filter by the Part ID? part_id = params.get('part', None) if part_id: try: part = Part.objects.get(pk=part_id) # Filter by any parts "under" the given part parts = part.get_descendants(include_self=True) queryset = queryset.filter(part__in=parts) except (ValueError, Part.DoesNotExist): raise ValidationError({"part": "Invalid Part ID specified"}) # Does the client wish to filter by the 'ancestor'? anc_id = self.request.query_params.get('ancestor', None) if anc_id: try: ancestor = StockItem.objects.get(pk=anc_id) # Only allow items which are descendants of the specified StockItem queryset = queryset.filter(id__in=[item.pk for item in ancestor.children.all()]) except (ValueError, Part.DoesNotExist): raise ValidationError({"ancestor": "Invalid ancestor ID specified"}) # Does the client wish to filter by stock location? loc_id = self.request.query_params.get('location', None) cascade = str2bool(self.request.query_params.get('cascade', True)) if loc_id is not None: # Filter by 'null' location (i.e. top-level items) if isNull(loc_id): queryset = queryset.filter(location=None) else: try: # If '?cascade=true' then include items which exist in sub-locations if cascade: location = StockLocation.objects.get(pk=loc_id) queryset = queryset.filter(location__in=location.getUniqueChildren()) else: queryset = queryset.filter(location=loc_id) except (ValueError, StockLocation.DoesNotExist): pass # Does the client wish to filter by part category? cat_id = self.request.query_params.get('category', None) if cat_id: try: category = PartCategory.objects.get(pk=cat_id) queryset = queryset.filter(part__category__in=category.getUniqueChildren()) except (ValueError, PartCategory.DoesNotExist): raise ValidationError({"category": "Invalid category id specified"}) # Filter by StockItem status status = self.request.query_params.get('status', None) if status: queryset = queryset.filter(status=status) # Filter by supplier_part ID supplier_part_id = self.request.query_params.get('supplier_part', None) if supplier_part_id: queryset = queryset.filter(supplier_part=supplier_part_id) # Filter by company (either manufacturer or supplier) company = self.request.query_params.get('company', None) if company is not None: queryset = queryset.filter(Q(supplier_part__supplier=company) | Q(supplier_part__manufacturer=company)) # Filter by supplier supplier = self.request.query_params.get('supplier', None) if supplier is not None: queryset = queryset.filter(supplier_part__supplier=supplier) # Filter by manufacturer manufacturer = self.request.query_params.get('manufacturer', None) if manufacturer is not None: queryset = queryset.filter(supplier_part__manufacturer=manufacturer) # Also ensure that we pre-fecth all the related items queryset = queryset.prefetch_related( 'part', 'part__category', 'location' ) queryset = queryset.order_by('part__name') return queryset
def filter_queryset(self, queryset): """ Perform custom filtering of the queryset. We overide the DRF filter_fields here because """ params = self.request.query_params queryset = super().filter_queryset(queryset) # Filter by 'ancestor'? ancestor = params.get('ancestor', None) if ancestor is not None: # If an 'ancestor' part is provided, filter to match only children try: ancestor = Part.objects.get(pk=ancestor) descendants = ancestor.get_descendants(include_self=False) queryset = queryset.filter(pk__in=[d.pk for d in descendants]) except (ValueError, Part.DoesNotExist): pass # Filter by whether the BOM has been validated (or not) bom_valid = params.get('bom_valid', None) # TODO: Querying bom_valid status may be quite expensive # TODO: (It needs to be profiled!) # TODO: It might be worth caching the bom_valid status to a database column if bom_valid is not None: bom_valid = str2bool(bom_valid) # Limit queryset to active assemblies queryset = queryset.filter(active=True, assembly=True) pks = [] for part in queryset: if part.is_bom_valid() == bom_valid: pks.append(part.pk) queryset = queryset.filter(pk__in=pks) # Filter by 'starred' parts? starred = params.get('starred', None) if starred is not None: starred = str2bool(starred) starred_parts = [ star.part.pk for star in self.request.user.starred_parts.all() ] if starred: queryset = queryset.filter(pk__in=starred_parts) else: queryset = queryset.exclude(pk__in=starred_parts) # Cascade? cascade = str2bool(params.get('cascade', None)) # Does the user wish to filter by category? cat_id = params.get('category', None) if cat_id is None: # No category filtering if category is not specified pass else: # Category has been specified! if isNull(cat_id): # A 'null' category is the top-level category if cascade is False: # Do not cascade, only list parts in the top-level category queryset = queryset.filter(category=None) else: try: category = PartCategory.objects.get(pk=cat_id) # If '?cascade=true' then include parts which exist in sub-categories if cascade: queryset = queryset.filter( category__in=category.getUniqueChildren()) # Just return parts directly in the requested category else: queryset = queryset.filter(category=cat_id) except (ValueError, PartCategory.DoesNotExist): pass # Annotate calculated data to the queryset # (This will be used for further filtering) queryset = part_serializers.PartSerializer.annotate_queryset(queryset) # Filter by whether the part has stock has_stock = params.get("has_stock", None) if has_stock is not None: has_stock = str2bool(has_stock) if has_stock: queryset = queryset.filter(Q(in_stock__gt=0)) else: queryset = queryset.filter(Q(in_stock__lte=0)) # If we are filtering by 'low_stock' status low_stock = params.get('low_stock', None) if low_stock is not None: low_stock = str2bool(low_stock) if low_stock: # 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'))) else: # Filter items which have an 'in_stock' level higher than 'minimum_stock' queryset = queryset.filter(Q(in_stock__gte=F('minimum_stock'))) # Filter by "parts which need stock to complete build" stock_to_build = params.get('stock_to_build', None) # TODO: This is super expensive, database query wise... # TODO: Need to figure out a cheaper way of making this filter query if stock_to_build is not None: # Filter only active parts queryset = queryset.filter(active=True) parts_need_stock = [] # Find parts with active builds # where any subpart's stock is lower than quantity being built for part in queryset: if part.active_builds and part.can_build < part.quantity_being_built: parts_need_stock.append(part.pk) queryset = queryset.filter(pk__in=parts_need_stock) # Limit choices limit = params.get('limit', None) if limit is not None: try: limit = int(limit) if limit > 0: queryset = queryset[:limit] except ValueError: pass return queryset