You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
147 lines
4.9 KiB
147 lines
4.9 KiB
from django.contrib.auth.models import User, Group |
|
from django.db.models import Max |
|
from rest_framework import viewsets, views, mixins, generics, exceptions |
|
from rest_framework.permissions import BasePermission, IsAuthenticated |
|
from rest_framework.response import Response |
|
from rest_auth.views import PasswordChangeView |
|
from rest_auth.registration.views import RegisterView |
|
from fuzzywuzzy import fuzz, process |
|
from collections import OrderedDict |
|
|
|
from . import models, serializers |
|
|
|
class AllowMetadata(BasePermission): |
|
def has_permission(self, request, view): |
|
return request.method in ['OPTIONS', 'HEAD'] |
|
|
|
def is_admin_director(user): |
|
return user.is_staff or user.member.is_director or user.member.is_staff |
|
|
|
class IsOwnerOrAdmin(BasePermission): |
|
def has_object_permission(self, request, view, obj): |
|
return obj.user == request.user or is_admin_director(request.user) |
|
|
|
|
|
class RetrieveUpdateViewSet( |
|
viewsets.GenericViewSet, |
|
mixins.RetrieveModelMixin, |
|
mixins.UpdateModelMixin): |
|
def list(self, request): |
|
raise exceptions.PermissionDenied |
|
|
|
|
|
search_strings = {} |
|
def gen_search_strings(): |
|
for m in models.Member.objects.all(): |
|
string = '{} {}'.format( |
|
m.preferred_name, |
|
m.last_name, |
|
).lower() |
|
search_strings[string] = m.id |
|
|
|
NUM_SEARCH_RESULTS = 10 |
|
class SearchViewSet(viewsets.GenericViewSet, mixins.RetrieveModelMixin): |
|
permission_classes = [AllowMetadata | IsAuthenticated] |
|
|
|
def get_serializer_class(self): |
|
if is_admin_director(self.request.user) and self.action == 'retrieve': |
|
return serializers.AdminSearchSerializer |
|
else: |
|
return serializers.SearchSerializer |
|
|
|
def get_queryset(self): |
|
queryset = models.Member.objects.all() |
|
search = self.request.data.get('q', '').lower() |
|
|
|
if not search_strings: |
|
gen_search_strings() # init cache |
|
|
|
if len(search): |
|
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] |
|
|
|
if len(results) == 0 and len(search) >= 3: |
|
# 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, truncate list |
|
results = list(OrderedDict.fromkeys(results))[:NUM_SEARCH_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: |
|
gen_search_strings() # update cache |
|
queryset = queryset.order_by('-vetted_date') |
|
|
|
return queryset |
|
|
|
# must POST so query string doesn't change so preflight request is cached |
|
def create(self, request): |
|
try: |
|
seq = int(request.data.get('seq', 0)) |
|
except ValueError: |
|
seq = 0 |
|
|
|
queryset = self.get_queryset()[:NUM_SEARCH_RESULTS] |
|
serializer = serializers.SearchSerializer(queryset, many=True) |
|
return Response({'seq': seq, 'results': serializer.data}) |
|
|
|
|
|
class MemberViewSet(RetrieveUpdateViewSet): |
|
permission_classes = [AllowMetadata | IsAuthenticated, IsOwnerOrAdmin] |
|
queryset = models.Member.objects.all() |
|
|
|
def get_serializer_class(self): |
|
if is_admin_director(self.request.user): |
|
return serializers.AdminMemberSerializer |
|
else: |
|
return serializers.MemberSerializer |
|
|
|
|
|
class CourseViewSet(viewsets.ModelViewSet): |
|
permission_classes = [AllowMetadata | IsAuthenticated] |
|
queryset = models.Course.objects.annotate(date=Max('sessions__datetime')).order_by('-date') |
|
|
|
def get_serializer_class(self): |
|
if self.action == 'retrieve': |
|
return serializers.CourseDetailSerializer |
|
else: |
|
return serializers.CourseSerializer |
|
|
|
|
|
class SessionViewSet(viewsets.ModelViewSet): |
|
permission_classes = [AllowMetadata | IsAuthenticated] |
|
|
|
def get_queryset(self): |
|
if self.action == 'list': |
|
return models.Session.objects.order_by('-datetime')[:20] |
|
else: |
|
return models.Session.objects.all() |
|
|
|
def get_serializer_class(self): |
|
#if self.action == 'retrieve': |
|
# return serializers.CourseDetailSerializer |
|
#else: |
|
return serializers.SessionSerializer |
|
|
|
|
|
class UserView(views.APIView): |
|
permission_classes = [AllowMetadata | IsAuthenticated] |
|
|
|
def get(self, request): |
|
serializer = serializers.UserSerializer(request.user) |
|
return Response(serializer.data) |
|
|
|
|
|
class RegistrationView(RegisterView): |
|
serializer_class = serializers.RegistrationSerializer |
|
|
|
class PasswordChangeView(PasswordChangeView): |
|
permission_classes = [AllowMetadata | IsAuthenticated]
|
|
|