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à !