The QwebJSON.dumps default handler returned non-serializable objects
unchanged, causing Python's json encoder to report "Circular reference
detected". Use odoo.tools.json.json_default() as fallback to properly
convert ReadonlyDict, lazy, datetime, bytes, Domain and other types.
🤖 assisted by claude
2.2 KiB
QwebJSON Circular Reference Bugfix
Problem
When loading the Odoo 19 web client, the web.webclient_bootstrap QWeb template
fails with:
ValueError: Circular reference detected
at the line:
<t t-out="json.dumps(session_info)"/>
Root Cause
The QwebJSON.dumps() default handler in odoo/addons/base/models/ir_qweb.py
returned non-serializable objects unchanged:
# Original (broken)
class QwebJSON(json.JSON):
def dumps(self, *args, **kwargs):
prev_default = kwargs.pop('default', lambda obj: obj)
return super().dumps(*args, **kwargs, default=(
lambda obj: prev_default(str(obj) if isinstance(obj, QwebContent) else obj)
))
When the JSON encoder encounters a non-serializable type (e.g. ReadonlyDict,
lazy, datetime, bytes, Domain), it calls the default handler. The handler
returns the object unchanged (since it's not QwebContent). The encoder tries to
serialize it again, gets the same object back, and raises "Circular reference detected."
This is a latent bug in the Odoo 19 core. In vanilla Odoo it rarely triggers
because session_info values are typically JSON-serializable. However, any module
(OCA or custom) that introduces a non-serializable type into session_info or any
other QWeb JSON-serialized context will expose this bug.
Fix
Use odoo.tools.json.json_default() as the fallback for non-QwebContent objects:
# Fixed
class QwebJSON(json.JSON):
def dumps(self, *args, **kwargs):
prev_default = kwargs.pop('default', lambda obj: obj)
return super().dumps(*args, **kwargs, default=(
lambda obj: prev_default(str(obj) if isinstance(obj, QwebContent) else json.json_default(obj))
))
json_default() (from odoo/tools/json.py) properly handles:
datetime-> string viafields.Datetime.to_string()date-> string viafields.Date.to_string()lazy-> resolved valueReadonlyDict-> regulardictbytes-> decoded stringDomain-> list- anything else ->
str(obj)
File Changed
odoo-bringout-oca-ocb-base/odoo/addons/base/models/ir_qweb.py(classQwebJSON)
Date
2026-03-10