Initial commit: OCA Technical packages (595 packages)

This commit is contained in:
Ernad Husremovic 2025-08-29 15:43:03 +02:00
commit 2cc02aac6e
24950 changed files with 2318079 additions and 0 deletions

View file

@ -0,0 +1,25 @@
##############################################################################
#
# Author: Nicolas Bessi
# Copyright 2011-2012 Camptocamp SA
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
"""Module that manages map view and vector/raster layer"""
from . import base
from . import geo_raster_layer
from . import geo_vector_layer
from . import ir_view
from . import ir_model

View file

@ -0,0 +1,172 @@
# 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).
import logging
from odoo import _, api, models
from odoo.exceptions import MissingError, UserError
from odoo.osv.expression import AND
from .. import fields as geo_fields
DEFAULT_EXTENT = (
"-123164.85222423, 5574694.9538936, " "1578017.6490538, 6186191.1800898"
)
_logger = logging.getLogger(__name__)
class Base(models.AbstractModel):
"""Extend Base class for to allow definition of geo fields."""
_inherit = "base"
# Array of ash that define layer and data to use
_georepr = []
@api.model
def fields_get(self, allfields=None, attributes=None):
"""Add geo_type definition for geo fields"""
res = super().fields_get(allfields=allfields, attributes=attributes)
for f_name in res:
field = self._fields.get(f_name)
if field and field.type.startswith("geo_"):
geo_type = {
"type": field.type,
"dim": int(field.dim),
"srid": field.srid,
"geo_type": field.geo_type,
}
# TODO
if field.compute or field.related:
if not field.dim:
geo_type["dim"] = 2
if not field.srid:
geo_type["srid"] = 3857
res[f_name]["geo_type"] = geo_type
return res
@api.model
def _get_geo_view(self):
IrView = self.env["ir.ui.view"]
geo_view = IrView.sudo().search(
[("model", "=", self._name), ("type", "=", "geoengine")],
limit=1,
)
if not geo_view:
raise UserError(
_("No GeoEngine view defined for the model %s") % self._name,
_("Please create a view or modify view mode"),
)
return geo_view
@api.model
def set_field_real_name(self, in_tuple):
field_obj = self.env["ir.model.fields"]
if not in_tuple:
return in_tuple
name = field_obj.browse(in_tuple[0]).name
out = (in_tuple[0], name, in_tuple[1])
return out
@api.model
def get_geoengine_layers(self, view_id=None, view_type="geoengine", **options):
view_obj = self.env["ir.ui.view"]
if not view_id:
view = self._get_geo_view()
else:
view = view_obj.browse(view_id)
geoengine_layers = {
"backgrounds": [],
"actives": [],
"projection": view.projection,
"restricted_extent": view.restricted_extent,
"default_extent": view.default_extent or DEFAULT_EXTENT,
"default_zoom": view.default_zoom,
}
for layer in view.raster_layer_ids:
layer_dict = layer.read()[0]
geoengine_layers["backgrounds"].append(layer_dict)
for layer in view.vector_layer_ids:
layer_dict = layer.read()[0]
layer_dict["attribute_field_id"] = self.set_field_real_name(
layer_dict.get("attribute_field_id", False)
)
layer_dict["geo_field_id"] = self.set_field_real_name(
layer_dict.get("geo_field_id", False)
)
layer_dict["resModel"] = layer._name
layer_dict["model"] = layer.model_id.model
layer_dict["model_domain"] = layer.model_domain
geoengine_layers["actives"].append(layer_dict)
return geoengine_layers
@api.model
def get_edit_info_for_geo_column(self, column):
raster_obj = self.env["geoengine.raster.layer"]
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
)
view = self._get_geo_view()
raster = raster_obj.search(
[("view_id", "=", view.id), ("use_to_edit", "=", True)], limit=1
)
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,))
return {
"edit_raster": raster.read()[0],
"srid": field.srid,
"projection": view.projection,
"restricted_extent": view.restricted_extent,
"default_extent": view.default_extent or DEFAULT_EXTENT,
"default_zoom": view.default_zoom,
}
@api.model
def geo_search(
self, domain=None, geo_domain=None, offset=0, limit=None, order=None
):
"""Perform a geo search it allows direct domain:
geo_search(
domain=[('name', 'ilike', 'toto']),
geo_domain=[('the_point', 'geo_intersect',
myshaply_obj or mywkt or mygeojson)])
We can also support indirect geo_domain (
geom, geo_operator, {res.zip.poly: [id, in, [1,2,3]] })
The supported operators are :
* geo_greater
* geo_lesser
* geo_equal
* geo_touch
* geo_within
* geo_contains
* geo_intersect"""
# First we do a standard search in order to apply security rules
# and do a search on standard attributes
# 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")
)
domain = domain or []
geo_domain = geo_domain or []
search_domain = domain or []
if domain and geo_domain:
search_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"))
return self.search(search_domain, limit=limit, offset=offset, order=order)

View file

@ -0,0 +1,89 @@
# 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 odoo import api, fields, models
class GeoRasterLayerType(models.Model):
_name = "geoengine.raster.layer.type"
_description = "Raster Layer Type"
name = fields.Char(translate=True, required=True)
code = fields.Char(required=True)
service = fields.Char(required=True)
class GeoRasterLayer(models.Model):
_name = "geoengine.raster.layer"
_description = "Raster Layer"
raster_type = fields.Selection(
[
("osm", "OpenStreetMap"),
("wmts", "WMTS"),
("d_wms", "Distant WMS"),
("odoo", "Odoo field"),
],
string="Raster layer type",
default="osm",
required=True,
)
name = fields.Char("Layer Name", translate=True, required=True)
url = fields.Char("Service URL")
# technical field to display or not wmts options
is_wmts = fields.Boolean(compute="_compute_is_wmts")
# technical field to display or not wms options
is_wms = fields.Boolean(compute="_compute_is_wms")
# wmts options
matrix_set = fields.Char("Matrix set")
format_suffix = fields.Char("Format", help="eg. png")
request_encoding = fields.Char("Request encoding", help="eg. REST")
projection = fields.Char(help="eg. EPSG:21781")
units = fields.Char(help="eg. m") # Not used
resolutions = fields.Char()
max_extent = fields.Char("Max extent")
dimensions = fields.Char(help="List of dimensions separated by ','")
params = fields.Char(help="Dictiorary of values for dimensions as JSON")
# wms options
params_wms = fields.Char(
"Params WMS", help="Need to provide at least a LAYERS param"
)
server_type = fields.Char(
help="The type of the remote WMS server: mapserver, geoserver, carmentaserver, or qgis",
)
# technical field to display or not layer type -- Not used
has_type = fields.Boolean(compute="_compute_has_type")
type_id = fields.Many2one(
"geoengine.raster.layer.type", "Layer", domain="[('service', '=', raster_type)]"
)
type = fields.Char(related="type_id.code")
sequence = fields.Integer("Layer priority", default=6)
overlay = fields.Boolean("Is overlay layer?")
field_id = fields.Many2one(
"ir.model.fields",
"Odoo layer field to use",
domain=[("ttype", "ilike", "geo_"), ("model", "=", "view_id.model")],
)
view_id = fields.Many2one(
"ir.ui.view", "Related View", domain=[("type", "=", "geoengine")], required=True
)
use_to_edit = fields.Boolean("Use to edit")
@api.depends("raster_type", "is_wmts")
def _compute_has_type(self):
for rec in self:
rec.has_type = rec.raster_type == "is_wmts"
@api.depends("raster_type")
def _compute_is_wmts(self):
for rec in self:
rec.is_wmts = rec.raster_type == "wmts"
@api.depends("raster_type")
def _compute_is_wms(self):
for rec in self:
rec.is_wms = rec.raster_type == "d_wms"

View file

@ -0,0 +1,156 @@
# 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 odoo import _, api, fields, models
from odoo.exceptions import ValidationError
SUPPORTED_ATT = [
"float",
"integer",
"integer_big",
"related",
"function",
"date",
"datetime",
"char",
"text",
"selection",
]
NUMBER_ATT = ["float", "integer", "integer_big"]
class GeoVectorLayer(models.Model):
_name = "geoengine.vector.layer"
_description = "Vector Layer"
_order = "sequence ASC, name"
geo_repr = fields.Selection(
[
("basic", "Basic"),
# Actually we have to think if we should separate it for colored
("proportion", "Proportional Symbol"),
("colored", "Colored range/Chroma.js"),
],
string="Representation mode",
required=True,
)
classification = fields.Selection(
[
("unique", "Unique value"),
("interval", "Interval"),
("quantile", "Quantile"),
("custom", "Custom"),
],
string="Classification mode",
required=False,
)
name = fields.Char("Layer Name", translate=True, required=True)
begin_color = fields.Char("Begin color class", required=False, help="hex value")
end_color = fields.Char(
"End color class", required=False, help="hex value", default="#FF680A"
)
nb_class = fields.Integer("Number of class", default=1)
geo_field_id = fields.Many2one(
"ir.model.fields",
"Geo field",
required=True,
ondelete="cascade",
domain=[("ttype", "ilike", "geo_")],
)
attribute_field_id = fields.Many2one(
"ir.model.fields", "Attribute field", domain=[("ttype", "in", SUPPORTED_ATT)]
)
model_id = fields.Many2one(
"ir.model",
"Model to use",
store=True,
readonly=False,
compute="_compute_model_id",
)
model_name = fields.Char(related="model_id.model", readonly=True)
view_id = fields.Many2one(
"ir.ui.view", "Related View", domain=[("type", "=", "geoengine")], required=True
)
sequence = fields.Integer("Layer Priority", default=6)
readonly = fields.Boolean("Layer is read only")
display_polygon_labels = fields.Boolean("Display Labels on Polygon")
active_on_startup = fields.Boolean(
help="Layer will be shown on startup if checked."
)
layer_opacity = fields.Float(default=1.0)
model_domain = fields.Char(default="[]")
model_view_id = fields.Many2one(
"ir.ui.view",
"Model view",
domain=[("type", "=", "geoengine")],
compute="_compute_model_view_id",
readonly=False,
)
@api.constrains("geo_field_id", "model_id")
def _check_geo_field_id(self):
for rec in self:
if rec.model_id:
if not rec.geo_field_id.model_id == rec.model_id:
raise ValidationError(
_(
"The geo_field_id must be a field in %s model",
rec.model_id.display_name,
)
)
@api.constrains("geo_repr", "attribute_field_id")
def _check_geo_repr(self):
for rec in self:
if (
rec.attribute_field_id
and rec.attribute_field_id.ttype not in NUMBER_ATT
):
if (
rec.geo_repr == "colored"
and rec.classification != "unique"
or rec.geo_repr == "proportion"
):
raise ValidationError(
_(
"You need to select a numeric field",
)
)
@api.constrains("attribute_field_id", "geo_field_id")
def _check_if_attribute_in_geo_field(self):
for rec in self:
if rec.attribute_field_id and rec.geo_field_id:
if rec.attribute_field_id.model != rec.geo_field_id.model:
raise ValidationError(
_(
"You need to provide an attribute that exists in %s model",
rec.geo_field_id.model_id.display_name,
)
)
@api.depends("model_id")
def _compute_model_view_id(self):
for rec in self:
if rec.model_id:
for view in rec.model_id.view_ids:
if view.type == "geoengine":
rec.model_view_id = view
else:
rec.model_view_id = ""
@api.depends("geo_field_id", "view_id")
def _compute_model_id(self):
for rec in self:
if rec.view_id and rec.geo_field_id:
if rec.view_id.model != rec.geo_field_id.model:
rec.model_id = rec.geo_field_id.model_id
else:
rec.model_id = ""
else:
rec.model_id = ""

View file

@ -0,0 +1,51 @@
# Copyright 2011-2012 Nicolas Bessi (Camptocamp SA)
# Copyright 2023 Yannick Payot (Camptocamp SA)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
from odoo.addons import base
if "geoengine" not in base.models.ir_actions.VIEW_TYPES:
base.models.ir_actions.VIEW_TYPES.append(("geoengine", "Geoengine"))
GEO_TYPES = [
("geo_polygon", "geo_polygon"),
("geo_multi_polygon", "geo_multi_polygon"),
("geo_point", "geo_point"),
("geo_multi_point", "geo_multi_point"),
("geo_line", "geo_line"),
("geo_multi_line", "geo_multi_line"),
]
GEO_TYPES_ONDELETE = {
"geo_polygon": "cascade",
"geo_multi_polygon": "cascade",
"geo_point": "cascade",
"geo_multi_point": "cascade",
"geo_line": "cascade",
"geo_multi_line": "cascade",
}
POSTGIS_GEO_TYPES = [
("POINT", "POINT"),
("MULTIPOINT", "MULTIPOINT"),
("LINESTRING", "LINESTRING"),
("MULTILINESTRING", "MULTILINESTRING"),
("POLYGON", "POLYGON"),
("MULTIPOLYGON", "MULTIPOLYGON"),
]
class IrModelField(models.Model):
_inherit = "ir.model.fields"
srid = fields.Integer("srid", required=False)
geo_type = fields.Selection(POSTGIS_GEO_TYPES, string="PostGIs type")
dim = fields.Selection(
[("2", "2"), ("3", "3"), ("4", "4")], string="PostGIs Dimension", default="2"
)
gist_index = fields.Boolean("Create gist index")
ttype = fields.Selection(
selection_add=GEO_TYPES,
ondelete=GEO_TYPES_ONDELETE,
)

View file

@ -0,0 +1,30 @@
# Copyright 2011-2012 Nicolas Bessi (Camptocamp SA)
# Copyright 2016-2023 Yannick Payot (Camptocamp SA)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
class IrUIView(models.Model):
_inherit = "ir.ui.view"
type = fields.Selection(
selection_add=[("geoengine", "GeoEngine")],
ondelete={"geoengine": "cascade"},
)
raster_layer_ids = fields.One2many(
"geoengine.raster.layer", "view_id", "Raster layers", required=False
)
vector_layer_ids = fields.One2many(
"geoengine.vector.layer", "view_id", "Vector layers", required=True
)
projection = fields.Char(default="EPSG:3857", required=True)
default_extent = fields.Char(
"Default map extent",
default="-123164.85222423, 5574694.9538936, 1578017.6490538,"
" 6186191.1800898",
)
default_zoom = fields.Integer("Default map zoom")
restricted_extent = fields.Char("Restricted map extent")