diff --git a/AUTHORS b/AUTHORS index 45a754cc..aa044bd2 100644 --- a/AUTHORS +++ b/AUTHORS @@ -252,3 +252,4 @@ that much better: * Paulo Amaral (https://github.com/pauloAmaral) * Gaurav Dadhania (https://github.com/GVRV) * Yurii Andrieiev (https://github.com/yandrieiev) + * Filip Kucharczyk (https://github.com/Pacu2) diff --git a/docs/changelog.rst b/docs/changelog.rst index 0b4893a6..102e826d 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -23,6 +23,7 @@ Development - Switch from nosetest to pytest as test runner #2114 - The codebase is now formatted using ``black``. #2109 - In bulk write insert, the detailed error message would raise in exception. +- Added ability to compare Q and Q operations #2204 Changes in 0.18.2 ================= diff --git a/mongoengine/queryset/visitor.py b/mongoengine/queryset/visitor.py index 0fe139fd..058c722a 100644 --- a/mongoengine/queryset/visitor.py +++ b/mongoengine/queryset/visitor.py @@ -96,9 +96,11 @@ class QNode(object): """Combine this node with another node into a QCombination object. """ + # If the other Q() is empty, ignore it and just use `self`. if getattr(other, "empty", True): return self + # Or if this Q is empty, ignore it and just use `other`. if self.empty: return other @@ -146,6 +148,13 @@ class QCombination(QNode): def empty(self): return not bool(self.children) + def __eq__(self, other): + return ( + self.__class__ == other.__class__ + and self.operation == other.operation + and self.children == other.children + ) + class Q(QNode): """A simple query object, used in a query tree to build up more complex @@ -164,3 +173,6 @@ class Q(QNode): @property def empty(self): return not bool(self.query) + + def __eq__(self, other): + return self.__class__ == other.__class__ and self.query == other.query diff --git a/tests/document/test_instance.py b/tests/document/test_instance.py index 1d3e18d0..173e02f2 100644 --- a/tests/document/test_instance.py +++ b/tests/document/test_instance.py @@ -3644,7 +3644,7 @@ class TestInstance(MongoDBTestCase): User.objects().select_related() def test_embedded_document_failed_while_loading_instance_when_it_is_not_a_dict( - self + self, ): class LightSaber(EmbeddedDocument): color = StringField() diff --git a/tests/queryset/test_visitor.py b/tests/queryset/test_visitor.py index e597e3d8..afa00839 100644 --- a/tests/queryset/test_visitor.py +++ b/tests/queryset/test_visitor.py @@ -374,6 +374,39 @@ class TestQ(unittest.TestCase): == 2 ) + def test_equality(self): + assert Q(name="John") == Q(name="John") + assert Q() == Q() + + def test_inequality(self): + assert Q(name="John") != Q(name="Ralph") + + def test_operation_equality(self): + q1 = Q(name="John") | Q(title="Sir") & Q(surname="Paul") + q2 = Q(name="John") | Q(title="Sir") & Q(surname="Paul") + assert q1 == q2 + + def test_operation_inequality(self): + q1 = Q(name="John") | Q(title="Sir") + q2 = Q(title="Sir") | Q(name="John") + assert q1 != q2 + + def test_combine_and_empty(self): + q = Q(x=1) + assert q & Q() == q + assert Q() & q == q + + def test_combine_and_both_empty(self): + assert Q() & Q() == Q() + + def test_combine_or_empty(self): + q = Q(x=1) + assert q | Q() == q + assert Q() | q == q + + def test_combine_or_both_empty(self): + assert Q() | Q() == Q() + if __name__ == "__main__": unittest.main()