def _update_obj_index_iter(self, obj): """Either updates the given object index, or yields an unsaved search entry.""" model = obj.__class__ adapter = self.get_adapter(model) content_type = ContentType.objects.get_for_model(model) object_id = force_text(obj.pk) # Create the search entry data. search_entry_data = { "engine_slug": self._engine_slug, "title": adapter.get_title(obj), "description": adapter.get_description(obj), "content": adapter.get_content(obj), "url": adapter.get_url(obj), "meta_encoded": json.dumps(adapter.get_meta(obj)), } # Try to get the existing search entry. object_id_int, search_entries = self._get_entries_for_obj(obj) # Attempt to update the search entries. update_count = search_entries.update(**search_entry_data) if update_count == 0: # This is the first time the entry was created. search_entry_data.update(( ("content_type", content_type), ("object_id", object_id), ("object_id_int", object_id_int), )) yield SearchEntry(**search_entry_data) elif update_count > 1: # Oh no! Somehow we've got duplicated search entries! search_entries.exclude(id=search_entries[0].id).delete()
def get_title(self, obj): """ Returns the title of this search result. This is given high priority in search result ranking. You can access the title of the search entry as `entry.title` in your search results. The default implementation returns `force_text(obj)`. """ return force_text(obj)
def testSiteSearchJSON(self): # Test a search that should find everything. response = self.client.get("/simple/json/?q=title") self.assertEqual(response["Content-Type"], "application/json; charset=utf-8") results = set(result["title"] for result in json.loads(force_text(response.content))["results"]) self.assertEqual(len(results), 4) self.assertTrue("title model1 instance11" in results) self.assertTrue("title model1 instance12" in results) self.assertTrue("title model2 instance21" in results) self.assertTrue("title model2 instance22" in results)
def _resolve_field(self, obj, name): """Resolves the content of the given model field.""" name_parts = name.split("__", 1) prefix = name_parts[0] # If we're at the end of the resolve chain, return. if obj is None: return "" # Try to get the attribute from the object. try: value = getattr(obj, prefix) except ObjectDoesNotExist: return "" except AttributeError: # Try to get the attribute from the search adapter. try: value = getattr(self, prefix) except AttributeError: raise SearchAdapterError("Could not find a property called {name!r} on either {obj!r} or {search_adapter!r}".format( name = prefix, obj = obj, search_adapter = self, )) else: # Run the attribute on the search adapter, if it's callable. if not isinstance(value, (QuerySet, models.Manager)): if callable(value): value = value(obj) else: # Run the attribute on the object, if it's callable. if not isinstance(value, (QuerySet, models.Manager)): if callable(value): value = value() # Look up recursive fields. if len(name_parts) == 2: if isinstance(value, (QuerySet, models.Manager)): return " ".join(force_text(self._resolve_field(obj, name_parts[1])) for obj in value.all()) return self._resolve_field(value, name_parts[1]) # Resolve querysets. if isinstance(value, (QuerySet, models.Manager)): value = " ".join(force_text(related) for related in value.all()) # Resolution complete! return value
def testSiteSearchCustomJSON(self): # Test a search that should find everything. response = self.client.get("/custom/json/?fooo=title&page=last") self.assertEqual(response["Content-Type"], "application/json; charset=utf-8") results = set(result["title"] for result in json.loads(force_text(response.content))["results"]) self.assertEqual(len(results), 4) self.assertTrue("title model1 instance11" in results) self.assertTrue("title model1 instance12" in results) self.assertTrue("title model2 instance21" in results) self.assertTrue("title model2 instance22" in results) # Test a search with an invalid page. response = self.client.get("/custom/json/?fooo=title&page=200") self.assertEqual(response.status_code, 404)
def get_content(self, obj): """ Returns the content of this search result. This is given low priority in search result ranking. You can access the content of the search entry as `entry.content` in your search results, although this field generally contains a big mess of search data so is less suitable for frontend display. The default implementation returns all the registered fields in your model joined together. """ # Get the field names to look up. field_names = self.fields or (field.name for field in self.model._meta.fields if isinstance(field, (models.CharField, models.TextField))) # Exclude named fields. field_names = (field_name for field_name in field_names if field_name not in self.exclude) # Create the text. return self.prepare_content(" ".join( force_text(self._resolve_field(obj, field_name)) for field_name in field_names ))
def resolve_url(to, *args, **kwargs): """ Return a URL appropriate for the arguments passed. The arguments could be: * A model: the model's `get_absolute_url()` function will be called. * A view name, possibly with arguments: `urlresolvers.reverse()` will be used to reverse-resolve the name. * A URL, which will be returned as-is. """ from compat import six, force_text # If it's a model, use get_absolute_url() if hasattr(to, 'get_absolute_url'): return to.get_absolute_url() if isinstance(to, Promise): # Expand the lazy instance, as it can cause issues when it is passed # further to some Python functions like urlparse. to = force_text(to) if isinstance(to, six.string_types): # Handle relative URLs if any(to.startswith(path) for path in ('./', '../')): return to # Next try a reverse URL resolution. try: return urlresolvers.reverse(to, args=args, kwargs=kwargs) except urlresolvers.NoReverseMatch: # If this is a callable, re-raise. if callable(to): raise # If this doesn't "feel" like a URL, re-raise. if '/' not in to and '.' not in to: raise # Finally, fall back and assume it's a URL return to
def _get_entries_for_obj(self, obj): """Returns a queryset of entries associate with the given obj.""" model = obj.__class__ content_type = ContentType.objects.get_for_model(model) object_id = force_text(obj.pk) # Get the basic list of search entries. search_entries = SearchEntry.objects.filter( content_type = content_type, engine_slug = self._engine_slug, ) if has_int_pk(model): # Do a fast indexed lookup. object_id_int = int(obj.pk) search_entries = search_entries.filter( object_id_int = object_id_int, ) else: # Alas, have to do a slow unindexed lookup. object_id_int = None search_entries = search_entries.filter( object_id = object_id, ) return object_id_int, search_entries
def escape_query(text): text = force_text(text) text = RE_SPACE.sub(" ", text) # Standardize spacing. text = RE_NON_WORD.sub("", text) # Remove non-word characters. return text
def __str__(self): return force_text(self.title)