general: search for users without looking at accents (#13061)

This commit is contained in:
Frédéric Péters 2017-03-31 14:42:38 +02:00
parent 0dfa04e91f
commit 1f1ebe035f
6 changed files with 75 additions and 9 deletions

View File

@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
import pytest
import json
import shutil
@ -1502,6 +1504,21 @@ def test_users(pub, local_user):
resp = get_app(pub).get(sign_uri('/api/users/?q=foobar'))
assert len(resp.json['data']) == 0
def test_users_unaccent(pub, local_user):
local_user.name = 'Jean Sénisme'
local_user.store()
resp = get_app(pub).get(sign_uri('/api/users/?q=jean'))
assert resp.json['data'][0]['user_email'] == local_user.email
resp = get_app(pub).get(sign_uri('/api/users/?q=senisme'))
assert resp.json['data'][0]['user_email'] == local_user.email
resp = get_app(pub).get(sign_uri('/api/users/?q=sénisme'))
assert resp.json['data'][0]['user_email'] == local_user.email
resp = get_app(pub).get(sign_uri('/api/users/?q=blah'))
assert len(resp.json['data']) == 0
def test_workflow_trigger(pub, local_user):
workflow = Workflow(name='test')
st1 = workflow.add_status('Status1', 'st1')

View File

@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
import datetime
import os
import random
@ -1040,6 +1042,31 @@ def test_migration_12_users_fts():
conn.commit()
cur.close()
@postgresql
def test_migration_21_users_ascii_name():
conn, cur = sql.get_connection_and_cursor()
cur.execute('UPDATE wcs_meta SET value = 11 WHERE key = %s', ('sql_level',))
sql.SqlUser.wipe()
user = sql.SqlUser()
user.name = 'Jean Sénisme'
user.store()
# remove the ascii_name column
cur.execute('ALTER TABLE users DROP COLUMN ascii_name')
assert not column_exists_in_table(cur, 'users', 'ascii_name')
sql.migrate()
assert column_exists_in_table(cur, 'users', 'ascii_name')
assert migration_level(cur) >= 21
# make sure the ascii_name is filled after the migration
assert sql.SqlUser.count([st.Equal('ascii_name', 'jean senisme')]) == 1
conn.commit()
cur.close()
def drop_formdef_tables():
conn, cur = sql.get_connection_and_cursor()
cur.execute('''SELECT table_name FROM information_schema.tables''')

View File

@ -528,7 +528,10 @@ class ApiUsersDirectory(Directory):
if query:
from admin.settings import UserFieldsFormDef
formdef = UserFieldsFormDef()
criteria_fields = [st.ILike('name', query), st.ILike('email', query)]
criteria_fields = [
st.ILike('name', query),
st.ILike('ascii_name', misc.simplify(query, ' ')),
st.ILike('email', query)]
for field in formdef.fields:
if field.type in ('string', 'text', 'email'):
criteria_fields.append(st.ILike('f%s' % field.id, query))

View File

@ -343,7 +343,10 @@ class UsersViewDirectory(Directory):
return r.getvalue()
formdef = UserFieldsFormDef()
criteria_fields = [ILike('name', query), ILike('email', query)]
criteria_fields = [
ILike('name', query),
ILike('ascii_name', misc.simplify(query, ' ')),
ILike('email', query)]
for field in formdef.get_all_fields():
if field.type in ('string', 'text', 'email'):
criteria_fields.append(ILike('f%s' % field.id, query))

View File

@ -473,6 +473,7 @@ def do_user_table():
if cur.fetchone()[0] == 0:
cur.execute('''CREATE TABLE %s (id serial PRIMARY KEY,
name varchar,
ascii_name varchar,
email varchar,
roles text[],
is_admin bool,
@ -487,7 +488,7 @@ def do_user_table():
needed_fields = set(['id', 'name', 'email', 'roles', 'is_admin',
'anonymous', 'name_identifiers', 'verified_fields',
'lasso_dump', 'last_seen', 'fts'])
'lasso_dump', 'last_seen', 'fts', 'ascii_name'])
from admin.settings import UserFieldsFormDef
formdef = UserFieldsFormDef()
@ -523,6 +524,9 @@ def do_user_table():
if not 'verified_fields' in existing_fields:
cur.execute('ALTER TABLE %s ADD COLUMN verified_fields text[]' % table_name)
if not 'ascii_name' in existing_fields:
cur.execute('ALTER TABLE %s ADD COLUMN ascii_name varchar' % table_name)
# delete obsolete fields
for field in (existing_fields - needed_fields):
cur.execute('''ALTER TABLE %s DROP COLUMN %s''' % (table_name, field))
@ -1287,6 +1291,7 @@ class SqlFormData(SqlMixin, wcs.formdata.FormData):
user = self.get_user()
if user:
fts_strings.append(user.get_display_name())
fts_strings.append(user.ascii_name)
sql_statement = '''UPDATE %s SET fts = to_tsvector( %%(fts)s)
WHERE id = %%(id)s''' % self._table_name
@ -1448,7 +1453,8 @@ class SqlUser(SqlMixin, wcs.users.User):
('name_identifiers', 'varchar[]'),
('verified_fields', 'varchar[]'),
('lasso_dump', 'text'),
('last_seen', 'timestamp')
('last_seen', 'timestamp'),
('ascii_name', 'varchar'),
]
id = None
@ -1463,6 +1469,7 @@ class SqlUser(SqlMixin, wcs.users.User):
def store(self):
sql_dict = {
'name': self.name,
'ascii_name': self.ascii_name,
'email': self.email,
'roles': self.roles,
'is_admin': self.is_admin,
@ -1507,6 +1514,7 @@ class SqlUser(SqlMixin, wcs.users.User):
fts_strings = []
if self.name:
fts_strings.append(self.name)
fts_strings.append(self.ascii_name)
if self.email:
fts_strings.append(self.email)
if user_formdef and user_formdef.fields:
@ -1535,7 +1543,7 @@ class SqlUser(SqlMixin, wcs.users.User):
o = cls()
(o.id, o.name, o.email, o.roles, o.is_admin, o.anonymous,
o.name_identifiers, o.verified_fields, o.lasso_dump,
o.last_seen) = tuple(row[:10])
o.last_seen, ascii_name) = tuple(row[:11])
if o.last_seen:
o.last_seen = time.mktime(o.last_seen.timetuple())
if o.roles:
@ -1870,7 +1878,7 @@ def get_yearly_totals(period_start=None, period_end=None, criterias=None):
return result
SQL_LEVEL = 20
SQL_LEVEL = 21
def migrate_global_views(conn, cur):
cur.execute('''SELECT COUNT(*) FROM information_schema.tables
@ -1918,11 +1926,12 @@ def migrate():
# 19: add geolocation to views
# 20: remove user hash stuff
migrate_views(conn, cur)
if sql_level < 16:
if sql_level < 21:
# 3: introduction of _structured for user fields
# 4: removal of identification_token
# 12: (first part) add fts to users
# 16: add verified_fields to users
# 21: (first part) add ascii_name to users
do_user_table()
if sql_level < 6:
# 6: add actions_roles_array to tables and views
@ -1930,13 +1939,15 @@ def migrate():
migrate_views(conn, cur)
for formdef in FormDef.select():
formdef.data_class().rebuild_security()
if sql_level < 12:
if sql_level < 21:
# 12: (second part), store fts in existing rows
# 21: (second part), store ascii_name of users
for user_id in SqlUser.keys():
SqlUser.get(user_id).store()
if sql_level < 18:
if sql_level < 21:
# 17: store last_update_time in tables
# 18: add user name to full-text search index
# 21: (third part), add user ascii_names to full-text index
# load and store all formdatas
from wcs.formdef import FormDef
for formdef in FormDef.select():

View File

@ -15,6 +15,7 @@
# along with this program; if not, see <http://www.gnu.org/licenses/>.
from qommon import _
from qommon.misc import simplify
from qommon.storage import StorableObject
from qommon import get_cfg
import wcs.qommon.storage as st
@ -70,6 +71,10 @@ class User(StorableObject):
from admin.settings import UserFieldsFormDef
return UserFieldsFormDef()
@property
def ascii_name(self):
return simplify(self.get_display_name(), space=' ')
def get_display_name(self):
if self.name:
return self.name