1660692840
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à 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:
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:
Cụm Elasticsearch có cấu trúc sau:
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 |
---|---|
Cluster | Cluster |
Phiên bản RDBMS | Nút |
Bàn | Mục lục |
Hàng ngang | Tà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.
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:
Ưu điểm của Elasticsearch:
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 .
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:
blog
- cho các mô hình Django, bộ tuần tự và ViewSets của chúng tôisearch
- cho các tài liệu, chỉ mục và truy vấn ElasticsearchBắ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
]
Tiếp theo, tạo Category
và lập Article
mô 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ú:
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.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
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)
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.
Bây giờ hãy cài đặt djangorestframework
bằ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.
Để 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 ModelSerializer
lớ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ú:
UserSerializer
và CategorySerializer
khá đơ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.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 UserSerializer
và CategorySerializer
.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 .
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_class
và queryset
cho mỗi ViewSet.
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:
/blog/user/
liệt kê tất cả người dùng/blog/user/<USER_ID>/
tìm nạp một người dùng cụ thể/blog/category/
liệt kê tất cả các danh mục/blog/category/<CATEGORY_ID>/
tìm nạp một danh mục cụ thể/blog/article/
liệt kê tất cả các bài báo/blog/article/<ARTICLE_ID>/
tìm nạp một bài báo cụ thể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.
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:
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ý search
và django_elasticsearch_dsl
trong 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.
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 type
củ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 Index
và Django
lớp. Trong Index
lớp, chúng ta cần cung cấp tên chỉ mục và cài đặt chỉ mục Elasticsearch . Trong Django
lớ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ú:
type
thuộc tính vào ArticleDocument
.Article
mô hình của chúng tôi có mối quan hệ nhiều-nhiều (M: N) Category
và mối quan hệ nhiều-một (N: 1) User
nê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 ObjectField
các thuộc tính.Để tạo và điền chỉ mục Elasticsearch và ánh xạ, hãy sử dụng search_index
lệ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.
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 Search
ví 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, Search
chú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 và đố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:
Bạn có thể xem tất cả các API tìm kiếm Elasticsearch tại đây.
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ú:
serializer_class
và document_class
ghi đè của chúng ta generate_q_expression()
.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')
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),
]
Ứ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:
URL | Sự 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ờ.
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:
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
1623185400
This article looks at how permissions work in Django REST Framework (DRF).
By the end of this article, you should be able to explain:
has_permission
and has_object_permission
has_permission
and has_object_permission
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
1652251528
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
1620177818
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.
#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
1652251629
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
1668836834
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.
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:
To learn more about Elasticsearch check out What is Elasticsearch? from the official documentation.
Before working with Elasticsearch, we should get familiar with the basic Elasticsearch concepts. These are listed from biggest to smallest:
The Elasticsearch cluster has the following structure:
Curious how relational database concepts relate to Elasticsearch concepts?
Relational Database | Elasticsearch |
---|---|
Cluster | Cluster |
RDBMS Instance | Node |
Table | Index |
Row | Document |
Column | Field |
Review Mapping concepts across SQL and Elasticsearch for more on how concepts in SQL and Elasticsearch relate to one another.
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:
Elasticsearch advantages:
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.
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:
blog
- for our Django models, serializers, and ViewSetssearch
- for Elasticsearch documents, indexes, and queriesStart 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
]
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:
Category
represents an article category -- i.e, programming, Linux, testing.Article
represents an individual article. Each article can have multiple categories. Articles have a specific type -- Tutorial
, Research
, Review
, or Unspecified
.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)
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.
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.
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:
UserSerializer
and CategorySerializer
are fairly simple: We just provided the fields we want serialized.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.
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.
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:
/blog/user/
lists all users/blog/user/<USER_ID>/
fetches a specific user/blog/category/
lists all categories/blog/category/<CATEGORY_ID>/
fetches a specific category/blog/article/
lists all articles/blog/article/<ARTICLE_ID>/
fetches a specific articleNow 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.
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:
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.
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:
type
attribute to the ArticleDocument
.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.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.
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:
You can see all the Elasticsearch Search APIs here.
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:
serializer_class
and document_class
and override generate_q_expression()
.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')
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),
]
Our web application is done. We can test our search endpoints by visiting the following URLs:
URL | Description |
---|---|
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.
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:
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/