views.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729
  1. # # #==================== views.py ====================
  2. # # from rest_framework.views import APIView
  3. # # from rest_framework.response import Response
  4. # # from rest_framework import status
  5. # # from .serializers import (
  6. # # ProductAttributeRequestSerializer,
  7. # # ProductAttributeResponseSerializer
  8. # # )
  9. # # from .services import ProductAttributeService
  10. # # class ExtractProductAttributesView(APIView):
  11. # # """
  12. # # API endpoint to extract product attributes using Groq LLM.
  13. # # POST /api/extract-attributes/
  14. # # Request Body:
  15. # # {
  16. # # "title": "Product title (optional)",
  17. # # "short_desc": "Short description (optional)",
  18. # # "long_desc": "Long description (optional)",
  19. # # "mandatory_attrs": {
  20. # # "Attribute1": ["value1", "value2", "value3"],
  21. # # "Attribute2": ["valueA", "valueB"]
  22. # # },
  23. # # "model": "llama-3.1-8b-instant (optional)",
  24. # # "extract_additional": true (optional, default: true)
  25. # # }
  26. # # Response:
  27. # # {
  28. # # "mandatory": {
  29. # # "Attribute1": "value1",
  30. # # "Attribute2": "valueA"
  31. # # },
  32. # # "additional": {
  33. # # "Color": "Blue",
  34. # # "Brand": "Example"
  35. # # }
  36. # # }
  37. # # """
  38. # # def post(self, request):
  39. # # # Validate request data
  40. # # serializer = ProductAttributeRequestSerializer(data=request.data)
  41. # # if not serializer.is_valid():
  42. # # return Response(
  43. # # {"error": serializer.errors},
  44. # # status=status.HTTP_400_BAD_REQUEST
  45. # # )
  46. # # validated_data = serializer.validated_data
  47. # # # Combine product text
  48. # # product_text = ProductAttributeService.combine_product_text(
  49. # # title=validated_data.get('title'),
  50. # # short_desc=validated_data.get('short_desc'),
  51. # # long_desc=validated_data.get('long_desc')
  52. # # )
  53. # # # Extract attributes
  54. # # result = ProductAttributeService.extract_attributes(
  55. # # product_text=product_text,
  56. # # mandatory_attrs=validated_data['mandatory_attrs'],
  57. # # model=validated_data.get('model'),
  58. # # extract_additional=validated_data.get('extract_additional', True)
  59. # # )
  60. # # # Return response
  61. # # response_serializer = ProductAttributeResponseSerializer(data=result)
  62. # # if response_serializer.is_valid():
  63. # # return Response(response_serializer.data, status=status.HTTP_200_OK)
  64. # # return Response(result, status=status.HTTP_200_OK)
  65. # from rest_framework.views import APIView
  66. # from rest_framework.response import Response
  67. # from rest_framework import status
  68. # from .serializers import (
  69. # SingleProductRequestSerializer,
  70. # BatchProductRequestSerializer,
  71. # ProductAttributeResultSerializer,
  72. # BatchProductResponseSerializer
  73. # )
  74. # from .services import ProductAttributeService
  75. # class ExtractProductAttributesView(APIView):
  76. # """
  77. # API endpoint to extract product attributes for a single product.
  78. # POST /api/extract-attributes/
  79. # Request Body:
  80. # {
  81. # "title": "Product title (optional)",
  82. # "short_desc": "Short description (optional)",
  83. # "long_desc": "Long description (optional)",
  84. # "mandatory_attrs": {
  85. # "Attribute1": ["value1", "value2", "value3"],
  86. # "Attribute2": ["valueA", "valueB"]
  87. # },
  88. # "model": "llama-3.1-8b-instant (optional)",
  89. # "extract_additional": true (optional, default: true)
  90. # }
  91. # """
  92. # def post(self, request):
  93. # serializer = SingleProductRequestSerializer(data=request.data)
  94. # if not serializer.is_valid():
  95. # return Response(
  96. # {"error": serializer.errors},
  97. # status=status.HTTP_400_BAD_REQUEST
  98. # )
  99. # validated_data = serializer.validated_data
  100. # product_text = ProductAttributeService.combine_product_text(
  101. # title=validated_data.get('title'),
  102. # short_desc=validated_data.get('short_desc'),
  103. # long_desc=validated_data.get('long_desc')
  104. # )
  105. # result = ProductAttributeService.extract_attributes(
  106. # product_text=product_text,
  107. # mandatory_attrs=validated_data['mandatory_attrs'],
  108. # model=validated_data.get('model'),
  109. # extract_additional=validated_data.get('extract_additional', True)
  110. # )
  111. # response_serializer = ProductAttributeResultSerializer(data=result)
  112. # if response_serializer.is_valid():
  113. # return Response(response_serializer.data, status=status.HTTP_200_OK)
  114. # return Response(result, status=status.HTTP_200_OK)
  115. # class BatchExtractProductAttributesView(APIView):
  116. # """
  117. # API endpoint to extract product attributes for multiple products in batch.
  118. # POST /api/batch-extract-attributes/
  119. # Request Body:
  120. # {
  121. # "products": [
  122. # {
  123. # "product_id": "prod_001",
  124. # "title": "Product 1 title",
  125. # "short_desc": "Short description",
  126. # "long_desc": "Long description"
  127. # },
  128. # {
  129. # "product_id": "prod_002",
  130. # "title": "Product 2 title",
  131. # "short_desc": "Short description"
  132. # }
  133. # ],
  134. # "mandatory_attrs": {
  135. # "Attribute1": ["value1", "value2", "value3"],
  136. # "Attribute2": ["valueA", "valueB"]
  137. # },
  138. # "model": "llama-3.1-8b-instant (optional)",
  139. # "extract_additional": true (optional, default: true)
  140. # }
  141. # Response:
  142. # {
  143. # "results": [
  144. # {
  145. # "product_id": "prod_001",
  146. # "mandatory": {...},
  147. # "additional": {...}
  148. # },
  149. # {
  150. # "product_id": "prod_002",
  151. # "mandatory": {...},
  152. # "additional": {...}
  153. # }
  154. # ],
  155. # "total_products": 2,
  156. # "successful": 2,
  157. # "failed": 0
  158. # }
  159. # """
  160. # def post(self, request):
  161. # serializer = BatchProductRequestSerializer(data=request.data)
  162. # if not serializer.is_valid():
  163. # return Response(
  164. # {"error": serializer.errors},
  165. # status=status.HTTP_400_BAD_REQUEST
  166. # )
  167. # validated_data = serializer.validated_data
  168. # # Extract attributes for all products in batch
  169. # result = ProductAttributeService.extract_attributes_batch(
  170. # products=validated_data['products'],
  171. # mandatory_attrs=validated_data['mandatory_attrs'],
  172. # model=validated_data.get('model'),
  173. # extract_additional=validated_data.get('extract_additional', True)
  174. # )
  175. # response_serializer = BatchProductResponseSerializer(data=result)
  176. # if response_serializer.is_valid():
  177. # return Response(response_serializer.data, status=status.HTTP_200_OK)
  178. # return Response(result, status=status.HTTP_200_OK)
  179. # ==================== views.py ====================
  180. from rest_framework.views import APIView
  181. from rest_framework.response import Response
  182. from rest_framework import status
  183. from .serializers import (
  184. SingleProductRequestSerializer,
  185. BatchProductRequestSerializer,
  186. ProductAttributeResultSerializer,
  187. BatchProductResponseSerializer
  188. )
  189. from .services import ProductAttributeService
  190. from .ocr_service import OCRService
  191. # class ExtractProductAttributesView(APIView):
  192. # """
  193. # API endpoint to extract product attributes for a single product.
  194. # Now supports image URL for OCR-based text extraction.
  195. # """
  196. # def post(self, request):
  197. # serializer = SingleProductRequestSerializer(data=request.data)
  198. # if not serializer.is_valid():
  199. # return Response(
  200. # {"error": serializer.errors},
  201. # status=status.HTTP_400_BAD_REQUEST
  202. # )
  203. # validated_data = serializer.validated_data
  204. # # Process image if URL provided
  205. # ocr_results = None
  206. # ocr_text = None
  207. # if validated_data.get('process_image', True) and validated_data.get('image_url'):
  208. # ocr_service = OCRService()
  209. # ocr_results = ocr_service.process_image(validated_data['image_url'])
  210. # # Extract attributes from OCR
  211. # if ocr_results and ocr_results.get('detected_text'):
  212. # ocr_attrs = ProductAttributeService.extract_attributes_from_ocr(
  213. # ocr_results,
  214. # validated_data.get('model')
  215. # )
  216. # ocr_results['extracted_attributes'] = ocr_attrs
  217. # # Format OCR text
  218. # ocr_text = "\n".join([
  219. # f"{item['text']} (confidence: {item['confidence']:.2f})"
  220. # for item in ocr_results['detected_text']
  221. # ])
  222. # # Combine all product information
  223. # product_text = ProductAttributeService.combine_product_text(
  224. # title=validated_data.get('title'),
  225. # short_desc=validated_data.get('short_desc'),
  226. # long_desc=validated_data.get('long_desc'),
  227. # ocr_text=ocr_text
  228. # )
  229. # # Extract attributes
  230. # result = ProductAttributeService.extract_attributes(
  231. # product_text=product_text,
  232. # mandatory_attrs=validated_data['mandatory_attrs'],
  233. # model=validated_data.get('model'),
  234. # extract_additional=validated_data.get('extract_additional', True)
  235. # )
  236. # # Add OCR results if available
  237. # if ocr_results:
  238. # result['ocr_results'] = ocr_results
  239. # response_serializer = ProductAttributeResultSerializer(data=result)
  240. # if response_serializer.is_valid():
  241. # return Response(response_serializer.data, status=status.HTTP_200_OK)
  242. # return Response(result, status=status.HTTP_200_OK)
  243. from .models import Product
  244. class ExtractProductAttributesView(APIView):
  245. """
  246. API endpoint to extract product attributes for a single product by item_id.
  247. Fetches product details from database.
  248. """
  249. def post(self, request):
  250. serializer = SingleProductRequestSerializer(data=request.data)
  251. if not serializer.is_valid():
  252. return Response({"error": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
  253. validated_data = serializer.validated_data
  254. item_id = validated_data.get("item_id")
  255. # Fetch product from DB
  256. try:
  257. product = Product.objects.get(item_id=item_id)
  258. except Product.DoesNotExist:
  259. return Response(
  260. {"error": f"Product with item_id '{item_id}' not found."},
  261. status=status.HTTP_404_NOT_FOUND
  262. )
  263. # Extract product details
  264. title = product.product_name
  265. short_desc = product.product_short_description
  266. long_desc = product.product_long_description
  267. image_url = product.image_path
  268. # Process image for OCR if required
  269. ocr_results = None
  270. ocr_text = None
  271. if validated_data.get("process_image", True) and image_url:
  272. ocr_service = OCRService()
  273. ocr_results = ocr_service.process_image(image_url)
  274. if ocr_results and ocr_results.get("detected_text"):
  275. ocr_attrs = ProductAttributeService.extract_attributes_from_ocr(
  276. ocr_results, validated_data.get("model")
  277. )
  278. ocr_results["extracted_attributes"] = ocr_attrs
  279. ocr_text = "\n".join([
  280. f"{item['text']} (confidence: {item['confidence']:.2f})"
  281. for item in ocr_results["detected_text"]
  282. ])
  283. # Combine all product text
  284. product_text = ProductAttributeService.combine_product_text(
  285. title=title,
  286. short_desc=short_desc,
  287. long_desc=long_desc,
  288. ocr_text=ocr_text
  289. )
  290. # Extract attributes
  291. result = ProductAttributeService.extract_attributes(
  292. product_text=product_text,
  293. mandatory_attrs=validated_data["mandatory_attrs"],
  294. model=validated_data.get("model"),
  295. extract_additional=validated_data.get("extract_additional", True)
  296. )
  297. # Attach OCR results if available
  298. if ocr_results:
  299. result["ocr_results"] = ocr_results
  300. response_serializer = ProductAttributeResultSerializer(data=result)
  301. if response_serializer.is_valid():
  302. return Response(response_serializer.data, status=status.HTTP_200_OK)
  303. return Response(result, status=status.HTTP_200_OK)
  304. # class BatchExtractProductAttributesView(APIView):
  305. # """
  306. # API endpoint to extract product attributes for multiple products in batch.
  307. # Now supports image URLs for OCR-based text extraction.
  308. # """
  309. # def post(self, request):
  310. # serializer = BatchProductRequestSerializer(data=request.data)
  311. # if not serializer.is_valid():
  312. # return Response(
  313. # {"error": serializer.errors},
  314. # status=status.HTTP_400_BAD_REQUEST
  315. # )
  316. # validated_data = serializer.validated_data
  317. # # Extract attributes for all products in batch
  318. # result = ProductAttributeService.extract_attributes_batch(
  319. # products=validated_data['products'],
  320. # mandatory_attrs=validated_data['mandatory_attrs'],
  321. # model=validated_data.get('model'),
  322. # extract_additional=validated_data.get('extract_additional', True),
  323. # process_image=validated_data.get('process_image', True)
  324. # )
  325. # response_serializer = BatchProductResponseSerializer(data=result)
  326. # if response_serializer.is_valid():
  327. # return Response(response_serializer.data, status=status.HTTP_200_OK)
  328. # return Response(result, status=status.HTTP_200_OK)
  329. from .models import Product
  330. class BatchExtractProductAttributesView(APIView):
  331. """
  332. API endpoint to extract product attributes for multiple products in batch by item_id.
  333. Fetches all product details from database automatically.
  334. """
  335. def post(self, request):
  336. serializer = BatchProductRequestSerializer(data=request.data)
  337. if not serializer.is_valid():
  338. return Response({"error": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
  339. validated_data = serializer.validated_data
  340. item_ids = validated_data.get("item_ids", [])
  341. model = validated_data.get("model")
  342. extract_additional = validated_data.get("extract_additional", True)
  343. process_image = validated_data.get("process_image", True)
  344. mandatory_attrs = validated_data["mandatory_attrs"]
  345. # Fetch all products in one query
  346. products = Product.objects.filter(item_id__in=item_ids)
  347. found_ids = set(products.values_list("item_id", flat=True))
  348. missing_ids = [pid for pid in item_ids if pid not in found_ids]
  349. results = []
  350. successful = 0
  351. failed = 0
  352. for product in products:
  353. try:
  354. title = product.product_name
  355. short_desc = product.product_short_description
  356. long_desc = product.product_long_description
  357. image_url = product.image_path
  358. ocr_results = None
  359. ocr_text = None
  360. if process_image and image_url:
  361. ocr_service = OCRService()
  362. ocr_results = ocr_service.process_image(image_url)
  363. if ocr_results and ocr_results.get("detected_text"):
  364. ocr_attrs = ProductAttributeService.extract_attributes_from_ocr(
  365. ocr_results, model
  366. )
  367. ocr_results["extracted_attributes"] = ocr_attrs
  368. ocr_text = "\n".join([
  369. f"{item['text']} (confidence: {item['confidence']:.2f})"
  370. for item in ocr_results["detected_text"]
  371. ])
  372. product_text = ProductAttributeService.combine_product_text(
  373. title=title,
  374. short_desc=short_desc,
  375. long_desc=long_desc,
  376. ocr_text=ocr_text
  377. )
  378. extracted = ProductAttributeService.extract_attributes(
  379. product_text=product_text,
  380. mandatory_attrs=mandatory_attrs,
  381. model=model,
  382. extract_additional=extract_additional
  383. )
  384. result = {
  385. "product_id": product.item_id,
  386. "mandatory": extracted.get("mandatory", {}),
  387. "additional": extracted.get("additional", {}),
  388. }
  389. if ocr_results:
  390. result["ocr_results"] = ocr_results
  391. results.append(result)
  392. successful += 1
  393. except Exception as e:
  394. failed += 1
  395. results.append({
  396. "product_id": product.item_id,
  397. "error": str(e)
  398. })
  399. # Add missing item_ids as failed entries
  400. for mid in missing_ids:
  401. failed += 1
  402. results.append({
  403. "product_id": mid,
  404. "error": "Product not found in database"
  405. })
  406. batch_result = {
  407. "results": results,
  408. "total_products": len(item_ids),
  409. "successful": successful,
  410. "failed": failed
  411. }
  412. response_serializer = BatchProductResponseSerializer(data=batch_result)
  413. if response_serializer.is_valid():
  414. return Response(response_serializer.data, status=status.HTTP_200_OK)
  415. return Response(batch_result, status=status.HTTP_200_OK)
  416. from rest_framework.views import APIView
  417. from rest_framework.response import Response
  418. from rest_framework import status
  419. from .models import Product
  420. from .serializers import ProductSerializer
  421. class ProductListView(APIView):
  422. """
  423. GET API to list all products with details
  424. """
  425. def get(self, request):
  426. products = Product.objects.all()
  427. serializer = ProductSerializer(products, many=True)
  428. return Response(serializer.data, status=status.HTTP_200_OK)
  429. import pandas as pd
  430. from rest_framework.parsers import MultiPartParser, FormParser
  431. from rest_framework.views import APIView
  432. from rest_framework.response import Response
  433. from rest_framework import status
  434. from .models import Product
  435. from .serializers import ProductSerializer
  436. # class ProductUploadExcelView(APIView):
  437. # """
  438. # POST API to upload an Excel file and add data to Product model
  439. # """
  440. # parser_classes = (MultiPartParser, FormParser)
  441. # def post(self, request, *args, **kwargs):
  442. # file_obj = request.FILES.get('file')
  443. # if not file_obj:
  444. # return Response({'error': 'No file provided'}, status=status.HTTP_400_BAD_REQUEST)
  445. # try:
  446. # # Read the Excel file
  447. # df = pd.read_excel(file_obj)
  448. # # Normalize column names
  449. # df.columns = [c.strip().lower().replace(' ', '_') for c in df.columns]
  450. # # Expected columns
  451. # expected_cols = {
  452. # 'item_id',
  453. # 'product_name',
  454. # 'product_long_description',
  455. # 'product_short_description',
  456. # 'product_type',
  457. # 'image_path'
  458. # }
  459. # if not expected_cols.issubset(df.columns):
  460. # return Response({
  461. # 'error': 'Missing required columns',
  462. # 'required_columns': list(expected_cols)
  463. # }, status=status.HTTP_400_BAD_REQUEST)
  464. # # Loop through rows and create Product entries
  465. # created_count = 0
  466. # for _, row in df.iterrows():
  467. # Product.objects.create(
  468. # item_id=row.get('item_id', ''),
  469. # product_name=row.get('product_name', ''),
  470. # product_long_description=row.get('product_long_description', ''),
  471. # product_short_description=row.get('product_short_description', ''),
  472. # product_type=row.get('product_type', ''),
  473. # image_path=row.get('image_path', ''),
  474. # )
  475. # created_count += 1
  476. # return Response({
  477. # 'message': f'Successfully uploaded {created_count} products.'
  478. # }, status=status.HTTP_201_CREATED)
  479. # except Exception as e:
  480. # return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
  481. class ProductUploadExcelView(APIView):
  482. """
  483. POST API to upload an Excel file and add data to Product model (skip duplicates)
  484. """
  485. parser_classes = (MultiPartParser, FormParser)
  486. def post(self, request, *args, **kwargs):
  487. file_obj = request.FILES.get('file')
  488. if not file_obj:
  489. return Response({'error': 'No file provided'}, status=status.HTTP_400_BAD_REQUEST)
  490. try:
  491. import pandas as pd
  492. df = pd.read_excel(file_obj)
  493. df.columns = [c.strip().lower().replace(' ', '_') for c in df.columns]
  494. expected_cols = {
  495. 'item_id',
  496. 'product_name',
  497. 'product_long_description',
  498. 'product_short_description',
  499. 'product_type',
  500. 'image_path'
  501. }
  502. if not expected_cols.issubset(df.columns):
  503. return Response({
  504. 'error': 'Missing required columns',
  505. 'required_columns': list(expected_cols)
  506. }, status=status.HTTP_400_BAD_REQUEST)
  507. created_count = 0
  508. skipped_count = 0
  509. for _, row in df.iterrows():
  510. item_id = row.get('item_id', '')
  511. # Check if this item already exists
  512. if Product.objects.filter(item_id=item_id).exists():
  513. skipped_count += 1
  514. continue
  515. Product.objects.create(
  516. item_id=item_id,
  517. product_name=row.get('product_name', ''),
  518. product_long_description=row.get('product_long_description', ''),
  519. product_short_description=row.get('product_short_description', ''),
  520. product_type=row.get('product_type', ''),
  521. image_path=row.get('image_path', ''),
  522. )
  523. created_count += 1
  524. return Response({
  525. 'message': f'Successfully uploaded {created_count} products.',
  526. 'skipped': f'Skipped {skipped_count} duplicates.'
  527. }, status=status.HTTP_201_CREATED)
  528. except Exception as e:
  529. return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
  530. import pandas as pd
  531. from rest_framework.views import APIView
  532. from rest_framework.response import Response
  533. from rest_framework import status
  534. from rest_framework.parsers import MultiPartParser, FormParser
  535. from .models import ProductType, ProductAttribute, AttributePossibleValue
  536. class ProductAttributesUploadView(APIView):
  537. """
  538. POST API to upload an Excel file and add mandatory/additional attributes
  539. for product types with possible values.
  540. """
  541. parser_classes = (MultiPartParser, FormParser)
  542. def post(self, request):
  543. file_obj = request.FILES.get('file')
  544. if not file_obj:
  545. return Response({"error": "No file provided."}, status=status.HTTP_400_BAD_REQUEST)
  546. try:
  547. df = pd.read_excel(file_obj)
  548. required_columns = {'product_type', 'attribute_name', 'is_mandatory', 'possible_values'}
  549. if not required_columns.issubset(df.columns):
  550. return Response({
  551. "error": f"Missing required columns. Found: {list(df.columns)}"
  552. }, status=status.HTTP_400_BAD_REQUEST)
  553. for _, row in df.iterrows():
  554. product_type_name = str(row['product_type']).strip()
  555. attr_name = str(row['attribute_name']).strip()
  556. is_mandatory = str(row['is_mandatory']).strip().lower() in ['yes', 'true', '1']
  557. possible_values = str(row.get('possible_values', '')).strip()
  558. # Get or create product type
  559. product_type, _ = ProductType.objects.get_or_create(name=product_type_name)
  560. # Get or create attribute
  561. attribute, _ = ProductAttribute.objects.get_or_create(
  562. product_type=product_type,
  563. name=attr_name,
  564. defaults={'is_mandatory': is_mandatory}
  565. )
  566. attribute.is_mandatory = is_mandatory
  567. attribute.save()
  568. # Handle possible values
  569. AttributePossibleValue.objects.filter(attribute=attribute).delete()
  570. if possible_values:
  571. for val in [v.strip() for v in possible_values.split(',') if v.strip()]:
  572. AttributePossibleValue.objects.create(attribute=attribute, value=val)
  573. return Response({"message": "Attributes uploaded successfully."}, status=status.HTTP_201_CREATED)
  574. except Exception as e:
  575. return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)