Search code examples
pythondjangodjango-haystack

Programmatically create haystack index


The standard way to create a Haystack search index is in a search_indexes.py file, with a class per model to index, like so:

class FooIndex(indexes.Indexable):
    def get_model(self):
        return models.Foo

However, I have a lot of models that all behave similarly and I want to index them (as well as registering in other ways).

For example, given this models.py code:

class BaseModel(models.model):
   class Meta:
       abstract = True
   # some shared fields
class Foo(BaseModel):
  pass # some different fields
class Bar(BaseModel):
  pass

This code works:

def register_base_model(sub_class, *args, **kwargs):
    #other shared actions here
    admin.site.register(sub_class,BaseAdmin) # BaseAdmin defined elsewhere
register_base_model(models.Foo)

But this won't:

def register_base_model(sub_class, *args, **kwargs):
    #other shared actions here
    admin.site.register(sub_class,BaseAdmin) # BaseAdmin defined elsewhere
    class SubclassedIndex(indexes.SearchIndex,indexes.Indexable):
        def get_model(self):
            return sub_class

register_base_model(models.Foo)

Because the class SubclassedIndex is only in the scope of register_base_model. Haystack 1.x used to have a register/unregister API, but this is now gone, and while the new method is mostly easier, I need a similar behaviour.

How can I programmatically register a HaystackIndex without relying on the automagical way it operates now?


Solution

  • This can be done in at least one way, albeit in a very hacky way.

    Firstly, you must have an app with a search_indexes.py module, and it must be imported where you programmatically add indexes:

    from haystack import connections
    from haystack.constants import DEFAULT_ALIAS
    
    import my_app.search_indexes as search_index
    
    for my_class in some_list_of_models:
    
        search_index_class_name = "%s_%sSearchIndex"%(concept_class._meta.app_label,concept_class.__name__)
    
        # Create a new subclass of search index (see below)
        # And monkey-patch it in.
        setattr(search_index, class_name, create(concept_class))
    
    # Since we've added a new class, kill the index so it is rebuilt next time we access the index.
    connections[DEFAULT_ALIAS]._index = None
    

    Then you need a function to build the class

    def create(cls):
        class DynamicSearchIndex(indexes.SearchIndex, indexes.Indexable):
            # What ever fields are needed go here
            def get_model(self):
                return cls
        return SubclassedConceptIndex
    

    As I found out, you need to make sure the templates exist in the right spots, and might want to add a try/except wrapper to catch errors when building classes.