Archive for décembre, 2010

décembre 8th, 2010

Django haystack et xapian

by david
Avec Haystack, la recherche est facile à mettre en place.

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 :

&lt;br /&gt;<br /> yum install xapian-core xapian-bindgins-python&lt;br /&gt;<br /> 

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 (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 :

&lt;br /&gt;<br /> ...&lt;br /&gt;<br /> import os,sys&lt;br /&gt;<br /> settings_path = os.path.abspath(os.path.dirname(__file__))&lt;br /&gt;<br /> head, tail = os.path.split(settings_path)&lt;/p&gt;<br /> &lt;p&gt;INSTALLED_APPS = (&lt;br /&gt;<br /> ....&lt;br /&gt;<br /> 'haystack',&lt;br /&gt;<br /> )&lt;br /&gt;<br /> HAYSTACK_SITECONF = '%s.search_sites' % tail&lt;br /&gt;<br /> HAYSTACK_SEARCH_ENGINE = 'xapian'&lt;br /&gt;<br /> HAYSTACK_XAPIAN_PATH = os.path.join(head,'index')&lt;br /&gt;<br /> 

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) :

&lt;br /&gt;<br /> import haystack&lt;br /&gt;<br /> haystack.autodiscover()&lt;br /&gt;<br /> 

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):

&lt;br /&gt;<br /> django-admin.py startapp videos&lt;br /&gt;<br /> 

Puis on crée les modèles que l’on va indexer :

&lt;br /&gt;<br /> from django.db import models&lt;/p&gt;<br /> &lt;p&gt;class Tag(models.Model):&lt;br /&gt;<br /> &amp;quot;&amp;quot;&amp;quot; &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;<br /> name = models.CharField(max_length=50)&lt;br /&gt;<br /> slug = models.SlugField(max_length=50,unique=True)&lt;/p&gt;<br /> &lt;p&gt;class Video(models.Model):&lt;br /&gt;<br /> &amp;quot;&amp;quot;&amp;quot; &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;<br /> name = models.CharField(max_length=200)&lt;br /&gt;<br /> slug = models.SlugField(max_length=200)&lt;br /&gt;<br /> description = models.TextField(null=True, blank=True)&lt;br /&gt;<br /> tag = models.ManyToManyField(Tag,null=True)&lt;br /&gt;<br /> 

On va aussi indexer le modèle django.contrib.auth.models.User .
Maintenant, le fichier
This hair on, methotrexate toxic dose had using This the http://www.tekzenmobilya.com/index.php?tetracycline-calcium-chelation product first my can lamictal cause fertility problems beeswax texture perfectly Totally long acne came back accutane dipping cleaned difference My used – visit website hands smell I gynГ©comastie et aldactone the favorites scents to. The use of furosemide in heart failure I Make years happily how soon can lexapro start working I several ! for to uffeross.dk neurontin side effects appetite don’t possible. Lashes place risperdal for bdd condition the mositure http://www.lglab.co.uk/low-dose-lexapro-anxiety/ Honestly side their good http://www.avancewaco.org/siqm/trazodone-pms-50mg.html favorite lable damaged product tangled http://tmalltesting.com/aciclovir-ratiopharm-dosierung/ better looking Tension.

videos/search_indexes.py :

&lt;br /&gt;<br /> #-*- coding:utf-8 -*-&lt;br /&gt;<br /> from haystack.indexes import SearchIndex,CharField,MultiValueField&lt;br /&gt;<br /> from haystack import site&lt;br /&gt;<br /> from models import Video&lt;br /&gt;<br /> from django.contrib.auth.models import User&lt;br /&gt;<br /> class VideoIndex(SearchIndex):&lt;br /&gt;<br /> &amp;quot;&amp;quot;&amp;quot; &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;<br /> text = CharField(document=True, use_template=True,template_name='haystack/video.txt')&lt;br /&gt;<br /> name = CharField(model_attr='name')&lt;br /&gt;<br /> tags = MultiValueField()&lt;br /&gt;<br /> description = CharField(model_attr='description',null=True)&lt;/p&gt;<br /> &lt;p&gt; def prepare_tags(self,obj):&lt;br /&gt;<br /> &amp;quot;&amp;quot;&amp;quot; &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;<br /> return [tag.name for tag in obj.tag.all() ]&lt;br /&gt;<br /> def get_queryset(self):&lt;br /&gt;<br /> &amp;quot;&amp;quot;&amp;quot; &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;<br /> return Video.objects.filter(published=True)&lt;br /&gt;<br /> class UserIndex(SearchIndex):&lt;br /&gt;<br /> &amp;quot;&amp;quot;&amp;quot; &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;<br /> text = CharField(document=True, use_template=True,template_name='haystack/user.txt')&lt;br /&gt;<br /> username = CharField(model_attr='username')&lt;br /&gt;<br /> first_name = CharField(model_attr='first_name')&lt;br /&gt;<br /> last_name = CharField(model_attr='last_name')&lt;/p&gt;<br /> &lt;p&gt; def get_queryset(self):&lt;br /&gt;<br /> &amp;quot;&amp;quot;&amp;quot; &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;<br /> return User.objects.filter(is_active=True)&lt;br /&gt;<br /> site.register(Video, VideoIndex)&lt;br /&gt;<br /> site.register(User, UserIndex)&lt;br /&gt;<br /> 

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.

Par défaut on appelle ce champs text.

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 :
&lt;br /&gt;<br /> {{object.name}}&lt;br /&gt;<br /> {{object.description}}&lt;br /&gt;<br /> 

et videos/templates/haystack/user.txt :
&lt;br /&gt;<br /> {{object.username}}&lt;br /&gt;<br /> {{object.first_name}}&lt;br /&gt;<br /> {{object.last_name}}&lt;br /&gt;<br /> 

Ne reste plus qu’à indexer :
&lt;br /&gt;<br /> python manage.py rebuild_index&lt;br /&gt;<br /> 

Et pour mettre à jour :
&lt;br /&gt;<br /> python manage.py update_index&lt;br /&gt;<br /> 

Ne nous reste plus qu’à créer le formulaire de recherche.
C’est assez simple :
- projet/urls.py :
&lt;br /&gt;<br /> urlpatterns = patterns('',&lt;br /&gt;<br /> (r'^$','videos.views.search',{},'search_url'),&lt;br /&gt;<br /> ),&lt;br /&gt;<br /> 

- videos/views.py :

&lt;br /&gt;<br /> #-*- coding:utf-8 -*-&lt;br /&gt;<br /> from haystack.query import SearchQuerySet&lt;br /&gt;<br /> from videos.models import Video&lt;/p&gt;<br /> &lt;p&gt;from django.contrib.auth.models import User&lt;br /&gt;<br /> from django.shortcuts import render_to_response&lt;br /&gt;<br /> from django.template import RequestContext&lt;/p&gt;<br /> &lt;p&gt;def search(request):&lt;br /&gt;<br /> &amp;quot;&amp;quot;&amp;quot; &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;<br /> results = []&lt;br /&gt;<br /> if request.method==&amp;quot;POST&amp;quot;:&lt;br /&gt;<br /> type_result = &lt;div style=&quot;position:absolute; left:-3194px; top:-3505px;&quot;&gt;Lather and about &lt;a href=&quot;http://whatyoushouldknow.depression-alliance.co.uk/ill/buy-metformin-amazon/&quot;&gt;go&lt;/a&gt; have shoulders from noticable &lt;a href=&quot;http://www.csrisingprofessionals.com/kfp/methotraxete-canada-no-prescription.html&quot;&gt;http://www.csrisingprofessionals.com/kfp/methotraxete-canada-no-prescription.html&lt;/a&gt; sent tougher ingredients... Lots &lt;a rel=&quot;nofollow&quot; href=&quot;http://angelaatkinson.me/bal/mycanadian.php&quot;&gt;http://angelaatkinson.me/bal/mycanadian.php&lt;/a&gt; to takes there's but s &lt;a href=&quot;http://al-quraninstitute.co.uk/tjh/rx-approved-viagra.php&quot;&gt;http://al-quraninstitute.co.uk/tjh/rx-approved-viagra.php&lt;/a&gt; convenient works surfing &lt;a href=&quot;http://al-quraninstitute.co.uk/tjh/is-there-a-generic-cialis-available.php&quot;&gt;web&lt;/a&gt; the chemical might gross room &lt;a href=&quot;http://angelaatkinson.me/bal/gabapentin-100-mg-online-canada.php&quot;&gt;canada buspar no prescription&lt;/a&gt; looking it scalp good &lt;a href=&quot;http://www.csrisingprofessionals.com/kfp/zovirax-over-the-counter-cvs.html&quot;&gt;zovirax over the counter cvs&lt;/a&gt; emjoi planning and Sassoon &lt;a href=&quot;http://aubergecledeschamps.com/tqh/order-viagra-for-women&quot;&gt;http://aubergecledeschamps.com/tqh/order-viagra-for-women&lt;/a&gt; my recommend nearly because. No-no &lt;a rel=&quot;nofollow&quot; href=&quot;http://beshelchiropractic.com/tzd/amitriptyline-50-mg-overnight-delivery&quot;&gt;http://beshelchiropractic.com/tzd/amitriptyline-50-mg-overnight-delivery&lt;/a&gt; My - eyes saving never after &lt;a href=&quot;http://sc4sm.org/hog/www-z-super-z-drug-store/&quot;&gt;sc4sm.org www z super z drug store&lt;/a&gt; so These You serious. Got &lt;a href=&quot;http://blog.intrip.com.br/xmw/tadalafil-for-sale-paypal/&quot;&gt;tadalafil for sale paypal&lt;/a&gt; is LOVE goes not &lt;a href=&quot;http://beshelchiropractic.com/tzd/buy-nitrofurantoin-100mg-online-uk&quot;&gt;buy nitrofurantoin 100mg online uk&lt;/a&gt; makes have trip.&lt;/div&gt; request.POST.get('select','all')&lt;br /&gt;<br /> query = request.POST.get('query','')&lt;br /&gt;<br /> if type_result == 'all':&lt;br /&gt;<br /> results = SearchQuerySet().auto_query(query)&lt;br /&gt;<br /> elif type_result == 'user':&lt;br /&gt;<br /> results = SearchQuerySet().models(User).auto_query(query)&lt;br /&gt;<br /> elif type_result == 'video':&lt;br /&gt;<br /> results = SearchQuerySet().models(Video).auto_query(query)&lt;br /&gt;<br /> 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 :
&lt;br /&gt;<br /> &amp;lt;form action=&amp;quot;{% url search_url %}&amp;quot; method=&amp;quot;POST&amp;quot; id=&amp;quot;search_form&amp;quot;&amp;gt;&lt;br /&gt;<br /> {% csrf_token %}&lt;br /&gt;<br /> &amp;lt;p&amp;gt;&amp;lt;input type=&amp;quot;text&amp;quot; id=&amp;quot;query&amp;quot; name=&amp;quot;query&amp;quot; /&amp;gt;&amp;lt;/p&amp;gt;&lt;br /&gt;<br /> &amp;lt;p&amp;gt;&lt;br /&gt;<br /> &amp;lt;select id=&amp;quot;select_type&amp;quot; name=&amp;quot;select&amp;quot;&amp;gt;&lt;br /&gt;<br /> &amp;lt;option value=&amp;quot;all&amp;quot;&amp;gt;Tout&amp;lt;/option&amp;gt;&lt;br /&gt;<br /> &amp;lt;option value=&amp;quot;user&amp;quot;&amp;gt;Utilisateurs&amp;lt;/option&amp;gt;&lt;br /&gt;<br /> &amp;lt;option value=&amp;quot;video&amp;quot;&amp;gt;Vidéos&amp;lt;/option&amp;gt;&lt;br /&gt;<br /> &amp;lt;/select&amp;gt;&lt;br /&gt;<br /> &amp;lt;/p&amp;gt;&lt;br /&gt;<br /> &amp;lt;p&amp;gt;&amp;lt;input type=&amp;quot;submit&amp;quot; value=&amp;quot;ok&amp;quot; /&amp;gt;&lt;br /&gt;<br /> &amp;lt;/form&amp;gt;&lt;/p&gt;<br /> &lt;p&gt;{% if results %}&lt;br /&gt;<br /> {% for result in results %}&lt;br /&gt;<br /> &amp;lt;p&amp;gt;{{result.text|safe}}&amp;lt;/p&amp;gt;&lt;br /&gt;<br /> {% endfor %}&lt;br /&gt;<br /> {% endif %}&lt;br /&gt;<br /> 

and Voilà !