fuzzy match on lookups

This commit is contained in:
smilerz
2021-06-06 14:12:19 -05:00
parent 11b258b405
commit 26a5821cfc
8 changed files with 57 additions and 19 deletions

View File

@@ -484,10 +484,11 @@ class SearchPreferenceForm(forms.ModelForm):
class Meta:
model = SearchPreference
fields = ('search', 'unaccent', 'icontains', 'istartswith', 'trigram', 'fulltext')
fields = ('search', 'lookup', 'unaccent', 'icontains', 'istartswith', 'trigram', 'fulltext')
help_texts = {
'search': _('Select type method of search. Click <a href="/docs/search/">here</a> for full desciption of choices.'),
'lookup': _('Use fuzzy matching on units, keywords and ingredients when editing and importing recipes.'),
'unaccent': _('Fields to search ignoring accents. Selecting this option can improve or degrade search quality depending on language'),
'icontains': _("Fields to search for partial matches. (e.g. searching for 'Pie' will return 'pie' and 'piece' and 'soapie')"),
'istartswith': _("Fields to search for beginning of word matches. (e.g. searching for 'sa' will return 'salad' and 'sandwich')"),
@@ -497,6 +498,7 @@ class SearchPreferenceForm(forms.ModelForm):
labels = {
'search': _('Search Method'),
'lookup': _('Fuzzy Lookups'),
'unaccent': _('Ignore Accent'),
'icontains': _("Partial Match"),
'istartswith': _("Starts Wtih"),

View File

@@ -95,7 +95,8 @@ class Migration(migrations.Migration):
name='SearchPreference',
fields=[
('user', annoying.fields.AutoOneToOneField(on_delete=deletion.CASCADE, primary_key=True, serialize=False, to='auth.user')),
('search', models.CharField(choices=[('plain', 'Plain'), ('phrase', 'Phrase'), ('websearch', 'Web'), ('raw', 'Raw')], default='plain', max_length=32)),
('search', models.CharField(choices=[('plain', 'Simple'), ('phrase', 'Phrase'), ('websearch', 'Web'), ('raw', 'Raw')], default='plain', max_length=32)),
('lookup', models.BooleanField(default=False)),
('fulltext', models.ManyToManyField(blank=True, related_name='fulltext_fields', to='cookbook.SearchFields')),
('icontains', models.ManyToManyField(blank=True, default=nameSearchField, related_name='icontains_fields', to='cookbook.SearchFields')),
('istartswith', models.ManyToManyField(blank=True, related_name='istartswith_fields', to='cookbook.SearchFields')),

View File

@@ -763,6 +763,7 @@ class SearchPreference(models.Model, PermissionModelMixin):
user = AutoOneToOneField(User, on_delete=models.CASCADE, primary_key=True)
search = models.CharField(choices=SEARCH_STYLE, max_length=32, default=SIMPLE)
lookup = models.BooleanField(default=False)
unaccent = models.ManyToManyField(SearchFields, related_name="unaccent_fields", blank=True, default=allSearchFields)
icontains = models.ManyToManyField(SearchFields, related_name="icontains_fields", blank=True, default=nameSearchField)
istartswith = models.ManyToManyField(SearchFields, related_name="istartswith_fields", blank=True)

File diff suppressed because one or more lines are too long

View File

@@ -180,5 +180,15 @@
token.select();
document.execCommand("copy");
}
// Javascript to enable link to tab
var hash = location.hash.replace(/^#/, ''); // ^ means starting, meaning only match the first hash
if (hash) {
$('.nav-tabs a[href="#' + hash + '"]').tab('show');
}
// Change hash for page-reload
$('.nav-tabs a').on('shown.bs.tab', function (e) {
window.location.hash = e.target.hash;
})
</script>
{% endblock %}

View File

@@ -9,6 +9,7 @@ from annoying.decorators import ajax_request
from annoying.functions import get_object_or_None
from django.contrib import messages
from django.contrib.auth.models import User
from django.contrib.postgres.search import TrigramSimilarity
from django.core.exceptions import FieldError, ValidationError
from django.core.files import File
from django.db.models import Q
@@ -86,6 +87,38 @@ class StandardFilterMixin(ViewSetMixin):
return queryset
class FuzzyFilterMixin(ViewSetMixin):
def get_queryset(self):
queryset = self.queryset
query = self.request.query_params.get('query', None)
fuzzy = self.request.user.searchpreference.lookup
if query is not None or query != '':
if fuzzy:
queryset = queryset.annotate(trigram=TrigramSimilarity('name', query)).filter(trigram__gt=0.2).order_by("-trigram")
else:
# TODO have this check unaccent search settings?
queryset = queryset.filter(name__icontains=query)
updated_at = self.request.query_params.get('updated_at', None)
if updated_at is not None:
try:
queryset = queryset.filter(updated_at__gte=updated_at)
except FieldError:
pass
except ValidationError:
raise APIException(_('Parameter updated_at incorrectly formatted'))
limit = self.request.query_params.get('limit', None)
random = self.request.query_params.get('random', False)
if limit is not None:
if random:
queryset = queryset.order_by("?")
queryset = queryset[:int(limit)]
return queryset
class UserNameViewSet(viewsets.ReadOnlyModelViewSet):
"""
list:
@@ -157,17 +190,7 @@ class SupermarketViewSet(viewsets.ModelViewSet, StandardFilterMixin):
return super().get_queryset()
class SupermarketCategoryViewSet(viewsets.ModelViewSet, StandardFilterMixin):
queryset = SupermarketCategory.objects
serializer_class = SupermarketCategorySerializer
permission_classes = [CustomIsUser]
def get_queryset(self):
self.queryset = self.queryset.filter(space=self.request.space)
return super().get_queryset()
class KeywordViewSet(viewsets.ModelViewSet, StandardFilterMixin):
class KeywordViewSet(viewsets.ModelViewSet, FuzzyFilterMixin):
"""
list:
optional parameters
@@ -185,7 +208,7 @@ class KeywordViewSet(viewsets.ModelViewSet, StandardFilterMixin):
return super().get_queryset()
class UnitViewSet(viewsets.ModelViewSet, StandardFilterMixin):
class UnitViewSet(viewsets.ModelViewSet, FuzzyFilterMixin):
queryset = Unit.objects
serializer_class = UnitSerializer
permission_classes = [CustomIsUser]
@@ -195,7 +218,7 @@ class UnitViewSet(viewsets.ModelViewSet, StandardFilterMixin):
return super().get_queryset()
class FoodViewSet(viewsets.ModelViewSet, StandardFilterMixin):
class FoodViewSet(viewsets.ModelViewSet, FuzzyFilterMixin):
queryset = Food.objects
serializer_class = FoodSerializer
permission_classes = [CustomIsUser]

View File

@@ -33,7 +33,6 @@ from cookbook.models import (Comment, CookLog, InviteLink, MealPlan,
from cookbook.tables import (CookLogTable, RecipeTable, RecipeTableSmall,
ViewLogTable)
from cookbook.views.data import Object
from recipes import settings
from recipes.settings import DEMO
from recipes.version import BUILD_REF, VERSION_NUMBER
@@ -361,6 +360,7 @@ def user_settings(request):
search_error = True
else:
sp.search = search_form.cleaned_data['search']
sp.lookup = search_form.cleaned_data['lookup']
sp.unaccent.set(search_form.cleaned_data['unaccent'])
sp.icontains.set(search_form.cleaned_data['icontains'])
sp.istartswith.set(search_form.cleaned_data['istartswith'])
@@ -385,6 +385,7 @@ def user_settings(request):
# these fields require postgress - just disable them if postgress isn't available
if not settings.DATABASES['default']['ENGINE'] in ['django.db.backends.postgresql_psycopg2', 'django.db.backends.postgresql']:
search_form.fields['search'].disabled = True
search_form.fields['lookup'].disabled = True
search_form.fields['trigram'].disabled = True
search_form.fields['fulltext'].disabled = True

View File

@@ -92,7 +92,7 @@
</div>
<div class="row" style="margin-top: 1vh">
<div class="col-12">
<a :href="resolveDjangoUrl('view_settings')">{{ $t('Advanced Search Settings') }}</a>
<a :href="resolveDjangoUrl('view_settings') + '#search'">{{ $t('Advanced Search Settings') }}</a>
</div>
</div>
<div class="row" style="margin-top: 1vh">