Example #1
0
    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
Example #2
0
    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
Example #3
0
    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)
Example #4
0
    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
Example #5
0
    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
Example #6
0
    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
Example #7
0
    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
Example #8
0
    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
Example #9
0
    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
Example #10
0
    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
Example #11
0
    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
Example #12
0
    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
Example #13
0
    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
Example #14
0
    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
Example #15
0
    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