很多人把 Odoo 的地址定位理解成一个按钮:
- 点一下;
- 调一个地图接口;
- 回来两串经纬度。
但 /home/ubuntu/odoo-temp/addons/base_geolocalize 这套实现,其实比“调 API”多了好几层边界控制。
它真正关心的是:
什么时候该查、查什么地址、查失败怎么办,以及旧坐标什么时候必须作废。
一、最重要的安全动作,反而发生在 write() 里
res_partner.write() 一上来就做了一件很值钱的事:
如果你改了这些地址字段:
streetzipcitystate_idcountry_id
但又没有同时显式带上新的 partner_latitude / partner_longitude,Odoo 会把原坐标清零。
这一步特别关键,因为它体现了一个很清晰的原则:
旧地址对应的旧坐标,一旦地址变了,就不再可信。
很多系统的坑就在这里:地址改了,地图坐标却还挂着老值,最后附近搜索、路线、地图标点全错。
Odoo 在这里选择“宁可清空,也别错着留”。
二、真正的查找流程分成“完整地址”与“降级地址”两轮
_geo_localize() 的流程并不复杂,但很像有经验的人工检索:
第一轮:尽量用完整地址查
先把:
- street
- zip
- city
- state
- country
拼成查询串,交给 geocoder。
第二轮:完整地址没命中,再降级到城市级别
如果第一轮没找到,Odoo 会退一步,只拿:
- city
- state
- country
再查一次。
这说明框架作者知道真实世界地址很脏:
- 门牌不标准;
- 街道写法不一致;
- 邮编不完整;
- 州/省翻译混乱。
所以它不把“完整地址没命中”直接判死,而是再给一个更宽松的 fallback。
三、provider 不是写死的,而是配置驱动
base.geocoder._get_provider() 会先看系统参数:
base_geolocalize.geo_provider
如果没配或配错,再回退到数据库里第一条 provider 记录。
这意味着 geolocation 不是硬编码某家服务,而是:
- 服务可换;
- 配置可以后补;
- 数据结构先抽象成 provider,再谈具体 API。
从扩展设计看,这是很标准的平台做法。
四、默认 OpenStreetMap,Google 是显式收费路线
OpenStreetMap / Nominatim
默认链路走 _call_openstreetmap():
- 调
https://nominatim.openstreetmap.org/search - 带上 Odoo 的 User-Agent
- 返回第一条命中结果
- 转成
(lat, lon)
这个默认链路的优点是开箱即用,但它也暗示:
- 结果质量依赖外部服务;
- 查询文本质量很重要;
- 命中不稳定时必须接受 None。
Google Maps
_call_googlemap() 的态度更直接:
- 没 API key 就抛
UserError - 即便有 key,Google 状态不是
OK也会报配置/计费问题 - 还会在
force_country存在时加国家组件约束
也就是说,Google 不是“自动兜底更强服务”,而是一条明确需要付费配置和运维意识的路线。
五、为什么 geo_localize() 会主动跳过导入、测试和 registry 未就绪场景
geo_localize() 里有一个非常容易被忽略的门:
如果没有 force_geo_localize,遇到这些场景会直接返回 False:
import_file- 当前在测试中
- registry 还没 ready
这其实非常合理。
因为 geolocation 是外部调用,天然不稳定、不可预测、还会拖慢流程。
如果在:
- 批量导入联系人;
- 测试环境;
- 系统初始化阶段;
都默认强行打外部 API,系统会非常脆。
所以 Odoo 选择:
把地址定位当成增强动作,而不是基础事务的阻塞步骤。
六、失败时为什么只提醒,不写“猜的坐标”
geo_localize() 查不到结果时,并不会写一个模糊坐标,也不会偷偷沿用旧值。
它做的是:
- 把失败 partner 收集起来;
- 给当前用户发一个
simple_notificationdanger 提醒; - 告诉你哪些地址没匹配上。
这套处理很克制,也很对。
因为 geolocation 这件事最怕“看起来成功,实际上错了”。
错坐标比没坐标更危险。
七、反向定位 _get_localisation() 也不是只依赖地图 API
base_geocoder._get_localisation() 先尝试:
request.geoip.city.namerequest.geoip.country_code
只有拿不到时,才调用 OpenStreetMap reverse geocoding。
这说明 Odoo 在做反向地址展示时,也会优先利用已有请求上下文,而不是每次都打外部服务。
换句话说,作者很清楚:
- 已知上下文最便宜;
- 外部 API 最贵也最不稳;
- 能少打一层就少打一层。
八、测试用例暴露了两个关键边界
test_geolocalize.py 至少说明两件事:
1)Google 没 key 就不该“悄悄失败”
这里明确期待抛 UserError,而不是静默吞掉。
2)失败提醒是正式产品行为,不是调试日志
测试会 mock bus 发送,确认用户真能收到“哪些地址没匹配上”的提醒。
这代表产品层面对 geolocation 的定位是:
- 可以失败;
- 但失败要可见;
- 且不能污染主数据。
九、实战里最该注意什么
1)改地址后坐标被清空,不是 bug
那是为了避免旧坐标假装还有效。
2)批量导入不要默认期待即时定位
源码本来就倾向跳过这类场景。
3)Google 不只是“填个 key”
它还意味着账单、API 启用状态和错误处理成本。
4)查不到时先怀疑地址质量
很多问题不在 geocoder,而在:
- 街道写法不规范;
- 国家/州名语言不统一;
- 地址字段本来就不完整。
总结
base_geolocalize 真正做的不是“地图集成”这么简单。
它在源码里明确处理了四个问题:
- 地址变更后旧坐标怎么失效;
- 查询时先完整查、再降级查;
- provider 怎么配置与切换;
- 查不到时如何提醒而不污染数据。
如果只记一句,可以记这句:
Odoo 的地址定位设计重点不是“尽量写出坐标”,而是“只在结果足够可信时才把坐标写回联系人”。
DISCUSSION
评论区