fixed and expanded on trigram search

This commit is contained in:
vabene1111 2021-09-28 19:12:51 +02:00
parent a0e9b88062
commit 856c34a3bf
6 changed files with 62 additions and 27 deletions

View File

@ -1,7 +1,7 @@
from django import forms from django import forms
from django.conf import settings from django.conf import settings
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.forms import widgets from django.forms import widgets, NumberInput
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django_scopes import scopes_disabled from django_scopes import scopes_disabled
from django_scopes.forms import SafeModelChoiceField, SafeModelMultipleChoiceField from django_scopes.forms import SafeModelChoiceField, SafeModelMultipleChoiceField
@ -390,10 +390,12 @@ class UserCreateForm(forms.Form):
class SearchPreferenceForm(forms.ModelForm): class SearchPreferenceForm(forms.ModelForm):
prefix = 'search' prefix = 'search'
trigram_threshold = forms.DecimalField(min_value=0.01, max_value=1, decimal_places=2, widget=NumberInput(attrs={'class': "form-control-range", 'type': 'range'}),
help_text=_('Determines how fuzzy a search is if it uses trigram similarity matching (e.g. low values mean more typos are ignored).'))
class Meta: class Meta:
model = SearchPreference model = SearchPreference
fields = ('search', 'lookup', 'unaccent', 'icontains', 'istartswith', 'trigram', 'fulltext') fields = ('search', 'lookup', 'unaccent', 'icontains', 'istartswith', 'trigram', 'fulltext', 'trigram_threshold')
help_texts = { help_texts = {
'search': _('Select type method of search. Click <a href="/docs/search/">here</a> for full desciption of choices.'), 'search': _('Select type method of search. Click <a href="/docs/search/">here</a> for full desciption of choices.'),
@ -402,7 +404,7 @@ class SearchPreferenceForm(forms.ModelForm):
'icontains': _("Fields to search for partial matches. (e.g. searching for 'Pie' will return 'pie' and 'piece' and 'soapie')"), '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')"), 'istartswith': _("Fields to search for beginning of word matches. (e.g. searching for 'sa' will return 'salad' and 'sandwich')"),
'trigram': _("Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) Note: this option will conflict with 'web' and 'raw' methods of search."), 'trigram': _("Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) Note: this option will conflict with 'web' and 'raw' methods of search."),
'fulltext': _("Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods only function with fulltext fields.") 'fulltext': _("Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods only function with fulltext fields."),
} }
labels = { labels = {

View File

@ -112,16 +112,15 @@ def search_recipes(request, queryset, params):
) )
# iterate through fields to use in trigrams generating a single trigram # iterate through fields to use in trigrams generating a single trigram
if search_trigram & len(trigram_include) > 1: if search_trigram and len(trigram_include) > 0:
trigram = None trigram = None
for f in trigram_include: for f in trigram_include:
if trigram: if trigram:
trigram += TrigramSimilarity(f, search_string) trigram += TrigramSimilarity(f, search_string)
else: else:
trigram = TrigramSimilarity(f, search_string) trigram = TrigramSimilarity(f, search_string)
queryset.annotate(simularity=trigram) queryset = queryset.annotate(similarity=trigram)
# TODO allow user to play with trigram scores filters += [Q(similarity__gt=search_prefs.trigram_threshold)]
filters += [Q(simularity__gt=0.5)]
if 'name' in fulltext_include: if 'name' in fulltext_include:
filters += [Q(name_search_vector=search_query)] filters += [Q(name_search_vector=search_query)]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.7 on 2021-09-28 16:45
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0155_mealtype_default'),
]
operations = [
migrations.AddField(
model_name='searchpreference',
name='trigram_threshold',
field=models.DecimalField(decimal_places=2, default=0.1, max_digits=3),
),
]

View File

@ -873,6 +873,7 @@ class SearchPreference(models.Model, PermissionModelMixin):
istartswith = models.ManyToManyField(SearchFields, related_name="istartswith_fields", blank=True) istartswith = models.ManyToManyField(SearchFields, related_name="istartswith_fields", blank=True)
trigram = models.ManyToManyField(SearchFields, related_name="trigram_fields", blank=True) trigram = models.ManyToManyField(SearchFields, related_name="trigram_fields", blank=True)
fulltext = models.ManyToManyField(SearchFields, related_name="fulltext_fields", blank=True) fulltext = models.ManyToManyField(SearchFields, related_name="fulltext_fields", blank=True)
trigram_threshold = models.DecimalField(default=0.1, decimal_places=2, max_digits=3)
class UserFile(ExportModelOperationsMixin('user_files'), models.Model, PermissionModelMixin): class UserFile(ExportModelOperationsMixin('user_files'), models.Model, PermissionModelMixin):

View File

@ -21,27 +21,31 @@
<!-- Nav tabs --> <!-- Nav tabs -->
<ul class="nav nav-tabs" id="myTab" role="tablist" style="margin-bottom: 2vh"> <ul class="nav nav-tabs" id="myTab" role="tablist" style="margin-bottom: 2vh">
<li class="nav-item" role="presentation"> <li class="nav-item" role="presentation">
<a class="nav-link {% if active_tab == 'account' %} active {% endif %}" id="account-tab" data-toggle="tab" href="#account" role="tab" <a class="nav-link {% if active_tab == 'account' %} active {% endif %}" id="account-tab" data-toggle="tab"
aria-controls="account" href="#account" role="tab"
aria-controls="account"
aria-selected="{% if active_tab == 'account' %} 'true' {% else %} 'false' {% endif %}"> aria-selected="{% if active_tab == 'account' %} 'true' {% else %} 'false' {% endif %}">
{% trans 'Account' %}</a> {% trans 'Account' %}</a>
</li> </li>
<li class="nav-item" role="presentation"> <li class="nav-item" role="presentation">
<a class="nav-link {% if active_tab == 'prefernces' %} active {% endif %}" id="preferences-tab" data-toggle="tab" href="#preferences" role="tab" <a class="nav-link {% if active_tab == 'prefernces' %} active {% endif %}" id="preferences-tab"
aria-controls="preferences" data-toggle="tab" href="#preferences" role="tab"
aria-controls="preferences"
aria-selected="{% if active_tab == 'prefernces' %} 'true' {% else %} 'false' {% endif %}"> aria-selected="{% if active_tab == 'prefernces' %} 'true' {% else %} 'false' {% endif %}">
{% trans 'Preferences' %}</a> {% trans 'Preferences' %}</a>
</li> </li>
<li class="nav-item" role="presentation"> <li class="nav-item" role="presentation">
<a class="nav-link {% if active_tab == 'api' %} active {% endif %}" id="api-tab" data-toggle="tab" href="#api" role="tab" <a class="nav-link {% if active_tab == 'api' %} active {% endif %}" id="api-tab" data-toggle="tab"
aria-controls="api" href="#api" role="tab"
aria-selected="{% if active_tab == 'api' %} 'true' {% else %} 'false' {% endif %}"> aria-controls="api"
aria-selected="{% if active_tab == 'api' %} 'true' {% else %} 'false' {% endif %}">
{% trans 'API-Settings' %}</a> {% trans 'API-Settings' %}</a>
</li> </li>
<li class="nav-item" role="presentation"> <li class="nav-item" role="presentation">
<a class="nav-link {% if active_tab == 'search' %} active {% endif %}" id="search-tab" data-toggle="tab" href="#search" role="tab" <a class="nav-link {% if active_tab == 'search' %} active {% endif %}" id="search-tab" data-toggle="tab"
aria-controls="search" href="#search" role="tab"
aria-selected="{% if active_tab == 'search' %} 'true' {% else %} 'false' {% endif %}"> aria-controls="search"
aria-selected="{% if active_tab == 'search' %} 'true' {% else %} 'false' {% endif %}">
{% trans 'Search-Settings' %}</a> {% trans 'Search-Settings' %}</a>
</li> </li>
@ -49,7 +53,8 @@
<!-- Tab panes --> <!-- Tab panes -->
<div class="tab-content"> <div class="tab-content">
<div class="tab-pane {% if active_tab == 'account' %} active {% endif %}" id="account" role="tabpanel" aria-labelledby="account-tab"> <div class="tab-pane {% if active_tab == 'account' %} active {% endif %}" id="account" role="tabpanel"
aria-labelledby="account-tab">
<h4>{% trans 'Name Settings' %}</h4> <h4>{% trans 'Name Settings' %}</h4>
<form action="." method="post"> <form action="." method="post">
{% csrf_token %} {% csrf_token %}
@ -60,8 +65,8 @@
<h4>{% trans 'Account Settings' %}</h4> <h4>{% trans 'Account Settings' %}</h4>
<a href="{% url 'account_email'%}" class="btn btn-primary">{% trans 'Emails' %}</a> <a href="{% url 'account_email' %}" class="btn btn-primary">{% trans 'Emails' %}</a>
<a href="{% url 'account_change_password'%}" class="btn btn-primary">{% trans 'Password' %}</a> <a href="{% url 'account_change_password' %}" class="btn btn-primary">{% trans 'Password' %}</a>
<a href="{% url 'socialaccount_connections' %}" class="btn btn-primary">{% trans 'Social' %}</a> <a href="{% url 'socialaccount_connections' %}" class="btn btn-primary">{% trans 'Social' %}</a>
<br/> <br/>
@ -70,7 +75,8 @@
<br/> <br/>
</div> </div>
<div class="tab-pane {% if active_tab == 'preferences' %} active {% endif %}" id="preferences" role="tabpanel" aria-labelledby="preferences-tab"> <div class="tab-pane {% if active_tab == 'preferences' %} active {% endif %}" id="preferences" role="tabpanel"
aria-labelledby="preferences-tab">
<div class="row"> <div class="row">
@ -121,7 +127,8 @@
</div> </div>
<div class="tab-pane {% if active_tab == 'api' %} active {% endif %}" id="api" role="tabpanel" aria-labelledby="api-tab"> <div class="tab-pane {% if active_tab == 'api' %} active {% endif %}" id="api" role="tabpanel"
aria-labelledby="api-tab">
<div class="row"> <div class="row">
<div class="col col-md-12"> <div class="col col-md-12">
@ -153,7 +160,8 @@
</div> </div>
<div class="tab-pane {% if active_tab == 'search' %} active {% endif %}" id="search" role="tabpanel" aria-labelledby="search-tab"> <div class="tab-pane {% if active_tab == 'search' %} active {% endif %}" id="search" role="tabpanel"
aria-labelledby="search-tab">
<h4>{% trans 'Search Settings' %}</h4> <h4>{% trans 'Search Settings' %}</h4>
<form action="." method="post"> <form action="." method="post">
{% csrf_token %} {% csrf_token %}
@ -162,20 +170,26 @@
class="fas fa-save"></i> {% trans 'Save' %}</button> class="fas fa-save"></i> {% trans 'Save' %}</button>
</form> </form>
</div> </div>
</div> </div>
<script type="application/javascript"> <script type="application/javascript">
$(function () {
$('#id_search-trigram_threshold').get(0).type = 'range';
})
function copyToken() { function copyToken() {
let token = $('#id_token'); let token = $('#id_token');
token.select(); token.select();
document.execCommand("copy"); document.execCommand("copy");
} }
// Javascript to enable link to tab // Javascript to enable link to tab
var hash = location.hash.replace(/^#/, ''); // ^ means starting, meaning only match the first hash var hash = location.hash.replace(/^#/, ''); // ^ means starting, meaning only match the first hash
if (hash) { if (hash) {
$('.nav-tabs a[href="#' + hash + '"]').tab('show'); $('.nav-tabs a[href="#' + hash + '"]').tab('show');
} }
// Change hash for page-reload // Change hash for page-reload
$('.nav-tabs a').on('shown.bs.tab', function (e) { $('.nav-tabs a').on('shown.bs.tab', function (e) {

View File

@ -366,6 +366,7 @@ def user_settings(request):
sp.istartswith.set(search_form.cleaned_data['istartswith']) sp.istartswith.set(search_form.cleaned_data['istartswith'])
sp.trigram.set(search_form.cleaned_data['trigram']) sp.trigram.set(search_form.cleaned_data['trigram'])
sp.fulltext.set(search_form.cleaned_data['fulltext']) sp.fulltext.set(search_form.cleaned_data['fulltext'])
sp.trigram_threshold = search_form.cleaned_data['trigram_threshold']
sp.save() sp.save()
if up: if up: