def test_view_custom_mass_actions(rf, admin_user):
    factories.get_default_shop()
    request = apply_request_middleware(rf.get("/", {"jq": json.dumps({"perPage": 100, "page": 1})}), user=admin_user)
    list_view_func = ManufacturerListView.as_view()

    # no mass actions
    response = list_view_func(request)
    data = json.loads(response.content.decode("utf-8"))
    assert not data["massActions"]

    # test with specific key
    with override_provides("manufacturer_list_mass_actions_provider", [
        "shuup.testing.modules.mocker.mass_actions:DummyMassActionProvider"
    ]):
        response = list_view_func(request)
        data = json.loads(response.content.decode("utf-8"))
        identifiers = [action["key"] for action in data["massActions"]]
        assert "dummy_mass_action_1" in identifiers
        assert "dummy_mass_action_2" in identifiers

    list_view_func = SalesUnitListView.as_view()
    # test with global
    with override_provides("admin_mass_actions_provider", [
        "shuup.testing.modules.mocker.mass_actions:DummyMassActionProvider"
    ]):
        response = list_view_func(request)
        data = json.loads(response.content.decode("utf-8"))
        identifiers = [action["key"] for action in data["massActions"]]
        assert "dummy_mass_action_1" in identifiers
        assert "dummy_mass_action_2" in identifiers
Example #2
0
def test_contact_details_view_with_many_groups(rf, admin_user):
    get_default_shop()
    person = create_random_person()
    person.groups.add(
        ContactGroup.objects.create(name="Czz Group"),
        ContactGroup.objects.create(name="Azz Group"),
        ContactGroup.objects.create(name="Bzz Group"),
        ContactGroup.objects.language('fi').create(name="Dzz ryhmä"),
    )

    # Group with name in two languages
    grp_e = ContactGroup.objects.language('en').create(name="Ezz Group")
    grp_e.set_current_language('fi')
    grp_e.name = "Ezz ryhmä"
    grp_e.save()
    person.groups.add(grp_e)

    request = apply_request_middleware(rf.get("/"), user=admin_user)
    with translation.override('en'):
        view_func = ContactDetailView.as_view()
        response = view_func(request, pk=person.pk)
    content = response.render().content.decode('utf-8')
    assert "Azz Group" in content
    assert "Bzz Group" in content
    assert "Czz Group" in content
    assert "Dzz ryhmä" in content, "no name in active language, still present"
    assert "Ezz Group" in content, "rendered with active language"
    positions = [content.index(x + "zz ") for x in 'ABCDE']
    assert positions == sorted(positions), "Groups are sorted"
    assert response.status_code == 200
Example #3
0
def test_refund_entire_order():
    shop = get_default_shop()
    supplier = get_simple_supplier()
    product = create_product(
        "test-sku",
        shop=get_default_shop(),
        default_price=10,
    )
    supplier.adjust_stock(product.id, 5)
    check_stock_counts(supplier, product, 5, 5)

    order = create_order_with_product(product, supplier, 2, 200, Decimal("0.24"), shop=shop)
    order.cache_prices()

    original_total_price = order.taxful_total_price
    check_stock_counts(supplier, product, 5, 3)

    # Create a full refund with `restock_products` set to False
    order.create_full_refund(restock_products=False)

    # Confirm the refund was created with correct amount
    assert order.taxless_total_price.amount.value == 0
    assert order.taxful_total_price.amount.value == 0
    refund_line = order.lines.order_by("ordering").last()
    assert refund_line.type == OrderLineType.REFUND
    assert refund_line.taxful_price == -original_total_price

    # Make sure logical count reflects refunded products
    check_stock_counts(supplier, product, 5, 3)
def test_behavior_delete_save(rf, admin_user, view, model, get_object, service_provider_attr):
    """
    Only testing one initial behavior component
    """
    get_default_shop()
    with override_settings(LANGUAGES=[("en", "en")]):
        object = get_object()
        view = view.as_view()
        service_provider_attr_field = "base-%s" % service_provider_attr

        component = WeightLimitsBehaviorComponent.objects.create(min_weight=0, max_weight=1)
        object.behavior_components.add(component)
        components_before = object.behavior_components.count()
        assert components_before == 1

        data = get_default_data(object, service_provider_attr, service_provider_attr_field, delete=True)
        data["weightlimitsbehaviorcomponent-0-id"] = component.id
        data["weightlimitsbehaviorcomponent-INITIAL_FORMS"] = 1
        data["weightlimitsbehaviorcomponent-TOTAL_FORMS"] = 2

        request = apply_request_middleware(rf.post("/", data=data, user=admin_user))
        response = view(request, pk=object.pk)
        if hasattr(response, "render"):
            response.render()
        components_after = object.behavior_components.count()
        assert not components_after

        assert not WaivingCostBehaviorComponent.objects.first()
Example #5
0
def test_refund_without_shipment(restock):
    shop = get_default_shop()
    supplier = get_simple_supplier()
    product = create_product(
        "test-sku",
        shop=get_default_shop(),
        default_price=10,
    )
    # Start out with a supplier with quantity of 10 of a product
    supplier.adjust_stock(product.id, 10)
    check_stock_counts(supplier, product, physical=10, logical=10)

    order = create_order_with_product(product, supplier, 2, 200, shop=shop)
    order.cache_prices()
    check_stock_counts(supplier, product, physical=10, logical=8)

    # Restock value shouldn't matter if we don't have any shipments
    product_line = order.lines.first()
    order.create_refund([
        {"line": product_line, "quantity": 2, "amount": Money(400, order.currency), "restock_products": restock}])

    if restock:
        check_stock_counts(supplier, product, physical=10, logical=10)
    else:
        check_stock_counts(supplier, product, physical=10, logical=8)
    assert product_line.refunded_quantity == 2
    assert order.get_total_tax_amount() == Money(
        order.taxful_total_price_value - order.taxless_total_price_value,
        order.currency)
def test_rules_and_effects(rf, admin_user):
    """
    To make things little bit more simple let's use only english as
    a language.
    """
    get_default_shop()
    with override_settings(LANGUAGES=[("en", "en")]):
        shop = get_default_shop()
        object = BasketCampaign.objects.create(name="test campaign", active=True, shop=shop)
        assert object.conditions.count() == 0
        assert object.discount_effects.count() == 0
        view = BasketCampaignEditView.as_view()
        data = {
            "base-name": "test campaign",
            "base-public_name__en": "Test Campaign",
            "base-shop": get_default_shop().id,
            "base-active": True,
            "base-basket_line_text": "Test campaign activated!"
        }
        with override_provides(
                "campaign_basket_condition", ["shuup.campaigns.admin_module.forms:BasketTotalProductAmountConditionForm"]):
            with override_provides(
                    "campaign_basket_discount_effect_form", ["shuup.campaigns.admin_module.forms:BasketDiscountAmountForm"]):
                with override_provides("campaign_basket_line_effect_form", []):
                    data.update(get_products_in_basket_data())
                    data.update(get_free_product_data(object))
                    request = apply_request_middleware(rf.post("/", data=data), user=admin_user)
                    view(request, pk=object.pk)

        object.refresh_from_db()
        assert object.conditions.count() == 1
        assert object.discount_effects.count() == 1
def test_campaign_new_mode_view_formsets(rf, admin_user):
    view = BasketCampaignEditView
    get_default_shop()
    request = apply_request_middleware(rf.get("/"), user=admin_user)
    form_parts = get_form_parts(request, view, view.model())
    assert len(form_parts) == 1
    assert issubclass(form_parts[0].__class__, BasketBaseFormPart)
Example #8
0
def test_services_edit_view_formsets(rf, admin_user, view, get_object):
    get_default_shop()
    object = get_object()
    request = apply_request_middleware(rf.get("/"), user=admin_user)
    form_parts = get_form_parts(request, view, object)
    # form parts should include forms, form parts and plus one for the base form
    assert len(form_parts) == (len(DEFAULT_BEHAVIOR_FORMS) + len(DEFAULT_BEHAVIOR_FORM_PARTS) + 1)
def test_campaign_edit_save(rf, admin_user):
    """
    To make things little bit more simple let's use only english as
    a language.
    """
    with override_settings(LANGUAGES=[("en", "en")]):
        shop = get_default_shop()
        object = BasketCampaign.objects.create(name="test campaign", active=True, shop=shop)
        object.save()
        view = BasketCampaignEditView.as_view()
        new_name = "Test Campaign"
        assert object.name != new_name
        data = {
            "base-name": new_name,
            "base-public_name__en": "Test Campaign",
            "base-shop": get_default_shop().id,
            "base-active": True,
            "base-basket_line_text": "Test campaign activated!"
        }
        methods_before = BasketCampaign.objects.count()
        # Conditions and effects is tested separately
        with override_provides("campaign_basket_condition", []):
            with override_provides("campaign_basket_discount_effect_form", []):
                with override_provides("campaign_basket_line_effect_form", []):
                    request = apply_request_middleware(rf.post("/", data=data), user=admin_user)
                    response = view(request, pk=object.pk)
                    assert response.status_code in [200, 302]

        assert BasketCampaign.objects.count() == methods_before
        assert BasketCampaign.objects.get(pk=object.pk).name == new_name
Example #10
0
def test_login_with_email_3(client, regular_user, rf):
    if "shuup.front.apps.auth" not in settings.INSTALLED_APPS:
        pytest.skip("Need shuup.front.apps.auth in INSTALLED_APPS")

    new_user_password = "******"
    new_user = get_user_model().objects.create_user(
        username=regular_user.email,
        password=new_user_password,
        email=regular_user.email
    )

    get_default_shop()
    prepare_user(regular_user)
    redirect_target = "/redirect-success/"

    # Login with new_user username should work even if there is users with same email
    response = client.post(reverse("shuup:login"), data={
        "username": regular_user.email,
        "password": new_user_password,
        REDIRECT_FIELD_NAME: redirect_target
    })

    assert response.get("location")
    assert response.get("location").endswith(redirect_target)

    request = rf.get("/")
    request.session = client.session
    assert get_user(request) == new_user, "User is logged in"
Example #11
0
def test_login_inactive_user_fails(client, regular_user, rf):
    if "shuup.front.apps.auth" not in settings.INSTALLED_APPS:
        pytest.skip("Need shuup.front.apps.auth in INSTALLED_APPS")

    get_default_shop()
    prepare_user(regular_user)

    response = client.post(reverse("shuup:login"), data={
        "username": regular_user.username,
        "password": REGULAR_USER_PASSWORD,
    })

    request = rf.get("/")
    request.session = client.session
    assert get_user(request) == regular_user, "User is logged in"

    request = rf.get("/")
    request.session = client.session
    logout(request)

    user_contact = regular_user.contact
    assert user_contact.is_active

    user_contact.is_active = False
    user_contact.save()

    client.post(reverse("shuup:login"), data={
        "username": regular_user.username,
        "password": REGULAR_USER_PASSWORD,
    })

    request = rf.get("/")
    request.session = client.session
    assert get_user(request).is_anonymous(), "User is still anonymous"
def test_single_page_checkout_with_login_and_register(browser, live_server, settings):
    # initialize
    product_name = "Test Product"
    get_default_shop()
    pm = get_default_payment_method()
    sm = get_default_shipping_method()
    product = create_orderable_product(product_name, "test-123", price=100)
    OrderStatus.objects.create(
        identifier="initial",
        role=OrderStatusRole.INITIAL,
        name="initial",
        default=True
    )

    # Initialize test and go to front page
    browser = initialize_front_browser_test(browser, live_server)

    wait_until_condition(browser, lambda x: x.is_text_present("Welcome to Default!"))
    navigate_to_checkout(browser, product)

    # Let's assume that after addresses the checkout is normal
    wait_until_condition(browser, lambda x: x.is_text_present("Checkout Method"))
    test_username = "******"
    test_email = "*****@*****.**"
    test_password = "******"
    register_test(browser, live_server, test_username, test_email, test_password)

    login_and_finish_up_the_checkout(browser, live_server, test_username, test_email, test_password)
Example #13
0
def test_category_links_plugin_with_customer(rf, show_all_categories):
    """
    Test plugin for categories that is visible for certain group
    """
    shop = get_default_shop()
    group = get_default_customer_group()
    customer = create_random_person()
    customer.groups.add(group)
    customer.save()

    request = rf.get("/")
    request.shop = get_default_shop()
    apply_request_middleware(request)
    request.customer = customer

    category = get_default_category()
    category.status = CategoryStatus.VISIBLE
    category.visibility = CategoryVisibility.VISIBLE_TO_GROUPS
    category.visibility_groups.add(group)
    category.shops.add(shop)
    category.save()

    vars = {"request": request}
    context = get_jinja_context(**vars)
    plugin = CategoryLinksPlugin({"categories": [category.pk], "show_all_categories": show_all_categories})
    assert category.is_visible(customer)
    assert category in plugin.get_context_data(context)["categories"]

    customer_without_groups = create_random_person()
    customer_without_groups.groups.clear()

    assert not category.is_visible(customer_without_groups)
    request.customer = customer_without_groups
    context = get_jinja_context(**vars)
    assert category not in plugin.get_context_data(context)["categories"]
Example #14
0
def test_edit_view_adding_messages_to_form_group(rf, admin_user):
    get_default_shop()  # obvious prerequisite
    product = get_default_product()
    view = ProductEditView.as_view()
    request = apply_request_middleware(rf.get("/"), user=admin_user)
    response = view(request, pk=product.pk)
    response.render()
    assert 200 <= response.status_code < 300

    assert ProductEditView.add_form_errors_as_messages

    content = force_text(response.content)
    post = extract_form_fields(BeautifulSoup(content))
    post_data = {
        # Error in the base form part
        "base-name__en": "",
    }
    post.update(post_data)
    request = apply_request_middleware(rf.post("/", post), user=admin_user)
    response = view(request, pk=product.pk)

    errors = response.context_data["form"].errors

    assert "base" in errors
    assert "name__en" in errors["base"]
def test_campaign_end_date(rf, admin_user):
    """
    To make things little bit more simple let's use only english as
    a language.
    """
    with override_settings(LANGUAGES=[("en", "en")]):
        shop = get_default_shop()
        old_name = "test_campaign"
        object = CatalogCampaign.objects.create(name=old_name, active=True, shop=shop)
        object.save()
        view = CatalogCampaignEditView.as_view()
        new_name = "Test Campaign"
        assert object.name != new_name
        data = {
            "base-name": new_name,
            "base-public_name__en": "Test Campaign",
            "base-shop": get_default_shop().id,
            "base-active": True,
            "base-basket_line_text": "Test campaign activated!",
            "base-start_datetime": datetime.datetime(year=2016, month=6, day=19),
            "base-end_datetime": datetime.datetime(year=2016, month=6, day=10)
        }
        methods_before = CatalogCampaign.objects.count()
        # Conditions, effects and effects is tested separately
        with override_provides("campaign_context_condition", []):
            with override_provides("campaign_catalog_filter", []):
                with override_provides("campaign_product_discount_effect_form", []):
                    request = apply_request_middleware(rf.post("/", data=data), user=admin_user)
                    response = view(request, pk=object.pk)
                    assert response.status_code in [200, 302]
                    content = response.render().content.decode("utf-8")
                    assert "Campaign end date can&#39;t be before start date." in content
        assert CatalogCampaign.objects.count() == methods_before
        assert CatalogCampaign.objects.get(pk=object.pk).name == old_name
def test_theme_without_default_template_dir():
    get_default_shop()
    with override_current_theme_class(ShuupTestingTheme):
        c = SmartClient()
        soup = c.soup(reverse("shuup:index"))
        assert "Simple base for themes to use" not in soup
        assert "Welcome to test Shuup!" in soup.find("div", {"class": "page-content"}).text
def test_theme_with_default_template_dir():
    get_default_shop()
    with override_current_theme_class(ShuupTestingThemeWithCustomBase):
        c = SmartClient()
        soup = c.soup(reverse("shuup:index"))
        assert "Simple base for themes to use" in soup.find("h1").text
        assert "Welcome to test Shuup!" in soup.find("h1").text
Example #18
0
def test_menu_toggle(browser, admin_user, live_server, settings):
    get_default_shop()
    initialize_admin_browser_test(browser, live_server, settings)

    wait_until_condition(browser, lambda x: x.is_text_present("Welcome!"))
    wait_until_condition(browser, lambda x: x.is_text_present("Quicklinks"))

    wait_until_condition(browser, lambda x: x.is_element_present_by_css("#menu-button"))

    # Close menu
    try:
        browser.find_by_css("#menu-button").first.click()
    except selenium.common.exceptions.TimeoutException as e:
        browser.find_by_css("#menu-button").first.click()
    wait_until_condition(browser, lambda x: x.is_element_present_by_css(".desktop-menu-closed"))

    url = reverse("shuup_admin:order.list")
    browser.visit("%s%s" % (live_server, url))
    wait_until_condition(browser, condition=lambda x: x.is_text_present("Orders"))

    # Should be closed after page load
    wait_until_condition(browser, lambda x: x.is_element_present_by_css(".desktop-menu-closed"))

    # Open menu
    browser.find_by_css("#menu-button").first.click()
    wait_until_condition(browser, lambda x: not x.is_element_present_by_css(".desktop-menu-closed"))

    url = reverse("shuup_admin:shop_product.list")
    browser.visit("%s%s" % (live_server, url))
    wait_until_condition(browser, condition=lambda x: x.is_text_present("Products"))

    # Should be still open after page load
    wait_until_condition(browser, lambda x: not x.is_element_present_by_css(".desktop-menu-closed"))
def test_customer_edit_redirects_to_login_if_not_logged_in():
    get_default_shop()  # Front middleware needs a Shop to exists
    urls = ["shuup:customer_edit", "shuup:company_edit"]
    for url in urls:
        response = SmartClient().get(reverse(url), follow=False)
        assert response.status_code == 302  # Redirection ("Found")
        assert resolve_url(settings.LOGIN_URL) in response.url
Example #20
0
def test_refund_with_product_restock():
    shop = get_default_shop()
    supplier = get_simple_supplier()
    product = create_product(
        "test-sku",
        shop=get_default_shop(),
        default_price=10,
        stock_behavior=StockBehavior.STOCKED
    )
    supplier.adjust_stock(product.id, 5)
    assert supplier.get_stock_statuses([product.id])[product.id].logical_count == 5

    order = create_order_with_product(product, supplier, 2, 200, shop=shop)
    order.cache_prices()

    assert supplier.get_stock_statuses([product.id])[product.id].logical_count == 3

    # Create a refund with a parent line and quanity with `restock_products` set to False
    product_line = order.lines.first()
    order.create_refund([{"line": product_line, "quantity": 1, "restock_products": False}])

    assert supplier.get_stock_statuses([product.id])[product.id].logical_count == 3
    assert order.lines.last().taxful_price == -product_line.base_unit_price
    assert order.get_total_unrefunded_amount() == product_line.base_unit_price.amount

    # Create a refund with a parent line and quanity with `restock_products` set to True
    product_line = order.lines.first()
    order.create_refund([{"line": product_line, "quantity": 1, "restock_products": True}])

    assert supplier.get_stock_statuses([product.id])[product.id].logical_count == 4
    assert not order.taxful_total_price
Example #21
0
def test_refunds_with_quantities():
    shop = get_default_shop()
    supplier = get_default_supplier()
    product = create_product(
        "test-sku",
        shop=get_default_shop(),
        default_price=10,
        stock_behavior=StockBehavior.STOCKED
    )

    order = create_order_with_product(product, supplier, 3, 200, shop=shop)
    order.cache_prices()
    assert not order.lines.refunds()

    product_line = order.lines.first()
    refund_amount = Money(100, order.currency)
    order.create_refund([{"line": product_line, "quantity": 2, "amount": refund_amount}])
    assert len(order.lines.refunds()) == 2

    quantity_line = order.lines.refunds().filter(quantity=2).first()
    assert quantity_line
    amount_line = order.lines.refunds().filter(quantity=1).first()
    assert amount_line

    assert quantity_line.taxful_base_unit_price == -product_line.taxful_base_unit_price
    assert amount_line.taxful_price.amount == -refund_amount
Example #22
0
def test_save_cart_errors(rf, regular_user):
    get_default_shop()
    request = apply_request_middleware(rf.post("/", {
        "title": "test"
    }))
    response = CartSaveView.as_view()(request)
    data = json.loads(response.content.decode("utf8"))
    assert response.status_code == 403, "can't save cart as anonymous user"
    assert not data["ok"], "can't save cart without title"

    customer = get_person_contact(regular_user)
    request = apply_request_middleware(rf.post("/", {
        "title": ""
    }), customer=customer, user=regular_user)
    response = CartSaveView.as_view()(request)
    data = json.loads(response.content.decode("utf8"))
    assert response.status_code == 400
    assert not data["ok"], "can't save cart without title"

    request = apply_request_middleware(rf.post("/", {
        "title": "test"
    }), customer=customer, user=regular_user)
    response = CartSaveView.as_view()(request)
    data = json.loads(response.content.decode("utf8"))
    assert response.status_code == 400
    assert not data["ok"], "can't save an empty cart"
Example #23
0
def test_register_api():
    get_default_shop()
    configuration.set(None, "api_permission_FrontUserViewSet", PermissionLevel.PUBLIC_WRITE)
    client = _get_client()
    register_data = {
        "username": "******",
        "password": "******"
    }
    response = client.post("/api/shuup/front/user/",
                           content_type="application/json",
                           data=json.dumps(register_data))
    assert response.status_code == status.HTTP_201_CREATED
    response_json = json.loads(response.content.decode("utf-8"))
    assert "token" in response_json
    assert get_user_model().objects.count() == 1

    # Try to register with same username
    register_data = {
        "username": "******",
        "password": "******"
    }
    response = client.post("/api/shuup/front/user/",
                           content_type="application/json",
                           data=json.dumps(register_data))
    assert response.status_code == status.HTTP_400_BAD_REQUEST
    assert "User already exists" in response.content.decode("utf-8")
Example #24
0
def test_order_creator_view_for_customer(rf, admin_user):
    get_default_shop()
    contact = create_random_person(locale="en_US", minimum_name_comp_len=5)
    request = apply_request_middleware(rf.get("/", {"contact_id": contact.id}), user=admin_user)
    response = OrderEditView.as_view()(request)
    assert_contains(response, "customerData")  # in the config
    assert_contains(response, "isCompany")  # in the config
Example #25
0
def test_custom_view_toolbar_buttons(rf, admin_user):
    factories.get_default_shop()
    request = apply_request_middleware(rf.get("/"), user=admin_user)
    list_view_func = ContactGroupPriceDisplayListView.as_view()
    edit_view_func = ContactGroupPriceDisplayEditView.as_view()

    with override_provides("contact_group_price_list_toolbar_provider", [
        "shuup.testing.modules.mocker.toolbar:ContactGroupPriceDisplayButtonProvider"
    ]):
        list_response = list_view_func(request)
        list_response.render()
        list_content = list_response.content.decode("utf-8")
        assert "btn-contact-group-hello" in list_content

        edit_response = edit_view_func(request)
        edit_response.render()
        edit_response = edit_response.content.decode("utf-8")
        assert "btn-contact-group-hello" not in edit_response

    # use global provider - all views should have that button
    with override_provides("admin_toolbar_button_provider", [
        "shuup.testing.modules.mocker.toolbar:ContactGroupPriceDisplayButtonProvider"
    ]):
        list_response = list_view_func(request)
        list_response.render()
        list_content = list_response.content.decode("utf-8")
        assert "btn-contact-group-hello" in list_content

        edit_response = edit_view_func(request)
        edit_response.render()
        edit_response = edit_response.content.decode("utf-8")
        assert "btn-contact-group-hello" in edit_response
def test_sorts_and_filter_in_category_edit(rf, admin_user):
    get_default_shop()
    cache.clear()
    activate("en")
    with override_provides("front_extend_product_list_form", DEFAULT_FORM_MODIFIERS):
        category = get_default_category()
        view = CategoryEditView.as_view()
        assert get_configuration(category=category) == settings.SHUUP_FRONT_DEFAULT_SORT_CONFIGURATION
        data = {
            "base-name__en": category.name,
            "base-status": category.status.value,
            "base-visibility": category.visibility.value,
            "base-ordering": category.ordering,
            "product_list_facets-sort_products_by_name": True,
            "product_list_facets-sort_products_by_name_ordering": 6,
            "product_list_facets-sort_products_by_price": False,
            "product_list_facets-sort_products_by_price_ordering": 32,
            "product_list_facets-filter_products_by_manufacturer": True,
            "product_list_facets-filter_products_by_manufacturer_ordering": 1
        }
        request = apply_request_middleware(rf.post("/", data=data), user=admin_user)
        response = view(request, pk=category.pk)
        if hasattr(response, "render"):
            response.render()
        assert response.status_code in [200, 302]
        expected_configurations = {
            "sort_products_by_name": True,
            "sort_products_by_name_ordering": 6,
            "sort_products_by_price": False,
            "sort_products_by_price_ordering": 32,
            "filter_products_by_manufacturer": True,
            "filter_products_by_manufacturer_ordering": 1
        }
        assert get_configuration(category=category) == expected_configurations
Example #27
0
def test_user_will_be_redirected_to_user_account_page_after_activation(client):
    """
    1. Register user
    2. Dig out the urls from the email
    3. Get the url and see where it redirects
    4. See that user's email is in content (in input)
    5. Check that the url poins to user_account-page
    """
    if "shuup.front.apps.registration" not in settings.INSTALLED_APPS:
        pytest.skip("shuup.front.apps.registration required in installed apps")
    if "shuup.front.apps.customer_information" not in settings.INSTALLED_APPS:
        pytest.skip("shuup.front.apps.customer_information required in installed apps")

    get_default_shop()
    response = client.post(reverse("shuup:registration_register"), data={
        "username": username,
        "email": email,
        "password1": "password",
        "password2": "password",
    }, follow=True)
    body = mail.outbox[-1].body
    urls = re.findall('http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', body)
    response = client.get(urls[0], follow=True)
    assert email.encode('utf-8') in response.content, 'email should be found from the page.'
    assert reverse('shuup:customer_edit') == response.request['PATH_INFO'], 'user should be on the account-page.'
Example #28
0
def test_register_bad_data():
    get_default_shop()
    client = _get_client()
    # no username or email
    register_data = {
        "password": "******"
    }
    response = client.post("/api/shuup/front/user/",
                           content_type="application/json",
                           data=json.dumps(register_data))
    assert response.status_code == status.HTTP_400_BAD_REQUEST
    assert "username and/or email is required" in response.content.decode("utf-8")

    # invalid email
    register_data = {
        "email": "invalidemail",
        "password": "******"
    }
    response = client.post("/api/shuup/front/user/",
                           content_type="application/json",
                           data=json.dumps(register_data))
    assert response.status_code == status.HTTP_400_BAD_REQUEST
    assert "Enter a valid email address" in response.content.decode("utf-8")

    # no password
    register_data = {
        "email": "*****@*****.**",
    }
    response = client.post("/api/shuup/front/user/",
                           content_type="application/json",
                           data=json.dumps(register_data))
    assert response.status_code == status.HTTP_400_BAD_REQUEST
    assert "password" in json.loads(response.content.decode("utf-8"))
Example #29
0
def test_new_service_providers_type_select(rf, admin_user, sp_model, type_param):
    """
    Test `ServiceProvideEditView`` with different types of
    ``ServiceProvider`` subclasses. Make sure that view is rendered
    and creating new object works.

    To make things little bit more simple let's use only english as
    an language.
    """
    with override_settings(LANGUAGES=[("en", "en")]):
        get_default_shop()
        view = ServiceProviderEditView.as_view()
        url = "/"
        if type_param:
            url += "?type=%s" % type_param
        soup = get_bs_object_for_view(rf.get(url), view, admin_user)
        selected_type = soup.find("select", attrs={"id": "id_type"}).find("option", selected=True)["value"]
        if type_param:
            assert type_param == selected_type
        else:
            assert selected_type in [
                "shuup.customcarrier", "shuup.custompaymentprocessor",
                "shuup_testing.pseudopaymentprocessor"
            ]

        if sp_model:
            name = "Some provider"
            data = {
                "type": type_param,
                "name__en": name,
                "enabled": True
            }
            provider_count = sp_model.objects.count()
            get_bs_object_for_view(rf.post(url, data=data), view, admin_user)
            assert sp_model.objects.count() == provider_count + 1
Example #30
0
def test_refund_entire_order():
    shop = get_default_shop()
    supplier = get_simple_supplier()
    product = create_product(
        "test-sku",
        shop=get_default_shop(),
        default_price=10,
        stock_behavior=StockBehavior.STOCKED
    )
    supplier.adjust_stock(product.id, 5)
    assert supplier.get_stock_statuses([product.id])[product.id].logical_count == 5

    order = create_order_with_product(product, supplier, 2, 200, Decimal("0.1"), shop=shop)
    order.cache_prices()
    original_total_price = order.taxful_total_price
    assert supplier.get_stock_statuses([product.id])[product.id].logical_count == 3

    # Create a full refund with `restock_products` set to False
    order.create_full_refund(restock_products=False)

    # Confirm the refund was created with correct amount
    assert order.taxless_total_price.amount.value == 0
    assert order.taxful_total_price.amount.value == 0
    refund_line = order.lines.order_by("ordering").last()
    assert refund_line.type == OrderLineType.REFUND
    assert refund_line.taxful_price == -original_total_price

    # Make sure stock status didn't change
    assert supplier.get_stock_statuses([product.id])[product.id].logical_count == 3
Example #31
0
def test_nearby_products(admin_user):
    get_default_shop()
    supplier = create_simple_supplier("supplier1")

    # create Apple and its products
    shop1 = Shop.objects.create(status=ShopStatus.ENABLED)
    shop1.contact_address = MutableAddress.objects.create(
        name="Apple Infinite Loop",
        street="1 Infinite Loop",
        country="US",
        city="Cupertino",
        latitude=37.331667,
        longitude=-122.030146)
    shop1.save()
    product1 = create_product("macbook", shop1, supplier=supplier)
    product2 = create_product("imac", shop1, supplier=supplier)

    # create Google and its products
    shop2 = Shop.objects.create(status=ShopStatus.ENABLED)
    shop2.contact_address = MutableAddress.objects.create(
        name="Google",
        street="1600 Amphitheatre Pkwy",
        country="US",
        city="Mountain View",
        latitude=37.422000,
        longitude=-122.084024)
    shop2.save()
    product3 = create_product("nexus 1", shop2, supplier=supplier)
    product4 = create_product("nexux 7", shop2, supplier=supplier)

    my_position_to_apple = 2.982
    my_position_to_google = 10.57
    # YMCA
    my_position = (37.328330, -122.063612)

    # fetch products and their closest shops
    params = {
        "lat": my_position[0],
        "lng": my_position[1],
        "ordering": "-distance"
    }
    request = get_request("/api/shuup/front/products/",
                          admin_user,
                          data=params)
    response = FrontProductViewSet.as_view({"get": "list"})(request)
    response.render()
    assert response.status_code == status.HTTP_200_OK
    products = json.loads(response.content.decode("utf-8"))
    assert len(products) == 4

    assert products[0]["id"] == product3.id
    assert products[0]["name"] == product3.name
    assert products[0][
        "closest_shop_distance"] - my_position_to_google < 0.05  # 5 meters of error margin

    assert products[1]["id"] == product4.id
    assert products[1]["name"] == product4.name
    assert products[1][
        "closest_shop_distance"] - my_position_to_google < 0.05  # 5 meters of error margin

    assert products[2]["id"] == product1.id
    assert products[2]["name"] == product1.name
    assert products[2][
        "closest_shop_distance"] - my_position_to_apple < 0.05  # 5 meters of error margin

    assert products[3]["id"] == product2.id
    assert products[3]["name"] == product2.name
    assert products[3][
        "closest_shop_distance"] - my_position_to_apple < 0.05  # 5 meters of error margin
Example #32
0
def test_product_summary():
    if "shuup.simple_supplier" not in settings.INSTALLED_APPS:
        pytest.skip("Need shuup.simple_supplier in INSTALLED_APPS")
    from shuup_tests.simple_supplier.utils import get_simple_supplier
    shop = get_default_shop()
    supplier = get_simple_supplier()
    product = create_product("test-sku",
                             shop=get_default_shop(),
                             default_price=10,
                             stock_behavior=StockBehavior.STOCKED)
    supplier.adjust_stock(product.id, 5)

    # Order with 2 unshipped, non-refunded items and a shipping cost
    order = create_order_with_product(product, supplier, 2, 200, shop=shop)
    order.cache_prices()
    product_line = order.lines.first()
    shipping_line = order.lines.create(type=OrderLineType.SHIPPING,
                                       base_unit_price_value=5,
                                       quantity=1)

    # Make sure no invalid entries and check product quantities
    product_summary = order.get_product_summary()
    assert all(product_summary.keys())
    summary = product_summary[product.id]
    assert_defaultdict_values(summary,
                              ordered=2,
                              shipped=0,
                              refunded=0,
                              unshipped=2)

    # Create a shipment for the other item, make sure status changes
    assert order.shipping_status == ShippingStatus.NOT_SHIPPED
    assert order.can_create_shipment()
    order.create_shipment(supplier=supplier, product_quantities={product: 1})
    assert order.shipping_status == ShippingStatus.PARTIALLY_SHIPPED

    order.create_refund([{
        "line": shipping_line,
        "quantity": 1,
        "amount": Money(5, order.currency),
        "restock": False
    }])

    product_summary = order.get_product_summary()
    assert all(product_summary.keys())
    summary = product_summary[product.id]
    assert_defaultdict_values(summary,
                              ordered=2,
                              shipped=1,
                              refunded=0,
                              unshipped=1)

    # Create a refund for 2 items, we should get no negative values
    order.create_refund([{
        "line": product_line,
        "quantity": 2,
        "amount": Money(200, order.currency),
        "restock": False
    }])

    product_summary = order.get_product_summary()
    assert all(product_summary.keys())
    summary = product_summary[product.id]
    assert_defaultdict_values(summary,
                              ordered=2,
                              shipped=1,
                              refunded=2,
                              unshipped=0)
Example #33
0
def test_refunds_for_discounted_order_lines():
    shop = get_default_shop()
    supplier = get_default_supplier()
    product = create_product("test-sku",
                             shop=get_default_shop(),
                             default_price=10,
                             stock_behavior=StockBehavior.STOCKED)

    order = create_order_with_product(product, supplier, 2, 200, shop=shop)
    discount_line = OrderLine(order_id=order.id,
                              type=OrderLineType.DISCOUNT,
                              quantity=1,
                              discount_amount_value=Decimal("0.54321"))
    discount_line.save()
    order.lines.add(discount_line)

    product_line = order.lines.filter(type=OrderLineType.PRODUCT).first()
    product_line.discount_amount = TaxfulPrice(100, order.currency)
    product_line.save()
    taxful_price_with_discount = product_line.taxful_price
    order.cache_prices()
    order.save()

    assert product_line.base_price == TaxfulPrice(400, order.currency)
    assert taxful_price_with_discount == TaxfulPrice(300, order.currency)

    # try to refund only the product line - should fail since this would result in a negative total
    with pytest.raises(RefundExceedsAmountException):
        order.create_refund([{
            "line": product_line,
            "quantity": 2,
            "amount": taxful_price_with_discount.amount
        }])

    # try to refund the product line with a negative amount
    with pytest.raises(InvalidRefundAmountException):
        order.create_refund([{
            "line": product_line,
            "quantity": 1,
            "amount": -taxful_price_with_discount.amount
        }])
    # try to refund the discount line with a positive amount
    with pytest.raises(InvalidRefundAmountException):
        order.create_refund([{
            "line": discount_line,
            "quantity": 1,
            "amount": -discount_line.taxful_price.amount
        }])

    order.create_refund([{
        "line": discount_line,
        "quantity": 1,
        "amount": discount_line.taxful_price.amount
    }, {
        "line": product_line,
        "quantity": 2,
        "amount": taxful_price_with_discount.amount
    }])
    assert product_line.max_refundable_amount.value == 0
    assert discount_line.max_refundable_amount.value == 0
    assert order.taxful_total_price.value == 0

    order = create_order_with_product(product, supplier, 2, 200, shop=shop)
    discount_line = OrderLine(order_id=order.id,
                              type=OrderLineType.DISCOUNT,
                              quantity=1,
                              discount_amount_value=Decimal("0.54321"))
    discount_line.save()
    order.lines.add(discount_line)
    product_line = order.lines.filter(type=OrderLineType.PRODUCT).first()
    product_line.discount_amount = TaxfulPrice(100, order.currency)
    product_line.save()
    order.cache_prices()
    order.save()

    order.create_full_refund(restock_products=False)
    assert order.taxful_total_price.value == 0
Example #34
0
def test_refunds():
    shop = get_default_shop()
    supplier = get_default_supplier()
    product = create_product(
        "test-sku",
        shop=get_default_shop(),
        default_price=10,
    )
    tax_rate = Decimal("0.1")
    taxless_base_unit_price = shop.create_price(200)
    order = create_order_with_product(product,
                                      supplier,
                                      3,
                                      taxless_base_unit_price,
                                      tax_rate,
                                      shop=shop)
    order.payment_status = PaymentStatus.DEFERRED
    order.cache_prices()
    order.save()

    assert order.get_total_refunded_amount().value == 0
    assert order.get_total_unrefunded_amount(
    ).value == order.taxful_total_price.value
    assert not order.can_edit()

    assert len(order.lines.all()) == 1

    product_line = order.lines.first()
    assert product_line.ordering == 0
    assert order.can_create_refund()
    assert not order.has_refunds()

    order.create_refund([{
        "line": product_line,
        "quantity": 1,
        "amount": (product_line.taxful_price.amount / 3)
    }])
    assert len(order.lines.all()) == 2
    assert order.lines.last().ordering == 1
    assert order.has_refunds()

    # Confirm the value of the refund
    assert order.lines.last().taxful_price == -product_line.base_unit_price
    assert order.lines.last().tax_amount == -(
        product_line.taxless_base_unit_price * tax_rate).amount

    # Create a refund with a parent line and amount
    order.create_refund([{
        "line": product_line,
        "quantity": 1,
        "amount": product_line.taxful_price.amount / 3
    }])
    assert len(order.lines.all()) == 3
    assert order.lines.last().ordering == 2

    assert order.lines.last(
    ).taxful_price.amount == -taxless_base_unit_price.amount * (1 + tax_rate)
    assert order.lines.last(
    ).tax_amount == -taxless_base_unit_price.amount * tax_rate

    assert order.taxless_total_price.amount == taxless_base_unit_price.amount
    assert order.taxful_total_price.amount == taxless_base_unit_price.amount * (
        1 + tax_rate)
    assert order.can_create_refund()

    # Try to refunding remaining amount without a parent line
    with pytest.raises(AssertionError):
        order.create_refund([{"amount": taxless_base_unit_price}])

    # refund remaining amount
    order.create_refund([{
        "line": product_line,
        "quantity": 1,
        "amount": product_line.taxful_price.amount / 3
    }])
    assert len(order.lines.all()) == 4
    assert order.lines.last().ordering == 3
    assert order.lines.last(
    ).taxful_price.amount == -taxless_base_unit_price.amount * (1 + tax_rate)

    assert not order.taxful_total_price.amount
    assert not order.can_create_refund()

    with pytest.raises(RefundExceedsAmountException):
        order.create_refund([{
            "line": product_line,
            "quantity": 1,
            "amount": taxless_base_unit_price.amount
        }])
def test_order_refunds_with_multiple_suppliers():
    shop = get_default_shop()
    supplier1 = Supplier.objects.create(identifier="1", name="supplier1")
    supplier1.shops.add(shop)
    supplier2 = Supplier.objects.create(identifier="2")
    supplier2.shops.add(shop)
    supplier3 = Supplier.objects.create(identifier="3", name="s")
    supplier3.shops.add(shop)

    product1 = create_product("sku1", shop=shop, default_price=10)
    shop_product1 = product1.get_shop_instance(shop=shop)
    shop_product1.suppliers.set([supplier1, supplier2, supplier3])

    product2 = create_product("sku2", shop=shop, default_price=10)
    shop_product2 = product1.get_shop_instance(shop=shop)
    shop_product2.suppliers.set([supplier1, supplier2])

    product3 = create_product("sku3",
                              shop=shop,
                              default_price=10,
                              shipping_mode=ShippingMode.NOT_SHIPPED)
    shop_product3 = product1.get_shop_instance(shop=shop)
    shop_product3.suppliers.set([supplier3])

    product_quantities = {
        supplier1: {
            product1: 5,
            product2: 6
        },
        supplier2: {
            product1: 3,
            product2: 13
        },
        supplier3: {
            product1: 1,
            product3: 50
        }
    }

    def get_quantity(supplier, product):
        return product_quantities[supplier.pk][product.pk]

    order = create_empty_order(shop=shop)
    order.full_clean()
    order.save()

    for supplier, product_data in six.iteritems(product_quantities):
        for product, quantity in six.iteritems(product_data):
            add_product_to_order(order, supplier, product, quantity, 5)

    # Lines without quantity shouldn't affect refunds
    other_line = OrderLine(order=order,
                           type=OrderLineType.OTHER,
                           text="This random line for textual information",
                           quantity=0)
    other_line.save()
    order.lines.add(other_line)

    order.cache_prices()
    order.create_payment(order.taxful_total_price)
    assert order.is_paid()

    # All supplier should be able to refund the order
    assert order.can_create_refund()
    assert order.can_create_refund(supplier1)
    assert order.can_create_refund(supplier2)
    assert order.can_create_refund(supplier3)

    assert order.get_total_unrefunded_amount(supplier1).value == Decimal(
        "55")  # 11 * 5
    assert order.get_total_unrefunded_quantity(supplier1) == Decimal(
        "11")  # 5 x product1 and 6 x product2

    with pytest.raises(RefundExceedsAmountException):
        order.create_refund([{
            "line": "amount",
            "quantity": 1,
            "amount": order.shop.create_price(60)
        }],
                            supplier=supplier1)

    # Supplier 1 refunds the order
    order.create_refund(_get_refund_data(order, supplier1))
    assert order.get_total_refunded_amount(supplier1).value == Decimal(
        "55")  # 11 * 5
    assert order.get_total_unrefunded_amount(supplier1).value == Decimal("0")

    assert not order.can_create_refund(supplier1)
    assert order.can_create_refund()
    assert order.can_create_refund(supplier2)
    assert order.can_create_refund(supplier3)

    assert order.get_total_unrefunded_amount(supplier2).value == Decimal(
        "80")  # 16 * 5
    assert order.get_total_unrefunded_quantity(supplier2) == Decimal(
        "16")  # 3 x product1 and 13 x product2

    with pytest.raises(RefundExceedsAmountException):
        order.create_refund([{
            "line": "amount",
            "quantity": 1,
            "amount": order.shop.create_price(81)
        }],
                            supplier=supplier2)

    # Supplier 2 refunds the order
    order.create_refund(_get_refund_data(order, supplier2))
    assert order.get_total_refunded_amount(supplier2).value == Decimal(
        "80")  # 11 * 5
    assert order.get_total_unrefunded_amount(supplier2).value == Decimal("0")
    assert not order.can_create_refund(supplier1)
    assert not order.can_create_refund(supplier2)
    assert order.can_create_refund()
    assert order.can_create_refund(supplier3)

    assert order.get_total_unrefunded_amount(supplier3).value == Decimal(
        "255")  # 51 * 5
    assert order.get_total_unrefunded_quantity(supplier3) == Decimal(
        "51")  # 3 x product1 and 13 x product2

    with override_settings(SHUUP_ALLOW_ARBITRARY_REFUNDS=False):
        with pytest.raises(RefundArbitraryRefundsNotAllowedException):
            order.create_refund([{
                "line": "amount",
                "quantity": 1,
                "amount": order.shop.create_price(200)
            }],
                                supplier=supplier3)

    order.create_refund([{
        "line": "amount",
        "quantity": 1,
        "amount": order.shop.create_price(200)
    }],
                        supplier=supplier3)
    assert OrderLine.objects.filter(order=order,
                                    supplier=supplier3,
                                    type=OrderLineType.REFUND).exists()

    # Supplier 3 refunds the order
    order.create_refund(_get_refund_data(order, supplier3))
    assert order.get_total_refunded_amount(supplier3).value == Decimal(
        "255")  # 11 * 5
    assert order.get_total_unrefunded_amount(supplier3).value == Decimal("0")
    assert not order.can_create_refund(supplier1)
    assert not order.can_create_refund(supplier2)
    assert not order.can_create_refund(supplier3)
    assert not order.can_create_refund()
def test_order_arbitrary_refunds_with_multiple_suppliers():
    shop = get_default_shop()
    supplier1 = Supplier.objects.create(identifier="1", name="supplier1")
    supplier1.shops.add(shop)
    supplier2 = Supplier.objects.create(identifier="2")
    supplier2.shops.add(shop)
    supplier3 = Supplier.objects.create(identifier="3", name="s")
    supplier3.shops.add(shop)

    product1 = create_product("sku1", shop=shop, default_price=10)
    shop_product1 = product1.get_shop_instance(shop=shop)
    shop_product1.suppliers.set([supplier1, supplier2, supplier3])

    product2 = create_product("sku2", shop=shop, default_price=10)
    shop_product2 = product1.get_shop_instance(shop=shop)
    shop_product2.suppliers.set([supplier1, supplier2])

    product3 = create_product("sku3",
                              shop=shop,
                              default_price=10,
                              shipping_mode=ShippingMode.NOT_SHIPPED)
    shop_product3 = product1.get_shop_instance(shop=shop)
    shop_product3.suppliers.set([supplier3])

    product_quantities = {
        supplier1: {
            product1: 5,
            product2: 6
        },
        supplier2: {
            product1: 3,
            product2: 13
        },
        supplier3: {
            product1: 1,
            product3: 50
        }
    }

    def get_quantity(supplier, product):
        return product_quantities[supplier.pk][product.pk]

    order = create_empty_order(shop=shop)
    order.full_clean()
    order.save()

    for supplier, product_data in six.iteritems(product_quantities):
        for product, quantity in six.iteritems(product_data):
            add_product_to_order(order, supplier, product, quantity, 5)

    # Lines without quantity shouldn't affect refunds
    other_line = OrderLine(order=order,
                           type=OrderLineType.OTHER,
                           text="This random line for textual information",
                           quantity=0)
    other_line.save()
    order.lines.add(other_line)

    order.cache_prices()
    order.create_payment(order.taxful_total_price)
    assert order.is_paid()

    # All supplier should be able to refund the order
    assert order.can_create_refund()
    assert order.can_create_refund(supplier1)
    assert order.can_create_refund(supplier2)
    assert order.can_create_refund(supplier3)

    # Step by step refund lines for supplier1
    assert order.can_create_refund()
    assert order.get_total_unrefunded_amount(supplier1).value == Decimal(
        "55")  # 11 * 5
    assert order.get_total_unrefunded_amount().value == Decimal(
        "390")  # 55 + 80 + 255
    proudct1_line_for_supplier1 = order.lines.filter(supplier=supplier1,
                                                     product=product1).first()
    supplier1_refund_data = [{
        "line": proudct1_line_for_supplier1,
        "quantity": proudct1_line_for_supplier1.quantity,
        "amount":
        order.shop.create_price(20).amount,  # Line total is 5 * 5 = 25
        "restock_products": True
    }]
    order.create_refund(supplier1_refund_data)
    assert order.get_total_unrefunded_amount(supplier1).value == Decimal("35")

    order.create_refund([{
        "line": "amount",
        "quantity": 1,
        "amount": order.shop.create_price(30).amount
    }],
                        supplier=supplier1)

    assert order.get_total_unrefunded_amount(supplier1).value == Decimal("5")

    order.create_refund([{
        "line": "amount",
        "quantity": 1,
        "amount": order.shop.create_price(5).amount
    }],
                        supplier=supplier1)

    assert order.get_total_unrefunded_amount(supplier1).value == Decimal("0")
    assert order.can_create_refund(
        supplier1)  # Some quantity still left to refund

    proudct2_line_for_supplier1 = order.lines.filter(supplier=supplier1,
                                                     product=product2).first()
    supplier1_restock_refund_data = [{
        "line": proudct2_line_for_supplier1,
        "quantity": proudct2_line_for_supplier1.quantity,
        "amount":
        order.shop.create_price(0).amount,  # Line total is 5 * 5 = 25
        "restock_products": True
    }]
    order.create_refund(supplier1_restock_refund_data)
    assert not order.can_create_refund(supplier1)

    # Step by step refund lines for supplier2
    assert order.can_create_refund()
    assert order.get_total_unrefunded_amount(supplier2).value == Decimal(
        "80")  # 16 * 5
    assert order.get_total_unrefunded_amount().value == Decimal(
        "335")  # 80 + 255
    proudct2_line_for_supplier2 = order.lines.filter(supplier=supplier2,
                                                     product=product2).first()
    supplier2_refund_data = [{
        "line": proudct2_line_for_supplier2,
        "quantity": 10,
        "amount":
        order.shop.create_price(50).amount,  # Line total is 13 * 5 = 65
        "restock_products": True
    }]
    order.create_refund(supplier2_refund_data)
    assert order.get_total_unrefunded_amount(supplier2).value == Decimal("30")

    order.create_refund([{
        "line": "amount",
        "quantity": 1,
        "amount": order.shop.create_price(5).amount
    }],
                        supplier=supplier2)

    assert order.get_total_unrefunded_amount(supplier2).value == Decimal("25")

    order.create_refund([{
        "line": "amount",
        "quantity": 1,
        "amount": order.shop.create_price(25).amount
    }],
                        supplier=supplier2)

    assert order.get_total_unrefunded_amount(supplier2).value == Decimal("0")
    assert order.can_create_refund(
        supplier2)  # Some quantity still left to refund

    supplier2_restock_refund_data = [{
        "line": proudct2_line_for_supplier2,
        "quantity": 3,
        "amount":
        order.shop.create_price(0).amount,  # Line total is 5 * 5 = 25
        "restock_products": True
    }]
    order.create_refund(supplier2_restock_refund_data)

    proudct1_line_for_supplier2 = order.lines.filter(supplier=supplier2,
                                                     product=product1).first()
    supplier1_restock_refund_data = [{
        "line": proudct1_line_for_supplier2,
        "quantity": proudct1_line_for_supplier2.quantity,
        "amount":
        order.shop.create_price(0).amount,  # Line total is 5 * 5 = 25
        "restock_products": True
    }]
    order.create_refund(supplier1_restock_refund_data)
    assert not order.can_create_refund(supplier2)

    # Step by step refund lines for supplier3
    assert order.can_create_refund()
    assert order.get_total_unrefunded_amount(supplier3).value == Decimal(
        "255")  # 51 * 5
    assert order.get_total_unrefunded_amount().value == Decimal("255")  # 255

    order.create_refund([{
        "line": "amount",
        "quantity": 1,
        "amount": order.shop.create_price(55).amount
    }],
                        supplier=supplier3)

    assert order.get_total_unrefunded_amount(supplier3).value == Decimal("200")

    proudct3_line_for_supplier3 = order.lines.filter(supplier=supplier3,
                                                     product=product3).first()
    supplier3_refund_data = [{
        "line": proudct3_line_for_supplier3,
        "quantity": 50,
        "amount":
        order.shop.create_price(200).amount,  # Line total is 13 * 5 = 65
        "restock_products": True
    }]
    order.create_refund(supplier3_refund_data)
    assert order.get_total_unrefunded_amount(supplier2).value == Decimal("0")
    assert order.get_total_unrefunded_amount().value == Decimal("0")
    assert order.can_create_refund(
        supplier3)  # Some quantity still left to refund

    proudct1_line_for_supplier3 = order.lines.filter(supplier=supplier3,
                                                     product=product1).first()
    supplier3_restock_refund_data = [{
        "line": proudct1_line_for_supplier3,
        "quantity": proudct1_line_for_supplier3.quantity,
        "amount":
        order.shop.create_price(0).amount,  # Line total is 5 * 5 = 25
        "restock_products": True
    }]
    order.create_refund(supplier3_restock_refund_data)
    assert not order.can_create_refund(supplier3)
    assert not order.can_create_refund()
Example #37
0
def test_product_price_info(admin_user, prices_include_tax, product_price,
                            discount, tax_rate, taxful_price, taxless_price):
    shop = get_default_shop()
    shop.prices_include_tax = prices_include_tax
    shop.save()

    customer = create_random_person()
    group = customer.get_default_group()
    customer.user = admin_user
    customer.groups.add(group)
    customer.save()

    tax = factories.get_default_tax()
    tax.rate = Decimal(tax_rate)
    tax.save()

    product = create_product("Just-A-Product",
                             shop,
                             default_price=product_price)
    CgpDiscount.objects.create(product=product,
                               shop=shop,
                               group=group,
                               discount_amount_value=discount)

    client = _get_client(admin_user)
    response = client.get("/api/shuup/front/shop_products/", format="json")
    data = response.data[0]

    discounted_price = (product_price - discount)
    price = (discounted_price if prices_include_tax else discounted_price *
             (1 + tax_rate))
    base_price = (product_price if prices_include_tax else
                  (product_price * (1 + tax_rate)))
    discount_value = (discount if prices_include_tax else
                      (discount * (1 + tax_rate)))

    price_info = data["price_info"]

    def money_round(value):
        return Money(value, shop.currency).as_rounded(2)

    assert money_round(data["price"]) == money_round(price)
    assert money_round(price_info['base_price']) == money_round(base_price)
    assert money_round(price_info['taxful_price']) == money_round(taxful_price)

    if prices_include_tax:
        assert 'taxless_price' not in price_info
        assert 'tax_amount' not in price_info
    else:
        assert money_round(
            price_info['taxless_price']) == money_round(taxless_price)
        assert money_round(price_info['tax_amount']) == money_round(
            (product_price - discount) * tax_rate)

    assert money_round(price_info['price']) == money_round(price)
    assert money_round(
        price_info['discount_amount']) == money_round(discount_value)
    assert money_round(
        price_info['discount_rate']) == money_round(discount_value /
                                                    price if discount else 0)
    assert price_info['is_discounted'] is (True if discount else False)
Example #38
0
def test_product_selection_plugin(rf):
    shop = factories.get_default_shop()
    category1 = factories.CategoryFactory(status=CategoryStatus.VISIBLE)
    category2 = factories.CategoryFactory(status=CategoryStatus.VISIBLE)

    p1 = factories.create_product("p1", shop, factories.get_default_supplier(),
                                  "10")
    p2 = factories.create_product("p2", shop, factories.get_default_supplier(),
                                  "20")
    p3 = factories.create_product("p3", shop, factories.get_default_supplier(),
                                  "30")
    p4 = factories.create_product("p4", shop, factories.get_default_supplier(),
                                  "40")
    p5 = factories.create_product("p5", shop, factories.get_default_supplier(),
                                  "50")

    sp1 = p1.get_shop_instance(shop)
    sp2 = p2.get_shop_instance(shop)
    sp3 = p3.get_shop_instance(shop)
    sp4 = p4.get_shop_instance(shop)

    sp1.categories.add(category1, category2)
    sp2.categories.add(category1)
    sp3.categories.add(category2)
    sp4.categories.add(category2)

    # this discount should show products: p1, p2 and p5
    discount1 = Discount.objects.create(name="discount1",
                                        active=True,
                                        start_datetime=now() -
                                        timedelta(days=10),
                                        end_datetime=now() + timedelta(days=1),
                                        product=p5,
                                        category=category1)
    discount1.shops = [shop]

    # this discount should show products: p1, p3 and p4
    discount2 = Discount.objects.create(name="discount2",
                                        active=True,
                                        start_datetime=now() -
                                        timedelta(days=10),
                                        end_datetime=now() + timedelta(days=1),
                                        category=category2)
    discount2.shops = [shop]

    # this discount shouldn't be available for this shop
    discount3 = Discount.objects.create(name="discount3",
                                        active=True,
                                        start_datetime=now() -
                                        timedelta(days=10),
                                        end_datetime=now() + timedelta(days=1),
                                        category=category2)

    context = get_context(rf)

    # test only discount1
    plugin = DiscountedProductsPlugin({
        "discounts": [discount1.pk],
        "count": 10
    })
    context_products = plugin.get_context_data(context)["products"]
    assert p1 in context_products
    assert p2 in context_products
    assert p3 not in context_products
    assert p4 not in context_products
    assert p5 in context_products

    for status in range(2):
        if status == 1:
            discount2.active = False
            discount2.save()

        # test only discount2
        plugin = DiscountedProductsPlugin({
            "discounts": [discount2.pk],
            "count": 10
        })
        context_products = plugin.get_context_data(context)["products"]

        if status == 1:
            assert context_products == []
        else:
            assert p1 in context_products
            assert p2 not in context_products
            assert p3 in context_products
            assert p4 in context_products
            assert p5 not in context_products

    # test discount3
    plugin = DiscountedProductsPlugin({
        "discounts": [discount3.pk],
        "count": 10
    })
    assert plugin.get_context_data(context)["products"] == []

    discount2.active = True
    discount2.save()

    # test both discount1 and discount2
    plugin = DiscountedProductsPlugin({
        "discounts": [discount1.pk, discount2.pk],
        "count": 10
    })
    context_products = plugin.get_context_data(context)["products"]
    assert p1 in context_products
    assert p2 in context_products
    assert p3 in context_products
    assert p4 in context_products
    assert p5 in context_products

    # test the plugin form
    with override_current_theme_class(None):
        theme = get_current_theme(shop)
        cell = LayoutCell(theme,
                          DiscountedProductsPlugin.identifier,
                          sizes={"md": 8})
        lcfg = LayoutCellFormGroup(layout_cell=cell,
                                   theme=theme,
                                   request=apply_request_middleware(
                                       rf.get("/")))
        # not valid, products are required
        assert not lcfg.is_valid()

        lcfg = LayoutCellFormGroup(data={
            "general-cell_width":
            "8",
            "general-cell_align":
            "pull-right",
            "plugin-discounts": [discount1.pk, discount2.pk],
            "plugin-count":
            6
        },
                                   layout_cell=cell,
                                   theme=theme,
                                   request=apply_request_middleware(
                                       rf.get("/")))
        assert lcfg.is_valid()
        lcfg.save()
        assert cell.config["discounts"] == [discount1.pk, discount2.pk]
Example #39
0
def test_variatins_import(rf):
    filename = "product_sample_import_with_variations.xlsx"
    if "shuup.simple_supplier" not in settings.INSTALLED_APPS:
        pytest.skip("Need shuup.simple_supplier in INSTALLED_APPS")

    from shuup_tests.simple_supplier.utils import get_simple_supplier

    activate("en")
    shop = get_default_shop()
    product_type = get_default_product_type()
    sales_unit = get_default_sales_unit()
    tax_class = get_default_tax_class()

    # Create media
    _create_random_media_file(shop, "shirt1.jpeg")
    _create_random_media_file(shop, "shirt2.jpeg")
    _create_random_media_file(shop, "shirt3.jpeg")

    path = os.path.join(os.path.dirname(__file__), "data", "product", filename)
    transformed_data = transform_file(filename.split(".")[1], path)

    importer = ProductImporter(
        transformed_data,
        ProductImporter.get_importer_context(rf.get("/"),
                                             shop=shop,
                                             language="en"))
    importer.process_data()

    assert len(importer.unmatched_fields) == 0

    importer.do_import(ImportMode.CREATE_UPDATE)
    products = importer.new_objects

    assert len(products) == 42  # 2 parents 20 variations each

    supplier = Supplier.objects.first()
    assert supplier and supplier.stock_managed and supplier.module_identifier == "simple_supplier"
    assert ShopProduct.objects.filter(suppliers=supplier).count() == 42

    parent1 = Product.objects.filter(sku=1).first()
    assert parent1.mode == ProductMode.VARIABLE_VARIATION_PARENT
    assert parent1.variation_children.all().count() == 20

    # Check product names
    child1 = Product.objects.filter(sku=10).first()
    assert child1.name == "T-Shirt - Pink - S"

    child2 = Product.objects.filter(sku=11).first()
    assert child2.name == "T-Shirt - Pink - XS"

    # Check stock counts
    assert supplier.get_stock_status(child1.pk).logical_count == Decimal(5)
    assert supplier.get_stock_status(child2.pk).logical_count == Decimal(10)

    parent2 = Product.objects.filter(sku=22).first()
    assert parent1.mode == ProductMode.VARIABLE_VARIATION_PARENT
    assert parent1.variation_children.all().count() == 20

    # Check product names
    child1 = Product.objects.filter(sku=38).first()
    assert child1.name == "Custom T-Shirt - Black - XL"

    child2 = Product.objects.filter(sku=39).first()
    assert child2.name == "Custom T-Shirt - Black - L"

    # Check stock counts
    assert supplier.get_stock_status(child1.pk).logical_count == Decimal(5)
    assert supplier.get_stock_status(child2.pk).logical_count == Decimal(10)

    path = os.path.join(os.path.dirname(__file__), "data", "product",
                        "product_sample_import_with_variations_update.xlsx")
    transformed_data = transform_file(filename.split(".")[1], path)

    importer = ProductImporter(
        transformed_data,
        ProductImporter.get_importer_context(rf.get("/"),
                                             shop=shop,
                                             language="en"))
    importer.process_data()

    assert len(importer.unmatched_fields) == 0

    importer.do_import(ImportMode.CREATE_UPDATE)
    products = importer.new_objects

    assert len(products) == 0

    updated_products = importer.updated_objects

    assert len(updated_products) == 4

    # Check product names
    child1 = Product.objects.filter(sku=10).first()
    assert child1.name == "T-Shirt - Pink - S"

    child2 = Product.objects.filter(sku=11).first()
    assert child2.name == "Test"

    # Check stock counts
    assert supplier.get_stock_status(child1.pk).logical_count == Decimal(5)
    assert supplier.get_stock_status(child2.pk).logical_count == Decimal(
        20)  # Did not add 20 but made the logical to 20

    parent2 = Product.objects.filter(sku=22).first()
    assert parent1.mode == ProductMode.VARIABLE_VARIATION_PARENT
    assert parent1.variation_children.all().count() == 20

    # Check product names
    child1 = Product.objects.filter(sku=38).first()
    assert child1.name == "Custom T-Shirt - Black - XL"

    child2 = Product.objects.filter(sku=39).first()
    assert child2.name == "Custom T-Shirt - Black - L"

    # Check stock counts
    assert supplier.get_stock_status(child1.pk).logical_count == Decimal(15)
    assert supplier.get_stock_status(child2.pk).logical_count == Decimal(10)

    # Test file with missing variations
    path = os.path.join(
        os.path.dirname(__file__), "data", "product",
        "product_sample_import_with_variations_missing_variables.xlsx")
    transformed_data = transform_file(filename.split(".")[1], path)

    importer = ProductImporter(
        transformed_data,
        ProductImporter.get_importer_context(rf.get("/"),
                                             shop=shop,
                                             language="en"))
    importer.process_data()

    assert len(importer.unmatched_fields) == 0

    importer.do_import(ImportMode.CREATE_UPDATE)
    products = importer.new_objects

    assert len(products) == 0

    updated_products = importer.updated_objects

    assert len(updated_products) == 4

    for log_message in importer.log_messages:
        assert "Parent SKU set for the row, but no variation" in log_message[
            "messages"][0]

    # check that both variation products still looks correct
    parent1 = Product.objects.filter(sku=1).first()
    assert parent1.mode == ProductMode.VARIABLE_VARIATION_PARENT
    assert parent1.variation_children.all().count() == 20

    parent2 = Product.objects.filter(sku=22).first()
    assert parent1.mode == ProductMode.VARIABLE_VARIATION_PARENT
    assert parent1.variation_children.all().count() == 20
Example #40
0
def test_get_best_selling_products(admin_user):
    shop1 = get_default_shop()
    shop2 = get_shop(True)
    person1 = create_random_person()
    person1.user = admin_user
    person1.save()

    supplier = create_simple_supplier("supplier1")
    client = _get_client(admin_user)

    # list best selling products
    response = client.get("/api/shuup/front/shop_products/best_selling/", {
        "shop": shop2.pk,
        "limit": 20
    })
    assert response.status_code == status.HTTP_200_OK
    products = json.loads(response.content.decode("utf-8"))
    assert len(products["results"]) == 0

    # THIS IS IMPORTANT!
    cache.clear()

    products = [
        create_product("Standard-%d" % x, supplier=supplier, shop=shop2)
        for x in range(10)
    ]

    # create 1 product with 4 variations
    parent_product = create_product("ParentProduct1",
                                    supplier=supplier,
                                    shop=shop2)
    children = [
        create_product("SimpleVarChild-%d" % x, supplier=supplier, shop=shop2)
        for x in range(4)
    ]
    for child in children:
        child.link_to_parent(parent_product)

    best_selling = defaultdict(int)

    # create orders with standard products
    for p_index in range(len(products)):
        order = create_empty_order(shop=shop2)
        order.save()
        qty = (len(products) - p_index)
        add_product_to_order(order, supplier, products[p_index], qty,
                             Decimal(1.0))
        order.create_shipment_of_all_products()
        order.status = OrderStatus.objects.get_default_complete()
        order.save(update_fields=("status", ))

        best_selling[products[p_index].id] = qty

    # create orders with variation products - the parent product is counted instead of its children
    for p_index in range(2):
        variation = random.choice(children)
        qty = 5
        order = create_empty_order(shop=shop2)
        order.save()
        add_product_to_order(order, supplier, variation, qty, Decimal(1.0))
        order.create_shipment_of_all_products()
        order.status = OrderStatus.objects.get_default_complete()
        order.save(update_fields=("status", ))
        best_selling[parent_product.id] = best_selling[parent_product.id] + qty

    # get the top 100 best selling products
    response = client.get("/api/shuup/front/shop_products/best_selling/", {
        "shop": shop2.pk,
        "limit": 100
    })
    assert response.status_code == status.HTTP_200_OK
    products = json.loads(response.content.decode("utf-8"))
    assert len(products["results"]) == len(
        best_selling)  # as we added less then 100, this must be true
    assert products["next"] is None

    # check the if all IDS are part of best selling
    for ix in range(len(products)):
        assert products["results"][ix]["product_id"] in best_selling.keys()

    # get the top 5 best selling products (we should get paginated results)
    response = client.get("/api/shuup/front/shop_products/best_selling/", {
        "shop": shop2.pk,
        "limit": 5
    })
    assert response.status_code == status.HTTP_200_OK
    products = json.loads(response.content.decode("utf-8"))
    assert len(products["results"]) == 5
    assert products["count"] == len(best_selling)
    assert products["next"] is not None
    sorted_best_selling_ids = [
        prod[0]
        for prod in sorted(best_selling.items(), key=lambda prod: -prod[1])
    ][:5]

    # check the if all the 5 best sellers are part of best selling
    for ix in range(len(products)):
        assert products["results"][ix]["product_id"] in sorted_best_selling_ids
Example #41
0
def test_attr_api(admin_user):
    activate("en")
    get_default_shop()
    client = APIClient()
    client.force_authenticate(user=admin_user)

    attr_data = {
        "identifier": "attr1",
        "translations": {
            "en": {"name": "Kilo"}
        },
        "searchable": False,
        "type": AttributeType.BOOLEAN.value,
        "visibility_mode": AttributeVisibility.HIDDEN.value
    }
    response = client.post("/api/shuup/attribute/",
                           content_type="application/json",
                           data=json.dumps(attr_data))
    assert response.status_code == status.HTTP_201_CREATED
    attr = Attribute.objects.first()
    assert attr.name == attr_data["translations"]["en"]["name"]
    assert attr.searchable == attr_data["searchable"]
    assert attr.type.value == attr_data["type"]
    assert attr.identifier == attr_data["identifier"]
    assert attr.visibility_mode.value == attr_data["visibility_mode"]

    attr_data["translations"]["en"]["name"] = "Pound"
    attr_data["identifier"] = "attr2"
    attr_data["searchable"] = True
    attr_data["type"] = AttributeType.DECIMAL.value
    attr_data["visibility_mode"] = AttributeVisibility.SEARCHABLE_FIELD.value

    response = client.put("/api/shuup/attribute/%d/" % attr.id,
                          content_type="application/json",
                          data=json.dumps(attr_data))
    assert response.status_code == status.HTTP_200_OK
    attr = Attribute.objects.first()
    assert attr.name == attr_data["translations"]["en"]["name"]
    assert attr.searchable == attr_data["searchable"]
    assert attr.type.value == attr_data["type"]
    assert attr.identifier == attr_data["identifier"]
    assert attr.visibility_mode.value == attr_data["visibility_mode"]

    response = client.get("/api/shuup/attribute/%d/" % attr.id)
    assert response.status_code == status.HTTP_200_OK
    data = json.loads(response.content.decode("utf-8"))
    assert attr.name == data["translations"]["en"]["name"]
    assert attr.searchable == attr_data["searchable"]
    assert attr.type.value == attr_data["type"]
    assert attr.identifier == attr_data["identifier"]
    assert attr.visibility_mode.value == attr_data["visibility_mode"]

    response = client.get("/api/shuup/attribute/")
    assert response.status_code == status.HTTP_200_OK
    data = json.loads(response.content.decode("utf-8"))
    assert attr.searchable == data[0]["searchable"]
    assert attr.type.value == data[0]["type"]
    assert attr.identifier == data[0]["identifier"]
    assert attr.visibility_mode.value == data[0]["visibility_mode"]

    response = client.delete("/api/shuup/attribute/%d/" % attr.id)
    assert response.status_code == status.HTTP_204_NO_CONTENT
    assert Attribute.objects.count() == 0
def test_order_flow_with_multiple_suppliers():
    cache.clear()

    shop = factories.get_default_shop()
    factories.create_default_order_statuses()
    factories.get_default_payment_method()
    factories.get_default_shipping_method()

    n_orders_pre = Order.objects.count()
    product = factories.create_product("sku", shop=shop, default_price=30)
    shop_product = product.get_shop_instance(shop)

    # Activate show supplier info for front
    assert ThemeSettings.objects.count() == 1
    theme_settings = ThemeSettings.objects.first()
    theme_settings.update_settings({"show_supplier_info": True})

    supplier_data = [
        ("Johnny Inc", 30),
        ("Mike Inc", 10),
        ("Simon Inc", 20),
    ]
    for name, product_price in supplier_data:
        supplier = Supplier.objects.create(name=name)
        shop_product.suppliers.add(supplier)
        SupplierPrice.objects.create(supplier=supplier, shop=shop, product=product, amount_value=product_price)

    strategy = "shuup.testing.supplier_pricing.supplier_strategy:CheapestSupplierPriceSupplierStrategy"
    with override_settings(SHUUP_PRICING_MODULE="supplier_pricing", SHUUP_SHOP_PRODUCT_SUPPLIERS_STRATEGY=strategy):

        # Ok so cheapest price should be default supplier
        expected_supplier = shop_product.get_supplier()
        assert expected_supplier.name == "Mike Inc"
        with override_current_theme_class(ClassicGrayTheme, shop):  # Ensure settings is refreshed from DB
            c = SmartClient()

            # Case 1: use default supplier
            _add_to_basket(c, product.pk, 2)
            order = _complete_checkout(c, n_orders_pre + 1)
            assert order
            product_lines = order.lines.products()
            assert len(product_lines) == 1
            assert product_lines[0].supplier.pk == expected_supplier.pk
            assert product_lines[0].base_unit_price_value == decimal.Decimal("10")

            # Case 2: force supplier to Johnny Inc
            johnny_supplier = Supplier.objects.filter(name="Johnny Inc").first()
            _add_to_basket(c, product.pk, 3, johnny_supplier)
            order = _complete_checkout(c, n_orders_pre + 2)
            assert order
            product_lines = order.lines.products()
            assert len(product_lines) == 1
            assert product_lines[0].supplier.pk == johnny_supplier.pk
            assert product_lines[0].base_unit_price_value == decimal.Decimal("30")

            # Case 3: order 2 pcs from Mike and 3 pcs from Simon Inc
            mike_supplier = Supplier.objects.filter(name="Mike Inc").first()
            _add_to_basket(c, product.pk, 2, mike_supplier)

            simon_supplier = Supplier.objects.filter(name="Simon Inc").first()
            _add_to_basket(c, product.pk, 3, simon_supplier)

            order = _complete_checkout(c, n_orders_pre + 3)
            assert order
            assert order.taxful_total_price_value == decimal.Decimal("80")  # Math: 2x10e + 3x20e

            product_lines = order.lines.products()
            assert len(product_lines) == 2

            mikes_line = [line for line in product_lines if line.supplier.pk == mike_supplier.pk][0]
            assert mikes_line
            assert mikes_line.quantity == 2
            assert mikes_line.base_unit_price_value == decimal.Decimal("10")

            simon_line = [line for line in product_lines if line.supplier.pk == simon_supplier.pk][0]
            assert simon_line
            assert simon_line.quantity == 3
            assert simon_line.base_unit_price_value == decimal.Decimal("20")
Example #43
0
def test_adjust_stock(admin_user):
    get_default_shop()
    sp = get_default_shop_product()
    client = _get_client(admin_user)
    supplier1 = get_simple_supplier()
    supplier2 = get_default_supplier()
    # invalid type
    response = client.post("/api/shuup/supplier/%s/adjust_stock/" %
                           supplier1.pk,
                           format="json",
                           data={
                               "product": sp.product.pk,
                               "delta": 100,
                               "type": 200
                           })
    assert response.status_code == status.HTTP_400_BAD_REQUEST
    data = json.loads(response.content.decode("utf-8"))
    assert "type" in data

    # invalid supplier
    response = client.post("/api/shuup/supplier/%s/adjust_stock/" % 100,
                           format="json",
                           data={
                               "product": sp.product.pk,
                               "delta": 100,
                               "type": StockAdjustmentType.INVENTORY.value
                           })
    assert response.status_code == status.HTTP_404_NOT_FOUND

    # invalid product
    response = client.post("/api/shuup/supplier/%s/adjust_stock/" %
                           supplier1.pk,
                           format="json",
                           data={
                               "product": 100,
                               "delta": 100,
                               "type": StockAdjustmentType.INVENTORY.value
                           })
    assert response.status_code == status.HTTP_400_BAD_REQUEST
    data = json.loads(response.content.decode("utf-8"))
    assert "product" in data

    # invalid delta
    response = client.post("/api/shuup/supplier/%s/adjust_stock/" %
                           supplier1.pk,
                           format="json",
                           data={
                               "product": sp.product.pk,
                               "delta": "not-a-number",
                               "type": StockAdjustmentType.INVENTORY.value
                           })
    assert response.status_code == status.HTTP_400_BAD_REQUEST
    data = json.loads(response.content.decode("utf-8"))
    assert "delta" in data

    # adjust stock not implemented
    response = client.post("/api/shuup/supplier/%s/adjust_stock/" %
                           supplier2.pk,
                           format="json",
                           data={
                               "product": sp.product.pk,
                               "delta": 100,
                               "type": StockAdjustmentType.INVENTORY.value
                           })
    assert response.status_code == status.HTTP_400_BAD_REQUEST

    # add 100 to inventory
    response = client.post("/api/shuup/supplier/%s/adjust_stock/" %
                           supplier1.pk,
                           format="json",
                           data={
                               "product": sp.product.pk,
                               "delta": 100,
                               "type": StockAdjustmentType.RESTOCK.value
                           })
    assert response.status_code == status.HTTP_200_OK
    stock = supplier1.get_stock_status(sp.product.pk)
    assert stock.logical_count == 100
    assert stock.physical_count == 100

    # remove the stocks adjustments and calculate the stock again
    StockAdjustment.objects.all().delete()

    # update the stock,
    response = client.post("/api/shuup/supplier/%s/update_stocks/" %
                           supplier1.pk,
                           format="json",
                           data={"products": [sp.product.pk]})
    assert response.status_code == status.HTTP_200_OK
    # everything should be zero
    stock = supplier1.get_stock_status(sp.product.pk)
    assert stock.logical_count == 0
    assert stock.physical_count == 0
Example #44
0
def test_sample_import_all_match(filename):
    activate("en")
    shop = get_default_shop()
    tax_class = get_default_tax_class()
    product_type = get_default_product_type()
    sales_unit = get_default_sales_unit()

    path = os.path.join(os.path.dirname(__file__), "data", "product", filename)
    if filename == bom_file:
        import codecs
        bytes = min(32, os.path.getsize(path))
        raw = open(path, 'rb').read(bytes)
        assert raw.startswith(codecs.BOM_UTF8)

    transformed_data = transform_file(filename.split(".")[1], path)
    importer = ProductImporter(transformed_data, shop, "en")
    importer.process_data()

    if filename == images_file:
        assert len(importer.unmatched_fields) == 2
        _create_random_media_file(shop, "image1.jpeg")
        _create_random_media_file(shop, "products/images/image2.jpeg")
        _create_random_media_file(shop, "products/images/image3.jpeg")
        _create_random_media_file(shop, "image4.jpeg")
        _create_random_media_file(shop, "products2/images/image5.jpeg")
        _create_random_media_file(shop, "product1.jpeg")
        _create_random_media_file(shop, "products/images/product2.jpeg")
        _create_random_media_file(shop, "products/images2/product2.jpeg")
    else:
        assert len(importer.unmatched_fields) == 0

    importer.do_import(ImportMode.CREATE_UPDATE)
    products = importer.new_objects

    if filename == images_file:
        assert len(products) == 3
    else:
        assert len(products) == 2

    for product in products:
        shop_product = product.get_shop_instance(shop)
        assert shop_product.pk
        assert shop_product.default_price_value == 150
        assert shop_product.default_price == shop.create_price(150)
        assert product.type == product_type  # product type comes from importer defaults
        assert product.sales_unit == sales_unit

        if product.pk == 1:
            assert product.tax_class.pk == 2  # new was created
            assert product.name == "Product English"
            assert product.description == "Description English"

            if filename == images_file:
                assert product.media.count() == 3
        elif product.pk == 2:
            assert product.tax_class.pk == tax_class.pk  # old was found as should
            assert product.name == "Product 2 English"
            assert product.description == "Description English 2"

            if filename == images_file:
                assert product.media.count() == 2
        elif product.pk == 3 and filename == images_file:
            assert product.media.count() == 3

        assert shop_product.primary_category.pk == 1
        assert [c.pk for c in shop_product.categories.all()] == [1, 2]
def test_product_variable_variation_link(admin_user):
    activate("en")
    client = _get_client(admin_user)
    shop = get_default_shop()

    product1 = create_product("product1", shop=shop)
    product2 = create_product("product2", shop=shop)
    product3 = create_product("product3", shop=shop)

    assert product1.mode == ProductMode.NORMAL
    var = ProductVariationVariable.objects.create(name="Complex Variable",
                                                  product=product1)
    ProductVariationVariableValue.objects.create(variable=var, value="Value A")
    ProductVariationVariableValue.objects.create(variable=var, value="Value B")
    product1.verify_mode()
    product1.save()

    # create the combinations
    combinations = list(get_all_available_combinations(product1))
    hash1 = combinations[0]["hash"]  # this must be Complex Variable: Value A
    hash2 = combinations[1]["hash"]  # this must be Complex Variable: Value B

    # link product1 <- product2
    linkage_data = {
        "product": product2.pk,
        "hash": hash1,
        "status": ProductVariationLinkStatus.VISIBLE.value
    }
    response = client.post("/api/shuup/product/%d/variable_variation/" %
                           product1.pk,
                           content_type="application/json",
                           data=json.dumps(linkage_data))
    assert response.status_code == status.HTTP_201_CREATED

    # link product1 <- product3
    linkage_data = {
        "product": product3.pk,
        "hash": hash2,
        "status": ProductVariationLinkStatus.VISIBLE.value
    }
    response = client.post("/api/shuup/product/%d/variable_variation/" %
                           product1.pk,
                           content_type="application/json",
                           data=json.dumps(linkage_data))
    assert response.status_code == status.HTTP_201_CREATED

    result1 = ProductVariationResult.objects.get(combination_hash=hash1)
    assert result1.product.pk == product1.pk
    assert result1.status.value == ProductVariationLinkStatus.VISIBLE.value
    assert result1.result.pk == product2.pk

    result2 = ProductVariationResult.objects.get(combination_hash=hash2)
    assert result2.product.pk == product1.pk
    assert result2.status.value == ProductVariationLinkStatus.VISIBLE.value
    assert result2.result.pk == product3.pk

    combinations = list(get_all_available_combinations(product1))
    assert combinations[0]["result_product_pk"] == product2.pk
    assert combinations[1]["result_product_pk"] == product3.pk

    # update
    linkage_data = {
        "product": product2.pk,
        "hash": hash2,
        "status": ProductVariationLinkStatus.INVISIBLE.value
    }
    response = client.put("/api/shuup/product/%d/variable_variation/" %
                          product1.pk,
                          content_type="application/json",
                          data=json.dumps(linkage_data))
    assert response.status_code == status.HTTP_200_OK
    result2 = ProductVariationResult.objects.get(combination_hash=hash2)
    assert result2.product.pk == product1.pk
    assert result2.status.value == ProductVariationLinkStatus.INVISIBLE.value
    assert result2.result.pk == product2.pk

    # delete
    response = client.delete("/api/shuup/product/%d/variable_variation/" %
                             product1.pk,
                             content_type="application/json",
                             data=json.dumps({"hash": hash2}))
    assert response.status_code == status.HTTP_204_NO_CONTENT
    assert ProductVariationResult.objects.count() == 1

    response = client.delete("/api/shuup/product/%d/variable_variation/" %
                             product1.pk,
                             content_type="application/json",
                             data=json.dumps({"hash": hash1}))
    assert response.status_code == status.HTTP_204_NO_CONTENT
    assert ProductVariationResult.objects.count() == 0

    # delete something not existent
    response = client.delete("/api/shuup/product/%d/variable_variation/" %
                             product1.pk,
                             content_type="application/json",
                             data=json.dumps({"hash": hash1}))
    assert response.status_code == status.HTTP_404_NOT_FOUND

    # update not existent
    response = client.put("/api/shuup/product/%d/variable_variation/" %
                          product1.pk,
                          content_type="application/json",
                          data=json.dumps(linkage_data))
    assert response.status_code == status.HTTP_404_NOT_FOUND
Example #46
0
def test_page_form(rf, admin_user):
    shop = get_default_shop()
    request = rf.get("/")
    request.user = admin_user
    request.shop = shop
    view = PageEditView(request=request)
    form_class = view.get_form_class()
    form = form_class(**view.get_form_kwargs())
    assert not form.is_bound

    data = get_form_data(form)
    data.update({
        "available_from": "",
        "available_to": "",
        "content__en": "",
        "content__fi": "suomi",
        "content__ja": "",
        "identifier": "",
        "title__en": "",
        "title__fi": "",
        "title__ja": "",
        "url__en": "",
        "url__fi": "suomi",
        "url__ja": "",
    })

    form = form_class(**dict(view.get_form_kwargs(), data=data))
    form.full_clean()
    assert "title__fi" in form.errors  # We get an error because all of a given language's fields must be filled if any are
    data["title__fi"] = "suomi"
    form = form_class(**dict(view.get_form_kwargs(), data=data))
    form.full_clean()
    assert not form.errors
    page = form.save()
    assert set(page.get_available_languages()) == {
        "fi"
    }  # The page should be only in Finnish
    # Let's edit that page
    original_url = "errrnglish"
    data.update({
        "title__en": "englaish",
        "url__en": original_url,
        "content__en": "ennnn ennnn ennnnnnn-nn-n-n"
    })
    form = form_class(**dict(view.get_form_kwargs(), data=data, instance=page))
    form.full_clean()
    assert not form.errors
    page = form.save()
    assert set(page.get_available_languages()) == {"fi", "en"}  # English GET

    # Try to make page a child of itself
    data.update({"parent": page.pk})
    form = form_class(**dict(view.get_form_kwargs(), data=data, instance=page))
    form.full_clean()
    assert form.errors
    del data["parent"]

    # add dummy page with simple url, page is in english
    dummy = create_page(url="test", shop=get_default_shop())

    # edit page again and try to set duplicate url
    data.update({
        "title__en": "englaish",
        "url__en": "test",
        "content__en": "ennnn ennnn ennnnnnn-nn-n-n"
    })
    form = form_class(**dict(view.get_form_kwargs(), data=data, instance=page))
    form.full_clean()

    assert len(form.errors) == 1
    assert "url__en" in form.errors
    assert form.errors["url__en"].as_data()[0].code == "invalid_url"

    # it should be possible to change back to the original url
    data.update({
        "title__en": "englaish",
        "url__en": original_url,
        "content__en": "ennnn ennnn ennnnnnn-nn-n-n"
    })
    form = form_class(**dict(view.get_form_kwargs(), data=data, instance=page))
    form.full_clean()
    assert not form.errors
    page = form.save()

    # add finnish urls, it should not be possible to enter original url
    data.update({
        "title__fi": "englaish",
        "url__fi": original_url,
        "content__fi": "ennnn ennnn ennnnnnn-nn-n-n"
    })

    assert data["url__fi"] == data[
        "url__en"]  # both urls are same, should raise two errors

    form = form_class(**dict(view.get_form_kwargs(), data=data, instance=page))
    form.full_clean()
    assert len(form.errors) == 1
    assert "url__fi" in form.errors
    error_data = form.errors["url__fi"].as_data()
    assert error_data[0].code == "invalid_url"
    assert error_data[1].code == "invalid_unique_url"
def test_edit_button_no_permission(browser, admin_user, live_server, settings):
    shop = get_default_shop()

    manager_group = Group.objects.create(name="Managers")

    manager = create_random_user("en", is_staff=True)
    manager.username = "******"
    manager.set_password("password")
    manager.save()
    manager.groups.add(manager_group)
    shop.staff_members.add(manager)

    # add permissions for Product admin module
    manager_permissions = set(["dashboard", "Products", "shop_product.new"])
    set_permissions_for_group(manager_group, manager_permissions)

    get_default_product_type()
    get_default_sales_unit()
    get_default_tax_class()
    initialize_admin_browser_test(browser,
                                  live_server,
                                  settings,
                                  username=manager.username)

    url = reverse("shuup_admin:shop_product.new")
    browser.visit("%s%s" % (live_server, url))

    sku = "testsku"
    name = "Some product name"
    price_value = 10
    short_description = "short but gold"

    browser.fill("base-sku", sku)
    browser.fill("base-name__en", name)
    browser.fill("base-short_description__en", short_description)
    browser.fill("shop%s-default_price_value" % shop.pk, price_value)

    wait_until_appeared(
        browser,
        "#id_shop%d-primary_category ~ .quick-add-btn a.btn" % shop.id)
    click_element(
        browser,
        "#id_shop%d-primary_category ~ .quick-add-btn a.btn" % shop.id)
    wait_until_appeared(browser, "#create-object-iframe")

    # no permission to add category
    with browser.get_iframe('create-object-iframe') as iframe:
        error = "Can't view this page. You do not have the required permissions: category.new"
        wait_until_condition(iframe,
                             condition=lambda x: x.is_text_present(error))

    # close iframe
    click_element(browser, "#create-object-overlay a.close-btn")

    # add permission to add category
    manager_permissions.add("category.new")
    manager_permissions.add("category.edit")
    set_permissions_for_group(manager_group, manager_permissions)

    # click to add category again
    click_element(
        browser,
        "#id_shop%d-primary_category ~ .quick-add-btn a.btn" % shop.id)
    wait_until_appeared(browser, "#create-object-iframe")

    # add the category
    with browser.get_iframe('create-object-iframe') as iframe:
        assert Category.objects.count() == 0
        wait_until_appeared(iframe, "input[name='base-name__en']")
        iframe.fill("base-name__en", "Test Category")
        time.sleep(
            3
        )  # Let's just wait here to the iFrame to open fully (for Chrome and headless)
        wait_until_appeared(iframe, "button[form='category_form']")
        click_element(browser, "button[form='category_form']")
        wait_until_condition(browser,
                             condition=lambda x: Category.objects.count() == 1,
                             timeout=20)

    assert Category.objects.first().name == "Test Category"

    # remove the edit category permissions
    # add permission to add category
    manager_permissions.remove("category.edit")
    set_permissions_for_group(manager_group, manager_permissions)

    # click to edit the button
    click_element(
        browser,
        "#id_shop%d-primary_category ~ .edit-object-btn a.btn" % shop.id)

    # no permission to edit category
    with browser.get_iframe('create-object-iframe') as iframe:
        error = "Can't view this page. You do not have the required permission(s): category.edit"
        wait_until_condition(iframe,
                             condition=lambda x: x.is_text_present(error))

    # close iframe
    click_element(browser, "#create-object-overlay a.close-btn")

    manager_permissions.add("category.edit")
    set_permissions_for_group(manager_group, manager_permissions)

    click_element(
        browser,
        "#id_shop%d-primary_category ~ .edit-object-btn a.btn" % shop.id)
    wait_until_appeared(browser, "#create-object-iframe")

    new_cat_name = "Changed Name"
    with browser.get_iframe('create-object-iframe') as iframe:
        wait_until_appeared(iframe, "input[name='base-name__en']")
        iframe.fill("base-name__en", new_cat_name)
        time.sleep(
            3
        )  # Let's just wait here to the iFrame to open fully (for Chrome and headless)
        wait_until_appeared(iframe, "button[form='category_form']")
        click_element(browser, "button[form='category_form']")

    wait_until_condition(
        browser,
        condition=lambda x: Category.objects.first().name == new_cat_name,
        timeout=20)
def test_product_variable_variation_api(admin_user):
    client = _get_client(admin_user)
    shop = get_default_shop()
    product = create_product("product", shop=shop)
    assert product.mode == ProductMode.NORMAL

    # create
    variation_data = {
        "product": product.pk,
        "translations": {
            "en": {
                "name": "Complex Variation Variable"
            }
        }
    }
    response = client.post("/api/shuup/product_variation_variable/",
                           content_type="application/json",
                           data=json.dumps(variation_data))
    assert response.status_code == status.HTTP_201_CREATED
    product.refresh_from_db()
    assert product.mode == ProductMode.VARIABLE_VARIATION_PARENT
    assert ProductVariationVariable.objects.filter(
        product=product).count() == 1
    assert ProductVariationVariableValue.objects.filter(
        variable__product=product).count() == 0

    # fetch
    response = client.get("/api/shuup/product_variation_variable/%d/" %
                          ProductVariationVariable.objects.first().pk)
    assert response.status_code == status.HTTP_200_OK
    variable_data = json.loads(response.content.decode("utf-8"))
    assert variable_data["translations"]["en"]["name"] == variation_data[
        "translations"]["en"]["name"]

    # update
    variation_data = {
        "product": product.pk,
        "translations": {
            "en": {
                "name": "Complex Variation Variable MODIFIED"
            }
        }
    }
    response = client.put("/api/shuup/product_variation_variable/%d/" %
                          ProductVariationVariable.objects.first().pk,
                          content_type="application/json",
                          data=json.dumps(variation_data))
    assert response.status_code == status.HTTP_200_OK
    product.refresh_from_db()
    assert product.mode == ProductMode.VARIABLE_VARIATION_PARENT
    assert ProductVariationVariable.objects.filter(
        product=product).count() == 1
    assert ProductVariationVariableValue.objects.filter(
        variable__product=product).count() == 0

    # list
    response = client.get("/api/shuup/product_variation_variable/")
    assert response.status_code == status.HTTP_200_OK
    variable_data = json.loads(response.content.decode("utf-8"))
    assert variable_data[0]["translations"]["en"]["name"] == variation_data[
        "translations"]["en"]["name"]

    # delete
    response = client.delete("/api/shuup/product_variation_variable/%d/" %
                             ProductVariationVariable.objects.first().pk)
    assert response.status_code == status.HTTP_204_NO_CONTENT
    assert ProductVariationVariable.objects.count() == 0
    product.refresh_from_db()
    assert product.mode == ProductMode.NORMAL
def test_protected_contact_groups(rf, admin_user, contact):
    get_default_shop()
    request = apply_request_middleware(rf.get("/"), user=admin_user)
    check_for_delete(request, contact().get_default_group(), False)
def test_quick_add(browser, admin_user, live_server, settings):
    shop = get_default_shop()
    get_default_product_type()
    get_default_sales_unit()
    get_default_tax_class()
    initialize_admin_browser_test(browser, live_server, settings)

    url = reverse("shuup_admin:shop_product.new")
    browser.visit("%s%s" % (live_server, url))
    sku = "testsku"
    name = "Some product name"
    price_value = 10
    short_description = "short but gold"

    browser.fill("base-sku", sku)
    browser.fill("base-name__en", name)
    browser.fill("base-short_description__en", short_description)
    browser.fill("shop%s-default_price_value" % shop.pk, price_value)

    wait_until_appeared(
        browser,
        "#id_shop%d-primary_category ~ .quick-add-btn a.btn" % shop.id)
    click_element(
        browser,
        "#id_shop%d-primary_category ~ .quick-add-btn a.btn" % shop.id)
    wait_until_appeared(browser, "#create-object-iframe")

    with browser.get_iframe('create-object-iframe') as iframe:
        assert Category.objects.count() == 0
        wait_until_appeared(iframe, "input[name='base-name__en']")
        iframe.fill("base-name__en", "Test Category")
        time.sleep(
            3
        )  # Let's just wait here to the iFrame to open fully (for Chrome and headless)
        wait_until_appeared(iframe, "button[form='category_form']")
        click_element(browser, "button[form='category_form']")
        wait_until_condition(browser,
                             condition=lambda x: Category.objects.count() == 1,
                             timeout=20)

    assert Category.objects.first().name == "Test Category"

    # click to edit the button
    click_element(
        browser,
        "#id_shop%d-primary_category ~ .edit-object-btn a.btn" % shop.id)

    with browser.get_iframe('create-object-iframe') as iframe:
        wait_until_appeared(iframe, "input[name='base-name__en']")
        new_cat_name = "Changed Name"
        iframe.fill("base-name__en", new_cat_name)
        time.sleep(
            3
        )  # Let's just wait here to the iFrame to open fully (for Chrome and headless)
        wait_until_appeared(iframe, "button[form='category_form']")
        click_element(iframe, "button[form='category_form']")

    wait_until_condition(
        browser,
        condition=lambda x: Category.objects.first().name == new_cat_name,
        timeout=20)

    click_element(browser, "button[form='product_form']")
    wait_until_appeared(browser, "div[class='message success']")
Example #51
0
def test_notify_on_company_created(regular_user, allow_company_registration):
    if "shuup.front.apps.customer_information" not in settings.INSTALLED_APPS:
        pytest.skip(
            "shuup.front.apps.customer_information required in installed apps")
    if "shuup.notify" not in settings.INSTALLED_APPS:
        pytest.skip("shuup.notify required in installed apps")

    configuration.set(None, "allow_company_registration",
                      allow_company_registration)
    step = Step(cond_op=StepConditionOperator.NONE,
                actions=[
                    AddNotification({
                        "message": {
                            "constant": "It Works. {{ customer_email }}"
                        },
                        "message_identifier": {
                            "constant": "company_created"
                        },
                    })
                ],
                next=StepNext.STOP)
    script = Script(event_identifier=CompanyAccountCreated.identifier,
                    name="Test Script",
                    enabled=True,
                    shop=get_default_shop())
    script.set_steps([step])
    script.save()

    assert not Notification.objects.filter(
        identifier="company_created").exists()

    assert get_person_contact(regular_user)
    assert not get_company_contact(regular_user)

    client = SmartClient()
    client.login(username=REGULAR_USER_USERNAME,
                 password=REGULAR_USER_PASSWORD)
    company_edit_url = reverse("shuup:company_edit")

    if allow_company_registration:
        client.soup(company_edit_url)

        data = _default_company_data()
        data.update(_default_address_data("billing"))
        data.update(_default_address_data("shipping"))

        response, soup = client.response_and_soup(company_edit_url, data,
                                                  "post")

        assert response.status_code == 302
        assert get_company_contact(regular_user)
        assert Notification.objects.filter(
            identifier="company_created").count() == 1
        notification = Notification.objects.filter(
            identifier="company_created").first()
        assert notification
        assert data["contact-email"] in notification.message

        # New save should not add new notifications
        response, soup = client.response_and_soup(company_edit_url, data,
                                                  "post")
        assert response.status_code == 302
        assert Notification.objects.filter(
            identifier="company_created").count() == 1
        script.delete()
    else:
        response = client.get(company_edit_url)
        assert response.status_code == 404
        assert Notification.objects.filter(
            identifier="company_created").count() == 0
def test_contact_group_delete_button(rf, admin_user):
    get_default_shop()
    request = apply_request_middleware(rf.get("/"), user=admin_user)
    check_for_delete(request, get_default_customer_group(), True)
Example #53
0
def test_sample_import_no_match(rf, stock_managed):
    filename = "sample_import_nomatch.xlsx"
    if "shuup.simple_supplier" not in settings.INSTALLED_APPS:
        pytest.skip("Need shuup.simple_supplier in INSTALLED_APPS")
    from shuup_tests.simple_supplier.utils import get_simple_supplier

    activate("en")
    shop = get_default_shop()
    tax_class = get_default_tax_class()
    product_type = get_default_product_type()
    supplier = get_simple_supplier(stock_managed)
    sales_unit = get_default_sales_unit()

    Manufacturer.objects.create(name="manufctr")
    path = os.path.join(os.path.dirname(__file__), "data", "product", filename)
    transformed_data = transform_file(filename.split(".")[1], path)

    importer = ProductImporter(
        transformed_data,
        ProductImporter.get_importer_context(rf.get("/"), shop=shop, language="en")
    )
    importer.process_data()
    assert len(importer.unmatched_fields) == 1
    assert "gtiin" in importer.unmatched_fields
    importer.manually_match("gtiin", "shuup.core.models.Product:gtin")
    importer.do_remap()
    assert len(importer.unmatched_fields) == 0
    importer.do_import(ImportMode.CREATE_UPDATE)
    products = importer.new_objects
    assert len(products) == 2

    for product in products:
        assert product.gtin == "1280x720"
        shop_product = product.get_shop_instance(shop)
        assert shop_product.pk
        assert shop_product.default_price_value == 150
        assert shop_product.default_price == shop.create_price(150)
        assert product.type == product_type  # product type comes from importer defaults
        assert product.sales_unit == sales_unit
        if product.pk == 1:
            assert product.tax_class.pk == 2  # new was created
            assert product.name == "Product English"
            assert product.description == "Description English"
        else:
            assert product.tax_class.pk == tax_class.pk  # old was found as should
            assert product.name == "Product 2 English"
            assert product.description == "Description English 2"
        assert shop_product.primary_category.pk == 1
        assert [c.pk for c in shop_product.categories.all()] == [1, 2]

    # stock was not managed since supplier doesn't like that
    for msg in importer.other_log_messages:
        assert "please set `Stock Managed` on" in msg

    supplier.stock_managed = True
    supplier.save()
    importer.do_import("create,update")

    assert len(importer.other_log_messages) == 0
    for sa in StockAdjustment.objects.all():
        assert sa.product.pk
        assert sa.delta == 20
Example #54
0
def test_remap(rf, admin_user):
    lang = "en"
    import_mode = ImportMode.CREATE_UPDATE
    sku = "123"
    name = "test"

    import_path = reverse("shuup_admin:importer.import.new")
    process_path = reverse("shuup_admin:importer.import_process")
    get_default_tax_class()
    get_default_product_type()
    get_default_sales_unit()

    activate("en")
    shop = get_default_shop()
    shop.staff_members.add(admin_user)
    client = SmartClient()
    client.login(username="******", password="******")
    csv_content = str.encode("sku;name;gtiin\n%s;%s;111" % (sku, name))

    # show import view
    data = {
        "importer":
        "product_importer",
        "shop":
        shop.pk,
        "language":
        lang,
        "file":
        SimpleUploadedFile("file.csv", csv_content, content_type="text/csv"),
    }
    response = client.post(import_path, data=data)
    assert response.status_code == 302
    query_string = urlparse(response["location"]).query
    query = parse_qs(query_string)

    data = {  # data with missing `n`
        "importer": "product_importer",
        "shop": shop.pk,
        "language": lang,
        "n": query.get("n"),
    }
    soup = client.soup(process_path, data=data)
    assert "The following fields must be manually tied" in str(soup)

    for row in soup.findAll("tr"):
        first_td = row.find("td")
        if not first_td:
            continue

        tds = row.findAll("td")
        if len(tds) < 2:
            continue
        second_td = tds[1]
        if first_td.text.startswith("Connection"):
            # check that sku was connected
            badges = second_td.findAll("span", {"class": "badge"})
            assert len(badges) == 1
            assert badges[0].text == "sku"
        elif first_td.text.startswith("The following fields"):
            badges = second_td.findAll("span", {"class": "badge"})
            assert len(badges) == 2
            badge_texts = []
            for b in badges:
                badge_texts.append(b.text)
            badge_texts.sort()
            assert badge_texts[0] == "name"
            assert badge_texts[1] == "sku"

    data = {}
    data["import_mode"] = import_mode.value
    data["remap[gtiin]"] = "Product:gtin"  # map gtin
    process_submit_path = "%s?%s" % (process_path, query_string)
    response = client.post(process_submit_path, data=data)
    assert response.status_code == 302
    assert Product.objects.count() == 1
    product = Product.objects.first()
    assert product.gtin == "111"
Example #55
0
def test_user_create(rf, admin_user):
    shop = get_default_shop()
    view_func = UserDetailView.as_view()
    before_count = get_user_model().objects.count()
    response = view_func(
        apply_request_middleware(rf.post(
            "/", {
                "username": "******",
                "email": "*****@*****.**",
                "first_name": "test",
                "last_name": "test",
                "password": "******",
                "send_confirmation": True
            }),
                                 user=admin_user))
    assert response.status_code == 302
    assert get_user_model().objects.count() == before_count + 1
    last_user = get_user_model().objects.last()
    assert last_user not in shop.staff_members.all()
    assert not len(mail.outbox), "mail not sent since user is not staff"

    response = view_func(
        apply_request_middleware(rf.post(
            "/", {
                "username": "******",
                "email": "*****@*****.**",
                "first_name": "test",
                "last_name": "test",
                "password": "******",
                "is_staff": True,
                "send_confirmation": True
            }),
                                 user=admin_user))
    assert response.status_code == 302
    assert get_user_model().objects.count() == before_count + 2
    last_user = get_user_model().objects.last()
    assert last_user in shop.staff_members.all()
    assert len(mail.outbox) == 1, "mail sent"

    user = get_user_model().objects.create(username=printable_gibberish(20),
                                           first_name=printable_gibberish(10),
                                           last_name=printable_gibberish(10),
                                           password="******",
                                           is_staff=True,
                                           is_superuser=False)
    response = view_func(
        apply_request_middleware(rf.get("/"), user=user, skip_session=True))
    assert response.status_code == 200
    response.render()
    assert "Staff status" not in force_text(response.content)
    assert "Superuser status" not in force_text(response.content)

    # remove user staff permission
    view_func = UserChangePermissionsView.as_view()
    response = view_func(apply_request_middleware(rf.post(
        "/", {"is_staff": False}),
                                                  user=admin_user),
                         pk=last_user.id)
    assert response.status_code == 302
    last_user = get_user_model().objects.last()
    assert last_user not in shop.staff_members.all()

    # add again
    view_func = UserChangePermissionsView.as_view()
    response = view_func(apply_request_middleware(rf.post(
        "/", {"is_staff": True}),
                                                  user=admin_user),
                         pk=last_user.id)
    assert response.status_code == 302
    last_user = get_user_model().objects.last()
    assert last_user in shop.staff_members.all()

    # create a superuser
    view_func = UserDetailView.as_view()
    response = view_func(
        apply_request_middleware(rf.post(
            "/", {
                "username": "******",
                "email": "*****@*****.**",
                "first_name": "test",
                "last_name": "test",
                "password": "******",
                "is_staff": True,
                "is_superuser": True,
                "send_confirmation": False
            }),
                                 user=admin_user))
    assert response.status_code == 302
    assert get_user_model().objects.count() == before_count + 4
    last_user = get_user_model().objects.last()
    # superuser shouldn't be added to staff members
    assert last_user not in shop.staff_members.all()

    # change the superuser
    response = view_func(apply_request_middleware(rf.post(
        "/", {
            "username": "******",
            "email": "*****@*****.**",
            "first_name": "test2",
            "last_name": "test",
            "password": "******",
            "is_staff": True,
            "is_superuser": True,
        }),
                                                  user=admin_user),
                         pk=last_user.pk)
    assert response.status_code == 302
    assert get_user_model().objects.count() == before_count + 4
    last_user = get_user_model().objects.last()
    # superuser shouldn't be added to staff members
    assert last_user not in shop.staff_members.all()
Example #56
0
def test_product_edit_view(rf, admin_user, settings):
    shop = get_default_shop()  # obvious prerequisite
    shop.staff_members.add(admin_user)
    parent = create_product("ComplexVarParent",
                            shop=shop,
                            supplier=get_default_supplier())
    sizes = [("%sL" % ("X" * x)) for x in range(4)]
    for size in sizes:
        child = create_product("ComplexVarChild-%s" % size,
                               shop=shop,
                               supplier=get_default_supplier())
        child.link_to_parent(parent, variables={"size": size})
    shop_product = parent.get_shop_instance(shop)
    cat = CategoryFactory()

    assert not shop_product.categories.exists()
    assert not shop_product.primary_category

    view = ProductEditView.as_view()
    request = apply_request_middleware(rf.get("/"), user=admin_user)
    response = view(request, pk=shop_product.pk)
    response.render()

    content = force_text(response.content)
    post = extract_form_fields(BeautifulSoup(content, "lxml"))

    # Needed for Django 1.8 tests to pass
    post.update({
        'shop1-default_price_value': '42',
        'images-TOTAL_FORMS': '0',
        'media-TOTAL_FORMS': '0',
        'base-name__fi': 'test',
        'base-name__it': 'test',
        'base-name__ja': 'test',
        'base-name__pt-br': 'test',
        'base-name__zh-hans': 'test',
    })

    post_data = {'shop1-primary_category': [], 'shop1-categories': []}
    post.update(post_data)
    request = apply_request_middleware(rf.post("/", post), user=admin_user)
    response = view(request, pk=shop_product.pk)

    shop_product.refresh_from_db()
    assert not shop_product.categories.exists()
    assert not shop_product.primary_category

    post_data = {
        'shop1-default_price_value': 12,
        'shop1-primary_category': cat.pk,
        'shop1-categories': []
    }
    post.update(post_data)
    usable_post = {}
    for k, v in six.iteritems(post):
        if not k:
            continue
        if not post[k]:
            continue
        usable_post[k] = v

    request = apply_request_middleware(rf.post("/", usable_post),
                                       user=admin_user)
    response = view(request, pk=shop_product.pk)

    shop_product = ShopProduct.objects.first()
    assert shop_product.primary_category

    if settings.SHUUP_AUTO_SHOP_PRODUCT_CATEGORIES:
        assert shop_product.categories.count() == 1
        assert shop_product.categories.first() == cat
    else:
        assert not shop_product.categories.count()

    assert shop_product.primary_category == cat

    post_data = {'shop1-primary_category': [], 'shop1-categories': []}
    usable_post.update(post_data)

    request = apply_request_middleware(rf.post("/", usable_post),
                                       user=admin_user)
    response = view(request, pk=shop_product.pk)

    # empty again
    shop_product = ShopProduct.objects.first()
    assert not shop_product.categories.exists()
    assert not shop_product.primary_category

    post_data = {'shop1-primary_category': [], 'shop1-categories': [cat.pk]}
    usable_post.update(post_data)

    request = apply_request_middleware(rf.post("/", usable_post),
                                       user=admin_user)
    response = view(request, pk=shop_product.pk)

    shop_product = ShopProduct.objects.first()
    assert shop_product.categories.count() == 1
    assert shop_product.categories.first() == cat
    if settings.SHUUP_AUTO_SHOP_PRODUCT_CATEGORIES:
        assert shop_product.primary_category == cat
    else:
        assert not shop_product.primary_category

    cat2 = CategoryFactory()

    post_data = {
        'shop1-primary_category': [],
        'shop1-categories': [cat.pk, cat2.pk]
    }
    usable_post.update(post_data)

    request = apply_request_middleware(rf.post("/", usable_post),
                                       user=admin_user)
    response = view(request, pk=shop_product.pk)

    shop_product = ShopProduct.objects.first()
    assert shop_product.categories.count() == 2
    assert cat in shop_product.categories.all()
    assert cat2 in shop_product.categories.all()
    if settings.SHUUP_AUTO_SHOP_PRODUCT_CATEGORIES:
        assert shop_product.primary_category == cat
    else:
        assert not shop_product.primary_category
Example #57
0
def test_consolidate_objects(rf, admin_user):
    shop = get_default_shop()

    # just visit to make sure GET is ok
    request = apply_request_middleware(rf.get("/"), user=admin_user)
    response = ConsolidateSampleObjectsView.as_view()(request)
    assert response.status_code == 200

    def populate_samples():
        manager.clear_installed_samples(shop)
        categories = [
            CategoryFactory().pk,
            CategoryFactory().pk,
            CategoryFactory().pk
        ]
        products = [
            ProductFactory().pk,
            ProductFactory().pk,
            ProductFactory().pk,
            ProductFactory().pk
        ]
        carousel = Carousel.objects.create(name="crazy stuff").pk
        manager.save_categories(shop, categories)
        manager.save_products(shop, products)
        manager.save_carousel(shop, carousel)

    def clear_objs():
        Product.objects.all().delete()
        Category.objects.all().delete()
        Carousel.objects.all().delete()

    # consolidate everything
    populate_samples()
    data = {"categories": False, "products": False, "carousel": False}
    request = apply_request_middleware(rf.post("/", data=data),
                                       user=admin_user)
    response = ConsolidateSampleObjectsView.as_view()(request)
    assert response.status_code == 302
    assert response["Location"] == reverse("shuup_admin:dashboard")
    assert Category.objects.count() == 3
    assert Product.objects.count() == 4
    assert Carousel.objects.count() == 1
    assert manager.get_installed_business_segment(shop) is None
    assert manager.get_installed_products(shop) == []
    assert manager.get_installed_categories(shop) == []
    assert manager.get_installed_carousel(shop) is None

    # consolidate nothing
    clear_objs()
    populate_samples()
    data = {"products": True, "categories": True, "carousel": True}
    request = apply_request_middleware(rf.post("/", data=data),
                                       user=admin_user)
    response = ConsolidateSampleObjectsView.as_view()(request)
    assert response.status_code == 302
    assert response["Location"] == reverse("shuup_admin:dashboard")
    assert Category.objects.all_except_deleted().count() == 0
    assert Product.objects.all_except_deleted().count() == 0
    assert Carousel.objects.count() == 0
    assert manager.get_installed_business_segment(shop) is None
    assert manager.get_installed_products(shop) == []
    assert manager.get_installed_categories(shop) == []
    assert manager.get_installed_carousel(shop) is None

    # consolidate some
    clear_objs()
    populate_samples()
    data = {"products": False, "categories": False, "carousel": True}
    request = apply_request_middleware(rf.post("/", data=data),
                                       user=admin_user)
    response = ConsolidateSampleObjectsView.as_view()(request)
    assert response.status_code == 302
    assert response["Location"] == reverse("shuup_admin:dashboard")
    assert Category.objects.all_except_deleted().count() == 3
    assert Product.objects.all_except_deleted().count() == 4
    assert Carousel.objects.count() == 0
    assert manager.get_installed_business_segment(shop) is None
    assert manager.get_installed_products(shop) == []
    assert manager.get_installed_categories(shop) == []
    assert manager.get_installed_carousel(shop) is None
Example #58
0
 def get_default(request):
     return get_default_shop()
def test_many_price_info_cache_bump(rf):
    initial_price = 10
    shop = factories.get_default_shop()
    tax = factories.get_default_tax()
    tax_class = factories.get_default_tax_class()
    product = factories.create_product(
        "product",
        shop=shop,
        supplier=factories.get_default_supplier(),
        default_price=initial_price
    )
    child1 = factories.create_product(
        "child1",
        shop=shop,
        supplier=factories.get_default_supplier(),
        default_price=5
    )
    child2 = factories.create_product(
        "child2",
        shop=shop,
        supplier=factories.get_default_supplier(),
        default_price=9
    )
    child1.link_to_parent(product, variables={"color": "red"})
    child2.link_to_parent(product, variables={"color": "blue"})

    request = apply_request_middleware(rf.get("/"))
    child1_pi = child1.get_price_info(request)
    child2_pi = child1.get_price_info(request)

    def assert_cache_products():
        cache_many_price_info(request, product, 1, [child1_pi, child2_pi])
        assert get_many_cached_price_info(request, product, 1)[0].price == child1_pi.price
        assert get_many_cached_price_info(request, product, 1)[1].price == child2_pi.price

    def assert_nothing_is_cached():
        # nothing is cached
        assert get_many_cached_price_info(request, product, 1) is None

    # cache the item
    assert_nothing_is_cached()
    assert_cache_products()

    # cache bumped - the cache should be dropped - then, cache again
    tax.save()
    assert_nothing_is_cached()
    assert_cache_products()

    # cache bumped - the cache should be dropped - then, cache again
    tax_class.save()
    assert_nothing_is_cached()
    assert_cache_products()

    # cache bumped - the cache should be dropped - then, cache again
    product.save()
    assert_nothing_is_cached()
    assert_cache_products()

    shop_product = product.get_shop_instance(shop)

    # cache bumped - the cache should be dropped - then, cache again
    shop_product.save()
    assert_nothing_is_cached()
    assert_cache_products()

    category = factories.get_default_category()

    # cache bumped - the cache should be dropped - then, cache again
    shop_product.categories.add(category)
    assert_nothing_is_cached()
    assert_cache_products()
Example #60
0
def test_product_catalog_campaigns():
    shop = get_default_shop()
    product = create_product("test", shop, default_price=20)
    shop_product = product.get_shop_instance(shop)

    cat = Category.objects.create(name="test")
    campaign = CatalogCampaign.objects.create(shop=shop,
                                              name="test",
                                              active=True)

    # no rules
    assert CatalogCampaign.get_for_product(shop_product).count() == 0

    # category filter that doesn't match
    cat_filter = CategoryFilter.objects.create()
    cat_filter.categories.add(cat)
    campaign.filters.add(cat_filter)
    assert CatalogCampaign.get_for_product(shop_product).count() == 0

    shop_product.primary_category = cat
    shop_product.save()
    assert CatalogCampaign.get_for_product(shop_product).count() == 1
    shop_product.primary_category = None
    shop_product.save()
    assert CatalogCampaign.get_for_product(shop_product).count() == 0
    # category filter that matches
    shop_product.categories.add(cat)
    assert CatalogCampaign.get_for_product(shop_product).count() == 1

    # create other shop
    shop1 = Shop.objects.create(name="testshop",
                                identifier="testshop",
                                status=ShopStatus.ENABLED,
                                public_name="testshop")
    sp = ShopProduct.objects.create(product=product,
                                    shop=shop1,
                                    default_price=shop1.create_price(200))

    assert product.get_shop_instance(shop1) == sp

    campaign2 = CatalogCampaign.objects.create(shop=shop1,
                                               name="test1",
                                               active=True)
    cat_filter2 = CategoryFilter.objects.create()
    cat_filter2.categories.add(cat)
    campaign2.filters.add(cat_filter2)
    assert CatalogCampaign.get_for_product(sp).count() == 0

    # add product to this category
    sp.primary_category = cat
    sp.save()

    assert CatalogCampaign.get_for_product(sp).count() == 1  # matches now
    sp.primary_category = None
    sp.save()
    assert CatalogCampaign.get_for_product(sp).count() == 0  # no match
    sp.categories.add(cat)

    assert CatalogCampaign.get_for_product(sp).count() == 1  # matches now

    campaign3 = CatalogCampaign.objects.create(shop=shop1,
                                               name="test1",
                                               active=True)
    cat_filter3 = CategoryFilter.objects.create()
    cat_filter3.categories.add(cat)
    campaign3.filters.add(cat_filter3)

    assert CatalogCampaign.get_for_product(
        sp).count() == 2  # there are now two matching campaigns in same shop
    assert CatalogCampaign.get_for_product(
        shop_product).count() == 1  # another campaign matches only once