diff --git a/src/watson/models.py b/src/watson/models.py index 9fa3bb6..6224b95 100644 --- a/src/watson/models.py +++ b/src/watson/models.py @@ -70,6 +70,9 @@ class SearchEntry(models.Model): meta_encoded = models.TextField() + def _deserialize_meta(self): + return json.loads(self.meta_encoded) + @property def meta(self): """Returns the meta information stored with the search entry.""" @@ -77,7 +80,7 @@ class SearchEntry(models.Model): if hasattr(self, META_CACHE_KEY): return getattr(self, META_CACHE_KEY) # Decode the meta. - meta_value = json.loads(self.meta_encoded) + meta_value = self._deserialize_meta() setattr(self, META_CACHE_KEY, meta_value) return meta_value diff --git a/src/watson/registration.py b/src/watson/registration.py index 24453ce..651abac 100644 --- a/src/watson/registration.py +++ b/src/watson/registration.py @@ -2,7 +2,9 @@ from __future__ import unicode_literals -import sys, json +import sys +import json +import types from itertools import chain, islice from threading import local from functools import wraps @@ -18,6 +20,8 @@ from django.db.models.query import QuerySet from django.db.models.signals import post_save, pre_delete from django.utils.encoding import force_text from django.utils.html import strip_tags +from django.utils.functional import Promise +from django.core.serializers.json import DjangoJSONEncoder try: from importlib import import_module except ImportError: @@ -26,6 +30,24 @@ except ImportError: from watson.models import SearchEntry, has_int_pk +class UniversalEncoder(DjangoJSONEncoder): + ENCODER_BY_TYPE = { + set: list, + frozenset: list, + types.GeneratorType: list, + bytes: lambda o: o.decode('utf-8', errors='replace'), + } + + def default(self, obj): + encoder = self.ENCODER_BY_TYPE.get(type(obj)) + if encoder: + return encoder(obj) + elif isinstance(obj, Promise): + return force_text(obj) + else: + return super(UniversalEncoder, self).default(obj) + + class SearchAdapterError(Exception): """Something went wrong with a search adapter.""" @@ -439,6 +461,10 @@ class SearchEngine(object): ) return object_id_int, search_entries + def serialize_meta(self, meta_obj): + """serialise meta ready to be saved in "meta_encoded".""" + return json.dumps(meta_obj, cls=UniversalEncoder) + def _update_obj_index_iter(self, obj): """Either updates the given object index, or yields an unsaved search entry.""" model = obj.__class__ @@ -452,7 +478,7 @@ class SearchEngine(object): "description": adapter.get_description(obj), "content": adapter.get_content(obj), "url": adapter.get_url(obj), - "meta_encoded": json.dumps(adapter.get_meta(obj)), + "meta_encoded": self.serialize_meta(adapter.get_meta(obj)), } # Try to get the existing search entry. object_id_int, search_entries = self._get_entries_for_obj(obj)