From 402ec28ff5042974128013a1e520c843e6877571 Mon Sep 17 00:00:00 2001 From: Tanner Collin Date: Sat, 11 Jan 2020 00:47:13 +0000 Subject: [PATCH] Add member search api route --- apiserver/apiserver/api/models.py | 2 ++ apiserver/apiserver/api/serializers.py | 8 +++++ apiserver/apiserver/api/views.py | 49 ++++++++++++++++++++++++++ apiserver/apiserver/urls.py | 1 + apiserver/import_old_portal.py | 4 +++ apiserver/requirements.txt | 12 +++++++ 6 files changed, 76 insertions(+) diff --git a/apiserver/apiserver/api/models.py b/apiserver/apiserver/api/models.py index a3463e4..7d6f465 100644 --- a/apiserver/apiserver/api/models.py +++ b/apiserver/apiserver/api/models.py @@ -12,7 +12,9 @@ class Member(models.Model): set_details = models.BooleanField(default=False) preferred_name = models.CharField(max_length=32, blank=True) + status = models.CharField(max_length=32, blank=True) phone = models.CharField(max_length=32, blank=True) + expire_date = models.DateField(default=date.today, blank=True, null=True) current_start_date = models.DateField(default=date.today, blank=True, null=True) application_date = models.DateField(default=date.today, blank=True, null=True) vetted_date = models.DateField(blank=True, null=True) diff --git a/apiserver/apiserver/api/serializers.py b/apiserver/apiserver/api/serializers.py index 8d642f1..17a924c 100644 --- a/apiserver/apiserver/api/serializers.py +++ b/apiserver/apiserver/api/serializers.py @@ -22,12 +22,20 @@ class UserSerializer(serializers.ModelSerializer): depth = 1 +# member viewing member list or other member +class OtherMemberSerializer(serializers.ModelSerializer): + class Meta: + model = models.Member + fields = ['preferred_name', 'last_name', 'status', 'current_start_date'] + +# member viewing himself class MemberSerializer(serializers.ModelSerializer): class Meta: model = models.Member fields = '__all__' read_only_fields = ['user', 'application_date', 'current_start_date', 'vetted_date', 'monthly_fees', 'old_member_id'] +# adming viewing member class AdminMemberSerializer(serializers.ModelSerializer): class Meta: model = models.Member diff --git a/apiserver/apiserver/api/views.py b/apiserver/apiserver/api/views.py index f3cf19e..cc75e49 100644 --- a/apiserver/apiserver/api/views.py +++ b/apiserver/apiserver/api/views.py @@ -3,6 +3,8 @@ from django.db.models import Max from rest_framework import viewsets, views, permissions from rest_framework.response import Response from rest_auth.registration.views import RegisterView +from fuzzywuzzy import fuzz, process +from collections import OrderedDict from . import models, serializers @@ -16,6 +18,53 @@ class UserViewSet(viewsets.ModelViewSet): serializer_class = serializers.UserSerializer +search_strings = {} +def gen_search_strings(): + for m in models.Member.objects.all(): + string = '{} {} {} {}'.format( + m.preferred_name, + m.last_name, + m.first_name, + m.last_name, + ).lower() + search_strings[string] = m.id +gen_search_strings() + +class SearchViewSet(viewsets.ReadOnlyModelViewSet): + permission_classes = [AllowMetadata | permissions.IsAuthenticated] + serializer_class = serializers.OtherMemberSerializer + + def get_queryset(self): + NUM_SEARCH_RESULTS = 10 + + queryset = models.Member.objects.all() + params = self.request.query_params + + if 'q' in params and len(params['q']) >= 3: + search = params['q'].lower() + choices = search_strings.keys() + + # get exact starts with matches + results = [x for x in choices if x.startswith(search)] + # then get exact substring matches + results += [x for x in choices if search in x] + # then get fuzzy matches + fuzzy_results = process.extract(search, choices, limit=NUM_SEARCH_RESULTS, scorer=fuzz.token_set_ratio) + results += [x[0] for x in fuzzy_results] + + # remove dupes + results = list(OrderedDict.fromkeys(results)) + + result_ids = [search_strings[x] for x in results] + result_objects = [queryset.get(id=x) for x in result_ids] + + queryset = result_objects + else: + queryset = queryset.order_by('-vetted_date') + + return queryset[:NUM_SEARCH_RESULTS] + + class MemberViewSet(viewsets.ModelViewSet): permission_classes = [AllowMetadata | permissions.IsAuthenticated] http_method_names = ['options', 'head', 'get', 'put', 'patch'] diff --git a/apiserver/apiserver/urls.py b/apiserver/apiserver/urls.py index cc87ad0..b876bdf 100644 --- a/apiserver/apiserver/urls.py +++ b/apiserver/apiserver/urls.py @@ -10,6 +10,7 @@ router.register(r'users', views.UserViewSet) router.register(r'members', views.MemberViewSet, basename='member') router.register(r'courses', views.CourseViewSet, basename='course') router.register(r'sessions', views.SessionViewSet, basename='session') +router.register(r'search', views.SearchViewSet, basename='search') #router.register(r'me', views.FullMemberView, basename='fullmember') #router.register(r'registration', views.RegistrationViewSet, basename='register') diff --git a/apiserver/import_old_portal.py b/apiserver/import_old_portal.py index 883c7b4..b60d227 100755 --- a/apiserver/import_old_portal.py +++ b/apiserver/import_old_portal.py @@ -6,8 +6,12 @@ from apiserver.api import models, old_models MEMBER_FIELDS = [ 'id', + 'first_name', + 'last_name', 'preferred_name', + 'status', 'phone', + 'expire_date', 'current_start_date', 'application_date', 'vetted_date', diff --git a/apiserver/requirements.txt b/apiserver/requirements.txt index 3ed66bc..ed354f8 100644 --- a/apiserver/requirements.txt +++ b/apiserver/requirements.txt @@ -1,11 +1,23 @@ argon2-cffi==19.2.0 asgiref==3.2.3 +certifi==2019.11.28 cffi==1.13.2 +chardet==3.0.4 +defusedxml==0.6.0 Django==3.0.2 +django-allauth==0.41.0 django-rest-auth==0.9.5 djangorestframework==3.11.0 +fuzzywuzzy==0.17.0 +idna==2.8 +oauthlib==3.1.0 pkg-resources==0.0.0 pycparser==2.19 +python-Levenshtein==0.12.0 +python3-openid==3.1.0 pytz==2019.3 +requests==2.22.0 +requests-oauthlib==1.3.0 six==1.13.0 sqlparse==0.3.0 +urllib3==1.25.7