# # views.py (Enhanced) # from django.shortcuts import render, get_object_or_404 # from django.http import JsonResponse # from django.views import View # from django.core.cache import cache # import json # import logging # from core.models import AttributeScore, CategoryAttributeRule, Product # from core.services.attribute_scorer import AttributeQualityScorer # from django.views.decorators.csrf import csrf_exempt # from django.utils.decorators import method_decorator # logger = logging.getLogger(__name__) # @method_decorator(csrf_exempt, name='dispatch') # class AttributeScoreView(View): # """Enhanced API view with caching and AI suggestions""" # def __init__(self, *args, **kwargs): # super().__init__(*args, **kwargs) # self.scorer = AttributeQualityScorer(use_ai=True) # enable AI # def post(self, request, *args, **kwargs): # """Score a single product with AI suggestions""" # try: # data = json.loads(request.body) # product_data = data.get('product', {}) # sku = product_data.get('sku') # use_ai = data.get('use_ai', True) # if not sku: # return JsonResponse({'error': 'SKU is required'}, status=400) # category = product_data.get('category', '') # if not category: # return JsonResponse({'error': 'Category is required'}, status=400) # # Get or create product # product, created = Product.objects.get_or_create( # sku=sku, # defaults={ # 'title': product_data.get('title', ''), # 'description': product_data.get('description', ''), # 'category': category, # 'attributes': product_data.get('attributes', {}) # } # ) # # Update if exists # if not created: # product.title = product_data.get('title', product.title) # product.description = product_data.get('description', product.description) # product.attributes = product_data.get('attributes', product.attributes) # product.save() # # Get rules (cached) # cache_key = f"category_rules_{category}" # rules = cache.get(cache_key) # if rules is None: # rules = list(CategoryAttributeRule.objects.filter(category=category).values()) # cache.set(cache_key, rules, 3600) # if not rules: # return JsonResponse({'error': f'No rules defined for {category}'}, status=400) # # Force AI suggestions # score_result = self.scorer.score_product( # { # 'sku': product.sku, # 'category': product.category, # 'title': product.title, # 'description': product.description, # 'attributes': product.attributes # }, # rules, # generate_ai_suggestions=True # always generate AI # ) # # Save score # AttributeScore.objects.create( # product=product, # score=score_result['final_score'], # max_score=score_result['max_score'], # details=score_result['breakdown'], # issues=score_result['issues'], # suggestions=score_result['suggestions'], # ai_suggestions=score_result.get('ai_suggestions', {}), # processing_time=score_result.get('processing_time', 0) # ) # return JsonResponse({ # 'success': True, # 'product_sku': sku, # 'created': created, # 'score_result': score_result # }) # except json.JSONDecodeError: # return JsonResponse({'error': 'Invalid JSON'}, status=400) # except Exception as e: # logger.error(f"Error scoring product: {str(e)}", exc_info=True) # return JsonResponse({'error': str(e)}, status=500) # from django.views import View # from django.http import JsonResponse # from django.utils.decorators import method_decorator # from django.views.decorators.csrf import csrf_exempt # import json # import logging # from .models import Product, CategoryAttributeRule, AttributeScore # from .services.attribute_scorer import AttributeQualityScorer # logger = logging.getLogger(__name__) # @method_decorator(csrf_exempt, name='dispatch') # class BatchScoreView(View): # """Batch scoring with AI suggestions""" # def __init__(self, *args, **kwargs): # super().__init__(*args, **kwargs) # self.scorer = AttributeQualityScorer(use_ai=True) # enable AI even for batch # def post(self, request): # try: # data = json.loads(request.body) # products = data.get('products', []) # if not products: # return JsonResponse({'error': 'No products provided'}, status=400) # results = [] # errors = [] # for product_data in products[:100]: # limit 100 # try: # sku = product_data.get('sku') # category = product_data.get('category') # if not sku or not category: # errors.append({'sku': sku, 'error': 'Missing SKU or category'}) # continue # # Get rules # rules = list(CategoryAttributeRule.objects.filter(category=category).values()) # if not rules: # errors.append({'sku': sku, 'error': f'No rules for category {category}'}) # continue # # Force AI suggestions # score_result = self.scorer.score_product( # product_data, # rules, # generate_ai_suggestions=True # <- key change # ) # results.append({ # 'sku': sku, # 'final_score': score_result['final_score'], # 'max_score': score_result['max_score'], # 'breakdown': score_result['breakdown'], # 'issues': score_result['issues'], # 'suggestions': score_result['suggestions'], # 'ai_suggestions': score_result.get('ai_suggestions', {}), # 'processing_time': score_result.get('processing_time', 0) # }) # except Exception as e: # errors.append({'sku': product_data.get('sku'), 'error': str(e)}) # return JsonResponse({ # 'success': True, # 'processed': len(results), # 'results': results, # 'errors': errors # }) # except Exception as e: # logger.error(f"Batch scoring error: {str(e)}") # return JsonResponse({'error': str(e)}, status=500) # views.py (Enhanced with ProductContentRule support - FIXED) from django.shortcuts import render, get_object_or_404 from django.http import JsonResponse from django.views import View from django.core.cache import cache from django.db.models import Q # ← FIXED: Import Q from django.db.models from django.views.decorators.csrf import csrf_exempt from django.utils.decorators import method_decorator import json import logging from core.models import AttributeScore, CategoryAttributeRule, ProductContentRule, Product from core.services.attribute_scorer import AttributeQualityScorer logger = logging.getLogger(__name__) @method_decorator(csrf_exempt, name='dispatch') class AttributeScoreView(View): """Enhanced API view with ProductContentRule support""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.scorer = AttributeQualityScorer(use_ai=True) def post(self, request, *args, **kwargs): """Score a single product with AI suggestions and content rules validation""" try: data = json.loads(request.body) product_data = data.get('product', {}) sku = product_data.get('sku') use_ai = data.get('use_ai', True) if not sku: return JsonResponse({'error': 'SKU is required'}, status=400) category = product_data.get('category', '') if not category: return JsonResponse({'error': 'Category is required'}, status=400) # Get or create product product, created = Product.objects.get_or_create( sku=sku, defaults={ 'title': product_data.get('title', ''), 'description': product_data.get('description', ''), 'short_description': product_data.get('short_description', ''), 'seo_title': product_data.get('seo_title', ''), 'seo_description': product_data.get('seo_description', ''), 'category': category, 'attributes': product_data.get('attributes', {}) } ) # Update if exists if not created: product.title = product_data.get('title', product.title) product.description = product_data.get('description', product.description) product.short_description = product_data.get('short_description', product.short_description) product.seo_title = product_data.get('seo_title', product.seo_title) product.seo_description = product_data.get('seo_description', product.seo_description) product.attributes = product_data.get('attributes', product.attributes) product.save() # Get CategoryAttributeRules (cached) cache_key = f"category_rules_{category}" category_rules = cache.get(cache_key) if category_rules is None: category_rules = list(CategoryAttributeRule.objects.filter(category=category).values()) cache.set(cache_key, category_rules, 3600) if not category_rules: return JsonResponse({'error': f'No attribute rules defined for {category}'}, status=400) # Get ProductContentRules (cached) - FIXED: Use Q from django.db.models content_cache_key = f"content_rules_{category}" content_rules = cache.get(content_cache_key) if content_rules is None: # Get both global rules (category=None) and category-specific rules content_rules = list( ProductContentRule.objects.filter( Q(category__isnull=True) | Q(category=category) ).values() ) cache.set(content_cache_key, content_rules, 3600) # Build product dict with all fields product_dict = { 'sku': product.sku, 'category': product.category, 'title': product.title, 'description': product.description, 'short_description': product.short_description, 'seo_title': product.seo_title, 'seo_description': product.seo_description, 'attributes': product.attributes } # Score product with content rules score_result = self.scorer.score_product( product_dict, category_rules, content_rules=content_rules, generate_ai_suggestions=True ) # Save score AttributeScore.objects.create( product=product, score=score_result['final_score'], max_score=score_result['max_score'], details=score_result['breakdown'], issues=score_result['issues'], suggestions=score_result['suggestions'], ai_suggestions=score_result.get('ai_suggestions', {}), processing_time=score_result.get('processing_time', 0) ) return JsonResponse({ 'success': True, 'product_sku': sku, 'created': created, 'score_result': score_result }) except json.JSONDecodeError: return JsonResponse({'error': 'Invalid JSON'}, status=400) except Exception as e: logger.error(f"Error scoring product: {str(e)}", exc_info=True) return JsonResponse({'error': str(e)}, status=500) @method_decorator(csrf_exempt, name='dispatch') class BatchScoreView(View): """Batch scoring with AI suggestions and content rules""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.scorer = AttributeQualityScorer(use_ai=True) def post(self, request): try: data = json.loads(request.body) products = data.get('products', []) if not products: return JsonResponse({'error': 'No products provided'}, status=400) results = [] errors = [] for product_data in products[:100]: # limit 100 try: sku = product_data.get('sku') category = product_data.get('category') if not sku or not category: errors.append({'sku': sku, 'error': 'Missing SKU or category'}) continue # Get attribute rules category_rules = list(CategoryAttributeRule.objects.filter(category=category).values()) if not category_rules: errors.append({'sku': sku, 'error': f'No attribute rules for category {category}'}) continue # Get content rules - FIXED: Use Q from django.db.models content_rules = list( ProductContentRule.objects.filter( Q(category__isnull=True) | Q(category=category) ).values() ) # Score with content rules score_result = self.scorer.score_product( product_data, category_rules, content_rules=content_rules, generate_ai_suggestions=True ) results.append({ 'sku': sku, 'final_score': score_result['final_score'], 'max_score': score_result['max_score'], 'breakdown': score_result['breakdown'], 'issues': score_result['issues'], 'suggestions': score_result['suggestions'], 'ai_suggestions': score_result.get('ai_suggestions', {}), 'processing_time': score_result.get('processing_time', 0) }) except Exception as e: logger.error(f"Error scoring product {product_data.get('sku')}: {str(e)}", exc_info=True) errors.append({'sku': product_data.get('sku'), 'error': str(e)}) return JsonResponse({ 'success': True, 'processed': len(results), 'results': results, 'errors': errors }) except Exception as e: logger.error(f"Batch scoring error: {str(e)}", exc_info=True) return JsonResponse({'error': str(e)}, status=500) @method_decorator(csrf_exempt, name='dispatch') class ContentRulesView(View): """API to manage ProductContentRules""" def get(self, request): """Get all content rules, optionally filtered by category""" try: category = request.GET.get('category') if category: # FIXED: Use Q from django.db.models rules = ProductContentRule.objects.filter( Q(category__isnull=True) | Q(category=category) ) else: rules = ProductContentRule.objects.all() rules_data = list(rules.values()) return JsonResponse({ 'success': True, 'count': len(rules_data), 'rules': rules_data }) except Exception as e: logger.error(f"Error fetching content rules: {e}", exc_info=True) return JsonResponse({'error': str(e)}, status=500) def post(self, request): """Create a new content rule""" try: data = json.loads(request.body) required_fields = ['field_name'] if not all(field in data for field in required_fields): return JsonResponse({'error': 'field_name is required'}, status=400) # Create rule rule = ProductContentRule.objects.create( category=data.get('category'), field_name=data['field_name'], is_mandatory=data.get('is_mandatory', True), min_length=data.get('min_length'), max_length=data.get('max_length'), min_word_count=data.get('min_word_count'), max_word_count=data.get('max_word_count'), must_contain_keywords=data.get('must_contain_keywords', []), validation_regex=data.get('validation_regex', ''), description=data.get('description', '') ) # Clear cache if data.get('category'): cache.delete(f"content_rules_{data['category']}") return JsonResponse({ 'success': True, 'rule_id': rule.id, 'message': 'Content rule created successfully' }) except Exception as e: logger.error(f"Error creating content rule: {e}", exc_info=True) return JsonResponse({'error': str(e)}, status=500) @method_decorator(csrf_exempt, name='dispatch') class ProductScoreDetailView(View): """Get detailed score for a specific product""" def get(self, request, sku): try: product = get_object_or_404(Product, sku=sku) # Get latest score latest_score = AttributeScore.objects.filter(product=product).order_by('-created_at').first() if not latest_score: return JsonResponse({'error': 'No score found for this product'}, status=404) # Get interpretation scorer = AttributeQualityScorer() interpretation = scorer.get_score_interpretation(latest_score.score) return JsonResponse({ 'success': True, 'product': { 'sku': product.sku, 'category': product.category, 'title': product.title, 'description': product.description, 'short_description': product.short_description, 'seo_title': product.seo_title, 'seo_description': product.seo_description, 'attributes': product.attributes }, 'score': { 'final_score': latest_score.score, 'max_score': latest_score.max_score, 'breakdown': latest_score.details, 'interpretation': interpretation }, 'issues': latest_score.issues, 'suggestions': latest_score.suggestions, 'ai_suggestions': latest_score.ai_suggestions, 'scored_at': latest_score.created_at.isoformat() }) except Exception as e: logger.error(f"Error fetching product score: {e}", exc_info=True) return JsonResponse({'error': str(e)}, status=500)