コード例 #1
0
class CheckCategories:
    """ Class responsible for checking if the categories exist on WooCommerce """
    def __init__(self, woocommerce_records: List[Dict]):
        self._woocommerce_records = woocommerce_records
        self._api: Optional[WooCommerceAPI] = None
        self._categories: Dict = dict()
        self._missing_categories: List = []

    def api_setup(self):
        """ Setup WooCommerce API object """
        self._api = WooCommerceAPI(
            username=config.WooCommerceAPICred.USERNAME,
            password=config.WooCommerceAPICred.PASSWORD,
        )

    def get_category_id(self, category: str):
        """ From the category name get the category id """
        # Category has hierarchy information
        cat_list = [x.strip() for x in category.split(">")]
        parents = []  # List of dict [{"id": 123}. {"id": 456}]
        for name in cat_list:
            if parents:
                woocommerce_categories, _ = self._api.get_all_categories(
                    search=name, parent=parents[-1]["id"])
            else:
                woocommerce_categories, _ = self._api.get_all_categories(
                    search=name)

            if not woocommerce_categories:
                print(f"No woocommerce categories found for name {name}")
                self._missing_categories.append(name)
                return parents[-1]
            filtered_category = self.filter_category(woocommerce_categories,
                                                     name)
            parents.append({"id": filtered_category["id"]})
        return parents[-1]

    def find_missing_category(self):
        """ Find missing categories  """
        self.api_setup()
        for record in self._woocommerce_records:
            category = record["Categories"]
            if category is not None:
                list_of_categories = [x.strip() for x in category.split(",")]
                for catname in list_of_categories:
                    if catname not in self._categories:
                        self._categories[catname] = self.get_category_id(
                            catname)

    @staticmethod
    def filter_category(category_list: List, category: str):
        """ Filter out the category based on category name. It will only return one category """
        for cat in category_list:
            if cat["name"] == category:
                return cat
        return category_list.pop()

    @property
    def missing_categories(self):
        return self._missing_categories
コード例 #2
0
 def api_setup(self):
     """ Setup WooCommerce API object """
     self._api = WooCommerceAPI(
         username=config.WooCommerceAPICred.USERNAME,
         password=config.WooCommerceAPICred.PASSWORD
     )
コード例 #3
0
class ProductIntegration:
    """ Product Integration with WooCommerce website using API """
    class UpdateImageCode(IntEnum):
        ALlProducts = 1
        NewProducts = 2     # Update for new products or for products that don't have images

    PRODUCT_LIMIT = os.environ.get("PRODUCT_LIMIT") or 20
    UPDATE_IMAGE_MODE = UpdateImageCode.ALlProducts

    def __init__(
            self,
            csv_file: str,
            template: str,
            update_image_mode: UpdateImageCode = UpdateImageCode.ALlProducts
    ):
        self._csv_file: str = csv_file
        self._template: str = template
        self._update_image_mode: ProductIntegration.UpdateImageCode = update_image_mode
        self._api: Optional[WooCommerceAPI] = None
        self._supplier_csv_2_woocommerce_csv: Optional[SupplierCSV2WoocommerceCSV] = None
        self._map_csv_to_api: Optional[MapCsvToApi] = None
        self._api_data: Optional[List] = None
        self._woocommerce_products: Optional[List] = None
        self._api_responses: List = []
        self._products_upload_table: List[Dict] = []

    def api_setup(self):
        """ Setup WooCommerce API object """
        self._api = WooCommerceAPI(
            username=config.WooCommerceAPICred.USERNAME,
            password=config.WooCommerceAPICred.PASSWORD
        )

    def setup(self):
        """ Setup """
        print(f"Running in update image mode: {self._update_image_mode}")
        self.api_setup()
        self._supplier_csv_2_woocommerce_csv = SupplierCSV2WoocommerceCSV(
            csv_file=self._csv_file,
            template=self._template
        )
        self._supplier_csv_2_woocommerce_csv.convert()
        self._map_csv_to_api = MapCsvToApi(
            csv_data=self._supplier_csv_2_woocommerce_csv.product_records
        )
        self._map_csv_to_api.map()
        self._api_data = self._map_csv_to_api.api_data
        skus = [x[ApiProductFields.Sku] for x in self._api_data]
        # Get all the products available on WooCommerce
        self._woocommerce_products, _ = self._api.get_all_products_as_dict(skus=skus)

    def create_or_update_products(self):
        """ Create new products or update existing products """
        print(f"Using product limit of {ProductIntegration.PRODUCT_LIMIT} for batch request")
        create_product_data: List = []
        update_product_data: List = []

        for product in self._api_data:
            product_sku = product.get(ApiProductFields.Sku)
            if product_sku is None:
                print(f"Skipping processing product without {ApiProductFields.Sku}")
                continue

            # IF the products exist and the product has images, then don't update the image in
            # NewProducts mode. In all other cases update the images.
            if product_sku in self._woocommerce_products:
                print(f"Updating product for SKU {product_sku}")
                if self._update_image_mode == ProductIntegration.UpdateImageCode.NewProducts:
                    # Image already exist. So we shouldn't update the image in this mode
                    if self._woocommerce_products[product_sku].get(ApiProductFields.Images) and \
                            product.get(ApiProductFields.Images):
                        # Remove "images" key from the product
                        print(f"Removing images for SKU {product_sku}")
                        product.pop(ApiProductFields.Images)
                product_id = self._woocommerce_products[product_sku][ApiProductFields.Id]
                product.update({ApiProductFields.Id: product_id})
                update_product_data.append(product)
            else:
                print(f"Creating product for SKU {product_sku}")
                create_product_data.append(product)

        # Create a table for the status of products
        self._products_upload_table += [
            {"SKU": product.get(ApiProductFields.Sku), "Status": "Created"}
            for product in create_product_data
        ]
        self._products_upload_table += [
            {"SKU": product.get(ApiProductFields.Sku), "Status": "Updated"}
            for product in update_product_data
        ]

        # Max of 100 objects can be created or updated
        if len(create_product_data) + len(update_product_data) > ProductIntegration.PRODUCT_LIMIT:
            # Send create and update request separately
            # Send create request in a batch of 100 objects
            while create_product_data:
                response, success = self._api.create_multiple_products(
                    data=create_product_data[:ProductIntegration.PRODUCT_LIMIT]
                )
                # Save the response if success is False
                if not success:
                    self._api_responses.append(response)
                create_product_data = create_product_data[ProductIntegration.PRODUCT_LIMIT:]

            # Send update request in a batch of 100 objects
            while update_product_data:
                response, success = self._api.update_multiple_products(
                    data=update_product_data[:ProductIntegration.PRODUCT_LIMIT]
                )
                # Save the response if success is False
                if not success:
                    self._api_responses.append(response)
                update_product_data = update_product_data[ProductIntegration.PRODUCT_LIMIT:]
        else:
            response, success = self._api.create_or_update_products(
                create_data=create_product_data, update_data=update_product_data
            )
            # Save the response if success is False
            if not success:
                self._api_responses.append(response)


    @property
    def api_data(self):
        return self._api_data

    @property
    def api_responses(self):
        return self._api_responses

    @property
    def product_upload_table(self):
        return self._products_upload_table
コード例 #4
0
class MapCsvToApi:
    """ Class responsible for mapping CSV data to API data """
    def __init__(self, csv_data: List):
        self._csv_data: List = csv_data
        self._api_data: List = []
        self._api: Optional[WooCommerceAPI] = None
        self._categories: Dict = dict()

    def api_setup(self):
        """ Setup WooCommerce API object """
        self._api = WooCommerceAPI(
            username=config.WooCommerceAPICred.USERNAME,
            password=config.WooCommerceAPICred.PASSWORD,
        )

    def get_category_id(self, category: str):
        """ From the category name get the category id """
        # Category has hierarchy information
        cat_list = [x.strip() for x in category.split(">")]
        parents = []  # List of dict [{"id": 123}. {"id": 456}]
        for name in cat_list:
            if parents:
                woocommerce_categories, _ = self._api.get_all_categories(
                    search=name, parent=parents[-1]["id"])
            else:
                woocommerce_categories, _ = self._api.get_all_categories(
                    search=name)

            if not woocommerce_categories:
                print(f"No woocommerce categories found for name {name}")
            filtered_category = self.filter_category(woocommerce_categories,
                                                     name)
            parents.append({"id": filtered_category["id"]})
        return parents[-1]

    def map_csv_category(self, category: str):
        """ Map categories from csv to woocommerce api """
        list_of_categories = [x.strip() for x in category.split(",")]
        list_of_categories_id = []
        for catname in list_of_categories:
            if catname not in self._categories:
                self._categories[catname] = self.get_category_id(catname)
            list_of_categories_id.append(self._categories[catname])
        return list_of_categories_id

    def csv_category_to_api(self):
        """ Replace the CSV category with API category """
        # [{'id': 49}, {'id': 60}, {'id': 146}, {'id': 1882}]
        for data in self._api_data:
            data["categories"] = self.map_csv_category(data["categories"])
            # data["categories"] = [{'id': 49}, {'id': 60}, {'id': 146}, {'id': 1882}]

    def map_csv_attributes(self, attributes: List[tuple]):
        """ Map all the attributes from CSV to API data """
        woocommerce_attributes, _ = self._api.get_all_attributes()
        api_attributes = []
        for index, attrs in enumerate(attributes):
            woo_attr = next(
                (x for x in woocommerce_attributes if x["name"] == attrs[0]),
                None)
            woo_attr_id = 0
            woo_attr_name = attrs[0]
            if woo_attr:
                woo_attr_id = woo_attr["id"]
                woo_attr_name = woo_attr["name"]
            api_attributes.append({
                "id": woo_attr_id,
                "name": woo_attr_name,
                "position": index,
                "visible": True,
                "options": [attrs[1]]
            })
        return api_attributes

    def csv_attributes_to_api(self):
        """ Remove individual CSV attributes and create a single attributes for api """
        for data in self._api_data:
            attr1 = data.pop("attribute_1_name")
            value1 = data.pop("attribute_1_value")
            attr2 = data.pop("attribute_2_name")
            value2 = data.pop("attribute_2_value")
            attr3 = data.pop("attribute_3_name")
            value3 = data.pop("attribute_3_value")
            attr4 = data.pop("attribute_4_name")
            value4 = data.pop("attribute_4_value")
            attributes = [(attr1, value1), (attr2, value2), (attr3, value3),
                          (attr4, value4)]
            data["attributes"] = self.map_csv_attributes(attributes)

    def csv_dimensions_to_api(self):
        """ Remove individual dimensions and create a dimensions api attribute """
        for data in self._api_data:
            length = data.pop("length")
            width = data.pop("width")
            height = data.pop("height")
            data["dimensions"] = self.map_csv_dimensions(length, width, height)

    def csv_images_to_api(self):
        """ Map csv images to api images """
        for data in self._api_data:
            if "images" in data:
                data["images"] = self.map_csv_images(data["images"])

    def map(self):
        """ Map data to api """
        self.api_setup()
        for data in self._csv_data:
            mapped_data = dict()
            for key, value in data.items():
                mapped_data[CSV_MAPPING[key]] = str(value)
            self._api_data.append(mapped_data)

        self.csv_category_to_api()
        self.csv_attributes_to_api()
        self.csv_dimensions_to_api()
        self.csv_images_to_api()

    @staticmethod
    def filter_category(category_list: List, category: str):
        """ Filter out the category based on category name. It will only return one category """
        for cat in category_list:
            if cat["name"] == category:
                return cat
        return category_list.pop()

    @staticmethod
    def map_csv_dimensions(length: str, width: str, height: str):
        """ Create a dimensions attribute """
        return {"length": length, "width": width, "height": height}

    @staticmethod
    def map_csv_images(images: str):
        """ Map csv images to api images """
        api_images = []
        images = [x.strip() for x in images.split(",")]
        for image in images:
            api_images.append({"src": image, "name": os.path.basename(image)})
        return api_images

    @property
    def api_data(self):
        return self._api_data