Skip to content

Commit da40467

Browse files
authored
Merge pull request #142 from FEWS-NET/HEA-651/API-Endpoint-for-Dynamic-Search
Hea 651/api endpoint for dynamic search
2 parents edb772f + 1489e7a commit da40467

File tree

7 files changed

+408
-2
lines changed

7 files changed

+408
-2
lines changed

apps/baseline/models.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from django.contrib.gis.db import models
99
from django.core.exceptions import ValidationError
1010
from django.core.validators import MaxValueValidator, MinValueValidator
11-
from django.db.models import F
11+
from django.db.models import F, Q
1212
from django.utils.translation import gettext_lazy as _
1313
from model_utils.managers import InheritanceManager
1414

@@ -18,6 +18,7 @@
1818
ClassifiedProduct,
1919
Country,
2020
Currency,
21+
SearchQueryMixin,
2122
UnitOfMeasure,
2223
UnitOfMeasureConversion,
2324
)
@@ -62,6 +63,28 @@ class ExtraMeta:
6263
identifier = ["name"]
6364

6465

66+
class LivelihoodZoneQuerySet(SearchQueryMixin, models.QuerySet):
67+
"""
68+
Searchable LivelihoodZones
69+
"""
70+
71+
def get_search_filter(self, search_term):
72+
return (
73+
Q(code__iexact=search_term)
74+
| Q(alternate_code__iexact=search_term)
75+
| Q(name_en__icontains=search_term)
76+
| Q(name_pt__icontains=search_term)
77+
| Q(name_es__icontains=search_term)
78+
| Q(name_fr__icontains=search_term)
79+
| Q(name_ar__icontains=search_term)
80+
| Q(description_en__icontains=search_term)
81+
| Q(description_pt__icontains=search_term)
82+
| Q(description_es__icontains=search_term)
83+
| Q(description_fr__icontains=search_term)
84+
| Q(description_ar__icontains=search_term)
85+
)
86+
87+
6588
class LivelihoodZone(common_models.Model):
6689
"""
6790
A geographical area within a Country in which people share broadly the same
@@ -98,6 +121,7 @@ class LivelihoodZone(common_models.Model):
98121
name = TranslatedField(common_models.NameField(max_length=200, unique=True))
99122
description = TranslatedField(common_models.DescriptionField())
100123
country = models.ForeignKey(Country, verbose_name=_("Country"), db_column="country_code", on_delete=models.PROTECT)
124+
objects = common_models.IdentifierManager.from_queryset(LivelihoodZoneQuerySet)()
101125

102126
class Meta:
103127
verbose_name = _("Livelihood Zone")

apps/baseline/tests/test_viewsets.py

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
from baseline.models import LivelihoodZoneBaseline
1313
from common.fields import translation_fields
1414
from common.tests.factories import ClassifiedProductFactory, CountryFactory
15+
from metadata.tests.factories import (
16+
LivelihoodCategoryFactory,
17+
WealthCharacteristicFactory,
18+
)
1519

1620
from .factories import (
1721
BaselineLivelihoodActivityFactory,
@@ -536,6 +540,157 @@ def test_population_estimate_range_filter(self):
536540
self.assertEqual(response.status_code, 200)
537541
self.assertEqual(len(response.data), 0)
538542

543+
def test_filter_by_product(self):
544+
product = ClassifiedProductFactory(
545+
cpc="K0111",
546+
description_en="my product",
547+
common_name_en="common",
548+
kcals_per_unit=550,
549+
aliases=["test alias"],
550+
)
551+
ClassifiedProductFactory(cpc="K01111")
552+
baseline = LivelihoodZoneBaselineFactory()
553+
LivelihoodStrategyFactory(product=product, livelihood_zone_baseline=baseline)
554+
response = self.client.get(self.url, {"product": "K011"})
555+
self.assertEqual(response.status_code, 200)
556+
self.assertEqual(len(json.loads(response.content)), 1)
557+
# filter by cpc
558+
response = self.client.get(self.url, {"product": "K0111"})
559+
self.assertEqual(response.status_code, 200)
560+
self.assertEqual(len(json.loads(response.content)), 1)
561+
# filter by cpc startswith
562+
response = self.client.get(self.url, {"product": "K01111"})
563+
self.assertEqual(response.status_code, 200)
564+
self.assertEqual(len(json.loads(response.content)), 0)
565+
# filter by description icontains
566+
response = self.client.get(self.url, {"product": "my"})
567+
self.assertEqual(response.status_code, 200)
568+
self.assertEqual(len(json.loads(response.content.decode("utf-8"))), 1)
569+
# filter by description
570+
response = self.client.get(self.url, {"product": "my product"})
571+
self.assertEqual(response.status_code, 200)
572+
self.assertEqual(len(json.loads(response.content.decode("utf-8"))), 1)
573+
# filter by alias
574+
response = self.client.get(self.url, {"product": "test"})
575+
self.assertEqual(response.status_code, 200)
576+
self.assertEqual(len(json.loads(response.content.decode("utf-8"))), 1)
577+
578+
def test_filter_by_wealth_characteristic(self):
579+
baseline = LivelihoodZoneBaselineFactory()
580+
wealth_characteristic = WealthCharacteristicFactory()
581+
WealthGroupCharacteristicValueFactory(
582+
wealth_group__livelihood_zone_baseline=baseline, wealth_characteristic=wealth_characteristic
583+
)
584+
response = self.client.get(self.url, {"wealth_characteristic": wealth_characteristic.code})
585+
self.assertEqual(response.status_code, 200)
586+
self.assertEqual(len(json.loads(response.content.decode("utf-8"))), 1)
587+
response = self.client.get(self.url, {"wealth_characteristic": wealth_characteristic.name})
588+
self.assertEqual(response.status_code, 200)
589+
self.assertEqual(len(response.json()), 1)
590+
591+
592+
class LivelihoodZoneBaselineFacetedSearchViewTestCase(APITestCase):
593+
def setUp(self):
594+
self.category1 = LivelihoodCategoryFactory()
595+
self.baseline1 = LivelihoodZoneBaselineFactory(main_livelihood_category=self.category1)
596+
self.baseline2 = LivelihoodZoneBaselineFactory(main_livelihood_category=self.category1)
597+
self.baseline3 = LivelihoodZoneBaselineFactory()
598+
self.product1 = ClassifiedProductFactory(
599+
cpc="K0111",
600+
description_en="my test",
601+
common_name_en="common",
602+
kcals_per_unit=550,
603+
aliases=["test alias"],
604+
)
605+
self.product2 = ClassifiedProductFactory(
606+
cpc="L0111",
607+
description_en="my mukera",
608+
common_name_en="common mukera",
609+
kcals_per_unit=550,
610+
)
611+
LivelihoodStrategyFactory(product=self.product1, livelihood_zone_baseline=self.baseline1)
612+
self.characteristic1 = WealthCharacteristicFactory(description_en="my test")
613+
self.characteristic2 = WealthCharacteristicFactory(description_en="my mukera", description_fr="my test")
614+
WealthGroupCharacteristicValueFactory(
615+
wealth_group__livelihood_zone_baseline=self.baseline1, wealth_characteristic=self.characteristic1
616+
)
617+
WealthGroupCharacteristicValueFactory(
618+
wealth_group__livelihood_zone_baseline=self.baseline2, wealth_characteristic=self.characteristic2
619+
)
620+
self.characteristic3 = WealthCharacteristicFactory()
621+
self.strategy = LivelihoodStrategyFactory(product=self.product1, livelihood_zone_baseline=self.baseline3)
622+
self.baseline = LivelihoodZoneBaselineFactory(main_livelihood_category=self.category1)
623+
self.url = reverse("livelihood-zone-baseline-faceted-search")
624+
625+
def test_search_with_product(self):
626+
# Test when search matches entries
627+
response = self.client.get(self.url, {"search": self.product1.description_en, "language": "en"})
628+
self.assertEqual(response.status_code, 200)
629+
search_data = response.data
630+
self.assertEqual(len(search_data["products"]), 1)
631+
self.assertEqual(search_data["products"][0]["count"], 2) # 2 zones have this product
632+
# confirm the product value is correct
633+
self.assertEqual(search_data["products"][0]["value"], self.product1.cpc)
634+
# Apply the filters to the baseline
635+
baseline_url = reverse("livelihoodzonebaseline-list")
636+
response = self.client.get(
637+
baseline_url, {search_data["products"][0]["filter"]: search_data["products"][0]["value"]}
638+
)
639+
self.assertEqual(response.status_code, 200)
640+
self.assertEqual(len(json.loads(response.content)), 2)
641+
data = json.loads(response.content)
642+
self.assertTrue(any(d["name"] == self.baseline1.name for d in data))
643+
self.assertTrue(any(d["name"] == self.baseline3.name for d in data))
644+
self.assertFalse(any(d["name"] == self.baseline2.name for d in data))
645+
646+
response = self.client.get(baseline_url, {search_data["items"][0]["filter"]: search_data["items"][0]["value"]})
647+
self.assertEqual(response.status_code, 200)
648+
self.assertEqual(len(json.loads(response.content)), 1)
649+
data = json.loads(response.content)
650+
self.assertTrue(any(d["name"] == self.baseline1.name for d in data))
651+
self.assertFalse(any(d["name"] == self.baseline2.name for d in data))
652+
self.assertFalse(any(d["name"] == self.baseline3.name for d in data))
653+
# Search by the second product
654+
response = self.client.get(
655+
self.url,
656+
{
657+
"search": self.product2.description_en,
658+
},
659+
)
660+
self.assertEqual(response.status_code, 200)
661+
search_data = response.data
662+
self.assertEqual(len(search_data["products"]), 0)
663+
664+
def test_search_with_wealth_characterstics(self):
665+
# Test when search matches entries
666+
response = self.client.get(self.url, {"search": self.characteristic1.description_en})
667+
self.assertEqual(response.status_code, 200)
668+
data = response.data
669+
self.assertEqual(len(data["items"]), 2)
670+
self.assertEqual(data["items"][0]["count"], 1) # 1 zone for this characteristic
671+
self.assertEqual(data["items"][1]["count"], 1) # 1 zone for this characteristic
672+
# Search by the second characteristic
673+
response = self.client.get(
674+
self.url,
675+
{
676+
"search": self.characteristic2.description_en,
677+
},
678+
)
679+
self.assertEqual(response.status_code, 200)
680+
data = response.data
681+
self.assertEqual(len(data["items"]), 1)
682+
self.assertEqual(data["items"][0]["count"], 1) # 1 zone for this characteristic
683+
# Search by the third characteristic
684+
response = self.client.get(
685+
self.url,
686+
{
687+
"search": self.characteristic3.description_en,
688+
},
689+
)
690+
data = response.data
691+
self.assertEqual(response.status_code, 200)
692+
self.assertEqual(len(data["items"]), 0)
693+
539694

540695
class LivelihoodProductCategoryViewSetTestCase(APITestCase):
541696
@classmethod

0 commit comments

Comments
 (0)