views.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. # # views.py (Enhanced)
  2. # from django.shortcuts import render, get_object_or_404
  3. # from django.http import JsonResponse
  4. # from django.views import View
  5. # from django.core.cache import cache
  6. # import json
  7. # import logging
  8. # from core.models import AttributeScore, CategoryAttributeRule, Product
  9. # from core.services.attribute_scorer import AttributeQualityScorer
  10. # from django.views.decorators.csrf import csrf_exempt
  11. # from django.utils.decorators import method_decorator
  12. # logger = logging.getLogger(__name__)
  13. # @method_decorator(csrf_exempt, name='dispatch')
  14. # class AttributeScoreView(View):
  15. # """Enhanced API view with caching and AI suggestions"""
  16. # def __init__(self, *args, **kwargs):
  17. # super().__init__(*args, **kwargs)
  18. # self.scorer = AttributeQualityScorer(use_ai=True) # enable AI
  19. # def post(self, request, *args, **kwargs):
  20. # """Score a single product with AI suggestions"""
  21. # try:
  22. # data = json.loads(request.body)
  23. # product_data = data.get('product', {})
  24. # sku = product_data.get('sku')
  25. # use_ai = data.get('use_ai', True)
  26. # if not sku:
  27. # return JsonResponse({'error': 'SKU is required'}, status=400)
  28. # category = product_data.get('category', '')
  29. # if not category:
  30. # return JsonResponse({'error': 'Category is required'}, status=400)
  31. # # Get or create product
  32. # product, created = Product.objects.get_or_create(
  33. # sku=sku,
  34. # defaults={
  35. # 'title': product_data.get('title', ''),
  36. # 'description': product_data.get('description', ''),
  37. # 'category': category,
  38. # 'attributes': product_data.get('attributes', {})
  39. # }
  40. # )
  41. # # Update if exists
  42. # if not created:
  43. # product.title = product_data.get('title', product.title)
  44. # product.description = product_data.get('description', product.description)
  45. # product.attributes = product_data.get('attributes', product.attributes)
  46. # product.save()
  47. # # Get rules (cached)
  48. # cache_key = f"category_rules_{category}"
  49. # rules = cache.get(cache_key)
  50. # if rules is None:
  51. # rules = list(CategoryAttributeRule.objects.filter(category=category).values())
  52. # cache.set(cache_key, rules, 3600)
  53. # if not rules:
  54. # return JsonResponse({'error': f'No rules defined for {category}'}, status=400)
  55. # # Force AI suggestions
  56. # score_result = self.scorer.score_product(
  57. # {
  58. # 'sku': product.sku,
  59. # 'category': product.category,
  60. # 'title': product.title,
  61. # 'description': product.description,
  62. # 'attributes': product.attributes
  63. # },
  64. # rules,
  65. # generate_ai_suggestions=True # always generate AI
  66. # )
  67. # # Save score
  68. # AttributeScore.objects.create(
  69. # product=product,
  70. # score=score_result['final_score'],
  71. # max_score=score_result['max_score'],
  72. # details=score_result['breakdown'],
  73. # issues=score_result['issues'],
  74. # suggestions=score_result['suggestions'],
  75. # ai_suggestions=score_result.get('ai_suggestions', {}),
  76. # processing_time=score_result.get('processing_time', 0)
  77. # )
  78. # return JsonResponse({
  79. # 'success': True,
  80. # 'product_sku': sku,
  81. # 'created': created,
  82. # 'score_result': score_result
  83. # })
  84. # except json.JSONDecodeError:
  85. # return JsonResponse({'error': 'Invalid JSON'}, status=400)
  86. # except Exception as e:
  87. # logger.error(f"Error scoring product: {str(e)}", exc_info=True)
  88. # return JsonResponse({'error': str(e)}, status=500)
  89. # from django.views import View
  90. # from django.http import JsonResponse
  91. # from django.utils.decorators import method_decorator
  92. # from django.views.decorators.csrf import csrf_exempt
  93. # import json
  94. # import logging
  95. # from .models import Product, CategoryAttributeRule, AttributeScore
  96. # from .services.attribute_scorer import AttributeQualityScorer
  97. # logger = logging.getLogger(__name__)
  98. # @method_decorator(csrf_exempt, name='dispatch')
  99. # class BatchScoreView(View):
  100. # """Batch scoring with AI suggestions"""
  101. # def __init__(self, *args, **kwargs):
  102. # super().__init__(*args, **kwargs)
  103. # self.scorer = AttributeQualityScorer(use_ai=True) # enable AI even for batch
  104. # def post(self, request):
  105. # try:
  106. # data = json.loads(request.body)
  107. # products = data.get('products', [])
  108. # if not products:
  109. # return JsonResponse({'error': 'No products provided'}, status=400)
  110. # results = []
  111. # errors = []
  112. # for product_data in products[:100]: # limit 100
  113. # try:
  114. # sku = product_data.get('sku')
  115. # category = product_data.get('category')
  116. # if not sku or not category:
  117. # errors.append({'sku': sku, 'error': 'Missing SKU or category'})
  118. # continue
  119. # # Get rules
  120. # rules = list(CategoryAttributeRule.objects.filter(category=category).values())
  121. # if not rules:
  122. # errors.append({'sku': sku, 'error': f'No rules for category {category}'})
  123. # continue
  124. # # Force AI suggestions
  125. # score_result = self.scorer.score_product(
  126. # product_data,
  127. # rules,
  128. # generate_ai_suggestions=True # <- key change
  129. # )
  130. # results.append({
  131. # 'sku': sku,
  132. # 'final_score': score_result['final_score'],
  133. # 'max_score': score_result['max_score'],
  134. # 'breakdown': score_result['breakdown'],
  135. # 'issues': score_result['issues'],
  136. # 'suggestions': score_result['suggestions'],
  137. # 'ai_suggestions': score_result.get('ai_suggestions', {}),
  138. # 'processing_time': score_result.get('processing_time', 0)
  139. # })
  140. # except Exception as e:
  141. # errors.append({'sku': product_data.get('sku'), 'error': str(e)})
  142. # return JsonResponse({
  143. # 'success': True,
  144. # 'processed': len(results),
  145. # 'results': results,
  146. # 'errors': errors
  147. # })
  148. # except Exception as e:
  149. # logger.error(f"Batch scoring error: {str(e)}")
  150. # return JsonResponse({'error': str(e)}, status=500)
  151. # views.py (Enhanced with ProductContentRule support - FIXED)
  152. from django.shortcuts import render, get_object_or_404
  153. from django.http import JsonResponse
  154. from django.views import View
  155. from django.core.cache import cache
  156. from django.db.models import Q # ← FIXED: Import Q from django.db.models
  157. from django.views.decorators.csrf import csrf_exempt
  158. from django.utils.decorators import method_decorator
  159. import json
  160. import logging
  161. from core.models import AttributeScore, CategoryAttributeRule, ProductContentRule, Product
  162. from core.services.attribute_scorer import AttributeQualityScorer
  163. logger = logging.getLogger(__name__)
  164. @method_decorator(csrf_exempt, name='dispatch')
  165. class AttributeScoreView(View):
  166. """Enhanced API view with ProductContentRule support"""
  167. def __init__(self, *args, **kwargs):
  168. super().__init__(*args, **kwargs)
  169. self.scorer = AttributeQualityScorer(use_ai=True)
  170. def post(self, request, *args, **kwargs):
  171. """Score a single product with AI suggestions and content rules validation"""
  172. try:
  173. data = json.loads(request.body)
  174. product_data = data.get('product', {})
  175. sku = product_data.get('sku')
  176. use_ai = data.get('use_ai', True)
  177. if not sku:
  178. return JsonResponse({'error': 'SKU is required'}, status=400)
  179. category = product_data.get('category', '')
  180. if not category:
  181. return JsonResponse({'error': 'Category is required'}, status=400)
  182. # Get or create product
  183. product, created = Product.objects.get_or_create(
  184. sku=sku,
  185. defaults={
  186. 'title': product_data.get('title', ''),
  187. 'description': product_data.get('description', ''),
  188. 'short_description': product_data.get('short_description', ''),
  189. 'seo_title': product_data.get('seo_title', ''),
  190. 'seo_description': product_data.get('seo_description', ''),
  191. 'category': category,
  192. 'attributes': product_data.get('attributes', {})
  193. }
  194. )
  195. # Update if exists
  196. if not created:
  197. product.title = product_data.get('title', product.title)
  198. product.description = product_data.get('description', product.description)
  199. product.short_description = product_data.get('short_description', product.short_description)
  200. product.seo_title = product_data.get('seo_title', product.seo_title)
  201. product.seo_description = product_data.get('seo_description', product.seo_description)
  202. product.attributes = product_data.get('attributes', product.attributes)
  203. product.save()
  204. # Get CategoryAttributeRules (cached)
  205. cache_key = f"category_rules_{category}"
  206. category_rules = cache.get(cache_key)
  207. if category_rules is None:
  208. category_rules = list(CategoryAttributeRule.objects.filter(category=category).values())
  209. cache.set(cache_key, category_rules, 3600)
  210. if not category_rules:
  211. return JsonResponse({'error': f'No attribute rules defined for {category}'}, status=400)
  212. # Get ProductContentRules (cached) - FIXED: Use Q from django.db.models
  213. content_cache_key = f"content_rules_{category}"
  214. content_rules = cache.get(content_cache_key)
  215. if content_rules is None:
  216. # Get both global rules (category=None) and category-specific rules
  217. content_rules = list(
  218. ProductContentRule.objects.filter(
  219. Q(category__isnull=True) | Q(category=category)
  220. ).values()
  221. )
  222. cache.set(content_cache_key, content_rules, 3600)
  223. # Build product dict with all fields
  224. product_dict = {
  225. 'sku': product.sku,
  226. 'category': product.category,
  227. 'title': product.title,
  228. 'description': product.description,
  229. 'short_description': product.short_description,
  230. 'seo_title': product.seo_title,
  231. 'seo_description': product.seo_description,
  232. 'attributes': product.attributes
  233. }
  234. # Score product with content rules
  235. score_result = self.scorer.score_product(
  236. product_dict,
  237. category_rules,
  238. content_rules=content_rules,
  239. generate_ai_suggestions=True
  240. )
  241. # Save score
  242. AttributeScore.objects.create(
  243. product=product,
  244. score=score_result['final_score'],
  245. max_score=score_result['max_score'],
  246. details=score_result['breakdown'],
  247. issues=score_result['issues'],
  248. suggestions=score_result['suggestions'],
  249. ai_suggestions=score_result.get('ai_suggestions', {}),
  250. processing_time=score_result.get('processing_time', 0)
  251. )
  252. return JsonResponse({
  253. 'success': True,
  254. 'product_sku': sku,
  255. 'created': created,
  256. 'score_result': score_result
  257. })
  258. except json.JSONDecodeError:
  259. return JsonResponse({'error': 'Invalid JSON'}, status=400)
  260. except Exception as e:
  261. logger.error(f"Error scoring product: {str(e)}", exc_info=True)
  262. return JsonResponse({'error': str(e)}, status=500)
  263. @method_decorator(csrf_exempt, name='dispatch')
  264. class BatchScoreView(View):
  265. """Batch scoring with AI suggestions and content rules"""
  266. def __init__(self, *args, **kwargs):
  267. super().__init__(*args, **kwargs)
  268. self.scorer = AttributeQualityScorer(use_ai=True)
  269. def post(self, request):
  270. try:
  271. data = json.loads(request.body)
  272. products = data.get('products', [])
  273. if not products:
  274. return JsonResponse({'error': 'No products provided'}, status=400)
  275. results = []
  276. errors = []
  277. for product_data in products[:100]: # limit 100
  278. try:
  279. sku = product_data.get('sku')
  280. category = product_data.get('category')
  281. if not sku or not category:
  282. errors.append({'sku': sku, 'error': 'Missing SKU or category'})
  283. continue
  284. # Get attribute rules
  285. category_rules = list(CategoryAttributeRule.objects.filter(category=category).values())
  286. if not category_rules:
  287. errors.append({'sku': sku, 'error': f'No attribute rules for category {category}'})
  288. continue
  289. # Get content rules - FIXED: Use Q from django.db.models
  290. content_rules = list(
  291. ProductContentRule.objects.filter(
  292. Q(category__isnull=True) | Q(category=category)
  293. ).values()
  294. )
  295. # Score with content rules
  296. score_result = self.scorer.score_product(
  297. product_data,
  298. category_rules,
  299. content_rules=content_rules,
  300. generate_ai_suggestions=True
  301. )
  302. results.append({
  303. 'sku': sku,
  304. 'final_score': score_result['final_score'],
  305. 'max_score': score_result['max_score'],
  306. 'breakdown': score_result['breakdown'],
  307. 'issues': score_result['issues'],
  308. 'suggestions': score_result['suggestions'],
  309. 'ai_suggestions': score_result.get('ai_suggestions', {}),
  310. 'processing_time': score_result.get('processing_time', 0)
  311. })
  312. except Exception as e:
  313. logger.error(f"Error scoring product {product_data.get('sku')}: {str(e)}", exc_info=True)
  314. errors.append({'sku': product_data.get('sku'), 'error': str(e)})
  315. return JsonResponse({
  316. 'success': True,
  317. 'processed': len(results),
  318. 'results': results,
  319. 'errors': errors
  320. })
  321. except Exception as e:
  322. logger.error(f"Batch scoring error: {str(e)}", exc_info=True)
  323. return JsonResponse({'error': str(e)}, status=500)
  324. @method_decorator(csrf_exempt, name='dispatch')
  325. class ContentRulesView(View):
  326. """API to manage ProductContentRules"""
  327. def get(self, request):
  328. """Get all content rules, optionally filtered by category"""
  329. try:
  330. category = request.GET.get('category')
  331. if category:
  332. # FIXED: Use Q from django.db.models
  333. rules = ProductContentRule.objects.filter(
  334. Q(category__isnull=True) | Q(category=category)
  335. )
  336. else:
  337. rules = ProductContentRule.objects.all()
  338. rules_data = list(rules.values())
  339. return JsonResponse({
  340. 'success': True,
  341. 'count': len(rules_data),
  342. 'rules': rules_data
  343. })
  344. except Exception as e:
  345. logger.error(f"Error fetching content rules: {e}", exc_info=True)
  346. return JsonResponse({'error': str(e)}, status=500)
  347. def post(self, request):
  348. """Create a new content rule"""
  349. try:
  350. data = json.loads(request.body)
  351. required_fields = ['field_name']
  352. if not all(field in data for field in required_fields):
  353. return JsonResponse({'error': 'field_name is required'}, status=400)
  354. # Create rule
  355. rule = ProductContentRule.objects.create(
  356. category=data.get('category'),
  357. field_name=data['field_name'],
  358. is_mandatory=data.get('is_mandatory', True),
  359. min_length=data.get('min_length'),
  360. max_length=data.get('max_length'),
  361. min_word_count=data.get('min_word_count'),
  362. max_word_count=data.get('max_word_count'),
  363. must_contain_keywords=data.get('must_contain_keywords', []),
  364. validation_regex=data.get('validation_regex', ''),
  365. description=data.get('description', '')
  366. )
  367. # Clear cache
  368. if data.get('category'):
  369. cache.delete(f"content_rules_{data['category']}")
  370. return JsonResponse({
  371. 'success': True,
  372. 'rule_id': rule.id,
  373. 'message': 'Content rule created successfully'
  374. })
  375. except Exception as e:
  376. logger.error(f"Error creating content rule: {e}", exc_info=True)
  377. return JsonResponse({'error': str(e)}, status=500)
  378. @method_decorator(csrf_exempt, name='dispatch')
  379. class ProductScoreDetailView(View):
  380. """Get detailed score for a specific product"""
  381. def get(self, request, sku):
  382. try:
  383. product = get_object_or_404(Product, sku=sku)
  384. # Get latest score
  385. latest_score = AttributeScore.objects.filter(product=product).order_by('-created_at').first()
  386. if not latest_score:
  387. return JsonResponse({'error': 'No score found for this product'}, status=404)
  388. # Get interpretation
  389. scorer = AttributeQualityScorer()
  390. interpretation = scorer.get_score_interpretation(latest_score.score)
  391. return JsonResponse({
  392. 'success': True,
  393. 'product': {
  394. 'sku': product.sku,
  395. 'category': product.category,
  396. 'title': product.title,
  397. 'description': product.description,
  398. 'short_description': product.short_description,
  399. 'seo_title': product.seo_title,
  400. 'seo_description': product.seo_description,
  401. 'attributes': product.attributes
  402. },
  403. 'score': {
  404. 'final_score': latest_score.score,
  405. 'max_score': latest_score.max_score,
  406. 'breakdown': latest_score.details,
  407. 'interpretation': interpretation
  408. },
  409. 'issues': latest_score.issues,
  410. 'suggestions': latest_score.suggestions,
  411. 'ai_suggestions': latest_score.ai_suggestions,
  412. 'scored_at': latest_score.created_at.isoformat()
  413. })
  414. except Exception as e:
  415. logger.error(f"Error fetching product score: {e}", exc_info=True)
  416. return JsonResponse({'error': str(e)}, status=500)