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.conf import settings
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_scopes import scopes_disabled
from django_scopes.forms import SafeModelChoiceField, SafeModelMultipleChoiceField
@ -390,10 +390,12 @@ class UserCreateForm(forms.Form):
class SearchPreferenceForm(forms.ModelForm):
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:
model = SearchPreference
fields = ('search', 'lookup', 'unaccent', 'icontains', 'istartswith', 'trigram', 'fulltext')
fields = ('search', 'lookup', 'unaccent', 'icontains', 'istartswith', 'trigram', 'fulltext', 'trigram_threshold')
help_texts = {
'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')"),
'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."),
'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 = {

View File

@ -112,16 +112,15 @@ def search_recipes(request, queryset, params):
)
# 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
for f in trigram_include:
if trigram:
trigram += TrigramSimilarity(f, search_string)
else:
trigram = TrigramSimilarity(f, search_string)
queryset.annotate(simularity=trigram)
# TODO allow user to play with trigram scores
filters += [Q(simularity__gt=0.5)]
queryset = queryset.annotate(similarity=trigram)
filters += [Q(similarity__gt=search_prefs.trigram_threshold)]
if 'name' in fulltext_include:
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)
trigram = models.ManyToManyField(SearchFields, related_name="trigram_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):

View File

@ -21,27 +21,31 @@
<!-- Nav tabs -->
<ul class="nav nav-tabs" id="myTab" role="tablist" style="margin-bottom: 2vh">
<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"
aria-controls="account"
<a class="nav-link {% if active_tab == 'account' %} active {% endif %}" id="account-tab" data-toggle="tab"
href="#account" role="tab"
aria-controls="account"
aria-selected="{% if active_tab == 'account' %} 'true' {% else %} 'false' {% endif %}">
{% trans 'Account' %}</a>
{% trans 'Account' %}</a>
</li>
<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"
aria-controls="preferences"
<a class="nav-link {% if active_tab == 'prefernces' %} active {% endif %}" id="preferences-tab"
data-toggle="tab" href="#preferences" role="tab"
aria-controls="preferences"
aria-selected="{% if active_tab == 'prefernces' %} 'true' {% else %} 'false' {% endif %}">
{% trans 'Preferences' %}</a>
{% trans 'Preferences' %}</a>
</li>
<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"
aria-controls="api"
aria-selected="{% if active_tab == 'api' %} 'true' {% else %} 'false' {% endif %}">
<a class="nav-link {% if active_tab == 'api' %} active {% endif %}" id="api-tab" data-toggle="tab"
href="#api" role="tab"
aria-controls="api"
aria-selected="{% if active_tab == 'api' %} 'true' {% else %} 'false' {% endif %}">
{% trans 'API-Settings' %}</a>
</li>
<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"
aria-controls="search"
aria-selected="{% if active_tab == 'search' %} 'true' {% else %} 'false' {% endif %}">
<a class="nav-link {% if active_tab == 'search' %} active {% endif %}" id="search-tab" data-toggle="tab"
href="#search" role="tab"
aria-controls="search"
aria-selected="{% if active_tab == 'search' %} 'true' {% else %} 'false' {% endif %}">
{% trans 'Search-Settings' %}</a>
</li>
@ -49,7 +53,8 @@
<!-- Tab panes -->
<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>
<form action="." method="post">
{% csrf_token %}
@ -60,8 +65,8 @@
<h4>{% trans 'Account Settings' %}</h4>
<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_email' %}" class="btn btn-primary">{% trans 'Emails' %}</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>
<br/>
@ -70,7 +75,8 @@
<br/>
</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">
@ -121,7 +127,8 @@
</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="col col-md-12">
@ -153,7 +160,8 @@
</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>
<form action="." method="post">
{% csrf_token %}
@ -162,20 +170,26 @@
class="fas fa-save"></i> {% trans 'Save' %}</button>
</form>
</div>
</div>
<script type="application/javascript">
$(function () {
$('#id_search-trigram_threshold').get(0).type = 'range';
})
function copyToken() {
let token = $('#id_token');
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) {

View File

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