Esempio n. 1
0
def test_product_catalog_availability():
    shop = factories.get_default_shop()
    supplier = factories.get_default_supplier()
    product1 = factories.create_product("p1", shop=shop, supplier=supplier, default_price=Decimal("30"))
    product2 = factories.create_product("p2", shop=shop, supplier=supplier, default_price=Decimal("10"))

    supplier.stock_managed = True
    supplier.save()

    # add 10 products to product1 stock
    supplier.adjust_stock(product1.pk, delta=10)

    catalog_available_only = ProductCatalog(context=ProductCatalogContext(purchasable_only=True))
    catalog_visible_only = ProductCatalog(context=ProductCatalogContext(purchasable_only=False))
    catalog_all = ProductCatalog(context=ProductCatalogContext(purchasable_only=False, visible_only=False))
    ProductCatalog.index_product(product1)
    ProductCatalog.index_product(product2)

    assert catalog_available_only.get_products_queryset().count() == 1
    assert catalog_visible_only.get_products_queryset().count() == 2
    assert catalog_all.get_products_queryset().count() == 2

    # change the product1 visibility
    ShopProduct.objects.all().update(visibility=ShopProductVisibility.NOT_VISIBLE)
    ProductCatalog.index_product(product1)
    ProductCatalog.index_product(product2)

    assert catalog_available_only.get_products_queryset().count() == 0
    assert catalog_visible_only.get_products_queryset().count() == 0
    assert catalog_all.get_products_queryset().count() == 2
Esempio n. 2
0
def test_admin_catalog_discount_product_filter(rf, admin_user):
    shop, supplier, contact, group, category = _init_test()
    default_price = Decimal("15.0")
    discount_percentage = Decimal("10")
    product = factories.create_product("p1", shop=shop, supplier=supplier, default_price=default_price)
    product2 = factories.create_product("p2", shop=shop, supplier=supplier, default_price=default_price)

    ProductCatalog.index_product(product)
    ProductCatalog.index_product(product2)
    view = DiscountEditView.as_view()

    # create a 10% discount for the product
    payload = _get_default_discount_data({"product": product.pk, "discount_percentage": str(discount_percentage)})
    request = apply_request_middleware(rf.post("/", data=payload), shop=shop, user=admin_user)
    with patch("django.db.transaction.on_commit", new=atomic_commit_mock):
        response = view(request, pk=None)
    assert response.status_code == 302

    anon_catalog = ProductCatalog(context=ProductCatalogContext(purchasable_only=False))
    customer_catalog = ProductCatalog(context=ProductCatalogContext(purchasable_only=False, contact=contact))

    # discount is indexed
    discounted_price = (default_price - (default_price * discount_percentage * Decimal(0.01))).quantize(Decimal("0.01"))
    _assert_products_queryset(
        anon_catalog,
        [
            (product.pk, default_price, discounted_price),
            (product2.pk, default_price, None),
        ],
    )
    _assert_products_queryset(
        customer_catalog,
        [
            (product.pk, default_price, discounted_price),
            (product2.pk, default_price, None),
        ],
    )

    # changed the discount product
    discount = Discount.objects.last()
    payload = _get_default_discount_data({"product": product2.pk, "discount_percentage": str(discount_percentage)})
    request = apply_request_middleware(rf.post("/", data=payload), shop=shop, user=admin_user)
    with patch("django.db.transaction.on_commit", new=atomic_commit_mock):
        response = view(request, pk=discount.pk)
    assert response.status_code == 302

    _assert_products_queryset(
        anon_catalog,
        [
            (product.pk, default_price, None),
            (product2.pk, default_price, discounted_price),
        ],
    )
    _assert_products_queryset(
        customer_catalog,
        [
            (product.pk, default_price, None),
            (product2.pk, default_price, discounted_price),
        ],
    )
Esempio n. 3
0
def test_admin_catalog_discount_contact_group_filter(rf, admin_user):
    shop, supplier, contact, group, category = _init_test()
    default_price = Decimal("15.0")
    discounted_price_value = Decimal("12")
    product = factories.create_product("p1", shop=shop, supplier=supplier, default_price=default_price)
    product.get_shop_instance(shop).categories.add(category)

    ProductCatalog.index_product(product)
    view = DiscountEditView.as_view()

    # create a discounted price for the contact group
    payload = _get_default_discount_data(
        {"contact_group": group.pk, "discounted_price_value": str(discounted_price_value)}
    )
    request = apply_request_middleware(rf.post("/", data=payload), shop=shop, user=admin_user)
    with patch("django.db.transaction.on_commit", new=atomic_commit_mock):
        response = view(request, pk=None)
    assert response.status_code == 302

    anon_catalog = ProductCatalog(context=ProductCatalogContext(purchasable_only=False))
    customer_catalog = ProductCatalog(context=ProductCatalogContext(purchasable_only=False, contact=contact))

    _assert_products_queryset(anon_catalog, [(product.pk, default_price, None)])
    _assert_products_queryset(customer_catalog, [(product.pk, default_price, discounted_price_value)])

    # remove the group from the discount
    discount = Discount.objects.last()
    payload = _get_default_discount_data({"discounted_price_value": str(discounted_price_value)})
    request = apply_request_middleware(rf.post("/", data=payload), shop=shop, user=admin_user)
    with patch("django.db.transaction.on_commit", new=atomic_commit_mock):
        response = view(request, pk=discount.pk)
    assert response.status_code == 302

    _assert_products_queryset(anon_catalog, [(product.pk, default_price, discounted_price_value)])
    _assert_products_queryset(customer_catalog, [(product.pk, default_price, discounted_price_value)])
Esempio n. 4
0
def test_product_catalog_simple_list():
    shop = factories.get_default_shop()
    supplier = factories.get_default_supplier()
    product1 = factories.create_product("p1", shop=shop, supplier=supplier, default_price=Decimal("30"))
    product2 = factories.create_product("p2", shop=shop, supplier=supplier, default_price=Decimal("10"))
    product3 = factories.create_product("p3", shop=shop, supplier=supplier, default_price=Decimal("20"))

    catalog = ProductCatalog(context=ProductCatalogContext(purchasable_only=False))
    ProductCatalog.index_product(product1)
    ProductCatalog.index_product(product2)
    ProductCatalog.index_product(product3)

    _assert_products_queryset(
        catalog,
        [
            (product2.pk, Decimal("10"), None),
            (product3.pk, Decimal("20"), None),
            (product1.pk, Decimal("30"), None),
        ],
    )
    _assert_shop_products_queryset(
        catalog,
        [
            (product2.get_shop_instance(shop).pk, Decimal("10"), None),
            (product3.get_shop_instance(shop).pk, Decimal("20"), None),
            (product1.get_shop_instance(shop).pk, Decimal("30"), None),
        ],
    )
    _assert_price(product1, shop, Decimal("30"), Decimal("30"))
    _assert_price(product2, shop, Decimal("10"), Decimal("10"))
    _assert_price(product3, shop, Decimal("20"), Decimal("20"))
Esempio n. 5
0
def get_priced_children_for_price_range(request, product, quantity, supplier):
    catalog = ProductCatalog(
        ProductCatalogContext(
            shop=request.shop,
            user=getattr(request, "user", None),
            supplier=supplier,
            contact=getattr(request, "customer", None),
            purchasable_only=True,
        ))

    product_queryset = (catalog.get_products_queryset().filter(
        pk__in=product.variation_children.values_list(
            "pk", flat=True)).order_by("catalog_price"))

    low = product_queryset.first()

    if low is None:
        return []

    high = product_queryset.last()

    return [
        (low, _get_item_price_info(request, low, quantity, supplier)),
        (high, _get_item_price_info(request, high, quantity, supplier)),
    ]
Esempio n. 6
0
    def best_selling(self, request):
        data = get_best_selling_product_info(
            shop_ids=[request.GET.get("shop", request.shop.pk)],
            cutoff_days=30,
            orderable_only=True,
        )
        sorted_product_ids = sorted(data,
                                    key=lambda item: item[1],
                                    reverse=True)
        product_ids = [item[0] for item in sorted_product_ids]

        catalog = ProductCatalog(
            ProductCatalogContext(
                shop=request.GET.get("shop", request.shop.pk),
                user=getattr(request, "user", None),
                contact=getattr(request, "customer", None),
                purchasable_only=True,
            ))
        valid_shop_products_qs = catalog.get_shop_products_queryset().filter(
            product__in=product_ids,
            product__mode__in=ProductMode.get_parent_modes())

        shop_products_qs = self.filter_queryset(
            valid_shop_products_qs).distinct()
        page = self.paginate_queryset(shop_products_qs)
        if page is not None:
            serializer = self.get_serializer_class()(
                page, many=True, context=self.get_serializer_context())
            return self.get_paginated_response(serializer.data)
        serializer = self.get_serializer_class()(
            shop_products_qs, many=True, context=self.get_serializer_context())
        return Response(serializer.data)
Esempio n. 7
0
def _get_best_selling_products(cutoff_days, n_products, orderable_only, request, supplier=None):
    data = get_best_selling_product_info(
        shop_ids=[request.shop.pk],
        cutoff_days=cutoff_days,
        supplier=supplier,
        orderable_only=orderable_only,
        quantity=n_products,
    )
    sorted_product_ids = sorted(data, key=lambda item: item[1], reverse=True)
    product_ids = [item[0] for item in sorted_product_ids]

    catalog = ProductCatalog(
        ProductCatalogContext(
            shop=request.shop,
            user=getattr(request, "user", None),
            supplier=supplier,
            contact=getattr(request, "customer", None),
            purchasable_only=orderable_only,
            visibility=ShopProductVisibility.LISTED,
        )
    )
    valid_products_qs = (
        catalog.get_products_queryset()
        .filter(id__in=product_ids, mode__in=ProductMode.get_parent_modes())
        .distinct()[:n_products]
    )

    products = cache_product_things(request, valid_products_qs)
    # order products by the best selling order
    products = sorted(products, key=lambda product: product_ids.index(product.pk))
    return products
Esempio n. 8
0
def test_product_catalog_category_discount():
    shop = factories.get_default_shop()
    supplier = factories.get_default_supplier()
    contact = factories.create_random_person()
    group = PersonContact.get_default_group()
    category = factories.get_default_category()
    contact.groups.add(group)
    product1 = factories.create_product("p1",
                                        shop=shop,
                                        supplier=supplier,
                                        default_price=Decimal("10"))
    product2 = factories.create_product("p2",
                                        shop=shop,
                                        supplier=supplier,
                                        default_price=Decimal("20"))
    product3 = factories.create_product("p3",
                                        shop=shop,
                                        supplier=supplier,
                                        default_price=Decimal("30"))
    product1.get_shop_instance(shop).categories.add(category)
    product3.get_shop_instance(shop).categories.add(category)

    # create a 10% discount for the category
    Discount.objects.create(
        shop=shop,
        category=category,
        discount_percentage=Decimal(0.1),
        start_datetime=timezone.now(),
        end_datetime=timezone.now() + timedelta(days=1),
    )

    catalog = ProductCatalog(context=ProductCatalogContext(
        purchasable_only=False))
    ProductCatalog.index_product(product1)
    ProductCatalog.index_product(product2)
    ProductCatalog.index_product(product3)

    _assert_products_queryset(
        catalog,
        [
            (product1.pk, Decimal("10"), Decimal("9")),
            (product2.pk, Decimal("20"), None),
            (product3.pk, Decimal("30"), Decimal("27")),
        ],
    )
    _assert_shop_products_queryset(
        catalog,
        [
            (product1.get_shop_instance(shop).pk, Decimal("10"), Decimal("9")),
            (product2.get_shop_instance(shop).pk, Decimal("20"), None),
            (product3.get_shop_instance(shop).pk, Decimal("30"),
             Decimal("27")),
        ],
    )
    _assert_price(product1, shop, Decimal("9"), Decimal("10"))
    _assert_price(product2, shop, Decimal("20"), Decimal("20"))
    _assert_price(product3, shop, Decimal("27"), Decimal("30"))
Esempio n. 9
0
def test_admin_catalog_discount_category_filter(rf, admin_user):
    shop, supplier, contact, group, category = _init_test()
    default_price = Decimal("15.0")
    discount_amount = Decimal("5")
    product = factories.create_product("p1", shop=shop, supplier=supplier, default_price=default_price)
    product.get_shop_instance(shop).categories.add(category)

    ProductCatalog.index_product(product)
    view = DiscountEditView.as_view()

    # create a $5 discount for the category
    payload = _get_default_discount_data({"category": category.pk, "discount_amount_value": str(discount_amount)})
    request = apply_request_middleware(rf.post("/", data=payload), shop=shop, user=admin_user)
    with patch("django.db.transaction.on_commit", new=atomic_commit_mock):
        response = view(request, pk=None)
    assert response.status_code == 302

    anon_catalog = ProductCatalog(context=ProductCatalogContext(purchasable_only=False))
    customer_catalog = ProductCatalog(context=ProductCatalogContext(purchasable_only=False, contact=contact))

    # discount is indexed
    discounted_price = (default_price - discount_amount).quantize(Decimal("0.01"))
    _assert_products_queryset(anon_catalog, [(product.pk, default_price, discounted_price)])
    _assert_products_queryset(customer_catalog, [(product.pk, default_price, discounted_price)])

    # make the exclude_selected_category flag be True
    discount = Discount.objects.last()
    payload = _get_default_discount_data(
        {
            "category": category.pk,
            "exclude_selected_category": "on",
            "discount_amount_value": str(discount_amount),
        }
    )
    request = apply_request_middleware(rf.post("/", data=payload), shop=shop, user=admin_user)
    with patch("django.db.transaction.on_commit", new=atomic_commit_mock):
        response = view(request, pk=discount.pk)
    assert response.status_code == 302

    # discounts removed from the product
    _assert_products_queryset(anon_catalog, [(product.pk, default_price, None)])
    _assert_products_queryset(customer_catalog, [(product.pk, default_price, None)])
Esempio n. 10
0
def test_product_catalog_indexing(rf, admin_user, settings):
    shop = get_default_shop()
    supplier = get_simple_supplier(shop=shop)
    supplier.stock_managed = True
    supplier.save()
    product = create_product("simple-test-product", shop, supplier)

    ProductCatalog.index_product(product)

    # no purchasable products
    catalog = ProductCatalog(
        ProductCatalogContext(shop=shop, purchasable_only=True))
    assert catalog.get_products_queryset().count() == 0

    # add 10 items to the stock
    stock_qty = 10
    request = apply_request_middleware(rf.post("/",
                                               data={
                                                   "purchase_price":
                                                   decimal.Decimal(32.00),
                                                   "delta":
                                                   stock_qty
                                               }),
                                       user=admin_user)
    response = process_stock_adjustment(request, supplier.id, product.id)
    assert response.status_code == 200
    pss = supplier.get_stock_status(product.pk)
    assert pss.logical_count == stock_qty

    # now there are purchasable products
    assert catalog.get_products_queryset().count() == 1
    assert product in catalog.get_products_queryset()

    # create a random order with 10 units of the product
    source = OrderSource(shop)
    source.status = get_initial_order_status()
    source.add_line(
        type=OrderLineType.PRODUCT,
        supplier=supplier,
        product=product,
        base_unit_price=source.create_price(1),
        quantity=10,
    )
    OrderCreator().create_order(source)

    pss = supplier.get_stock_status(product.pk)
    assert pss.logical_count == 0

    # stocks are gone
    assert catalog.get_products_queryset().count() == 0
Esempio n. 11
0
def _get_listed_products(context, n_products, ordering=None, filter_dict=None, orderable_only=True, extra_filters=None):
    """
    Returns all products marked as listed that are determined to be
    visible based on the current context.

    :param context: Rendering context
    :type context: jinja2.runtime.Context
    :param n_products: Number of products to return
    :type n_products: int
    :param ordering: String specifying ordering
    :type ordering: str
    :param filter_dict: Dictionary of filter parameters
    :type filter_dict: dict[str, object]
    :param orderable_only: Boolean limiting results to orderable products
    :type orderable_only: bool
    :param extra_filters: Extra filters to be used in Product Queryset
    :type extra_filters: django.db.models.Q
    :rtype: list[shuup.core.models.Product]
    """
    request = context["request"]
    customer = getattr(request, "customer", None)
    shop = request.shop

    catalog = ProductCatalog(
        ProductCatalogContext(
            shop=shop,
            user=getattr(request, "user", None),
            contact=customer,
            purchasable_only=orderable_only,
            visibility=ShopProductVisibility.LISTED,
        )
    )

    if not filter_dict:
        filter_dict = {}

    products_qs = (
        catalog.get_products_queryset()
        .language(get_language())
        .filter(mode__in=ProductMode.get_parent_modes(), **filter_dict)
    )

    if extra_filters:
        products_qs = products_qs.filter(extra_filters)

    if ordering:
        products_qs = products_qs.order_by(ordering)

    return products_qs.distinct()[:n_products]
Esempio n. 12
0
def test_product_catalog_visibilities():
    shop = factories.get_default_shop()
    supplier = factories.get_default_supplier()
    contact = factories.create_random_person()
    group = factories.create_random_contact_group(shop)
    contact.groups.add(group)
    product = factories.create_product("p", shop=shop, supplier=supplier, default_price=Decimal("10"))

    catalog_visible_only = ProductCatalog(context=ProductCatalogContext(purchasable_only=False))
    catalog_visible_contact = ProductCatalog(context=ProductCatalogContext(purchasable_only=False, contact=contact))
    catalog_all = ProductCatalog(context=ProductCatalogContext(purchasable_only=False, visible_only=False))
    ProductCatalog.index_product(product)

    assert catalog_visible_only.get_products_queryset().count() == 1
    assert catalog_visible_contact.get_products_queryset().count() == 1
    assert catalog_all.get_products_queryset().count() == 1

    # change the visibility to groups
    shop_product = product.get_shop_instance(shop)
    shop_product.visibility_limit = ProductVisibility.VISIBLE_TO_GROUPS
    shop_product.save()
    shop_product.visibility_groups.add(group)
    ProductCatalog.index_product(product)

    assert catalog_visible_only.get_products_queryset().count() == 0
    assert catalog_visible_contact.get_products_queryset().count() == 1
    assert catalog_all.get_products_queryset().count() == 1

    # change the visibility to logged in
    shop_product.visibility_limit = ProductVisibility.VISIBLE_TO_LOGGED_IN
    shop_product.save()
    ProductCatalog.index_product(product)

    assert catalog_visible_only.get_products_queryset().count() == 0
    assert catalog_visible_contact.get_products_queryset().count() == 1
    assert catalog_all.get_products_queryset().count() == 1
Esempio n. 13
0
def get_all_manufacturers(context, purchasable_only=False):
    request = context["request"]
    catalog = ProductCatalog(
        ProductCatalogContext(
            shop=request.shop,
            user=getattr(request, "user", None),
            contact=getattr(request, "customer", None),
            purchasable_only=purchasable_only,
            visibility=ShopProductVisibility.LISTED,
        )
    )
    manufacturers = Manufacturer.objects.filter(
        pk__in=catalog.get_products_queryset().values_list("manufacturer_id", flat=True).distinct()
    )
    return manufacturers
Esempio n. 14
0
def _get_cross_sell_products(
    context,
    product: Product,
    types: Iterable[ProductCrossSellType],
    count=5,
    orderable_only=True,
    use_variation_parents=False,
):
    related_product_cross_sells = ProductCrossSell.objects.filter(
        type__in=types)
    # if this product is parent, then use all children instead
    if product.is_variation_parent():
        # Remember to exclude relations with the same parent
        related_product_cross_sells = related_product_cross_sells.filter(
            product1__in=product.variation_children.all()).exclude(
                product2__in=product.variation_children.all())
    else:
        related_product_cross_sells = ProductCrossSell.objects.filter(
            product1=product)

    if use_variation_parents:
        related_product_cross_sells = set(
            related_product_cross_sells.order_by("-weight").values_list(
                Coalesce("product2__variation_parent_id", "product2_id"),
                "weight").distinct())
    else:
        related_product_cross_sells = set(
            related_product_cross_sells.order_by("-weight").values_list(
                "product2_id", "weight").distinct())

    products_ids = [pcs[0] for pcs in related_product_cross_sells]

    request = context["request"]
    customer = get_person_contact(request.user)
    catalog = ProductCatalog(
        ProductCatalogContext(
            shop=request.shop,
            user=getattr(request, "user", None),
            contact=customer,
            purchasable_only=orderable_only,
            visibility=ShopProductVisibility.LISTED,
        ))
    products = catalog.get_products_queryset().filter(
        pk__in=products_ids).distinct()[:count]
    return sorted(products, key=lambda product: products_ids.index(product.id))
Esempio n. 15
0
def test_supplier_changed_reindex_catalog(rf, admin_user):
    shop = factories.get_default_shop()
    supplier = factories.get_default_supplier(shop)
    supplier.stock_managed = True
    supplier.save()
    product = factories.create_product("p1",
                                       shop,
                                       supplier,
                                       default_price=Decimal("10"))
    supplier.adjust_stock(product.pk, 40)  # add 40 to the stock
    ProductCatalog.index_product(product)

    catalog = ProductCatalog(ProductCatalogContext(purchasable_only=True))
    assert product in catalog.get_products_queryset()

    # disable the supplier
    edit_view = SupplierEditView.as_view()
    payload = {
        "base-name": supplier.name,
        "base-description__en": "",
        "base-type": SupplierType.INTERNAL.value,
        "base-stock_managed": True,
        "base-supplier_modules": [supplier.supplier_modules.first().pk],
        "base-shops": shop.pk,
        "base-enabled": "",
        "base-logo": "",
        "address-name": "Address Name",
        "address-email": "*****@*****.**",
        "address-phone": "23742578329",
        "address-tax_number": "ABC123",
        "address-street": "Streetz",
        "address-postal_code": "90014",
        "address-city": "Los Angeles",
        "address-region_code": "CA",
        "address-country": "US",
    }
    request = apply_request_middleware(rf.post("/", payload), user=admin_user)
    response = edit_view(request, pk=supplier.pk)
    assert response.status_code == 302

    supplier.refresh_from_db()
    assert not supplier.enabled

    # product is not available anymore
    assert product not in catalog.get_products_queryset()
Esempio n. 16
0
def test_product_catalog_purchasable():
    shop = factories.get_default_shop()
    supplier = factories.get_default_supplier()
    product1 = factories.create_product("p1", shop=shop, supplier=supplier, default_price=Decimal("30"))
    product2 = factories.create_product("p2", shop=shop, supplier=supplier, default_price=Decimal("10"))

    supplier.stock_managed = True
    supplier.save()

    # add 10 products to product1 stock
    supplier.adjust_stock(product1.pk, delta=10)

    catalog = ProductCatalog(context=ProductCatalogContext(purchasable_only=True))
    ProductCatalog.index_product(product1)
    ProductCatalog.index_product(product2)

    _assert_products_queryset(catalog, [(product1.pk, Decimal("30"), None)])
    _assert_shop_products_queryset(catalog, [(product1.get_shop_instance(shop).pk, Decimal("30"), None)])
Esempio n. 17
0
def test_product_catalog_product_discount():
    shop = factories.get_default_shop()
    supplier = factories.get_default_supplier()
    product1 = factories.create_product("p1",
                                        shop=shop,
                                        supplier=supplier,
                                        default_price=Decimal("10"))
    product2 = factories.create_product("p2",
                                        shop=shop,
                                        supplier=supplier,
                                        default_price=Decimal("20"))

    # create a $5 discount for the product
    Discount.objects.create(
        shop=shop,
        product=product1,
        discount_amount_value=Decimal(5),
        start_datetime=timezone.now(),
        end_datetime=timezone.now() + timedelta(days=1),
    )

    catalog = ProductCatalog(context=ProductCatalogContext(
        purchasable_only=False))
    ProductCatalog.index_product(product1)
    ProductCatalog.index_product(product2)

    _assert_products_queryset(
        catalog,
        [
            (product1.pk, Decimal("10"), Decimal("5")),
            (product2.pk, Decimal("20"), None),
        ],
    )
    _assert_shop_products_queryset(
        catalog,
        [
            (product1.get_shop_instance(shop).pk, Decimal("10"), Decimal("5")),
            (product2.get_shop_instance(shop).pk, Decimal("20"), None),
        ],
    )
    _assert_price(product1, shop, Decimal("5"), Decimal("10"))
    _assert_price(product2, shop, Decimal("20"), Decimal("20"))
Esempio n. 18
0
    def get_queryset(self):
        if not self.form.is_valid():
            return Product.objects.none()
        data = self.form.cleaned_data
        if not (data and data.get("q")):  # pragma: no cover
            return Product.objects.none()

        catalog = ProductCatalog(
            ProductCatalogContext(
                shop=self.request.shop,
                user=self.request.user,
                contact=getattr(self.request, "customer", None),
                purchasable_only=True,
                visibility=ShopProductVisibility.SEARCHABLE,
            ))
        products = catalog.get_products_queryset().filter(
            Q(mode__in=ProductMode.get_parent_modes()),
            Q(get_query_filters(self.request, None, data=data)))
        products = get_product_queryset(products, self.request, None, data)
        products = sort_products(self.request, None, products, data)
        return products.distinct()
Esempio n. 19
0
    def _visible(self,
                 shop,
                 customer,
                 language=None,
                 purchasable_only=False,
                 visibility: "ShopProductVisibility" = None):
        root = self.language(language) if language else self

        from shuup.core.catalog import ProductCatalog, ProductCatalogContext

        catalog = ProductCatalog(
            ProductCatalogContext(shop=shop,
                                  contact=customer,
                                  purchasable_only=purchasable_only,
                                  visibility=visibility))

        qs = catalog.annotate_products_queryset(root.all())
        qs = qs.select_related(
            *Product.COMMON_SELECT_RELATED).prefetch_related(
                *Product.COMMON_PREFETCH_RELATED)
        return qs.exclude(type__isnull=True)
Esempio n. 20
0
    def get_context_data(self, context):
        context = super().get_context_data(context)
        request = context["request"]
        product_ids = [
            int(pid) for pid in request.COOKIES.get("rvp", "").split(",")
            if pid != ""
        ]

        catalog = ProductCatalog(
            ProductCatalogContext(
                shop=request.shop,
                user=getattr(request, "user", None),
                contact=getattr(request, "customer", None),
                purchasable_only=True,
                visibility=ShopProductVisibility.LISTED,
            ))
        context["products"] = sorted(
            catalog.get_products_queryset().filter(
                id__in=product_ids, mode__in=ProductMode.get_parent_modes()),
            key=lambda p: product_ids.index(p.pk),
        )
        return context
Esempio n. 21
0
    def get_context_data(self, context):
        request = context["request"]
        products = self.config.get("products")
        products_qs = Product.objects.none()

        if products:
            catalog = ProductCatalog(
                ProductCatalogContext(
                    shop=request.shop,
                    user=getattr(request, "user", None),
                    contact=getattr(request, "customer", None),
                    purchasable_only=True,
                    visibility=ShopProductVisibility.LISTED,
                ))
            products_qs = catalog.get_products_queryset().filter(
                pk__in=products, mode__in=ProductMode.get_parent_modes())

        return {
            "request": request,
            "title": self.get_translated_value("title"),
            "products": products_qs
        }
Esempio n. 22
0
def test_product_catalog_variations():
    shop = factories.get_default_shop()
    supplier = factories.get_default_supplier()
    parent = factories.create_product("p1", shop=shop, supplier=supplier, default_price=Decimal("10"))
    child1 = factories.create_product("p2", shop=shop, supplier=supplier, default_price=Decimal("20"))
    child2 = factories.create_product("p3", shop=shop, supplier=supplier, default_price=Decimal("40"))
    child3 = factories.create_product("p4", shop=shop, supplier=supplier, default_price=Decimal("50"))

    child1.link_to_parent(parent)
    child2.link_to_parent(parent)
    child3.link_to_parent(parent)

    catalog = ProductCatalog(context=ProductCatalogContext(purchasable_only=False))
    ProductCatalog.index_product(parent)

    _assert_products_queryset(
        catalog,
        [
            (parent.pk, Decimal("10"), None),
            (child1.pk, Decimal("20"), None),
            (child2.pk, Decimal("40"), None),
            (child3.pk, Decimal("50"), None),
        ],
    )
    _assert_shop_products_queryset(
        catalog,
        [
            (parent.get_shop_instance(shop).pk, Decimal("10"), None),
            (child1.get_shop_instance(shop).pk, Decimal("20"), None),
            (child2.get_shop_instance(shop).pk, Decimal("40"), None),
            (child3.get_shop_instance(shop).pk, Decimal("50"), None),
        ],
    )
    _assert_price(parent, shop, Decimal("10"), Decimal("10"))
    _assert_price(child1, shop, Decimal("20"), Decimal("20"))
    _assert_price(child2, shop, Decimal("40"), Decimal("40"))
    _assert_price(child3, shop, Decimal("50"), Decimal("50"))
Esempio n. 23
0
    def get_context_data(self, context):
        request = context["request"]
        products = self.config.get("products", [])
        cache_timeout = self.config.get("cache_timeout", 0)
        products_qs = Product.objects.none()

        if request.is_ajax() and products:
            catalog = ProductCatalog(
                ProductCatalogContext(
                    shop=request.shop,
                    user=getattr(request, "user", None),
                    contact=getattr(request, "customer", None),
                    purchasable_only=True,
                    visibility=ShopProductVisibility.LISTED,
                ))
            products_qs = catalog.get_products_queryset().filter(
                pk__in=products, mode__in=ProductMode.get_parent_modes())

        return {
            "request":
            request,
            "title":
            self.get_translated_value("title"),
            "products":
            products_qs,
            "data_url":
            reverse(
                "shuup:xtheme-product-selections-highlight",
                kwargs=dict(
                    product_ids=",".join([
                        (str(prod.pk) if hasattr(prod, "pk") else str(prod))
                        for prod in products
                    ]),
                    cache_timeout=cache_timeout,
                ),
            ),
        }
Esempio n. 24
0
def get_context_data(context, request, category, product_filters):
    data = request.GET
    context["form"] = form = ProductListForm(request=request,
                                             shop=request.shop,
                                             category=category,
                                             data=data)
    form.full_clean()
    data = form.cleaned_data
    if "sort" in form.fields and not data.get("sort"):
        # Use first choice by default
        data["sort"] = form.fields["sort"].widget.choices[0][0]

    catalog = ProductCatalog(
        ProductCatalogContext(
            shop=request.shop,
            user=getattr(request, "user", None),
            contact=getattr(request, "customer", None),
            purchasable_only=True,
            supplier=data.get("supplier") or None,
            visibility=ShopProductVisibility.LISTED,
        ))
    products = (catalog.get_products_queryset().filter(
        **product_filters).filter(
            get_query_filters(request, category, data=data)).select_related(
                "primary_image", "sales_unit",
                "tax_class").prefetch_related("translations",
                                              "sales_unit__translations"))
    products = get_product_queryset(products, request, category, data)
    products = sort_products(request, category, products, data).distinct()
    context["page_size"] = data.get("limit", 12)
    context["products"] = products

    if "supplier" in data:
        context["supplier"] = data.get("supplier")

    return context
Esempio n. 25
0
def test_product_catalog_discounted_price():
    shop = factories.get_default_shop()
    supplier = factories.get_default_supplier()
    contact = factories.create_random_person()
    group = PersonContact.get_default_group()
    contact.groups.add(group)
    product1 = factories.create_product("p1",
                                        shop=shop,
                                        supplier=supplier,
                                        default_price=Decimal("50"))
    product2 = factories.create_product("p2",
                                        shop=shop,
                                        supplier=supplier,
                                        default_price=Decimal("30"))

    # set price for product2
    CgpPrice.objects.create(shop=shop,
                            product=product2,
                            group=group,
                            price_value=Decimal(25))
    # create a discount for product2
    CgpDiscount.objects.create(shop=shop,
                               product=product2,
                               group=group,
                               discount_amount_value=Decimal(7))

    anon_catalog = ProductCatalog(context=ProductCatalogContext(
        purchasable_only=False))
    customer_catalog = ProductCatalog(
        context=ProductCatalogContext(purchasable_only=False, contact=contact))
    ProductCatalog.index_product(product1)
    ProductCatalog.index_product(product2)

    _assert_products_queryset(
        anon_catalog,
        [
            (product2.pk, Decimal("30"), None),
            (product1.pk, Decimal("50"), None),
        ],
    )
    _assert_products_queryset(
        customer_catalog,
        [
            (product2.pk, Decimal("25"), Decimal("23")),
            (product1.pk, Decimal("50"), None),
        ],
    )
    _assert_shop_products_queryset(
        anon_catalog,
        [
            (product2.get_shop_instance(shop).pk, Decimal("30"), None),
            (product1.get_shop_instance(shop).pk, Decimal("50"), None),
        ],
    )
    _assert_shop_products_queryset(
        customer_catalog,
        [
            (product2.get_shop_instance(shop).pk, Decimal("25"),
             Decimal("23")),
            (product1.get_shop_instance(shop).pk, Decimal("50"), None),
        ],
    )
    # no customer
    _assert_price(product1, shop, Decimal("50"), Decimal("50"))
    _assert_price(product2, shop, Decimal("30"), Decimal("30"))
    # with the customer in the group
    _assert_price(product1,
                  shop,
                  Decimal("50"),
                  Decimal("50"),
                  customer=contact)
    _assert_price(product2,
                  shop,
                  Decimal("18"),
                  Decimal("30"),
                  customer=contact)
Esempio n. 26
0
def test_admin_custom_customer_price_updates(rf, admin_user):
    shop = factories.get_default_shop()
    supplier = factories.get_default_supplier()
    contact = factories.create_random_person()
    group = PersonContact.get_default_group()
    contact.groups.add(group)
    product_type = factories.get_default_product_type()
    tax_class = factories.get_default_tax_class()
    sales_unit = factories.get_default_sales_unit()

    view = ProductEditView.as_view()
    group_price = "10.0"
    default_price = "15.0"

    payload = {
        "base-name__en": "My Product",
        "base-type": product_type.pk,
        "base-sku": "p1",
        "base-shipping_mode": ShippingMode.NOT_SHIPPED.value,
        "base-tax_class": tax_class.pk,
        "base-sales_unit": sales_unit.pk,
        "base-width": "0",
        "base-height": "0",
        "base-depth": "0",
        "base-net_weight": "0",
        "base-gross_weight": "0",
        f"shop{shop.pk}-default_price_value": default_price,
        f"shop{shop.pk}-visibility":
        ShopProductVisibility.ALWAYS_VISIBLE.value,
        f"shop{shop.pk}-visibility_limit":
        ProductVisibility.VISIBLE_TO_ALL.value,
        f"shop{shop.pk}-minimum_purchase_quantity": "1",
        f"shop{shop.pk}-purchase_multiple": "1",
        f"shop{shop.pk}-suppliers": [supplier.pk],
        f"customer_group_pricing-s_{shop.pk}_g_{group.pk}":
        group_price,  # set price for the group
    }

    # create a new product
    request = apply_request_middleware(rf.post("/", data=payload),
                                       shop=shop,
                                       user=admin_user)
    with patch("django.db.transaction.on_commit", new=atomic_commit_mock):
        response = view(request, pk=None)

    assert response.status_code == 302

    anon_catalog = ProductCatalog(context=ProductCatalogContext(
        purchasable_only=False))
    customer_catalog = ProductCatalog(
        context=ProductCatalogContext(purchasable_only=False, contact=contact))

    product = Product.objects.first()
    _assert_products_queryset(anon_catalog,
                              [(product.pk, Decimal(default_price), None)])
    _assert_products_queryset(customer_catalog,
                              [(product.pk, Decimal(group_price), None)])

    payload.update({
        # remove the customer group price
        f"customer_group_pricing-s_{shop.pk}_g_{group.pk}": "",
        "media-TOTAL_FORMS": 0,
        "media-INITIAL_FORMS": 0,
        "media-MIN_NUM_FORMS": 0,
        "media-MAX_NUM_FORMS": 1000,
        "images-TOTAL_FORMS": 0,
        "images-INITIAL_FORMS": 0,
        "images-MIN_NUM_FORMS": 0,
        "images-MAX_NUM_FORMS": 1000,
    })

    request = apply_request_middleware(rf.post("/", data=payload),
                                       shop=shop,
                                       user=admin_user)
    with patch("django.db.transaction.on_commit", new=atomic_commit_mock):
        response = view(request, pk=product.get_shop_instance(shop).pk)
    assert response.status_code == 302

    # default price for both
    _assert_products_queryset(anon_catalog,
                              [(product.pk, Decimal(default_price), None)])
    _assert_products_queryset(customer_catalog,
                              [(product.pk, Decimal(default_price), None)])
Esempio n. 27
0
def test_product_catalog_happy_hour_timezone_discount():
    """
    Make sure a discount is valid for different timezones
    """
    shop = factories.get_default_shop()
    supplier = factories.get_default_supplier()
    product1 = factories.create_product("p1",
                                        shop=shop,
                                        supplier=supplier,
                                        default_price=Decimal("10"))

    discount = Discount.objects.create(
        shop=shop,
        discount_percentage=Decimal(0.1),
        start_datetime=datetime(2021, 1, 1, tzinfo=pytz.utc),
        end_datetime=datetime(2021, 1, 30, tzinfo=pytz.utc),
    )
    happy_hour = HappyHour.objects.create(name="Super Happy", shop=shop)
    # the happy hour is available on Mondays, from 2am-4am (UTC)
    TimeRange.objects.create(from_hour=time(2, 0),
                             to_hour=time(4, 0),
                             weekday=0,
                             happy_hour=happy_hour)
    discount.happy_hours.add(happy_hour)

    catalog = ProductCatalog(context=ProductCatalogContext(
        purchasable_only=False))
    ProductCatalog.index_product(product1)

    # Monday, 2am UTC - valid discounts
    with patch.object(timezone,
                      "now",
                      return_value=datetime(2021, 1, 4, 2, 0,
                                            tzinfo=pytz.utc)):
        _assert_price(product1, shop, Decimal("9"), Decimal("10"))
        _assert_products_queryset(catalog,
                                  [(product1.pk, Decimal("10"), Decimal("9"))])

    # it's Monday, 11:58pm in Brazil, the discount shouldn't be valid
    sao_paulo_tz = pytz.timezone("America/Sao_Paulo")
    with patch.object(timezone,
                      "now",
                      return_value=sao_paulo_tz.localize(
                          datetime(2021, 1, 4, 23, 58))):
        _assert_price(product1, shop, Decimal("10"), Decimal("10"))
        _assert_products_queryset(catalog,
                                  [(product1.pk, Decimal("10"), None)])

    # it's Monday, 2:58am in Brazil on Monday, the discount shouldn't be valid
    with patch.object(timezone,
                      "now",
                      return_value=sao_paulo_tz.localize(
                          datetime(2021, 1, 4, 2, 58))):
        _assert_price(product1, shop, Decimal("10"), Decimal("10"))
        _assert_products_queryset(catalog,
                                  [(product1.pk, Decimal("10"), None)])

    # it's Sunday, 11:58pm in Brazil, the discount should be available as it is Monday 2:58am in UTC
    with patch.object(timezone,
                      "now",
                      return_value=sao_paulo_tz.localize(
                          datetime(2021, 1, 3, 23, 58))):
        _assert_price(product1, shop, Decimal("9"), Decimal("10"))
        _assert_products_queryset(catalog,
                                  [(product1.pk, Decimal("10"), Decimal("9"))])
Esempio n. 28
0
def test_admin_catalog_discount_happy_hour_filter(rf, admin_user):
    shop, supplier, contact, group, category = _init_test()
    default_price = Decimal("15.0")
    discounted_price_value = Decimal("12")
    product = factories.create_product("p1", shop=shop, supplier=supplier, default_price=default_price)
    product.get_shop_instance(shop).categories.add(category)

    ProductCatalog.index_product(product)
    discount_view = DiscountEditView.as_view()
    happy_hour_view = HappyHourEditView.as_view()

    # create a happy hour, from 9am to 11am
    payload = {"name": "Mondays", "from_hour": "09:00", "to_hour": "11:00", "weekdays": "0"}
    request = apply_request_middleware(rf.post("/", data=payload), shop=shop, user=admin_user)
    with patch("django.db.transaction.on_commit", new=atomic_commit_mock):
        response = happy_hour_view(request, pk=None)
    assert response.status_code == 302

    happy_hour = HappyHour.objects.last()

    # create a discounted price using the happy hour
    payload = _get_default_discount_data(
        {
            "start_datetime": datetime(2021, 1, 1).strftime("%Y-%m-%d %H:%M"),
            "end_datetime": datetime(2022, 1, 1).strftime("%Y-%m-%d %H:%M"),
            "happy_hours": happy_hour.pk,
            "discounted_price_value": str(discounted_price_value),
        }
    )
    request = apply_request_middleware(rf.post("/", data=payload), shop=shop, user=admin_user)
    with patch("django.db.transaction.on_commit", new=atomic_commit_mock):
        response = discount_view(request, pk=None)
    assert response.status_code == 302

    catalog = ProductCatalog(context=ProductCatalogContext(purchasable_only=False))

    # # Monday, 6am
    # with patch.object(timezone, "now", return_value=datetime(2021, 1, 4, 6, 0, tzinfo=pytz.utc)):
    #     _assert_products_queryset(catalog, [(product.pk, default_price, None)])
    # # Monday, 10am
    # with patch.object(timezone, "now", return_value=datetime(2021, 1, 4, 10, 0, tzinfo=pytz.utc)):
    #     _assert_products_queryset(catalog, [(product.pk, default_price, discounted_price_value)])

    # change the happy hour time to Tuesdays, from 3pm to 6pm
    discount = happy_hour.discounts.last()
    assert discount
    payload = {"name": "Tuesdays", "from_hour": "15:00", "to_hour": "18:00", "weekdays": "1", "discounts": discount.pk}
    request = apply_request_middleware(rf.post("/", data=payload), shop=shop, user=admin_user)
    with patch("django.db.transaction.on_commit", new=atomic_commit_mock):
        response = happy_hour_view(request, pk=happy_hour.pk)
    assert response.status_code == 302

    # Monday, 10am
    with patch.object(timezone, "now", return_value=datetime(2021, 1, 4, 10, 0, tzinfo=pytz.utc)):
        _assert_products_queryset(catalog, [(product.pk, default_price, None)])
    # Monday, 5pm
    with patch.object(timezone, "now", return_value=datetime(2021, 1, 4, 17, 0, tzinfo=pytz.utc)):
        _assert_products_queryset(catalog, [(product.pk, default_price, None)])
    # Tuesday, 5pm
    with patch.object(timezone, "now", return_value=datetime(2021, 1, 5, 17, 0, tzinfo=pytz.utc)):
        _assert_products_queryset(catalog, [(product.pk, default_price, discounted_price_value)])
Esempio n. 29
0
def test_product_catalog_happy_hour_discount():
    shop = factories.get_default_shop()
    supplier = factories.get_default_supplier()
    product1 = factories.create_product("p1",
                                        shop=shop,
                                        supplier=supplier,
                                        default_price=Decimal("10"))
    product2 = factories.create_product("p2",
                                        shop=shop,
                                        supplier=supplier,
                                        default_price=Decimal("20"))

    # create a 20% discount for a happy hour (should be in range of 8pm-9pm)
    discount = Discount.objects.create(
        shop=shop,
        discount_percentage=Decimal(0.2),
        start_datetime=datetime(2021, 1, 1, tzinfo=pytz.utc),
        end_datetime=datetime(2021, 1, 30, tzinfo=pytz.utc),
    )
    happy_hour = HappyHour.objects.create(name="Super Happy", shop=shop)
    # the happy hour is on Mondays from 8-9pm
    TimeRange.objects.create(from_hour=time(20, 0),
                             to_hour=time(21, 0),
                             weekday=0,
                             happy_hour=happy_hour)
    discount.happy_hours.add(happy_hour)

    catalog = ProductCatalog(context=ProductCatalogContext(
        purchasable_only=False))
    ProductCatalog.index_product(product1)
    ProductCatalog.index_product(product2)

    # Monday, 12pm
    with patch.object(timezone,
                      "now",
                      return_value=datetime(2021, 1, 4, 12, 0,
                                            tzinfo=pytz.utc)):
        _assert_products_queryset(
            catalog,
            [
                (product1.pk, Decimal("10"), None),
                (product2.pk, Decimal("20"), None),
            ],
        )
        _assert_shop_products_queryset(
            catalog,
            [
                (product1.get_shop_instance(shop).pk, Decimal("10"), None),
                (product2.get_shop_instance(shop).pk, Decimal("20"), None),
            ],
        )
        _assert_price(product1, shop, Decimal("10"), Decimal("10"))
        _assert_price(product2, shop, Decimal("20"), Decimal("20"))

    # Monday, 8:30pm
    with patch.object(timezone,
                      "now",
                      return_value=datetime(2021,
                                            1,
                                            4,
                                            20,
                                            30,
                                            tzinfo=pytz.utc)):
        _assert_products_queryset(
            catalog,
            [
                (product1.pk, Decimal("10"), Decimal("8")),
                (product2.pk, Decimal("20"), Decimal("16")),
            ],
        )
        _assert_shop_products_queryset(
            catalog,
            [
                (product1.get_shop_instance(shop).pk, Decimal("10"),
                 Decimal("8")),
                (product2.get_shop_instance(shop).pk, Decimal("20"),
                 Decimal("16")),
            ],
        )
        _assert_price(product1, shop, Decimal("8"), Decimal("10"))
        _assert_price(product2, shop, Decimal("16"), Decimal("20"))
Esempio n. 30
0
def test_product_catalog_cgp_with_variations():
    shop = factories.get_default_shop()
    supplier = factories.get_default_supplier()
    contact = factories.create_random_person()
    group = PersonContact.get_default_group()
    contact.groups.add(group)
    parent = factories.create_product("p1",
                                      shop=shop,
                                      supplier=supplier,
                                      default_price=Decimal("10"))
    child1 = factories.create_product("p2",
                                      shop=shop,
                                      supplier=supplier,
                                      default_price=Decimal("20"))
    child2 = factories.create_product("p3",
                                      shop=shop,
                                      supplier=supplier,
                                      default_price=Decimal("40"))
    child3 = factories.create_product("p4",
                                      shop=shop,
                                      supplier=supplier,
                                      default_price=Decimal("50"))

    child1.link_to_parent(parent)
    child2.link_to_parent(parent)
    child3.link_to_parent(parent)

    # set a price for child2
    CgpPrice.objects.create(shop=shop,
                            product=child2,
                            group=group,
                            price_value=Decimal("5"))
    # create a discount for child3
    CgpDiscount.objects.create(shop=shop,
                               product=child3,
                               group=group,
                               discount_amount_value=Decimal("35"))

    catalog = ProductCatalog(
        context=ProductCatalogContext(purchasable_only=False, contact=contact))
    ProductCatalog.index_product(parent)

    _assert_products_queryset(
        catalog,
        [
            (child2.pk, Decimal("5"), None),
            (parent.pk, Decimal("10"), None),
            (child1.pk, Decimal("20"), None),
            (child3.pk, Decimal("50"), Decimal("15")),
        ],
    )
    _assert_shop_products_queryset(
        catalog,
        [
            (child2.get_shop_instance(shop).pk, Decimal("5"), None),
            (parent.get_shop_instance(shop).pk, Decimal("10"), None),
            (child1.get_shop_instance(shop).pk, Decimal("20"), None),
            (child3.get_shop_instance(shop).pk, Decimal("50"), Decimal("15")),
        ],
    )
    # no customer
    _assert_price(parent, shop, Decimal("10"), Decimal("10"))
    _assert_price(child1, shop, Decimal("20"), Decimal("20"))
    _assert_price(child2, shop, Decimal("40"), Decimal("40"))
    _assert_price(child3, shop, Decimal("50"), Decimal("50"))
    # with the customer in the group
    _assert_price(parent, shop, Decimal("10"), Decimal("10"), customer=contact)
    _assert_price(child1, shop, Decimal("20"), Decimal("20"), customer=contact)
    _assert_price(child2, shop, Decimal("5"), Decimal("40"), customer=contact)
    _assert_price(child3, shop, Decimal("15"), Decimal("50"), customer=contact)