first base of project-changed apps: Herd-livestock-tag-log-elasticsearch-

This commit is contained in:
2025-05-24 15:01:55 +03:30
parent eab40af15d
commit 90a46e493c
129 changed files with 3844 additions and 187 deletions

View File

View File

61
apps/search/api/v1/api.py Normal file
View File

@@ -0,0 +1,61 @@
from .serializers import UserRelationDocumentSerializer
from django_elasticsearch_dsl_drf.viewsets import DocumentViewSet
from rest_framework.pagination import LimitOffsetPagination
from apps.search.document.user_document import UserRelationDocument
from django.http.response import HttpResponse
from rest_framework.response import Response
from apps.authentication.models import User
from rest_framework.views import APIView
from elasticsearch_dsl.query import Q
import abc
class PaginatedElasticSearchApiView(APIView, LimitOffsetPagination):
"""Base ApiView Class for elasticsearch views with pagination,
Other ApiView classes should inherit from this class"""
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) # noqa
serializer = self.serializer_class(results, many=True)
return self.get_paginated_response(serializer.data)
except Exception as e:
return HttpResponse(e, status=500)
class SearchUserDocumentApiView(PaginatedElasticSearchApiView):
"""Base ApiView Class for elasticsearch views with pagination,
Other ApiView classes should inherit from this class"""
serializer_class = UserRelationDocumentSerializer
document_class = UserRelationDocument
def generate_q_expression(self, query):
return Q(
'bool',
should=[
Q("match", user__username=query),
Q("match", user__mobile=query),
Q("match", user__national_code=query),
Q("match", organization__type__key=query),
Q("match", organization__name=query),
Q("match", organization__city__name=query),
Q("match", organization__province__name=query),
Q("match", organization__national_unique_id=query),
Q("match", organization__company_code=query),
Q("match", role__role_name=query),
],
minimum_should_match=1,
)

View File

@@ -0,0 +1,15 @@
from django_elasticsearch_dsl_drf.serializers import DocumentSerializer
from apps.search.documents import UserRelationDocument
class UserRelationDocumentSerializer(DocumentSerializer):
"""Serializer for user relation document."""
class Meta:
document = UserRelationDocument
fields = (
'id',
'user',
'organization',
'role'
)

View File

@@ -0,0 +1,10 @@
from django.urls import path, include
from .api import SearchUserDocumentApiView
from rest_framework import routers
router = routers.DefaultRouter()
urlpatterns = [
path('', include(router.urls), name='search_user'),
path('user_elastic/<str:query>/', SearchUserDocumentApiView.as_view()),
]

View File

View File

View File

View File

View File

Binary file not shown.

View File

@@ -1,31 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIFWjCCA0KgAwIBAgIVAJzJVclRzLeDn2zXiqaZcs009WrZMA0GCSqGSIb3DQEB
CwUAMDwxOjA4BgNVBAMTMUVsYXN0aWNzZWFyY2ggc2VjdXJpdHkgYXV0by1jb25m
aWd1cmF0aW9uIEhUVFAgQ0EwHhcNMjUwNTA2MDUyODMwWhcNMjgwNTA1MDUyODMw
WjA8MTowOAYDVQQDEzFFbGFzdGljc2VhcmNoIHNlY3VyaXR5IGF1dG8tY29uZmln
dXJhdGlvbiBIVFRQIENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA
lpZfJN3SR/HhgRd6wDOEZwSanoSgI0s/y7RcLBxtH29HGmlgegX38KugErYhBOx0
CgGivcUG7CiyWPEg8CP71+pn1iQburH1zcnKovLO5pZc7p2bnnKESNsAH9j+EEza
NVUR9+tFyKaoss8QU0r1uKHFjghWR8aFRBVjPIPZs8z2GqbRzI0NBHmhxD0Tedp/
67amF7N/64ID1LVgUWyQH28DtguEjp4jEL3gbU7gEiorg42XHymu4sKieWJQczaH
sVDTmcT8HhY8wL7qf9KD6UPyqtT2NnxyODWnaO2epjbII0XvIrXH3mfPzxNQ2mQT
2V+sv34dHS6yCr+jHGU2Z6nD+5Kr2l8QRJaxW0WNHgcKXW45TLbEWDOxfJCGSQYU
zXP9LsLk2SBeAQ4OECs0jBymVSf4c4TaYtloVpdZTKMbm66VwfsJjSxSKd03HTWz
SESkM0YQ8y33SU+RDkVCkWlV3isf1/FzibfSeDbPlrRIfV0oRlzkiY4mLrytQO2Q
L7JDZtZ2+y04AgtzrAtHwfAH/KsKjUr63gzouIwfdahZudQvNYQUCSyOMLGGQ5VJ
UA8K5cCj+Z0C0R7/A/0uNsMhIRA2KftFJtfEnQok3m7kJnrrkD8xUaJ3P8FGdP9J
fIy6JXKBp1nBS+YAkou6tR7BsPTrroKDyK4pbhCsrt8CAwEAAaNTMFEwHQYDVR0O
BBYEFHEPMqbc3BiLqvXgYaxm2yqMQp1MMB8GA1UdIwQYMBaAFHEPMqbc3BiLqvXg
Yaxm2yqMQp1MMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBADB6
te9AWGobEXYfEt+0rlsTWJNVksyQs94OTfBLtjgPcDb0EzXteoxrFSUh4KblioT3
+KVGPNfgYEEH6hnPNU2ea2ZL3cVDdrSWwCYqSJAmxOKKPpISu2/HZ2xtVuEjWqSe
CJcJvb2Fh8HSSRFTwko3h8B9ie4cb3cOiHle6tM+Kc1JxxLvAurlHl0xq4wdZJqW
RJUMYs+R+gCQiT4wBZlFoUHSCOlSDPb0YxMDvISaJ4DOxGjbL2TSw6wP9LtOcUlA
Agjgsq+xzCim2vzW3h7IMdw1z/amXbyl1J3an7n8P41deB/7EePiJuNVU9zsSu68
anlQamvPawEFsucL3QfiX0kSQd1pcT1r/XYAm0jPVBx2Qc3EOoD4wlkz5ATpE1ts
vWCgfvQsuhUoL6cD9WFzAiWXXZ3qRDcY5L7zs0c/geUybRB/gWgMh/ROypfUoxfT
F0Cy/cfm/cBpbdy7frN9XWAigh/TDnCdwnOhfcvwr3AMxVR7X7NIVwBvTOPGIfwh
FLoPiaPrGkPntItdZWqGUUpkPrIzOpSZWurJ40pPCc0uBVUudYMH686MiyB5wZSS
/sGJIjfeivKVvH77kKCXld3Z35/E47msUvtl/DuUVtXQb12tQ4boyQJ6O6JVyK2n
1cRq9qLHcpfoG81BzrGIJvTWOBXTs02lOCRObF7z
-----END CERTIFICATE-----

Binary file not shown.

View File

@@ -0,0 +1,154 @@
from django_elasticsearch_dsl_drf.compat import StringField, KeywordField
from elasticsearch_dsl import analyzer
from apps.authorization.models import UserRelations, Role
from apps.authentication.models import User, Organization
from django_elasticsearch_dsl.registries import registry
from django_elasticsearch_dsl import Document, fields
html_strip = analyzer(
'html_strip',
tokenizer="standard",
filter=["lowercase", "stop", "snowball"],
char_filter=["html_strip"]
)
@registry.register_document
class UserRelationDocument(Document):
"""Address Elasticsearch document."""
# In different parts of the code different fields are used. There are
# a couple of use cases: (1) more-like-this functionality, where `title`,
# `description` and `summary` fields are used, (2) search and filtering
# functionality where all the fields are used.
user = fields.ObjectField(properties={
'username': StringField(
analyzer=html_strip,
fields={
'raw': KeywordField(),
'suggest': fields.CompletionField()
}
),
'mobile': StringField(
analyzer=html_strip,
fields={
'raw': KeywordField(),
'suggest': KeywordField()
}
),
'national_code': StringField(
analyzer=html_strip,
fields={
'raw': KeywordField(),
'suggest': fields.CompletionField()
}
),
'first_name': StringField(
analyzer=html_strip,
fields={
'raw': KeywordField(),
'suggest': fields.CompletionField()
}
),
'last_name': StringField(
analyzer=html_strip,
fields={
'raw': KeywordField(),
'suggest': fields.CompletionField()
}
),
})
organization = fields.ObjectField(properties={
'name': StringField(
analyzer=html_strip,
fields={
'raw': KeywordField(),
'suggest': fields.CompletionField()
}
),
'type': fields.ObjectField(properties={
'key': StringField(
analyzer=html_strip,
fields={
'raw': KeywordField(),
'suggest': fields.CompletionField()
}
)
}),
'national_unique_id': StringField(
analyzer=html_strip,
fields={
'raw': KeywordField(),
'suggest': fields.CompletionField()
}
),
'field_of_activity': StringField(
analyzer=html_strip,
fields={
'raw': KeywordField(),
'suggest': fields.CompletionField()
}
),
'company_code': StringField(
analyzer=html_strip,
fields={
'raw': KeywordField(),
'suggest': fields.CompletionField()
}
),
'province': fields.ObjectField(properties={
'name': StringField(
analyzer=html_strip,
fields={
'raw': KeywordField(),
'suggest': fields.CompletionField()
}
),
}),
'city': fields.ObjectField(properties={
'name': StringField(
analyzer=html_strip,
fields={
'raw': KeywordField(),
'suggest': fields.CompletionField()
}
),
}),
'parent_organization': fields.ObjectField(properties={
'name': StringField(
analyzer=html_strip,
fields={
'raw': KeywordField(),
'suggest': fields.CompletionField()
}
),
'unique_national_id': StringField(
analyzer=html_strip,
fields={
'raw': KeywordField(),
'suggest': fields.CompletionField()
}
),
})
})
role = fields.ObjectField(properties={
'role_name': StringField(
analyzer=html_strip,
fields={
'raw': KeywordField(),
'suggest': fields.CompletionField()
}
)
})
class Index:
name = 'userrelations' # noqa
settings = {
'number_of_shards': 1,
'number_of_replicas': 1 # number of copies from data in document
}
class Django:
model = UserRelations
relates_models = [User, Organization, Role]

1
apps/search/documents.py Normal file
View File

@@ -0,0 +1 @@
from apps.search.document.user_document import UserRelationDocument

View File

View File

View File

@@ -0,0 +1 @@
# Your custom management commands go here.

71
apps/search/signals.py Normal file
View File

@@ -0,0 +1,71 @@
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from django_elasticsearch_dsl.registries import registry
@receiver(post_save)
def update_document(sender, **kwargs):
"""Update document on added/changed records.
Update Book document index if related `books.Publisher` (`publisher`),
`books.Author` (`authors`), `books.Tag` (`tags`) fields have been updated
in the database.
"""
app_label = sender._meta.app_label
model_name = sender._meta.model_name
instance = kwargs['instance']
if app_label == 'book':
# If it is `books.Publisher` that is being updated.
if model_name == 'publisher':
instances = instance.books.all()
for _instance in instances:
registry.update(_instance)
# If it is `books.Author` that is being updated.
if model_name == 'author':
instances = instance.books.all()
for _instance in instances:
registry.update(_instance)
# If it is `books.Tag` that is being updated.
if model_name == 'tag':
instances = instance.books.all()
for _instance in instances:
registry.update(_instance)
@receiver(post_delete)
def delete_document(sender, **kwargs):
"""Update document on deleted records.
Updates Book document from index if related `books.Publisher`
(`publisher`), `books.Author` (`authors`), `books.Tag` (`tags`) fields
have been removed from database.
"""
app_label = sender._meta.app_label
model_name = sender._meta.model_name
instance = kwargs['instance']
if app_label == 'books':
# If it is `books.Publisher` that is being updated.
if model_name == 'publisher':
instances = instance.books.all()
for _instance in instances:
registry.update(_instance)
# registry.delete(_instance, raise_on_error=False)
# If it is `books.Author` that is being updated.
if model_name == 'author':
instances = instance.books.all()
for _instance in instances:
registry.update(_instance)
# registry.delete(_instance, raise_on_error=False)
# If it is `books.Tag` that is being updated.
if model_name == 'tag':
instances = instance.books.all()
for _instance in instances:
registry.update(_instance)
# registry.delete(_instance, raise_on_error=False)

5
apps/search/urls.py Normal file
View File

@@ -0,0 +1,5 @@
from django.urls import path, include
urlpatterns = [
path('api/v1/', include('apps.search.api.v1.urls'))
]