- ID
- edbff570-b5f3-49de-a6e4-2a2a954b6bb4
BE-7349 Add contains ALL product_ids filter to V2 and V3 parirings API
- source :: https://hungryroot.atlassian.net/browse/BE-7349
- tags :: Hungryroot API Project
- epic :: EP-411 Eng Parking Lot
- sprint :: HR BE Sprint 105
- status :: Status - Complete
Tasks
DONE Ask Cursor to walk through adding product_ids_all filters to V2 and V3 pairings
- ID
- 28d7a3c6-a7df-11f0-a4a7-0df08b843da6
DONE Get a screencap of the changes and make sure they work
- ID
- a75cdc01-9edc-c55d-3ff6-bcf52409a003
Description
Background
The v2 (/api/v2/pairings/) and v3 pairing list APIs currently support a
product_ids query parameter that filters pairings containing any of the
specified ingredient product IDs (OR semantics). There is no way to query for
pairings that contain all of the specified products.
Current behavior
GET /api/v2/pairings/?product_ids=101,202,303 returns pairings that have product
101 OR 202 OR 303 as an ingredient.
This is implemented via ModelMultipleChoiceCSVFilter with conjoined=False and a
filter_product_ids method that uses needed_ingredients__ingredient__in=value.
Desired behavior
Add a new query parameter (e.g. product_ids_all) that returns only pairings
whose ingredients include every product ID in the list (AND semantics).
GET /api/v2/pairings/?product_ids_all=101,202,303 should return only pairings that
have product 101 AND 202 AND 303 as ingredients.
Scope
v2 API - Add the new filter to PairingFilter in app/rest/pairing.py
v3 API - Add the new filter to BasePairingFilter in app/rest/v3/pairing.py
Tests - Add test coverage in app/tests/rest/test_pairings.py (and v3 equivalent)
for:
Single product ID (equivalent to existing behavior)
Multiple product IDs where a pairing matches all
Multiple product IDs where no pairing matches all
Empty value handling
OpenAPI docs - Add OpenApiParameter description for the new filter
Implementation Notes
The filter can be implemented using Count + annotate:
#+begin_src python
from django.db.models import Count, Q
def filter_product_ids_all(self, queryset, name, value): if not value: return
queryset product_ids = [v.pk for v in value] return ( queryset
.filter(needed_ingredients__ingredient__in=product_ids)
.annotate(matched_count=Count( "needed_ingredients__ingredient",
filter=Q(needed_ingredients__ingredient__in=product_ids), ))
.filter(matched_count=len(product_ids)) )
#+end_src
Files to modify
app/rest/pairing.py - PairingFilter
app/rest/v3/pairing.py - BasePairingFilter
app/tests/rest/test_pairings.py
app/tests/rest/v3/test_pairing.py