def test_get_variant_picker_data_no_nested_attributes(variant, product_type, category): """Ensures that if someone bypassed variant attributes checks (e.g. a raw SQL query) and inserted an attribute with multiple values, it doesn't return invalid data to the storefront that would crash it.""" variant_attr = Attribute.objects.create( slug="modes", name="Available Modes", input_type=AttributeInputType.MULTISELECT) attr_val_1 = AttributeValue.objects.create(attribute=variant_attr, name="Eco Mode", slug="eco") attr_val_2 = AttributeValue.objects.create(attribute=variant_attr, name="Performance Mode", slug="power") product_type.variant_attributes.clear() product_type.variant_attributes.add(variant_attr) associate_attribute_values_to_instance(variant, variant_attr, attr_val_1, attr_val_2) product = variant.product data = get_variant_picker_data(product, discounts=None, local_currency=None).as_dict() assert len(data["variantAttributes"]) == 0
def product_with_two_variants(color_attribute, size_attribute, category): product_type = ProductType.objects.create(name="Type with two variants", has_variants=True, is_shipping_required=True) product_type.variant_attributes.add(color_attribute) product_type.variant_attributes.add(size_attribute) product = Product.objects.create( name="Test product with two variants", price=Money("10.00", "USD"), product_type=product_type, category=category, ) variant = ProductVariant.objects.create( product=product, sku="prodVar1", cost_price=Money("1.00", "USD"), quantity=10, quantity_allocated=1, ) associate_attribute_values_to_instance(variant, color_attribute, color_attribute.values.first()) associate_attribute_values_to_instance(variant, size_attribute, size_attribute.values.first()) return product
def product(product_type, category): product_attr = product_type.product_attributes.first() product_attr_value = product_attr.values.first() product = Product.objects.create( name="Test product", price=Money("10.00", "USD"), product_type=product_type, category=category, ) associate_attribute_values_to_instance(product, product_attr, product_attr_value) variant_attr = product_type.variant_attributes.first() variant_attr_value = variant_attr.values.first() variant = ProductVariant.objects.create( product=product, sku="123", cost_price=Money("1.00", "USD"), quantity=10, quantity_allocated=1, ) associate_attribute_values_to_instance(variant, variant_attr, variant_attr_value) return product
def product_list(product_type, category): product_attr = product_type.product_attributes.first() attr_value = product_attr.values.first() products = list( Product.objects.bulk_create([ Product( pk=1486, name="Test product 1", price=Money(10, "USD"), category=category, product_type=product_type, is_published=True, ), Product( pk=1487, name="Test product 2", price=Money(20, "USD"), category=category, product_type=product_type, is_published=False, ), Product( pk=1489, name="Test product 3", price=Money(20, "USD"), category=category, product_type=product_type, is_published=True, ), ])) ProductVariant.objects.bulk_create([ ProductVariant( product=products[0], sku=str(uuid.uuid4()).replace("-", ""), track_inventory=True, quantity=100, ), ProductVariant( product=products[1], sku=str(uuid.uuid4()).replace("-", ""), track_inventory=True, quantity=100, ), ProductVariant( product=products[2], sku=str(uuid.uuid4()).replace("-", ""), track_inventory=True, quantity=100, ), ]) for product in products: associate_attribute_values_to_instance(product, product_attr, attr_value) return products
def test_update_product_variant_with_duplicated_attribute( staff_api_client, product_with_variant_with_two_attributes, color_attribute, size_attribute, permission_manage_products, ): product = product_with_variant_with_two_attributes variant = product.variants.first() variant2 = product.variants.first() variant2.pk = None variant2.sku = str(uuid4())[:12] variant2.save() associate_attribute_values_to_instance(variant2, color_attribute, color_attribute.values.last()) associate_attribute_values_to_instance(variant2, size_attribute, size_attribute.values.last()) assert variant.attributes.first().values.first().slug == "red" assert variant.attributes.last().values.first().slug == "small" assert variant2.attributes.first().values.first().slug == "blue" assert variant2.attributes.last().values.first().slug == "big" variant_id = graphene.Node.to_global_id("ProductVariant", variant.pk) color_attribute_id = graphene.Node.to_global_id("Attribute", color_attribute.pk) size_attribute_id = graphene.Node.to_global_id("Attribute", size_attribute.pk) variables = { "id": variant_id, "attributes": [ { "id": color_attribute_id, "values": ["blue"] }, { "id": size_attribute_id, "values": ["big"] }, ], } response = staff_api_client.post_graphql( QUERY_UPDATE_VARIANT_ATTRIBUTES, variables, permissions=[permission_manage_products], ) content = get_graphql_content(response) data = content["data"]["productVariantUpdate"] assert data["productErrors"][0] == { "field": "attributes", "code": ProductErrorCode.UNIQUE.name, }
def test_associate_attribute_to_non_product_instance(color_attribute): instance = ProductType() attribute = color_attribute value = color_attribute.values.first() with pytest.raises(AssertionError) as exc: associate_attribute_values_to_instance(instance, attribute, value) # noqa assert exc.value.args == ("ProductType is unsupported",)
def test_sort_product_not_having_attribute_data(api_client, category, count_queries): """Test the case where a product has a given attribute assigned to their product type but no attribute data assigned, i.e. the product's PT was changed after the product creation. """ expected_results = ["Z", "Y", "A"] product_create_kwargs = { "category": category, "price": zero_money(), "is_published": True, } # Create two product types, with one forced to be at the bottom (no such attribute) product_type = product_models.ProductType.objects.create(name="Apples", slug="apples") other_product_type = product_models.ProductType.objects.create( name="Chocolates", slug="chocolates") # Assign an attribute to the product type attribute = product_models.Attribute.objects.create(name="Kind", slug="kind") value = product_models.AttributeValue.objects.create(name="Value", slug="value", attribute=attribute) product_type.product_attributes.add(attribute) # Create a product with a value product_having_attr_value = product_models.Product.objects.create( name="Z", slug="z", product_type=product_type, **product_create_kwargs) associate_attribute_values_to_instance(product_having_attr_value, attribute, value) # Create a product having the same product type but no attribute data product_models.Product.objects.create(name="Y", slug="y", product_type=product_type, **product_create_kwargs) # Create a new product having a name that would be ordered first in ascending # as the default ordering is by name for non matching products product_models.Product.objects.create(name="A", slug="a", product_type=other_product_type, **product_create_kwargs) # Sort the products qs = product_models.Product.objects.sort_by_attribute( attribute_pk=attribute.pk) qs = qs.values_list("name", flat=True) # Compare the results sorted_results = list(qs) assert sorted_results == expected_results
def test_associate_attribute_to_product_instance_from_different_attribute( product, color_attribute, size_attribute ): """Ensure an assertion error is raised when one tries to associate attribute values to an object that don't belong to the supplied attribute. """ instance = product attribute = color_attribute value = size_attribute.values.first() with pytest.raises(AssertionError) as exc: associate_attribute_values_to_instance(instance, attribute, value) assert exc.value.args == ("Some values are not from the provided attribute.",)
def categories_tree(db, product_type): # pylint: disable=W0613 parent = Category.objects.create(name="Parent", slug="parent") parent.children.create(name="Child", slug="child") child = parent.children.first() product_attr = product_type.product_attributes.first() attr_value = product_attr.values.first() product = Product.objects.create( name="Test product", price=Money(10, "USD"), product_type=product_type, category=child, ) associate_attribute_values_to_instance(product, product_attr, attr_value) return parent
def product_with_multiple_values_attributes(product, product_type, category) -> Product: attribute = Attribute.objects.create( slug="modes", name="Available Modes", input_type=AttributeInputType.MULTISELECT ) attr_val_1 = AttributeValue.objects.create( attribute=attribute, name="Eco Mode", slug="eco" ) attr_val_2 = AttributeValue.objects.create( attribute=attribute, name="Performance Mode", slug="power" ) product_type.product_attributes.clear() product_type.product_attributes.add(attribute) associate_attribute_values_to_instance(product, attribute, attr_val_1, attr_val_2) return product
def test_get_brand_from_attributes(product): attribute = Attribute.objects.create( slug="brand", name="Brand", input_type=AttributeInputType.MULTISELECT) product.product_type.product_attributes.add(attribute) # Set the brand attribute as a multi-value attribute attribute.input_type = AttributeInputType.MULTISELECT attribute.save(update_fields=["input_type"]) # Add some brands names brands = AttributeValue.objects.bulk_create([ AttributeValue(attribute=attribute, name="Saleor", slug="saleor"), AttributeValue(attribute=attribute, name="Mirumee", slug="mirumee"), ]) # Associate the brand names to the product associate_attribute_values_to_instance(product, attribute, *tuple(brands)) # Retrieve the brand names from the product attributes brand_names_str = get_brand_from_attributes(product.attributes) assert brand_names_str == "Saleor, Mirumee"
def test_filtering_by_attribute(db, color_attribute, category, settings): product_type_a = models.ProductType.objects.create(name="New class", slug="new-class1", has_variants=True) product_type_a.product_attributes.add(color_attribute) product_type_b = models.ProductType.objects.create(name="New class", slug="new-class2", has_variants=True) product_type_b.variant_attributes.add(color_attribute) product_a = models.Product.objects.create( name="Test product a", slug="test-product-a", price=Money(10, settings.DEFAULT_CURRENCY), product_type=product_type_a, category=category, ) models.ProductVariant.objects.create(product=product_a, sku="1234") product_b = models.Product.objects.create( name="Test product b", slug="test-product-b", price=Money(10, settings.DEFAULT_CURRENCY), product_type=product_type_b, category=category, ) variant_b = models.ProductVariant.objects.create(product=product_b, sku="12345") color = color_attribute.values.first() color_2 = color_attribute.values.last() # Associate color to a product and a variant associate_attribute_values_to_instance(product_a, color_attribute, color) associate_attribute_values_to_instance(variant_b, color_attribute, color) product_qs = models.Product.objects.all().values_list("pk", flat=True) filters = {color_attribute.pk: [color.pk]} filtered = filter_products_by_attributes_values(product_qs, filters) assert product_a.pk in list(filtered) assert product_b.pk in list(filtered) associate_attribute_values_to_instance(product_a, color_attribute, color_2) filters = {color_attribute.pk: [color.pk]} filtered = filter_products_by_attributes_values(product_qs, filters) assert product_a.pk not in list(filtered) assert product_b.pk in list(filtered) filters = {color_attribute.pk: [color_2.pk]} filtered = filter_products_by_attributes_values(product_qs, filters) assert product_a.pk in list(filtered) assert product_b.pk not in list(filtered) # Filter by multiple values, should trigger a OR condition filters = {color_attribute.pk: [color.pk, color_2.pk]} filtered = filter_products_by_attributes_values(product_qs, filters) assert product_a.pk in list(filtered) assert product_b.pk in list(filtered)
def test_generate_name_for_variant( variant_with_no_attributes, color_attribute_without_values, size_attribute ): """Test the name generation from a given variant containing multiple attributes and different input types (dropdown and multiselect). """ variant = variant_with_no_attributes color_attribute = color_attribute_without_values # Assign the attributes to the product type variant.product.product_type.variant_attributes.set( (color_attribute, size_attribute) ) # Set the color attribute to a multi-value attribute color_attribute.input_type = AttributeInputType.MULTISELECT color_attribute.save(update_fields=["input_type"]) # Create colors colors = AttributeValue.objects.bulk_create( [ AttributeValue(attribute=color_attribute, name="Yellow", slug="yellow"), AttributeValue(attribute=color_attribute, name="Blue", slug="blue"), AttributeValue(attribute=color_attribute, name="Red", slug="red"), ] ) # Retrieve the size attribute value "Big" size = size_attribute.values.get(slug="big") # Associate the colors and size to variant attributes associate_attribute_values_to_instance(variant, color_attribute, *tuple(colors)) associate_attribute_values_to_instance(variant, size_attribute, size) # Generate the variant name from the attributes name = generate_name_for_variant(variant) assert name == "Yellow, Blue, Red / Big"
def test_associate_attribute_to_product_instance_without_values(product): """Ensure clearing the values from a product is properly working.""" old_assignment = product.attributes.first() assert old_assignment is not None, "The product doesn't have attribute-values" assert old_assignment.values.count() == 1 attribute = old_assignment.attribute # Clear the values new_assignment = associate_attribute_values_to_instance(product, attribute) # Ensure the values were cleared and no new assignment entry was created assert new_assignment.pk == old_assignment.pk assert new_assignment.values.count() == 0
def test_product_page_renders_attributes_properly( settings, client, product_with_multiple_values_attributes, color_attribute): """This test ensures the product attributes are properly rendered as expected including the translations.""" settings.LANGUAGE_CODE = "fr" product = product_with_multiple_values_attributes multi_values_attribute = product.product_type.product_attributes.first() # Retrieve the attributes' values red, blue = color_attribute.values.all() eco_mode, performance_mode = multi_values_attribute.values.all() # Assign the dropdown attribute to the product product.product_type.product_attributes.add(color_attribute) associate_attribute_values_to_instance(product, color_attribute, red) # Create the attribute name translations AttributeTranslation.objects.bulk_create([ AttributeTranslation( language_code="fr", attribute=multi_values_attribute, name="Multiple Valeurs", ), AttributeTranslation(language_code="fr", attribute=color_attribute, name="Couleur"), ]) # Create the attribute value translations AttributeValueTranslation.objects.bulk_create([ AttributeValueTranslation(language_code="fr", attribute_value=red, name="Rouge"), AttributeValueTranslation(language_code="fr", attribute_value=blue, name="Bleu"), AttributeValueTranslation(language_code="fr", attribute_value=eco_mode, name="Mode économique"), AttributeValueTranslation( language_code="fr", attribute_value=performance_mode, name="Mode performance", ), ]) # Render the page url = reverse("product:details", kwargs={ "product_id": product.pk, "slug": product.get_slug() }) response = client.get(url) # type: TemplateResponse assert response.status_code == 200 # Retrieve the attribute table soup = BeautifulSoup(response.content, "lxml") attribute_table = soup.select_one(".product__info table") # type: Tag assert attribute_table, "Did not find the attribute table" # Retrieve the table rows expected_attributes_re = re.compile( r"Multiple Valeurs:Mode économique,\s+Mode performance\n\s*" # noqa: no black! r"Couleur:Rouge") attribute_rows = attribute_table.select("tr") actual_attributes = "\n".join( row.get_text(strip=True) for row in attribute_rows) assert len(attribute_rows) == 2 assert expected_attributes_re.match(actual_attributes)
def products_structures(category): def attr_value(attribute, *values): return [attribute.values.get_or_create(name=v, slug=v)[0] for v in values] assert product_models.Product.objects.count() == 0 in_multivals = product_models.AttributeInputType.MULTISELECT pt_apples, pt_oranges, pt_other = list( product_models.ProductType.objects.bulk_create( [ product_models.ProductType( name="Apples", slug="apples", has_variants=False ), product_models.ProductType( name="Oranges", slug="oranges", has_variants=False ), product_models.ProductType( name="Other attributes", slug="other", has_variants=False ), ] ) ) colors_attr, trademark_attr, dummy_attr = list( product_models.Attribute.objects.bulk_create( [ product_models.Attribute( name="Colors", slug="colors", input_type=in_multivals ), product_models.Attribute(name="Trademark", slug="trademark"), product_models.Attribute(name="Dummy", slug="dummy"), ] ) ) # Manually add every attribute to given product types # to force the preservation of ordering pt_apples.product_attributes.add(colors_attr) pt_apples.product_attributes.add(trademark_attr) pt_oranges.product_attributes.add(colors_attr) pt_oranges.product_attributes.add(trademark_attr) pt_other.product_attributes.add(dummy_attr) assert len(COLORS) == len(TRADEMARKS) apples = list( product_models.Product.objects.bulk_create( [ product_models.Product( name=f"{attrs[0]} Apple - {attrs[1]} ({i})", slug=f"{attrs[0]}-apple-{attrs[1]}-({i})", product_type=pt_apples, category=category, price=zero_money(), is_published=True, ) for i, attrs in enumerate(zip(COLORS, TRADEMARKS)) ] ) ) oranges = list( product_models.Product.objects.bulk_create( [ product_models.Product( name=f"{attrs[0]} Orange - {attrs[1]} ({i})", slug=f"{attrs[0]}-orange-{attrs[1]}-({i})", product_type=pt_oranges, category=category, price=zero_money(), is_published=True, ) for i, attrs in enumerate(zip(COLORS, TRADEMARKS)) ] ) ) dummy = product_models.Product.objects.create( name=f"Oopsie Dummy", slug="oopsie-dummy", product_type=pt_other, category=category, price=zero_money(), is_published=True, ) product_models.Product.objects.create( name=f"Another Dummy but first in ASC and has no attribute value", slug="another-dummy", product_type=pt_other, category=category, price=zero_money(), is_published=True, ) dummy_attr_value = attr_value(dummy_attr, DUMMIES[0]) associate_attribute_values_to_instance(dummy, dummy_attr, *dummy_attr_value) for products in (apples, oranges): for product, attr_values in zip(products, COLORS): attr_values = attr_value(colors_attr, *attr_values) associate_attribute_values_to_instance(product, colors_attr, *attr_values) for product, attr_values in zip(products, TRADEMARKS): attr_values = attr_value(trademark_attr, attr_values) associate_attribute_values_to_instance( product, trademark_attr, *attr_values ) return colors_attr, trademark_attr, dummy_attr
def products_for_pagination(product_type, color_attribute, category, warehouse): product_type2 = ProductType.objects.create(name="Apple") products = Product.objects.bulk_create([ Product( name="Product1", slug="prod1", price=Money("10.00", "USD"), category=category, product_type=product_type2, is_published=True, description="desc1", ), Product( name="ProductProduct1", slug="prod_prod1", price=Money("15.00", "USD"), category=category, product_type=product_type, is_published=False, ), Product( name="ProductProduct2", slug="prod_prod2", price=Money("8.00", "USD"), category=category, product_type=product_type2, is_published=True, ), Product( name="Product2", slug="prod2", price=Money("7.00", "USD"), category=category, product_type=product_type, is_published=False, description="desc2", ), Product( name="Product3", slug="prod3", price=Money("15.00", "USD"), category=category, product_type=product_type2, is_published=True, description="desc3", ), ]) product_attrib_values = color_attribute.values.all() associate_attribute_values_to_instance(products[1], color_attribute, product_attrib_values[0]) associate_attribute_values_to_instance(products[3], color_attribute, product_attrib_values[1]) variants = ProductVariant.objects.bulk_create([ ProductVariant( product=products[0], sku=str(uuid.uuid4()).replace("-", ""), track_inventory=True, ), ProductVariant( product=products[2], sku=str(uuid.uuid4()).replace("-", ""), track_inventory=True, ), ProductVariant( product=products[4], sku=str(uuid.uuid4()).replace("-", ""), track_inventory=True, ), ]) Stock.objects.bulk_create([ Stock(warehouse=warehouse, product_variant=variants[0], quantity=100), Stock(warehouse=warehouse, product_variant=variants[1], quantity=0), Stock(warehouse=warehouse, product_variant=variants[2], quantity=0), ]) return products