The stock ``base_geocoder._call_openstreetmap`` sends a generic Odoo ``User-Agent`` shared across thousands of instances, which Nominatim rate-limits and blocks (HTTP 429 "Too many requests"), so mass geolocation fails regardless of client-side throttling. Override ``_call_openstreetmap`` to: - send a deployment-specific ``User-Agent`` read from a new system parameter ``partner_geolocalize_usability.user_agent`` (shipped with an obvious placeholder so an unconfigured instance fails loudly instead of silently reusing the banned generic agent); - throttle requests to one per second (``time.sleep``) before each call, complying with the Nominatim usage policy even when callers loop over many partners; - return ``None`` instead of raising ``IndexError`` when Nominatim returns an empty result for an unknown address. Document the required system parameter in the README and add tests covering the User-Agent selection, throttling and empty/blank results.
80 lines
3.0 KiB
Python
80 lines
3.0 KiB
Python
import logging
|
|
import time
|
|
|
|
import requests
|
|
|
|
from odoo import api, models
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
# System parameter holding the User-Agent sent to Nominatim.
|
|
# Nominatim's usage policy requires a genuine, identifying User-Agent with a
|
|
# valid contact. The stock Odoo User-Agent is shared by thousands of instances
|
|
# and is rate-limited/blocked (HTTP 429), so we let each deployment set its own.
|
|
USER_AGENT_PARAM = "partner_geolocalize_usability.user_agent"
|
|
|
|
# Placeholder shown until the deployment sets a real value. It intentionally
|
|
# does NOT identify any real service so an unconfigured instance fails loudly
|
|
# rather than silently reusing a banned generic agent.
|
|
USER_AGENT_PLACEHOLDER = (
|
|
"SET-ME partner_geolocalize_usability (contact: you@example.invalid)"
|
|
)
|
|
|
|
# Nominatim's public API allows at most 1 request per second. We wait slightly
|
|
# more than a second between calls to stay safely under the limit.
|
|
NOMINATIM_MIN_INTERVAL = 1.1
|
|
|
|
|
|
class GeoCoder(models.AbstractModel):
|
|
_inherit = "base.geocoder"
|
|
|
|
def _get_nominatim_user_agent(self):
|
|
"""Return the configured User-Agent for Nominatim requests.
|
|
|
|
Falls back to an obvious placeholder so a misconfigured instance is easy
|
|
to spot in logs (and so we never silently reuse Odoo's banned agent).
|
|
"""
|
|
return (
|
|
self.env["ir.config_parameter"]
|
|
.sudo()
|
|
.get_param(USER_AGENT_PARAM, USER_AGENT_PLACEHOLDER)
|
|
)
|
|
|
|
@api.model
|
|
def _call_openstreetmap(self, addr, **kw):
|
|
"""Query Nominatim with a configurable User-Agent and rate limiting.
|
|
|
|
Overrides the stock implementation to fix two problems that cause
|
|
HTTP 429 ("Too many requests"):
|
|
|
|
* the shared/generic Odoo User-Agent is blocked by Nominatim -> use a
|
|
deployment-specific one from a system parameter;
|
|
* bursts of requests exceed the 1 req/s policy -> throttle between calls.
|
|
"""
|
|
if not addr:
|
|
_logger.info("invalid address given")
|
|
return None
|
|
url = "https://nominatim.openstreetmap.org/search"
|
|
try:
|
|
headers = {"User-Agent": self._get_nominatim_user_agent()}
|
|
# Respect Nominatim's 1 req/s policy. Sleeping before the call keeps
|
|
# the throttle effective even when callers loop over many records.
|
|
time.sleep(NOMINATIM_MIN_INTERVAL)
|
|
response = requests.get(
|
|
url, headers=headers, params={"format": "json", "q": addr}
|
|
)
|
|
_logger.info("openstreetmap nominatim service called")
|
|
if response.status_code != 200:
|
|
_logger.warning(
|
|
"Request to openstreetmap failed.\nCode: %s\nContent: %s",
|
|
response.status_code,
|
|
response.content,
|
|
)
|
|
result = response.json()
|
|
except Exception as e:
|
|
self._raise_query_error(e)
|
|
if not result:
|
|
return None
|
|
geo = result[0]
|
|
return float(geo["lat"]), float(geo["lon"])
|