C’est une app django s’interfaçant avec certains moteurs de recherche (xapian,whoosh,solr)qui nous permet d’effectuer des recherches de manière très « django-ique ».
Dans cet article, on va utiliser Haystack avec Xapian.
Pour Xapian, il faut télécharger et installer xapian-core et xapian-bindings ici.
Ou sur Fedora&co :
yum install xapian-core xapian-bindgins-python
Pour Haystack, vous pouvez l’installer via pip/easy_install ou encore en téléchargeant le code source ici.
Ensuite, il faut télécharger&installer le backend de xapian pour haystack là (il n’est pas intégré dans haystack à cause d’un problème de licence).
Maintenant que tout est installé, il faut éditer le fichier settings.py :
... import os,sys settings_path = os.path.abspath(os.path.dirname(__file__)) head, tail = os.path.split(settings_path) INSTALLED_APPS = ( .... 'haystack', ) HAYSTACK_SITECONF = '%s.search_sites' % tail HAYSTACK_SEARCH_ENGINE = 'xapian' HAYSTACK_XAPIAN_PATH = os.path.join(head,'index')
Ici,
on a ajouté haystack dans les applications installées.
Puis après, quelques configurations pour haystack :
HAYSTACK_SITECONF : par convention on est censé appeler le module de conf search_sites.py et le placer à la racine du projet.
HAYSTACK_SEARCH_ENGINE : comme son nom l’indique, permet de définir quel moteur de recherche nous utilisons.
HAYSTACK_XAPIAN_PATH : Le dossier où vont être stockés les index de xapian (ne pas oublier de créer le dossier).
Créons le fichier search_sites.py (à la racine du projet) :
import haystack haystack.autodiscover()
Comme pour le « admin.autodiscover() », haystack va checker dans chacune de vos apps si un fichier search_indexes.py, dans lequel on crée nos index de recherche, existe.
Maintenant on va créer une app : videos (ne pas oublier de la rajouter dans settings.py):
django-admin.py startapp videos
Puis on crée les modèles que l’on va indexer :
from django.db import models
class Tag(models.Model):
""" """
name = models.CharField(max_length=50)
slug = models.SlugField(max_length=50,unique=True)
class Video(models.Model):
""" """
name = models.CharField(max_length=200)
slug = models.SlugField(max_length=200)
description = models.TextField(null=True, blank=True)
tag = models.ManyToManyField(Tag,null=True)
On va aussi indexer le modèledjango.contrib.auth.models.User .
Maintenant, le fichier videos/search_indexes.py :
#-*- coding:utf-8 -*-
from haystack.indexes import SearchIndex,CharField,MultiValueField
from haystack import site
from models import Video
from django.contrib.auth.models import User
class VideoIndex(SearchIndex):
""" """
text = CharField(document=True, use_template=True,template_name='haystack/video.txt')
name = CharField(model_attr='name')
tags = MultiValueField()
description = CharField(model_attr='description',null=True)
def prepare_tags(self,obj):
""" """
return [tag.name for tag in obj.tag.all() ]
def get_queryset(self):
""" """
return Video.objects.filter(published=True)
class UserIndex(SearchIndex):
""" """
text = CharField(document=True, use_template=True,template_name='haystack/user.txt')
username = CharField(model_attr='username')
first_name = CharField(model_attr='first_name')
last_name = CharField(model_attr='last_name')
def get_queryset(self):
""" """
return User.objects.filter(is_active=True)
site.register(Video, VideoIndex)
site.register(User, UserIndex)
On a créer nos indexes de recherche : 2 classes héritant de SearchIndex.
On les lie avec leur modèles via site.register().
Ensuite chaque classe, doit avoir obligatoirement un champs ayant le paramètre document=True, qui permet d’indiquer que l’on doit commencer par rechercher à l’intérieur de celui-ci.
Du coup, on ajoute souvent à ce champs(vu qu’il n’existe pas dans nos modèles) le paramètre use_template=True nous permettant de créer un template contenant les informations à indexer pour la recherche.
Ensuite on indique les autres champs que l’on veut indexer : par exemple dans VideoIndex,name est un CharField et correspond à l’attribut name de Video. Si dans un de vos modèles,vous avez un ManyToMany, dans l’index il faut déclarer un champs en tant que MultiValueField, ensuite il faut créer la méthode : prepare_nomduchamps(self,obj) qui va lister tous les objets liés à ce champs.Cf le champs tags et sa méthode prepare_tags(). Enfin la méthode get_queryset() permet d’éffectuer un pré-traitement lors de l’indexage : ne pas indexer les utilisateurs inactifs, les vidéos non publiées,etc. Il ne nous reste plus qu’à créer respectivement les templates videos/templates/haystack/video.txt :
{{object.name}}
{{object.description}}
et videos/templates/haystack/user.txt :
{{object.username}}
{{object.first_name}}
{{object.last_name}}
Ne reste plus qu’à indexer :
python manage.py rebuild_indexEt pour mettre à jour :
python manage.py update_indexNe nous reste plus qu’à créer le formulaire de recherche. C’est assez simple : - projet/urls.py :
urlpatterns = patterns('',
(r'^$','videos.views.search',{},'search_url'),
),
- videos/views.py :
#-*- coding:utf-8 -*-
from haystack.query import SearchQuerySet
from videos.models import Video
from django.contrib.auth.models import User
from django.shortcuts import render_to_response
from django.template import RequestContext
def search(request):
""" """
results = []
if request.method=="POST":
type_result = request.POST.get('select','all')
query = request.POST.get('query','')
if type_result == 'all':
results = SearchQuerySet().auto_query(query)
elif type_result == 'user':
results = SearchQuerySet().models(User).auto_query(query)
elif type_result == 'video':
results = SearchQuerySet().models(Video).auto_query(query)
return render_to_response('search.html',{'results':results},context_instance=RequestContext(request))
Un simple SearchQuerySet().auto_query() suffit pour la recherche.
On filtre éventuellement les résultat via models() selon le type de recherche effectuée. - templates/search.html :<form action="{% url search_url %}" method="POST" id="search_form">
{% csrf_token %}
<p><input type="text" id="query" name="query" /></p>
<p>
<select id="select_type" name="select">
<option value="all">Tout</option>
<option value="user">Utilisateurs</option>
<option value="video">Vidéos</option>
</select>
</p>
<p><input type="submit" value="ok" />
</form>
{% if results %}
{% for result in results %}
<p>{{result.text|safe}}</p>
{% endfor %}
{% endif %}
and Voilà !