DEV Community

Cover image for Algolia For Django
Lyamaa
Lyamaa

Posted on • Edited on

Algolia For Django

Algolia is a hosted search engine, offering full-text, numerical, and faceted search, capable of delivering real-time results from the first keystroke. Algolia's powerful API lets you quickly and seamlessly implement search within your websites and mobile applications. Our search API powers billions of queries for thousands of companies every month, delivering relevant results in under 100ms anywhere in the world.

Resources

Well before continuing, i believe most of you are fimailiar with django, and won't be explaining much of it.

Demo:

Setup

  • create a project or clone from github
  • create Virtualenv.
virtualenv venv
Enter fullscreen mode Exit fullscreen mode
  • create requirements.txt
Django==4.0.5
django-taggit==3.0.0
django-treebeard==4.5.1
djangorestframework==3.13.1
algoliasearch-django==2.0.0
django-environ==0.8.1
Enter fullscreen mode Exit fullscreen mode
  • create a new app algolia_search in the project
python manage.py startapp algolia_search
Enter fullscreen mode Exit fullscreen mode
  • create .env file
DJANGO_SUPERUSER_PASSWORD=root
DJANGO_SUPERUSER_USERNAME=root
DJANGO_SUPERUSER_EMAIL=root@root.com

APPLICATION_ID=
ADMIN_API_KEY=
SEARCH_ONLY_API_KEY=
Enter fullscreen mode Exit fullscreen mode

Config Settings

configure the settings.py file

import os
import environ
from pathlib import Path

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent

env = environ.Env()
environ.Env.read_env(os.path.join(BASE_DIR, ".env"))

INSTALLED_APPS = [

    # *** 3rd party apps ***
    "rest_framework",
    "rest_framework.authtoken",
    "treebeard",
    "taggit",
    "algoliasearch_django",
    # ** apps **
    "algolia_search
]

ALGOLIA = {
    "APPLICATION_ID": os.environ.get("APPLICATION_ID"),
    "API_KEY": os.environ.get("ADMIN_API_KEY"),
    "SEARCH_API_KEY": os.environ.get("SEARCH_ONLY_API_KEY"),
}

Enter fullscreen mode Exit fullscreen mode

Creating Models

# algolia_search/models.py

from this import s
from unicodedata import category
from django.db import models
from django.db.models import Manager, QuerySet
from taggit.managers import TaggableManager
from treebeard.mp_tree import MP_Node

from django.contrib.auth import get_user_model

User = get_user_model()


class TimeStamp(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)
    update_at = models.DateTimeField(auto_now=True)

    class Meta:
        abstract = True


class Category(MP_Node):
    class Meta:
        db_table = "categories"
        verbose_name_plural = "Categories"

    name = models.CharField(max_length=30)

    node_order_by = ["name"]

    def __str__(self):
        return f"Category: {self.name}"


class ArticleQuerySet(QuerySet):
    def update(self, **kwargs):
        super(ArticleQuerySet, self).update(**kwargs)


class CustomManager(Manager):
    def get_queryset(self):
        return ArticleQuerySet(self.model, using=self._db)


class ArticleLike(TimeStamp):
    class Meta:
        db_table = "article_like"
        unique_together = ("user", "article")

    user = models.ForeignKey(User, on_delete=models.CASCADE)
    article = models.ForeignKey(
        "Article", on_delete=models.CASCADE, related_name="likes_article"
    )


class Article(TimeStamp):
    class Meta:
        db_table = "articles"

    category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True)
    title = models.CharField(max_length=255)
    description = models.TextField(null=True, blank=True)
    is_published = models.BooleanField(default=False)
    tags = TaggableManager()
    likes = models.ManyToManyField(
        User, related_name="user_likes_article", through=ArticleLike, blank=True
    )

    objects = CustomManager()

    def __str__(self):
        return self.title

    def is_published_indexing(self):
        return self.is_published == True

    @property
    def likes_count(self):
        return int(self.likes.count())

    @property
    def tags_indexing(self):
        return [tag.name for tag in self.tags.all()]

    @property
    def category_indexing(self):
        return list(self.category.get_descendants())
Enter fullscreen mode Exit fullscreen mode

Modify admin.py

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User
from django.db import models
from django.utils.safestring import mark_safe
from treebeard.admin import TreeAdmin
from treebeard.forms import movenodeform_factory

from algolia_search.models import Article, Category, ArticleLike


class UserAdmin(UserAdmin):
    model = User
    list_display = (
        "username",
        "email",
        "is_staff",
        "is_active",
    )
    list_filters = (
        "username",
        "email",
        "is_staff",
        "is_active",
    )
    fieldsets = (
        (None, {"fields": ("username", "email", "password")}),
        (
            "Permissions",
            {
                "fields": (
                    "is_staff",
                    (
                        "is_active",
                        "is_superuser",
                    ),
                )
            },
        ),
        ("Important dates", {"fields": ("last_login", "date_joined")}),
        ("Advanced options", {"classes": ("collapse",), "fields": ("groups",)}),
    )
    add_fieldsets = (
        (
            None,
            {
                "classes": ("wide",),
                "fields": (
                    "username",
                    "email",
                    "password1",
                    "password2",
                    "is_staff",
                    "is_active",
                    "is_superuser",
                    "groups",
                ),
            },
        ),
    )


admin.site.unregister(User)
admin.site.register(User, UserAdmin)


class CategoryAdmin(TreeAdmin):
    form = movenodeform_factory(Category)


admin.site.register(Category, CategoryAdmin)


class ArticleLikeInline(admin.TabularInline):
    model = ArticleLike
    extra = 0


class ArticleAdmin(admin.ModelAdmin):
    list_display = [
        "id",
        "title",
        "get_tags",
        "likes_count",
        "is_published",
    ]
    search_fields = ["id", "title"]

    def get_tags(self, article):
        tags = [str(tag) for tag in article.tags.all()]
        return ", ".join(tags)

    inlines = [
        ArticleLikeInline,
    ]


admin.site.register(Article, ArticleAdmin)

Enter fullscreen mode Exit fullscreen mode

Create index.py file.

# algolia_search/index.py

import algoliasearch_django as algoliasearch
from algoliasearch_django import AlgoliaIndex

from .models import Article


class ArticleIndex(AlgoliaIndex):
    """Index for Article model"""

    # ** fields to index if is_published is True
    name = "article"
    should_index = "is_published_indexing"
    fields = (
        "title",
        "description",
        "is_published",
        "tags_indexing",
        "category_indexing",
        "likes_count",
    )
    settings = {
        "searchableAttributes": [
            "title",
            "description",
            "tags_indexing",
            "category_indexing",
        ],
        "attributesForFaceting": [
            "title",
            "tags_indexing",
            "description",
            "category_indexing",
        ],
        "queryType": "prefixAll",
        # ** custom ranking rules with like_count
        "customRanking": [
            "desc(likes_count)",
        ],
        "advancedSyntax": True,
        "highlightPreTag": "<mark>",
        "highlightPostTag": "</mark>",
        "hitsPerPage": 15,
    }

    index_name = "articles"

algoliasearch.register(Article, ArticleIndex)

Enter fullscreen mode Exit fullscreen mode
  • Above we created ArticleIndex class for indexing.
 should_index = "is_published_indexing"
Enter fullscreen mode Exit fullscreen mode
  • here should_index attribute helps to index Article object which is_published is true. So, if it is false no objects are indexed.
fields = (
        "title",
        "description",
        "is_published",
        "category_indexing",
        "tags_indexing",
        "likes_count",
    )
Enter fullscreen mode Exit fullscreen mode
  • Simply, it is just defining out fields from article models.
settings = {
        "searchableAttributes": [
            "title",
            "description",
            "tags_indexing",
            "category_indexing",
        ],
        "attributesForFaceting": [
            "title",
            "tags_indexing",
            "description",
            "category_indexing",
        ],
        "queryType": "prefixAll",
        # ** custom ranking rules with like_count
        "customRanking": [
            "desc(likes_count)",
        ],
        "advancedSyntax": True,
        "highlightPreTag": "<mark>",
        "highlightPostTag": "</mark>",
        "hitsPerPage": 15,
    }
Enter fullscreen mode Exit fullscreen mode

in setting options:

  • searchableAttributes: The complete list of attributes used for searching.
  • attributesForFaceting: The complete list of attributes that will be used for faceting.
    • to turn an attribute into a facet
    • to make any string attribute filterable.
  • queryType: Controls if and how query words are interpreted as prefixes.

    • prefixAll: All query words are interpreted as prefixes. This option is not recommended, as it tends to yield counterintuitive results and has a negative impact on performance.
    • prefixLast: Only the last word is interpreted as a prefix (default behavior).
    • prefixNone: No query word is interpreted as a prefix. This option is not recommended, especially in an instant search setup, as the user will have to type the entire word(s) before getting any relevant results.
  • customRanking: Specifies the custom ranking criterion.

    • Modifiers:#
    • asc: Sort by increasing value of the attribute.
    • desc: Sort by decreasing value of the attribute.

Note: we have our custom rank according to like count of articles

  • advancedSyntax: Enables the advanced query syntax.

    • This advanced syntax brings two additional features:
    • Phrase query: a specific sequence of terms that must be matched next to one another. A phrase query needs to be surrounded by double quotes ("). For example, the query "search engine" only returns a record if it contains “search engine” exactly in at least one attribute.

Note: Typo tolerance is disabled inside the phrase (i.e. within the quotes).

  • Prohibit operator: excludes records that contain a specific term. To exclude a term, you need to prefix it with a minus (-). The engine only interprets the minus (-) as a prohibit operator when you place it at the start of a word. A minus (-) within double quotes (") is not treated as a prohibit operator.

    • highlightPreTag: The HTML string to insert before the highlighted parts in all highlight and snippet results.
    • highlightPostTag: The HTML string to insert after the highlighted parts in all highlight and snippet results.
    • hitsPerPage: is a widget that displays a menu of options to change the number of results per page.

Finally, register Article, ArticleIndex.

Creating Custom Commands.

  • create dir algolia_search/management/commands & copy from following links.
  • After creating then run the following commands
$ python manage.py migrate
$ python manage.py makemigrations
$ python manage.py seed_superuser
$ python manage.py seed_users
$ python manage.py seed_articles
$ python manage.py seed_likes
Enter fullscreen mode Exit fullscreen mode

ALgolia Dashboard:

so we are going to create index and query suggestion:

  • index: indexing a list of article objects.
  • query suggestion: retrieves articles matching the query and displays a preview

    • Login to Algolia with your desired accounts.
    • create new application

algolia-11

  • Select Algolia Package

algolia-10

-select a region, suitable for you

algolia-9

  • click on Review Application Details
  • click on check boxesand create application
  • create index articles

algolia-8

  • Return to Dashboard and click on API keys

algolia-7

  • Copy keys from Application ID, Search-only API Key, and Admin API Key and paste it to.env` file

algolia-6

  • Run commands python to index your data from backends: $ python manage.py algolia_reindex
  • Go to home Dashboard and click on articles.

algolia-5

  • List of datas are displayed as shown below algolia-11

Create query suggestions

  • click on query suggestions tab.

algolia-4

  • click button to create query suggestions

algolia-3

algolia-2

  • Accept and continue, wait for a while until it creates your suggestion index

  • click to Edit Categories button and add the following categories attributes as shown in image.

algolia-one

  • click to save button and wait for a while until it re-index.

Frontend Integration


GitHub logo lyamaa / algolia-search-frontend

ALgolia search in React

Final Words

Well, i found algolia to be simple and easy to configure than elastic search. ES is much more of writing code than algolia and bit more of complex.

Read comparision of ES and algolia : Algolia VS Elasticsearch

till then Bye bye 👋👋👋...


Top comments (0)