diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6101a2b02..a1c5d6843 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -42,7 +42,7 @@ jobs: makepot: "true" services: postgres: - image: postgres:13 + image: postgis/postgis:13-3.4 env: POSTGRES_USER: odoo POSTGRES_PASSWORD: odoo diff --git a/base_geoengine/README.rst b/base_geoengine/README.rst index 0c3a30ad2..518ab5557 100644 --- a/base_geoengine/README.rst +++ b/base_geoengine/README.rst @@ -21,13 +21,13 @@ Geospatial support for Odoo :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fgeospatial-lightgray.png?logo=github - :target: https://github.com/OCA/geospatial/tree/18.0/base_geoengine + :target: https://github.com/OCA/geospatial/tree/19.0/base_geoengine :alt: OCA/geospatial .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/geospatial-18-0/geospatial-18-0-base_geoengine + :target: https://translation.odoo-community.org/projects/geospatial-19-0/geospatial-19-0-base_geoengine :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png - :target: https://runboat.odoo-community.org/builds?repo=OCA/geospatial&target_branch=18.0 + :target: https://runboat.odoo-community.org/builds?repo=OCA/geospatial&target_branch=19.0 :alt: Try me on Runboat |badge1| |badge2| |badge3| |badge4| |badge5| @@ -306,7 +306,7 @@ Bug Tracker Bugs are tracked on `GitHub Issues `_. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -363,6 +363,6 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use. -This module is part of the `OCA/geospatial `_ project on GitHub. +This module is part of the `OCA/geospatial `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/base_geoengine/__init__.py b/base_geoengine/__init__.py index 205ee0f38..5985c83ac 100644 --- a/base_geoengine/__init__.py +++ b/base_geoengine/__init__.py @@ -4,3 +4,4 @@ from . import geo_convertion_helper from . import geo_operators from .geo_db import init_postgis +from . import domains diff --git a/base_geoengine/__manifest__.py b/base_geoengine/__manifest__.py index f87a4d562..639c24069 100644 --- a/base_geoengine/__manifest__.py +++ b/base_geoengine/__manifest__.py @@ -4,7 +4,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). { "name": "Geospatial support for Odoo", - "version": "18.0.1.0.1", + "version": "19.0.1.0.1", "category": "GeoBI", "author": "Camptocamp,ACSONE SA/NV,Odoo Community Association (OCA)", "license": "AGPL-3", diff --git a/base_geoengine/domains.py b/base_geoengine/domains.py new file mode 100644 index 000000000..61df5530a --- /dev/null +++ b/base_geoengine/domains.py @@ -0,0 +1,149 @@ +import contextlib +import logging +import warnings + +from odoo.fields import Domain +from odoo.models import BaseModel +from odoo.orm import domains +from odoo.orm.domains import ( + CONDITION_OPERATORS, + NEGATIVE_CONDITION_OPERATORS, + SQL, + DomainCondition, + OptimizationLevel, + Query, +) +from odoo.orm.identifiers import NewId + +_logger = logging.getLogger(__name__) + +GEO_OPERATORS = frozenset( + [ + "geo_greater", + "geo_lesser", + "geo_equal", + "geo_touch", + "geo_within", + "geo_contains", + "geo_intersect", + ] +) + + +def checked(self) -> DomainCondition: + """Validate `self` and return it if correct, otherwise raise an exception.""" + if not isinstance(self.field_expr, str) or not self.field_expr: + self._raise("Empty field name", error=TypeError) + operator = self.operator.lower() + if operator != self.operator: + warnings.warn( + ( + f"Deprecated since 19.0, the domain condition " + f"{(self.field_expr, self.operator, self.value)!r} " + f"should have a lower-case operator" + ), + DeprecationWarning, + # + stacklevel=2, + # + ) + return DomainCondition(self.field_expr, operator, self.value).checked() + if operator not in CONDITION_OPERATORS: + # + if operator not in GEO_OPERATORS: + # + self._raise("Invalid operator") + + # check already the consistency for domain manipulation + # these are common mistakes and optimizations, + # do them here to avoid recreating the domain + # - NewId is not a value + # - records are not accepted, use values + # - Query and Domain values should be using a relational operator + # + # from .models import BaseModel # noqa: PLC0415 + # + + value = self.value + if value is None: + value = False + elif isinstance(value, NewId): + _logger.warning( + "Domains don't support NewId, use .ids instead, for %r", + (self.field_expr, self.operator, self.value), + ) + operator = "not in" if operator in NEGATIVE_CONDITION_OPERATORS else "in" + value = [] + elif isinstance(value, BaseModel): + _logger.warning( + "The domain condition %r should not have a value which is a model", + (self.field_expr, self.operator, self.value), + ) + value = value.ids + elif isinstance(value, (Domain, Query, SQL)) and operator not in ( + "any", + "not any", + "any!", + "not any!", + "in", + "not in", + ): + # accept SQL object in the right part for simple operators + # use case: compare 2 fields + _logger.warning( + "The domain condition %r should use the 'any' or 'not any' operator.", + (self.field_expr, self.operator, self.value), + ) + if value is not self.value: + return DomainCondition(self.field_expr, operator, value) + return self + + +def _to_sql(self, model: BaseModel, alias: str, query: Query) -> SQL: + """Enhanced _to_sql that handles geospatial operators.""" + field_expr, operator, value = self.field_expr, self.operator, self.value + + # Only handle geospatial operators here, delegate everything else to original method + if operator in GEO_OPERATORS: + # Ensure geospatial conditions are fully optimized + assert self._opt_level >= OptimizationLevel.FULL, ( + "Must fully optimize before generating the query " + f"{(field_expr, operator, value)}" + ) + + field = self._field(model) + model._check_field_access(field, "read") + return field.condition_to_sql(field_expr, operator, value, model, alias, query) + + # For all other operators, use the original method + return original__to_sql(self, model, alias, query) + + +def _optimize_step(self, model: BaseModel, level: OptimizationLevel) -> Domain: + """Optimization step for geospatial operators.""" + # For geospatial operators, we need to handle them specially during optimization + # If this is a geospatial operator, mark it as optimized at FULL level + if self.operator in GEO_OPERATORS: + # Perform basic validation and normalization + with contextlib.suppress(Exception): + field = self._field(model) + # Basic geospatial operator validation + if hasattr(field, "geo_type"): # It's a geospatial field + # Create optimized version with FULL level + optimized = DomainCondition(self.field_expr, self.operator, self.value) + object.__setattr__(optimized, "_opt_level", OptimizationLevel.FULL) + return optimized + + # Fall back to original optimization for non-geo operators + return original__optimize_step(self, model, level) + + +# Store original methods before monkey patching +original__optimize_step = DomainCondition._optimize_step +original__to_sql = DomainCondition._to_sql + +DomainCondition.checked = checked +DomainCondition._to_sql = _to_sql +DomainCondition._optimize_step = _optimize_step + +domains.CONDITION_OPERATORS = domains.CONDITION_OPERATORS.union(GEO_OPERATORS) diff --git a/base_geoengine/expressions.py b/base_geoengine/expressions.py index f6ed0f740..cc22d5187 100644 --- a/base_geoengine/expressions.py +++ b/base_geoengine/expressions.py @@ -1,18 +1,21 @@ # Copyright 2023 ACSONE SA/NV # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import logging import random import string +from odoo import fields +from odoo.fields import Domain from odoo.models import BaseModel -from odoo.osv import expression -from odoo.osv.expression import TERM_OPERATORS from odoo.tools import SQL, Query from .fields import GeoField from .geo_operators import GeoOperator -original___condition_to_sql = BaseModel._condition_to_sql +logger = logging.getLogger(__name__) + +original___condition_to_sql = fields.Field._condition_to_sql GEO_OPERATORS = { "geo_greater": ">", @@ -23,6 +26,7 @@ "geo_contains": "ST_Contains", "geo_intersect": "ST_Intersects", } + GEO_SQL_OPERATORS = { "geo_greater": SQL(">"), "geo_lesser": SQL("<"), @@ -32,23 +36,23 @@ "geo_contains": SQL("ST_Contains"), "geo_intersect": SQL("ST_Intersects"), } -term_operators_list = list(TERM_OPERATORS) -for op in GEO_OPERATORS: - term_operators_list.append(op) - -expression.TERM_OPERATORS = tuple(term_operators_list) -expression.SQL_OPERATORS.update(GEO_SQL_OPERATORS) def _condition_to_sql( - self, alias: str, fname: str, operator: str, value, query: Query + self, + field_expr: str, + operator: str, + value, + model: BaseModel, + alias: str, + query: Query, ) -> SQL: """ This method has been monkey patched in order to be able to include geo_operators into the Odoo search method. """ if operator in GEO_OPERATORS.keys(): - current_field = self._fields.get(fname) + current_field = model._fields.get(field_expr) current_operator = GeoOperator(current_field) if current_field and isinstance(current_field, GeoField): params = [] @@ -59,9 +63,9 @@ def _condition_to_sql( sub_queries = [] for key in ref_search: i = key.rfind(".") - rel_model = key[0:i] + rel_model_name = key[0:i] rel_col = key[i + 1 :] - rel_model = self.env[rel_model] + rel_model = model.env[rel_model_name] # we compute the attributes search on spatial rel if ref_search[key]: rel_alias = ( @@ -75,42 +79,52 @@ def _condition_to_sql( active_test=True, alias=rel_alias, ) - self._apply_ir_rules(rel_query, "read") + model._check_field_access(current_field, "read") if operator == "geo_equal": rel_query.add_where( - f'"{alias}"."{fname}" {GEO_OPERATORS[operator]} ' + f'"{alias}"."{field_expr}" {GEO_OPERATORS[operator]} ' f"{rel_alias}.{rel_col}" ) elif operator in ("geo_greater", "geo_lesser"): rel_query.add_where( - f"ST_Area({alias}.{fname}) {GEO_OPERATORS[operator]} " + f"ST_Area({alias}.{field_expr}) " + f"{GEO_OPERATORS[operator]} " f"ST_Area({rel_alias}.{rel_col})" ) else: rel_query.add_where( - f'{GEO_OPERATORS[operator]}("{alias}"."{fname}", ' + f'{GEO_OPERATORS[operator]}("{alias}"."{field_expr}", ' f"{rel_alias}.{rel_col})" ) - subquery, subparams = rel_query.subselect("1") + subquery_sql = rel_query.subselect("1") sub_query_mogrified = ( - self.env.cr.mogrify(subquery, subparams) + model.env.cr.mogrify(subquery_sql.code, subquery_sql.params) .decode("utf-8") .replace(f"'{rel_model._table}'", f'"{rel_model._table}"') .replace("%", "%%") ) sub_queries.append(f"EXISTS({sub_query_mogrified})") - query = " AND ".join(sub_queries) + query_str = " AND ".join(sub_queries) else: - query = get_geo_func( - current_operator, operator, fname, value, params, self._table + query_str = get_geo_func( + current_operator, operator, field_expr, value, params, model._table ) - return SQL(query, *params) + return SQL(query_str, *params) return original___condition_to_sql( - self, alias=alias, fname=fname, operator=operator, value=value, query=query + self, + field_expr=field_expr, + operator=operator, + value=value, + model=model, + alias=alias, + query=query, ) +fields.Field._condition_to_sql = _condition_to_sql + + def get_geo_func(current_operator, operator, left, value, params, table): """ This method will call the SQL query corresponding to the requested geo operator @@ -149,8 +163,12 @@ def where_calc(model, domain, active_test=True, alias=None): query = Query(model.env, alias, model._table) if domain: - return expression.expression(domain, model, alias=alias, query=query).query - return query + # In Odoo 19, create Domain object and use its _to_sql method + domain_obj = Domain(domain) + optimized_domain = domain_obj.optimize_full(model) + sql_condition = optimized_domain._to_sql(model, alias, query) + query.add_where(sql_condition) + return query -BaseModel._condition_to_sql = _condition_to_sql + return query diff --git a/base_geoengine/fields.py b/base_geoengine/fields.py index 300eaeb1d..1cd79f843 100644 --- a/base_geoengine/fields.py +++ b/base_geoengine/fields.py @@ -1,12 +1,16 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. # Copyright 2011-2012 Nicolas Bessi (Camptocamp SA) # Copyright 2016 Yannick Payot (Camptocamp SA) # Copyright 2023 ACSONE SA/NV # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from __future__ import annotations + import json import logging from operator import attrgetter -from odoo import _, fields +from odoo import fields from odoo.tools import sql from . import geo_convertion_helper as convert @@ -110,7 +114,7 @@ def entry_to_shape(self, value, same_type=False): shape = convert.value_to_shape(value) if same_type and not shape.is_empty: if shape.geom_type.lower() != self.geo_type.lower(): - msg = _( + msg = self.env._( "Geo Value %(geom_type)s must be of the same type %(geo_type)s \ as fields", geom_type=shape.geom_type.lower(), @@ -121,7 +125,7 @@ def entry_to_shape(self, value, same_type=False): def update_geo_db_column(self, model): """Update the column type in the database.""" - cr = model._cr + cr = model.env.cr query = """SELECT srid, type, coord_dimension FROM geometry_columns WHERE f_table_name = %s @@ -130,14 +134,14 @@ def update_geo_db_column(self, model): check_data = cr.fetchone() if not check_data: raise TypeError( - _( + self.env._( "geometry_columns table seems to be corrupted." " SRID check is not possible" ) ) if check_data[0] != self.srid: raise TypeError( - _( + self.env._( "Reprojection of column is not implemented." " We can not change srid %(srid)s to %(data)s", srid=self.srid, @@ -146,7 +150,7 @@ def update_geo_db_column(self, model): ) elif check_data[1] != self.geo_type.upper(): raise TypeError( - _( + self.env._( "Geo type modification is not implemented." " We can not change type %(data)s to %(geo_type)s", data=check_data[1], @@ -155,7 +159,7 @@ def update_geo_db_column(self, model): ) elif check_data[2] != self.dim: raise TypeError( - _( + self.env._( "Geo dimention modification is not implemented." " We can not change dimention %(data)s to %(dim)s", data=check_data[2], @@ -179,7 +183,7 @@ def update_db_column(self, model, column): if not column: create_geo_column( - model._cr, + model.env.cr, model._table, self.name, self.geo_type.upper(), @@ -188,28 +192,30 @@ def update_db_column(self, model, column): self.string, ) if self.gist_index: - create_geo_index(model._cr, self.name, model._table) + create_geo_index(model.env.cr, self.name, model._table) return if column["udt_name"] == self.column_type[0]: if self.gist_index: - create_geo_index(model._cr, self.name, model._table) + create_geo_index(model.env.cr, self.name, model._table) return self.update_geo_db_column(model) if column["udt_name"] in self.column_cast_from: - sql.convert_column(model._cr, model._table, self.name, self.column_type[1]) + sql.convert_column( + model.env.cr, model._table, self.name, self.column_type[1] + ) else: newname = (self.name + "_moved{}").format i = 0 - while sql.column_exists(model._cr, model._table, newname(i)): + while sql.column_exists(model.env.cr, model._table, newname(i)): i += 1 if column["is_nullable"] == "NO": - sql.drop_not_null(model._cr, model._table, self.name) - sql.rename_column(model._cr, model._table, self.name, newname(i)) + sql.drop_not_null(model.env.cr, model._table, self.name) + sql.rename_column(model.env.cr, model._table, self.name, newname(i)) sql.create_column( - model._cr, model._table, self.name, self.column_type[1], self.string + model.env.cr, model._table, self.name, self.column_type[1], self.string ) diff --git a/base_geoengine/geo_convertion_helper.py b/base_geoengine/geo_convertion_helper.py index faa015c3f..f884349ed 100644 --- a/base_geoengine/geo_convertion_helper.py +++ b/base_geoengine/geo_convertion_helper.py @@ -4,6 +4,8 @@ from odoo import _ +logger = logging.getLogger(__name__) + try: import geojson from shapely import wkb, wkt @@ -11,7 +13,7 @@ from shapely.geometry.base import BaseGeometry except ImportError: logger = logging.getLogger(__name__) - logger.warning(_("Shapely or geojson are not available in the sys path")) + logger.warning(_("Shapely or geojson are not available in the sys path")) # pylint: disable=prefer-env-translation def value_to_shape(value, use_wkb=False): @@ -35,7 +37,7 @@ def value_to_shape(value, use_wkb=False): return wkt.loads(value.wkt) else: raise TypeError( - _( + _( # pylint: disable=prefer-env-translation "Write/create/search geo type must be wkt/geojson " "string or must respond to wkt" ) diff --git a/base_geoengine/geo_db.py b/base_geoengine/geo_db.py index 9c8e4b633..65221a019 100644 --- a/base_geoengine/geo_db.py +++ b/base_geoengine/geo_db.py @@ -4,7 +4,6 @@ import logging -from odoo import _ from odoo.exceptions import MissingError from odoo.tools import sql @@ -41,7 +40,7 @@ def init_postgis(env): ) except Exception as exc: raise MissingError( - _( + env._( "Error, can not automatically initialize spatial postgis" " support. Database user may have to be superuser and" " postgres/postgis extensions with their devel header have" diff --git a/base_geoengine/models/base.py b/base_geoengine/models/base.py index e02b021d2..3bbe96188 100644 --- a/base_geoengine/models/base.py +++ b/base_geoengine/models/base.py @@ -4,9 +4,9 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). import logging -from odoo import _, api, models +from odoo import api, models from odoo.exceptions import MissingError, UserError -from odoo.osv.expression import AND +from odoo.fields import Domain from .. import fields as geo_fields @@ -53,12 +53,12 @@ def _get_geo_view(self): limit=1, ) if not geo_view: - raise UserError( - _( - "No GeoEngine view defined for the model %s. \ + message = self.env._( + "No GeoEngine view defined for the model %s. \ Please create a view or modify view mode" - ) - % self._name, + ) + raise UserError( + message % self._name, ) return geo_view @@ -111,9 +111,8 @@ def get_edit_info_for_geo_column(self, column): field = self._fields.get(column) if not field or not isinstance(field, geo_fields.GeoField): - raise ValueError( - _("%s column does not exists or is not a geo field") % column - ) + message = self.env._("%s column does not exists or is not a geo field") + raise ValueError(message % column) view = self._get_geo_view() raster = raster_obj.search( [("view_id", "=", view.id), ("use_to_edit", "=", True)], limit=1 @@ -121,7 +120,8 @@ def get_edit_info_for_geo_column(self, column): if not raster: raster = raster_obj.search([("view_id", "=", view.id)], limit=1) if not raster: - raise MissingError(_("No raster layer for view %s") % (view.name,)) + message = self.env._("No raster layer for view %s") + raise MissingError(message % (view.name,)) return { "edit_raster": raster.read()[0], "srid": field.srid, @@ -157,17 +157,21 @@ def geo_search( # Limit and offset are managed after, we may loose a lot of performance # here _logger.debug( - _("geo_search is deprecated: uses search method defined on base model") + self.env._( + "geo_search is deprecated: uses search method defined on base model" + ) ) domain = domain or [] geo_domain = geo_domain or [] search_domain = domain or [] if domain and geo_domain: - search_domain = AND([domain, geo_domain]) + search_domain = Domain.AND([domain, geo_domain]) elif geo_domain: search_domain = geo_domain if not search_domain: - raise ValueError(_("You must at least provide one of domain or geo_domain")) + raise ValueError( + self.env._("You must at least provide one of domain or geo_domain") + ) return self.search(search_domain, limit=limit, offset=offset, order=order) diff --git a/base_geoengine/models/geo_vector_layer.py b/base_geoengine/models/geo_vector_layer.py index f7c90460c..0fc80c378 100644 --- a/base_geoengine/models/geo_vector_layer.py +++ b/base_geoengine/models/geo_vector_layer.py @@ -3,7 +3,7 @@ # Copyright 2023 ACSONE SA/NV # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import _, api, fields, models +from odoo import api, fields, models from odoo.exceptions import ValidationError SUPPORTED_ATT = [ @@ -99,7 +99,7 @@ def _check_geo_field_id(self): if rec.model_id: if not rec.geo_field_id.model_id == rec.model_id: raise ValidationError( - _( + self.env._( "The geo_field_id must be a field in %s model", rec.model_id.display_name, ) @@ -118,7 +118,7 @@ def _check_geo_repr(self): or rec.geo_repr == "proportion" ): raise ValidationError( - _( + self.env._( "You need to select a numeric field", ) ) @@ -129,7 +129,7 @@ def _check_if_attribute_in_geo_field(self): if rec.attribute_field_id and rec.geo_field_id: if rec.attribute_field_id.model != rec.geo_field_id.model: raise ValidationError( - _( + self.env._( "You need to provide an attribute that exists in %s model", rec.geo_field_id.model_id.display_name, ) diff --git a/base_geoengine/security/data.xml b/base_geoengine/security/data.xml index 066eead19..f14235ee3 100644 --- a/base_geoengine/security/data.xml +++ b/base_geoengine/security/data.xml @@ -7,7 +7,7 @@ Geoengine Admin - + diff --git a/base_geoengine/static/description/index.html b/base_geoengine/static/description/index.html index 314155244..03fc525c3 100644 --- a/base_geoengine/static/description/index.html +++ b/base_geoengine/static/description/index.html @@ -374,7 +374,7 @@

Geospatial support for Odoo

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! source digest: sha256:a22713167bb39e1995e9bc618b29382cd03bba76a910d58cf62993288b0a385b !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

Beta License: AGPL-3 OCA/geospatial Translate me on Weblate Try me on Runboat

+

Beta License: AGPL-3 OCA/geospatial Translate me on Weblate Try me on Runboat

GeoEngine is an Odoo module that adds spatial/GIS capabilites to Odoo. It will allow you to :

    @@ -660,7 +660,7 @@

    Bug Tracker

    Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed -feedback.

    +feedback.

    Do not contact contributors directly about support or help with technical issues.

    @@ -713,7 +713,7 @@

    Maintainers

    OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.

    -

    This module is part of the OCA/geospatial project on GitHub.

    +

    This module is part of the OCA/geospatial project on GitHub.

    You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

    diff --git a/base_geoengine/static/src/js/views/geoengine/geoengine_renderer/geoengine_renderer.esm.js b/base_geoengine/static/src/js/views/geoengine/geoengine_renderer/geoengine_renderer.esm.js index c6aa0c346..cde519140 100644 --- a/base_geoengine/static/src/js/views/geoengine/geoengine_renderer/geoengine_renderer.esm.js +++ b/base_geoengine/static/src/js/views/geoengine/geoengine_renderer/geoengine_renderer.esm.js @@ -985,7 +985,7 @@ export class GeoengineRenderer extends Component { openGroupsByDefault: true, domain: [], orderBy: [], - groupBy: {}, + groupBy: [], resModel: model, fields: fields, }; diff --git a/base_geoengine/tests/test_model.py b/base_geoengine/tests/test_model.py index 114212d0a..8afba32f9 100644 --- a/base_geoengine/tests/test_model.py +++ b/base_geoengine/tests/test_model.py @@ -1,10 +1,10 @@ # Copyright 2023 ACSONE SA/NV import geojson -from odoo_test_helper import FakeModelLoader from shapely import wkt from shapely.geometry import shape +from odoo.orm.model_classes import add_to_registry from odoo.tests.common import TransactionCase from ..fields import GeoPoint @@ -14,12 +14,24 @@ class TestModel(TransactionCase): @classmethod def setUpClass(cls): super().setUpClass() - cls.loader = FakeModelLoader(cls.env, cls.__module__) - cls.loader.backup_registry() - from .models import DummyZip, GeoModelTest, RetailMachine - cls.loader.update_registry((GeoModelTest, DummyZip, RetailMachine)) + add_to_registry(cls.registry, GeoModelTest) + cls.registry._setup_models__(cls.env.cr, ["geo.model.test"]) + cls.registry.init_models( + cls.env.cr, ["geo.model.test"], {"models_to_check": True} + ) + + add_to_registry(cls.registry, DummyZip) + cls.registry._setup_models__(cls.env.cr, ["dummy.zip"]) + cls.registry.init_models(cls.env.cr, ["dummy.zip"], {"models_to_check": True}) + + add_to_registry(cls.registry, RetailMachine) + cls.registry._setup_models__(cls.env.cr, ["retail.machine"]) + cls.registry.init_models( + cls.env.cr, ["retail.machine"], {"models_to_check": True} + ) + cls.geo_model = cls.env["geo.model.test"].create({}) cls.env["dummy.zip"].create( { @@ -145,7 +157,10 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - cls.loader.restore_registry() + cls.addClassCleanup(cls.registry.__delitem__, "geo.model.test") + cls.addClassCleanup(cls.registry.__delitem__, "retail.machine") + cls.addClassCleanup(cls.registry.__delitem__, "dummy.zip") + super().tearDownClass() def test_create_multipolygon_wkt_format(self):