mirror of
https://github.com/Alexander-D-Karpov/akarpov
synced 2024-12-01 06:23:42 +03:00
Compare commits
2 Commits
4a407dfc06
...
3adeb7c5a7
Author | SHA1 | Date | |
---|---|---|---|
3adeb7c5a7 | |||
af5f1f8afc |
|
@ -1,4 +1,4 @@
|
||||||
from django_elasticsearch_dsl import Document
|
from django_elasticsearch_dsl import Document, fields
|
||||||
from django_elasticsearch_dsl.registries import registry
|
from django_elasticsearch_dsl.registries import registry
|
||||||
|
|
||||||
from akarpov.files.models import File
|
from akarpov.files.models import File
|
||||||
|
@ -6,26 +6,41 @@
|
||||||
|
|
||||||
@registry.register_document
|
@registry.register_document
|
||||||
class FileDocument(Document):
|
class FileDocument(Document):
|
||||||
class Index:
|
name = fields.TextField(
|
||||||
name = "files"
|
attr="name",
|
||||||
settings = {"number_of_shards": 1, "number_of_replicas": 0}
|
fields={
|
||||||
|
"raw": fields.KeywordField(normalizer="lowercase"),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
description = fields.TextField(
|
||||||
|
attr="description",
|
||||||
|
fields={
|
||||||
|
"raw": fields.KeywordField(normalizer="lowercase"),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
content = fields.TextField(
|
||||||
|
attr="content",
|
||||||
|
fields={
|
||||||
|
"raw": fields.KeywordField(normalizer="lowercase"),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
class Django:
|
class Django:
|
||||||
model = File
|
model = File
|
||||||
fields = [
|
|
||||||
"name",
|
|
||||||
"description",
|
|
||||||
"content",
|
|
||||||
]
|
|
||||||
|
|
||||||
def prepare_description(self, instance):
|
def prepare_description(self, instance):
|
||||||
# This method is called for every instance before indexing
|
|
||||||
return instance.description or ""
|
return instance.description or ""
|
||||||
|
|
||||||
def prepare_content(self, instance):
|
def prepare_content(self, instance):
|
||||||
# This method is called for every instance before indexing
|
# check instance.content is not None
|
||||||
return (
|
return (
|
||||||
instance.content.decode("utf-8")
|
instance.content.decode("utf-8")
|
||||||
if isinstance(instance.content, bytes)
|
if instance.content and isinstance(instance.content, bytes)
|
||||||
else instance.content
|
else ""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class Index:
|
||||||
|
name = "files"
|
||||||
|
settings = {"number_of_shards": 1, "number_of_replicas": 0}
|
||||||
|
|
|
@ -40,13 +40,16 @@ def search(self, query: str):
|
||||||
ES_Q(
|
ES_Q(
|
||||||
"multi_match",
|
"multi_match",
|
||||||
query=query,
|
query=query,
|
||||||
fields=["name", "description", "content"],
|
fields=["name^3", "description^2", "content"],
|
||||||
type="best_fields",
|
type="best_fields",
|
||||||
|
fuzziness="AUTO",
|
||||||
),
|
),
|
||||||
ES_Q("match_phrase_prefix", name=query),
|
ES_Q("wildcard", name__raw=f"*{query.lower()}*"),
|
||||||
ES_Q("wildcard", name=f"*{query}*"),
|
ES_Q("wildcard", description__raw=f"*{query.lower()}*"),
|
||||||
ES_Q("wildcard", description=f"*{query}*"),
|
ES_Q("wildcard", content__raw=f"*{query.lower()}*"),
|
||||||
ES_Q("wildcard", content=f"*{query}*"),
|
ES_Q("wildcard", file_type__raw=f"*{query.lower()}*"),
|
||||||
|
ES_Q("wildcard", file_obj__raw=f"*{query.lower()}*"),
|
||||||
|
ES_Q("wildcard", preview__raw=f"*{query.lower()}*"),
|
||||||
],
|
],
|
||||||
minimum_should_match=1,
|
minimum_should_match=1,
|
||||||
)
|
)
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
SongUserRating,
|
SongUserRating,
|
||||||
UserListenHistory,
|
UserListenHistory,
|
||||||
)
|
)
|
||||||
|
from akarpov.music.services.search import search_song
|
||||||
from akarpov.music.tasks import listen_to_song
|
from akarpov.music.tasks import listen_to_song
|
||||||
|
|
||||||
|
|
||||||
|
@ -83,6 +84,10 @@ class ListCreateSongAPIView(LikedSongsContextMixin, generics.ListCreateAPIView):
|
||||||
pagination_class = StandardResultsSetPagination
|
pagination_class = StandardResultsSetPagination
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
search = self.request.query_params.get("search", None)
|
||||||
|
if search:
|
||||||
|
qs = search_song(search)
|
||||||
|
else:
|
||||||
qs = Song.objects.cache()
|
qs = Song.objects.cache()
|
||||||
|
|
||||||
if "sort" in self.request.query_params:
|
if "sort" in self.request.query_params:
|
||||||
|
@ -111,6 +116,12 @@ def get_queryset(self):
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
parameters=[
|
parameters=[
|
||||||
|
OpenApiParameter(
|
||||||
|
name="search",
|
||||||
|
description="Search query",
|
||||||
|
required=False,
|
||||||
|
type=str,
|
||||||
|
),
|
||||||
OpenApiParameter(
|
OpenApiParameter(
|
||||||
name="sort",
|
name="sort",
|
||||||
description="Sorting algorithm",
|
description="Sorting algorithm",
|
||||||
|
|
67
akarpov/music/documents.py
Normal file
67
akarpov/music/documents.py
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
from django_elasticsearch_dsl import Document, fields
|
||||||
|
from django_elasticsearch_dsl.registries import registry
|
||||||
|
|
||||||
|
from akarpov.music.models import Song
|
||||||
|
|
||||||
|
|
||||||
|
@registry.register_document
|
||||||
|
class SongDocument(Document):
|
||||||
|
authors = fields.NestedField(
|
||||||
|
attr="authors",
|
||||||
|
properties={
|
||||||
|
"name": fields.TextField(
|
||||||
|
fields={
|
||||||
|
"raw": fields.KeywordField(normalizer="lowercase"),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
"link": fields.TextField(),
|
||||||
|
"meta": fields.ObjectField(dynamic=True),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
album = fields.NestedField(
|
||||||
|
attr="album",
|
||||||
|
properties={
|
||||||
|
"name": fields.TextField(
|
||||||
|
fields={
|
||||||
|
"raw": fields.KeywordField(normalizer="lowercase"),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
"link": fields.TextField(),
|
||||||
|
"meta": fields.ObjectField(dynamic=True),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
name = fields.TextField(
|
||||||
|
attr="name",
|
||||||
|
fields={
|
||||||
|
"raw": fields.KeywordField(normalizer="lowercase"),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
meta = fields.ObjectField(dynamic=True) # Added meta field here as dynamic object
|
||||||
|
|
||||||
|
class Index:
|
||||||
|
name = "songs"
|
||||||
|
settings = {"number_of_shards": 1, "number_of_replicas": 0}
|
||||||
|
# settings = {
|
||||||
|
# "number_of_shards": 1,
|
||||||
|
# "number_of_replicas": 0,
|
||||||
|
# "analysis": {
|
||||||
|
# "analyzer": {
|
||||||
|
# "russian_icu": {
|
||||||
|
# "type": "custom",
|
||||||
|
# "tokenizer": "icu_tokenizer",
|
||||||
|
# "filter": ["icu_folding","icu_normalizer"]
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# } TODO
|
||||||
|
|
||||||
|
class Django:
|
||||||
|
model = Song
|
||||||
|
|
||||||
|
def get_instances_from_related(self, related_instance):
|
||||||
|
if isinstance(related_instance, Song):
|
||||||
|
return related_instance.album
|
||||||
|
return related_instance.songs.all()
|
47
akarpov/music/services/search.py
Normal file
47
akarpov/music/services/search.py
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
from django.db.models import Case, When
|
||||||
|
from elasticsearch_dsl import Q as ES_Q
|
||||||
|
|
||||||
|
from akarpov.music.documents import SongDocument
|
||||||
|
from akarpov.music.models import Song
|
||||||
|
|
||||||
|
|
||||||
|
def search_song(query):
|
||||||
|
search = SongDocument.search()
|
||||||
|
search_query = ES_Q(
|
||||||
|
"bool",
|
||||||
|
should=[
|
||||||
|
ES_Q(
|
||||||
|
"multi_match",
|
||||||
|
query=query,
|
||||||
|
fields=["name^3", "authors.name^2", "album.name"],
|
||||||
|
fuzziness="AUTO",
|
||||||
|
), # Change here
|
||||||
|
ES_Q("wildcard", name__raw=f"*{query.lower()}*"),
|
||||||
|
ES_Q(
|
||||||
|
"nested",
|
||||||
|
path="authors",
|
||||||
|
query=ES_Q("wildcard", authors__name__raw=f"*{query.lower()}*"),
|
||||||
|
),
|
||||||
|
ES_Q(
|
||||||
|
"nested",
|
||||||
|
path="album",
|
||||||
|
query=ES_Q("wildcard", album__name__raw=f"*{query.lower()}*"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
minimum_should_match=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
search = search.query(search_query)
|
||||||
|
|
||||||
|
response = search.execute()
|
||||||
|
|
||||||
|
# Check for hits and get song instances
|
||||||
|
if response.hits:
|
||||||
|
hit_ids = [hit.meta.id for hit in response.hits]
|
||||||
|
songs = Song.objects.filter(id__in=hit_ids).order_by(
|
||||||
|
Case(*[When(pk=pk, then=pos) for pos, pk in enumerate(hit_ids)])
|
||||||
|
)
|
||||||
|
|
||||||
|
return songs
|
||||||
|
|
||||||
|
return Song.objects.none()
|
|
@ -48,6 +48,14 @@ COPY ./compose/production/django/entrypoint /entrypoint
|
||||||
RUN sed -i 's/\r$//g' /entrypoint
|
RUN sed -i 's/\r$//g' /entrypoint
|
||||||
RUN chmod +x /entrypoint
|
RUN chmod +x /entrypoint
|
||||||
|
|
||||||
|
COPY ./compose/production/django/manage /manage
|
||||||
|
RUN sed -i 's/\r$//g' /manage
|
||||||
|
RUN chmod +x /manage
|
||||||
|
|
||||||
|
COPY ./compose/production/django/manage /manage.py
|
||||||
|
RUN sed -i 's/\r$//g' /manage
|
||||||
|
RUN chmod +x /manage
|
||||||
|
|
||||||
COPY ./compose/local/django/start /start
|
COPY ./compose/local/django/start /start
|
||||||
RUN sed -i 's/\r$//g' /start
|
RUN sed -i 's/\r$//g' /start
|
||||||
RUN chmod +x /start
|
RUN chmod +x /start
|
||||||
|
|
14
compose/production/django/manage
Normal file
14
compose/production/django/manage
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -o errexit
|
||||||
|
set -o pipefail
|
||||||
|
set -o nounset
|
||||||
|
|
||||||
|
if [ -z "${POSTGRES_USER}" ]; then
|
||||||
|
base_postgres_image_default_user='postgres'
|
||||||
|
export POSTGRES_USER="${base_postgres_image_default_user}"
|
||||||
|
fi
|
||||||
|
export DATABASE_URL="postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}"
|
||||||
|
|
||||||
|
|
||||||
|
exec /venv/bin/python /app/manage.py "$@"
|
Loading…
Reference in New Issue
Block a user