ElasticSearch med Django på den nemme måde

For et stykke tid tilbage arbejdede jeg på et Django-projekt og ønskede at implementere hurtig fritekstsøgning. I stedet for at bruge en almindelig database til denne søgefunktion - såsom MySQL eller PostgreSQL - besluttede jeg at bruge en NoSQL-database. Det var da jeg opdagede ElasticSearch.

ElasticSearch indekserer dokumenter til dine data i stedet for at bruge datatabeller som en almindelig relationsdatabase gør. Dette fremskynder søgningen og tilbyder mange andre fordele, som du ikke får med en almindelig database. Jeg har også holdt en regelmæssig relationsdatabase til lagring af brugeroplysninger, logins og andre data, som ElasticSearch ikke behøvede at indeksere.

Efter at have søgt længe om, hvordan jeg korrekt implementerer ElasticSearch med Django, fandt jeg ikke rigtig tilfredsstillende svar. Nogle guider eller tutorials var indviklede og syntes at tage unødvendige skridt for at indeksere dataene til ElasticSearch. Der var ganske lidt information om, hvordan man udfører søgning, men ikke så meget om, hvordan indekseringen skal gøres. Jeg følte, at der måtte være en enklere løsning derude, så jeg besluttede selv at prøve det.

Jeg ønskede at holde det så simpelt som muligt, fordi enkle løsninger har tendens til at være de bedste efter min mening. KISS (Keep It Simple Stupid), Less is More, og alt det der er noget, der resonerer meget med mig, især når enhver anden løsning derude er kompleks. Jeg besluttede at bruge Honza Králs eksempel i denne video til at have noget at basere min kode på. Jeg anbefaler at se det, selvom det er lidt forældet på dette tidspunkt.

Da jeg brugte Django - som er skrevet i Python - var det let at interagere med ElasticSearch. Der er to klientbiblioteker, der kan interagere med ElasticSearch med Python. Der er elasticsearch-py, som er den officielle lavniveauklient. Og der er elasticsearch-dsl, som bygger på den tidligere, men giver en højere abstraktion med lidt mindre funktionalitet.

Vi kommer snart ind på nogle eksempler, men først skal jeg afklare, hvad vi vil opnå:

  • Opsætning af ElasticSearch på vores lokale maskine og sikre, at den fungerer korrekt
  • Opsætning af et nyt Django-projekt
  • Masseindeksering af data, der allerede er i databasen
  • Indeksering af hver ny forekomst, som en bruger gemmer i databasen
  • Et grundlæggende søgeeksempel

Okay, det virker simpelt nok. Lad os komme i gang ved at installere ElasticSearch på vores maskine. Også al koden vil være tilgængelig på min GitHub, så du nemt kan følge eksemplerne.

Installation af ElasticSearch

Da ElasticSearch kører på Java, skal du sikre dig, at du har en opdateret JVM-version. Tjek hvilken version du har med java -versioni terminalen. Derefter kører du følgende kommandoer for at oprette en ny mappe, downloade, udtrække og starte ElasticSearch:

mkdir elasticsearch-example
wget //artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.1.1.tar.gz
tar -xzf elasticsearch-5.1.1.tar.gz
./elasticsearch-5.1.1/bin/elasticsearch

Når ElasticSearch starter, skal der udskrives en masse output til terminalvinduet. For at kontrollere, at det fungerer korrekt, skal du åbne et nyt terminalvindue og køre denne curlkommando:

curl -XGET //localhost:9200

Svaret skal være sådan:

{ "name" : "6xIrzqq", "cluster_name" : "elasticsearch", "cluster_uuid" : "eUH9REKyQOy4RKPzkuRI1g", "version" : { "number" : "5.1.1", "build_hash" : "5395e21", "build_date" : "2016-12-06T12:36:15.409Z", "build_snapshot" : false, "lucene_version" : "6.3.0" }, "tagline" : "You Know, for Search"

Godt, du har nu ElasticSearch kørende på din lokale maskine! Det er tid til at oprette dit Django-projekt.

Oprettelse af et Django-projekt

Først opretter du et virtuelt miljø med virtualenv venvog indtaster det med source venv/bin/activatefor at holde alt indeholdt. Derefter installerer du nogle pakker:

pip install djangopip install elasticsearch-dsl

For at starte et nyt Django-projekt, du kører:

django-admin startproject elasticsearchprojectcd elasticsearchprojectpython manage.py startapp elasticsearchapp

Når du har oprettet dine nye Django-projekter, skal du oprette en model, som du vil bruge. Til denne guide valgte jeg at gå med et godt gammeldags blogindlægseksempel. I models.pyplacerer du følgende kode:

from django.db import modelsfrom django.utils import timezonefrom django.contrib.auth.models import User# Create your models here.# Blogpost to be indexed into ElasticSearchclass BlogPost(models.Model): author = models.ForeignKey(User, on_delete=models.CASCADE, related_name="blogpost") posted_date = models.DateField(default=timezone.now) title = models.CharField(max_length=200) text = models.TextField(max_length=1000)

Hidtil ret ligetil. Glem ikke at tilføje elasticsearchapptil INSTALLED_APPSi settings.pyog registrere din nye blogpost model admin.pypå denne måde:

from django.contrib import adminfrom .models import BlogPost# Register your models here.# Need to register my BlogPost so it shows up in the adminadmin.site.register(BlogPost)

Du skal også python manage.py makemigrations, python manage.py migrate og python manage.py createsuperuserat oprette databasen og en admin konto. Gå nu python manage.py runservertil //localhost:8000/admin/og log ind. Du skal nu kunne se din blogindlægsmodel der. Gå videre og opret dit første blogindlæg i administratoren.

Tillykke, du har nu et fungerende Django-projekt! Det er endelig tid til at komme ind i de sjove ting - at forbinde ElasticSearch.

Forbinder ElasticSearch med Django

Du begynder med at oprette en ny fil, der kaldes search.pyi vores elasticsearchappbibliotek. Det er her, ElasticSearch-koden vil bo. Den første ting, du skal gøre her, er at oprette en forbindelse fra din Django-applikation til ElasticSearch. Du gør dette i din search.pyfil:

from elasticsearch_dsl.connections import connectionsconnections.create_connection()

Nu hvor du har en global forbindelse til din ElasticSearch-opsætning, skal du definere, hvad du vil indeksere i den. Skriv denne kode:

from elasticsearch_dsl.connections import connectionsfrom elasticsearch_dsl import DocType, Text, Dateconnections.create_connection()class BlogPostIndex(DocType): author = Text() posted_date = Date() title = Text() text = Text() class Meta: index = 'blogpost-index'

Det ligner din model, ikke? Det DocTypefungerer som en indpakning, så du kan skrive et indeks som en model, og Textog Dateer felterne, så de får det rigtige format, når de indekseres.

Inde i Meta fortæller du ElasticSearch, hvad du vil have, at indekset skal hedde. Dette vil være et referencepunkt for ElasticSearch, så det ved, hvilket indeks det har at gøre med, når det initialiseres i databasen og gemmes hver oprettet ny objektforekomst.

Nu skal du faktisk oprette kortlægningen af ​​din nyoprettede BlogPostIndexi ElasticSearch. Du kan gøre dette og også oprette en måde at udføre bulkindeksering på samme tid - hvor praktisk er det?

Bulkindeksering af data

Den bulkkommando ligger i elasticsearch.helpers, som er inkluderet, når du har installeret elasticsearch_dsl, da det er bygget på toppen af det bibliotek. Gør følgende i search.py:

...from elasticsearch.helpers import bulkfrom elasticsearch import Elasticsearchfrom . import models...
...def bulk_indexing(): BlogPostIndex.init() es = Elasticsearch() bulk(client=es, actions=(b.indexing() for b in models.BlogPost.objects.all().iterator()))

"Hvad sker der her?" tænker du måske. Det er faktisk ikke så kompliceret.

Since you only want to do bulk indexing whenever you change something in our model you init() the model which maps it into ElasticSearch. Then, you use the bulk and pass it an instance of Elasticsearch() which will create a connection to ElasticSearch. You then pass a generator to actions= and iterate over all the BlogPost objects you have in your regular database and call the .indexing() method on each object. Why a generator? Because if you had a lot of objects to iterate over a generator would not have to first load them into memory.

There is just one problem with the above code. You don’t have an .indexing() method on your model yet. Lets fix that:

...from .search import BlogPostIndex...
...# Add indexing method to BlogPostdef indexing(self): obj = BlogPostIndex( meta={'id': self.id}, author=self.author.username, posted_date=self.posted_date, title=self.title, text=self.text ) obj.save() return obj.to_dict(include_meta=True)

You add the indexing method to the BlogPost model. It returns a BlogPostIndex and gets saved to ElasticSearch.

Lets try this out now and see if you can bulk index the blog post you previously created. By running python manage.py shell you go into the Django shell and import your search.py with from elasticsearchapp.search import * and then run bulk_indexing() to index all the blog posts in your database. To see if it worked you run the following curl command:

curl -XGET 'localhost:9200/blogpost-index/blog_post_index/1?pretty'

You should get back your first blog post in the terminal.

Indexing of newly saved instance

Next you need to add a signal that fires the .indexing() on each new instance that is saved every time a user saves a new blog post. In elasticsearchapp create a new file called signals.py and add this code:

from .models import BlogPostfrom django.db.models.signals import post_savefrom django.dispatch import [email protected](post_save, sender=BlogPost)def index_post(sender, instance, **kwargs): instance.indexing()

The post_save signal will ensure that the saved instance will get indexed with the .indexing() method after it is saved.

In order for this to work we also need to register Django that we’re using signals. We do this opening apps.py and adding the following code:

from django.apps import AppConfigclass ElasticsearchappConfig(AppConfig): name = 'elasticsearchapp' def ready(self): import elasticsearchapp.signals

To to complete this we also need to tell Django that we’re using this new configuration. We do this inside the __init__.py inside our elasticsearchapp directory by adding:

default_app_config = 'elasticsearchapp.apps.ElasticsearchappConfig'

Now the post_save signal is registered with Django and is ready to listen for whenever a new blogpost is being saved.

Try it our by going into the Django admin again and saving a new blogpost. Then check with a curl command if it was successfully indexed into ElasticSearch.

Simple search

Now lets make a simple search function in search.py to find all posts filtered by author:

...from elasticsearch_dsl import DocType, Text, Date, Search...
...def search(author): s = Search().filter('term', author=author) response = s.execute() return response

Lets try the search out. In the shell: from elasticsearchapp.search import * and run print(search(author=" gt;")) :

>>> print(search(author="home"))
     

There you have it! You have now successfully indexed all your instances into ElasticSearch, created a post_save signal that indexes each newly saved instance, and created a function to search our ElasticSearch database for your data.

Conclusion

This was a quite lengthy article but I hope it is written simple enough for even the beginner to be able to understand.

I explained how to connect a Django model to ElasticSearch for indexing and searching, but there is so much more that ElasticSearch can do. I recommend reading on their website and exploring what other possibilities exist, such as spatial operations and full text search with intelligent highlighting. Its a great tool and I will be sure to use it in future projects!

If you liked this article or have a comment or suggestion, please feel free to leave a message below. And stay tuned for more interesting stuff!