Skip to content
Snippets Groups Projects
Commit 82141a96 authored by Petr.Kalashnikov's avatar Petr.Kalashnikov
Browse files

Merge branch 'greg292' into 'master'

GREG-292: search for person via v1-api

See merge request !348
parents cf0e869c a5916895
No related branches found
No related tags found
1 merge request!348GREG-292: search for person via v1-api
Pipeline #161511 passed
......@@ -36,6 +36,7 @@ backend test:
stage: tests and linting
script:
- make test
coverage: /^TOTAL.*\s+(\d+\%)$/
artifacts:
reports:
coverage_report:
......
......@@ -24,3 +24,18 @@ class PersonSerializer(serializers.ModelSerializer):
"roles",
"consents",
]
class PersonSearchSerializer(serializers.ModelSerializer):
person_id = serializers.CharField(source="id")
first = serializers.CharField(source="first_name")
last = serializers.CharField(source="last_name")
class Meta:
model = Person
fields = [
"person_id",
"first",
"last",
"date_of_birth",
]
......@@ -17,6 +17,7 @@ from greg.api.views.sponsor import (
SponsorGuestsViewSet,
SponsorOrgunitLinkView,
)
from greg.api.views.person import PersonSearchSet
router = DefaultRouter(trailing_slash=False)
router.register(r"persons", PersonViewSet, basename="person")
......@@ -74,4 +75,9 @@ urlpatterns += [
SponsorOrgunitLinkView.as_view({"post": "create", "delete": "destroy"}),
name="sponsor_orgunit-detail",
),
re_path(
"person-search/",
PersonSearchSet.as_view({"get": "list"}),
name="person_search-list",
),
]
......@@ -15,10 +15,12 @@ from greg.api.pagination import PrimaryKeyCursorPagination
from greg.api.serializers.consent import ConsentSerializerBrief
from greg.api.serializers.person import (
PersonSerializer,
PersonSearchSerializer,
IdentitySerializer,
)
from greg.api.serializers.role import RoleSerializer, RoleWriteSerializer
from greg.models import Person, Role, Identity, Consent, ConsentChoice, ConsentType
from search_tools.person_search import person_by_string_query
class PersonViewSet(viewsets.ModelViewSet):
......@@ -58,6 +60,38 @@ class PersonViewSet(viewsets.ModelViewSet):
return super().list(request, *args, **kwargs)
class PersonSearchSet(viewsets.ModelViewSet):
"""Search for persons using name, email, phone number and birth date"""
permission_classes = (permissions.IsAdminUser,)
serializer_class = PersonSearchSerializer
def list(self, request, *args, **kwargs):
if "q" not in self.request.query_params:
return Response(
status=status.HTTP_400_BAD_REQUEST,
data={
"code": "no_search_term",
"message": "No search query parameter given",
},
)
if len(self.request.query_params["q"]) > 50:
return Response(
status=status.HTTP_400_BAD_REQUEST,
data={
"code": "search_term_too_large",
"message": "Search term is too large",
},
)
hits = self.get_hits()
return Response(hits)
def get_hits(self):
return person_by_string_query(self.request)
class RoleViewSet(viewsets.ModelViewSet):
"""Person role API"""
......
......@@ -276,6 +276,19 @@ def test_identity_add_duplicate_fails(client, person_foo, person_bar):
assert response.json() == {"non_field_errors": ["Identity already exists"]}
@pytest.mark.django_db
def test_person_search_list(client, person_foo):
response = client.get(reverse("v1:person_search-list") + "?q=Foo")
data = response.json()
assert len(data) == 1
person = response.json()[0]
birth_date = person_foo.date_of_birth
assert person["first"] == person_foo.first_name
assert person["last"] == person_foo.last_name
assert person["date_of_birth"] == birth_date.strftime("%Y-%m-%d")
@pytest.mark.django_db
def test_add_invalid_type(client, person):
data = {
......
from django.db.models import Q
from rest_framework import mixins
from rest_framework import status
from rest_framework.authentication import SessionAuthentication, BasicAuthentication
......@@ -16,6 +15,7 @@ from gregui.api.serializers.guest import (
)
from gregui.api.serializers.identity import IdentityDuplicateError
from gregui.models import GregUserProfile
from search_tools.person_search import person_by_string_query
class PersonViewSet(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, GenericViewSet):
......@@ -106,57 +106,7 @@ class PersonSearchViewSet(GenericViewSet):
return Response(hits)
def get_hits(self):
search = self.request.query_params["q"]
split_search = search.split()
words_joined = "|".join(map(str, split_search))
# Create a regex with the terms in the search or-ed together. This will trigger a match
# if one of the fields that are being searched contains one of the terms
search_regex = r"^(%s)" % words_joined
hits = []
# First look for hits on name and birth date
persons = Person.objects.filter(
Q(first_name__iregex=search_regex)
| Q(last_name__iregex=search_regex)
| Q(date_of_birth__iregex=search_regex)
)[:10]
included_persons = []
for person in persons:
hits.append(
{"pid": person.id, "first": person.first_name, "last": person.last_name}
)
included_persons.append(person.id)
if len(hits) == 10:
# Max number of hits, no need to search more
return hits
# Look for hits in e-mail and mobile phone
identities = Identity.objects.filter(
value__iregex=search_regex,
type__in=[
Identity.IdentityType.PRIVATE_EMAIL,
Identity.IdentityType.PRIVATE_MOBILE_NUMBER,
],
)[: (10 - len(hits))]
for identity in identities:
if identity.person_id in included_persons:
continue
hits.append(
{
"pid": identity.person_id,
"first": identity.person.first_name,
"last": identity.person.last_name,
"value": identity.value,
"type": identity.type,
}
)
return hits
return person_by_string_query(self.request)
class GuestInfoViewSet(mixins.ListModelMixin, GenericViewSet):
......
This diff is collapsed.
from django.db.models import Q
from greg.models import Identity, Person
def person_by_string_query(request):
search = request.query_params["q"]
if "gregui" in request.version:
id_field_name = "pid"
else:
id_field_name = "person_id"
split_search = search.split()
words_joined = "|".join(map(str, split_search))
# Create a regex with the terms in the search or-ed together. This will trigger a match
# if one of the fields that are being searched contains one of the terms
search_regex = r"^(%s)" % words_joined
hits = []
# First look for hits on name and birth date
persons = Person.objects.filter(
Q(first_name__iregex=search_regex)
| Q(last_name__iregex=search_regex)
| Q(date_of_birth__iregex=search_regex)
)[:10]
included_persons = []
for person in persons:
hits.append(
{
id_field_name: person.id,
"first": person.first_name,
"last": person.last_name,
"date_of_birth": person.date_of_birth,
}
)
included_persons.append(person.id)
if len(hits) == 10:
# Max number of hits, no need to search more
return hits
# Look for hits in e-mail and mobile phone
identities = Identity.objects.filter(
value__iregex=search_regex,
type__in=[
Identity.IdentityType.PRIVATE_EMAIL,
Identity.IdentityType.PRIVATE_MOBILE_NUMBER,
],
)[: (10 - len(hits))]
for identity in identities:
if identity.person_id in included_persons:
continue
hits.append(
{
id_field_name: identity.person_id,
"first": identity.person.first_name,
"last": identity.person.last_name,
"value": identity.value,
"type": identity.type,
}
)
return hits
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment