| import logging
|
| import secrets
|
| from django.conf import settings
|
| from django.core.exceptions import ValidationError
|
| from rest_framework import status
|
| from rest_framework.decorators import api_view, authentication_classes, permission_classes
|
| from rest_framework.permissions import IsAuthenticated, AllowAny
|
| from rest_framework.response import Response
|
| from rest_framework_simplejwt.authentication import JWTAuthentication
|
|
|
| from ai_chatbot.serializers import EnableAutoChatSerializer, PropertyCustomQASerializer
|
|
|
| from .models import MasterQuestion, PropertyCustomQA, PropertyQA, AgencyAutoChatSetting, PropertyAutoChatState, GlobalQA
|
| from .services import EmbeddingService
|
|
|
| logger = logging.getLogger(__name__)
|
| embedding_service = EmbeddingService()
|
|
|
|
|
|
|
| from rest_framework import serializers
|
|
|
| class QAAnswerSerializer(serializers.Serializer):
|
| master_question_id = serializers.UUIDField(required=True)
|
| answer = serializers.CharField(max_length=5000, required=True, allow_blank=False)
|
|
|
| class BulkQASerializer(serializers.Serializer):
|
| property_id = serializers.UUIDField(required=True)
|
| answers = QAAnswerSerializer(many=True, required=True)
|
|
|
|
|
|
|
| class WebhookSecretAuthentication:
|
| """Custom authentication for webhook endpoint"""
|
|
|
| def authenticate(self, request):
|
| secret = request.headers.get('X-Webhook-Secret')
|
| expected_secret = getattr(settings, 'AI_WEBHOOK_SECRET', None)
|
|
|
| if not expected_secret:
|
| logger.warning("AI_WEBHOOK_SECRET not set in settings")
|
| return None
|
|
|
| if secret and secrets.compare_digest(secret, expected_secret):
|
| return (None, None)
|
| return None
|
|
|
| def authenticate_header(self, request):
|
| return 'X-Webhook-Secret'
|
|
|
|
|
| @api_view(['GET'])
|
| @authentication_classes([JWTAuthentication])
|
| @permission_classes([IsAuthenticated])
|
| def get_master_questions(request):
|
| """Get all master questions for agency to answer"""
|
| questions = MasterQuestion.objects.filter(is_active=True).order_by('order')
|
| data = [{'id': str(q.id), 'question': q.question, 'order': q.order} for q in questions]
|
| return Response({'questions': data})
|
|
|
|
|
| @api_view(['GET'])
|
| @authentication_classes([JWTAuthentication])
|
| @permission_classes([IsAuthenticated])
|
| def get_property_qa_status(request, property_id):
|
| """Get Q&A status for a property"""
|
| user = request.user
|
|
|
| master_questions = MasterQuestion.objects.filter(is_active=True).order_by('order')
|
|
|
|
|
| existing_qa = {
|
| str(qa.master_question_id): {
|
| 'answer': qa.answer,
|
| 'qa_id': str(qa.id),
|
| 'updated_at': qa.updated_at
|
| }
|
| for qa in PropertyQA.objects.filter(
|
| property_id=property_id,
|
| agency=user
|
| ).select_related('master_question')
|
| }
|
|
|
| questions_data = []
|
| for mq in master_questions:
|
| mq_id = str(mq.id)
|
| questions_data.append({
|
| 'id': mq_id,
|
| 'question': mq.question,
|
| 'order': mq.order,
|
| 'is_answered': mq_id in existing_qa,
|
| 'answer': existing_qa[mq_id]['answer'] if mq_id in existing_qa else None,
|
| 'updated_at': existing_qa[mq_id]['updated_at'] if mq_id in existing_qa else None,
|
| })
|
|
|
|
|
| try:
|
| auto_chat_setting = AgencyAutoChatSetting.objects.get(agency=user)
|
| auto_chat_enabled = auto_chat_setting.is_enabled
|
| delay_seconds = auto_chat_setting.delay_seconds
|
| confidence_threshold = auto_chat_setting.confidence_threshold
|
| except AgencyAutoChatSetting.DoesNotExist:
|
| auto_chat_enabled = False
|
| delay_seconds = 30
|
| confidence_threshold = 0.6
|
|
|
| try:
|
| property_auto_state = PropertyAutoChatState.objects.get(property_id=property_id)
|
| property_auto_enabled = property_auto_state.is_auto_chat_enabled
|
| except PropertyAutoChatState.DoesNotExist:
|
| property_auto_enabled = False
|
|
|
| answered_count = sum(1 for q in questions_data if q['is_answered'])
|
| total_questions = len(questions_data)
|
|
|
| return Response({
|
| 'property_id': property_id,
|
| 'master_questions': questions_data,
|
| 'stats': {
|
| 'total_questions': total_questions,
|
| 'answered_count': answered_count,
|
| 'completion_percentage': round((answered_count / total_questions) * 100, 1) if total_questions > 0 else 0,
|
| 'remaining_count': total_questions - answered_count
|
| },
|
| 'auto_chat_settings': {
|
| 'is_enabled': auto_chat_enabled,
|
| 'property_auto_enabled': property_auto_enabled,
|
| 'delay_seconds': delay_seconds,
|
| 'confidence_threshold': confidence_threshold
|
| }
|
| })
|
|
|
|
|
| @api_view(['POST'])
|
| @authentication_classes([JWTAuthentication])
|
| @permission_classes([IsAuthenticated])
|
| def save_property_qa_bulk(request):
|
| """
|
| FIX #5 & #9: Optimized bulk save with proper auto-enable logic
|
| """
|
| user = request.user
|
|
|
|
|
| if not hasattr(user, 'role') or getattr(user.role, 'role_type', None) != 'agency':
|
| return Response({'error': 'Only agencies can add Q&A'}, status=status.HTTP_403_FORBIDDEN)
|
|
|
|
|
| serializer = BulkQASerializer(data=request.data)
|
| if not serializer.is_valid():
|
| return Response({'error': 'Invalid data', 'details': serializer.errors},
|
| status=status.HTTP_400_BAD_REQUEST)
|
|
|
| validated_data = serializer.validated_data
|
| property_id = validated_data['property_id']
|
| answers = validated_data['answers']
|
|
|
| if not answers:
|
| return Response({'error': 'No answers provided'}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
| existing_qa = {
|
| str(qa.master_question_id): qa
|
| for qa in PropertyQA.objects.filter(
|
| property_id=property_id,
|
| agency=user
|
| ).select_related('master_question')
|
| }
|
|
|
|
|
| answered_master_ids = set()
|
| created_count = 0
|
| updated_count = 0
|
| errors = []
|
|
|
| for item in answers:
|
| master_q_id = str(item['master_question_id'])
|
| answer = item['answer'].strip()
|
|
|
| if not answer:
|
| continue
|
|
|
| try:
|
| master_q = MasterQuestion.objects.get(id=master_q_id, is_active=True)
|
| answered_master_ids.add(master_q_id)
|
|
|
|
|
| embedding = embedding_service.generate_embedding(master_q.question)
|
|
|
|
|
| if master_q_id in existing_qa:
|
|
|
| qa = existing_qa[master_q_id]
|
| qa.answer = answer
|
| qa.question_embedding = embedding
|
| qa.save(update_fields=['answer', 'question_embedding', 'updated_at'])
|
| updated_count += 1
|
| else:
|
|
|
| PropertyQA.objects.create(
|
| agency=user,
|
| property_id=property_id,
|
| master_question=master_q,
|
| answer=answer,
|
| question_embedding=embedding
|
| )
|
| created_count += 1
|
|
|
| except MasterQuestion.DoesNotExist:
|
| errors.append(f'Master question not found: {master_q_id}')
|
| except Exception as e:
|
| errors.append(f'Error for question {master_q_id}: {str(e)}')
|
|
|
|
|
| to_delete_ids = set(existing_qa.keys()) - answered_master_ids
|
| if to_delete_ids:
|
| deleted_count = PropertyQA.objects.filter(
|
| property_id=property_id,
|
| master_question_id__in=to_delete_ids
|
| ).delete()[0]
|
| logger.info(f"Deleted {deleted_count} removed answers for property {property_id}")
|
|
|
|
|
| total_active_questions = MasterQuestion.objects.filter(is_active=True).count()
|
| total_answered = PropertyQA.objects.filter(
|
| property_id=property_id,
|
| master_question__is_active=True
|
| ).count()
|
|
|
| auto_chat_enabled = False
|
| if total_answered >= total_active_questions and total_active_questions > 0:
|
| property_state, created = PropertyAutoChatState.objects.update_or_create(
|
| property_id=property_id,
|
| defaults={'is_auto_chat_enabled': True}
|
| )
|
| auto_chat_enabled = True
|
| logger.info(f"✅ Auto-chat enabled for property {property_id} ({total_answered}/{total_active_questions} questions answered)")
|
|
|
| return Response({
|
| 'message': f'Saved answers for property',
|
| 'created': created_count,
|
| 'updated': updated_count,
|
| 'total': created_count + updated_count,
|
| 'auto_chat_enabled': auto_chat_enabled,
|
| 'completion': {
|
| 'answered': total_answered,
|
| 'total': total_active_questions,
|
| 'percentage': round((total_answered / total_active_questions) * 100, 1) if total_active_questions > 0 else 0
|
| },
|
| 'errors': errors if errors else None
|
| }, status=status.HTTP_200_OK if (created_count + updated_count) > 0 else status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
| @api_view(['POST'])
|
| @authentication_classes([JWTAuthentication])
|
| @permission_classes([IsAuthenticated])
|
| def toggle_auto_chat(request, property_id):
|
| """Enable/disable auto-chat for a specific property"""
|
| user = request.user
|
|
|
| if not hasattr(user, 'role') or getattr(user.role, 'role_type', None) != 'agency':
|
| return Response({'error': 'Only agencies can configure auto-chat'}, status=status.HTTP_403_FORBIDDEN)
|
|
|
| is_enabled = request.data.get('is_enabled', False)
|
|
|
|
|
| if is_enabled:
|
| total_required = MasterQuestion.objects.filter(is_active=True).count()
|
| total_answered = PropertyQA.objects.filter(
|
| property_id=property_id,
|
| agency=user,
|
| master_question__is_active=True
|
| ).count()
|
|
|
| if total_answered < total_required:
|
| return Response({
|
| 'error': f'Please answer all {total_required} active questions first. Currently answered: {total_answered}/{total_required}'
|
| }, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
| property_state, created = PropertyAutoChatState.objects.get_or_create(
|
| property_id=property_id,
|
| defaults={'is_auto_chat_enabled': is_enabled}
|
| )
|
|
|
| if not created:
|
| property_state.is_auto_chat_enabled = is_enabled
|
| property_state.save()
|
|
|
|
|
| agency_setting, _ = AgencyAutoChatSetting.objects.get_or_create(
|
| agency=user,
|
| defaults={
|
| 'is_enabled': is_enabled,
|
| 'delay_seconds': 30,
|
| 'confidence_threshold': 0.6
|
| }
|
| )
|
|
|
| if not agency_setting.is_enabled and is_enabled:
|
| agency_setting.is_enabled = True
|
| agency_setting.save()
|
|
|
| return Response({
|
| 'property_id': property_id,
|
| 'is_enabled': property_state.is_auto_chat_enabled,
|
| 'message': f'Auto-chat {"enabled" if is_enabled else "disabled"} for this property'
|
| })
|
|
|
|
|
| @api_view(['POST'])
|
| @authentication_classes([WebhookSecretAuthentication])
|
| @permission_classes([AllowAny])
|
| def webhook_auto_reply(request):
|
| """
|
| FIX #3: Secured webhook endpoint for Chat app to trigger auto-reply
|
| """
|
| from .services import AutoChatService
|
|
|
|
|
| required_fields = ['chat_id', 'property_id', 'message']
|
| for field in required_fields:
|
| if field not in request.data:
|
| return Response({'error': f'Missing required field: {field}'},
|
| status=status.HTTP_400_BAD_REQUEST)
|
|
|
| data = request.data
|
| chat_id = data.get('chat_id')
|
| property_id = data.get('property_id')
|
| client_message = data.get('message')
|
| last_agency_reply_at = data.get('last_agency_reply_at')
|
| last_client_message_at = data.get('last_client_message_at')
|
|
|
| auto_chat_service = AutoChatService()
|
|
|
|
|
| if auto_chat_service.should_auto_reply(chat_id, property_id, last_agency_reply_at, last_client_message_at):
|
| reply = auto_chat_service.generate_auto_reply(client_message, property_id, chat_id)
|
| if reply:
|
| return Response(reply)
|
| return Response({'should_auto_reply': False, 'error': 'Failed to generate reply'})
|
|
|
| return Response({'should_auto_reply': False})
|
|
|
|
|
| @api_view(['POST'])
|
| @authentication_classes([JWTAuthentication])
|
| @permission_classes([IsAuthenticated])
|
| def enable_auto_chat_with_questions(request):
|
| """
|
| Enable auto-chat for a property and add custom questions (max 5)
|
| """
|
| user = request.user
|
|
|
|
|
| if not hasattr(user, 'role') or getattr(user.role, 'role_type', None) != 'agency':
|
| return Response({'error': 'Only agencies can enable auto-chat'},
|
| status=status.HTTP_403_FORBIDDEN)
|
|
|
|
|
| serializer = EnableAutoChatSerializer(data=request.data)
|
| if not serializer.is_valid():
|
| return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
|
|
| validated_data = serializer.validated_data
|
| property_id = validated_data['property_id']
|
| enable = validated_data['enable']
|
| custom_questions = validated_data.get('custom_questions', [])
|
|
|
|
|
| from Property.models import Property
|
| try:
|
| property_obj = Property.objects.get(id=property_id, user=user)
|
| except Property.DoesNotExist:
|
| return Response({'error': 'Property not found or does not belong to you'},
|
| status=status.HTTP_404_NOT_FOUND)
|
|
|
|
|
| PropertyCustomQA.objects.filter(property_id=property_id).delete()
|
|
|
|
|
| created_questions = []
|
| for idx, q_data in enumerate(custom_questions):
|
|
|
| embedding = embedding_service.generate_embedding(q_data['question'])
|
|
|
| custom_qa = PropertyCustomQA.objects.create(
|
| agency=user,
|
| property=property_obj,
|
| question=q_data['question'],
|
| answer=q_data['answer'],
|
| question_embedding=embedding,
|
| order=idx + 1,
|
| is_active=True
|
| )
|
| created_questions.append({
|
| 'order': custom_qa.order,
|
| 'question': custom_qa.question,
|
| 'answer': custom_qa.answer
|
| })
|
|
|
|
|
| property_state, created = PropertyAutoChatState.objects.update_or_create(
|
| property_id=property_id,
|
| defaults={'is_auto_chat_enabled': enable}
|
| )
|
|
|
|
|
| agency_setting, _ = AgencyAutoChatSetting.objects.get_or_create(
|
| agency=user,
|
| defaults={
|
| 'is_enabled': enable,
|
| 'delay_seconds': 30,
|
| 'confidence_threshold': 0.7
|
| }
|
| )
|
| if enable and not agency_setting.is_enabled:
|
| agency_setting.is_enabled = True
|
| agency_setting.save()
|
|
|
| return Response({
|
| 'success': True,
|
| 'property_id': str(property_id),
|
| 'auto_chat_enabled': property_state.is_auto_chat_enabled,
|
| 'custom_questions_added': len(created_questions),
|
| 'custom_questions': created_questions,
|
| 'message': f'Auto-chat {"enabled" if enable else "disabled"} with {len(created_questions)} custom questions'
|
| }, status=status.HTTP_200_OK)
|
|
|
|
|
| @api_view(['GET'])
|
| @authentication_classes([JWTAuthentication])
|
| @permission_classes([IsAuthenticated])
|
| def get_custom_questions(request, property_id):
|
| """
|
| Get custom questions for a property (for editing)
|
| """
|
| user = request.user
|
|
|
|
|
| from Property.models import Property
|
| try:
|
| property_obj = Property.objects.get(id=property_id, user=user)
|
| except Property.DoesNotExist:
|
| return Response({'error': 'Property not found'}, status=status.HTTP_404_NOT_FOUND)
|
|
|
| custom_questions = PropertyCustomQA.objects.filter(
|
| property_id=property_id,
|
| is_active=True
|
| ).order_by('order')
|
|
|
| serializer = PropertyCustomQASerializer(custom_questions, many=True)
|
|
|
|
|
| auto_chat_state = PropertyAutoChatState.objects.filter(property_id=property_id).first()
|
|
|
| return Response({
|
| 'property_id': str(property_id),
|
| 'auto_chat_enabled': auto_chat_state.is_auto_chat_enabled if auto_chat_state else False,
|
| 'custom_questions': serializer.data,
|
| 'max_allowed': 5,
|
| 'remaining_slots': 5 - len(serializer.data)
|
| })
|
|
|
| import uuid
|
| from rest_framework.decorators import api_view, permission_classes
|
| from rest_framework.permissions import IsAdminUser
|
|
|
|
|
| @api_view(['GET', 'POST', 'PUT', 'DELETE'])
|
| @permission_classes([IsAdminUser])
|
| def admin_master_questions(request, question_id=None):
|
| """
|
| Admin CRUD for master questions
|
| """
|
|
|
|
|
| if request.method == 'GET':
|
| if question_id:
|
| try:
|
| question = MasterQuestion.objects.get(id=question_id)
|
| data = {
|
| 'id': str(question.id),
|
| 'question': question.question,
|
| 'order': question.order,
|
| 'is_active': question.is_active,
|
| 'created_at': question.created_at
|
| }
|
| return Response(data)
|
| except MasterQuestion.DoesNotExist:
|
| return Response({'error': 'Question not found'}, status=404)
|
| else:
|
| questions = MasterQuestion.objects.all().order_by('order')
|
| data = [{
|
| 'id': str(q.id),
|
| 'question': q.question,
|
| 'order': q.order,
|
| 'is_active': q.is_active
|
| } for q in questions]
|
| return Response({
|
| 'count': len(data),
|
| 'questions': data
|
| })
|
|
|
|
|
| elif request.method == 'POST':
|
| question_text = request.data.get('question')
|
| order = request.data.get('order')
|
| is_active = request.data.get('is_active', True)
|
|
|
| if not question_text:
|
| return Response({'error': 'Question text is required'}, status=400)
|
|
|
|
|
| if MasterQuestion.objects.filter(question=question_text).exists():
|
| return Response({'error': 'Question already exists'}, status=400)
|
|
|
|
|
| if order is None:
|
| order = MasterQuestion.objects.count() + 1
|
|
|
| question = MasterQuestion.objects.create(
|
| id=uuid.uuid4(),
|
| question=question_text,
|
| order=order,
|
| is_active=is_active
|
| )
|
|
|
| return Response({
|
| 'id': str(question.id),
|
| 'question': question.question,
|
| 'order': question.order,
|
| 'is_active': question.is_active,
|
| 'message': 'Master question created successfully'
|
| }, status=201)
|
|
|
|
|
| elif request.method == 'PUT':
|
| if not question_id:
|
| return Response({'error': 'Question ID required'}, status=400)
|
|
|
| try:
|
| question = MasterQuestion.objects.get(id=question_id)
|
| except MasterQuestion.DoesNotExist:
|
| return Response({'error': 'Question not found'}, status=404)
|
|
|
| question_text = request.data.get('question', question.question)
|
| order = request.data.get('order', question.order)
|
| is_active = request.data.get('is_active', question.is_active)
|
|
|
| question.question = question_text
|
| question.order = order
|
| question.is_active = is_active
|
| question.save()
|
|
|
| return Response({
|
| 'id': str(question.id),
|
| 'question': question.question,
|
| 'order': question.order,
|
| 'is_active': question.is_active,
|
| 'message': 'Master question updated successfully'
|
| })
|
|
|
|
|
| elif request.method == 'DELETE':
|
| if not question_id:
|
| return Response({'error': 'Question ID required'}, status=400)
|
|
|
| try:
|
| question = MasterQuestion.objects.get(id=question_id)
|
| question.delete()
|
| return Response({'message': 'Master question deleted successfully'})
|
| except MasterQuestion.DoesNotExist:
|
| return Response({'error': 'Question not found'}, status=404)
|
|
|
|
|
|
|
| @api_view(['POST'])
|
| @permission_classes([IsAdminUser])
|
| def admin_master_questions_bulk(request):
|
| """
|
| Admin can create multiple master questions at once
|
| """
|
| questions_data = request.data.get('questions', [])
|
|
|
| if not questions_data:
|
| return Response({'error': 'Questions list is required'}, status=400)
|
|
|
| created = []
|
| errors = []
|
|
|
| for idx, q_data in enumerate(questions_data):
|
| question_text = q_data.get('question')
|
| order = q_data.get('order', idx + 1)
|
| is_active = q_data.get('is_active', True)
|
|
|
| if not question_text:
|
| errors.append({'index': idx, 'error': 'Question text required'})
|
| continue
|
|
|
|
|
| if MasterQuestion.objects.filter(question=question_text).exists():
|
| errors.append({'index': idx, 'question': question_text, 'error': 'Already exists'})
|
| continue
|
|
|
| try:
|
| question = MasterQuestion.objects.create(
|
| id=uuid.uuid4(),
|
| question=question_text,
|
| order=order,
|
| is_active=is_active
|
| )
|
| created.append({
|
| 'id': str(question.id),
|
| 'question': question.question,
|
| 'order': question.order
|
| })
|
| except Exception as e:
|
| errors.append({'index': idx, 'question': question_text, 'error': str(e)})
|
|
|
| return Response({
|
| 'message': f'Created {len(created)} questions',
|
| 'created': created,
|
| 'errors': errors if errors else None
|
| }, status=201 if created else 400)
|
|
|
|
|
|
|
| @api_view(['GET'])
|
| @authentication_classes([JWTAuthentication])
|
| @permission_classes([IsAuthenticated])
|
| def get_master_questions(request):
|
| """
|
| Get all active master questions for agency to answer
|
| """
|
| questions = MasterQuestion.objects.filter(is_active=True).order_by('order')
|
| data = [{
|
| 'id': str(q.id),
|
| 'question': q.question,
|
| 'order': q.order
|
| } for q in questions]
|
| return Response({
|
| 'total': len(data),
|
| 'questions': data
|
| })
|
|
|
|
|
|
|
| @api_view(['GET', 'POST', 'PUT', 'DELETE'])
|
| @permission_classes([IsAdminUser])
|
| def admin_global_qa(request, qa_id=None):
|
| """
|
| Admin CRUD for Global Q&A (applies to ALL properties)
|
| """
|
|
|
|
|
| if request.method == 'GET':
|
| if qa_id:
|
| try:
|
| qa = GlobalQA.objects.get(id=qa_id)
|
| data = {
|
| 'id': str(qa.id),
|
| 'question': qa.question,
|
| 'answer': qa.answer,
|
| 'language': qa.language,
|
| 'priority': qa.priority,
|
| 'is_active': qa.is_active
|
| }
|
| return Response(data)
|
| except GlobalQA.DoesNotExist:
|
| return Response({'error': 'Not found'}, status=404)
|
| else:
|
| qa_list = GlobalQA.objects.all().order_by('-priority', 'question')
|
| data = [{
|
| 'id': str(q.id),
|
| 'question': q.question,
|
| 'answer': q.answer[:100],
|
| 'language': q.language,
|
| 'priority': q.priority,
|
| 'is_active': q.is_active
|
| } for q in qa_list]
|
| return Response({'total': len(data), 'global_qa': data})
|
|
|
|
|
| elif request.method == 'POST':
|
| question = request.data.get('question')
|
| answer = request.data.get('answer')
|
| language = request.data.get('language', 'both')
|
| priority = request.data.get('priority', 0)
|
| is_active = request.data.get('is_active', True)
|
|
|
| if not question or not answer:
|
| return Response({'error': 'Question and answer required'}, status=400)
|
|
|
| if GlobalQA.objects.filter(question=question).exists():
|
| return Response({'error': 'Question already exists'}, status=400)
|
|
|
|
|
| embedding = embedding_service.generate_embedding(question)
|
|
|
| qa = GlobalQA.objects.create(
|
| id=uuid.uuid4(),
|
| question=question,
|
| answer=answer,
|
| question_embedding=embedding,
|
| language=language,
|
| priority=priority,
|
| is_active=is_active
|
| )
|
|
|
| return Response({
|
| 'id': str(qa.id),
|
| 'question': qa.question,
|
| 'answer': qa.answer,
|
| 'message': 'Global Q&A created successfully'
|
| }, status=201)
|
|
|
|
|
| elif request.method == 'PUT':
|
| if not qa_id:
|
| return Response({'error': 'ID required'}, status=400)
|
|
|
| try:
|
| qa = GlobalQA.objects.get(id=qa_id)
|
| except GlobalQA.DoesNotExist:
|
| return Response({'error': 'Not found'}, status=404)
|
|
|
| question = request.data.get('question', qa.question)
|
| answer = request.data.get('answer', qa.answer)
|
| language = request.data.get('language', qa.language)
|
| priority = request.data.get('priority', qa.priority)
|
| is_active = request.data.get('is_active', qa.is_active)
|
|
|
|
|
| if question != qa.question:
|
| embedding = embedding_service.generate_embedding(question)
|
| qa.question_embedding = embedding
|
|
|
| qa.question = question
|
| qa.answer = answer
|
| qa.language = language
|
| qa.priority = priority
|
| qa.is_active = is_active
|
| qa.save()
|
|
|
| return Response({'message': 'Global Q&A updated successfully'})
|
|
|
|
|
| elif request.method == 'DELETE':
|
| if not qa_id:
|
| return Response({'error': 'ID required'}, status=400)
|
|
|
| try:
|
| qa = GlobalQA.objects.get(id=qa_id)
|
| qa.delete()
|
| return Response({'message': 'Global Q&A deleted successfully'})
|
| except GlobalQA.DoesNotExist:
|
| return Response({'error': 'Not found'}, status=404)
|
|
|
|
|
|
|
|
|
| @api_view(['POST'])
|
| @permission_classes([IsAdminUser])
|
| def admin_global_qa_bulk(request):
|
| """
|
| Admin can create multiple Global Q&A at once
|
| """
|
| items = request.data.get('items', [])
|
|
|
| if not items:
|
| return Response({'error': 'Items list required'}, status=400)
|
|
|
| created = []
|
| errors = []
|
|
|
| for idx, item in enumerate(items):
|
| question = item.get('question')
|
| answer = item.get('answer')
|
| language = item.get('language', 'both')
|
| priority = item.get('priority', 0)
|
|
|
| if not question or not answer:
|
| errors.append({'index': idx, 'error': 'Question and answer required'})
|
| continue
|
|
|
| if GlobalQA.objects.filter(question=question).exists():
|
| errors.append({'index': idx, 'question': question, 'error': 'Already exists'})
|
| continue
|
|
|
| try:
|
| embedding = embedding_service.generate_embedding(question)
|
| qa = GlobalQA.objects.create(
|
| id=uuid.uuid4(),
|
| question=question,
|
| answer=answer,
|
| question_embedding=embedding,
|
| language=language,
|
| priority=priority,
|
| is_active=True
|
| )
|
| created.append({
|
| 'id': str(qa.id),
|
| 'question': qa.question,
|
| 'answer': qa.answer[:50]
|
| })
|
| except Exception as e:
|
| errors.append({'index': idx, 'question': question, 'error': str(e)})
|
|
|
| return Response({
|
| 'message': f'Created {len(created)} Global Q&A',
|
| 'created': created,
|
| 'errors': errors if errors else None
|
| }, status=201 if created else 400) |