diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bddba0548c..63ef139def 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,13 +11,13 @@ env: PYTHON_VERSION: "3.9" MARIADB_VERSION: "10.4.10" COVERALLS_VERSION: "3.3.1" # check if Coverage needs to be also updated in requirements-ci.txt + TYPESENSE_VERSION: "27.0" # needs to be also updated in scripts/define_variable.sh # As GitHub Action does not allow environment variables # to be used in services definitions, these are only for # reference. If you update these versions, you HAVE TO # update the versions in the services definitions of the # test job. - ELASTICSEARCH_VERSION: "5.5.2" MEMCACHED_VERSION: "1.6" jobs: @@ -132,7 +132,7 @@ jobs: # Test the zds-site project. # Install the project, using assets created during the previous job, - # and install elasticsearch & memcache as a service. Then, run the tests + # and install Typesense & Memcached as a service. Then, run the tests # in a matrix build to parallelize multiple components. test: name: Install and test zds-site @@ -144,27 +144,11 @@ jobs: module: [ "zds.tutorialv2", - "zds.member zds.gallery zds.searchv2 zds.middlewares zds.pages", + "zds.member zds.gallery zds.search zds.middlewares zds.pages", "zds.forum zds.featured zds.mp zds.notification zds.utils", ] services: - elasticsearch: - image: "elasticsearch:5.5.2" - ports: - - "9200:9200" - env: - "http.host": "0.0.0.0" - "transport.host": "127.0.0.1" - "xpack.security.enabled": false - "ES_JAVA_OPTS": "-Xms512m -Xmx512m" - options: >- - -e="discovery.type=single-node" - --health-cmd="curl http://localhost:9200/_cluster/health" - --health-interval=10s - --health-timeout=5s - --health-retries=10 - memcached: image: "memcached:1.6" ports: @@ -183,6 +167,12 @@ jobs: mysql database: "ci_db_name" mysql root password: "ci_root_password" + - name: Start Typesense + uses: jirevwe/typesense-github-action@v1.0.1 + with: + typesense-version: ${{ env.TYPESENSE_VERSION }} + typesense-api-key: xyz + - name: Checkout uses: actions/checkout@v4 diff --git a/Makefile b/Makefile index f8349682ef..59fc204670 100644 --- a/Makefile +++ b/Makefile @@ -99,16 +99,16 @@ zmd-stop: ## Stop the zmarkdown server node ./zmd/node_modules/pm2/bin/pm2 kill ## -## ~ Elastic Search +## ~ Search Engine -run-elasticsearch: ## Run the Elastic Search server - elasticsearch || echo 'No Elastic Search installed (you can add it locally with `./scripts/install_zds.sh +elastic-local`)' +run-search-engine: ## Run the search server + ./.local/typesense/typesense-server --data-dir=.local/typesense/typesense-data --api-key=xyz || echo 'No Typesense installed (you can add it locally with `./scripts/install_zds.sh +typesense-local`)' -index-all: ## Index the database in a new Elastic Search index - python manage.py es_manager index_all +index-all: ## Index the whole database in the search engine + python manage.py search_engine_manager index_all -index-flagged: ## Index the database in the current Elastic Search index - python manage.py es_manager index_flagged +index-flagged: ## Index new content in the search engine + python manage.py search_engine_manager index_flagged ## ## ~ PDF diff --git a/assets/scss/base/_base.scss b/assets/scss/base/_base.scss index f4f4f52441..a5676bbc59 100644 --- a/assets/scss/base/_base.scss +++ b/assets/scss/base/_base.scss @@ -138,6 +138,10 @@ nav { } } +.align-center { + text-align: center; +} + @include desktop { body { min-height: 100%; diff --git a/doc/source/back-end-code/searchv2.rst b/doc/source/back-end-code/search.rst similarity index 61% rename from doc/source/back-end-code/searchv2.rst rename to doc/source/back-end-code/search.rst index a31835f3f3..225dd48a4f 100644 --- a/doc/source/back-end-code/searchv2.rst +++ b/doc/source/back-end-code/search.rst @@ -1,19 +1,19 @@ ============================ -La recherche (``searchv2/``) +La recherche (``search/``) ============================ -Module situé dans ``zds/searchv2/``. +Module situé dans ``zds/search/``. .. contents:: Fichiers documentés : Modèles (``models.py``) ======================= -.. automodule:: zds.searchv2.models +.. automodule:: zds.search.models :members: Vues (``views.py``) =================== -.. automodule:: zds.searchv2.views +.. automodule:: zds.search.views :members: diff --git a/doc/source/back-end/search.rst b/doc/source/back-end/search.rst new file mode 100644 index 0000000000..110244e099 --- /dev/null +++ b/doc/source/back-end/search.rst @@ -0,0 +1,394 @@ +============ +La recherche +============ + +Principe +======== + +Comment faire une recherche ? +----------------------------- + +La recherche se découpe en deux parties distinctes : + + - L'indexation des données + - La recherche par l'utilisateur + +L'indexation des données +++++++++++++++++++++++++ + +**L'indexation** des données consiste à **rassembler toutes les données** dans +lesquelles l'utilisateur va **pouvoir rechercher**. Elle est faite au +préalable. Celle-ci est faite de telle façon qu'on puisse rechercher dans les +éléments suivants : + + - Les contenus (article, tutoriels et billets) ainsi que leurs chapitres (s'il + s'agit d'un moyen ou *big*-tuto) ; + - Les sujets ; + - Les réponses aux sujets. + +Cette indexation est réalisée à intervalle régulier (et de manière à n'indexer +que les données qui ont changé). + +La recherche +++++++++++++ + +L'utilisateur peut utiliser la recherche, en utilisant la recherche de +`l'en-tête <../front-end/structure-du-site.html#l-en-tete>`_, ou par la page +d'accueil, si elle est disponible. + + .. figure:: ../images/design/en-tete.png + :align: center + +Des critères de recherche peuvent être ajoutés sur la page de recherche. Le +seul critère de recherche disponible actuellement est le type de résultat +(contenu, sujet du forum ou message du forum). + + .. figure:: ../images/search/search-filters.png + :align: center + +Quelques mots sur Typesense +------------------------------- + +`Typesense `_ est un moteur de recherche qui permet +d’indexer et de rechercher des données. Typesense offre une interface de type +REST pour interroger son index, mais nous utilisons plutôt le module Python +dédié. + +Phase d'indexation +++++++++++++++++++ + +Typesense organise les données sous forme de documents, regroupés dans des +collections. On peut avoir différent types de collections (par exemple pour +Zeste de Savoir : *topics*, *posts*, contenus, chapitres, etc). + +La phase d'indexation est réalisée à l'aide de la commande ``python manage.py +search_engine_manager`` (voir ci-dessous). + +Phase de recherche +++++++++++++++++++ + +Durant la phase de recherche, les documents sont classés par ``text_match``, +valeur qui représente le score de correspondance avec le texte recherché. Ce +score dépend des champs que l'on souhaite indexer, il est calculé selon +plusieurs métriques : + ++ *Fréquence* : elle correspond au nombre de fois qu’un terme apparaît dans un + document ; ++ *Distance d'édition* : si un terme de la requête n'est pas trouvé dans les + documents, Typesense recherchera des mots qui diffèrent de la requête d'un + certain nombre de caractères (``num_typos``) en ajoutant, supprimant ou + remplaçant des caractères ; ++ *Proximité* : si la requête est constituée de plusieurs termes et que ces + termes sont proches alors le score sera plus élevé. Par exemple, si la + requête est "moteur de recherche". Le titre *Typesense est un moteur de + recherche* aura un meilleur score que le titre *La recherche d'un nouveau + moteur thermique à pistons rotatifs* ; ++ *Ordre des champs* : si on a indiqué qu'on recherche selon les champs *titre* + et *description* (dans cet ordre), alors le score sera plus important si le + terme est trouvé dans le champ *titre* ; ++ *Pondération des champs* : si un document possède un champ *titre* et un + champ *description*, alors avec des poids supérieur pour le champ *titre*, le + score sera plus élevé si le terme est trouvé dans le titre. + +Les différents poids sont modifiables directement dans les paramètres de Zeste +de Savoir (voir ci-dessous). + +Il est possible de rechercher dans plusieurs collections en une seule requête, +avec un mécanisme que Typesense appele le `Federated Multi-Search +`_. + +En pratique +=========== + +Configuration +------------- + +La configuration de la connexion se fait dans le fichier +``settings/abstract_base/zds.py``, à l'aide des deux variables suivantes : + +.. sourcecode:: python + + SEARCH_ENABLED = True + + SEARCH_CONNECTION = { + "nodes": [ + { + "host": "localhost", + "port": "8108", + "protocol": "http", + } + ], + "api_key": "xyz", + "connection_timeout_seconds": 2, + } + + +La première active le moteur de recherche, la seconde permet de configurer la +connexion au moteur de recherche. + +Pour indiquer, les poids associés à chacune des collections, il faut modifier +les variables suivantes dans ``settings/abstract_base/zds.py`` : + +.. sourcecode:: python + + global_weight_publishedcontent = 3 # contenus publiés (billets, tutoriaux, articles) + global_weight_topic = 2 # sujets de forum + global_weight_chapter = 1.5 # chapitres + global_weight_post = 1 # messages d'un sujet de forum + + +Il est possible de modifier les différents paramètres de la recherche dans +``settings/abstract_base/zds.py`` : + +.. sourcecode:: python + + "search": { + "mark_keywords": ["javafx", "haskell", "groovy", "powershell", "latex", "linux", "windows"], + "results_per_page": 20, + "search_groups": { + "publishedcontent": (_("Contenus publiés"), ["publishedcontent", "chapter"]), + "topic": (_("Sujets du forum"), ["topic"]), + "post": (_("Messages du forum"), ["post"]), + }, + "search_content_type": { + "tutorial": (_("Tutoriels"), ["tutorial"]), + "article": (_("Articles"), ["article"]), + "opinion": (_("Billet"), ["opinion"]), + }, + "search_validated_content": { + "validated": (_("Contenus validés"), ["validated"]), + "no_validated": (_("Contenus libres"), ["no_validated"]), + }, + "boosts": { + "publishedcontent": { + "global": global_weight_publishedcontent, + "if_article": 2.0, # s'il s'agit d'un article + "if_tutorial": 2.0, # s'il s'agit d'un tuto + "if_medium_or_big_tutorial": 2.5, # s'il s'agit d'un tuto d'une taille plutôt importante + "if_opinion": 1.66, # s'il s'agit d'un billet + "if_opinion_not_picked": 1.5, # s'il s'agit d'un billet pas mis en avant + + # poids des différents champs : + "title": global_weight_publishedcontent * 3, + "description": global_weight_publishedcontent * 2, + "categories": global_weight_publishedcontent * 1, + "subcategories": global_weight_publishedcontent * 1, + "tags": global_weight_publishedcontent * 1, + "text": global_weight_publishedcontent * 2, + }, + "topic": { + "global": global_weight_topic, + "if_solved": 1.1, # s'il s'agit d'un sujet résolu + "if_sticky": 1.2, # s'il s'agit d'un sujet épinglé + "if_locked": 0.1, # s'il s'agit d'un sujet fermé + + # poids des différents champs : + "title": global_weight_topic * 3, + "subtitle": global_weight_topic * 2, + "tags": global_weight_topic * 1, + }, + "chapter": { + "global": global_weight_chapter, + + # poids des différents champs : + "title": global_weight_chapter * 3, + "text": global_weight_chapter * 2, + }, + "post": { + "global": global_weight_post, + "if_first": 1.2, # s'il s'agit d'un message en première position + "if_useful": 1.5, # s'il s'agit d'un message jugé utile + "ld_ratio_above_1": 1.05, # si le ratio pouce vert/rouge est supérieur à 1 + "ld_ratio_below_1": 0.95, # si le ratio pouce vert/rouge est inférieur à 1 + "text_html": global_weight_post, # poids du champ + }, + }, + + ++ ``results_per_page`` est le nombre de résultats affichés, ++ ``search_groups`` définit les différents types de documents indexés et la + manière dont ils sont groupés sur le formulaire de recherche, ++ ``search_content_type`` définit les différents types de contenus publiés et + la manière dont ils sont groupés sur le formulaire de recherche, ++ ``search_validated_content`` définit les différentes validations des contenus + publiés et la manière dont elles sont groupées sur le formulaire de recherche, ++ ``boosts`` contient les différents facteurs de *boost* appliqués aux + différentes situations. Modifier ces valeurs permet de changer l'ordre des + résultats retourés lors d'une recherche. + + +Indexer les données +------------------- + +Une fois Typesense `installé <../install/extra-install-search-engine.html>`_, configuré et lancé, la commande suivante est utilisée : + +.. sourcecode:: bash + + python manage.py search_engine_manager + +où ```` peut être : + ++ ``setup`` : crée et configure le *client* Typesense (y compris la création des + *collections* avec *schémas*) ; ++ ``clear`` : supprime toutes les *collections* du *client* Typesense et marque + toutes les données comme "à indexer" ; ++ ``index_flagged`` : indexe les données marquées comme "à indexer" ; ++ ``index_all`` : invoque ``setup`` puis indexe toute les données (qu'elles + soient marquées comme "à indexer" ou non). + + +La commande ``index_flagged`` peut donc être lancée de manière régulière afin +d'indexer les nouvelles données ou les données modifiées. + +.. note:: + + Le caractère "à indexer" est fonction des actions effectuées sur l'objet + Django (par défaut, à chaque fois que la méthode ``save()`` du modèle est + appelée, l'objet est marqué comme "à indexer"). + Cette information est stockée dans la base de donnée MySQL. + +Aspects techniques +================== + +Indexation d'un modèle +---------------------- + + +Afin d'être indexable, un modèle Django doit dériver de +``AbstractSearchIndexableModel`` (qui dérive de ``models.Model`` et de +``AbstractSearchIndexable``). Par exemple : + +.. sourcecode:: python + + class Post(Comment, AbstractSearchIndexableModel): + # ... + + +.. note:: + + Le code est écrit de manière à ce que l'id utilisé par Typesense (champ + ``id``) corresponde à la *pk* du modèle (via la variable + ``search_engine_id``). De cette façon, si on en connait la *pk* d'un objet + Django, il est possible de récupérer l'objet Typesense correspondant à + l'aide de ``GET /collections//documents/``. + +Différentes méthodes de la classe ``AbstractSearchIndexableModel`` peuvent ou +doivent ensuite être surchargées : + ++ ``get_search_document_schema()`` permet de définir le *schéma* d'un document, c'est + à dire quels champs seront indexés avec quels types. Par exemple : + + .. sourcecode:: python + + @classmethod + def get_search_document_schema(cls): + search_engine_schema = super().get_search_document_schema() + + search_engine_schema["fields"] = [ + {"name": "topic_pk", "type": "int64"}, + {"name": "forum_pk", "type": "int64"}, + {"name": "topic_title", "type": "string", "facet": True}, + # ... + + Les schémas Typesense sont des `dictionnaires + `_. + On indique également dans les schémas un poids de recherche qui est + calculé selon différent critères, ce champ correspond au boost que reçoit + le contenu lors de la phase de recherche. + ++ ``get_indexable_objects`` permet de définir quels objets doivent être + récupérés et indexés. Cette fonction permet également d'utiliser + ``prefetch_related()`` ou ``select_related()`` pour minimiser le nombre de + requêtes SQL. Par exemple : + + .. sourcecode:: python + + @classmethod + def get_indexable_objects(cls, force_reindexing=False): + q = super(Post, cls).get_indexable_objects(force_reindexing)\ + .prefetch_related('topic')\ + .prefetch_related('topic__forum') + + où ``q`` est un *queryset* Django. + ++ ``get_document_source()`` permet de gérer des cas où le champ n'est pas + directement une propriété de la classe, ou si cette propriété ne peut pas + être indexée directement : + + .. sourcecode:: python + + def get_document_source(self, excluded_fields=None): + excluded_fields = excluded_fields or [] + excluded_fields.extend(["tags", "forum_pk", "forum_title", "forum_get_absolute_url", "pubdate", "weight"]) + + data = super().get_document_source(excluded_fields=excluded_fields) + data["tags"] = [tag.title for tag in self.tags.all()] + data["forum_pk"] = self.forum.pk + data["forum_title"] = self.forum.title + data["forum_get_absolute_url"] = self.forum.get_absolute_url() + data["pubdate"] = date_to_timestamp_int(self.pubdate) + data["text"] = clean_html(self.text_html) + data["weight"] = self._compute_search_weight() + + return data + + Dans cet exemple (issu de la classe ``Post``), on voit que certains + champs ne peuvent être directement indexés car ils appartiennent au + *topic* et au *forum* parent. Il sont donc exclus du mécanisme par défaut + (via la variable ``excluded_fields``) et leur valeur est récupérée et + définie dans la suite de la méthode. + + Cet exemple permet également de remarquer que le contenu indéxé ne + contient jamais de balises HTML (c'est le rôle de la fonction + ``clean_html()``). Il est ainsi possible d'afficher de façon sûre le + contenu renvoyé par Typesense (utile en particulier pour afficher les + balises ```` pour surligner les termes recherchés). + + +Finalement, il est important **pour chaque type de document** d'attraper le +signal de pré-suppression en base de données, afin que le document soit +également supprimé du moteur de recherche. + +Plus d'informations sur les méthodes qui peuvent être surchargées sont +disponibles `dans la documentation technique +<../back-end-code/search.html>`_. + +.. attention:: + + À chaque fois que vous modifiez la définition d'un schéma d'une + collection dans ``get_search_document_schema()``, toutes les données doivent + être réindexées. + +Le cas particulier des contenus +------------------------------- + +La plupart des informations des contenus, en particulier les textes, `ne sont +pas stockés dans la base de données +`_. + +Il a été choisi de n'inclure dans le moteur de recherche que les chapitres de +ces contenus (anciennement, les introductions et conclusions des parties +étaient également incluses). Ce sont les contenus HTML qui sont indexés et non +leur version écrite en Markdown, afin de rester cohérent avec ce qui se fait +pour les *posts*. Les avantages de cette décision sont multiples : + ++ Le *parsing* est déjà effectué et n'a pas à être refait durant l'indexation ; ++ Moins de fichiers à lire (pour rappel, les différentes parties d'un contenu + `sont rassemblées en un seul fichier + `_ à la publication) ; ++ Pas besoin d'utiliser Git durant le processus d'indexation ; + + +L'indexation des chapitres (représentés par la classe ``FakeChapter``, `voir +ici +<../back-end-code/tutorialv2.html#zds.tutorialv2.models.database.FakeChapter>`_) +est effectuée en même temps que l'indexation des contenus publiés +(``PublishedContent``). En particulier, c'est la méthode ``get_indexable()`` +qui est surchargée, profitant du fait que cette méthode peut renvoyer n'importe +quel type de document à indexer. + +Le code tient aussi compte du fait que la classe ``PublishedContent`` `gère le +changement de slug `_ afin de +maintenir le SEO. Ainsi, la méthode ``save()`` est modifiée de manière à +supprimer toute référence à elle même et aux chapitres correspondants si un +objet correspondant au même contenu mais avec un nouveau slug est créé. diff --git a/doc/source/back-end/searchv2.rst b/doc/source/back-end/searchv2.rst deleted file mode 100644 index 4585581806..0000000000 --- a/doc/source/back-end/searchv2.rst +++ /dev/null @@ -1,362 +0,0 @@ -============ -La recherche -============ - -Principe -======== - -Comment faire une recherche ? ------------------------------ - -La recherche se découpe en deux parties distinctes : - - - L'indexation des données - - La recherche par l'utilisateur - -L'indexation des données -++++++++++++++++++++++++ - -**L'indexation** des données consiste à **rassembler toutes les données** dans lesquelles l'utilisateur va **pouvoir rechercher**. Elle est faite au préalable. -Celle-ci est faite de telle façon qu'on puisse rechercher dans les éléments suivants : - - - Les contenus (article et tutoriels) ainsi que leurs chapitres (s'il s'agit d'un moyen ou *big*-tuto); - - Les sujets ; - - Les réponses aux sujets. - -Cette indexation est réalisée à intervalle régulier (et de manière à n'indexer que les données qui ont changées). - -La recherche -++++++++++++ - -L'utilisateur peut utiliser la recherche, en utilisant la recherche de `l'en-tête <../front-end/structure-du-site.html#l-en-tete>`_, ou par la page d'accueil, si elle est disponible. - - .. figure:: ../images/design/en-tete.png - :align: center - -Des critères de recherche peuvent être ajoutés sur la page de recherche. -Le seul critère de recherche disponible actuellement est le type de résultat (contenu, sujet du forum ou message du forum). - - .. figure:: ../images/search/search-filters.png - :align: center - -Quelques mots sur Elasticsearch -------------------------------- - -`Elasticsearch `_ (ES) est un serveur utilisant `Lucene `_ (bibliothèque d'indexation et de recherche de texte) et permet d'indexer et de rechercher des données. -Il est possible de l'interroger à travers une interface de type REST à laquelle on communique via des requêtes écrites en JSON. -Ce projet propose également des API `bas `_ et `plus haut `_ niveau en python pour interagir avec le serveur, maintenues par l'équipe d'Elasticsearch. - -Précédemment, ZdS utilisait `Haystack `_ pour communiquer avec `Solr `_ (équivalent à Elasticsearch) mais ces solutions ont été abandonnées par manque d'activité sur le dépôt de Haystack. - -Phase d'indexation -++++++++++++++++++ - -ES classe ses données sous forme de documents, rassemblés dans un *index*. On peut avoir différent types de documents (*topics*, *posts*, contenus, chapitres dans ce cas-ci). - -Lorsque les documents sont indexés, ils sont analysés afin d'en extraire les termes importants et de les simplifier (par défaut, "table" et "tables" ne sont pas le même mot, mais il est possible de faire en sorte que si). -Ce processus est effectué par l'*analyzer*, découpé en trois étapes: - -.. sourcecode:: none - - Entrée > character filter > tokenizer > token filter > sortie - -On retrouve: - -+ *character filter*: tâche de nettoyage basique, telle qu'enlever les tags HTML. Il y en a `trois `_ qui sont disponibles par défaut ; -+ *tokenizer*: découpe le texte en différents *tokens*. `Énormément `_ de *tokenizer* sont disponibles. -+ *token filter*: altère la liste de *tokens* obtenue pour les "normaliser" en modifiant, supprimant ou rajoutant des *tokens*. Typiquement: enlever certains mots (par exemple les *stopwords* "le", "la", "les" et autres en français), convertir le tout en minuscule, et ainsi de suite. Il en existe également `une pléthore `_. - -Ces différents filtres permettent d'éliminer le superflu afin de se concentrer sur l'essentiel : les *tokens* obtenus. -Par la suite, ES construit une table (un *index inversé*) reliant ces *tokens* aux documents qui les contiennent, qu'il utilise pour la recherche. - -Sans entrer dans les détails, l'*analyzer* utilisé par ES pour ZdS : - -+ Enlève les tags HTML (en pratique, l'indexation du texte se fait systématiquement sur le contenu converti en HTML et non sur le texte en *markdown*) ; -+ N'utilise par le *tokenizer* par défaut (découper en *token* après tout caractère non alpha-numérique, en gros) afin de conserver "c++" intact, par exemple ; -+ Utilise une série de *token filter*s orientés pour comprendre le français, parmi lesquels un *stopper* (pour enlever les prépositions, déterminants, ...) et un *stemmer* (qui se charge, à partir d'un mot, d'en extraire la racine. Par exemple "programmation", "programmer" ou "programmes" seront tout les trois interprétés et indexés de la même manière car ils partagent la même racine). - -Les différents *tokens* qui resortent de cette phase d'analyse sont alors indexés, et c'est de ces *tokens* dont ES se servira ensuite pour la recherche, plutôt que de réaliser des recherches *full-text*. - -La phase d'indexation est réalisée à l'aide de la commande ``python manage.py es_manager`` (voir ci-dessous). - -Phase de recherche -++++++++++++++++++ - -Durant la phase de recherche, les documents sont classés par **score**, valeur que ES calcule comme étant le produit ``TF * IDF``, où la partie TF (*term frequencies*) est le nombre de fois qu'un terme apparait dans un document et IDF (*inverse document frequencies*) est la fréquence à laquelle ce terme apparait dans l'ensemble des documents indexés. - -C'est en fonction de ce score que seront ensuite classés les résultats, du plus important au plus faible. -Il est possible de manipuler ce score afin d'obtenir les résultats les plus pertinents possible : - -+ *Booster* le champ (à priori) : si le terme recherché est contenu dans un champ donné (par exemple le titre, ou une note de bas de page), le score est multiplié par le facteur de *boost* du champ. -+ *Booster* le score (à postériori): si le document obtenu possède d'autres propriétés (par exemple, *booster* le score si le *post* trouvé à "aidé l'auteur du sujet"). -+ *Booster* un type de document par rapport à un autre : cas particulier du précédent. - -Ces facteurs de *boost* sont modifiables soit directement dans le code de ZdS pour ce qui concerne les facteurs de *boost* sur les champs (voir ci-dessous), soit dans le ``settings.py`` en ce qui concerne les *boosts* à postériori (voir ci-dessous). - - -En pratique -=========== - -Configuration -------------- - -La configuration de la connexion et de l'*index* se fait dans le ``settings.py``, à l'aide des trois variables suivantes : - -.. sourcecode:: python - - ES_ENABLED = True - - ES_CONNECTIONS = { - 'default': { - 'hosts': ['localhost:9200'], - } - } - - ES_SEARCH_INDEX = { - 'name': 'zds_search', - 'shards': 5, - 'replicas': 0, - } - - -La première active Elasticsearch pour ZdS. -La seconde permet de configurer la connexion à Elasticsearch. ``default`` est l'*alias* de la connexion, au cas où il serait nécessaire d'utiliser plusieurs *clusters*. -La troisième est la configuration de l'*index* avec son nom, son nombre de *shards* et de *replicas*. - -Pour modifier les différents paramètres d'une recherche, c'est cette fois dans la variable ``ZDS_APP`` que ça se passe: - -.. sourcecode:: python - - 'search': { - 'mark_keywords': ['javafx', 'haskell', 'groovy', 'powershell', 'latex', 'linux', 'windows'], - 'results_per_page': 20, - 'search_groups': { - 'content': ( - _(u'Contenus publiés'), ['publishedcontent', 'chapter'] - ), - 'topic': ( - _(u'Sujets du forum'), ['topic'] - ), - 'post': ( - _(u'Messages du forum'), ['post'] - ), - }, - 'boosts': { - 'publishedcontent': { - 'global': 3.0, - 'if_article': 1.0, # s'il s'agit d'un article - 'if_tutorial': 1.0, # … d'un tuto - }, - 'topic': { - 'global': 2.0, - 'if_solved': 1.1, # si le sujet est résolu - 'if_sticky': 1.2, # si le sujet est en post-it - 'if_locked': 0.1, # si le sujet est fermé - }, - 'chapter': { - 'global': 1.5, - }, - 'post': { - 'global': 1.0, - 'if_first': 1.2, # si le post est le premier du topic - 'if_useful': 1.5, # si le post a été marqué comme étant utile - 'ld_ratio_above_1': 1.05, # si le ratio pouce vert/rouge est supérieur à 1 - 'ld_ratio_below_1': 0.95, # ... inférieur à 1. - } - } - } - -où ``'mark_keywords'`` liste les mots qui ne doivent pas être découpés par le *stemmer* (souvent des noms propres), -``'results_per_page'`` est le nombre de résultats affichés, -``'search_groups'`` définit les différents types de documents indexé et la manière dont il sont groupés quand recherchés (sur le formulaire de recherche), -et ``'boosts'`` les différents facteurs de *boost* appliqués aux différentes situations. - -Puisque la phase de *stemming* advient à la fin de l'analyse, tous les mots listés dans ``'mark_keywords'`` doivent être en minuscule et sans éventuels déterminants. - -Dans ``'boosts'``, on peut ensuite modifier le comportement de la recherche en choisissant différents facteurs de *boost*. -Chacune des valeurs multiplie le score (donc l'agrandit si elle est supérieure à 1 et le diminue si elle est inférieure à 1). -Un *boost global* (dans chacune des variables ``'global'``) est tout d'abord présent et permet de mettre en avant un type de document par rapport à un autre. -Ensuite, différentes situations peuvent modifier le score. - -.. note:: - - Ces valeurs sont données à titre indicatif et doivent être adaptées à la situation. - -.. attention:: - - Pour que les changements dans ``'mark_keywords'`` soient pris en compte, il est nécessaire de réindexer **tout** le contenu - (grâce à ``python manage.py es_manager index_all``). - -Indexer les données de ZdS --------------------------- - -Une fois Elasticsearch `installé <../install/install-es.html>`_ puis configuré et lancé, la commande suivante est utilisée : - -.. sourcecode:: bash - - python manage.py es_manager - -où ```` peut être - -+ ``setup`` : crée et configure l'*index* (y compris le *mapping* et l'*analyzer*) dans le *cluster* d'ES ; -+ ``clear`` : supprime l'*index* du *cluster* d'ES et marque toutes les données comme "à indexer" ; -+ ``index_flagged`` : indexe les données marquées comme "à indexer" ; -+ ``index_all`` : invoque ``setup`` puis indexe toute les données (qu'elles soient marquées comme "à indexer" ou non). - - -La commande ``index_flagged`` peut donc être lancée de manière régulière (via un *cron* ou un timer *systemd*) afin d'indexer les nouvelles données ou les données modifiées de manière régulière. - -.. note:: - - Le caractère "à indexer" est fonction des actions effectuées sur l'objet Django (par défaut, à chaque fois que la méthode ``save()`` du modèle est appelée, l'objet est marqué comme "à indexer"). - Cette information est stockée dans la base de donnée MySQL. - -Aspects techniques -================== - -Indexation d'un modèle ----------------------- - - -Afin d'être indexable, un modèle Django doit dériver de ``AbstractESDjangoIndexable`` (qui dérive de ``models.Model`` et de ``AbstractESIndexable``). Par exemple, - -.. sourcecode:: python - - class Post(Comment, AbstractESDjangoIndexable): - # ... - - -.. note:: - - Le code est écrit de telle manière à ce que l'id utilisé par ES (champ ``_id``) corresponde à la *pk* du modèle (via la variable ``es_id``). - Il est donc facile de récupérer un objet dans ES si on en connait la *pk*, à l'aide de ``GET ///``. - -Différentes méthodes d'``AbstractESDjangoIndexable`` peuvent ou doivent ensuite être surchargées. Parmi ces dernières, - -+ ``get_es_mapping()`` permet de définir le *mapping* d'un document, c'est à dire quels champs seront indexés avec quels types. Par exemple, - - .. sourcecode:: python - - @classmethod - def get_es_mapping(cls): - es_mapping = super(Post, cls).get_es_mapping() - - es_mapping.field('text_html', Text()) - es_mapping.field('is_useful', Boolean()) - es_mapping.field('position', Integer()) - # ... - - ``Mapping`` est un type de donnée défini par ``elasticsearch_dsl`` (voir à ce sujet `la documentation `_). Si le champ a le même nom qu'une propriété de votre classe, sa valeur sera automatiquement récupérée et indexée. À noter que vous pouvez également marquer une variable comme "à ne pas analyser" avec la variable ``index`` (par exemple, ``Text(index='not_analyzed')``) si vous voulez simplement stocker cette valeur mais ne pas l'utiliser pour effectuer une recherche dessus. On peut également indiquer la valeur du facteur de *boost* avec ``boost`` (par exemple, ``Text(boost=2.0)``). - - .. note:: - - Elasticsearch requiert que deux champs portant le même nom dans le même *index* (même si ils sont issus de types de document différents) aient le même *mapping*. - Ainsi, tous les champs ``title`` doivent être de type ``Text(boost=1.5)`` et ``tags`` de type ``Keyword(boost=2.0)``. - -+ ``get_es_django_indexable()`` permet de définir quels objets doivent être récupérés et indexés. Cette fonction permet également d'utiliser ``prefetch_related()`` ou ``select_related()`` pour éviter les requêtes inutiles. Par exemple, - - .. sourcecode:: python - - @classmethod - def get_es_django_indexable(cls, force_reindexing=False): - q = super(Post, cls).get_es_django_indexable(force_reindexing)\ - .prefetch_related('topic')\ - .prefetch_related('topic__forum') - - où ``q`` est un *queryset* Django. - -+ ``get_es_document_source()`` permet de gérer des cas où le champ n'est pas directement une propriété de la classe, ou si cette propriété ne peut pas être indexée directement : - - .. sourcecode:: python - - def get_es_document_source(self, excluded_fields=None): - excluded_fields = excluded_fields or [] - excluded_fields.extend( - ['topic_title', 'forum_title', 'forum_pk', 'forum_get_absolute_url']) - - data = super(Post, self).get_es_document_source(excluded_fields=excluded_fields) - - data['topic_title'] = self.topic.title - data['forum_pk'] = self.topic.forum.pk - data['forum_title'] = self.topic.forum.title - data['forum_get_absolute_url'] = self.topic.forum.get_absolute_url() - - return data - - Dans cet exemple (issu de la classe ``Post``), on voit que certains champs ne peuvent être directement indexés car ils appartiennent au *topic* et au *forum* parent. Il sont donc exclus du mécanisme par défaut (via la variable ``excluded_fields``), leur valeur est récupérée et définie par après. - - -Finalement, il est important **pour chaque type de document** d'attraper le signal de pré-suppression avec la fonction ``delete_document_in_elasticsearch()``, afin qu'un document supprimé par Django soit également supprimé de Elasticsearch. -Cela s'effectue comme suit (par exemple pour la classe ``Post``): - -.. sourcecode:: python - - @receiver(pre_delete, sender=Post) - def delete_post_in_elasticsearch(sender, instance, **kwargs): - return delete_document_in_elasticsearch(instance) - -Plus d'informations sur les méthodes qui peuvent être surchargées sont disponibles `dans la documentation technique <../back-end-code/searchv2.html>`_. - -.. attention:: - - À chaque fois que vous modifiez le *mapping* d'un document dans ``get_es_mapping()``, tout l'*index* **doit** être reconstruit **et** indexé. - N'oubliez donc pas de mentionner cette action à lancer manuellement dans le *update.md*. - -Le cas particulier des contenus -------------------------------- - -La plupart des informations des contenus, en particulier les textes, `ne sont pas indexés dans la base de donnée `_. - -Il a été choisi de n'inclure dans Elasticsearch que les chapitres de ces contenus (anciennement, les introductions et conclusions des parties étaient également incluses). -Ce sont les contenus HTML qui sont indexés et non leur version écrite en *markdown*, afin de rester cohérent avec ce qui se fait pour les *posts*. -Les avantages de cette décision sont multiples : - -+ Le *parsing* est déjà effectué et n'a pas à être refait durant l'indexation ; -+ Moins de fichiers à lire (pour rappel, les différentes parties d'un contenu `sont rassemblées en un seul fichier `_ à la publication) ; -+ Pas besoin d'utiliser Git durant le processus d'indexation ; - - -L'indexation des chapitres (représentés par la classe ``FakeChapter``, `voir ici <../back-end-code/tutorialv2.html#zds.tutorialv2.models.database.FakeChapter>`_) est effectuée en même temps que l'indexation des contenus publiés (``PublishedContent``). -En particulier, c'est la méthode ``get_es_indexable()`` qui est surchargée, profitant du fait que cette méthode peut renvoyer n'importe quel type de document à indexer. - -.. sourcecode:: python - - @classmethod - def get_es_indexable(cls, force_reindexing=False): - """Overridden to also include - """ - - index_manager = ESIndexManager(**settings.ES_SEARCH_INDEX) - last_pk = 0 - objects_source = super(PublishedContent, cls).get_es_indexable(force_reindexing) - objects = list(objects_source.filter(pk__gt=last_pk)[:PublishedContent.objects_per_batch]) - while objects: - chapters = [] - - for content in objects: - versioned = content.load_public_version() - - if versioned.has_sub_containers(): # chapters are only indexed for middle and big tuto - - # delete possible previous chapters - if content.es_already_indexed: - index_manager.delete_by_query( - FakeChapter.get_es_document_type(), ES_Q('match', _routing=content.es_id)) - - # (re)index the new one(s) - for chapter in versioned.get_list_of_chapters(): - chapters.append(FakeChapter(chapter, versioned, content.es_id)) - last_pk = objects[-1].pk - objects = list(objects_source.filter(pk__gt=last_pk)[:PublishedContent.objects_per_batch]) - yield chapters - yield objects - - - -Le code tient aussi compte du fait que la classe ``PublishedContent`` `gère le changement de slug `_ afin de maintenir le SEO. -Ainsi, la méthode ``save()`` est modifiée de manière à supprimer toute référence à elle même et aux chapitres correspondants si un objet correspondant au même contenu mais avec un nouveau slug est créé. - -.. note:: - - Dans ES, une relation de type parent-enfant (`cf. documentation `_) est définie entre les contenus et les chapitres correspondants. - Cette relation est utilisée pour la suppression, mais il est possible de l'exploiter à d'autres fins. diff --git a/doc/source/front-end/template-tags.rst b/doc/source/front-end/template-tags.rst index e51fe356e5..9c9d1d7904 100644 --- a/doc/source/front-end/template-tags.rst +++ b/doc/source/front-end/template-tags.rst @@ -105,13 +105,6 @@ sera rendu : …si le contenu de ``date_epoch`` était de ``122``. -``from_elasticsearch_date`` ---------------------------- - -Par défaut, Elasticsearch stocke ces dates au format ``yyyy-MM-dd'T'HH:mm:ss.SSSZ`` -(il s'agit du format ``strict_date_time``, voir à ce sujet `la documentation d'Elasticsearch `_). -Ce filtre transforme cette date en une date que les autres filtres de ce module peuvent exploiter. - Le module ``email_obfuscator`` ============================== @@ -572,21 +565,6 @@ Exemple : {% endfor %} -Le module ``elasticsearch`` -=========================== - -``highlight`` - -Permet de mettre en surbrillance les résultats d'une recherche. - -Exemple : - -.. sourcecode:: html+django - - {% if search_result.text %} - {% highlight search_result "text" %} - {% endif %} - Le module ``joinby`` ==================== diff --git a/doc/source/guides/install-linux.rst b/doc/source/guides/install-linux.rst index 0c2b71fe56..1daa087a97 100644 --- a/doc/source/guides/install-linux.rst +++ b/doc/source/guides/install-linux.rst @@ -77,7 +77,7 @@ Installation complète L'installation de base suffit grandement pour lancer le site web et découvrir le projet, mais elle est incomplète. Deux outils manquent à l'appel : - LaTeX, nécessaire pour la génération des PDFs des contenus ; -- ElasticSearch, nécessaire pour la page de recherche. +- Typesense, nécessaire pour la page de recherche. Là encore, une seule commande suffit : diff --git a/doc/source/images/search/no-connection.png b/doc/source/images/search/no-connection.png index 050d8c9005..97913d91a1 100644 Binary files a/doc/source/images/search/no-connection.png and b/doc/source/images/search/no-connection.png differ diff --git a/doc/source/install/extra-install-es.rst b/doc/source/install/extra-install-es.rst deleted file mode 100644 index dc4db866a5..0000000000 --- a/doc/source/install/extra-install-es.rst +++ /dev/null @@ -1,163 +0,0 @@ -================================================= -Installation de Elasticsearch (pour la recherche) -================================================= - -Zeste de Savoir utilise **Elasticsearch 5**, un moteur de recherche très performant. -Installer Elasticsearch est nécessaire pour faire fonctionner la recherche. - - -Installation -============ - -.. attention:: - - Par défaut, Elasticsearch requiert au moins 2 Gio de mémoire pour démarrer. - - Si vous ne souhaitez pas utiliser autant de mémoire, modifiez le fichier ``config/jvm.option``, en particuliez les options ``-Xms`` et ``-Xmx``. - Par exemple, vous pouvez passer la valeur de ces variables à 512 Mio grâce à: - - .. sourcecode:: none - - -Xms512m - -Xmx512m - - Plus d'informations sont disponibles `dans la documentation officielle `_. - -Sous Linux ----------- - -Installer java 8 -++++++++++++++++ - -Il est nécessaire d'utiliser au moins **la version 8** de Java pour faire tourner Elasticsearch, mais ce n'est probablement pas la version par défaut de votre système d'exploitation. - -**Sous Debian et dérivés**, le package à installer est ``openjdk-8-jdk`` : - -+ Sous Ubuntu (et dérivés), s'il n'est pas disponible pour votre système, ajoutez le PPA suivant : ``add-apt-repository ppa:openjdk-r/ppa`` (**en root**). -+ Sous Debian, il est disponible dans le dépôt ``jessie-backports`` (ajoutez ``deb http://ftp.fr.debian.org/debian jessie-backports main`` dans ``/etc/apt/sources.list``). - -Une fois installé, passez sur la version 8 de java à l'aide de ``update-alternatives --config java`` (**en root**). - -**Sous Fedora et dérivés** (CentOS, OpenSuse, ...), le paquet à installer est ``java-1.8.0-openjdk``. -Passez ensuite à la version 8 de java à l'aide de la commande ``alternatives --config java`` (**en root**). - -Installer Elasticsearch -+++++++++++++++++++++++ - -La procédure d'installation, si vous souhaitez utiliser Elasticsearch sans l'installer via le gestionnaire de paquets de votre distribution, est d'entrer les commandes suivantes dans votre *shell* préféré. Remplacez ``X.Y.Z`` par la version spécifiée dans ``requirements.txt`` ! - -.. sourcecode:: bash - - wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-X.Y.Z.zip - unzip elasticsearch-X.Y.Z.zip - cd elasticsearch-X.Y.Z/ - -Pour démarrer Elasticsearch, utilisez - -.. sourcecode:: bash - - ./bin/elasticsearch - -Vous pouvez arrêter Elasticsearch grâce à CTRL+C. - -.. note:: - - Vous pouvez également installer Elasticsearch comme *daemon* de votre système. - Rendez-vous `sur la page d'installation d'Elasticsearch `_ pour plus d'informations - -Sous macOS ----------- - -Utilisez les commandes suivantes pour installer Java 8 et Elasticsearch: - -.. sourcecode:: bash - - brew update - brew cask install java - brew install elasticsearch - - -Pour démarrer Elasticsearch, utilisez la commande suivante: - -.. sourcecode:: bash - - elasticsearch --config=/usr/local/opt/elasticsearch/config/elasticsearch.yml - -.. note:: - - Vous pouvez également le démarrer comme *daemon*, comme sous Linux. - Plus d'infos `ici `_. - -Sous Windows ------------- - -Elasticsearch requiert **la version 8** de Java, que vous pouvez trouver `sur la page officielle de java `_. Prenez la version correspondante à votre système d'exploitation. - -Pour télécharger Elasticsearch : - -* rendez-vous à l'adresse suivante : `https://www.elastic.co/fr/downloads/past-releases#elasticsearch `_ ; -* choisissez la version spécifiée dans le fichier `requirements.txt` (désignée ci-après par ``X.Y.Z``); -* téléchargez la version Windows ; -* extrayez le dossier ``elasticsearch-X.Y.Z`` du zip à l'aide de votre outil préféré. - -Pour démarer Elasticsearch, ouvrez un *shell* (ou un *powershell*) et rendez-vous dans le dossier ``elasticsearch-X.Y.Z``. -Exécutez ensuite la commande suivante : - -.. sourcecode:: bash - - bin\elasticsearch - - -Vous pouvez arrêter Elasticsearch grâce à CTRL+C, puis en répondant "o" lorsqu'il vous est demandé ``Terminer le programme de commandes (O/N) ?``. - -.. note:: - - Vous pouvez également le démarrer comme *daemon*, comme sous Linux. - Plus d'informations `dans la documentation `_. - -Indexation et recherche -======================= - -Pour tester que tout fonctionne, quand Elasticsearch est démarré, rendez-vous sur la page `http://localhost:9200/ `_. -Vous devriez observer une réponse du même genre que celle-ci : - -.. sourcecode:: none - - { - "name" : "p0bcxqN", - "cluster_name" : "elasticsearch", - "cluster_uuid" : "649S5bMUQOyRzYmQFVPA1A", - "version" : { - "number" : "X.Y.Z", - "build_hash" : "19c13d0", - "build_date" : "2017-07-18T20:44:24.823Z", - "build_snapshot" : false, - "lucene_version" : "6.6.0" - }, - "tagline" : "You Know, for Search" - } - - -Si ce n'est pas le cas, vérifiez que vous avez démarré Elasticsearch. - -Si c'est le cas, vous pouvez indexer les données à l'aide de la commande ``es_manager``, comme suit : - -.. sourcecode:: bash - - python manage.py es_manager index_all - -Une fois que c'est fait, en vous rendant sur la page de recherche, `http://localhost:8000/rechercher/ `_, vous devriez être capable d'utiliser la recherche. -En particulier, vous ne devriez pas observer de message d'erreur : - -.. figure:: ../images/search/no-connection.png - :align: center - - Si Elasticsearch n'est pas démarré, le message suivant apparait. - -Pour réindexer les nouvelles données, utilisez la commande suivante : - -.. sourcecode:: bash - - python manage.py es_manager index_flagged - -Plus d'informations sur la commande ``es_manager`` sont disponibles sur la page `concernant la recherche sur ZdS <../back-end/searchv2.html#indexer-les-donnees-de-zds>`_. diff --git a/doc/source/install/extra-install-search-engine.rst b/doc/source/install/extra-install-search-engine.rst new file mode 100644 index 0000000000..1c5205b65c --- /dev/null +++ b/doc/source/install/extra-install-search-engine.rst @@ -0,0 +1,86 @@ +=================================== +Installation du moteur de recherche +=================================== + +Zeste de Savoir utilise **Typesense** comme moteur de recherche. L'installer +est nécessaire pour faire fonctionner la recherche. + + +Installation +============ + +La version de Typesense utilisée par ZdS est définie par la variable +``$ZDS_TYPESENSE_VERSION`` dans ``scripts/define_variable.sh``. + +Il est possible d'installer Typesense de plusieurs façons, comme indiqué dans +`la documentation officielle +`_ (*Option 2: Local +Machine / Self-Hosting*). + +Depuis le script d'installation de ZdS +-------------------------------------- + +Cette méthode fonctionne uniquement sur un système Linux utilisant un processeur avec une architecture amd64. + +Exécutez : + +.. sourcecode:: bash + + ./scripts/install_zds.sh +typesense-local + +Référez-vous à `la documentation correspondante +<./install-linux.html#composant-typesense-local>`_ pour savoir ce que fait +cette commande. + +Il faut ensuite lancer Typesense avec la commande suivante : + +.. sourcecode:: bash + + make run-search-engine + +Avec Docker +----------- + +Cette méthode a l'avantage de fonctionner sur n'importe quel système qui dispose de Docker : + +.. sourcecode:: bash + + docker run -p 8108:8108 typesense/typesense:$ZDS_TYPESENSE_VERSION --api-key=xyz --data-dir=/tmp + +Vérifier le bon lancement de Typesense +====================================== + +Pour tester que tout fonctionne, quand Typesense est démarré, rendez-vous sur +la page `http://localhost:8108/health `_. Vous +devriez observer une réponse du même genre que celle-ci : + +.. sourcecode:: + + {"ok":true} + +Si ce n'est pas le cas, vérifiez que Typesense est correctement démarré. + +Indexation et recherche +======================= + +Une fois que Typesense est installé et démarré, vous pouvez indexer les données +à l'aide de la commande ``search_engine_manager``, comme suit : + +.. sourcecode:: bash + + python manage.py search_engine_manager index_all + +Une fois que c'est fait, en vous rendant sur la page de recherche de Zeste de +Savoir, `http://localhost:8000/rechercher/ +`_, vous devriez pouvoir d'utiliser la +recherche. + +Pour indexer uniquement les nouvelles données, utilisez la commande suivante : + +.. sourcecode:: bash + + python manage.py search_engine_manager index_flagged + +Plus d'informations sur la commande ``search_engine_manager`` sont disponibles +sur la page `concernant la recherche sur Zeste de Savoir +<../back-end/search.html>`_. diff --git a/doc/source/install/install-linux.rst b/doc/source/install/install-linux.rst index b6642ad0be..729228638e 100644 --- a/doc/source/install/install-linux.rst +++ b/doc/source/install/install-linux.rst @@ -24,7 +24,7 @@ Après avoir cloné le dépôt du code source, installer ZdS sous Linux est rela make install-linux -Notez que si vous voulez installer une version complète (avec une version locale `de LaTeX <#composant-tex-local-et-latex-template>`_ et `de Elasticsearch <#composant-elastic-local>`_, plus d'infos ci-dessous), utilisez plutôt +Notez que si vous voulez installer une version complète (avec une version locale `de LaTeX <#composant-tex-local-et-latex-template>`_ et `de Typesense <#composant-typesense-local>`_, plus d'infos ci-dessous), utilisez plutôt .. sourcecode:: bash @@ -72,7 +72,7 @@ Notez que si vous ne souhaitez pas un de ces compsants, vous pouvez utiliser la Composants ``full`` =================== -Équivalent à ``+base +elastic-local +tex-local +latex-template`` (plus de détails ci-dessous). +Équivalent à ``+base +typesense-local +tex-local +latex-template`` (plus de détails ci-dessous). De même que pour `base <#composants-base>`_, vous pouvez agrémenter de ``-composant`` pour ne pas installer un composant donné. @@ -164,14 +164,13 @@ Strictement équivalent à la commande suivantes: Plus d'info sur cette fonctionalité `sur la page dédiée <../utils/fixture_loaders.html>`_. -Composant ``elastic-local`` -=========================== +Composant ``typesense-local`` +============================= -Installe une version **locale** d'Elasticsearch dans un dossier ``.local`` situé dans le dossier de ZdS. -La commande ``elasticsearch`` est ensuite ajoutée dans le *virtualenv*, de telle sorte à ce que ce soit cette version locale qui soit utilisée. -La version d'Elasticsearch installée est controlée par la variable d'environnement ``ZDS_ELASTIC_VERSION`` (voir ``scripts/define_variable.sh`` pour la valeur par défaut). +Installe une version **locale** de Typesense dans un dossier ``.local`` situé dans le dossier de ZdS. +La version de Typesense installée est controlée par la variable d'environnement ``ZDS_TYPESENSE_VERSION`` (voir ``scripts/define_variable.sh`` pour la valeur par défaut). -Notez que vous pouvez choisir d'installer Elasticsearch manuellement, `comme décrit ici <./extra-install-es.html#sous-linux>`_. +Notez que vous pouvez choisir d'installer Typesense manuellement, `comme décrit ici <./extra-install-search-engine.html#sous-linux>`_. Composant ``tex-local`` et ``latex-template`` ============================================= diff --git a/doc/source/install/install-macos.rst b/doc/source/install/install-macos.rst index 07bbaf5bcd..93c762727d 100644 --- a/doc/source/install/install-macos.rst +++ b/doc/source/install/install-macos.rst @@ -205,10 +205,8 @@ lequel vous travaillerez avant d’envoyer des *Pull-Requests*. .. Attention:: La version complète **ne peut être automatiquement installée pour le moment** - car l’installeur télécharge une version de Java (pour ElasticSearch) spécifique - à Linux. Cependant, vous pouvez remplacer la version de Java installée dans - ``zds-site/zdsenv/lib/jdk`` par une version fonctionnant sous macOS et ElasticSearch - fonctionnera. + car l’installeur télécharge une version de Typesense (le moteur de recherche) spécifique + à Linux. Le système de génération et d’export des PDF devrait fonctionner normalement. @@ -216,7 +214,7 @@ lequel vous travaillerez avant d’envoyer des *Pull-Requests*. .. seealso:: - - `Installation d’Elasticsearch `_ ; + - `Installation de Typesense `_ ; - `installation de LaTeX `_. Pour installer la version minimale, exécutez depuis la racine du dépôt que vous @@ -261,9 +259,7 @@ Si vous voulez la version complète : Si vous installez la version complète, le script va, en plus : - - installer une version de Java pour Linux dans le dossier ``zds-site/zdsenv/lib/jdk`` - et modifier l’environnement virtuel pour que cette version de Java soit utilisée ; - - installer ElasticSearch dans le dossier ``zds-site/.local/elasticsearch`` ; + - installer Typesense dans le dossier ``zds-site/.local/typesense`` ; - installer TeXLive (permettant de compiler du LaTeX en PDF, utilisé pour les exports PDF) dans le dossier ``zds-site/.local/texlive`` ; - cloner le dépôt contenant le modèle LaTeX utilisé par l’export PDF dans le diff --git a/doc/source/install/install-windows.rst b/doc/source/install/install-windows.rst index b957d11bd2..8a47312189 100644 --- a/doc/source/install/install-windows.rst +++ b/doc/source/install/install-windows.rst @@ -140,9 +140,9 @@ On peut finalement lancer ZdS: Aller plus loin =============== -Pour faire fonctionner ZdS dans son ensemble vous devez installer les outils LateX et Elasticsearch: +Pour faire fonctionner ZdS dans son ensemble vous devez installer les outils LateX et Typesense : -- `Installez Elasticsearch `_ ; +- `Installez Typesense `_ ; - `Installez LaTeX `_. Vous pouvez également `indiquer à Git de ne pas effectuer de commit s'il y a des erreurs de formatage dans le code <../utils/git-pre-hook.html>`__. diff --git a/requirements.txt b/requirements.txt index 2c28ad1de3..12e87843ca 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,5 @@ # Implicit dependencies (optional dependencies of dependencies) crispy-forms-bootstrap2==2024.1 -elasticsearch-dsl==5.4.0 -elasticsearch==5.5.3 social-auth-app-django==5.4.0 # Explicit dependencies (references in code) @@ -19,6 +17,7 @@ lxml==5.1.0 Pillow==10.2.0 pymemcache==4.0.0 requests==2.31.0 +typesense==0.19.0 ua-parser==0.18.0 # Api dependencies diff --git a/scripts/define_variable.sh b/scripts/define_variable.sh old mode 100755 new mode 100644 index 152f813a1b..6286b37c35 --- a/scripts/define_variable.sh +++ b/scripts/define_variable.sh @@ -14,8 +14,12 @@ if [[ $ZDS_NVM_VERSION == "" ]]; then ZDS_NVM_VERSION="0.39.5" fi -if [[ $ZDS_ELASTIC_VERSION == "" ]]; then - ZDS_ELASTIC_VERSION="5.5.2" +if [[ $ZDS_TYPESENSE_VERSION == "" ]]; then + ZDS_TYPESENSE_VERSION="27.0" # needs to be also updated in .github/workflows/ci.yml +fi + +if [[ $ZDS_TYPESENSE_API_KEY == "" ]]; then + ZDS_TYPESENSE_API_KEY="xyz" fi if [[ $ZDS_LATEX_REPO == "" ]]; then diff --git a/scripts/install_zds.sh b/scripts/install_zds.sh index a9e45065fc..a778ff8e18 100755 --- a/scripts/install_zds.sh +++ b/scripts/install_zds.sh @@ -242,86 +242,36 @@ fi export ZDS_ENV=$(realpath $ZDS_VENV) -# local jdk -if ! $(_in "-jdk-local" $@) && ( $(_in "+jdk-local" $@) || $(_in "+full" $@) ); then - print_info "* [+jdk-local] installing a local version of JDK (v$ZDS_JDK_VERSION)" --bold - - mkdir -p $ZDS_VENV/lib/ - cd $ZDS_VENV/lib/ - - jdk_path=$(realpath jdk) - - if [ -d "$jdk_path" ]; then # remove previous install - rm -rf "$jdk_path" - fi - - baseURL="https://github.com/adoptium/temurin11-binaries/releases/download/" - foldername="jdk-${ZDS_JDK_VERSION}+${ZDS_JDK_REV}" - folderPATH="${foldername}/OpenJDK11U-jdk_x64_linux_hotspot_${ZDS_JDK_VERSION}_${ZDS_JDK_REV}.tar.gz" - - echo "GET ${baseURL}${folderPATH}" - wget -O ${foldername}.tar.gz ${baseURL}${folderPATH} -q --show-progress - tar xf ${foldername}.tar.gz - - if [[ $? == 0 ]]; then - rm ${foldername}.tar.gz - mv ${foldername} "$jdk_path" - - echo $($jdk_path/bin/java -version) - - export PATH="$PATH:$jdk_path/bin" - export JAVA_HOME="$jdk_path" - export ES_JAVA_OPTS="-Xms512m -Xmx512m" - - if [[ $(grep -c -i "export JAVA_HOME" $ZDS_ENV/bin/activate) == "0" ]]; then # add java to venv activate's - ACTIVATE_JAVA="export PATH=\"\$PATH:$jdk_path/bin\"\nexport JAVA_HOME=\"$jdk_path\"\nexport ES_JAVA_OPTS=\"-Xms512m -Xmx512m\"" - - echo -e $ACTIVATE_JAVA >> $ZDS_ENV/bin/activate - echo -e $ACTIVATE_JAVA >> $ZDS_ENV/bin/activate.csh - echo -e $ACTIVATE_JAVA >> $ZDS_ENV/bin/activate.fish - fi - else - print_error "!! Cannot get or extract jdk ${ZDS_JDK_VERSION}" - exit 1 - fi - cd $ZDSSITE_DIR -fi - - -# local elasticsearch -if ! $(_in "-elastic-local" $@) && ( $(_in "+elastic-local" $@) || $(_in "+full" $@) ); then - print_info "* [+elastic-local] installing a local version of elasticsearch (v$ZDS_ELASTIC_VERSION)" --bold +# local Typesense +if ! $(_in "-typesense-local" $@) && ( $(_in "+typesense-local" $@) || $(_in "+full" $@) ); then + print_info "* [+typesense-local] installing a local version of typesense (v$ZDS_TYPESENSE_VERSION)" --bold mkdir -p .local cd .local - es_path=$(realpath elasticsearch) + readonly typesense_path=$(realpath typesense) - if [ -d "$es_path" ]; then # remove previous install - rm -r "$es_path" + if [ -d "$typesense_path" ]; then # remove previous install + rm -r "$typesense_path" fi - wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-${ZDS_ELASTIC_VERSION}.zip -q --show-progress - if [[ $? == 0 ]]; then - unzip -q elasticsearch-${ZDS_ELASTIC_VERSION}.zip - rm elasticsearch-${ZDS_ELASTIC_VERSION}.zip - mv elasticsearch-${ZDS_ELASTIC_VERSION} elasticsearch + mkdir $typesense_path + cd $typesense_path - # add options to reduce memory consumption - print_info "#Options added by install_zds.sh" >> "$es_path/config/jvm.options" - print_info "-Xms512m" >> "$es_path/config/jvm.options" - print_info "-Xmx512m" >> "$es_path/config/jvm.options" + readonly archive_name=typesense-server-$ZDS_TYPESENSE_VERSION-linux-amd64.tar.gz - # symbolic link to elastic start script - ln -s "$es_path/bin/elasticsearch" $ZDS_ENV/bin/ + wget -q https://dl.typesense.org/releases/$ZDS_TYPESENSE_VERSION/$archive_name --show-progress + if [[ $? == 0 ]]; then + tar -xzf $archive_name + rm $archive_name + mkdir typesense-data else - print_error "!! Cannot get elasticsearch ${ZDS_ELASTIC_VERSION}" + print_error "!! Cannot get typesense ${ZDS_TYPESENSE_VERSION}" exit 1 fi cd $ZDSSITE_DIR fi - # local texlive if ! $(_in "-tex-local" $@) && ( $(_in "+tex-local" $@) || $(_in "+full" $@) ); then print_info "* [+tex-local] install texlive" --bold diff --git a/templates/searchv2/includes/chapter.part.html b/templates/search/includes/chapter.part.html similarity index 66% rename from templates/searchv2/includes/chapter.part.html rename to templates/search/includes/chapter.part.html index 6c6647df54..e25d6d6f56 100644 --- a/templates/searchv2/includes/chapter.part.html +++ b/templates/search/includes/chapter.part.html @@ -1,9 +1,7 @@ -{% load emarkdown %} {% load i18n %} {% load date %} -{% load elasticsearch %} - + {% if search_result.thumbnail %} {{ search_result.title }} {% endif %} @@ -17,9 +15,13 @@

-

+

- {% highlight search_result "text" %} + {% if search_result.highlights.snippet %} + {{ search_result.highlights.snippet|safe }} + {% else %} + {{ search_result.description }} + {% endif %}

@@ -30,9 +32,9 @@

{% trans "Chapitre du tutoriel" %} {{ search_result.parent_title }} • -