From ecc02b5e03425d983d5dbf0a64dcd3c66e8ea927 Mon Sep 17 00:00:00 2001 From: Jack Zhang Date: Sat, 31 Jul 2021 22:26:08 +0800 Subject: [PATCH 1/6] open sample data with utf-8 --- saleor/core/utils/random_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/saleor/core/utils/random_data.py b/saleor/core/utils/random_data.py index deab432c42f..b07900888e4 100644 --- a/saleor/core/utils/random_data.py +++ b/saleor/core/utils/random_data.py @@ -313,7 +313,7 @@ def create_products_by_schema(placeholder_dir, create_images): path = os.path.join( settings.PROJECT_ROOT, "saleor", "static", "populatedb_data.json" ) - with open(path) as f: + with open(path, encoding='utf-8') as f: db_items = json.load(f) types = defaultdict(list) # Sort db objects by its model From 84c1771c02a3bd23132b3e69c64739904470e819 Mon Sep 17 00:00:00 2001 From: Jack Zhang Date: Thu, 5 Aug 2021 16:38:52 +0800 Subject: [PATCH 2/6] batch import products from shopify --- .../product/bulk_mutations/products.py | 263 +++++++++++++++++- saleor/graphql/product/schema.py | 2 + saleor/product/thumbnails.py | 30 +- saleor/third/shopify/product.py | 44 +++ 4 files changed, 334 insertions(+), 5 deletions(-) create mode 100644 saleor/third/shopify/product.py diff --git a/saleor/graphql/product/bulk_mutations/products.py b/saleor/graphql/product/bulk_mutations/products.py index 903e1eba11c..38470c74a4d 100644 --- a/saleor/graphql/product/bulk_mutations/products.py +++ b/saleor/graphql/product/bulk_mutations/products.py @@ -1,16 +1,23 @@ -from collections import defaultdict - +import datetime import graphene -from django.core.exceptions import ValidationError +from collections import defaultdict +from decimal import Decimal +from django.core.exceptions import ValidationError, ObjectDoesNotExist from django.db import transaction +from django.utils.text import slugify +from measurement.measures import Weight from ....core.permissions import ProductPermissions, ProductTypePermissions +from ....menu import models as menu_models from ....order import OrderStatus, models as order_models from ....product import models from ....product.error_codes import ProductErrorCode from ....product.tasks import update_product_minimal_variant_price_task +from ....product.thumbnails import create_product_images_from_url from ....product.utils import delete_categories -from ....product.utils.attributes import generate_name_for_variant +from ....product.utils.attributes import generate_name_for_variant, \ + associate_attribute_values_to_instance +from ....third.shopify.product import Shopify from ....warehouse import models as warehouse_models from ....warehouse.error_codes import StockErrorCode from ...core.mutations import ( @@ -96,6 +103,254 @@ def bulk_action(cls, queryset, is_published): queryset.update(is_published=is_published) +class ProductBulkCreateFromShopify(BaseMutation): + # for code challenge, just hard code some properties + DEF_PRODUCT_TYPE_ID = 16 + DEF_PRODUCT_CATEGORY_ID = 24 + DEF_SIZE_ATTRIBUTE_ID = 13 + DEF_COLOR_ATTRIBUTE_ID = 14 + DEF_WAREHOUSE_ID = "74b279b5-77a5-49d3-9ba6-789eac7a2829" + DEF_MENU_ITEM_ID = 20 + + size_color_cache = {} + def_warehouse_cache = None + + products = graphene.Field( + Product, description="List of imported products." + ) + + class Arguments: + shop_url = graphene.String( + required=True, + description="A Shopify website URL, e.g. .myshopify.com" + ) + access_token = graphene.String( + required=True, + description="An access token for the above website", + ) + collection_id = graphene.ID( + required=True, + description="The ID of the collection to be imported", + ) + + class Meta: + model = models.Product + description = "Bulk import products from shopify collection." + permissions = (ProductPermissions.MANAGE_PRODUCTS,) + error_type_class = ProductError + error_type_field = "product_errors" + + @classmethod + def create_attribute_values(cls, attribute_id, values): + if attribute_id not in cls.size_color_cache: + attribute_cache = { + "attribute": models.Attribute.objects.get(pk=attribute_id), + "values": models.AttributeValue.objects.filter( + attribute_id=attribute_id + ) + } + cls.size_color_cache[attribute_id] = attribute_cache + else: + attribute_cache = cls.size_color_cache[attribute_id] + + for val in values: + val = val.replace(' ', '-') # data bug hack + try: + exist_attr_val = next( + v for v in attribute_cache["values"].iterator() if v.name == val + ) + except StopIteration: + exist_attr_val = None + + if not exist_attr_val: + new_attribute_val = models.AttributeValue.objects.create( + attribute=attribute_cache["attribute"], name=val, slug=slugify(val) + ) + attribute_cache["values"] |= models.AttributeValue.objects.filter( + pk=new_attribute_val.pk + ) + + @classmethod + def get_attribute_value(cls, attribute_id, value): + value = value.replace(' ', '-') + attribute_cache = cls.size_color_cache[attribute_id] + exist_attr_val = next( + v for v in attribute_cache["values"].iterator() if v.name == value + ) + + if not exist_attr_val: + raise ObjectDoesNotExist("attribute with value not exist: " + value) + + return attribute_cache["attribute"], exist_attr_val + + @classmethod + def get_default_warehouse(cls): + if not cls.def_warehouse_cache: + cls.def_warehouse_cache = warehouse_models.Warehouse.objects.get( + pk=cls.DEF_WAREHOUSE_ID + ) + return cls.def_warehouse_cache + + @classmethod + def create_variants(cls, product, shopify_variants): + new_variants = [] + for variant in shopify_variants: + sva = variant.attributes + weight = Weight() + setattr(weight, sva.get("weight_unit"), sva.get("weight")) + new_variant = models.ProductVariant.objects.create( + product=product, + weight=weight, + sku=variant.attributes.get("sku"), + price_amount=Decimal(sva.get("price")) + ) + + size_attr, exist_size = cls.get_attribute_value( + cls.DEF_SIZE_ATTRIBUTE_ID, variant.option1 + ) + color_attr, exist_color = cls.get_attribute_value( + cls.DEF_COLOR_ATTRIBUTE_ID, variant.option2 + ) + + warehouse_models.Stock.objects.create( + warehouse=cls.get_default_warehouse(), + product_variant=new_variant, + quantity=sva.get("inventory_quantity") + ) + associate_attribute_values_to_instance(new_variant, size_attr, exist_size) + associate_attribute_values_to_instance(new_variant, color_attr, exist_color) + new_variants.append(new_variant) + + return new_variants + + @classmethod + def create_color_sizes(cls, shopify_products): + size_values = [] + color_values = [] + for spd in shopify_products: + for variant in spd.attributes["variants"]: + if variant.option1 not in size_values: + size_values.append(variant.option1) + if variant.option2 not in color_values: + color_values.append(variant.option2) + + cls.create_attribute_values(cls.DEF_SIZE_ATTRIBUTE_ID, size_values) + cls.create_attribute_values(cls.DEF_COLOR_ATTRIBUTE_ID, color_values) + + @classmethod + def get_product_by_shopify_id(cls, shopify_product_ids): + products = models.Product.objects.filter( + metadata__shopifyid__in=shopify_product_ids + ) + return products + + @classmethod + def create_product_images(cls, products, product_images): + for product in products: + image_urls = product_images[product.id] + create_product_images_from_url.delay(product.id, image_urls) + + @classmethod + @transaction.atomic + def create_products(cls, shopify_products): + def_product_type = models.ProductType.objects.get(pk=cls.DEF_PRODUCT_TYPE_ID) + def_category = models.Category.objects.get(pk=cls.DEF_PRODUCT_CATEGORY_ID) + + products = list() + product_images = dict() + for shopy_product in shopify_products: + spa = shopy_product.attributes + new_product = models.Product.objects.create( + name=spa["title"], + slug=slugify(spa["title"] + str(spa["id"])), + product_type=def_product_type, + category=def_category, + description=spa["body_html"], + is_published=True, + visible_in_listings=True, + available_for_purchase=datetime.date.today(), + metadata={"shopifyid": str(spa["id"])} + ) + + cls.create_variants(new_product, spa["variants"]) + products.append(new_product) + + product_images[new_product.id] = [] + for image in spa["images"]: + product_images[new_product.id].append(image.src) + + return products, product_images + + @classmethod + @transaction.atomic + def create_collection(cls, shopify_collection, products): + all_collections = models.Collection.objects.all() + sca = shopify_collection.attributes + collection_name = sca["title"] + i = 1 + while True: + try: + collection = next(filter( + lambda c: c.name == collection_name, all_collections.iterator() + )) + except StopIteration: + collection = None + + if not collection: + break + i = i + 1 + collection_name = sca["title"] + "(" + str(i) + ")" + + new_collection = models.Collection.objects.create( + name=collection_name, + slug=slugify(collection_name), + is_published=True, + description=sca["body_html"], + metadata={"shopifyid": str(sca["collection_id"])} + ) + + collection_products = [] + for product in products: + new_col_product = models.CollectionProduct( + collection=new_collection, + product=product + ) + collection_products.append(new_col_product) + models.CollectionProduct.objects.bulk_create(collection_products) + cls.create_menu_item(new_collection) + + @classmethod + def create_menu_item(cls, collection): + menu = menu_models.Menu.objects.get(name="navbar") + menu_models.MenuItem.objects.create( + name=collection.name, + menu=menu, + collection=collection, + parent_id=cls.DEF_MENU_ITEM_ID + ) + + @classmethod + def perform_mutation(cls, root, info, **data): + shopify = Shopify(data["shop_url"]) + collection_id = data["collection_id"] + shopify_collection = shopify.get_collection(collection_id) + shopify_products = shopify.get_collection_products(collection_id) + + shopify_product_ids = list(map(lambda p: str(p.id), shopify_products)) + exist_products = cls.get_product_by_shopify_id(shopify_product_ids) + exist_product_ids = list(map(lambda p: p.metadata["shopifyid"], exist_products)) + shopify_products = list(filter( + lambda p: str(p.id) not in exist_product_ids, shopify_products + )) + + cls.create_color_sizes(shopify_products) + products, product_images = cls.create_products(shopify_products) + cls.create_collection(shopify_collection, products + list(exist_products)) + cls.create_product_images(products, product_images) + + return cls(products=products) + + class ProductBulkDelete(ModelBulkDeleteMutation): class Arguments: ids = graphene.List( diff --git a/saleor/graphql/product/schema.py b/saleor/graphql/product/schema.py index 9825dbc26da..3be2eb8ebbb 100644 --- a/saleor/graphql/product/schema.py +++ b/saleor/graphql/product/schema.py @@ -27,6 +27,7 @@ ProductVariantStocksCreate, ProductVariantStocksDelete, ProductVariantStocksUpdate, + ProductBulkCreateFromShopify, ) from .enums import StockAvailability from .filters import ( @@ -534,3 +535,4 @@ class ProductMutations(graphene.ObjectType): variant_image_assign = VariantImageAssign.Field() variant_image_unassign = VariantImageUnassign.Field() + product_bulk_create_from_shopify = ProductBulkCreateFromShopify.Field() diff --git a/saleor/product/thumbnails.py b/saleor/product/thumbnails.py index 8651eed5d8d..50eed067331 100644 --- a/saleor/product/thumbnails.py +++ b/saleor/product/thumbnails.py @@ -1,6 +1,13 @@ +import logging +import urllib + +from django.core.files.uploadedfile import SimpleUploadedFile + from ..celeryconf import app from ..core.utils import create_thumbnails -from .models import Category, Collection, ProductImage +from .models import Category, Collection, Product, ProductImage + +logger = logging.getLogger(__name__) @app.task @@ -29,3 +36,24 @@ def create_collection_background_image_thumbnails(collection_id: str): size_set="background_images", image_attr="background_image", ) + + +@app.task +def create_product_images_from_url(product_id, image_urls): + product = Product.objects.get(pk=product_id) + index = 1 + for image_url in image_urls: + # url_path = urllib.parse.urlparse(image_url).path + # ext = os.path.splitext(url_path)[1] + # img_path = os.path.join("/", "%s_%d%s" % (product.slug, index, ext)) + + try: + image_bytes = urllib.request.urlopen(image_url).read() + image = SimpleUploadedFile(product.slug + ".jpg", image_bytes, "image/png") + except Exception as e: + logger.exception("Unable to download image: " + image_url, e) + continue + + product_image = product.images.create(image=image, alt="") + create_product_thumbnails(product_image.pk) + index = index + 1 diff --git a/saleor/third/shopify/product.py b/saleor/third/shopify/product.py new file mode 100644 index 00000000000..e1322b374d6 --- /dev/null +++ b/saleor/third/shopify/product.py @@ -0,0 +1,44 @@ +import re +import shopify + +from django.core.exceptions import ValidationError, ImproperlyConfigured +from ...product.error_codes import ProductErrorCode + + +class Shopify: + API_KEY = "1140ed801c09cd500756cf467191814c" + API_PASSWORD = "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" + API_VERSION = "2021-07" + + def __init__(self, shop_url): + self.has_init = False + + match = re.match(r"https?://(.*?)/?$", shop_url) + if not match: + raise ValidationError( + "invalid shop url", code=ProductErrorCode.INVALID.value, + ) + + shop_url = "https://%s:%s@%s/admin/api/%s" % ( + self.API_KEY, + self.API_PASSWORD, + match.group(1), + self.API_VERSION + ) + shopify.ShopifyResource.set_site(shop_url) + shop = shopify.Shop.current() + if shop: + self.has_init = True + + def get_collection_products(self, collection_id): + if not self.has_init: + raise ImproperlyConfigured("shopify has not init") + + products = shopify.Product.find(collection_id=collection_id) + return products + + def get_collection(self, collection_id): + if not self.has_init: + raise ImproperlyConfigured("shopify has not init") + + return shopify.CollectionListing.find(id_=collection_id) From dcacbf777606c69c27da2deca608d7e8a290575b Mon Sep 17 00:00:00 2001 From: Jack Zhang Date: Fri, 6 Aug 2021 08:18:11 +0800 Subject: [PATCH 3/6] support import from different shopify shop --- .../product/bulk_mutations/products.py | 91 ++++++++++++------- saleor/third/shopify/product.py | 44 --------- 2 files changed, 59 insertions(+), 76 deletions(-) delete mode 100644 saleor/third/shopify/product.py diff --git a/saleor/graphql/product/bulk_mutations/products.py b/saleor/graphql/product/bulk_mutations/products.py index 38470c74a4d..a327f1d8d0c 100644 --- a/saleor/graphql/product/bulk_mutations/products.py +++ b/saleor/graphql/product/bulk_mutations/products.py @@ -17,7 +17,7 @@ from ....product.utils import delete_categories from ....product.utils.attributes import generate_name_for_variant, \ associate_attribute_values_to_instance -from ....third.shopify.product import Shopify +from ....third.shopify import Shopify from ....warehouse import models as warehouse_models from ....warehouse.error_codes import StockErrorCode from ...core.mutations import ( @@ -192,47 +192,75 @@ def get_default_warehouse(cls): return cls.def_warehouse_cache @classmethod - def create_variants(cls, product, shopify_variants): + def create_variants(cls, product, shopify_product): + size_attr_index, color_attr_index = cls.get_size_color_index(shopify_product) + if not (size_attr_index > 0 and color_attr_index > 0): + return + new_variants = [] - for variant in shopify_variants: - sva = variant.attributes + new_stocks = [] + for variant in shopify_product.variants: weight = Weight() - setattr(weight, sva.get("weight_unit"), sva.get("weight")) - new_variant = models.ProductVariant.objects.create( + setattr(weight, variant.weight_unit, variant.weight) + new_variant = models.ProductVariant( product=product, weight=weight, - sku=variant.attributes.get("sku"), - price_amount=Decimal(sva.get("price")) + sku=variant.sku, + price_amount=Decimal(variant.price) ) size_attr, exist_size = cls.get_attribute_value( - cls.DEF_SIZE_ATTRIBUTE_ID, variant.option1 + cls.DEF_SIZE_ATTRIBUTE_ID, + getattr(variant, "option" + str(size_attr_index)) ) color_attr, exist_color = cls.get_attribute_value( - cls.DEF_COLOR_ATTRIBUTE_ID, variant.option2 + cls.DEF_COLOR_ATTRIBUTE_ID, + getattr(variant, "option" + str(color_attr_index)) ) - warehouse_models.Stock.objects.create( + new_variant.save() + new_stock = warehouse_models.Stock( warehouse=cls.get_default_warehouse(), product_variant=new_variant, - quantity=sva.get("inventory_quantity") + quantity=variant.inventory_quantity ) + new_stocks.append(new_stock) + associate_attribute_values_to_instance(new_variant, size_attr, exist_size) associate_attribute_values_to_instance(new_variant, color_attr, exist_color) new_variants.append(new_variant) + warehouse_models.Stock.objects.bulk_create(new_stocks) return new_variants + @classmethod + def get_size_color_index(cls, product): + size_attr_index = 0 + color_attr_index = 0 + for option in product.options: + name = option.name.lower() + if name == 'color': + color_attr_index = option.position + elif name == 'size': + size_attr_index = option.position + return size_attr_index, color_attr_index + @classmethod def create_color_sizes(cls, shopify_products): size_values = [] color_values = [] for spd in shopify_products: - for variant in spd.attributes["variants"]: - if variant.option1 not in size_values: - size_values.append(variant.option1) - if variant.option2 not in color_values: - color_values.append(variant.option2) + size_attr_index, color_attr_index = cls.get_size_color_index(spd) + if not (size_attr_index > 0 and color_attr_index > 0): + continue + + for variant in spd.variants: + size_val = getattr(variant, "option" + str(size_attr_index)) + color_val = getattr(variant, "option" + str(color_attr_index)) + if not (size_val in size_values): + size_values.append(size_val) + if not (color_val in color_values): + color_values.append(color_val) cls.create_attribute_values(cls.DEF_SIZE_ATTRIBUTE_ID, size_values) cls.create_attribute_values(cls.DEF_COLOR_ATTRIBUTE_ID, color_values) @@ -258,25 +286,24 @@ def create_products(cls, shopify_products): products = list() product_images = dict() - for shopy_product in shopify_products: - spa = shopy_product.attributes + for spa in shopify_products: new_product = models.Product.objects.create( - name=spa["title"], - slug=slugify(spa["title"] + str(spa["id"])), + name=spa.title, + slug=slugify(spa.title + str(spa.id)), product_type=def_product_type, category=def_category, - description=spa["body_html"], + description=spa.body_html, is_published=True, visible_in_listings=True, available_for_purchase=datetime.date.today(), - metadata={"shopifyid": str(spa["id"])} + metadata={"shopifyid": str(spa.id)} ) - cls.create_variants(new_product, spa["variants"]) + cls.create_variants(new_product, spa) products.append(new_product) product_images[new_product.id] = [] - for image in spa["images"]: + for image in spa.images: product_images[new_product.id].append(image.src) return products, product_images @@ -285,13 +312,13 @@ def create_products(cls, shopify_products): @transaction.atomic def create_collection(cls, shopify_collection, products): all_collections = models.Collection.objects.all() - sca = shopify_collection.attributes - collection_name = sca["title"] + collection_name = shopify_collection.title i = 1 while True: try: + lower_collection_name = collection_name.lower() collection = next(filter( - lambda c: c.name == collection_name, all_collections.iterator() + lambda c: c.name.lower() == lower_collection_name, all_collections.iterator() )) except StopIteration: collection = None @@ -299,14 +326,14 @@ def create_collection(cls, shopify_collection, products): if not collection: break i = i + 1 - collection_name = sca["title"] + "(" + str(i) + ")" + collection_name = shopify_collection.title + "(" + str(i) + ")" new_collection = models.Collection.objects.create( name=collection_name, slug=slugify(collection_name), is_published=True, - description=sca["body_html"], - metadata={"shopifyid": str(sca["collection_id"])} + description=shopify_collection.body_html, + metadata={"shopifyid": str(shopify_collection.collection_id)} ) collection_products = [] @@ -331,7 +358,7 @@ def create_menu_item(cls, collection): @classmethod def perform_mutation(cls, root, info, **data): - shopify = Shopify(data["shop_url"]) + shopify = Shopify(data["shop_url"], data["access_token"]) collection_id = data["collection_id"] shopify_collection = shopify.get_collection(collection_id) shopify_products = shopify.get_collection_products(collection_id) diff --git a/saleor/third/shopify/product.py b/saleor/third/shopify/product.py deleted file mode 100644 index e1322b374d6..00000000000 --- a/saleor/third/shopify/product.py +++ /dev/null @@ -1,44 +0,0 @@ -import re -import shopify - -from django.core.exceptions import ValidationError, ImproperlyConfigured -from ...product.error_codes import ProductErrorCode - - -class Shopify: - API_KEY = "1140ed801c09cd500756cf467191814c" - API_PASSWORD = "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" - API_VERSION = "2021-07" - - def __init__(self, shop_url): - self.has_init = False - - match = re.match(r"https?://(.*?)/?$", shop_url) - if not match: - raise ValidationError( - "invalid shop url", code=ProductErrorCode.INVALID.value, - ) - - shop_url = "https://%s:%s@%s/admin/api/%s" % ( - self.API_KEY, - self.API_PASSWORD, - match.group(1), - self.API_VERSION - ) - shopify.ShopifyResource.set_site(shop_url) - shop = shopify.Shop.current() - if shop: - self.has_init = True - - def get_collection_products(self, collection_id): - if not self.has_init: - raise ImproperlyConfigured("shopify has not init") - - products = shopify.Product.find(collection_id=collection_id) - return products - - def get_collection(self, collection_id): - if not self.has_init: - raise ImproperlyConfigured("shopify has not init") - - return shopify.CollectionListing.find(id_=collection_id) From 0aacc2f88b46a09bd6eb4f7a2cd82fa595bd3bc6 Mon Sep 17 00:00:00 2001 From: Jack Zhang Date: Fri, 6 Aug 2021 08:29:22 +0800 Subject: [PATCH 4/6] add __init__.py --- saleor/third/shopify/__init__.py | 48 ++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 saleor/third/shopify/__init__.py diff --git a/saleor/third/shopify/__init__.py b/saleor/third/shopify/__init__.py new file mode 100644 index 00000000000..ed89ef98942 --- /dev/null +++ b/saleor/third/shopify/__init__.py @@ -0,0 +1,48 @@ +import re +import shopify + +from django.core.exceptions import ValidationError, ImproperlyConfigured +from ...product.error_codes import ProductErrorCode + + +class Shopify: + API_VERSION = "2021-04" + session = None + + def __init__(self, shop_url, access_token): + match = re.match(r"https?://(.*?)/?$", shop_url) + if not match: + raise ValidationError( + "invalid shop url", code=ProductErrorCode.INVALID.value, + ) + + if not access_token: + raise ValidationError( + "invalid access token", code=ProductErrorCode.INVALID.value, + ) + + self.session = shopify.Session(shop_url, self.API_VERSION, access_token) + shopify.ShopifyResource.activate_session(self.session) + + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + + def close(self): + if self.session: + shopify.ShopifyResource.clear_session() + + def get_collection_products(self, collection_id): + if not self.session: + raise ImproperlyConfigured("shopify has not init") + + products = shopify.Product.find(collection_id=collection_id) + return products + + def get_collection(self, collection_id): + if not self.session: + raise ImproperlyConfigured("shopify has not init") + + return shopify.CollectionListing.find(id_=collection_id) From cb6bca21b033221782d17bf585f373324de5c8f0 Mon Sep 17 00:00:00 2001 From: Jack Zhang Date: Fri, 6 Aug 2021 11:53:19 +0800 Subject: [PATCH 5/6] bug fix --- saleor/graphql/product/bulk_mutations/products.py | 9 ++++++--- saleor/third/shopify/__init__.py | 7 +++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/saleor/graphql/product/bulk_mutations/products.py b/saleor/graphql/product/bulk_mutations/products.py index a327f1d8d0c..2abea8ce7db 100644 --- a/saleor/graphql/product/bulk_mutations/products.py +++ b/saleor/graphql/product/bulk_mutations/products.py @@ -200,6 +200,9 @@ def create_variants(cls, product, shopify_product): new_variants = [] new_stocks = [] for variant in shopify_product.variants: + if not (variant.sku and variant.price): + continue + weight = Weight() setattr(weight, variant.weight_unit, variant.weight) new_variant = models.ProductVariant( @@ -292,7 +295,7 @@ def create_products(cls, shopify_products): slug=slugify(spa.title + str(spa.id)), product_type=def_product_type, category=def_category, - description=spa.body_html, + description=(spa.body_html if spa.body_html else ""), is_published=True, visible_in_listings=True, available_for_purchase=datetime.date.today(), @@ -332,8 +335,8 @@ def create_collection(cls, shopify_collection, products): name=collection_name, slug=slugify(collection_name), is_published=True, - description=shopify_collection.body_html, - metadata={"shopifyid": str(shopify_collection.collection_id)} + description=str(shopify_collection.body_html), + metadata={"shopifyid": str(shopify_collection.id)} ) collection_products = [] diff --git a/saleor/third/shopify/__init__.py b/saleor/third/shopify/__init__.py index ed89ef98942..2cc3c7a89d0 100644 --- a/saleor/third/shopify/__init__.py +++ b/saleor/third/shopify/__init__.py @@ -2,11 +2,13 @@ import shopify from django.core.exceptions import ValidationError, ImproperlyConfigured +from shopify import ApiVersion, Release + from ...product.error_codes import ProductErrorCode class Shopify: - API_VERSION = "2021-04" + API_VERSION = "2021-07" session = None def __init__(self, shop_url, access_token): @@ -21,6 +23,7 @@ def __init__(self, shop_url, access_token): "invalid access token", code=ProductErrorCode.INVALID.value, ) + ApiVersion.define_version(Release("2021-07")) self.session = shopify.Session(shop_url, self.API_VERSION, access_token) shopify.ShopifyResource.activate_session(self.session) @@ -45,4 +48,4 @@ def get_collection(self, collection_id): if not self.session: raise ImproperlyConfigured("shopify has not init") - return shopify.CollectionListing.find(id_=collection_id) + return shopify.Collection.find(id_=collection_id) From 7865fb624bdcacf30352969829a091aa15b6fe98 Mon Sep 17 00:00:00 2001 From: Jack Zhang Date: Fri, 6 Aug 2021 12:49:04 +0800 Subject: [PATCH 6/6] bug fix --- saleor/graphql/product/bulk_mutations/products.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/saleor/graphql/product/bulk_mutations/products.py b/saleor/graphql/product/bulk_mutations/products.py index 2abea8ce7db..13c3e40ee02 100644 --- a/saleor/graphql/product/bulk_mutations/products.py +++ b/saleor/graphql/product/bulk_mutations/products.py @@ -200,8 +200,10 @@ def create_variants(cls, product, shopify_product): new_variants = [] new_stocks = [] for variant in shopify_product.variants: - if not (variant.sku and variant.price): - continue + if not variant.sku: + variant.sku = str(product.id) + "_" + \ + variant.option1.replace(" ", "_") + "_" + \ + variant.option2.replace(" ", "_") weight = Weight() setattr(weight, variant.weight_unit, variant.weight)