forked from Github-Mirrors/canaille
refactor: move BackendModel.fuzzy to Backend.fuzzy
This commit is contained in:
parent
8425b2a3b8
commit
fa6488bcd1
10 changed files with 59 additions and 64 deletions
|
@ -187,7 +187,7 @@ class TableForm(I18NFormMixin, FlaskForm):
|
|||
filter = filter or {}
|
||||
super().__init__(**kwargs)
|
||||
if self.query.data:
|
||||
self.items = cls.fuzzy(self.query.data, fields, **filter)
|
||||
self.items = BaseBackend.get().fuzzy(cls, self.query.data, fields, **filter)
|
||||
else:
|
||||
self.items = BaseBackend.get().query(cls, **filter)
|
||||
|
||||
|
|
|
@ -73,6 +73,11 @@ class BaseBackend:
|
|||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def fuzzy(self, model, query, attributes=None, **kwargs):
|
||||
"""Works like :meth:`~canaille.backends.BaseBackend.query` but
|
||||
attribute values loosely be matched."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def check_user_password(self, user, password: str) -> bool:
|
||||
"""Check if the password matches the user password in the database."""
|
||||
raise NotImplementedError()
|
||||
|
|
|
@ -302,6 +302,15 @@ class Backend(BaseBackend):
|
|||
result = []
|
||||
return LDAPObjectQuery(model, result)
|
||||
|
||||
def fuzzy(self, model, query, attributes=None, **kwargs):
|
||||
query = ldap.filter.escape_filter_chars(query)
|
||||
attributes = attributes or model.may() + model.must()
|
||||
attributes = [model.python_attribute_to_ldap(name) for name in attributes]
|
||||
filter = (
|
||||
"(|" + "".join(f"({attribute}=*{query}*)" for attribute in attributes) + ")"
|
||||
)
|
||||
return self.query(model, filter=filter, **kwargs)
|
||||
|
||||
|
||||
def setup_ldap_models(config):
|
||||
from canaille.app import models
|
||||
|
|
|
@ -240,16 +240,6 @@ class LDAPObject(BackendModel, metaclass=LDAPObjectMetaclass):
|
|||
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def fuzzy(cls, query, attributes=None, **kwargs):
|
||||
query = ldap.filter.escape_filter_chars(query)
|
||||
attributes = attributes or cls.may() + cls.must()
|
||||
attributes = [cls.python_attribute_to_ldap(name) for name in attributes]
|
||||
filter = (
|
||||
"(|" + "".join(f"({attribute}=*{query}*)" for attribute in attributes) + ")"
|
||||
)
|
||||
return BaseBackend.get().query(cls, filter=filter, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def update_ldap_attributes(cls):
|
||||
all_object_classes = cls.ldap_object_classes()
|
||||
|
|
|
@ -65,3 +65,18 @@ class Backend(BaseBackend):
|
|||
instance._cache = {}
|
||||
|
||||
return instances
|
||||
|
||||
def fuzzy(self, model, query, attributes=None, **kwargs):
|
||||
attributes = attributes or model.attributes
|
||||
instances = self.query(model, **kwargs)
|
||||
|
||||
return [
|
||||
instance
|
||||
for instance in instances
|
||||
if any(
|
||||
query.lower() in value.lower()
|
||||
for attribute in attributes
|
||||
for value in model.listify(instance._state.get(attribute, []))
|
||||
if isinstance(value, str)
|
||||
)
|
||||
]
|
||||
|
|
|
@ -36,22 +36,6 @@ class MemoryModel(BackendModel):
|
|||
class_name or cls.__name__, {}
|
||||
).setdefault(attribute, {})
|
||||
|
||||
@classmethod
|
||||
def fuzzy(cls, query, attributes=None, **kwargs):
|
||||
attributes = attributes or cls.attributes
|
||||
instances = BaseBackend.get().query(cls, **kwargs)
|
||||
|
||||
return [
|
||||
instance
|
||||
for instance in instances
|
||||
if any(
|
||||
query.lower() in value.lower()
|
||||
for attribute in attributes
|
||||
for value in cls.listify(instance._state.get(attribute, []))
|
||||
if isinstance(value, str)
|
||||
)
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def get(cls, identifier=None, /, **kwargs):
|
||||
if identifier:
|
||||
|
|
|
@ -87,12 +87,6 @@ class BackendModel:
|
|||
implemented for every model and for every backend.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def fuzzy(cls, query, attributes=None, **kwargs):
|
||||
"""Works like :meth:`~canaille.backends.BaseBackend.query` but
|
||||
attribute values loosely be matched."""
|
||||
raise NotImplementedError()
|
||||
|
||||
@classmethod
|
||||
def get(cls, identifier=None, **kwargs):
|
||||
"""Works like :meth:`~canaille.backends.BaseBackend.query` but return
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from sqlalchemy import create_engine
|
||||
from sqlalchemy import or_
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.orm import declarative_base
|
||||
|
@ -78,3 +79,15 @@ class Backend(BaseBackend):
|
|||
.scalars()
|
||||
.all()
|
||||
)
|
||||
|
||||
def fuzzy(self, model, query, attributes=None, **kwargs):
|
||||
attributes = attributes or model.attributes
|
||||
filter = or_(
|
||||
getattr(model, attribute_name).ilike(f"%{query}%")
|
||||
for attribute_name in attributes
|
||||
if "str" in str(model.attributes[attribute_name])
|
||||
# erk, photo is an URL string according to SCIM, but bytes here
|
||||
and attribute_name != "photo"
|
||||
)
|
||||
|
||||
return self.db_session.execute(select(model).filter(filter)).scalars().all()
|
||||
|
|
|
@ -36,21 +36,6 @@ class SqlAlchemyModel(BackendModel):
|
|||
f"<{self.__class__.__name__} {self.identifier_attribute}={self.identifier}>"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def fuzzy(cls, query, attributes=None, **kwargs):
|
||||
attributes = attributes or cls.attributes
|
||||
filter = or_(
|
||||
getattr(cls, attribute_name).ilike(f"%{query}%")
|
||||
for attribute_name in attributes
|
||||
if "str" in str(cls.attributes[attribute_name])
|
||||
# erk, photo is an URL string according to SCIM, but bytes here
|
||||
and attribute_name != "photo"
|
||||
)
|
||||
|
||||
return (
|
||||
Backend.get().db_session.execute(select(cls).filter(filter)).scalars().all()
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def attribute_filter(cls, name, value):
|
||||
if isinstance(value, list):
|
||||
|
|
|
@ -144,28 +144,28 @@ def test_model_indexation(testclient, backend):
|
|||
|
||||
def test_fuzzy_unique_attribute(user, moderator, admin, backend):
|
||||
assert set(backend.query(models.User)) == {user, moderator, admin}
|
||||
assert set(models.User.fuzzy("Jack")) == {moderator}
|
||||
assert set(models.User.fuzzy("Jack", ["formatted_name"])) == {moderator}
|
||||
assert set(models.User.fuzzy("Jack", ["user_name"])) == set()
|
||||
assert set(models.User.fuzzy("Jack", ["user_name", "formatted_name"])) == {
|
||||
assert set(backend.fuzzy(models.User, "Jack")) == {moderator}
|
||||
assert set(backend.fuzzy(models.User, "Jack", ["formatted_name"])) == {moderator}
|
||||
assert set(backend.fuzzy(models.User, "Jack", ["user_name"])) == set()
|
||||
assert set(backend.fuzzy(models.User, "Jack", ["user_name", "formatted_name"])) == {
|
||||
moderator
|
||||
}
|
||||
assert set(models.User.fuzzy("moderator")) == {moderator}
|
||||
assert set(models.User.fuzzy("oderat")) == {moderator}
|
||||
assert set(models.User.fuzzy("oDeRat")) == {moderator}
|
||||
assert set(models.User.fuzzy("ack")) == {moderator}
|
||||
assert set(backend.fuzzy(models.User, "moderator")) == {moderator}
|
||||
assert set(backend.fuzzy(models.User, "oderat")) == {moderator}
|
||||
assert set(backend.fuzzy(models.User, "oDeRat")) == {moderator}
|
||||
assert set(backend.fuzzy(models.User, "ack")) == {moderator}
|
||||
|
||||
|
||||
def test_fuzzy_multiple_attribute(user, moderator, admin, backend):
|
||||
assert set(backend.query(models.User)) == {user, moderator, admin}
|
||||
assert set(models.User.fuzzy("jack@doe.com")) == {moderator}
|
||||
assert set(models.User.fuzzy("jack@doe.com", ["emails"])) == {moderator}
|
||||
assert set(models.User.fuzzy("jack@doe.com", ["formatted_name"])) == set()
|
||||
assert set(models.User.fuzzy("jack@doe.com", ["emails", "formatted_name"])) == {
|
||||
moderator
|
||||
}
|
||||
assert set(models.User.fuzzy("ack@doe.co")) == {moderator}
|
||||
assert set(models.User.fuzzy("doe.com")) == {user, moderator, admin}
|
||||
assert set(backend.fuzzy(models.User, "jack@doe.com")) == {moderator}
|
||||
assert set(backend.fuzzy(models.User, "jack@doe.com", ["emails"])) == {moderator}
|
||||
assert set(backend.fuzzy(models.User, "jack@doe.com", ["formatted_name"])) == set()
|
||||
assert set(
|
||||
backend.fuzzy(models.User, "jack@doe.com", ["emails", "formatted_name"])
|
||||
) == {moderator}
|
||||
assert set(backend.fuzzy(models.User, "ack@doe.co")) == {moderator}
|
||||
assert set(backend.fuzzy(models.User, "doe.com")) == {user, moderator, admin}
|
||||
|
||||
|
||||
def test_model_references(testclient, user, foo_group, admin, bar_group, backend):
|
||||
|
|
Loading…
Reference in a new issue