Merge pull request #83 from plers-org/sm/#71
✨ Feat: [#71] atlas search를 활용한 검색 기능 구현 완료
This commit is contained in:
@@ -12,6 +12,7 @@ urlpatterns = [
|
|||||||
path('api/project/', include('projects.urls')),
|
path('api/project/', include('projects.urls')),
|
||||||
path('api/notification/', include('notifications.urls')),
|
path('api/notification/', include('notifications.urls')),
|
||||||
path('api/nocodetool/', include('nocodetools.urls')),
|
path('api/nocodetool/', include('nocodetools.urls')),
|
||||||
|
path('api/home/', include('homes.urls')),
|
||||||
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||||
|
|
||||||
# if settings.DEBUG:
|
# if settings.DEBUG:
|
||||||
|
|||||||
0
homes/__init__.py
Normal file
0
homes/__init__.py
Normal file
3
homes/admin.py
Normal file
3
homes/admin.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
6
homes/apps.py
Normal file
6
homes/apps.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class HomesConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'homes'
|
||||||
0
homes/filters.py
Normal file
0
homes/filters.py
Normal file
0
homes/models.py
Normal file
0
homes/models.py
Normal file
1
homes/serializers.py
Normal file
1
homes/serializers.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# 포폴, 플젝 주간 랭킹, 광고 등
|
||||||
112
homes/services.py
Normal file
112
homes/services.py
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
from django.conf import settings
|
||||||
|
import certifi
|
||||||
|
|
||||||
|
from pymongo import MongoClient
|
||||||
|
|
||||||
|
from django.db.models import Case, When, IntegerField
|
||||||
|
|
||||||
|
from projects.models import Project
|
||||||
|
from portfolios.models import Portfolio
|
||||||
|
|
||||||
|
NOCODETOOL_MODEL_MAP = {
|
||||||
|
'project': Project,
|
||||||
|
'portfolio': Portfolio,
|
||||||
|
}
|
||||||
|
|
||||||
|
class NocodetoolSearchService:
|
||||||
|
@staticmethod
|
||||||
|
def search(search_term, object_type, page, page_size):
|
||||||
|
client = MongoClient(settings.MONGODB_URI, tlsCAFile=certifi.where())
|
||||||
|
db = client[settings.MONGODB_NAME]
|
||||||
|
coll = db["code"]
|
||||||
|
|
||||||
|
filters = []
|
||||||
|
if object_type in ("project", "portfolio"):
|
||||||
|
filters.append({"term": {"path": "object_type", "query": object_type}})
|
||||||
|
|
||||||
|
pipeline = [
|
||||||
|
{
|
||||||
|
"$search": {
|
||||||
|
"index": "nocodetool_content_keyword_search_index",
|
||||||
|
"compound": {
|
||||||
|
"filter": filters,
|
||||||
|
"should": [
|
||||||
|
{
|
||||||
|
"text": {
|
||||||
|
"query": search_term,
|
||||||
|
"path": "keyword",
|
||||||
|
"score": {"boost": {"value": 5}}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": {
|
||||||
|
"query": search_term,
|
||||||
|
"path": "pages.elements.content",
|
||||||
|
"score": {"boost": {"value": 10}}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": {
|
||||||
|
"query": search_term,
|
||||||
|
"path": "description",
|
||||||
|
"score": {"boost": {"value": 7}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"minimumShouldMatch": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$project": {
|
||||||
|
"keyword": 1,
|
||||||
|
"description": 1,
|
||||||
|
"pages.elements.content": 1,
|
||||||
|
"object_type": 1,
|
||||||
|
"object_id": 1,
|
||||||
|
"score": {"$meta": "searchScore"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$facet": {
|
||||||
|
"projects": [
|
||||||
|
{"$match": {"object_type": "project"}},
|
||||||
|
{"$sort": {"score": -1}},
|
||||||
|
{"$skip": (page - 1) * page_size},
|
||||||
|
{"$limit": page_size}
|
||||||
|
],
|
||||||
|
"portfolios": [
|
||||||
|
{"$match": {"object_type": "portfolio"}},
|
||||||
|
{"$sort": {"score": -1}},
|
||||||
|
{"$skip": (page - 1) * page_size},
|
||||||
|
{"$limit": page_size}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
agg_result = list(coll.aggregate(pipeline))
|
||||||
|
print(agg_result)
|
||||||
|
buckets = agg_result[0] if agg_result else {"projects": [], "portfolios": []}
|
||||||
|
return buckets
|
||||||
|
|
||||||
|
def get_ids_from_buckets(buckets: list) -> list:
|
||||||
|
return [object["object_id"] for object in buckets]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class NocodetoolToObjectService:
|
||||||
|
def get_objects_by_ids(object_type, objects_ids: list) -> list:
|
||||||
|
if not objects_ids:
|
||||||
|
return []
|
||||||
|
|
||||||
|
order = Case(
|
||||||
|
*[When(id=pk, then=pos) for pos, pk in enumerate(objects_ids)],
|
||||||
|
output_field=IntegerField()
|
||||||
|
)
|
||||||
|
|
||||||
|
object_model = NOCODETOOL_MODEL_MAP.get(object_type)
|
||||||
|
if not object_model:
|
||||||
|
return []
|
||||||
|
|
||||||
|
return list(object_model.objects.filter(id__in=objects_ids).order_by(order))
|
||||||
3
homes/tests.py
Normal file
3
homes/tests.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
9
homes/urls.py
Normal file
9
homes/urls.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from django.urls import include, path
|
||||||
|
|
||||||
|
from .views import *
|
||||||
|
|
||||||
|
app_name = 'homes'
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('search/', SearchAPIView.as_view()),
|
||||||
|
]
|
||||||
53
homes/views.py
Normal file
53
homes/views.py
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
from rest_framework.views import APIView
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework import status, mixins, viewsets
|
||||||
|
from rest_framework.permissions import AllowAny, IsAuthenticated
|
||||||
|
|
||||||
|
from django.db import transaction
|
||||||
|
|
||||||
|
from .serializers import *
|
||||||
|
from .services import *
|
||||||
|
|
||||||
|
from projects.models import Project
|
||||||
|
from projects.serializers import ProjectListViewSerializer
|
||||||
|
|
||||||
|
from portfolios.models import Portfolio
|
||||||
|
from portfolios.serializers import PortfolioListViewSerializer
|
||||||
|
|
||||||
|
from users.models import User
|
||||||
|
|
||||||
|
class HomeAPIView(APIView):
|
||||||
|
def get(self, request):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class SearchAPIView(APIView):
|
||||||
|
def get(self, request):
|
||||||
|
search_team = request.query_params.get('q', '')
|
||||||
|
object_type = request.query_params.get('type', None)
|
||||||
|
try:
|
||||||
|
page = int(request.query_params.get("page", 1))
|
||||||
|
except ValueError:
|
||||||
|
return Response({"is_page_int": False,"detail": "page and page_size must be integers"}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
if page < 1:
|
||||||
|
return Response({"is_page_gte_1": False, "detail": "page and page_size must be >= 1"}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
page_size = 8
|
||||||
|
|
||||||
|
buckets = NocodetoolSearchService.search(search_team, object_type, page, page_size)
|
||||||
|
project_list = NocodetoolSearchService.get_ids_from_buckets(buckets["projects"])
|
||||||
|
portfolio_list = NocodetoolSearchService.get_ids_from_buckets(buckets["portfolios"])
|
||||||
|
|
||||||
|
projects_data = ProjectListViewSerializer(
|
||||||
|
NocodetoolToObjectService.get_objects_by_ids('project', project_list),
|
||||||
|
many=True).data
|
||||||
|
portfolios_data = PortfolioListViewSerializer(
|
||||||
|
NocodetoolToObjectService.get_objects_by_ids('portfolio', portfolio_list),
|
||||||
|
many=True).data
|
||||||
|
|
||||||
|
return Response({
|
||||||
|
"projects": projects_data,
|
||||||
|
"portfolios": portfolios_data,
|
||||||
|
"page": page,
|
||||||
|
"page_size": page_size
|
||||||
|
}, status=status.HTTP_200_OK)
|
||||||
Reference in New Issue
Block a user