Beispiel #1
0
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
Beispiel #2
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
Beispiel #3
0
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
Beispiel #4
0
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
Beispiel #5
0
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.",)
Beispiel #9
0
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
Beispiel #10
0
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"
Beispiel #12
0
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
Beispiel #15
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)
Beispiel #16
0
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
Beispiel #17
0
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