Search

The [apis_core.search] module provides a way to create a search index over different models. It uses a combination of simple search queries and PostgreSQLs trigram search.

The functionality is based on the [SearchEntry][apis_core.search.models.SearchEntry] model which stores a serialized version of the model instances data.

All models inheriting from GenericModel are being indexed automatically. If you want to exclude a model from being indexed, set the Config.index_for_search attribute to False. If you want to include a model in the index, set Config.index_for_search to True.

from django.db import models
from apis_core.generic.abc import GenericModel

class MyModel(GenericModel):
    class Config(GenericModel.Config):
        index_for_search = False

For every model instance an [SearchEntry][apis_core.search.models.SearchEntry] will be added to the database, containing representations of the model instance optimized for search. The [SearchSerializer][apis_core.search.serializers.SearchSerializer] class is used by default to serialize the instance for search. It is based on [django.core.serializers.json.Serializer] but for m2m fields it adds the values instead of simply the keys.

To control which fields of the model should be serialized for search (and thus control if the model instances come up in search results), you can set the search_fields parameter in the Config subclass:

from django.db import models
from apis_core.generic.abc import GenericModel

class Person(GenericModel):
    forename = models.CharField()
    surname = models.CharField()
    long_description = models.TextField()

    class Config(GenericModel.Config):
        search_fields = {"forename", "surname"}

m2m fields

As mentioned above, m2m fields are being serialized as the values of the model they point to. If the instances of the ManyToManyField model change, the model containing this pointer does not change automatically. You can define with m2m_fields for which ManyToManyField the model should "follow" the updates.

from django.db import models
from apis_core.generic.abc import GenericModel

class Profession(GenericModel):
    name = models.CharField()

class Title(GenericModel):
    label = models.CharField()

class Person(GenericModel):
    forename = models.CharField()
    surname = models.CharField()
    profession = models.ManyToManyField(Profession)

    class Config(GenericModel.Config):
        search_follow_m2m = {"profession"}

In this case, when a profession is changed, the SearchEntrys of all Persons that point to that profession are also updated.

Management command

There is an initialize_search management command that (re)creates the SearchEntry instances for all registered models. You can pass the --content-types argument to create the SearchEntry only for a subset of models. You can pass the --recreate argument to recreate existing search index entries.

Weighting

The weighting logic annotates the search results with the result of the ranking calculation. In the search result overview you can see a 🛈 symbol - hovering over it shows the different numbers:

  • First number: the greater value of: (title rank * 2, content rank)
  • Second number: the title rank
  • Third number: the content rank
  • Fourth number: 10: title contains, 9: title contains case insensitive, 8: title slug contains, 7: title slug contains case insensitive, 6: content text contains, 5: content text contains case insensitive; 4: content text slug contains, 3: content text slug contains case insensitive