mirror of
https://github.com/netbox-community/netbox.git
synced 2026-01-11 21:10:29 +01:00
Improve API filtering 'OR' logic to work on all fields #2492
Closed
opened 2025-12-29 18:19:19 +01:00 by adam
·
13 comments
No Branch/Tag Specified
main
update-changelog-comments-docs
feature-removal-issue-type
20911-dropdown
20239-plugin-menu-classes-mutable-state
21097-graphql-id-lookups
feature
fix_module_substitution
20923-dcim-templates
20044-elevation-stuck-lightmode
feature-ip-prefix-link
v4.5-beta1-release
20068-import-moduletype-attrs
20766-fix-german-translation-code-literals
20378-del-script
7604-filter-modifiers-v3
circuit-swap
12318-case-insensitive-uniqueness
20637-improve-device-q-filter
20660-script-load
19724-graphql
20614-update-ruff
14884-script
02496-max-page
19720-macaddress-interface-generic-relation
19408-circuit-terminations-export-templates
20203-openapi-check
fix-19669-api-image-download
7604-filter-modifiers
19275-fixes-interface-bulk-edit
fix-17794-get_field_value_return_list
11507-show-aggregate-and-rir-on-api
9583-add_column_specific_search_field_to_tables
v4.5.0
v4.4.10
v4.4.9
v4.5.0-beta1
v4.4.8
v4.4.7
v4.4.6
v4.4.5
v4.4.4
v4.4.3
v4.4.2
v4.4.1
v4.4.0
v4.3.7
v4.4.0-beta1
v4.3.6
v4.3.5
v4.3.4
v4.3.3
v4.3.2
v4.3.1
v4.3.0
v4.2.9
v4.3.0-beta2
v4.2.8
v4.3.0-beta1
v4.2.7
v4.2.6
v4.2.5
v4.2.4
v4.2.3
v4.2.2
v4.2.1
v4.2.0
v4.1.11
v4.1.10
v4.1.9
v4.1.8
v4.2-beta1
v4.1.7
v4.1.6
v4.1.5
v4.1.4
v4.1.3
v4.1.2
v4.1.1
v4.1.0
v4.0.11
v4.0.10
v4.0.9
v4.1-beta1
v4.0.8
v4.0.7
v4.0.6
v4.0.5
v4.0.3
v4.0.2
v4.0.1
v4.0.0
v3.7.8
v3.7.7
v4.0-beta2
v3.7.6
v3.7.5
v4.0-beta1
v3.7.4
v3.7.3
v3.7.2
v3.7.1
v3.7.0
v3.6.9
v3.6.8
v3.6.7
v3.7-beta1
v3.6.6
v3.6.5
v3.6.4
v3.6.3
v3.6.2
v3.6.1
v3.6.0
v3.5.9
v3.6-beta2
v3.5.8
v3.6-beta1
v3.5.7
v3.5.6
v3.5.5
v3.5.4
v3.5.3
v3.5.2
v3.5.1
v3.5.0
v3.4.10
v3.4.9
v3.5-beta2
v3.4.8
v3.5-beta1
v3.4.7
v3.4.6
v3.4.5
v3.4.4
v3.4.3
v3.4.2
v3.4.1
v3.4.0
v3.3.10
v3.3.9
v3.4-beta1
v3.3.8
v3.3.7
v3.3.6
v3.3.5
v3.3.4
v3.3.3
v3.3.2
v3.3.1
v3.3.0
v3.2.9
v3.2.8
v3.3-beta2
v3.2.7
v3.3-beta1
v3.2.6
v3.2.5
v3.2.4
v3.2.3
v3.2.2
v3.2.1
v3.2.0
v3.1.11
v3.1.10
v3.2-beta2
v3.1.9
v3.2-beta1
v3.1.8
v3.1.7
v3.1.6
v3.1.5
v3.1.4
v3.1.3
v3.1.2
v3.1.1
v3.1.0
v3.0.12
v3.0.11
v3.0.10
v3.1-beta1
v3.0.9
v3.0.8
v3.0.7
v3.0.6
v3.0.5
v3.0.4
v3.0.3
v3.0.2
v3.0.1
v3.0.0
v2.11.12
v3.0-beta2
v2.11.11
v2.11.10
v3.0-beta1
v2.11.9
v2.11.8
v2.11.7
v2.11.6
v2.11.5
v2.11.4
v2.11.3
v2.11.2
v2.11.1
v2.11.0
v2.10.10
v2.10.9
v2.11-beta1
v2.10.8
v2.10.7
v2.10.6
v2.10.5
v2.10.4
v2.10.3
v2.10.2
v2.10.1
v2.10.0
v2.9.11
v2.10-beta2
v2.9.10
v2.10-beta1
v2.9.9
v2.9.8
v2.9.7
v2.9.6
v2.9.5
v2.9.4
v2.9.3
v2.9.2
v2.9.1
v2.9.0
v2.9-beta2
v2.8.9
v2.9-beta1
v2.8.8
v2.8.7
v2.8.6
v2.8.5
v2.8.4
v2.8.3
v2.8.2
v2.8.1
v2.8.0
v2.7.12
v2.7.11
v2.7.10
v2.7.9
v2.7.8
v2.7.7
v2.7.6
v2.7.5
v2.7.4
v2.7.3
v2.7.2
v2.7.1
v2.7.0
v2.6.12
v2.6.11
v2.6.10
v2.6.9
v2.7-beta1
Solcon-2020-01-06
v2.6.8
v2.6.7
v2.6.6
v2.6.5
v2.6.4
v2.6.3
v2.6.2
v2.6.1
v2.6.0
v2.5.13
v2.5.12
v2.6-beta1
v2.5.11
v2.5.10
v2.5.9
v2.5.8
v2.5.7
v2.5.6
v2.5.5
v2.5.4
v2.5.3
v2.5.2
v2.5.1
v2.5.0
v2.4.9
v2.5-beta2
v2.4.8
v2.5-beta1
v2.4.7
v2.4.6
v2.4.5
v2.4.4
v2.4.3
v2.4.2
v2.4.1
v2.4.0
v2.3.7
v2.4-beta1
v2.3.6
v2.3.5
v2.3.4
v2.3.3
v2.3.2
v2.3.1
v2.3.0
v2.2.10
v2.3-beta2
v2.2.9
v2.3-beta1
v2.2.8
v2.2.7
v2.2.6
v2.2.5
v2.2.4
v2.2.3
v2.2.2
v2.2.1
v2.2.0
v2.1.6
v2.2-beta2
v2.1.5
v2.2-beta1
v2.1.4
v2.1.3
v2.1.2
v2.1.1
v2.1.0
v2.0.10
v2.1-beta1
v2.0.9
v2.0.8
v2.0.7
v2.0.6
v2.0.5
v2.0.4
v2.0.3
v2.0.2
v2.0.1
v2.0.0
v2.0-beta3
v1.9.6
v1.9.5
v2.0-beta2
v1.9.4-r1
v1.9.3
v2.0-beta1
v1.9.2
v1.9.1
v1.9.0-r1
v1.8.4
v1.8.3
v1.8.2
v1.8.1
v1.8.0
v1.7.3
v1.7.2-r1
v1.7.1
v1.7.0
v1.6.3
v1.6.2-r1
v1.6.1-r1
1.6.1
v1.6.0
v1.5.2
v1.5.1
v1.5.0
v1.4.2
v1.4.1
v1.4.0
v1.3.2
v1.3.1
v1.3.0
v1.2.2
v1.2.1
v1.2.0
v1.1.0
v1.0.7-r1
v1.0.7
v1.0.6
v1.0.5
v1.0.4
v1.0.3-r1
v1.0.3
1.0.0
Labels
Clear labels
beta
breaking change
complexity: high
complexity: low
complexity: medium
needs milestone
netbox
pending closure
plugin candidate
pull-request
severity: high
severity: low
severity: medium
status: accepted
status: backlog
status: blocked
status: duplicate
status: needs owner
status: needs triage
status: revisions needed
status: under review
topic: GraphQL
topic: Internationalization
topic: OpenAPI
topic: UI/UX
topic: cabling
topic: event rules
topic: htmx navigation
topic: industrialization
topic: migrations
topic: plugins
topic: scripts
topic: templating
topic: testing
type: bug
type: deprecation
type: documentation
type: feature
type: housekeeping
type: translation
Mirrored from GitHub Pull Request
Milestone
No items
No Milestone
Projects
Clear projects
No project
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: starred/netbox#2492
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @ajknv on GitHub (Apr 3, 2019).
Environment
Proposed Functionality
Handling of API requests involving multiple filter values should be consistent regardless of the fields involved, such that the logical OR of matching records will be returned. At the very least, multiple filters involving the same field should always work to return all matches.
Use Case
The overall utility of the API is broadly reduced by the impaired ability to make any number of fairly typical bulk queries for filtered records. The inconsistent behavior -- especially that of quietly just returning the matches for the last filter value -- pretty severely violates the principal of least surprise for API clients.
Database Changes
Unknown. Best guess is that this does not correlate to any required structural changes to the database, but likely just additional/improved glue logic between the API handlers and SQL query logic.
External Dependencies
None anticipated.
@DanSheps commented on GitHub (Apr 3, 2019):
Not all filters make sense to include as multiples ("has ip address" for example, it is boolean, either on or off).
Please come up with a list of fields that you think could benefit from being able to be filtered on multiple times and we can investigate further.
@ajknv commented on GitHub (Apr 3, 2019):
Part of the problem with that request is that it is unclear which ones currently work and which don't short of an exhaustive search. Even for boolean fields, applying an OR filter isn't problematic from any technical perspective, though I grant that it could result in pointless queries that just return everything anyway. But consistent behavior strikes me as a much more desirable characteristic than trying to exclude pointless but harmless queries that no one is likely to attempt in practice anyway. From that perspective, I would still favor the easy answer as simply being "all of them". That said, here are a few I've hit so far that are of concern:
Devices:
name
mac_address
parent_device_id (doesn't currently work as a filter at all)
id (currently this doesn't even work as a singular filter; of course that can be accomplished with a URL component, but multiple matches are useful for bulk queries)
Interfaces:
mac_address
device_id
id
Device-bays:
name
device_id
installed_device_id (does not appear to work as a filter at all, even singular)
id
Inventory-items:
name
device_id
part_id
id
@cescone commented on GitHub (Apr 4, 2019):
I happened upon this issue last week, while trying to get an interface list filtered by the interface's device_id property.
While people may find uses to multiple filtering almost any property, I think a good compromise would be to allow repeated filters for any property that acts like a primary key or foreign key, i.e. any id and related objects' id.
@jeremystretch commented on GitHub (May 2, 2019):
NetBox relies on the django-filter library for filtering object requests via both the web UI and the REST API. django-filter does not provide any built-in support for ORing multiple values specified for the same field; it only accepts the most recent (last) value provided. For example:
GET /api/dcim/sites/?name=foo&name=barbecomes
Site.objects.get(name="bar")While django-filter does provide a MultipleChoiceFilter, it must be instantiated with a predefined set of choices, which is not practical for freeform fields. We'll need to introduce a custom filter to instead turn the above request into
Site.objects.get(Q(name="foo") | Q(name="bar"))so that objects matching either value are returned.
I feel like you're overlooking the case of ManyToMany relationships. For example, most people would expect a request for
?tag=foo&tag=barto return only objects with both tags (a logical AND). However, there's no clear way to differentiate that multiple values for one field should result in an AND, and multiple values for a different field should be an OR.I propose we adapt the existing FilterSets to perform a logical OR in all instances where multiple values are provided for a field, except in the case of ManyToManyFields, where the current behavior (a logical AND) is preserved. This approach has the benefit of being the least disruptive while addressing the most common use cases.
An alternative approach would be to use comma-separated values (e.g.
?tag=foo,bar) but that introduces validation problems and additional burdens around request formation, so I'd much prefer to avoid that approach.@ajknv commented on GitHub (May 2, 2019):
Interestingly this feels pretty subjective to me, actually. Wanting to find a union set of objects matching any of a few different characteristics (e.g. captured in tags) seems like as much a probable use case as wanting to query for objects with multiple constraints. I'm on board with the proposed solution as a definite improvement over the current behavior while being cognizant of backwards compatibility, but I would posit that the ideal answer here is to have a meta-parameter to specify the filter operation, e.g. something like:
?filter_mode=OR&tag=foo&tag=bar
@jeremystretch commented on GitHub (May 2, 2019):
What if I want to find devices at site A OR site B with tag A AND tag B?
GET /api/dcim/devices/?site=SiteA&site=SiteB&tag=tagA&tag=tagB@jeremystretch commented on GitHub (May 3, 2019):
I've done some digging into django-filter and I think I've identified the sticking point with filtering on multiple values for CharFilters. django-filter leverages Django forms for validation, thus any filter which needs to accept multiple values must utilize ChoiceField or one of its derivatives. ChoiceField requires that a set of
choicesbe defined at run time for validation and form widget rendering, both of which are irrelevant in our case.The closest solution I've found is django-filter's AllValuesFilter, which is a bit of hack: It populates a
choiceslist from the set of all values present in the database (which requires a query to retrieve all values). This is unnecessary for our use case and obviously does not scale well.It might be feasibly to use a dummy object for
choicesto essentially pretend that any given value exists in the set, but at that point we're pretty firmly into "dirty hack" territory.@jeremystretch commented on GitHub (May 3, 2019):
I've raised django-filter #1076 to see if there's any supported way of achieving this.
@ajknv commented on GitHub (May 3, 2019):
Touché. :-) Yes, I can understand there's a balancing act in terms of where to draw the line between expressiveness and coverage of possible use cases. The example you gave would require a more fully-defined query DSL to be supported. The way I'm looking at it though is that a capability to select AND vs OR provides additional coverage of fairly important and common use cases -- probably a significant majority of them. This seems worthwhile, even if the last 10% (say) of complex use cases can't be covered and the cost to support a complete query language isn't worth it for that small tail-set.
Essentially I'm just pushing for the highest possible bang for whatever is a reasonable buck.
@jeremystretch commented on GitHub (May 6, 2019):
It looks like this is doable under djnago-filter using a simple custom filter and form field (thanks @rpkilby!). Now we just need to lock down the logic. I propose the following.
1. Different fields result in an AND
Example:
GET /api/dcim/devices/?site=Foo&status=1meanssite == Foo AND status == 12. Multiple instances of the same field are ORs
This applies to all non-ManyToManyFields.
Example:
GET /api/dcim/devices/?site=Foo&site=Bar&status=1means(site == Foo OR site == Bar) AND status == 13. ManyToManyFields accept CSV-formatted values to AND
Because an object may have multiple values for a ManyToManyField, we must be able to differentiate between OR and AND. We should accept comma-separated values to indicate ANDing.
Example 1:
GET /api/dcim/devices/?tag=foo&tag=barmeanstag == foo OR tag == barExample 2:
GET /api/dcim/devices/?tag=foo,barmeanstag == foo AND tag == barNote: The web UI probably won't support the AND approach, at least not immediately, but I'd still like to expose the logic for API clients.
@ajknv commented on GitHub (May 6, 2019):
Can commas be part of the the value of a tag? If so, how should that be handled?
@jeremystretch commented on GitHub (May 7, 2019):
@ajknv the
tagfilter matches on Tag.slug, which is not allowed to have commas in it, so it shouldn't be an issue.@candlerb commented on GitHub (Jun 7, 2019):
@jeremystretch wrote on May 6:
This sounds good to me. However, I note that commit
ffa34c6on branch develop-2.6 from May 8 says something different: