Skip to content

Commit c15957f

Browse files
syedsajjadkazmiiNawfalAhmedMuhammad Noyan  Aziz
authored
feat: extract ct data into csv command (#367)
* feat: extract ct data into csv command * feat: create commercetools product command * feat: add update ct product management command * feat: extract discounts * chore: use custom ct client for call in command * chore: update ct csutom client * feat: rebase and finalize --------- Co-authored-by: Nawfal Ahmed <nawfal.ahmed@arbisoft.com> Co-authored-by: Muhammad Noyan Aziz <noyan.aziz@A006-01474.local>
1 parent 86fb75b commit c15957f

File tree

4 files changed

+376
-0
lines changed

4 files changed

+376
-0
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import json
2+
3+
from commercetools.platform.models import (
4+
ProductDraft,
5+
ProductPriceModeEnum,
6+
ProductTypeResourceIdentifier,
7+
ProductVariantDraft
8+
)
9+
10+
from commerce_coordinator.apps.commercetools.management.commands._ct_api_client_command import (
11+
CommercetoolsAPIClientCommand
12+
)
13+
14+
product_json = '''
15+
16+
17+
18+
'''
19+
20+
21+
class Command(CommercetoolsAPIClientCommand):
22+
help = "Create a commercetools product from a JSON string or file"
23+
24+
def handle(self, *args, **options):
25+
if product_json:
26+
try:
27+
product_data = json.loads(product_json)
28+
except json.JSONDecodeError as e:
29+
self.stderr.write(f"Invalid JSON format: {e}")
30+
return
31+
else:
32+
print("\n\n\n\nNo JSON data provided.\n\n\n\n")
33+
return
34+
35+
product_type_data = product_data.get("productType")
36+
if product_type_data:
37+
product_type = ProductTypeResourceIdentifier(id=product_type_data["id"])
38+
else:
39+
print("\n\n\n\nMissing productType data.\n\n\n\n")
40+
return
41+
42+
master_variant_data = product_data.get("masterVariant")
43+
variants_data = product_data.get("variants", [])
44+
45+
master_variant = ProductVariantDraft(**master_variant_data)
46+
variants = [ProductVariantDraft(**variant) for variant in variants_data]
47+
48+
product_draft_data = {
49+
"key": product_data.get("key"),
50+
"name": product_data.get("name"),
51+
"description": product_data.get("description"),
52+
"slug": product_data.get("slug"),
53+
"price_mode": ProductPriceModeEnum.STANDALONE,
54+
"publish": product_data.get("publish"),
55+
"tax_category": product_data.get("taxCategory"),
56+
"master_variant": master_variant,
57+
"variants": variants,
58+
"product_type": product_type
59+
}
60+
61+
try:
62+
product_draft = ProductDraft(**product_draft_data)
63+
created_product = self.ct_api_client.base_client.products.create(draft=product_draft)
64+
print(f"\n\n\n\nSuccessfully created product with ID: {created_product.id}")
65+
except Exception as e:
66+
print(f"\n\n\n\nError creating product: {e}")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import csv
2+
from datetime import datetime
3+
from typing import Dict, List
4+
5+
from commerce_coordinator.apps.commercetools.http_api_client import CTCustomAPIClient
6+
from commerce_coordinator.apps.commercetools.management.commands._timed_command import TimedCommand
7+
8+
9+
class Command(TimedCommand):
10+
help = "Fetch and verify discount attributes from CommerceTools"
11+
12+
def handle(self, *args, **options):
13+
try:
14+
ct_api_client = CTCustomAPIClient()
15+
except Exception as e:
16+
print(f"Error initializing Commercetools API client: {e}")
17+
return
18+
19+
# Fetch discounts based on type
20+
discounts = self.fetch_discounts(ct_api_client)
21+
22+
# Write data to CSV
23+
self.write_attributes_to_csv(discounts)
24+
25+
def fetch_discounts(self, ct_api_client):
26+
page_size = 500
27+
28+
lastId = None
29+
should_continue = True
30+
results = []
31+
while should_continue:
32+
if lastId is None:
33+
response = ct_api_client._make_request(
34+
method="GET",
35+
endpoint="discount-codes",
36+
params={
37+
"limit": page_size,
38+
"sort": "id asc",
39+
"expand": "cartDiscounts[*]",
40+
}
41+
)
42+
else:
43+
response = ct_api_client._make_request(
44+
method="GET",
45+
endpoint="discount-codes",
46+
params={
47+
"limit": page_size,
48+
"sort": "id asc",
49+
"expand": "cartDiscounts[*]",
50+
"where": f'id > "{lastId}"'
51+
}
52+
)
53+
if not response:
54+
print("Failed to get discount codes with code from Commercetools.")
55+
return None
56+
57+
batch_results = response["results"]
58+
results.extend(batch_results)
59+
should_continue = (len(batch_results) == page_size)
60+
61+
if batch_results:
62+
lastId = batch_results[-1]["id"]
63+
64+
return results
65+
66+
def write_attributes_to_csv(self, discounts: List[Dict]):
67+
if not discounts:
68+
print(f"No discounts found.")
69+
return
70+
71+
# Dynamically extract all unique keys across all discount dictionaries
72+
discounts_new = []
73+
for discount in discounts:
74+
cart_discount = discount["cartDiscounts"][0]["obj"]
75+
discount = {
76+
"name": discount["name"].get('en-US', ''),
77+
"code": discount["code"],
78+
"validFrom": discount.get("validFrom", None),
79+
"validUntil": discount.get("validUntil", None),
80+
"maxApplications": discount.get("maxApplications", None),
81+
"maxApplicationsPerCustomer": discount.get("maxApplicationsPerCustomer", None),
82+
"cartDiscountName": cart_discount["name"].get('en-US', ''),
83+
"cartDiscountKey": cart_discount["key"],
84+
"discountType": cart_discount["custom"]["fields"].get('discountType'),
85+
"category": cart_discount["custom"]["fields"].get('category'),
86+
"channel": cart_discount["custom"]["fields"].get('channel'),
87+
}
88+
discounts_new.append(discount)
89+
90+
# Define CSV filename with discount type and date
91+
filename = f"discount_{datetime.now().strftime('%Y%m%d')}.csv"
92+
93+
# Write to CSV
94+
with open(filename, "w", newline="") as output_file:
95+
dict_writer = csv.DictWriter(output_file, fieldnames=discounts_new[0].keys())
96+
dict_writer.writeheader()
97+
dict_writer.writerows(discounts_new)
98+
99+
print(f"\n\n\n\n\n\n\nCSV file '{filename}' written successfully with {len(discounts_new)} records.")
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import csv
2+
from datetime import datetime
3+
from enum import Enum
4+
5+
from commerce_coordinator.apps.commercetools.management.commands._ct_api_client_command import (
6+
CommercetoolsAPIClientCommand
7+
)
8+
9+
10+
# Enum for product types
11+
class ProductType(Enum):
12+
EDX_COURSE_ENTITLEMENT = "edx_course_entitlement"
13+
EDX_PROGRAM = "edx_program"
14+
OC_SELF_PACED = "oc_self_paced"
15+
EDX_COURSE = "oc_self_paced"
16+
17+
18+
STAGE_PRODUCT_TYPE_ID_MAPPING = {
19+
ProductType.EDX_COURSE_ENTITLEMENT.value: "12e5510c-a4d6-4301-9caf-17053e57ff71",
20+
ProductType.EDX_PROGRAM.value: "79fb6abe-8373-4dec-a8d1-51242b1798b8",
21+
ProductType.OC_SELF_PACED.value: "9f8ec882-043a-4225-8811-00ac5acfd580"
22+
}
23+
24+
PROD_PRODUCT_TYPE_ID_MAPPING = {
25+
ProductType.EDX_COURSE_ENTITLEMENT.value: "9f1f189a-4d79-4eaa-9c6e-cfcb61aa779f",
26+
ProductType.EDX_PROGRAM.value: "c6a2d629-a50e-4d88-bd01-ab05a0617eae",
27+
ProductType.EDX_COURSE.value: "b241ac79-fee2-461d-b714-8f3c4a1c4c0e"
28+
}
29+
30+
31+
class Command(CommercetoolsAPIClientCommand):
32+
help = "Fetch and verify course attributes from CommerceTools"
33+
34+
def handle(self, *args, **options):
35+
# Specify product type to fetch
36+
product_type = ProductType.EDX_PROGRAM
37+
38+
# Fetch products based on type
39+
products = self.fetch_products(product_type)
40+
41+
# Write data to CSV
42+
self.write_attributes_to_csv(products, product_type)
43+
44+
def fetch_products(self, product_type):
45+
limit = 500
46+
offset = 0
47+
products = []
48+
49+
product_type_id = PROD_PRODUCT_TYPE_ID_MAPPING.get(product_type.value)
50+
51+
while True:
52+
products_result = self.ct_api_client.base_client.products.query(
53+
limit=limit,
54+
offset=offset,
55+
where=f"productType(id=\"{product_type_id}\")"
56+
)
57+
for product in products_result.results:
58+
attributes = self.extract_product_attributes(product, product_type.value)
59+
products.extend(attributes)
60+
61+
if products_result.offset + products_result.limit >= products_result.total:
62+
break
63+
offset += limit
64+
65+
return products
66+
67+
def extract_product_attributes(self, product, product_type):
68+
# Extract common product-level attributes
69+
common_attributes = {
70+
"product_type": product_type,
71+
"product_id": product.id,
72+
"product_key": product.key,
73+
"published_status": product.master_data.published,
74+
"name": product.master_data.current.name.get('en-US', ''),
75+
"slug": product.master_data.current.slug.get('en-US', ''),
76+
"description": (
77+
product.master_data.current.description.get('en-US', '')
78+
if product.master_data.current.description
79+
else ''
80+
),
81+
"date_created": product.created_at,
82+
"master_variant_key": product.master_data.current.master_variant.key,
83+
"master_variant_sku": product.master_data.current.master_variant.sku,
84+
"master_variant_image_url": (
85+
product.master_data.current.master_variant.images[0].url
86+
if product.master_data.current.master_variant.images
87+
else None
88+
),
89+
}
90+
91+
product_rows = [] # This will hold the product and variant rows
92+
93+
# Add the master variant attributes
94+
if len(product.master_data.current.variants) == 0:
95+
master_variant_attributes = {attr.name: attr.value for attr in
96+
product.master_data.current.master_variant.attributes}
97+
product_rows.append({**common_attributes, **master_variant_attributes})
98+
99+
# Add attributes for each variant and create a separate row, including variant_key and variant_sku
100+
for variant in product.master_data.current.variants:
101+
variant_attributes = {attr.name: attr.value for attr in variant.attributes}
102+
variant_row = {
103+
**common_attributes,
104+
"variant_key": variant.key, # Add variant_key
105+
"variant_sku": variant.sku, # Add variant_sku
106+
"variant_image_url": (
107+
variant.images[0].url
108+
if variant.images
109+
else None
110+
),
111+
**variant_attributes,
112+
}
113+
# Create a new row for each variant, combining common product data with variant-specific attributes
114+
product_rows.append(variant_row)
115+
116+
return product_rows
117+
118+
def write_attributes_to_csv(self, products, product_type):
119+
if not products:
120+
print(f"No products found for type {product_type}.")
121+
return
122+
123+
# Dynamically extract all unique keys across all product dictionaries
124+
keys = set()
125+
for product in products:
126+
keys.update(product.keys())
127+
128+
# Convert keys set back to a list and sort them if you want a consistent order
129+
keys = sorted(list(keys))
130+
131+
# Define CSV filename with product type and date
132+
filename = f"{product_type.value}_attributes_{datetime.now().strftime('%Y%m%d')}.csv"
133+
134+
# Write to CSV
135+
with open(filename, "w", newline="") as output_file:
136+
dict_writer = csv.DictWriter(output_file, fieldnames=keys)
137+
dict_writer.writeheader()
138+
dict_writer.writerows(products)
139+
140+
print(f"\n\n\n\n\n\n\nCSV file '{filename}' written successfully with {len(products)} records.")
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import json
2+
3+
from commerce_coordinator.apps.commercetools.http_api_client import CTCustomAPIClient
4+
from commerce_coordinator.apps.commercetools.management.commands._ct_api_client_command import (
5+
CommercetoolsAPIClientCommand
6+
)
7+
8+
product_id = '21533075-1710-4cbc-88d2-a18ba4176fdd'
9+
product_json = '''
10+
11+
{
12+
"version": 9,
13+
"actions": [
14+
{
15+
"action": "setDescription",
16+
"description": {
17+
"en-US": "<p>This course reflects the most current version of the PMP exam.</p>"
18+
}
19+
},
20+
{
21+
"action": "publish"
22+
}
23+
]
24+
}
25+
26+
27+
'''
28+
29+
30+
class Command(CommercetoolsAPIClientCommand):
31+
help = "Update a commercetools product from a JSON string or file"
32+
33+
def handle(self, *args, **options):
34+
if product_json:
35+
try:
36+
product_data = json.loads(product_json)
37+
except json.JSONDecodeError as e:
38+
self.stderr.write(f"Invalid JSON format: {e}")
39+
return
40+
else:
41+
print("\n\n\n\nNo JSON data provided.\n\n\n\n")
42+
return
43+
44+
version = product_data.get("version")
45+
actions = product_data.get("actions")
46+
47+
if not product_id or not version or not actions:
48+
print("\n\n\n\nMissing product ID, version, or actions.\n\n\n\n")
49+
return
50+
51+
# Initialize the custom commercetools client
52+
ct_client = CTCustomAPIClient()
53+
54+
# Prepare the data for updating the product
55+
update_payload = {
56+
"version": version,
57+
"actions": actions
58+
}
59+
60+
# Make the request to update the product
61+
endpoint = f"products/{product_id}"
62+
response = ct_client._make_request(
63+
method="POST",
64+
endpoint=endpoint,
65+
json=update_payload
66+
)
67+
68+
if response:
69+
print(f"\n\n\n\n\nSuccessfully updated product with ID: {response.get('id')}\n\n\n\n\n")
70+
else:
71+
print("\n\n\n\n\nError updating product.\n\n\n\n\n\n")

0 commit comments

Comments
 (0)