searchengine/api/views.py
2026-10-03 15:31:13 +05:30

123 lines
5.1 KiB
Python

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from django.db import connection
from .typesense_client import TypesenseService
import logging
import time
logger = logging.getLogger(__name__)
class ProductSearchView(APIView):
def get(self, request):
try:
q = request.query_params.get('q', '*').strip()
category = request.query_params.get('category', '').strip()
min_price = request.query_params.get('min_price')
max_price = request.query_params.get('max_price')
is_gi_tagged = request.query_params.get('is_gi_tagged')
supplier_id = request.query_params.get('supplier_id')
sort_by_param = request.query_params.get('sort_by', 'newest')
page = int(request.query_params.get('page', 1))
per_page = int(request.query_params.get('per_page', 20))
filter_clauses = ['is_active:=true']
if category:
filter_clauses.append(f'category:="{category}"')
if supplier_id:
filter_clauses.append(f'supplier_id:={supplier_id}')
if is_gi_tagged is not None and is_gi_tagged.lower() in ('true', '1'):
filter_clauses.append('is_gi_tagged:=true')
if min_price is not None or max_price is not None:
p_min = float(min_price) if min_price else 0.0
p_max = float(max_price) if max_price else 9999999.0
filter_clauses.append(f'price:[{p_min}..{p_max}]')
filter_by_str = ' && '.join(filter_clauses)
sort_by_str = 'created_at:desc'
if sort_by_param == 'price_asc':
sort_by_str = 'price:asc'
elif sort_by_param == 'price_desc':
sort_by_str = 'price:desc'
elif sort_by_param == 'relevance' and q != '*':
sort_by_str = ''
res = TypesenseService.search_products(
query=q,
filter_by=filter_by_str,
sort_by=sort_by_str,
page=page,
per_page=per_page
)
hits = res.get('hits', [])
found = res.get('found', 0)
total_pages = (found + per_page - 1) // per_page if per_page > 0 else 1
results = [hit.get('document', {}) for hit in hits]
facet_counts = res.get('facet_counts', [])
return Response({
'count': found,
'page': page,
'per_page': per_page,
'total_pages': total_pages,
'facets': facet_counts,
'results': results
}, status=status.HTTP_200_OK)
except Exception as e:
logger.error(f"Typesense Search Error: {e}", exc_info=True)
return Response({'error': str(e), 'results': [], 'count': 0}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
class IndexProductView(APIView):
def post(self, request):
try:
product_data = request.data
if not product_data or ('id' not in product_data and 'sku' not in product_data):
return Response({'error': 'Product id or sku is required'}, status=status.HTTP_400_BAD_REQUEST)
res = TypesenseService.index_product(product_data)
return Response({'status': 'indexed', 'result': res}, status=status.HTTP_200_OK)
except Exception as e:
logger.error(f"Product Index Error: {e}", exc_info=True)
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
class DeleteProductView(APIView):
def delete(self, request, product_id):
try:
res = TypesenseService.delete_product(product_id)
return Response({'status': 'deleted', 'result': res}, status=status.HTTP_200_OK)
except Exception as e:
logger.error(f"Product Delete Error: {e}", exc_info=True)
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
class ReindexCatalogView(APIView):
def post(self, request):
try:
indexed_count = 0
TypesenseService.ensure_schema()
with connection.cursor() as cursor:
cursor.execute("""
SELECT id, sku, title, description, category, supplier_id, price,
compare_at_price, stock, image, is_gi_tagged, is_active, status
FROM api_product
""")
columns = [col[0] for col in cursor.description]
rows = cursor.fetchall()
for row in rows:
item = dict(zip(columns, row))
item['created_at'] = int(time.time())
TypesenseService.index_product(item)
indexed_count += 1
return Response({'status': 'completed', 'total_indexed': indexed_count}, status=status.HTTP_200_OK)
except Exception as e:
logger.error(f"Full Reindex Error: {e}", exc_info=True)
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)