diff --git a/apps/establishment/filters.py b/apps/establishment/filters.py index 6dd70222..58826e19 100644 --- a/apps/establishment/filters.py +++ b/apps/establishment/filters.py @@ -1,6 +1,8 @@ """Establishment app filters.""" from django.core.validators import EMPTY_VALUES from django_filters import rest_framework as filters +from rest_framework.serializers import ValidationError +from django.utils.translation import ugettext_lazy as _ from establishment import models @@ -79,3 +81,12 @@ class EmployeeBackFilter(filters.FilterSet): if value not in EMPTY_VALUES: return queryset.search_by_name_or_last_name(value) return queryset + + +class EmployeeBackSearchFilter(EmployeeBackFilter): + def search_by_name_or_last_name(self, queryset, name, value): + if value not in EMPTY_VALUES: + if len(value) < 3: + raise ValidationError({'detail': _('Type at least 3 characters to search please.')}) + return queryset.trigram_search(value) + return queryset diff --git a/apps/establishment/migrations/0072_auto_20200115_1702.py b/apps/establishment/migrations/0072_auto_20200115_1702.py new file mode 100644 index 00000000..4ea732fb --- /dev/null +++ b/apps/establishment/migrations/0072_auto_20200115_1702.py @@ -0,0 +1,16 @@ +# Generated by Django 2.2.7 on 2020-01-15 17:02 + +from django.db import migrations +from django.contrib.postgres.operations import TrigramExtension, BtreeGinExtension + + +class Migration(migrations.Migration): + + dependencies = [ + ('establishment', '0071_auto_20200110_1055'), + ] + + operations = [ + TrigramExtension(), + BtreeGinExtension(), + ] diff --git a/apps/establishment/migrations/0073_auto_20200115_1710.py b/apps/establishment/migrations/0073_auto_20200115_1710.py new file mode 100644 index 00000000..375c563e --- /dev/null +++ b/apps/establishment/migrations/0073_auto_20200115_1710.py @@ -0,0 +1,22 @@ +# Generated by Django 2.2.7 on 2020-01-15 17:10 + +import django.contrib.postgres.indexes +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('establishment', '0072_auto_20200115_1702'), + ] + + operations = [ + migrations.AddIndex( + model_name='employee', + index=django.contrib.postgres.indexes.GinIndex(fields=['name'], name='establishme_name_39fda6_gin'), + ), + migrations.AddIndex( + model_name='employee', + index=django.contrib.postgres.indexes.GinIndex(fields=['last_name'], name='establishme_last_na_3c53de_gin'), + ), + ] diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 1d693219..325d9d8d 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -11,6 +11,8 @@ from django.contrib.gis.db.models.functions import Distance from django.contrib.gis.geos import Point from django.contrib.gis.measure import Distance as DistanceMeasure from django.contrib.postgres.fields import ArrayField +from django.contrib.postgres.search import TrigramDistance +from django.contrib.postgres.indexes import GinIndex from django.core.exceptions import ValidationError from django.core.validators import MinValueValidator, MaxValueValidator from django.db import models @@ -977,6 +979,13 @@ class EmployeeQuerySet(models.QuerySet): ] return self.filter(reduce(lambda x, y: x | y, [models.Q(**i) for i in filters])) + def trigram_search(self, search_value: str): + """Search with mistakes by name or last name.""" + return self.annotate( + name_distance=TrigramDistance('name', search_value.lower()), + last_name_distance=TrigramDistance('last_name', search_value.lower()), + ).filter(Q(name_distance__lte=0.7) | Q(last_name_distance__lte=0.7)).order_by('name_distance') + def search_by_name_or_last_name(self, value): """Search by name or last_name.""" return self._generic_search(value, ['name', 'last_name']) @@ -987,6 +996,9 @@ class EmployeeQuerySet(models.QuerySet): queryset=EstablishmentEmployee.objects.actual() )).all().distinct() + def with_extended_related(self): + return self.prefetch_related('establishments') + class Employee(BaseAttributes): """Employee model.""" @@ -1030,6 +1042,10 @@ class Employee(BaseAttributes): verbose_name = _('Employee') verbose_name_plural = _('Employees') + indexes = [ + GinIndex(fields=('name',)), + GinIndex(fields=('last_name',)) + ] class EstablishmentScheduleQuerySet(models.QuerySet): diff --git a/apps/establishment/urls/back.py b/apps/establishment/urls/back.py index b2a30917..10351fe5 100644 --- a/apps/establishment/urls/back.py +++ b/apps/establishment/urls/back.py @@ -45,6 +45,7 @@ urlpatterns = [ path('/employees/', views.EstablishmentEmployeeListView.as_view(), name='establishment-employees'), path('employees/', views.EmployeeListCreateView.as_view(), name='employees'), + path('employees/search/', views.EmployeesListSearchViews.as_view(), name='employees-search'), path('employees//', views.EmployeeRUDView.as_view(), name='employees-rud'), path('/employee//position/', views.EstablishmentEmployeeCreateView.as_view(), diff --git a/apps/establishment/views/back.py b/apps/establishment/views/back.py index 19a75fd5..bc22f5ce 100644 --- a/apps/establishment/views/back.py +++ b/apps/establishment/views/back.py @@ -2,7 +2,7 @@ from django.http import Http404, HttpResponse from django.shortcuts import get_object_or_404 from django_filters.rest_framework import DjangoFilterBackend -from rest_framework import generics, permissions, status +from rest_framework import generics, permissions, status, filters as rest_filters from account.models import User from establishment import filters, models, serializers @@ -174,6 +174,15 @@ class EmployeeListCreateView(generics.ListCreateAPIView): queryset = models.Employee.objects.all() +class EmployeesListSearchViews(generics.ListAPIView): + """Employee search view""" + pagination_class = None + permission_classes = (permissions.AllowAny,) + queryset = models.Employee.objects.all() + filter_class = filters.EmployeeBackSearchFilter + serializer_class = serializers.EmployeeBackSerializers + + class EstablishmentEmployeeListView(generics.ListCreateAPIView): """Establishment emplyoees list view.""" permission_classes = (permissions.AllowAny,)