O Simple Engine do Haystack não gosta de campos relacionados.

Por um bom tempo esta manhã, eu estava batendo minha cabeça sobre um código bastante simples. A razão por trás disso, no final, foi porque eu estava usando o mecanismo simples do Haystack. Deixe-me explicar.

O que é Haystack?

Para quem não sabe, Haystack é um aplicativo escrito em Python que pode ser adicionado a qualquer aplicativo Django para criar índices de pesquisa de uma maneira limpa que requer o mínimo de código de sua parte e zero refatoração. É realmente tão simples quanto implementar uma única classe.

Ele, como o ORM do Django, pode utilizar vários back-ends, como Woosh e Solr, bem como este Simple Engine que mencionei antes. Além disso, todos os itens de consulta complexos com os quais você está acostumado com o ORM do Django, incluindo uma versão de objetos Q, estão disponíveis para você realizar consultas de simples a avançadas (com algumas ressalvas, uma das quais mencionarei mais tarde) .

Motor simples … Não é tão simples assim …

Vamos para este Simple Engine, que é o ponto desta postagem. Eu tinha um índice simples configurado de acordo com os documentos dev de haystacksearch.org. (Você encontrará um maravilhoso site de documentação de estilo readthedocs lá.)

class MyModelIndex(indexes.SearchIndex, indexes.Indexable):
text
= indexes.CharField(document=True, use_template=True, model_attr="field_1")
title
= indexes.CharField(model_attr='title_field')
multi
= indexes.MultiValueField()

def get_model(self):
return MyModel

def prepare_multi(self, obj):
return [p.pk for p in obj.multi.related_m2m.all()]

def index_queryset(self):
return self.get_model().objects.all()

Para completar esta imagem, vamos supor que meu arquivo models.py contenha o seguinte.

MAX_LENGTH = 255

class RelatedM2M(models.Model):
name
= models.CharField(max_length=MAX_LENGTH, primary_key=True)


class RelatedModel(models.Model):
name
= models.CharField(max_length=MAX_LENGTH, primary_key=True)
related_m2m
= models.ManyToManyField(RelatedM2M, blank=True, null=True)


class MyModel(models.Model):
field_1
= models.TextField()
title_field
= models.CharField(max_length=MAX_LENGTH)
multi
= models.ForeignKey(RelatedModel)

O problema

Agora que temos as bases estabelecidas, vamos nos concentrar no que o Simple Engine não gosta nisso. Este motor não gosta de nada que não esteja diretamente no modelo com o qual está preocupado. Assim, faz sentido que não goste que acessemos os dados através da chave estrangeira em “MeuModelo”.

A solução?

Para mim, foi usar Solr. Como planejávamos usar o Solr no ambiente de produção, essa mudança fez muito sentido. Utilizar o mesmo software me permite não apenas testar o resto do meu código no Solr, mas também me permite evitar quaisquer hacks desagradáveis ​​que eu possa ter precisado adicionar para obter informações de campo relacionadas carregadas em meus índices.

Agora, para uma advertência

Lembra que eu disse que havia advertências sobre a filtragem? Vamos examinar a função de preparação da primeira aula novamente.

def prepare_multi(self, obj):
return [p.pk for p in obj.multi.related_m2m.all()]

Isso vai causar problemas. Pelo menos ele fez por mim. Pelo que pude testar, o Solr não gosta de valores vazios. Existem duas soluções para isso. A primeira é alterar o modelo para garantir que null = False e blank = False e, em seguida, introduzir um dispositivo de fixação ou uma migração de dados que adiciona um objeto RelatedM2M “Padrão” ao banco de dados. Não é a solução mais bonita, mas é de longe a mais robusta e direta para quem está usando seu sistema.

Você também pode adicionar um padrão implícito à própria função prepare_multi do índice. Isso seria parecido com isso.

def prepare_multi(self, obj):
if obj.multi.related_m2m.count():
return [p.pk for p in obj.multi.related_m2m.all()]
return [u"Default"]

Apenas certifique-se de documentar isso claramente nas strings de ajuda. Novamente, a maneira que mencionei primeiro, criando um objeto explícito, é melhor.

Conclusão

Espero que isso evite dores de cabeça para algumas pessoas. Se você planeja usar o Solr, não se esqueça de usar o comando manage.py para construir seu schema.xml!

Links!