fuzzy match on lookups
This commit is contained in:
parent
32c488f4a8
commit
b556bed56e
@ -479,10 +479,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')"),
|
||||
@ -492,6 +493,7 @@ class SearchPreferenceForm(forms.ModelForm):
|
||||
|
||||
labels = {
|
||||
'search': _('Search Method'),
|
||||
'lookup': _('Fuzzy Lookups'),
|
||||
'unaccent': _('Ignore Accent'),
|
||||
'icontains': _("Partial Match"),
|
||||
'istartswith': _("Starts Wtih"),
|
||||
|
@ -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')),
|
||||
|
@ -760,6 +760,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
@ -171,5 +171,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 %}
|
@ -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
|
||||
@ -88,6 +89,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:
|
||||
@ -159,27 +192,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 SupermarketCategoryRelationViewSet(viewsets.ModelViewSet, StandardFilterMixin):
|
||||
queryset = SupermarketCategoryRelation.objects
|
||||
serializer_class = SupermarketCategoryRelationSerializer
|
||||
permission_classes = [CustomIsUser]
|
||||
|
||||
def get_queryset(self):
|
||||
self.queryset = self.queryset.filter(supermarket__space=self.request.space)
|
||||
return super().get_queryset()
|
||||
|
||||
|
||||
class KeywordViewSet(viewsets.ModelViewSet, StandardFilterMixin):
|
||||
class KeywordViewSet(viewsets.ModelViewSet, FuzzyFilterMixin):
|
||||
"""
|
||||
list:
|
||||
optional parameters
|
||||
@ -197,7 +210,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]
|
||||
@ -207,7 +220,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]
|
||||
|
@ -34,7 +34,6 @@ from cookbook.models import (Comment, CookLog, InviteLink, MealPlan,
|
||||
from cookbook.tables import (CookLogTable, RecipeTable, RecipeTableSmall,
|
||||
ViewLogTable, InviteLinkTable)
|
||||
from cookbook.views.data import Object
|
||||
from recipes import settings
|
||||
from recipes.settings import DEMO
|
||||
from recipes.version import BUILD_REF, VERSION_NUMBER
|
||||
|
||||
@ -374,6 +373,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'])
|
||||
@ -398,6 +398,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
|
||||
|
||||
|
@ -119,7 +119,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">
|
||||
|
Loading…
Reference in New Issue
Block a user