12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622 |
- # # 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)
- # # views.py (Enhanced with categorized issues and suggestions)
- # 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
- # 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__)
- # def categorize_issues_and_suggestions(issues, suggestions):
- # """
- # Categorize issues and suggestions by component (attributes, title, description, seo)
- # Returns a structured dict with categorized items
- # """
- # categorized = {
- # 'attributes': {'issues': [], 'suggestions': []},
- # 'title': {'issues': [], 'suggestions': []},
- # 'description': {'issues': [], 'suggestions': []},
- # 'seo': {'issues': [], 'suggestions': []},
- # 'content_rules': {'issues': [], 'suggestions': []},
- # 'general': {'issues': [], 'suggestions': []}
- # }
-
- # # Categorize issues
- # for issue in issues:
- # issue_lower = issue.lower()
-
- # if issue.startswith('Title:'):
- # categorized['title']['issues'].append(issue.replace('Title:', '').strip())
- # elif issue.startswith('Description:'):
- # categorized['description']['issues'].append(issue.replace('Description:', '').strip())
- # elif issue.startswith('SEO:'):
- # categorized['seo']['issues'].append(issue.replace('SEO:', '').strip())
- # elif any(field in issue_lower for field in ['seo_title', 'seo title', 'seo_description', 'seo description']):
- # categorized['seo']['issues'].append(issue)
- # elif 'content rule' in issue_lower or any(field in issue for field in ['Seo Title:', 'Seo Description:']):
- # categorized['content_rules']['issues'].append(issue)
- # elif any(keyword in issue_lower for keyword in ['mandatory field', 'attribute', 'valid values', 'placeholder', 'standardiz']):
- # categorized['attributes']['issues'].append(issue)
- # else:
- # categorized['general']['issues'].append(issue)
-
- # # Categorize suggestions
- # for suggestion in suggestions:
- # suggestion_lower = suggestion.lower()
-
- # # Check for explicit component prefixes
- # if any(prefix in suggestion_lower for prefix in ['title:', 'expand title', 'shorten title', 'add brand name']):
- # categorized['title']['suggestions'].append(suggestion)
- # elif any(prefix in suggestion_lower for prefix in ['description:', 'expand description', 'write comprehensive', 'add features']):
- # categorized['description']['suggestions'].append(suggestion)
- # elif any(prefix in suggestion_lower for prefix in ['seo:', 'improve seo', 'add keywords', 'search', 'discoverability']):
- # categorized['seo']['suggestions'].append(suggestion)
- # elif any(field in suggestion_lower for field in ['seo_title', 'seo title', 'seo_description', 'seo description', 'add seo']):
- # categorized['seo']['suggestions'].append(suggestion)
- # elif 'content rule' in suggestion_lower:
- # categorized['content_rules']['suggestions'].append(suggestion)
- # elif any(keyword in suggestion_lower for keyword in ['add required', 'add mandatory', 'provide a', 'attribute', 'standardize', 'correct capitalization']):
- # categorized['attributes']['suggestions'].append(suggestion)
- # else:
- # categorized['general']['suggestions'].append(suggestion)
-
- # # Remove empty categories
- # return {k: v for k, v in categorized.items() if v['issues'] or v['suggestions']}
- # @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)
- # content_cache_key = f"content_rules_{category}"
- # content_rules = cache.get(content_cache_key)
- # if content_rules is None:
- # 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
- # )
- # # Categorize issues and suggestions
- # categorized = categorize_issues_and_suggestions(
- # score_result['issues'],
- # score_result['suggestions']
- # )
- # # 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)
- # )
- # # Enhanced response with categorized issues
- # return JsonResponse({
- # 'success': True,
- # 'product_sku': sku,
- # 'created': created,
- # 'score_result': {
- # 'final_score': score_result['final_score'],
- # 'max_score': score_result['max_score'],
- # 'breakdown': score_result['breakdown'],
- # 'categorized_feedback': categorized, # NEW: Categorized issues/suggestions
- # 'ai_suggestions': score_result.get('ai_suggestions', {}),
- # 'processing_time': score_result.get('processing_time', 0),
- # # Keep original format for backward compatibility
- # 'issues': score_result['issues'],
- # 'suggestions': score_result['suggestions']
- # }
- # })
- # 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
- # 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
- # )
- # # Categorize issues and suggestions
- # categorized = categorize_issues_and_suggestions(
- # score_result['issues'],
- # score_result['suggestions']
- # )
- # results.append({
- # 'sku': sku,
- # 'final_score': score_result['final_score'],
- # 'max_score': score_result['max_score'],
- # 'breakdown': score_result['breakdown'],
- # 'categorized_feedback': categorized, # NEW: Categorized by component
- # 'ai_suggestions': score_result.get('ai_suggestions', {}),
- # 'processing_time': score_result.get('processing_time', 0),
- # # Keep original format for backward compatibility
- # 'issues': score_result['issues'],
- # 'suggestions': score_result['suggestions']
- # })
- # 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:
- # 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)
-
- # # Categorize issues and suggestions
- # categorized = categorize_issues_and_suggestions(
- # latest_score.issues,
- # latest_score.suggestions
- # )
-
- # 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
- # },
- # 'categorized_feedback': categorized, # NEW: Categorized by component
- # 'ai_suggestions': latest_score.ai_suggestions,
- # 'scored_at': latest_score.created_at.isoformat(),
- # # Keep original format for backward compatibility
- # 'issues': latest_score.issues,
- # 'suggestions': latest_score.suggestions
- # })
-
- # except Exception as e:
- # logger.error(f"Error fetching product score: {e}", exc_info=True)
- # return JsonResponse({'error': str(e)}, status=500)
- # views.py (Enhanced with categorized issues and suggestions)
- 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
- from django.views.decorators.csrf import csrf_exempt
- from django.utils.decorators import method_decorator
- import json
- import logging
- from core.services.image_scorer import ImageQualityScorer
- from core.models import AttributeScore, CategoryAttributeRule, ProductContentRule, Product
- from core.services.attribute_scorer import AttributeQualityScorer
- logger = logging.getLogger(__name__)
- def categorize_ai_improvements(ai_suggestions):
- """
- Consolidate AI improvements by component
- Returns a structured dict with grouped improvements per component
- """
- if not ai_suggestions or 'improvements' not in ai_suggestions:
- return None
-
- grouped = {
- 'attributes': [],
- 'title': [],
- 'description': [],
- 'seo': [],
- 'general': []
- }
-
- for improvement in ai_suggestions.get('improvements', []):
- component = improvement.get('component', 'general').lower()
-
- # Normalize component names
- if component in grouped:
- grouped[component].append({
- 'issue': improvement.get('issue', ''),
- 'suggestion': improvement.get('suggestion', ''),
- 'priority': improvement.get('priority', 'medium'),
- 'confidence': improvement.get('confidence', 'medium')
- })
- else:
- grouped['general'].append({
- 'issue': improvement.get('issue', ''),
- 'suggestion': improvement.get('suggestion', ''),
- 'priority': improvement.get('priority', 'medium'),
- 'confidence': improvement.get('confidence', 'medium')
- })
-
- # Remove empty categories
- grouped = {k: v for k, v in grouped.items() if v}
-
- return grouped
- def categorize_issues_and_suggestions(issues, suggestions):
- """
- Categorize issues and suggestions by component (attributes, title, description, seo)
- Returns a structured dict with categorized items
- """
- categorized = {
- 'attributes': {'issues': [], 'suggestions': []},
- 'title': {'issues': [], 'suggestions': []},
- 'description': {'issues': [], 'suggestions': []},
- 'seo': {'issues': [], 'suggestions': []},
- 'content_rules': {'issues': [], 'suggestions': []},
- 'general': {'issues': [], 'suggestions': []}
- }
-
- # Categorize issues
- for issue in issues:
- issue_lower = issue.lower()
-
- if issue.startswith('Title:'):
- categorized['title']['issues'].append(issue.replace('Title:', '').strip())
- elif issue.startswith('Description:'):
- categorized['description']['issues'].append(issue.replace('Description:', '').strip())
- elif issue.startswith('SEO:'):
- categorized['seo']['issues'].append(issue.replace('SEO:', '').strip())
- elif any(field in issue_lower for field in ['seo_title', 'seo title', 'seo_description', 'seo description']):
- categorized['seo']['issues'].append(issue)
- elif 'content rule' in issue_lower or any(field in issue for field in ['Seo Title:', 'Seo Description:']):
- categorized['content_rules']['issues'].append(issue)
- elif any(keyword in issue_lower for keyword in ['mandatory field', 'attribute', 'valid values', 'placeholder', 'standardiz']):
- categorized['attributes']['issues'].append(issue)
- else:
- categorized['general']['issues'].append(issue)
-
- # Categorize suggestions
- for suggestion in suggestions:
- suggestion_lower = suggestion.lower()
-
- # Check for explicit component prefixes
- if any(prefix in suggestion_lower for prefix in ['title:', 'expand title', 'shorten title', 'add brand name']):
- categorized['title']['suggestions'].append(suggestion)
- elif any(prefix in suggestion_lower for prefix in ['description:', 'expand description', 'write comprehensive', 'add features']):
- categorized['description']['suggestions'].append(suggestion)
- elif any(prefix in suggestion_lower for prefix in ['seo:', 'improve seo', 'add keywords', 'search', 'discoverability']):
- categorized['seo']['suggestions'].append(suggestion)
- elif any(field in suggestion_lower for field in ['seo_title', 'seo title', 'seo_description', 'seo description', 'add seo']):
- categorized['seo']['suggestions'].append(suggestion)
- elif 'content rule' in suggestion_lower:
- categorized['content_rules']['suggestions'].append(suggestion)
- elif any(keyword in suggestion_lower for keyword in ['add required', 'add mandatory', 'provide a', 'attribute', 'standardize', 'correct capitalization']):
- categorized['attributes']['suggestions'].append(suggestion)
- else:
- categorized['general']['suggestions'].append(suggestion)
-
- # Remove empty categories
- return {k: v for k, v in categorized.items() if v['issues'] or v['suggestions']}
- @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)
- content_cache_key = f"content_rules_{category}"
- content_rules = cache.get(content_cache_key)
- if content_rules is None:
- 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
- )
- # Categorize issues and suggestions
- categorized = categorize_issues_and_suggestions(
- score_result['issues'],
- score_result['suggestions']
- )
- # 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)
- )
- # Enhanced response with categorized issues
- return JsonResponse({
- 'success': True,
- 'product_sku': sku,
- 'created': created,
- 'score_result': {
- 'final_score': score_result['final_score'],
- 'max_score': score_result['max_score'],
- 'breakdown': score_result['breakdown'],
- 'categorized_feedback': categorized, # NEW: Categorized issues/suggestions
- 'ai_suggestions': score_result.get('ai_suggestions', {}),
- 'processing_time': score_result.get('processing_time', 0),
- # Keep original format for backward compatibility
- 'issues': score_result['issues'],
- 'suggestions': score_result['suggestions']
- }
- })
- 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
- # 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
- # )
- # # Categorize issues and suggestions
- # categorized = categorize_issues_and_suggestions(
- # score_result['issues'],
- # score_result['suggestions']
- # )
- # results.append({
- # 'sku': sku,
- # 'final_score': score_result['final_score'],
- # 'max_score': score_result['max_score'],
- # 'breakdown': score_result['breakdown'],
- # 'categorized_feedback': categorized, # NEW: Categorized by component
- # 'ai_suggestions': score_result.get('ai_suggestions', {}),
- # 'processing_time': score_result.get('processing_time', 0),
- # # Keep original format for backward compatibility
- # 'issues': score_result['issues'],
- # 'suggestions': score_result['suggestions']
- # })
- # 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 BatchScoreView(View):
- """Batch scoring with AI suggestions, content rules, and image scoring"""
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.attribute_scorer = AttributeQualityScorer(use_ai=True)
- self.image_scorer = ImageQualityScorer(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
- 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
- try:
- # 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
- content_rules = list(
- ProductContentRule.objects.filter(
- Q(category__isnull=True) | Q(category=category)
- ).values()
- )
- # Score attributes & content
- score_result = self.attribute_scorer.score_product(
- product_data,
- category_rules,
- content_rules=content_rules,
- generate_ai_suggestions=True
- )
- import base64
- with open("media/images/CLTH-001.jpg", "rb") as img:
- img_base64 = base64.b64encode(img.read()).decode('utf-8')
- # Score image if present
- # image_data = product_data.get('image_bytes') # assume base64 or bytes
- image_data = img_base64
- # image_path = product_data.get('image_path') # optional path
- import os
- from django.conf import settings
- # Get image path from payload
- image_rel_path = product_data.get('image_path') # e.g., "images/CLTH-001.jpg"
- print(f"Got image path from payload: {image_rel_path}")
- image_full_path = None
- if image_rel_path:
- # Normalize path safely (remove MEDIA_URL if present)
- cleaned_path = image_rel_path.replace(settings.MEDIA_URL, "").lstrip("/\\")
- # image_full_path = os.path.join(settings.MEDIA_ROOT, cleaned_path)
-
- image_full_path = "media/images/CLTH-001.jpg"
- if not os.path.exists(image_full_path):
- logger.warning(f"Image not found at {image_full_path} for SKU {sku}")
- image_full_path = None
- print(f"Resolved full image path: {image_full_path}")
- # Set final path variable for image scorer (even if None)
- image_path = image_full_path
- image_score_result = None
- try:
- image_score_result = self.image_scorer.score_image(
- product_data,
- image_data=image_data,
- image_path=image_path
- )
- except Exception as img_err:
- logger.warning(f"Image scoring failed for SKU {sku}: {img_err}")
- image_score_result = {
- 'image_score': None,
- 'breakdown': {},
- 'issues': ['Image scoring failed'],
- 'suggestions': [],
- 'image_metadata': {},
- 'ai_improvements': None
- }
- # Categorize issues and suggestions
- categorized = categorize_issues_and_suggestions(
- score_result['issues'] + image_score_result.get('issues', []),
- score_result['suggestions'] + image_score_result.get('suggestions', [])
- )
- results.append({
- 'sku': sku,
- 'final_score': score_result['final_score'],
- 'max_score': score_result['max_score'],
- 'breakdown': score_result['breakdown'],
- 'image_score': image_score_result.get('image_score'),
- 'image_breakdown': image_score_result.get('breakdown', {}),
- 'image_metadata': image_score_result.get('image_metadata', {}),
- 'ai_suggestions': {
- 'content': score_result.get('ai_suggestions', {}),
- 'image': image_score_result.get('ai_improvements', {})
- },
- 'categorized_feedback': categorized,
- 'processing_time': score_result.get('processing_time', 0),
- # Original arrays for backward compatibility
- 'issues': score_result['issues'] + image_score_result.get('issues', []),
- 'suggestions': score_result['suggestions'] + image_score_result.get('suggestions', [])
- })
- except Exception as e:
- logger.error(f"Error scoring product {sku}: {str(e)}", exc_info=True)
- errors.append({'sku': 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:
- 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)
-
- # Categorize issues and suggestions
- categorized = categorize_issues_and_suggestions(
- latest_score.issues,
- latest_score.suggestions
- )
-
- 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
- },
- 'categorized_feedback': categorized, # NEW: Categorized by component
- 'ai_suggestions': latest_score.ai_suggestions,
- 'scored_at': latest_score.created_at.isoformat(),
- # Keep original format for backward compatibility
- 'issues': latest_score.issues,
- 'suggestions': latest_score.suggestions
- })
-
- except Exception as e:
- logger.error(f"Error fetching product score: {e}", exc_info=True)
- return JsonResponse({'error': str(e)}, status=500)
|