diff --git a/mongoengine/fields.py b/mongoengine/fields.py index 25bf37ef..fd8236a6 100644 --- a/mongoengine/fields.py +++ b/mongoengine/fields.py @@ -157,10 +157,17 @@ class StringField(BaseField): regex = r"%s$" elif op == "exact": regex = r"^%s$" + elif op == "has_word": + regex = r"\b%s\b" + elif op == "regex": + regex = value # escape unsafe characters which could lead to a re.error - value = re.escape(value) - value = re.compile(regex % value, flags) + if op == 'regex': + value = re.compile(regex, flags) + else: + value = re.escape(value) + value = re.compile(regex % value, flags) return super().prepare_query_value(op, value) diff --git a/mongoengine/queryset/transform.py b/mongoengine/queryset/transform.py index 4c3d051b..375cceb4 100644 --- a/mongoengine/queryset/transform.py +++ b/mongoengine/queryset/transform.py @@ -51,6 +51,10 @@ STRING_OPERATORS = ( "iendswith", "exact", "iexact", + "regex", + "iregex", + "has_word", + "ihas_word", ) CUSTOM_OPERATORS = ("match",) MATCH_OPERATORS = ( diff --git a/tests/queryset/test_queryset.py b/tests/queryset/test_queryset.py index 1a15a114..cde7dc10 100644 --- a/tests/queryset/test_queryset.py +++ b/tests/queryset/test_queryset.py @@ -1257,6 +1257,35 @@ class TestQueryset(unittest.TestCase): obj = self.Person.objects(name__iexact="gUIDO VAN rOSSU").first() assert obj is None + + # Test has_word + obj = self.Person.objects(name__has_word="Guido").first() + assert obj == person + obj = self.Person.objects(name__has_word="rossum").first() + assert obj is None + obj = self.Person.objects(name__has_word="Rossu").first() + assert obj is None + + # Test ihas_word + obj = self.Person.objects(name__ihas_word="rOSSUM").first() + assert obj == person + obj = self.Person.objects(name__ihas_word="rOSSU").first() + assert obj is None + + # Test regex + obj = self.Person.objects(name__regex="^[Guido].*[Rossum]$").first() + assert obj == person + obj = self.Person.objects(name__regex="^[guido].*[rossum]$").first() + assert obj is None + obj = self.Person.objects(name__regex="^[uido].*[Rossum]$").first() + assert obj is None + + # Test iregex + obj = self.Person.objects(name__iregex="^[guido].*[rossum]$").first() + assert obj == person + obj = self.Person.objects(name__iregex="^[Uido].*[Rossum]$").first() + assert obj is None + # Test unsafe expressions person = self.Person(name="Guido van Rossum [.'Geek']") person.save() @@ -1341,7 +1370,12 @@ class TestQueryset(unittest.TestCase): person.save() people = self.Person.objects - people = people.filter(name__startswith="Gui").filter(name__not__endswith="tum") + people = people.filter(name__startswith="Gui")\ + .filter(name__not__endswith="tum")\ + .filter(name__icontains="VAN")\ + .filter(name__regex="^Guido")\ + .filter(name__has_word="Guido")\ + .filter(name__has_word="van") assert people.count() == 1 def assertSequence(self, qs, expected):