From ec1c3929210929af72ca1bca1b07619ef21a449f Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Thu, 6 Jul 2023 16:53:46 +0530 Subject: [PATCH 01/10] use postgres to search medicines with GinIndex --- care/facility/api/viewsets/prescription.py | 67 +++++-------------- .../0371_medibasemedicine_search_idx.py | 22 ++++++ care/facility/models/prescription.py | 9 +++ care/facility/static_data/medibase.py | 26 ------- config/api_router.py | 4 +- 5 files changed, 48 insertions(+), 80 deletions(-) create mode 100644 care/facility/migrations/0371_medibasemedicine_search_idx.py delete mode 100644 care/facility/static_data/medibase.py diff --git a/care/facility/api/viewsets/prescription.py b/care/facility/api/viewsets/prescription.py index ff1dfaa3a1..4def9d4e2e 100644 --- a/care/facility/api/viewsets/prescription.py +++ b/care/facility/api/viewsets/prescription.py @@ -1,19 +1,20 @@ -from re import IGNORECASE - from django.shortcuts import get_object_or_404 from django_filters import rest_framework as filters from drf_spectacular.utils import extend_schema from rest_framework import mixins, status from rest_framework.decorators import action +from rest_framework.filters import SearchFilter from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response -from rest_framework.viewsets import GenericViewSet, ViewSet +from rest_framework.viewsets import GenericViewSet from care.facility.api.serializers.prescription import ( + MedibaseMedicineSerializer, MedicineAdministrationSerializer, PrescriptionSerializer, ) from care.facility.models import ( + MedibaseMedicine, MedicineAdministration, Prescription, PrescriptionType, @@ -136,52 +137,14 @@ def administer(self, request, *args, **kwargs): # return Response({"success": True}, status=status.HTTP_200_OK) -class MedibaseViewSet(ViewSet): - permission_classes = (IsAuthenticated,) - - def serailize_data(self, objects): - result = [] - for object in objects: - if type(object) == tuple: - object = object[0] - result.append( - { - "id": object.external_id, - "name": object.name, - "type": object.type, - "generic": object.generic, - "company": object.company, - "contents": object.contents, - "cims_class": object.cims_class, - "atc_classification": object.atc_classification, - } - ) - return result - - def sort(self, query, results): - exact_matches = [] - partial_matches = [] - - for result in results: - if type(result) == tuple: - result = result[0] - words = result.searchable.lower().split() - if query in words: - exact_matches.append(result) - else: - partial_matches.append(result) - - return exact_matches + partial_matches - - def list(self, request): - from care.facility.static_data.medibase import MedibaseMedicineTable - - queryset = MedibaseMedicineTable - - if request.GET.get("query", False): - query = request.GET.get("query").strip().lower() - queryset = queryset.where( - searchable=queryset.re_match(r".*" + query + r".*", IGNORECASE) - ) - queryset = self.sort(query, queryset) - return Response(self.serailize_data(queryset[:15])) +class MedicineViewSet( + mixins.ListModelMixin, + mixins.RetrieveModelMixin, + GenericViewSet, +): + serializer_class = MedibaseMedicineSerializer + # permission_classes = (IsAuthenticated,) + queryset = MedibaseMedicine.objects.all() + lookup_field = "external_id" + filter_backends = (SearchFilter,) + search_fields = ("name", "generic", "company", "contents", "cims_class") diff --git a/care/facility/migrations/0371_medibasemedicine_search_idx.py b/care/facility/migrations/0371_medibasemedicine_search_idx.py new file mode 100644 index 0000000000..88520555bd --- /dev/null +++ b/care/facility/migrations/0371_medibasemedicine_search_idx.py @@ -0,0 +1,22 @@ +# Generated by Django 4.2.2 on 2023-07-06 11:18 + +import django.contrib.postgres.indexes +from django.contrib.postgres.operations import BtreeGinExtension +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("facility", "0370_merge_20230705_1500"), + ] + + operations = [ + BtreeGinExtension(), + migrations.AddIndex( + model_name="medibasemedicine", + index=django.contrib.postgres.indexes.GinIndex( + fields=["name", "generic", "company", "contents", "cims_class"], + name="search_idx", + ), + ), + ] diff --git a/care/facility/models/prescription.py b/care/facility/models/prescription.py index 93221302ad..66bab35a56 100644 --- a/care/facility/models/prescription.py +++ b/care/facility/models/prescription.py @@ -1,5 +1,6 @@ import enum +from django.contrib.postgres.indexes import GinIndex from django.core.exceptions import ValidationError from django.db import models from django.db.models import JSONField @@ -66,6 +67,14 @@ class MedibaseMedicine(BaseModel): def __str__(self): return " - ".join([self.name, self.generic, self.company]) + class Meta: + indexes = [ + GinIndex( + fields=["name", "generic", "company", "contents", "cims_class"], + name="search_idx", + ) + ] + class Prescription(BaseModel): consultation = models.ForeignKey( diff --git a/care/facility/static_data/medibase.py b/care/facility/static_data/medibase.py deleted file mode 100644 index 946360635f..0000000000 --- a/care/facility/static_data/medibase.py +++ /dev/null @@ -1,26 +0,0 @@ -from littletable import Table - -from care.facility.models.prescription import MedibaseMedicine - -MedibaseMedicineTable = Table("MedibaseMedicine") - -medibase_objects = MedibaseMedicine.objects.all() - -for obj in medibase_objects: - MedibaseMedicineTable.insert( - { - "id": obj.id, - "external_id": obj.external_id, - "name": obj.name, - "type": obj.type, - "generic": obj.generic or "", - "company": obj.company or "", - "contents": obj.contents or "", - "cims_class": obj.cims_class or "", - "atc_classification": obj.atc_classification or "", - "searchable": f"{obj.name} {obj.generic} {obj.company}", - } - ) - -MedibaseMedicineTable.create_index("id", unique=True) -MedibaseMedicineTable.create_search_index("searchable") diff --git a/config/api_router.py b/config/api_router.py index 918ae39302..6b809731f4 100644 --- a/config/api_router.py +++ b/config/api_router.py @@ -52,8 +52,8 @@ from care.facility.api.viewsets.patient_sample import PatientSampleViewSet from care.facility.api.viewsets.prescription import ( ConsultationPrescriptionViewSet, - MedibaseViewSet, MedicineAdministrationViewSet, + MedicineViewSet, ) from care.facility.api.viewsets.prescription_supplier import ( PrescriptionSupplierConsultationViewSet, @@ -200,7 +200,7 @@ consultation_nested_router.register( r"prescription_administration", MedicineAdministrationViewSet ) -router.register("medibase", MedibaseViewSet, basename="medibase") +router.register("medicine", MedicineViewSet) # HCX router.register("hcx/policy", PolicyViewSet) From 987a97001b47974e5ec2eac41b9afc83a7d15a7c Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Thu, 6 Jul 2023 16:54:14 +0530 Subject: [PATCH 02/10] uncomment permission_classes --- care/facility/api/viewsets/prescription.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/care/facility/api/viewsets/prescription.py b/care/facility/api/viewsets/prescription.py index 4def9d4e2e..5877df588d 100644 --- a/care/facility/api/viewsets/prescription.py +++ b/care/facility/api/viewsets/prescription.py @@ -143,7 +143,7 @@ class MedicineViewSet( GenericViewSet, ): serializer_class = MedibaseMedicineSerializer - # permission_classes = (IsAuthenticated,) + permission_classes = (IsAuthenticated,) queryset = MedibaseMedicine.objects.all() lookup_field = "external_id" filter_backends = (SearchFilter,) From 7759fceae072ed597c0e33ae49e59a9829992eea Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Thu, 6 Jul 2023 22:38:40 +0530 Subject: [PATCH 03/10] draft --- care/facility/api/viewsets/prescription.py | 10 +++++---- .../0371_medibasemedicine_search_idx.py | 22 ------------------- .../0371_medibasemedicine_search_vector.py | 18 +++++++++++++++ care/facility/models/prescription.py | 12 +++------- care/facility/tests/test_medibase_api.py | 12 +++++----- 5 files changed, 33 insertions(+), 41 deletions(-) delete mode 100644 care/facility/migrations/0371_medibasemedicine_search_idx.py create mode 100644 care/facility/migrations/0371_medibasemedicine_search_vector.py diff --git a/care/facility/api/viewsets/prescription.py b/care/facility/api/viewsets/prescription.py index 5877df588d..91690da11e 100644 --- a/care/facility/api/viewsets/prescription.py +++ b/care/facility/api/viewsets/prescription.py @@ -1,9 +1,9 @@ +from django.db.models.query import QuerySet from django.shortcuts import get_object_or_404 from django_filters import rest_framework as filters from drf_spectacular.utils import extend_schema from rest_framework import mixins, status from rest_framework.decorators import action -from rest_framework.filters import SearchFilter from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.viewsets import GenericViewSet @@ -143,8 +143,10 @@ class MedicineViewSet( GenericViewSet, ): serializer_class = MedibaseMedicineSerializer - permission_classes = (IsAuthenticated,) + # permission_classes = (IsAuthenticated,) queryset = MedibaseMedicine.objects.all() lookup_field = "external_id" - filter_backends = (SearchFilter,) - search_fields = ("name", "generic", "company", "contents", "cims_class") + + def get_queryset(self) -> QuerySet: + search_term = self.request.query_params.get("search_text", "") + return self.queryset.filter(search_vector=search_term) diff --git a/care/facility/migrations/0371_medibasemedicine_search_idx.py b/care/facility/migrations/0371_medibasemedicine_search_idx.py deleted file mode 100644 index 88520555bd..0000000000 --- a/care/facility/migrations/0371_medibasemedicine_search_idx.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 4.2.2 on 2023-07-06 11:18 - -import django.contrib.postgres.indexes -from django.contrib.postgres.operations import BtreeGinExtension -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [ - ("facility", "0370_merge_20230705_1500"), - ] - - operations = [ - BtreeGinExtension(), - migrations.AddIndex( - model_name="medibasemedicine", - index=django.contrib.postgres.indexes.GinIndex( - fields=["name", "generic", "company", "contents", "cims_class"], - name="search_idx", - ), - ), - ] diff --git a/care/facility/migrations/0371_medibasemedicine_search_vector.py b/care/facility/migrations/0371_medibasemedicine_search_vector.py new file mode 100644 index 0000000000..14eb1d2d3d --- /dev/null +++ b/care/facility/migrations/0371_medibasemedicine_search_vector.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.2 on 2023-07-06 17:00 + +import django.contrib.postgres.search +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("facility", "0370_merge_20230705_1500"), + ] + + operations = [ + migrations.AddField( + model_name="medibasemedicine", + name="search_vector", + field=django.contrib.postgres.search.SearchVectorField(null=True), + ), + ] diff --git a/care/facility/models/prescription.py b/care/facility/models/prescription.py index 66bab35a56..7dfb6029b1 100644 --- a/care/facility/models/prescription.py +++ b/care/facility/models/prescription.py @@ -1,6 +1,6 @@ import enum -from django.contrib.postgres.indexes import GinIndex +from django.contrib.postgres.search import SearchVectorField from django.core.exceptions import ValidationError from django.db import models from django.db.models import JSONField @@ -64,17 +64,11 @@ class MedibaseMedicine(BaseModel): cims_class = models.CharField(max_length=255, blank=True, null=True) atc_classification = models.TextField(blank=True, null=True) + search_vector = SearchVectorField(null=True) + def __str__(self): return " - ".join([self.name, self.generic, self.company]) - class Meta: - indexes = [ - GinIndex( - fields=["name", "generic", "company", "contents", "cims_class"], - name="search_idx", - ) - ] - class Prescription(BaseModel): consultation = models.ForeignKey( diff --git a/care/facility/tests/test_medibase_api.py b/care/facility/tests/test_medibase_api.py index 34ce13d7d5..3b3624ed24 100644 --- a/care/facility/tests/test_medibase_api.py +++ b/care/facility/tests/test_medibase_api.py @@ -5,21 +5,21 @@ class TestMedibaseApi(TestBase): def get_url(self, query=None): - return f"/api/v1/medibase/?query={query}" + return f"/api/v1/medicine/?search_text={query}" def test_search_by_name_exact_word(self): response = self.client.get(self.get_url(query="dolo")) self.assertEquals(response.status_code, status.HTTP_200_OK) - self.assertEquals(response.data[0]["name"], "DOLO") + self.assertEquals(response.data.results[0]["name"], "DOLO") def test_search_by_generic_exact_word(self): response = self.client.get(self.get_url(query="pAraCetAmoL")) self.assertEquals(response.status_code, status.HTTP_200_OK) - self.assertEquals(response.data[0]["generic"], "paracetamol") + self.assertEquals(response.data.results[0]["generic"], "paracetamol") def test_search_by_name_and_generic_exact_word(self): response = self.client.get(self.get_url(query="panadol paracetamol")) self.assertEquals(response.status_code, status.HTTP_200_OK) - self.assertEquals(response.data[0]["name"], "PANADOL") - self.assertEquals(response.data[0]["generic"], "paracetamol") - self.assertEquals(response.data[0]["company"], "GSK") + self.assertEquals(response.data.results[0]["name"], "PANADOL") + self.assertEquals(response.data.results[0]["generic"], "paracetamol") + self.assertEquals(response.data.results[0]["company"], "GSK") From e9de81d71fa314d7036a304974634fe89e5984cd Mon Sep 17 00:00:00 2001 From: Aakash Singh Date: Thu, 6 Jul 2023 22:59:08 +0530 Subject: [PATCH 04/10] add search rank --- care/facility/api/viewsets/prescription.py | 24 ++++++-- .../migrations/0371_auto_20230706_2228.py | 57 +++++++++++++++++++ .../0371_medibasemedicine_search_vector.py | 18 ------ care/facility/models/prescription.py | 6 ++ 4 files changed, 83 insertions(+), 22 deletions(-) create mode 100644 care/facility/migrations/0371_auto_20230706_2228.py delete mode 100644 care/facility/migrations/0371_medibasemedicine_search_vector.py diff --git a/care/facility/api/viewsets/prescription.py b/care/facility/api/viewsets/prescription.py index 91690da11e..54eb29b8cf 100644 --- a/care/facility/api/viewsets/prescription.py +++ b/care/facility/api/viewsets/prescription.py @@ -1,6 +1,8 @@ -from django.db.models.query import QuerySet +from django.contrib.postgres.search import SearchQuery, SearchRank +from django.db.models.query import F from django.shortcuts import get_object_or_404 from django_filters import rest_framework as filters +from drf_spectacular.openapi import OpenApiParameter from drf_spectacular.utils import extend_schema from rest_framework import mixins, status from rest_framework.decorators import action @@ -147,6 +149,20 @@ class MedicineViewSet( queryset = MedibaseMedicine.objects.all() lookup_field = "external_id" - def get_queryset(self) -> QuerySet: - search_term = self.request.query_params.get("search_text", "") - return self.queryset.filter(search_vector=search_term) + @extend_schema( + parameters=(OpenApiParameter(name="search", required=False, type=str),) + ) + def list(self, request, *args, **kwargs): + if "search" in request.query_params: + rank = SearchRank( + F("search_vector"), SearchQuery(request.query_params["search"]) + ) + queryset = self.queryset.annotate(rank=rank).order_by("-rank") + + page = self.paginate_queryset(queryset) + if page is not None: + serializer = self.get_serializer(page, many=True) + return self.get_paginated_response(serializer.data) + + serializer = self.get_serializer(queryset, many=True) + return Response(serializer.data) diff --git a/care/facility/migrations/0371_auto_20230706_2228.py b/care/facility/migrations/0371_auto_20230706_2228.py new file mode 100644 index 0000000000..089b24887b --- /dev/null +++ b/care/facility/migrations/0371_auto_20230706_2228.py @@ -0,0 +1,57 @@ +# Generated by Django 4.2.2 on 2023-07-06 16:58 + +import django.contrib.postgres.indexes +import django.contrib.postgres.search +from django.contrib.postgres.search import SearchVector +from django.db import migrations + + +def compute_search_vector(apps, schema_editor): + MedibaseMedicine = apps.get_model("facility", "MedibaseMedicine") + MedibaseMedicine.objects.update( + search_vector=( + SearchVector("name", weight="A") + + SearchVector("generic", weight="B") + + SearchVector("company", weight="C") + + SearchVector("cims_class", weight="D") + + SearchVector("contents", weight="D") + ) + ) + + +class Migration(migrations.Migration): + dependencies = [ + ("facility", "0370_merge_20230705_1500"), + ] + operations = [ + migrations.AddField( + model_name="medibasemedicine", + name="search_vector", + field=django.contrib.postgres.search.SearchVectorField(null=True), + ), + migrations.AddIndex( + model_name="medibasemedicine", + index=django.contrib.postgres.indexes.GinIndex( + fields=["search_vector"], name="medibase_search_vector_idx" + ), + ), + migrations.RunSQL( + sql=""" + CREATE TRIGGER medibase_search_vector_trigger + BEFORE INSERT OR UPDATE OF name, generic, company, cims_class, contents, search_vector + ON facility_medibasemedicine + FOR EACH ROW EXECUTE PROCEDURE + tsvector_update_trigger( + search_vector, 'pg_catalog.english', name, generic, company, cims_class, contents + ); + UPDATE facility_medibasemedicine SET search_vector = NULL; + """, + reverse_sql=""" + DROP TRIGGER IF EXISTS medibase_search_vector_trigger + ON facility_medibasemedicine; + """, + ), + migrations.RunPython( + compute_search_vector, reverse_code=migrations.RunPython.noop + ), + ] diff --git a/care/facility/migrations/0371_medibasemedicine_search_vector.py b/care/facility/migrations/0371_medibasemedicine_search_vector.py deleted file mode 100644 index 14eb1d2d3d..0000000000 --- a/care/facility/migrations/0371_medibasemedicine_search_vector.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 4.2.2 on 2023-07-06 17:00 - -import django.contrib.postgres.search -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [ - ("facility", "0370_merge_20230705_1500"), - ] - - operations = [ - migrations.AddField( - model_name="medibasemedicine", - name="search_vector", - field=django.contrib.postgres.search.SearchVectorField(null=True), - ), - ] diff --git a/care/facility/models/prescription.py b/care/facility/models/prescription.py index 7dfb6029b1..e89a172661 100644 --- a/care/facility/models/prescription.py +++ b/care/facility/models/prescription.py @@ -1,5 +1,6 @@ import enum +from django.contrib.postgres.indexes import GinIndex from django.contrib.postgres.search import SearchVectorField from django.core.exceptions import ValidationError from django.db import models @@ -69,6 +70,11 @@ class MedibaseMedicine(BaseModel): def __str__(self): return " - ".join([self.name, self.generic, self.company]) + class Meta: + indexes = ( + GinIndex(fields=["search_vector"], name="medibase_search_vector_idx"), + ) + class Prescription(BaseModel): consultation = models.ForeignKey( From 73c16e379f0a9d6109796d8677db2fd695da87fd Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Thu, 6 Jul 2023 23:52:56 +0530 Subject: [PATCH 05/10] give more weight to generic; update tests and fix no 500 on no query --- care/facility/api/viewsets/prescription.py | 11 +++++------ care/facility/migrations/0371_auto_20230706_2228.py | 4 ++-- care/facility/tests/test_medibase_api.py | 12 ++++++------ 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/care/facility/api/viewsets/prescription.py b/care/facility/api/viewsets/prescription.py index 54eb29b8cf..5d75ac44c2 100644 --- a/care/facility/api/viewsets/prescription.py +++ b/care/facility/api/viewsets/prescription.py @@ -145,7 +145,7 @@ class MedicineViewSet( GenericViewSet, ): serializer_class = MedibaseMedicineSerializer - # permission_classes = (IsAuthenticated,) + permission_classes = (IsAuthenticated,) queryset = MedibaseMedicine.objects.all() lookup_field = "external_id" @@ -153,11 +153,10 @@ class MedicineViewSet( parameters=(OpenApiParameter(name="search", required=False, type=str),) ) def list(self, request, *args, **kwargs): - if "search" in request.query_params: - rank = SearchRank( - F("search_vector"), SearchQuery(request.query_params["search"]) - ) - queryset = self.queryset.annotate(rank=rank).order_by("-rank") + rank = SearchRank( + F("search_vector"), SearchQuery(request.query_params.get("search", "")) + ) + queryset = self.queryset.annotate(rank=rank).order_by("-rank") page = self.paginate_queryset(queryset) if page is not None: diff --git a/care/facility/migrations/0371_auto_20230706_2228.py b/care/facility/migrations/0371_auto_20230706_2228.py index 089b24887b..85de452c6a 100644 --- a/care/facility/migrations/0371_auto_20230706_2228.py +++ b/care/facility/migrations/0371_auto_20230706_2228.py @@ -10,8 +10,8 @@ def compute_search_vector(apps, schema_editor): MedibaseMedicine = apps.get_model("facility", "MedibaseMedicine") MedibaseMedicine.objects.update( search_vector=( - SearchVector("name", weight="A") - + SearchVector("generic", weight="B") + SearchVector("generic", weight="A") + + SearchVector("name", weight="B") + SearchVector("company", weight="C") + SearchVector("cims_class", weight="D") + SearchVector("contents", weight="D") diff --git a/care/facility/tests/test_medibase_api.py b/care/facility/tests/test_medibase_api.py index 3b3624ed24..564554c3a4 100644 --- a/care/facility/tests/test_medibase_api.py +++ b/care/facility/tests/test_medibase_api.py @@ -5,21 +5,21 @@ class TestMedibaseApi(TestBase): def get_url(self, query=None): - return f"/api/v1/medicine/?search_text={query}" + return f"/api/v1/medicine/?search={query}" def test_search_by_name_exact_word(self): response = self.client.get(self.get_url(query="dolo")) self.assertEquals(response.status_code, status.HTTP_200_OK) - self.assertEquals(response.data.results[0]["name"], "DOLO") + self.assertEquals(response.json()["results"][0]["name"], "DOLO") def test_search_by_generic_exact_word(self): response = self.client.get(self.get_url(query="pAraCetAmoL")) self.assertEquals(response.status_code, status.HTTP_200_OK) - self.assertEquals(response.data.results[0]["generic"], "paracetamol") + self.assertEquals(response.json()["results"][0]["generic"], "paracetamol") def test_search_by_name_and_generic_exact_word(self): response = self.client.get(self.get_url(query="panadol paracetamol")) self.assertEquals(response.status_code, status.HTTP_200_OK) - self.assertEquals(response.data.results[0]["name"], "PANADOL") - self.assertEquals(response.data.results[0]["generic"], "paracetamol") - self.assertEquals(response.data.results[0]["company"], "GSK") + self.assertEquals(response.json()["results"][0]["name"], "PANADOL") + self.assertEquals(response.json()["results"][0]["generic"], "paracetamol") + self.assertEquals(response.json()["results"][0]["company"], "GSK") From 9dd2c3a4c77446225c6bd1069ac80e01f23ae753 Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Thu, 6 Jul 2023 23:54:49 +0530 Subject: [PATCH 06/10] fix swagger --- care/facility/api/viewsets/prescription.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/care/facility/api/viewsets/prescription.py b/care/facility/api/viewsets/prescription.py index 5d75ac44c2..b1f48ca614 100644 --- a/care/facility/api/viewsets/prescription.py +++ b/care/facility/api/viewsets/prescription.py @@ -150,7 +150,9 @@ class MedicineViewSet( lookup_field = "external_id" @extend_schema( - parameters=(OpenApiParameter(name="search", required=False, type=str),) + parameters=[ + OpenApiParameter(name="search", required=False, type=str), + ] ) def list(self, request, *args, **kwargs): rank = SearchRank( From e5adcdea0de6ee89b6271df5efb64f245a6c1d90 Mon Sep 17 00:00:00 2001 From: Aakash Singh Date: Fri, 7 Jul 2023 00:47:31 +0530 Subject: [PATCH 07/10] add BtreeGinExtension and add weights to trigger --- .../migrations/0371_auto_20230706_2228.py | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/care/facility/migrations/0371_auto_20230706_2228.py b/care/facility/migrations/0371_auto_20230706_2228.py index 85de452c6a..3a43d9fa8a 100644 --- a/care/facility/migrations/0371_auto_20230706_2228.py +++ b/care/facility/migrations/0371_auto_20230706_2228.py @@ -2,6 +2,7 @@ import django.contrib.postgres.indexes import django.contrib.postgres.search +from django.contrib.postgres.operations import BtreeGinExtension from django.contrib.postgres.search import SearchVector from django.db import migrations @@ -11,7 +12,7 @@ def compute_search_vector(apps, schema_editor): MedibaseMedicine.objects.update( search_vector=( SearchVector("generic", weight="A") - + SearchVector("name", weight="B") + + SearchVector("name", weight="A") + SearchVector("company", weight="C") + SearchVector("cims_class", weight="D") + SearchVector("contents", weight="D") @@ -24,6 +25,7 @@ class Migration(migrations.Migration): ("facility", "0370_merge_20230705_1500"), ] operations = [ + BtreeGinExtension(), migrations.AddField( model_name="medibasemedicine", name="search_vector", @@ -35,15 +37,28 @@ class Migration(migrations.Migration): fields=["search_vector"], name="medibase_search_vector_idx" ), ), + migrations.RunPython( + compute_search_vector, reverse_code=migrations.RunPython.noop + ), migrations.RunSQL( sql=""" + CREATE OR REPLACE FUNCTION medibase_search_vector_trigger() RETURNS trigger AS $$ + BEGIN + NEW.search_vector := + setweight(to_tsvector('pg_catalog.english', COALESCE(NEW.name, '')), 'A') || + setweight(to_tsvector('pg_catalog.english', COALESCE(NEW.generic, '')), 'A') || + setweight(to_tsvector('pg_catalog.english', COALESCE(NEW.company, '')), 'C') || + setweight(to_tsvector('pg_catalog.english', COALESCE(NEW.cims_class, '')), 'D') || + setweight(to_tsvector('pg_catalog.english', COALESCE(NEW.contents, '')), 'D'); + RETURN NEW; + END + $$ LANGUAGE plpgsql; + CREATE TRIGGER medibase_search_vector_trigger BEFORE INSERT OR UPDATE OF name, generic, company, cims_class, contents, search_vector ON facility_medibasemedicine - FOR EACH ROW EXECUTE PROCEDURE - tsvector_update_trigger( - search_vector, 'pg_catalog.english', name, generic, company, cims_class, contents - ); + FOR EACH ROW EXECUTE FUNCTION medibase_search_vector_trigger(); + UPDATE facility_medibasemedicine SET search_vector = NULL; """, reverse_sql=""" @@ -51,7 +66,4 @@ class Migration(migrations.Migration): ON facility_medibasemedicine; """, ), - migrations.RunPython( - compute_search_vector, reverse_code=migrations.RunPython.noop - ), ] From 4face4cc1f3f195ff2b2209ba1a41cc0d4c5e716 Mon Sep 17 00:00:00 2001 From: Aakash Singh Date: Fri, 7 Jul 2023 01:00:55 +0530 Subject: [PATCH 08/10] avoid ranking when no query is given --- care/facility/api/viewsets/prescription.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/care/facility/api/viewsets/prescription.py b/care/facility/api/viewsets/prescription.py index b1f48ca614..fd93938b8a 100644 --- a/care/facility/api/viewsets/prescription.py +++ b/care/facility/api/viewsets/prescription.py @@ -151,14 +151,20 @@ class MedicineViewSet( @extend_schema( parameters=[ - OpenApiParameter(name="search", required=False, type=str), + OpenApiParameter( + name="search", + required=False, + type=str, + description="Search for medicine by name, brand, composition, etc.", + ), ] ) def list(self, request, *args, **kwargs): - rank = SearchRank( - F("search_vector"), SearchQuery(request.query_params.get("search", "")) - ) - queryset = self.queryset.annotate(rank=rank).order_by("-rank") + queryset = self.get_queryset() + if search := request.query_params.get("search", None): + queryset = self.queryset.annotate( + rank=SearchRank(F("search_vector"), SearchQuery(search)) + ).order_by("-rank") page = self.paginate_queryset(queryset) if page is not None: From 198ce9c41d6c396cbdd7143c906b0ac1d3e706fe Mon Sep 17 00:00:00 2001 From: Aakash Singh Date: Fri, 7 Jul 2023 01:03:04 +0530 Subject: [PATCH 09/10] hide non required fields --- care/facility/api/serializers/prescription.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/care/facility/api/serializers/prescription.py b/care/facility/api/serializers/prescription.py index 89f5f61d9e..4b40ff8245 100644 --- a/care/facility/api/serializers/prescription.py +++ b/care/facility/api/serializers/prescription.py @@ -10,11 +10,12 @@ class MedibaseMedicineSerializer(serializers.ModelSerializer): class Meta: model = MedibaseMedicine - exclude = ("deleted",) - read_only_fields = ( - "external_id", + exclude = ( "created_date", "modified_date", + "deleted", + "search_vector", + "external_id", ) From 0c657bd6c5ee99f6713c301c3f0785083d6b9e3a Mon Sep 17 00:00:00 2001 From: Aakash Singh Date: Fri, 7 Jul 2023 01:24:29 +0530 Subject: [PATCH 10/10] redo the migrations --- ..._20230705_1500.py => 0368_merge_20230707_0121.py} | 4 ++-- ... => 0369_medibase_search_vectors_and_triggers.py} | 2 +- ...0366_auto_20230627_1806_0368_populate_medibase.py | 12 ------------ ...opulate_medibase.py => 0370_populate_medibase.py} | 2 +- 4 files changed, 4 insertions(+), 16 deletions(-) rename care/facility/migrations/{0370_merge_20230705_1500.py => 0368_merge_20230707_0121.py} (56%) rename care/facility/migrations/{0371_auto_20230706_2228.py => 0369_medibase_search_vectors_and_triggers.py} (98%) delete mode 100644 care/facility/migrations/0369_merge_0366_auto_20230627_1806_0368_populate_medibase.py rename care/facility/migrations/{0368_populate_medibase.py => 0370_populate_medibase.py} (85%) diff --git a/care/facility/migrations/0370_merge_20230705_1500.py b/care/facility/migrations/0368_merge_20230707_0121.py similarity index 56% rename from care/facility/migrations/0370_merge_20230705_1500.py rename to care/facility/migrations/0368_merge_20230707_0121.py index df6bb1f835..ad84c02ab6 100644 --- a/care/facility/migrations/0370_merge_20230705_1500.py +++ b/care/facility/migrations/0368_merge_20230707_0121.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.2 on 2023-07-05 09:30 +# Generated by Django 4.2.2 on 2023-07-06 19:51 from django.db import migrations @@ -6,7 +6,7 @@ class Migration(migrations.Migration): dependencies = [ ("facility", "0367_auto_20230704_1646"), - ("facility", "0369_merge_0366_auto_20230627_1806_0368_populate_medibase"), + ("facility", "0367_prescription_medicine_and_more"), ] operations = [] diff --git a/care/facility/migrations/0371_auto_20230706_2228.py b/care/facility/migrations/0369_medibase_search_vectors_and_triggers.py similarity index 98% rename from care/facility/migrations/0371_auto_20230706_2228.py rename to care/facility/migrations/0369_medibase_search_vectors_and_triggers.py index 3a43d9fa8a..1906e39d3d 100644 --- a/care/facility/migrations/0371_auto_20230706_2228.py +++ b/care/facility/migrations/0369_medibase_search_vectors_and_triggers.py @@ -22,7 +22,7 @@ def compute_search_vector(apps, schema_editor): class Migration(migrations.Migration): dependencies = [ - ("facility", "0370_merge_20230705_1500"), + ("facility", "0368_merge_20230707_0121"), ] operations = [ BtreeGinExtension(), diff --git a/care/facility/migrations/0369_merge_0366_auto_20230627_1806_0368_populate_medibase.py b/care/facility/migrations/0369_merge_0366_auto_20230627_1806_0368_populate_medibase.py deleted file mode 100644 index d7eb5a3524..0000000000 --- a/care/facility/migrations/0369_merge_0366_auto_20230627_1806_0368_populate_medibase.py +++ /dev/null @@ -1,12 +0,0 @@ -# Generated by Django 4.2.2 on 2023-06-30 02:01 - -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [ - ("facility", "0366_auto_20230627_1806"), - ("facility", "0368_populate_medibase"), - ] - - operations = [] diff --git a/care/facility/migrations/0368_populate_medibase.py b/care/facility/migrations/0370_populate_medibase.py similarity index 85% rename from care/facility/migrations/0368_populate_medibase.py rename to care/facility/migrations/0370_populate_medibase.py index 77f76d5e82..99cfd51c4f 100644 --- a/care/facility/migrations/0368_populate_medibase.py +++ b/care/facility/migrations/0370_populate_medibase.py @@ -6,7 +6,7 @@ class Migration(migrations.Migration): dependencies = [ - ("facility", "0367_prescription_medicine_and_more"), + ("facility", "0369_medibase_search_vectors_and_triggers"), ] operations = [