feat: add GI tag, ODOP, and sustainability fields to product model and API
All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 46s
All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 46s
This commit is contained in:
parent
b0f98e5702
commit
df3bccda14
5 changed files with 402 additions and 40 deletions
|
|
@ -0,0 +1,38 @@
|
|||
# Generated by Django 6.1 on 2026-11-08 09:46
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('api', '0019_supplieronboardingdraft_merchant_id_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='product',
|
||||
name='gi_tag_name',
|
||||
field=models.CharField(blank=True, max_length=200, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='product',
|
||||
name='is_odop',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='product',
|
||||
name='is_sustainable',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='product',
|
||||
name='odop_district',
|
||||
field=models.CharField(blank=True, max_length=200, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='product',
|
||||
name='sustainable_type',
|
||||
field=models.CharField(blank=True, max_length=200, null=True),
|
||||
),
|
||||
]
|
||||
|
|
@ -341,6 +341,11 @@ class Product(models.Model):
|
|||
# New heritage fields from UI designs
|
||||
description = models.TextField(blank=True, null=True)
|
||||
is_gi_tagged = models.BooleanField(default=False)
|
||||
gi_tag_name = models.CharField(max_length=200, blank=True, null=True)
|
||||
is_odop = models.BooleanField(default=False)
|
||||
odop_district = models.CharField(max_length=200, blank=True, null=True)
|
||||
is_sustainable = models.BooleanField(default=False)
|
||||
sustainable_type = models.CharField(max_length=200, blank=True, null=True)
|
||||
additional_images = models.JSONField(default=list, blank=True, null=True)
|
||||
video_url = models.URLField(blank=True, null=True)
|
||||
view_360_url = models.URLField(blank=True, null=True)
|
||||
|
|
|
|||
267
api/test_initiatives_upload.py
Normal file
267
api/test_initiatives_upload.py
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
import io
|
||||
import csv
|
||||
from unittest.mock import patch
|
||||
import pytest
|
||||
from django.urls import reverse
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APIClient
|
||||
from api.models import Product, SupplierProfile
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def api_client():
|
||||
return APIClient()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def seller_user(db):
|
||||
user = User.objects.create_user(
|
||||
username='artisan_initiatives_test',
|
||||
email='artisan_test@tradhox.com',
|
||||
password='TestPassword123!'
|
||||
)
|
||||
SupplierProfile.objects.create(
|
||||
user=user,
|
||||
phone='9876543210',
|
||||
pincode='560001',
|
||||
is_profile_complete=True
|
||||
)
|
||||
return user
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestInitiativesUpload:
|
||||
|
||||
def test_individual_upload_initiatives_creation(self, api_client, seller_user):
|
||||
"""Test individual product upload captures ODOP, GI, and Sustainable fields."""
|
||||
api_client.force_authenticate(user=seller_user)
|
||||
url = reverse('product-list')
|
||||
|
||||
payload = {
|
||||
'title': 'Kanchipuram Pure Mulberry Silk Saree',
|
||||
'sku': 'TRDX-KSS-INIT-001',
|
||||
'category': 'Clothing & Textiles',
|
||||
'price': 6500.00,
|
||||
'compare_at_price': 8500.00,
|
||||
'stock': 12,
|
||||
'description': 'Handloom silk saree with gold zari border.',
|
||||
'is_odop': True,
|
||||
'odop_district': 'Kanchipuram, Tamil Nadu',
|
||||
'is_gi_tagged': True,
|
||||
'gi_tag_name': 'Kancheepuram Silk (GI-12)',
|
||||
'is_sustainable': True,
|
||||
'sustainable_type': '100% Biodegradable & Natural Fibers',
|
||||
'package_weight': 0.8,
|
||||
'shipping_profile': 'Standard Fragile'
|
||||
}
|
||||
|
||||
with patch('api.views.shipping_client.is_pincode_pickup_serviceable', return_value=(True, 'OK')):
|
||||
response = api_client.post(url, payload, format='json')
|
||||
|
||||
assert response.status_code == status.HTTP_201_CREATED
|
||||
assert response.data['is_odop'] is True
|
||||
assert response.data['odop_district'] == 'Kanchipuram, Tamil Nadu'
|
||||
assert response.data['is_gi_tagged'] is True
|
||||
assert response.data['gi_tag_name'] == 'Kancheepuram Silk (GI-12)'
|
||||
assert response.data['is_sustainable'] is True
|
||||
assert response.data['sustainable_type'] == '100% Biodegradable & Natural Fibers'
|
||||
|
||||
# Verify DB persistence
|
||||
product = Product.objects.get(sku='TRDX-KSS-INIT-001')
|
||||
assert product.is_odop is True
|
||||
assert product.odop_district == 'Kanchipuram, Tamil Nadu'
|
||||
assert product.is_gi_tagged is True
|
||||
assert product.gi_tag_name == 'Kancheepuram Silk (GI-12)'
|
||||
assert product.is_sustainable is True
|
||||
assert product.sustainable_type == '100% Biodegradable & Natural Fibers'
|
||||
|
||||
def test_individual_upload_initiatives_update(self, api_client, seller_user):
|
||||
"""Test updating ODOP, GI, and Sustainable attributes via PATCH."""
|
||||
api_client.force_authenticate(user=seller_user)
|
||||
|
||||
product = Product.objects.create(
|
||||
supplier=seller_user,
|
||||
sku='TRDX-JBP-INIT-002',
|
||||
title='Jaipur Ceramic Vase',
|
||||
category='Handicrafts & Art',
|
||||
price=1200.00,
|
||||
stock=15,
|
||||
is_odop=False,
|
||||
is_gi_tagged=False,
|
||||
is_sustainable=False
|
||||
)
|
||||
|
||||
url = reverse('product-detail', kwargs={'pk': product.id})
|
||||
update_payload = {
|
||||
'is_odop': True,
|
||||
'odop_district': 'Jaipur, Rajasthan',
|
||||
'is_gi_tagged': True,
|
||||
'gi_tag_name': 'Jaipur Blue Pottery',
|
||||
'is_sustainable': True,
|
||||
'sustainable_type': 'Zero-Lead Eco Glaze'
|
||||
}
|
||||
|
||||
response = api_client.patch(url, update_payload, format='json')
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
|
||||
product.refresh_from_db()
|
||||
assert product.is_odop is True
|
||||
assert product.odop_district == 'Jaipur, Rajasthan'
|
||||
assert product.is_gi_tagged is True
|
||||
assert product.gi_tag_name == 'Jaipur Blue Pottery'
|
||||
assert product.is_sustainable is True
|
||||
assert product.sustainable_type == 'Zero-Lead Eco Glaze'
|
||||
|
||||
def test_bulk_upload_initiatives_csv(self, api_client, seller_user):
|
||||
"""Test bulk CSV upload parses and persists ODOP, GI, and Sustainable fields."""
|
||||
api_client.force_authenticate(user=seller_user)
|
||||
url = reverse('bulk-upload')
|
||||
|
||||
csv_rows = [
|
||||
[
|
||||
'Title', 'SKU', 'Category', 'Price', 'MRP', 'Stock', 'Description',
|
||||
'ODOP_Product', 'ODOP_District', 'GI_Tagged', 'GI_Tag_Name',
|
||||
'Sustainable_Product', 'Sustainable_Type', 'Package_Weight_Kg',
|
||||
'Shipping_Profile', 'Image_URL', 'Additional_Images', 'Video_URL'
|
||||
],
|
||||
[
|
||||
'Jaipur Blue Pottery Bowl', 'BULK-JBP-001', 'Handicrafts & Art',
|
||||
'899', '1199', '25', 'Authentic handmade glazed bowl.',
|
||||
'Yes', 'Jaipur (Rajasthan)', 'Yes', 'Jaipur Blue Pottery',
|
||||
'Yes', 'Zero-Waste Upcycled Glaze', '1.1',
|
||||
'Fragile Handcrafted', 'https://example.com/cover.jpg',
|
||||
'https://example.com/side.jpg;https://example.com/top.jpg',
|
||||
'https://example.com/pottery_demo.mp4'
|
||||
],
|
||||
[
|
||||
'Saharanpur Wood Carved Coasters', 'BULK-SHM-002', 'Home & Living',
|
||||
'450', '600', '40', 'Solid wood carving coasters.',
|
||||
'Yes', 'Saharanpur (Uttar Pradesh)', 'No', '',
|
||||
'Yes', 'Sustainably Harvested Wood', '0.5',
|
||||
'Standard Fragile', 'https://example.com/coasters.jpg',
|
||||
'', ''
|
||||
]
|
||||
]
|
||||
|
||||
out_io = io.StringIO()
|
||||
writer = csv.writer(out_io)
|
||||
for r in csv_rows:
|
||||
writer.writerow(r)
|
||||
|
||||
csv_file = SimpleUploadedFile(
|
||||
'initiatives_catalog.csv',
|
||||
out_io.getvalue().encode('utf-8'),
|
||||
content_type='text/csv'
|
||||
)
|
||||
|
||||
with patch('api.views.shipping_client.is_pincode_pickup_serviceable', return_value=(True, 'OK')):
|
||||
response = api_client.post(url, {'csv_file': csv_file}, format='multipart')
|
||||
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
assert response.data['summary']['created'] == 2
|
||||
assert response.data['summary']['skipped'] == 0
|
||||
|
||||
# Verify Item 1 (ODOP + GI + Sustainable + Media)
|
||||
prod1 = Product.objects.get(sku='BULK-JBP-001')
|
||||
assert prod1.is_odop is True
|
||||
assert prod1.odop_district == 'Jaipur (Rajasthan)'
|
||||
assert prod1.is_gi_tagged is True
|
||||
assert prod1.gi_tag_name == 'Jaipur Blue Pottery'
|
||||
assert prod1.is_sustainable is True
|
||||
assert prod1.sustainable_type == 'Zero-Waste Upcycled Glaze'
|
||||
assert prod1.additional_images == ['https://example.com/side.jpg', 'https://example.com/top.jpg']
|
||||
assert prod1.video_url == 'https://example.com/pottery_demo.mp4'
|
||||
assert prod1.upload_source == 'bulk'
|
||||
|
||||
# Verify Item 2 (ODOP + Sustainable, GI=False)
|
||||
prod2 = Product.objects.get(sku='BULK-SHM-002')
|
||||
assert prod2.is_odop is True
|
||||
assert prod2.odop_district == 'Saharanpur (Uttar Pradesh)'
|
||||
assert prod2.is_gi_tagged is False
|
||||
assert prod2.gi_tag_name == ''
|
||||
assert prod2.is_sustainable is True
|
||||
assert prod2.sustainable_type == 'Sustainably Harvested Wood'
|
||||
|
||||
def test_bulk_upload_column_aliases_and_case_insensitivity(self, api_client, seller_user):
|
||||
"""Test bulk upload gracefully accepts alternative header aliases and booleans."""
|
||||
api_client.force_authenticate(user=seller_user)
|
||||
url = reverse('bulk-upload')
|
||||
|
||||
# Using lowercase/alternate header keys
|
||||
csv_rows = [
|
||||
['title', 'sku', 'category', 'price', 'stock', 'odop', 'district', 'gi', 'gi_name', 'sustainable', 'sustainability_type'],
|
||||
['Channapatna Wooden Toy', 'BULK-CPT-003', 'Toys & Games', '350', '20', 'true', 'Ramanagara, Karnataka', '1', 'Channapatna Toys', 'yes', 'Non-Toxic Vegetable Dye']
|
||||
]
|
||||
|
||||
out_io = io.StringIO()
|
||||
writer = csv.writer(out_io)
|
||||
for r in csv_rows:
|
||||
writer.writerow(r)
|
||||
|
||||
csv_file = SimpleUploadedFile(
|
||||
'alias_catalog.csv',
|
||||
out_io.getvalue().encode('utf-8'),
|
||||
content_type='text/csv'
|
||||
)
|
||||
|
||||
with patch('api.views.shipping_client.is_pincode_pickup_serviceable', return_value=(True, 'OK')):
|
||||
response = api_client.post(url, {'csv_file': csv_file}, format='multipart')
|
||||
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
prod = Product.objects.get(sku='BULK-CPT-003')
|
||||
assert prod.is_odop is True
|
||||
assert prod.odop_district == 'Ramanagara, Karnataka'
|
||||
assert prod.is_gi_tagged is True
|
||||
assert prod.gi_tag_name == 'Channapatna Toys'
|
||||
assert prod.is_sustainable is True
|
||||
assert prod.sustainable_type == 'Non-Toxic Vegetable Dye'
|
||||
|
||||
def test_bulk_upload_error_reporting_includes_initiatives(self, api_client, seller_user):
|
||||
"""Test when bulk row fails, the error CSV keeps initiative fields intact for review."""
|
||||
api_client.force_authenticate(user=seller_user)
|
||||
url = reverse('bulk-upload')
|
||||
|
||||
csv_rows = [
|
||||
[
|
||||
'Title', 'SKU', 'Category', 'Price', 'MRP', 'Stock', 'Description',
|
||||
'ODOP_Product', 'ODOP_District', 'GI_Tagged', 'GI_Tag_Name',
|
||||
'Sustainable_Product', 'Sustainable_Type'
|
||||
],
|
||||
[
|
||||
'Invalid Price Item', 'BULK-ERR-001', 'Handicrafts & Art',
|
||||
'-50', '100', '5', 'Negative price item.',
|
||||
'Yes', 'Moradabad', 'Yes', 'Moradabad Metal Craft',
|
||||
'Yes', 'Recycled Brass'
|
||||
]
|
||||
]
|
||||
|
||||
out_io = io.StringIO()
|
||||
writer = csv.writer(out_io)
|
||||
for r in csv_rows:
|
||||
writer.writerow(r)
|
||||
|
||||
csv_file = SimpleUploadedFile(
|
||||
'error_catalog.csv',
|
||||
out_io.getvalue().encode('utf-8'),
|
||||
content_type='text/csv'
|
||||
)
|
||||
|
||||
with patch('api.views.shipping_client.is_pincode_pickup_serviceable', return_value=(True, 'OK')):
|
||||
response = api_client.post(url, {'csv_file': csv_file}, format='multipart')
|
||||
|
||||
# When all rows fail validation, BulkUploadView returns HTTP 400 with error summary and error CSV
|
||||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||
assert response.data['summary']['skipped'] == 1
|
||||
assert response.data['error_csv'] is not None
|
||||
|
||||
# Verify error CSV contains initiative columns
|
||||
err_csv_text = response.data['error_csv']
|
||||
assert 'ODOP_Product' in err_csv_text
|
||||
assert 'ODOP_District' in err_csv_text
|
||||
assert 'GI_Tagged' in err_csv_text
|
||||
assert 'GI_Tag_Name' in err_csv_text
|
||||
assert 'Sustainable_Product' in err_csv_text
|
||||
assert 'Sustainable_Type' in err_csv_text
|
||||
assert 'Moradabad Metal Craft' in err_csv_text
|
||||
130
api/views.py
130
api/views.py
|
|
@ -1298,37 +1298,25 @@ class BulkUploadView(APIView):
|
|||
rows_skipped = 0
|
||||
|
||||
for idx, row in enumerate(products_data, start=1):
|
||||
title = (
|
||||
row.get('Title') or row.get('title') or row.get('Product Title') or
|
||||
row.get('Product Name') or row.get('Item Name') or row.get('Name') or
|
||||
row.get('name') or row.get('Product') or row.get('product')
|
||||
)
|
||||
sku = (
|
||||
row.get('SKU') or row.get('sku') or row.get('Item Code') or
|
||||
row.get('Product Code') or row.get('Code') or row.get('code') or
|
||||
row.get('SKU Code') or row.get('sku_code')
|
||||
)
|
||||
category = (
|
||||
row.get('Category') or row.get('category') or row.get('Product Category') or
|
||||
row.get('category_name') or "Handicrafts & Art"
|
||||
)
|
||||
desc = (
|
||||
row.get('Description') or row.get('description') or row.get('Story') or
|
||||
row.get('desc') or row.get('Product Description') or ''
|
||||
)
|
||||
norm_map = {str(k).strip().lower().replace(' ', '_').replace('-', '_'): v for k, v in row.items() if k is not None}
|
||||
def get_fld(*keys, default=''):
|
||||
for k in keys:
|
||||
if k in row and row[k] is not None:
|
||||
return row[k]
|
||||
k_norm = str(k).strip().lower().replace(' ', '_').replace('-', '_')
|
||||
if k_norm in norm_map and norm_map[k_norm] is not None:
|
||||
return norm_map[k_norm]
|
||||
return default
|
||||
|
||||
raw_price = (
|
||||
row.get('Price') or row.get('Price (INR)') or row.get('price') or
|
||||
row.get('Selling Price') or row.get('Unit Price') or row.get('unit_price') or
|
||||
row.get('Rate') or row.get('rate')
|
||||
)
|
||||
title = get_fld('Title', 'title', 'Product Title', 'Product Name', 'Item Name', 'Name', 'Product', 'product')
|
||||
sku = get_fld('SKU', 'sku', 'Item Code', 'Product Code', 'Code', 'code', 'SKU Code', 'sku_code')
|
||||
category = get_fld('Category', 'category', 'Product Category', 'category_name', default="Handicrafts & Art")
|
||||
desc = get_fld('Description', 'description', 'Story', 'desc', 'Product Description')
|
||||
|
||||
raw_price = get_fld('Price', 'Price (INR)', 'price', 'Selling Price', 'Unit Price', 'unit_price', 'Rate', 'rate')
|
||||
price_val = clean_num_val(raw_price, default_val=0.0)
|
||||
|
||||
raw_stock = (
|
||||
row.get('Stock') or row.get('stock') or row.get('Quantity') or
|
||||
row.get('qty') or row.get('Qty') or row.get('Inventory') or
|
||||
row.get('inventory') or row.get('Available Stock')
|
||||
)
|
||||
raw_stock = get_fld('Stock', 'stock', 'Quantity', 'qty', 'Qty', 'Inventory', 'inventory', 'Available Stock')
|
||||
stock_num = clean_num_val(raw_stock, default_val=0.0)
|
||||
stock_val = int(stock_num) if stock_num >= 0 else -1
|
||||
|
||||
|
|
@ -1338,7 +1326,7 @@ class BulkUploadView(APIView):
|
|||
row_errors.append("Missing Product Title")
|
||||
if not sku or not str(sku).strip():
|
||||
from .sku_generator import generate_product_sku
|
||||
sku = generate_product_sku(user, category_val)
|
||||
sku = generate_product_sku(user, category)
|
||||
else:
|
||||
sku = str(sku).strip().upper()
|
||||
if price_val <= 0:
|
||||
|
|
@ -1363,27 +1351,66 @@ class BulkUploadView(APIView):
|
|||
error_rows.append(err_dict)
|
||||
continue
|
||||
|
||||
raw_mrp = (
|
||||
row.get('MRP') or row.get('mrp') or row.get('Compare At Price') or
|
||||
row.get('compare_at_price') or row.get('Compare_At_Price')
|
||||
)
|
||||
raw_mrp = get_fld('MRP', 'mrp', 'Compare At Price', 'compare_at_price', 'Compare_At_Price')
|
||||
mrp_parsed = clean_num_val(raw_mrp, default_val=0.0)
|
||||
compare_at_val = mrp_parsed if mrp_parsed > 0 else (price_val * 1.25)
|
||||
|
||||
raw_weight = row.get('Package_Weight_Kg') or row.get('package_weight') or row.get('Weight') or row.get('weight')
|
||||
raw_weight = get_fld('Package_Weight_Kg', 'package_weight', 'Weight', 'weight', default='1.0')
|
||||
weight_parsed = clean_num_val(raw_weight, default_val=1.0)
|
||||
weight_val = weight_parsed if weight_parsed > 0 else 1.0
|
||||
|
||||
gi_str = str(row.get('GI_Tagged') or row.get('is_gi_tagged') or row.get('GI Tagged') or '').strip().lower()
|
||||
is_gi_tagged = gi_str in ('true', 'yes', '1', 'y')
|
||||
def parse_bool(v):
|
||||
if v is None:
|
||||
return False
|
||||
s = str(v).strip().lower()
|
||||
return s in ('true', 'yes', '1', 'y', 't')
|
||||
|
||||
shipping_profile = row.get('Shipping_Profile') or row.get('shipping_profile') or 'Standard Fragile'
|
||||
gi_val = get_fld('GI_Tagged', 'is_gi_tagged', 'GI Tagged', 'GI_Tag', 'GI Tag', 'gi_tagged', 'gi', 'GI')
|
||||
is_gi_tagged = parse_bool(gi_val)
|
||||
gi_tag_name = str(
|
||||
get_fld('GI_Tag_Name', 'gi_tag_name', 'GI Name', 'GI_Name', 'gi_name', 'GI Certificate', 'gi_ref', 'GI_Ref') or ''
|
||||
).strip()
|
||||
|
||||
odop_val = get_fld('ODOP_Product', 'is_odop', 'ODOP', 'odop', 'ODOP Product', 'isOdop', 'One District One Product')
|
||||
is_odop = parse_bool(odop_val)
|
||||
odop_district = str(
|
||||
get_fld('ODOP_District', 'odop_district', 'ODOP District', 'District', 'district', 'Cluster', 'cluster') or ''
|
||||
).strip()
|
||||
|
||||
sust_val = get_fld('Sustainable_Product', 'is_sustainable', 'Sustainable', 'sustainable', 'Sustainable Product', 'isSustainable', 'Eco Friendly')
|
||||
is_sustainable = parse_bool(sust_val)
|
||||
sustainable_type = str(
|
||||
get_fld('Sustainable_Type', 'sustainable_type', 'Sustainability_Type', 'sustainability_type', 'Sustainable Type', 'Eco_Type') or ''
|
||||
).strip()
|
||||
|
||||
additional_images_raw = get_fld('Additional_Images', 'additional_images', 'Gallery_Images', 'gallery_images', 'Supporting_Images')
|
||||
additional_images = []
|
||||
if isinstance(additional_images_raw, list):
|
||||
additional_images = additional_images_raw
|
||||
elif isinstance(additional_images_raw, str) and additional_images_raw.strip():
|
||||
for delim in [';', '|', ',']:
|
||||
if delim in additional_images_raw:
|
||||
additional_images = [img.strip() for img in additional_images_raw.split(delim) if img.strip()]
|
||||
break
|
||||
if not additional_images and additional_images_raw.strip():
|
||||
additional_images = [additional_images_raw.strip()]
|
||||
|
||||
video_url = str(get_fld('Video_URL', 'video_url', 'Video', 'video') or '').strip() or None
|
||||
|
||||
raw_processing_days = get_fld('Processing_Days', 'processing_days', default=3)
|
||||
try:
|
||||
processing_days = int(clean_num_val(raw_processing_days, default_val=3))
|
||||
if processing_days <= 0:
|
||||
processing_days = 3
|
||||
except:
|
||||
processing_days = 3
|
||||
|
||||
shipping_profile = get_fld('Shipping_Profile', 'shipping_profile', default='Standard Fragile')
|
||||
|
||||
# Determine image URL
|
||||
image_url = (
|
||||
images_map.get(sku) or images_map.get(sku.upper()) or
|
||||
row.get('Image URL') or row.get('Image_URL') or row.get('image_url') or
|
||||
row.get('image') or row.get('Image') or row.get('Image Link') or row.get('image_link')
|
||||
get_fld('Image URL', 'Image_URL', 'image_url', 'image', 'Image', 'Image Link', 'image_link')
|
||||
)
|
||||
if not image_url:
|
||||
image_url = 'https://images.unsplash.com/photo-1544022613-e87ca75a784a?auto=format&fit=crop&q=80&w=600'
|
||||
|
|
@ -1401,6 +1428,16 @@ class BulkUploadView(APIView):
|
|||
existing_product.image = image_url
|
||||
existing_product.description = desc
|
||||
existing_product.is_gi_tagged = is_gi_tagged
|
||||
existing_product.gi_tag_name = gi_tag_name if is_gi_tagged else ''
|
||||
existing_product.is_odop = is_odop
|
||||
existing_product.odop_district = odop_district if is_odop else ''
|
||||
existing_product.is_sustainable = is_sustainable
|
||||
existing_product.sustainable_type = sustainable_type if is_sustainable else ''
|
||||
if additional_images:
|
||||
existing_product.additional_images = additional_images
|
||||
if video_url:
|
||||
existing_product.video_url = video_url
|
||||
existing_product.processing_days = processing_days
|
||||
existing_product.package_weight = weight_val
|
||||
existing_product.shipping_profile = shipping_profile
|
||||
existing_product.upload_source = 'bulk'
|
||||
|
|
@ -1422,6 +1459,14 @@ class BulkUploadView(APIView):
|
|||
'image': image_url,
|
||||
'description': desc,
|
||||
'is_gi_tagged': is_gi_tagged,
|
||||
'gi_tag_name': gi_tag_name if is_gi_tagged else '',
|
||||
'is_odop': is_odop,
|
||||
'odop_district': odop_district if is_odop else '',
|
||||
'is_sustainable': is_sustainable,
|
||||
'sustainable_type': sustainable_type if is_sustainable else '',
|
||||
'additional_images': additional_images,
|
||||
'video_url': video_url,
|
||||
'processing_days': processing_days,
|
||||
'package_weight': weight_val,
|
||||
'shipping_profile': shipping_profile,
|
||||
'upload_source': 'bulk',
|
||||
|
|
@ -1446,7 +1491,12 @@ class BulkUploadView(APIView):
|
|||
# 4. Generate Error CSV if any errors occurred
|
||||
if error_rows:
|
||||
out_io = io.StringIO()
|
||||
fnames = ['Row_Number', 'Title', 'SKU', 'Category', 'Price', 'MRP', 'Stock', 'Description', 'GI_Tagged', 'Package_Weight_Kg', 'Shipping_Profile', 'Error_Reason']
|
||||
fnames = [
|
||||
'Row_Number', 'Title', 'SKU', 'Category', 'Price', 'MRP', 'Stock',
|
||||
'Description', 'ODOP_Product', 'ODOP_District', 'GI_Tagged', 'GI_Tag_Name',
|
||||
'Sustainable_Product', 'Sustainable_Type', 'Package_Weight_Kg',
|
||||
'Shipping_Profile', 'Image_URL', 'Additional_Images', 'Video_URL', 'Error_Reason'
|
||||
]
|
||||
writer = csv.DictWriter(out_io, fieldnames=fnames, extrasaction='ignore')
|
||||
writer.writeheader()
|
||||
for er in error_rows:
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
[pytest]
|
||||
DJANGO_SETTINGS_MODULE = config.settings
|
||||
python_files = tests.py test_*.py *_tests.py
|
||||
testpaths = api
|
||||
addopts = -p no:cacheprovider
|
||||
|
|
|
|||
Loading…
Reference in a new issue