Skip to content

Conversation

@SchrodingersGat
Copy link
Member

@SchrodingersGat SchrodingersGat commented Dec 14, 2025

Problem Description

We have multiple places in the code where we call enable_filter - to dynamically add (or remove) nested related fields from an API serializer.

To prevent 1 + N database fetches, we must call prefetch_related on all fields which are to be included - even if they are not included. So, potentially we are prefetching querysets which never even get used.

This is a problem as there is a non-zero cost associated with prefetching - see #10996

Ideally, we should only prefetch related fields when they are actually needed - that way we do not pay the price of prefetching when it is not needed

Solution

This PR adds the option to specify a list of prefetch fields to the enable_filter hook.

  • If the field is added to the queryset, the required fields will be prefetched
  • If the field is not added, then the prefetches do not fire

This PR introduces the new functionality, but does not refactor all instances, to reduce scope.

Performance Comparison

  • Comparing between "master" and this PR
  • Testing with and without parameter annotations
  • Tested via external API calls
  • Averaged across 5 runs
  • Times are in milliseconds
API URL master PR
/api/part/?limit=500 1521 1454
/api/part/?limit=500&parameters=0 1608 1434
/api/part/?limit=500&parameters=1 1816 1861
/api/company/?limit=500 1000 251
/api/company/?limit=500&parameters=0 932 251
/api/company/?limit=500&parameters=1 1230 534

Follow-up PRs

  • Make "tags" field filterable - and prefetch accordingly
  • Refactor all instances of enable_filter calls
  • Performance profiling for all API endpoints

- Allows us to *not* prefetch fields (expensive) when they are not going to be used
- Enables re-usable components for common detail fields
- Automatically apply correct prefetch fields
- add 'enable_parameters_filter' function
- Prefetch parameters only when needed
- Refactor / consolidate code
- Make fields switchable
- Ensure correct prefetch_related
@SchrodingersGat SchrodingersGat added this to the 1.2.0 milestone Dec 14, 2025
@SchrodingersGat SchrodingersGat added enhancement This is an suggested enhancement or new feature part Related to Part models refactor labels Dec 14, 2025
@netlify
Copy link

netlify bot commented Dec 14, 2025

Deploy Preview for inventree-web-pui-preview canceled.

Name Link
🔨 Latest commit bb264f3
🔍 Latest deploy log https://app.netlify.com/projects/inventree-web-pui-preview/deploys/6940c70c9a9f0a0008c68566

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces conditional prefetching for filterable serializer fields to optimize database queries by only prefetching relationships when their corresponding fields are actually included in the API response.

Key Changes:

  • Added prefetch_fields and prefetch_func parameters to the enable_filter decorator to specify relationships that should be prefetched when a field is included
  • Implemented automatic prefetching in FilterableSerializerMixin.prefetch_queryset() method that collects and applies prefetch directives from enabled fields
  • Created reusable filter helper functions (enable_project_code_filter, enable_project_label_filter, enable_parameters_filter) in common.filters to standardize common patterns

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
InvenTree/serializers.py Added prefetch infrastructure to FilterableSerializerField and enable_filter decorator; implemented prefetch_queryset method in FilterableSerializerMixin
InvenTree/mixins.py Modified OutputOptionsMixin.get_queryset() to automatically apply prefetching based on included filterable fields
InvenTree/models.py Added prefetch for parameters_list__updated_by in annotate_parameters method
common/filters.py Added three helper functions for common filterable field patterns (project_code, project_label, parameters) with appropriate prefetch configurations
part/serializers.py Removed static parameter annotation; replaced manual parameters field with enable_parameters_filter() helper
order/serializers.py Replaced manual field definitions with filter helpers; added prefetch_fields to contact, responsible, address, supplier, and customer detail fields; removed static parameter annotations
company/serializers.py Replaced manual field definitions with filter helpers; wrapped part_detail, supplier_detail, manufacturer_detail, and manufacturer_part_detail with enable_filter including prefetch_fields
build/serializers.py Replaced manual field definitions with filter helpers; added prefetch_fields to part_detail, issued_by, and responsible_detail fields; removed static parameter annotation
order/api.py Removed manual prefetch_related calls for responsible, project_code, and lines (now handled by conditional prefetching)
company/api.py Removed manual prefetch_related calls for part and manufacturer (now handled by conditional prefetching)
build/api.py Removed manual prefetch_related calls for responsible, issued_by, build_lines, part, and project_code (now handled by conditional prefetching)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

@matmair matmair left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like a good improvement, using common filters is great;

I am not sure if we even need the provided flexibility for the common filters; did you have something specific in mind when providing it? Having these things static might promote a more uniform API

@codecov
Copy link

codecov bot commented Dec 14, 2025

Codecov Report

❌ Patch coverage is 99.18033% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 88.16%. Comparing base (71c2f5c) to head (bb264f3).
⚠️ Report is 3 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master   #11012      +/-   ##
==========================================
- Coverage   88.16%   88.16%   -0.01%     
==========================================
  Files        1290     1290              
  Lines       58039    58074      +35     
  Branches     1970     1970              
==========================================
+ Hits        51172    51201      +29     
- Misses       6376     6382       +6     
  Partials      491      491              
Flag Coverage Δ
backend 89.46% <99.18%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
Backend Apps 92.02% <98.82%> (-0.02%) ⬇️
Backend General 93.47% <100.00%> (+0.01%) ⬆️
Frontend 70.86% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment on lines +713 to +715
def test_filterable_fields(self):
"""Test inclusion/exclusion of optional API fields."""
fields = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SchrodingersGat you could also use the run_output_test helper for this

Comment on lines +139 to +145
filterable_fields = [
field
for field in self.fields.values()
if getattr(field, 'is_filterable', None)
]

for field in filterable_fields:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The serializer already contains filter_targets, that gets build by the gather_filters function. Could we use that here instead of iterating over all fields again?

@SchrodingersGat SchrodingersGat merged commit ba7b776 into inventree:master Dec 16, 2025
35 checks passed
@SchrodingersGat SchrodingersGat deleted the filter-prefetch branch December 16, 2025 03:46
SchrodingersGat added a commit to SchrodingersGat/InvenTree that referenced this pull request Dec 16, 2025
- Off by default
- Only prefetch when requested (expensive)
- Ref: inventree#11012
- Ref: inventree#11002
- Closes inventree#10996
SchrodingersGat added a commit to SchrodingersGat/InvenTree that referenced this pull request Dec 16, 2025
- Specify prefetch_fields for optional child serializers
- Ref: inventree#11012
SchrodingersGat added a commit that referenced this pull request Dec 16, 2025
* API refactoring

- Specify prefetch_fields for optional child serializers
- Ref: #11012

* Fixes for unit tests
SchrodingersGat added a commit that referenced this pull request Dec 16, 2025
* Add optional "tags" field

* Refactor "tags" field

- Off by default
- Only prefetch when requested (expensive)
- Ref: #11012
- Ref: #11002
- Closes #10996

* Bump API version

* Tweak unit tests

* Ensure all fields are available when writing data

* Handle case where request has *no* method
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement This is an suggested enhancement or new feature part Related to Part models refactor

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants