Hoang Tran

Hoang Tran

1660692840

Cách Tích Hợp Django REST Framework Với Elasticsearch

Trong hướng dẫn này, chúng ta sẽ xem xét cách tích hợp Django REST Framework (DRF) với Elasticsearch. Chúng tôi sẽ sử dụng Django để lập mô hình dữ liệu của chúng tôi và DRF để tuần tự hóa và cung cấp nó. Cuối cùng, chúng tôi sẽ lập chỉ mục dữ liệu với Elasticsearch và làm cho nó có thể tìm kiếm được.

Elasticsearch là gì?

Elasticsearch là công cụ phân tích và tìm kiếm phân tán, miễn phí và mở cho tất cả các loại dữ liệu, bao gồm văn bản, số, không gian địa lý, có cấu trúc và phi cấu trúc. Nó được biết đến với các API RESTful đơn giản, tính chất phân tán, tốc độ và khả năng mở rộng. Elasticsearch là thành phần trung tâm của Elastic Stack (còn được gọi là ELK Stack), một bộ công cụ miễn phí và mở để nhập, làm giàu, lưu trữ, phân tích và trực quan hóa dữ liệu.

Các trường hợp sử dụng của nó bao gồm:

  1. Tìm kiếm trang web và tìm kiếm ứng dụng
  2. Giám sát và hình dung các chỉ số hệ thống của bạn
  3. Phân tích bảo mật và kinh doanh
  4. Ghi nhật ký và phân tích nhật ký

Cấu trúc và khái niệm Elasticsearch

Trước khi làm việc với Elasticsearch, chúng ta nên làm quen với các khái niệm Elasticsearch cơ bản. Chúng được liệt kê từ lớn nhất đến nhỏ nhất:

  1. Cluster là một tập hợp của một hoặc nhiều nút.
  2. Node là một phiên bản máy chủ duy nhất chạy Elasticsearch. Trong khi giao tiếp với cụm, nó:
    1. Lưu trữ và lập chỉ mục dữ liệu của bạn
    2. Cung cấp tìm kiếm
  3. Chỉ mục được sử dụng để lưu trữ các tài liệu trong cấu trúc dữ liệu chuyên dụng tương ứng với kiểu dữ liệu của các trường (tương tự như cơ sở dữ liệu SQL). Mỗi chỉ mục có một hoặc nhiều phân đoạn và bản sao.
  4. Loại là một tập hợp các tài liệu có điểm chung (giống với một bảng SQL).
  5. Shard là một chỉ số Apache Lucene . Nó được sử dụng để phân chia các chỉ số và giữ cho lượng lớn dữ liệu có thể quản lý được.
  6. Bản sao là một cơ chế không an toàn và về cơ bản là một bản sao của phân đoạn chỉ mục của bạn.
  7. Tài liệu là một đơn vị thông tin cơ bản có thể được lập chỉ mục (tương tự như một hàng SQL). Nó được thể hiện bằng JSON , là một định dạng trao đổi dữ liệu internet phổ biến.
  8. Trường là đơn vị dữ liệu riêng lẻ nhỏ nhất trong Elasticsearch (tương tự như một cột SQL).

Cụm Elasticsearch có cấu trúc sau:

Cấu trúc cụm Elasticsearch

Bạn muốn biết các khái niệm cơ sở dữ liệu quan hệ liên quan như thế nào với các khái niệm Elasticsearch?

Cơ sở dữ liệu quan hệElasticsearch
ClusterCluster
Phiên bản RDBMSNút
BànMục lục
Hàng ngangTài liệu
CộtĐồng ruộng

Xem lại các khái niệm Ánh xạ trên SQL và Elasticsearch để biết thêm về cách các khái niệm trong SQL và Elasticsearch liên quan với nhau.

Tìm kiếm toàn văn bản Elasticsearch so với PostgreSQL

Liên quan đến tìm kiếm toàn văn bản, Elasticsearch và PostgreSQL đều có những ưu điểm và nhược điểm riêng. Khi lựa chọn giữa chúng, bạn nên xem xét tốc độ, độ phức tạp của truy vấn và ngân sách.

Ưu điểm của PostgreSQL:

  1. Hỗ trợ Django
  2. Cài đặt nhanh hơn và dễ dàng hơn
  3. Không yêu cầu bảo trì

Ưu điểm của Elasticsearch:

  1. Được tối ưu hóa chỉ để tìm kiếm
  2. Elasicsearch nhanh hơn (đặc biệt khi số lượng bản ghi tăng lên)
  3. Hỗ trợ các loại truy vấn khác nhau (Leaf, Compound, Fuzzy, Regexp, đến một số loại)

Nếu bạn đang làm việc trên một dự án đơn giản mà tốc độ không quan trọng, bạn nên chọn PostgreSQL. Nếu hiệu suất là quan trọng và bạn muốn viết các bản tra cứu phức tạp, hãy chọn Elasticsearch.

Để biết thêm về tìm kiếm toàn văn với Django và Postgres, hãy xem bài viết Tìm kiếm toàn văn và cơ bản với Django và Postgres .

Thiết lập dự án

Chúng tôi sẽ xây dựng một ứng dụng blog đơn giản. Dự án của chúng tôi sẽ bao gồm nhiều mô hình, sẽ được tuần tự hóa và phục vụ thông qua Django REST Framework . Sau khi tích hợp Elasticsearch, chúng tôi sẽ tạo một điểm cuối cho phép chúng tôi tra cứu các tác giả, danh mục và bài báo khác nhau.

Để giữ cho mã của chúng tôi sạch sẽ và có tính mô-đun, chúng tôi sẽ chia dự án của mình thành hai ứng dụng sau:

  1. blog- cho các mô hình Django, bộ tuần tự và ViewSets của chúng tôi
  2. search- cho các tài liệu, chỉ mục và truy vấn Elasticsearch

Bắt đầu bằng cách tạo một thư mục mới và thiết lập một dự án Django mới:

$ mkdir django-drf-elasticsearch && cd django-drf-elasticsearch
$ python3.9 -m venv env
$ source env/bin/activate

(env)$ pip install django==3.2.6
(env)$ django-admin.py startproject core .

Sau đó, tạo một ứng dụng mới có tên blog:

(env)$ python manage.py startapp blog

Đăng ký ứng dụng trong core / settings.py theo INSTALLED_APPS:

# core/settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'blog.apps.BlogConfig', # new
]

Mô hình cơ sở dữ liệu

Tiếp theo, tạo Categoryvà lập Articlemô hình trong blog / models.py :

# blog/models.py

from django.contrib.auth.models import User
from django.db import models


class Category(models.Model):
    name = models.CharField(max_length=32)
    description = models.TextField(null=True, blank=True)

    class Meta:
        verbose_name_plural = 'categories'

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


ARTICLE_TYPES = [
    ('UN', 'Unspecified'),
    ('TU', 'Tutorial'),
    ('RS', 'Research'),
    ('RW', 'Review'),
]


class Article(models.Model):
    title = models.CharField(max_length=256)
    author = models.ForeignKey(to=User, on_delete=models.CASCADE)
    type = models.CharField(max_length=2, choices=ARTICLE_TYPES, default='UN')
    categories = models.ManyToManyField(to=Category, blank=True, related_name='categories')
    content = models.TextField()
    created_datetime = models.DateTimeField(auto_now_add=True)
    updated_datetime = models.DateTimeField(auto_now=True)

    def __str__(self):
        return f'{self.author}: {self.title} ({self.created_datetime.date()})'

Ghi chú:

  1. Categoryđại diện cho một danh mục bài viết - tức là lập trình, Linux, thử nghiệm.
  2. Articleđại diện cho một bài báo cá nhân. Mỗi bài báo có thể có nhiều chuyên mục. Các bài báo có một loại cụ thể - Tutorial,, hoặc .ResearchReviewUnspecified
  3. Các tác giả được đại diện bởi mô hình người dùng Django mặc định.

Chạy Migrations

Thực hiện di chuyển và sau đó áp dụng chúng:

(env)$ python manage.py makemigrations
(env)$ python manage.py migrate

Đăng ký các mô hình trong blog / admin.py :

# blog/admin.py

from django.contrib import admin

from blog.models import Category, Article


admin.site.register(Category)
admin.site.register(Article)

Điền vào cơ sở dữ liệu

Trước khi chuyển sang bước tiếp theo, chúng ta cần một số dữ liệu để làm việc. Tôi đã tạo một lệnh đơn giản mà chúng ta có thể sử dụng để điền vào cơ sở dữ liệu.

Tạo một thư mục mới trong "blog" được gọi là "quản lý", sau đó bên trong thư mục đó tạo một thư mục khác được gọi là "lệnh". Bên trong thư mục "lệnh", hãy tạo một tệp mới có tên populate_db.py .

management
└── commands
    └── populate_db.py

Sao chép nội dung tệp từ populate_db.py và dán nó vào bên trong populate_db.py của bạn .

Chạy lệnh sau để điền DB:

(env)$ python manage.py populate_db

Nếu mọi thứ suôn sẻ, bạn sẽ thấy một Successfully populated the database.thông báo trong bảng điều khiển và sẽ có một vài bài báo trong cơ sở dữ liệu của bạn.

Khung Django REST

Bây giờ hãy cài đặt djangorestframeworkbằng cách sử dụng pip:

(env)$ pip install djangorestframework==3.12.4

Đăng ký nó trong settings.py của chúng tôi như vậy:

# core/settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'blog.apps.BlogConfig',
    'rest_framework', # new
]

Thêm các cài đặt sau:

# core/settings.py

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
    'PAGE_SIZE': 25
}

Chúng tôi sẽ cần những cài đặt này để triển khai phân trang.

Tạo Serializers

Để tuần tự hóa các mô hình Django của chúng tôi, chúng tôi cần tạo một bộ tuần tự cho từng mô hình trong số chúng. Cách dễ nhất để tạo bộ tuần tự phụ thuộc vào các mô hình Django là sử dụng ModelSerializerlớp.

blog / serializers.py :

# blog/serializers.py

from django.contrib.auth.models import User
from rest_framework import serializers

from blog.models import Article, Category


class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ('id', 'username', 'first_name', 'last_name')


class CategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = Category
        fields = '__all__'


class ArticleSerializer(serializers.ModelSerializer):
    author = UserSerializer()
    categories = CategorySerializer(many=True)

    class Meta:
        model = Article
        fields = '__all__'

Ghi chú:

  1. UserSerializerCategorySerializerkhá đơn giản: Chúng tôi chỉ cung cấp các trường mà chúng tôi muốn tuần tự hóa.
  2. Trong đó ArticleSerializer, chúng tôi cần quan tâm đến các mối quan hệ để đảm bảo rằng chúng cũng được nối tiếp nhau. Đây là lý do tại sao chúng tôi cung cấp UserSerializerCategorySerializer.

Bạn muốn tìm hiểu thêm về bộ tuần tự DRF? Kiểm tra hiệu quả bằng cách sử dụng Django REST Framework Serializers .

Tạo ViewSets

Hãy tạo một ViewSet cho từng mô hình của chúng tôi trong blog / views.py :

# blog/views.py

from django.contrib.auth.models import User
from rest_framework import viewsets

from blog.models import Category, Article
from blog.serializers import CategorySerializer, ArticleSerializer, UserSerializer


class UserViewSet(viewsets.ModelViewSet):
    serializer_class = UserSerializer
    queryset = User.objects.all()


class CategoryViewSet(viewsets.ModelViewSet):
    serializer_class = CategorySerializer
    queryset = Category.objects.all()


class ArticleViewSet(viewsets.ModelViewSet):
    serializer_class = ArticleSerializer
    queryset = Article.objects.all()

Trong khối mã này, chúng tôi đã tạo các ViewSets bằng cách cung cấp serializer_classquerysetcho mỗi ViewSet.

Xác định URL

Tạo URL cấp ứng dụng cho ViewSets:

# blog/urls.py

from django.urls import path, include
from rest_framework import routers

from blog.views import UserViewSet, CategoryViewSet, ArticleViewSet

router = routers.DefaultRouter()
router.register(r'user', UserViewSet)
router.register(r'category', CategoryViewSet)
router.register(r'article', ArticleViewSet)

urlpatterns = [
    path('', include(router.urls)),
]

Sau đó, kết nối các URL của ứng dụng với các URL của dự án:

# core/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('blog/', include('blog.urls')),
    path('admin/', admin.site.urls),
]

Ứng dụng của chúng tôi hiện có các URL sau:

  1. /blog/user/liệt kê tất cả người dùng
  2. /blog/user/<USER_ID>/tìm nạp một người dùng cụ thể
  3. /blog/category/liệt kê tất cả các danh mục
  4. /blog/category/<CATEGORY_ID>/tìm nạp một danh mục cụ thể
  5. /blog/article/liệt kê tất cả các bài báo
  6. /blog/article/<ARTICLE_ID>/tìm nạp một bài báo cụ thể

Thử nghiệm

Bây giờ chúng tôi đã đăng ký các URL, chúng tôi có thể kiểm tra các điểm cuối để xem mọi thứ có hoạt động chính xác hay không.

Chạy máy chủ phát triển:

(env)$ python manage.py runserver

Sau đó, trong trình duyệt bạn chọn, điều hướng đến http://127.0.0.1:8000/blog/article/ . Câu trả lời sẽ trông giống như sau:

{
    "count": 4,
    "next": null,
    "previous": null,
    "results": [
        {
            "id": 1,
            "author": {
                "id": 3,
                "username": "jess_",
                "first_name": "Jess",
                "last_name": "Brown"
            },
            "categories": [
                {
                    "id": 2,
                    "name": "SEO optimization",
                    "description": null
                }
            ],
            "title": "How to improve your Google rating?",
            "type": "TU",
            "content": "Firstly, add the correct SEO tags...",
            "created_datetime": "2021-08-12T17:34:31.271610Z",
            "updated_datetime": "2021-08-12T17:34:31.322165Z"
        },
        {
            "id": 2,
            "author": {
                "id": 4,
                "username": "johnny",
                "first_name": "Johnny",
                "last_name": "Davis"
            },
            "categories": [
                {
                    "id": 4,
                    "name": "Programming",
                    "description": null
                }
            ],
            "title": "Installing latest version of Ubuntu",
            "type": "TU",
            "content": "In this tutorial, we'll take a look at how to setup the latest version of Ubuntu. Ubuntu (/ʊˈbʊntuː/ is a Linux distribution based on Debian and composed mostly of free and open-source software. Ubuntu is officially released in three editions: Desktop, Server, and Core for Internet of things devices and robots.",
            "created_datetime": "2021-08-12T17:34:31.540628Z",
            "updated_datetime": "2021-08-12T17:34:31.592555Z"
        },
        ...
    ]
}

Kiểm tra thủ công các điểm cuối khác.

Thiết lập Elasticsearch

Bắt đầu bằng cách cài đặt và chạy Elasticsearch trong nền.

Cần trợ giúp để thiết lập và chạy Elasticsearch? Xem hướng dẫn Cài đặt Elasticsearch . Nếu bạn đã quen thuộc với Docker, bạn có thể chỉ cần chạy lệnh sau để kéo hình ảnh chính thức và quay lên vùng chứa với Elasticsearch đang chạy:

$ docker run -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:7.14.0

Để tích hợp Elasticsearch với Django, chúng ta cần cài đặt các gói sau:

  1. asticsearch - ứng dụng Python cấp thấp chính thức cho Elasticsearch
  2. asticsearch-dsl-py - thư viện cấp cao để viết và chạy các truy vấn chống lại Elasticsearch
  3. django -asticsearch-dsl - wrapper xung quanhasticsearch-dsl-py cho phép lập chỉ mục các mô hình Django trong Elasticsearch

Cài đặt:

(env)$ pip install elasticsearch==7.14.0
(env)$ pip install elasticsearch-dsl==7.4.0
(env)$ pip install django-elasticsearch-dsl==7.2.0

Bắt đầu một ứng dụng mới có tên search, ứng dụng này sẽ chứa các tài liệu, chỉ mục và truy vấn Elasticsearch của chúng tôi:

(env)$ python manage.py startapp search

Đăng ký searchdjango_elasticsearch_dsltrong core / settings.py dưới INSTALLED_APPS:

# core/settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django_elasticsearch_dsl', # new
    'blog.apps.BlogConfig',
    'search.apps.SearchConfig', # new
    'rest_framework',
]

Bây giờ chúng ta cần cho Django biết Elasticsearch đang chạy ở đâu. Chúng tôi làm điều đó bằng cách thêm phần sau vào tệp core / settings.py của chúng tôi :

# core/settings.py

# Elasticsearch
# https://django-elasticsearch-dsl.readthedocs.io/en/latest/settings.html

ELASTICSEARCH_DSL = {
    'default': {
        'hosts': 'localhost:9200'
    },
}

Nếu Elasticsearch của bạn đang chạy trên một cổng khác, hãy đảm bảo thay đổi các cài đặt trên cho phù hợp.

Chúng tôi có thể kiểm tra xem Django có thể kết nối với Elasticsearch bằng cách khởi động máy chủ của chúng tôi:

(env)$ python manage.py runserver

Nếu máy chủ Django của bạn bị lỗi, Elasticsearch có thể không hoạt động chính xác.

Tạo tài liệu

Trước khi tạo tài liệu, chúng ta cần đảm bảo rằng tất cả dữ liệu sẽ được lưu ở định dạng thích hợp. Chúng tôi đang sử dụng CharField(max_length=2)cho bài viết typecủa mình, bản thân nó không có nhiều ý nghĩa. Đây là lý do tại sao chúng tôi sẽ chuyển nó thành văn bản mà con người có thể đọc được.

Chúng tôi sẽ đạt được điều này bằng cách thêm một type_to_string()phương thức bên trong mô hình của chúng tôi như sau:

# blog/models.py

class Article(models.Model):
    title = models.CharField(max_length=256)
    author = models.ForeignKey(to=User, on_delete=models.CASCADE)
    type = models.CharField(max_length=2, choices=ARTICLE_TYPES, default='UN')
    categories = models.ManyToManyField(to=Category, blank=True, related_name='categories')
    content = models.TextField()
    created_datetime = models.DateTimeField(auto_now_add=True)
    updated_datetime = models.DateTimeField(auto_now=True)

    # new
    def type_to_string(self):
        if self.type == 'UN':
            return 'Unspecified'
        elif self.type == 'TU':
            return 'Tutorial'
        elif self.type == 'RS':
            return 'Research'
        elif self.type == 'RW':
            return 'Review'

    def __str__(self):
        return f'{self.author}: {self.title} ({self.created_datetime.date()})'

Nếu không có type_to_string()mô hình của chúng tôi sẽ được đăng hàng loạt như thế này:

{
    "title": "This is my article.",
    "type": "TU",
    ...
}

Sau khi thực hiện type_to_string()mô hình của chúng tôi được tuần tự như thế này:

{
    "title": "This is my article.",
    "type": "Tutorial",
    ...
}

Bây giờ chúng ta hãy tạo các tài liệu. Mỗi tài liệu cần có một IndexDjangolớp. Trong Indexlớp, chúng ta cần cung cấp tên chỉ mục và cài đặt chỉ mục Elasticsearch . Trong Djangolớp, chúng tôi cho tài liệu biết mô hình Django nào để liên kết nó với và cung cấp các trường mà chúng tôi muốn được lập chỉ mục.

blog / Documents.py :

# blog/documents.py

from django.contrib.auth.models import User
from django_elasticsearch_dsl import Document, fields
from django_elasticsearch_dsl.registries import registry

from blog.models import Category, Article


@registry.register_document
class UserDocument(Document):
    class Index:
        name = 'users'
        settings = {
            'number_of_shards': 1,
            'number_of_replicas': 0,
        }

    class Django:
        model = User
        fields = [
            'id',
            'first_name',
            'last_name',
            'username',
        ]


@registry.register_document
class CategoryDocument(Document):
    id = fields.IntegerField()

    class Index:
        name = 'categories'
        settings = {
            'number_of_shards': 1,
            'number_of_replicas': 0,
        }

    class Django:
        model = Category
        fields = [
            'name',
            'description',
        ]


@registry.register_document
class ArticleDocument(Document):
    author = fields.ObjectField(properties={
        'id': fields.IntegerField(),
        'first_name': fields.TextField(),
        'last_name': fields.TextField(),
        'username': fields.TextField(),
    })
    categories = fields.ObjectField(properties={
        'id': fields.IntegerField(),
        'name': fields.TextField(),
        'description': fields.TextField(),
    })
    type = fields.TextField(attr='type_to_string')

    class Index:
        name = 'articles'
        settings = {
            'number_of_shards': 1,
            'number_of_replicas': 0,
        }

    class Django:
        model = Article
        fields = [
            'title',
            'content',
            'created_datetime',
            'updated_datetime',
        ]

Ghi chú:

  1. Để chuyển đổi loại bài viết, chúng tôi đã thêm typethuộc tính vào ArticleDocument.
  2. Bởi vì Articlemô hình của chúng tôi có mối quan hệ nhiều-nhiều (M: N) Categoryvà mối quan hệ nhiều-một (N: 1) Usernên chúng tôi cần quan tâm đến các mối quan hệ. Chúng tôi đã làm điều đó bằng cách thêm ObjectFieldcác thuộc tính.

Điền Elasticsearch

Để tạo và điền chỉ mục Elasticsearch và ánh xạ, hãy sử dụng search_indexlệnh:

(env)$ python manage.py search_index --rebuild

Deleting index 'users'
Deleting index 'categories'
Deleting index 'articles'
Creating index 'users'
Creating index 'categories'
Creating index 'articles'
Indexing 3 'User' objects
Indexing 4 'Article' objects
Indexing 4 'Category' objects

Bạn cần chạy lệnh này mỗi khi thay đổi cài đặt chỉ mục của mình.

django -asticsearch-dsl đã tạo các tín hiệu cơ sở dữ liệu thích hợp để bộ nhớ Elasticsearch của bạn được cập nhật mỗi khi một phiên bản của mô hình được tạo, xóa hoặc chỉnh sửa.

Truy vấn Elasticsearch

Trước khi tạo các khung nhìn thích hợp, chúng ta hãy xem cách hoạt động của các truy vấn Elasticsearch.

Đầu tiên chúng ta phải lấy được Searchví dụ. Chúng tôi thực hiện điều đó bằng cách gọi search()trên Tài liệu của chúng tôi như vậy:

from blog.documents import ArticleDocument

search = ArticleDocument.search()

Vui lòng chạy các truy vấn này trong Django shell.

Khi chúng ta có phiên bản, Searchchúng ta có thể chuyển các truy vấn đến query()phương thức và tìm nạp phản hồi:

from elasticsearch_dsl import Q
from blog.documents import ArticleDocument


# Looks up all the articles that contain `How to` in the title.
query = 'How to'
q = Q(
     'multi_match',
     query=query,
     fields=[
         'title'
     ])
search = ArticleDocument.search().query(q)
response = search.execute()

# print all the hits
for hit in search:
    print(hit.title)

Chúng ta cũng có thể kết hợp nhiều câu lệnh Q như vậy:

from elasticsearch_dsl import Q
from blog.documents import ArticleDocument

"""
Looks up all the articles that:
1) Contain 'language' in the 'title'
2) Don't contain 'ruby' or 'javascript' in the 'title'
3) And contain the query either in the 'title' or 'description'
"""
query = 'programming'
q = Q(
     'bool',
     must=[
         Q('match', title='language'),
     ],
     must_not=[
         Q('match', title='ruby'),
         Q('match', title='javascript'),
     ],
     should=[
         Q('match', title=query),
         Q('match', description=query),
     ],
     minimum_should_match=1)
search = ArticleDocument.search().query(q)
response = search.execute()

# print all the hits
for hit in search:
    print(hit.title)

Một điều quan trọng khác khi làm việc với các truy vấn Elasticsearch là tính mờ. Truy vấn mờ là những truy vấn cho phép chúng ta xử lý lỗi chính tả. Họ sử dụng Thuật toán khoảng cách Levenshtein để tính toán khoảng cách giữa kết quả trong cơ sở dữ liệu của chúng tôi và truy vấn.

Hãy xem một ví dụ.

Bằng cách chạy truy vấn sau, chúng tôi sẽ không nhận được bất kỳ kết quả nào, vì người dùng đã viết sai chính tả 'django'.

from elasticsearch_dsl import Q
from blog.documents import ArticleDocument

query = 'djengo'  # notice the typo
q = Q(
     'multi_match',
     query=query,
     fields=[
         'title'
     ])
search = ArticleDocument.search().query(q)
response = search.execute()

# print all the hits
for hit in search:
    print(hit.title)

Nếu chúng ta kích hoạt tính năng mờ như vậy:

from elasticsearch_dsl import Q
from blog.documents import ArticleDocument

query = 'djengo'  # notice the typo
q = Q(
     'multi_match',
     query=query,
     fields=[
         'title'
     ],
     fuzziness='auto')
search = ArticleDocument.search().query(q)
response = search.execute()

# print all the hits
for hit in search:
    print(hit.title)

Người dùng sẽ nhận được kết quả chính xác.

Sự khác biệt giữa tìm kiếm toàn văn bảnđối sánh chính xác là tìm kiếm toàn văn chạy một trình phân tích trên văn bản trước khi nó được lập chỉ mục tới Elasticsearch. Văn bản được chia thành các mã thông báo khác nhau, được chuyển đổi về dạng gốc của chúng (ví dụ: đọc -> đọc). Các mã thông báo này sau đó được lưu vào Chỉ mục Đảo ngược . Do đó, tìm kiếm toàn văn bản mang lại nhiều kết quả hơn, nhưng mất nhiều thời gian hơn để xử lý.

Elasticsearch có một số tính năng bổ sung. Để làm quen với API, hãy thử triển khai:

  1. Máy phân tích của riêng bạn .
  2. Đề xuất hoàn thành - khi người dùng truy vấn 'j', ứng dụng của bạn sẽ đề xuất 'johhny' hoặc 'jess_'.
  3. Đánh dấu - khi người dùng mắc lỗi đánh máy, hãy đánh dấu nó (ví dụ: Linuks -> Linux ).

Bạn có thể xem tất cả các API tìm kiếm Elasticsearch tại đây.

Lượt xem Tìm kiếm

Cùng với đó, hãy tạo các khung nhìn sime. Để làm cho mã của chúng tôi KHÔ hơn, chúng tôi có thể sử dụng lớp trừu tượng sau trong search / views.py :

# search/views.py

import abc

from django.http import HttpResponse
from elasticsearch_dsl import Q
from rest_framework.pagination import LimitOffsetPagination
from rest_framework.views import APIView


class PaginatedElasticSearchAPIView(APIView, LimitOffsetPagination):
    serializer_class = None
    document_class = None

    @abc.abstractmethod
    def generate_q_expression(self, query):
        """This method should be overridden
        and return a Q() expression."""

    def get(self, request, query):
        try:
            q = self.generate_q_expression(query)
            search = self.document_class.search().query(q)
            response = search.execute()

            print(f'Found {response.hits.total.value} hit(s) for query: "{query}"')

            results = self.paginate_queryset(response, request, view=self)
            serializer = self.serializer_class(results, many=True)
            return self.get_paginated_response(serializer.data)
        except Exception as e:
            return HttpResponse(e, status=500)

Ghi chú:

  1. Để sử dụng lớp, chúng ta phải cung cấp serializer_classdocument_classghi đè của chúng ta generate_q_expression().
  2. Lớp không làm gì khác hơn là chạy generate_q_expression()truy vấn, tìm nạp phản hồi, phân trang nó và trả về dữ liệu được tuần tự hóa.

Tất cả các chế độ xem bây giờ sẽ kế thừa từ PaginatedElasticSearchAPIView:

# search/views.py

import abc

from django.http import HttpResponse
from elasticsearch_dsl import Q
from rest_framework.pagination import LimitOffsetPagination
from rest_framework.views import APIView

from blog.documents import ArticleDocument, UserDocument, CategoryDocument
from blog.serializers import ArticleSerializer, UserSerializer, CategorySerializer


class PaginatedElasticSearchAPIView(APIView, LimitOffsetPagination):
    serializer_class = None
    document_class = None

    @abc.abstractmethod
    def generate_q_expression(self, query):
        """This method should be overridden
        and return a Q() expression."""

    def get(self, request, query):
        try:
            q = self.generate_q_expression(query)
            search = self.document_class.search().query(q)
            response = search.execute()

            print(f'Found {response.hits.total.value} hit(s) for query: "{query}"')

            results = self.paginate_queryset(response, request, view=self)
            serializer = self.serializer_class(results, many=True)
            return self.get_paginated_response(serializer.data)
        except Exception as e:
            return HttpResponse(e, status=500)


# views


class SearchUsers(PaginatedElasticSearchAPIView):
    serializer_class = UserSerializer
    document_class = UserDocument

    def generate_q_expression(self, query):
        return Q('bool',
                 should=[
                     Q('match', username=query),
                     Q('match', first_name=query),
                     Q('match', last_name=query),
                 ], minimum_should_match=1)


class SearchCategories(PaginatedElasticSearchAPIView):
    serializer_class = CategorySerializer
    document_class = CategoryDocument

    def generate_q_expression(self, query):
        return Q(
                'multi_match', query=query,
                fields=[
                    'name',
                    'description',
                ], fuzziness='auto')


class SearchArticles(PaginatedElasticSearchAPIView):
    serializer_class = ArticleSerializer
    document_class = ArticleDocument

    def generate_q_expression(self, query):
        return Q(
                'multi_match', query=query,
                fields=[
                    'title',
                    'author',
                    'type',
                    'content'
                ], fuzziness='auto')

Xác định URL

Cuối cùng, hãy tạo URL cho chế độ xem của chúng tôi:

# search.urls.py

from django.urls import path

from search.views import SearchArticles, SearchCategories, SearchUsers

urlpatterns = [
    path('user/<str:query>/', SearchUsers.as_view()),
    path('category/<str:query>/', SearchCategories.as_view()),
    path('article/<str:query>/', SearchArticles.as_view()),
]

Sau đó, kết nối các URL của ứng dụng với các URL của dự án:

# core/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('blog/', include('blog.urls')),
    path('search/', include('search.urls')), # new
    path('admin/', admin.site.urls),
]

Thử nghiệm

Ứng dụng web của chúng tôi đã hoàn thành. Chúng tôi có thể kiểm tra các điểm cuối tìm kiếm của mình bằng cách truy cập các URL sau:

URLSự mô tả
http://127.0.0.1:8000/search/user/mike/Trả về người dùng 'mike13'
http://127.0.0.1:8000/search/user/jess_/Trả về người dùng 'jess_'
http://127.0.0.1:8000/search/category/seo/Trả về danh mục 'Tối ưu hóa SEO'
http://127.0.0.1:8000/search/category/progreming/Trả về danh mục 'Lập trình'
http://127.0.0.1:8000/search/article/linux/Trả về bài viết 'Cài đặt phiên bản Ubuntu mới nhất'
http://127.0.0.1:8000/search/article/java/Trả về bài viết 'Ngôn ngữ lập trình nào là tốt nhất?'

Chú ý lỗi đánh máy với yêu cầu thứ tư. Chúng tôi đã đánh vần là 'progreming', nhưng vẫn nhận được kết quả chính xác nhờ vào độ mờ.

Thư viện thay thế

Con đường chúng tôi đã đi không phải là cách duy nhất để tích hợp Django với Elasticsearch. Có một số thư viện khác mà bạn có thể muốn xem:

  1. django-elasicsearch-dsl-drf là ​​một trình bao bọc xung quanh Elasticsearch và Django REST Framework. Nó cung cấp các chế độ xem, bộ tuần tự hóa, phần phụ trợ bộ lọc, phân trang và hơn thế nữa. Nó hoạt động tốt, nhưng nó có thể là quá mức cần thiết cho các dự án nhỏ hơn. Tôi khuyên bạn nên sử dụng nó nếu bạn cần các tính năng Elasticsearch nâng cao.
  2. Haystack là một trình bao bọc cho một số phần mềm phụ trợ tìm kiếm, như Elasticsearch, SolrWhoosh . Nó cho phép bạn viết mã tìm kiếm của mình một lần và sử dụng lại nó với các phụ trợ tìm kiếm khác nhau. Nó hoạt động tuyệt vời để triển khai một hộp tìm kiếm đơn giản. Bởi vì Haystack là một lớp trừu tượng khác, có nhiều chi phí liên quan hơn, vì vậy bạn không nên sử dụng nó nếu hiệu suất thực sự quan trọng hoặc nếu bạn đang làm việc với lượng lớn dữ liệu. Nó cũng yêu cầu một số cấu hình.
  3. Haystack cho Django REST Framework là một thư viện nhỏ cố gắng đơn giản hóa việc tích hợp Haystack với Django REST Framework. Tại thời điểm viết bài, dự án hơi lỗi thời và tài liệu của họ được viết rất tệ. Tôi đã dành một khoảng thời gian kha khá để cố gắng làm cho nó hoạt động mà không gặp may.

Sự kết luận

Trong hướng dẫn này, bạn đã học những kiến ​​thức cơ bản về cách làm việc với Django REST Framework và Elasticsearch. Giờ đây, bạn biết cách tích hợp chúng, tạo các tài liệu và truy vấn Elasticsearch, đồng thời cung cấp dữ liệu thông qua API RESTful.

Trước khi khởi chạy dự án của bạn trong sản xuất, hãy cân nhắc sử dụng một trong các dịch vụ Elasticsearch được quản lý như Elastic Cloud , Amazon Elasticsearch Service hoặc Elastic on Azure . Chi phí sử dụng dịch vụ được quản lý sẽ cao hơn so với quản lý cụm của riêng bạn, nhưng chúng cung cấp tất cả cơ sở hạ tầng cần thiết để triển khai, bảo mật và chạy các cụm Elasticsearch. Ngoài ra, họ sẽ xử lý các bản cập nhật phiên bản, sao lưu thường xuyên và mở rộng quy mô.

Lấy mã từ repo django-drf -asticsearch trên GitHub.

Nguồn:  https://testdriven.io

#django #elasticsearch 

What is GEEK

Buddha Community

Cách Tích Hợp Django REST Framework Với Elasticsearch
Ahebwe  Oscar

Ahebwe Oscar

1623185400

Permissions in Django Rest Framework

This article looks at how permissions work in Django REST Framework (DRF).

Objectives

By the end of this article, you should be able to explain:

  1. How DRF permissions work
  2. The similarities and differences between has_permission and has_object_permission
  3. When to use has_permission and has_object_permission

DRF Permissions

In DRF, permissions, along with authentication and throttling, are used to grant or deny access for different classes of users to different parts of an API.

Authentication and authorization work hand in hand. Authentication is always executed before authorization.

While authentication is the process of checking a user’s identity (the user the request came from, the token that it was signed with), authorization is a process of checking if the request user has the necessary permissions for executing the request (are they a super user, are they the creators of the object).

The authorization process in DRF is covered by permissions.

#permissions in django rest framework #django rest framework #permissions #rest #rest framework #django

Lets Cms

Lets Cms

1652251528

Opencart REST API extensions - V3.x | Rest API Integration, Affiliate

Opencart REST API extensions - V3.x | Rest API Integration : OpenCart APIs is fully integrated with the OpenCart REST API. This is interact with your OpenCart site by sending and receiving data as JSON (JavaScript Object Notation) objects. Using the OpenCart REST API you can register the customers and purchasing the products and it provides data access to the content of OpenCart users like which is publicly accessible via the REST API. This APIs also provide the E-commerce Mobile Apps.

Opencart REST API 
OCRESTAPI Module allows the customer purchasing product from the website it just like E-commerce APIs its also available mobile version APIs.

Opencart Rest APIs List 
Customer Registration GET APIs.
Customer Registration POST APIs.
Customer Login GET APIs.
Customer Login POST APIs.
Checkout Confirm GET APIs.
Checkout Confirm POST APIs.


If you want to know Opencart REST API Any information, you can contact us at -
Skype: jks0586,
Email: letscmsdev@gmail.com,
Website: www.letscms.com, www.mlmtrees.com
Call/WhatsApp/WeChat: +91–9717478599.

Download : https://www.opencart.com/index.php?route=marketplace/extension/info&extension_id=43174&filter_search=ocrest%20api
View Documentation : https://www.letscms.com/documents/api/opencart-rest-api.html
More Information : https://www.letscms.com/blog/Rest-API-Opencart
VEDIO : https://vimeo.com/682154292  

#opencart_api_for_android #Opencart_rest_admin_api #opencart_rest_api #Rest_API_Integration #oc_rest_api #rest_api_ecommerce #rest_api_mobile #rest_api_opencart #rest_api_github #rest_api_documentation #opencart_rest_admin_api #rest_api_for_opencart_mobile_app #opencart_shopping_cart_rest_api #opencart_json_api

Ahebwe  Oscar

Ahebwe Oscar

1620177818

Django admin full Customization step by step

Welcome to my blog , hey everyone in this article you learn how to customize the Django app and view in the article you will know how to register  and unregister  models from the admin view how to add filtering how to add a custom input field, and a button that triggers an action on all objects and even how to change the look of your app and page using the Django suit package let’s get started.

Database

Custom Titles of Django Admin

Exclude in Django Admin

Fields in Django Admin

#django #create super user django #customize django admin dashboard #django admin #django admin custom field display #django admin customization #django admin full customization #django admin interface #django admin register all models #django customization

Lets Cms

Lets Cms

1652251629

Unilevel MLM Wordpress Rest API FrontEnd | UMW Rest API Woocommerce

Unilevel MLM Wordpress Rest API FrontEnd | UMW Rest API Woocommerce Price USA, Philippines : Our API’s handle the Unilevel MLM woo-commerce end user all functionalities like customer login/register. You can request any type of information which is listed below, our API will provide you managed results for your all frontend needs, which will be useful for your applications like Mobile App etc.
Business to Customer REST API for Unilevel MLM Woo-Commerce will empower your Woo-commerce site with the most powerful Unilevel MLM Woo-Commerce REST API, you will be able to get and send data to your marketplace from other mobile apps or websites using HTTP Rest API request.
Our plugin is used JWT authentication for the authorization process.

REST API Unilevel MLM Woo-commerce plugin contains following APIs.
User Login Rest API
User Register Rest API
User Join Rest API
Get User info Rest API
Get Affiliate URL Rest API 
Get Downlines list Rest API
Get Bank Details Rest API
Save Bank Details Rest API
Get Genealogy JSON Rest API
Get Total Earning Rest API
Get Current Balance Rest API
Get Payout Details Rest API
Get Payout List Rest API
Get Commissions List Rest API
Withdrawal Request Rest API
Get Withdrawal List Rest API

If you want to know more information and any queries regarding Unilevel MLM Rest API Woocommerce WordPress Plugin, you can contact our experts through 
Skype: jks0586, 
Mail: letscmsdev@gmail.com,
Website: www.letscms.com, www.mlmtrees.com,
Call/WhatsApp/WeChat: +91-9717478599.  

more information : https://www.mlmtrees.com/product/unilevel-mlm-woocommerce-rest-api-addon

Visit Documentation : https://letscms.com/documents/umw_apis/umw-apis-addon-documentation.html

#Unilevel_MLM_WooCommerce_Rest_API's_Addon #umw_mlm_rest_api #rest_api_woocommerce_unilevel #rest_api_in_woocommerce #rest_api_woocommerce #rest_api_woocommerce_documentation #rest_api_woocommerce_php #api_rest_de_woocommerce #woocommerce_rest_api_in_android #woocommerce_rest_api_in_wordpress #Rest_API_Woocommerce_unilevel_mlm #wp_rest_api_woocommerce

Sheldon  Grant

Sheldon Grant

1668836834

How to integrate Django REST Framework with Elasticsearch

In this tutorial, we'll look at how to integrate Django REST Framework (DRF) with Elasticsearch. We'll use Django to model our data and DRF to serialize and serve it. Finally, we'll index the data with Elasticsearch and make it searchable.

What is Elasticsearch?

Elasticsearch is a distributed, free and open search and analytics engine for all types of data, including textual, numerical, geospatial, structured, and unstructured. It's known for its simple RESTful APIs, distributed nature, speed, and scalability. Elasticsearch is the central component of the Elastic Stack (also known as the ELK Stack), a set of free and open tools for data ingestion, enrichment, storage, analysis, and visualization.

Its use cases include:

  1. Site search and application search
  2. Monitoring and visualizing your system metrics
  3. Security and business analytics
  4. Logging and log analysis

To learn more about Elasticsearch check out What is Elasticsearch? from the official documentation.

Elasticsearch Structure and Concepts

Before working with Elasticsearch, we should get familiar with the basic Elasticsearch concepts. These are listed from biggest to smallest:

  1. Cluster is a collection of one or more nodes.
  2. Node is a single server instance that runs Elasticsearch. While communicating with the cluster, it:
    1. Stores and indexes your data
    2. Provides search
  3. Index is used to store the documents in dedicated data structures corresponding to the data type of fields (akin to a SQL database). Each index has one or more shards and replicas.
  4. Type is a collection of documents, which have something in common (akin to a SQL table).
  5. Shard is an Apache Lucene index. It's used to split indices and keep large amounts of data manageable.
  6. Replica is a fail-safe mechanism and basically a copy of your index's shard.
  7. Document is a basic unit of information that can be indexed (akin to a SQL row). It's expressed in JSON, which is a ubiquitous internet data interchange format.
  8. Field is the smallest individual unit of data in Elasticsearch (akin to a SQL column).

The Elasticsearch cluster has the following structure:

Elasticsearch cluster structure

Curious how relational database concepts relate to Elasticsearch concepts?

Relational DatabaseElasticsearch
ClusterCluster
RDBMS InstanceNode
TableIndex
RowDocument
ColumnField

Review Mapping concepts across SQL and Elasticsearch for more on how concepts in SQL and Elasticsearch relate to one another.

Elasticsearch vs PostgreSQL Full-text Search

With regards to full-text search, Elasticsearch and PostgreSQL both have their advantages and disadvantages. When choosing between them you should consider speed, query complexity, and budget.

PostgreSQL advantages:

  1. Django support
  2. Faster and easier to setup
  3. Doesn't require maintenance

Elasticsearch advantages:

  1. Optimized just for searching
  2. Elasicsearch is faster (especially as the number of records increases)
  3. Supports different query types (Leaf, Compound, Fuzzy, Regexp, to name a few)

If you're working on a simple project where speed isn't important you should opt for PostgreSQL. If performance is important and you want to write complex lookups opt for Elasticsearch.

For more on full-text search with Django and Postgres, check out the Basic and Full-text Search with Django and Postgres article.

Project Setup

We'll be building a simple blog application. Our project will consist of multiple models, which will be serialized and served via Django REST Framework. After integrating Elasticsearch, we'll create an endpoint that will allow us to look up different authors, categories, and articles.

To keep our code clean and modular, we'll split our project into the following two apps:

  1. blog - for our Django models, serializers, and ViewSets
  2. search - for Elasticsearch documents, indexes, and queries

Start by creating a new directory and setting up a new Django project:

$ mkdir django-drf-elasticsearch && cd django-drf-elasticsearch
$ python3.9 -m venv env
$ source env/bin/activate

(env)$ pip install django==3.2.6
(env)$ django-admin.py startproject core .

After that, create a new app called blog:

(env)$ python manage.py startapp blog

Register the app in core/settings.py under INSTALLED_APPS:

# core/settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'blog.apps.BlogConfig', # new
]

Database Models

Next, create Category and Article models in blog/models.py:

# blog/models.py

from django.contrib.auth.models import User
from django.db import models


class Category(models.Model):
    name = models.CharField(max_length=32)
    description = models.TextField(null=True, blank=True)

    class Meta:
        verbose_name_plural = 'categories'

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


ARTICLE_TYPES = [
    ('UN', 'Unspecified'),
    ('TU', 'Tutorial'),
    ('RS', 'Research'),
    ('RW', 'Review'),
]


class Article(models.Model):
    title = models.CharField(max_length=256)
    author = models.ForeignKey(to=User, on_delete=models.CASCADE)
    type = models.CharField(max_length=2, choices=ARTICLE_TYPES, default='UN')
    categories = models.ManyToManyField(to=Category, blank=True, related_name='categories')
    content = models.TextField()
    created_datetime = models.DateTimeField(auto_now_add=True)
    updated_datetime = models.DateTimeField(auto_now=True)

    def __str__(self):
        return f'{self.author}: {self.title} ({self.created_datetime.date()})'

Notes:

  1. Category represents an article category -- i.e, programming, Linux, testing.
  2. Article represents an individual article. Each article can have multiple categories. Articles have a specific type -- Tutorial, Research, Review, or Unspecified.
  3. Authors are represented by the default Django user model.

Run Migrations

Make migrations and then apply them:

(env)$ python manage.py makemigrations
(env)$ python manage.py migrate

Register the models in blog/admin.py:

# blog/admin.py

from django.contrib import admin

from blog.models import Category, Article


admin.site.register(Category)
admin.site.register(Article)

Populate the Database

Before moving to the next step, we need some data to work with. I've created a simple command we can use to populate the database.

Create a new folder in "blog" called "management", and then inside that folder create another folder called "commands". Inside of the "commands" folder, create a new file called populate_db.py.

management
└── commands
    └── populate_db.py

Copy the file contents from populate_db.py and paste it inside your populate_db.py.

Run the following command to populate the DB:

(env)$ python manage.py populate_db

If everything went well you should see a Successfully populated the database. message in the console and there should be a few articles in your database.

Django REST Framework

Now let's install djangorestframework using pip:

(env)$ pip install djangorestframework==3.12.4

Register it in our settings.py like so:

# core/settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'blog.apps.BlogConfig',
    'rest_framework', # new
]

Add the following settings:

# core/settings.py

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
    'PAGE_SIZE': 25
}

We'll need these settings to implement pagination.

Create Serializers

To serialize our Django models, we need to create a serializer for each of them. The easiest way to create serializers that depend on Django models is by using the ModelSerializer class.

blog/serializers.py:

# blog/serializers.py

from django.contrib.auth.models import User
from rest_framework import serializers

from blog.models import Article, Category


class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ('id', 'username', 'first_name', 'last_name')


class CategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = Category
        fields = '__all__'


class ArticleSerializer(serializers.ModelSerializer):
    author = UserSerializer()
    categories = CategorySerializer(many=True)

    class Meta:
        model = Article
        fields = '__all__'

Notes:

  1. UserSerializer and CategorySerializer are fairly simple: We just provided the fields we want serialized.
  2. In the ArticleSerializer, we needed to take care of the relationships to make sure they also get serialized. This is why we provided UserSerializer and CategorySerializer.

Want to learn more about DRF serializers? Check out Effectively Using Django REST Framework Serializers.

Create ViewSets

Let's create a ViewSet for each of our models in blog/views.py:

# blog/views.py

from django.contrib.auth.models import User
from rest_framework import viewsets

from blog.models import Category, Article
from blog.serializers import CategorySerializer, ArticleSerializer, UserSerializer


class UserViewSet(viewsets.ModelViewSet):
    serializer_class = UserSerializer
    queryset = User.objects.all()


class CategoryViewSet(viewsets.ModelViewSet):
    serializer_class = CategorySerializer
    queryset = Category.objects.all()


class ArticleViewSet(viewsets.ModelViewSet):
    serializer_class = ArticleSerializer
    queryset = Article.objects.all()

In this block of code, we created the ViewSets by providing the serializer_class and queryset for each ViewSet.

Define URLs

Create the app-level URLs for the ViewSets:

# blog/urls.py

from django.urls import path, include
from rest_framework import routers

from blog.views import UserViewSet, CategoryViewSet, ArticleViewSet

router = routers.DefaultRouter()
router.register(r'user', UserViewSet)
router.register(r'category', CategoryViewSet)
router.register(r'article', ArticleViewSet)

urlpatterns = [
    path('', include(router.urls)),
]

Then, wire up the app URLs to the project URLs:

# core/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('blog/', include('blog.urls')),
    path('admin/', admin.site.urls),
]

Our app now has the following URLs:

  1. /blog/user/ lists all users
  2. /blog/user/<USER_ID>/ fetches a specific user
  3. /blog/category/ lists all categories
  4. /blog/category/<CATEGORY_ID>/ fetches a specific category
  5. /blog/article/ lists all articles
  6. /blog/article/<ARTICLE_ID>/ fetches a specific article

Testing

Now that we've registered the URLs, we can test the endpoints to see if everything works correctly.

Run the development server:

(env)$ python manage.py runserver

Then, in your browser of choice, navigate to http://127.0.0.1:8000/blog/article/. The response should look something like this:

{
    "count": 4,
    "next": null,
    "previous": null,
    "results": [
        {
            "id": 1,
            "author": {
                "id": 3,
                "username": "jess_",
                "first_name": "Jess",
                "last_name": "Brown"
            },
            "categories": [
                {
                    "id": 2,
                    "name": "SEO optimization",
                    "description": null
                }
            ],
            "title": "How to improve your Google rating?",
            "type": "TU",
            "content": "Firstly, add the correct SEO tags...",
            "created_datetime": "2021-08-12T17:34:31.271610Z",
            "updated_datetime": "2021-08-12T17:34:31.322165Z"
        },
        {
            "id": 2,
            "author": {
                "id": 4,
                "username": "johnny",
                "first_name": "Johnny",
                "last_name": "Davis"
            },
            "categories": [
                {
                    "id": 4,
                    "name": "Programming",
                    "description": null
                }
            ],
            "title": "Installing latest version of Ubuntu",
            "type": "TU",
            "content": "In this tutorial, we'll take a look at how to setup the latest version of Ubuntu. Ubuntu (/ʊˈbʊntuː/ is a Linux distribution based on Debian and composed mostly of free and open-source software. Ubuntu is officially released in three editions: Desktop, Server, and Core for Internet of things devices and robots.",
            "created_datetime": "2021-08-12T17:34:31.540628Z",
            "updated_datetime": "2021-08-12T17:34:31.592555Z"
        },
        ...
    ]
}

Manually test the other endpoints as well.

Elasticsearch Setup

Start by installing and running Elasticsearch in the background.

Need help getting Elasticsearch up and running? Check out the Installing Elasticsearch guide. If you're familiar with Docker, you can simply run the following command to pull the official image and spin up a container with Elasticsearch running:

$ docker run -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:7.14.0

To integrate Elasticsearch with Django, we need to install the following packages:

  1. elasticsearch - official low-level Python client for Elasticsearch
  2. elasticsearch-dsl-py - high-level library for writing and running queries against Elasticsearch
  3. django-elasticsearch-dsl - wrapper around elasticsearch-dsl-py that allows indexing Django models in Elasticsearch

Install:

(env)$ pip install elasticsearch==7.14.0
(env)$ pip install elasticsearch-dsl==7.4.0
(env)$ pip install django-elasticsearch-dsl==7.2.0

Start a new app called search, which will hold our Elasticsearch documents, indexes, and queries:

(env)$ python manage.py startapp search

Register the search and django_elasticsearch_dsl in core/settings.py under INSTALLED_APPS:

# core/settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django_elasticsearch_dsl', # new
    'blog.apps.BlogConfig',
    'search.apps.SearchConfig', # new
    'rest_framework',
]

Now we need to let Django know where Elasticsearch is running. We do that by adding the following to our core/settings.py file:

# core/settings.py

# Elasticsearch
# https://django-elasticsearch-dsl.readthedocs.io/en/latest/settings.html

ELASTICSEARCH_DSL = {
    'default': {
        'hosts': 'localhost:9200'
    },
}

If your Elasticsearch is running on a different port, make sure to change the above settings accordingly.

We can test if Django can connect to the Elasticsearch by starting our server:

(env)$ python manage.py runserver

If your Django server fails, Elasticsearch is probably not working correctly.

Creating Documents

Before creating the documents, we need to make sure all the data is going to get saved in the proper format. We're using CharField(max_length=2) for our article type, which by itself doesn't make much sense. This is why we'll transform it to human-readable text.

We'll achieve this by adding a type_to_string() method inside our model like so:

# blog/models.py

class Article(models.Model):
    title = models.CharField(max_length=256)
    author = models.ForeignKey(to=User, on_delete=models.CASCADE)
    type = models.CharField(max_length=2, choices=ARTICLE_TYPES, default='UN')
    categories = models.ManyToManyField(to=Category, blank=True, related_name='categories')
    content = models.TextField()
    created_datetime = models.DateTimeField(auto_now_add=True)
    updated_datetime = models.DateTimeField(auto_now=True)

    # new
    def type_to_string(self):
        if self.type == 'UN':
            return 'Unspecified'
        elif self.type == 'TU':
            return 'Tutorial'
        elif self.type == 'RS':
            return 'Research'
        elif self.type == 'RW':
            return 'Review'

    def __str__(self):
        return f'{self.author}: {self.title} ({self.created_datetime.date()})'

Without type_to_string() our model would be serialized like this:

{
    "title": "This is my article.",
    "type": "TU",
    ...
}

After implementing type_to_string() our model is serialized like this:

{
    "title": "This is my article.",
    "type": "Tutorial",
    ...
}

Now let's create the documents. Each document needs to have an Index and Django class. In the Index class, we need to provide the index name and Elasticsearch index settings. In the Django class, we tell the document which Django model to associate it to and provide the fields we want to be indexed.

blog/documents.py:

# blog/documents.py

from django.contrib.auth.models import User
from django_elasticsearch_dsl import Document, fields
from django_elasticsearch_dsl.registries import registry

from blog.models import Category, Article


@registry.register_document
class UserDocument(Document):
    class Index:
        name = 'users'
        settings = {
            'number_of_shards': 1,
            'number_of_replicas': 0,
        }

    class Django:
        model = User
        fields = [
            'id',
            'first_name',
            'last_name',
            'username',
        ]


@registry.register_document
class CategoryDocument(Document):
    id = fields.IntegerField()

    class Index:
        name = 'categories'
        settings = {
            'number_of_shards': 1,
            'number_of_replicas': 0,
        }

    class Django:
        model = Category
        fields = [
            'name',
            'description',
        ]


@registry.register_document
class ArticleDocument(Document):
    author = fields.ObjectField(properties={
        'id': fields.IntegerField(),
        'first_name': fields.TextField(),
        'last_name': fields.TextField(),
        'username': fields.TextField(),
    })
    categories = fields.ObjectField(properties={
        'id': fields.IntegerField(),
        'name': fields.TextField(),
        'description': fields.TextField(),
    })
    type = fields.TextField(attr='type_to_string')

    class Index:
        name = 'articles'
        settings = {
            'number_of_shards': 1,
            'number_of_replicas': 0,
        }

    class Django:
        model = Article
        fields = [
            'title',
            'content',
            'created_datetime',
            'updated_datetime',
        ]

Notes:

  1. In order to transform the article type, we added the type attribute to the ArticleDocument.
  2. Because our Article model is in a many-to-many (M:N) relationship with Category and a many-to-one (N:1) relationship with User we needed to take care of the relationships. We did that by adding ObjectField attributes.

Populate Elasticsearch

To create and populate the Elasticsearch index and mapping, use the search_index command:

(env)$ python manage.py search_index --rebuild

Deleting index 'users'
Deleting index 'categories'
Deleting index 'articles'
Creating index 'users'
Creating index 'categories'
Creating index 'articles'
Indexing 3 'User' objects
Indexing 4 'Article' objects
Indexing 4 'Category' objects

You need to run this command every time you change your index settings.

django-elasticsearch-dsl created the appropriate database signals so that your Elasticsearch storage gets updated every time an instance of a model is created, deleted, or edited.

Elasticsearch Queries

Before creating the appropriate views, let's look at how Elasticsearch queries work.

We first have to obtain the Search instance. We do that by calling search() on our Document like so:

from blog.documents import ArticleDocument

search = ArticleDocument.search()

Feel free to run these queries within the Django shell.

Once we have the Search instance we can pass queries to the query() method and fetch the response:

from elasticsearch_dsl import Q
from blog.documents import ArticleDocument


# Looks up all the articles that contain `How to` in the title.
query = 'How to'
q = Q(
     'multi_match',
     query=query,
     fields=[
         'title'
     ])
search = ArticleDocument.search().query(q)
response = search.execute()

# print all the hits
for hit in search:
    print(hit.title)

We can also combine multiple Q statements like so:

from elasticsearch_dsl import Q
from blog.documents import ArticleDocument

"""
Looks up all the articles that:
1) Contain 'language' in the 'title'
2) Don't contain 'ruby' or 'javascript' in the 'title'
3) And contain the query either in the 'title' or 'description'
"""
query = 'programming'
q = Q(
     'bool',
     must=[
         Q('match', title='language'),
     ],
     must_not=[
         Q('match', title='ruby'),
         Q('match', title='javascript'),
     ],
     should=[
         Q('match', title=query),
         Q('match', description=query),
     ],
     minimum_should_match=1)
search = ArticleDocument.search().query(q)
response = search.execute()

# print all the hits
for hit in search:
    print(hit.title)

Another important thing when working with Elasticsearch queries is fuzziness. Fuzzy queries are queries that allow us to handle typos. They use the Levenshtein Distance Algorithm which calculates the distance between the result in our database and the query.

Let's look at an example.

By running the following query we won't get any results, because the user misspelled 'django'.

from elasticsearch_dsl import Q
from blog.documents import ArticleDocument

query = 'djengo'  # notice the typo
q = Q(
     'multi_match',
     query=query,
     fields=[
         'title'
     ])
search = ArticleDocument.search().query(q)
response = search.execute()

# print all the hits
for hit in search:
    print(hit.title)

If we enable fuzziness like so:

from elasticsearch_dsl import Q
from blog.documents import ArticleDocument

query = 'djengo'  # notice the typo
q = Q(
     'multi_match',
     query=query,
     fields=[
         'title'
     ],
     fuzziness='auto')
search = ArticleDocument.search().query(q)
response = search.execute()

# print all the hits
for hit in search:
    print(hit.title)

The user will get the correct result.

The difference between a full-text search and exact match is that full-text search runs an analyzer on the text before it gets indexed to Elasticsearch. The text gets broken down into different tokens, which are transformed to their root form (e.g., reading -> read). These tokens then get saved into the Inverted Index. Because of that, full-text search yields more results, but takes longer to process.

Elasticsearch has a number of additional features. To get familiar with the API, try implementing:

  1. Your own analyzer.
  2. Completion suggester - when a user queries 'j' your app should suggest 'johhny' or 'jess_'.
  3. Highlighting - when user makes a typo, highlight it (e.g., Linuks -> Linux).

You can see all the Elasticsearch Search APIs here.

Search Views

With that, let's create sime views. To make our code more DRY we can use the following abstract class in search/views.py:

# search/views.py

import abc

from django.http import HttpResponse
from elasticsearch_dsl import Q
from rest_framework.pagination import LimitOffsetPagination
from rest_framework.views import APIView


class PaginatedElasticSearchAPIView(APIView, LimitOffsetPagination):
    serializer_class = None
    document_class = None

    @abc.abstractmethod
    def generate_q_expression(self, query):
        """This method should be overridden
        and return a Q() expression."""

    def get(self, request, query):
        try:
            q = self.generate_q_expression(query)
            search = self.document_class.search().query(q)
            response = search.execute()

            print(f'Found {response.hits.total.value} hit(s) for query: "{query}"')

            results = self.paginate_queryset(response, request, view=self)
            serializer = self.serializer_class(results, many=True)
            return self.get_paginated_response(serializer.data)
        except Exception as e:
            return HttpResponse(e, status=500)

Notes:

  1. To use the class, we have to provide our serializer_class and document_class and override generate_q_expression().
  2. The class does nothing else than run the generate_q_expression() query, fetch the response, paginate it, and return serialized data.

All the views should now inherit from PaginatedElasticSearchAPIView:

# search/views.py

import abc

from django.http import HttpResponse
from elasticsearch_dsl import Q
from rest_framework.pagination import LimitOffsetPagination
from rest_framework.views import APIView

from blog.documents import ArticleDocument, UserDocument, CategoryDocument
from blog.serializers import ArticleSerializer, UserSerializer, CategorySerializer


class PaginatedElasticSearchAPIView(APIView, LimitOffsetPagination):
    serializer_class = None
    document_class = None

    @abc.abstractmethod
    def generate_q_expression(self, query):
        """This method should be overridden
        and return a Q() expression."""

    def get(self, request, query):
        try:
            q = self.generate_q_expression(query)
            search = self.document_class.search().query(q)
            response = search.execute()

            print(f'Found {response.hits.total.value} hit(s) for query: "{query}"')

            results = self.paginate_queryset(response, request, view=self)
            serializer = self.serializer_class(results, many=True)
            return self.get_paginated_response(serializer.data)
        except Exception as e:
            return HttpResponse(e, status=500)


# views


class SearchUsers(PaginatedElasticSearchAPIView):
    serializer_class = UserSerializer
    document_class = UserDocument

    def generate_q_expression(self, query):
        return Q('bool',
                 should=[
                     Q('match', username=query),
                     Q('match', first_name=query),
                     Q('match', last_name=query),
                 ], minimum_should_match=1)


class SearchCategories(PaginatedElasticSearchAPIView):
    serializer_class = CategorySerializer
    document_class = CategoryDocument

    def generate_q_expression(self, query):
        return Q(
                'multi_match', query=query,
                fields=[
                    'name',
                    'description',
                ], fuzziness='auto')


class SearchArticles(PaginatedElasticSearchAPIView):
    serializer_class = ArticleSerializer
    document_class = ArticleDocument

    def generate_q_expression(self, query):
        return Q(
                'multi_match', query=query,
                fields=[
                    'title',
                    'author',
                    'type',
                    'content'
                ], fuzziness='auto')

Define URLs

Lastly, let's create the URLs for our views:

# search.urls.py

from django.urls import path

from search.views import SearchArticles, SearchCategories, SearchUsers

urlpatterns = [
    path('user/<str:query>/', SearchUsers.as_view()),
    path('category/<str:query>/', SearchCategories.as_view()),
    path('article/<str:query>/', SearchArticles.as_view()),
]

Then, wire up the app URLs to the project URLs:

# core/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('blog/', include('blog.urls')),
    path('search/', include('search.urls')), # new
    path('admin/', admin.site.urls),
]

Testing

Our web application is done. We can test our search endpoints by visiting the following URLs:

URLDescription
http://127.0.0.1:8000/search/user/mike/Returns user 'mike13'
http://127.0.0.1:8000/search/user/jess_/Returns user 'jess_'
http://127.0.0.1:8000/search/category/seo/Returns category 'SEO optimization'
http://127.0.0.1:8000/search/category/progreming/Returns category 'Programming'
http://127.0.0.1:8000/search/article/linux/Returns article 'Installing the latest version of Ubuntu'
http://127.0.0.1:8000/search/article/java/Returns article 'Which programming language is the best?'

Notice the typo with the fourth request. We spelled 'progreming', but still got the correct result thanks to fuzziness.

Alternative Libraries

The path we took isn't the only way to integrate Django with Elasticsearch. There are a few other libraries you might want to check out:

  1. django-elasicsearch-dsl-drf is a wrapper around Elasticsearch and Django REST Framework. It provides views, serializers, filter backends, pagination and more. It works well, but it might be overkill for smaller projects. I'd recommend using it if you need advanced Elasticsearch features.
  2. Haystack is a wrapper for a number of search backends, like Elasticsearch, Solr, and Whoosh. It allows you to write your search code once and reuse it with different search backends. It works great for implementing a simple search box. Because Haystack is another abstraction layer, there's more overhead involved so you shouldn't use it if performance is really important or if you're working with big amounts of data. It also requires some configuration.
  3. Haystack for Django REST Framework is a small library which tries to simplify integration of Haystack with Django REST Framework. At the time of writing, the project is a bit outdated and their documentation is badly written. I've spent a decent amount of time trying to get it to work with no luck.

Conclusion

In this tutorial, you learned the basics of working with Django REST Framework and Elasticsearch. You now know how to integrate them, create Elasticsearch documents and queries, and serve the data via a RESTful API.

Before launching your project in production, consider using one of the managed Elasticsearch services like Elastic Cloud, Amazon Elasticsearch Service, or Elastic on Azure. The cost of using a managed service will be higher than managing your own cluster, but they provide all of the infrastructure required for deploying, securing, and running Elasticsearch clusters. Plus, they'll handle version updates, regular backups, and scaling.

Grab the code from django-drf-elasticsearch repo on GitHub.

Original article source at: https://testdriven.io/

#django #rest #framework #elasticsearch