Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions base_geoengine/expressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import random
import string

from odoo import models
from odoo.osv import expression
from odoo.osv.expression import TERM_OPERATORS
from odoo.tools import SQL, Query
Expand All @@ -27,6 +28,31 @@
term_operators_list.append(op)

expression.TERM_OPERATORS = tuple(term_operators_list)
_original_filtered_domain = models.BaseModel.filtered_domain


def _filtered_domain_geo(self, domain):
"""Patch filtered_domain to support geo operators.

The original method evaluates domains in Python and raises ValueError
for unknown operators. Geo operators can only be resolved via SQL
(PostGIS), so we fall back to a search() call — the same strategy Odoo
uses for child_of / parent_of.
"""
if not domain or not self:
return _original_filtered_domain(self, domain)

has_geo = any(
isinstance(leaf, (list | tuple)) and len(leaf) == 3 and leaf[1] in GEO_OPERATORS
for leaf in domain
)
if not has_geo:
return _original_filtered_domain(self, domain)

return self.search([("id", "in", self.ids)] + domain, order="id")


models.BaseModel.filtered_domain = _filtered_domain_geo


def __leaf_to_sql(self, leaf, model, alias):
Expand Down
83 changes: 83 additions & 0 deletions base_geoengine/tests/test_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -703,3 +703,86 @@ def test_deprecated_geo_search__intersect_for_zip_1169_with_dict(self):
]
)
self.assertEqual(len(result), 2)

def test_filtered_domain_with_geo_within(self):
"""filtered_domain must not raise ValueError for geo_within."""
retails = self.env["retail.machine"].search([])
zip_yens = self.env["dummy.zip"].search([("city", "ilike", "Yens")])
domain = [
(
"the_point",
"geo_within",
{"dummy.zip.the_geom": [("id", "=", zip_yens.id)]},
)
]
result = retails.filtered_domain(domain)
self.assertEqual(len(result), 2)
self.assertEqual(set(result.mapped("name")), {"34", "33"})

def test_filtered_domain_with_geo_intersect(self):
"""filtered_domain must not raise ValueError for geo_intersect."""
retails = self.env["retail.machine"].search([])
zip_mollens = self.env["dummy.zip"].search([("city", "ilike", "Mollens (VD))")])
domain = [("the_point", "geo_intersect", zip_mollens.the_geom)]
result = retails.filtered_domain(domain)
self.assertEqual(len(result), 3)

def test_filtered_domain_geo_with_negation(self):
"""filtered_domain handles negated geo operators via SQL fallback."""
retails = self.env["retail.machine"].search([])
zip_yens = self.env["dummy.zip"].search([("city", "ilike", "Yens")])
domain = [
"!",
(
"the_point",
"geo_within",
{"dummy.zip.the_geom": [("id", "=", zip_yens.id)]},
),
]
result = retails.filtered_domain(domain)
self.assertEqual(len(result), 3)
self.assertFalse(set(result.mapped("name")) & {"34", "33"})

def test_filtered_domain_non_geo_unchanged(self):
"""Non-geo domains still use the original Python evaluation."""
retails = self.env["retail.machine"].search([])
domain = [("name", "=", "34")]
result = retails.filtered_domain(domain)
self.assertEqual(len(result), 1)
self.assertEqual(result.name, "34")

def test_filtered_domain_empty_domain(self):
"""Empty domain returns self unchanged."""
retails = self.env["retail.machine"].search([])
result = retails.filtered_domain([])
self.assertEqual(result, retails)

def test_filtered_domain_empty_recordset(self):
"""Empty recordset returns empty regardless of geo domain."""
retails = self.env["retail.machine"].browse()
zip_yens = self.env["dummy.zip"].search([("city", "ilike", "Yens")])
domain = [
(
"the_point",
"geo_within",
{"dummy.zip.the_geom": [("id", "=", zip_yens.id)]},
)
]
result = retails.filtered_domain(domain)
self.assertFalse(result)

def test_filtered_domain_mixed_geo_and_standard(self):
"""Domain combining geo and standard operators works correctly."""
retails = self.env["retail.machine"].search([])
zip_yens = self.env["dummy.zip"].search([("city", "ilike", "Yens")])
domain = [
("money_level", "=", "low"),
(
"the_point",
"geo_within",
{"dummy.zip.the_geom": [("id", "=", zip_yens.id)]},
),
]
result = retails.filtered_domain(domain)
self.assertEqual(len(result), 2)
self.assertTrue(all(r.money_level == "low" for r in result))
Loading