def seed_default(self): migrator = MigrateCommand() migrator.stdout = self.stdout migrator.handle(database="default", verbosity=1, noinput=True, app_label=None, migration_name=None) if not Shop.objects.exists(): Shop.objects.create(name="B2B", identifier="b2b", status=ShopStatus.ENABLED) try: tax_class = TaxClass.objects.create(identifier="default", tax_rate=0) except: tax_class = TaxClass.objects.create(identifier="default") PaymentMethod.objects.create(identifier="default", name="Invoice", tax_class=tax_class) PaymentMethod.objects.create(identifier="bank_xfer", name="Bank Transfer", tax_class=tax_class) PaymentMethod.objects.create(identifier="cash", name="Cash (Pickup Only)", tax_class=tax_class) ShippingMethod.objects.create(identifier="default", name="Post Parcel", tax_class=tax_class) ShippingMethod.objects.create(identifier="pickup", name="Pickup at Helsinki Store", tax_class=tax_class) create_default_order_statuses() get_default_supplier() ProductType.objects.create(identifier="default") SalesUnit.objects.create(identifier="pcs", short_name="pcs", name="pieces") print("Seeded basic shop information") if not User.objects.filter(is_superuser=True).exists(): User.objects.create_superuser( username="******", email="*****@*****.**", password="******", ) print("Superuser created: admin / admin")
def seed_default(self): migrator = MigrateCommand() migrator.stdout = self.stdout migrator.handle(database="default", verbosity=1, noinput=True, app_label=None, migration_name=None) if not Shop.objects.exists(): Shop.objects.create(name="Wintergear", identifier="default", status=ShopStatus.ENABLED) try: tax_class = TaxClass.objects.create(identifier="default", tax_rate=0) except: tax_class = TaxClass.objects.create(identifier="default") PaymentMethod.objects.create(identifier="default", name="Invoice", tax_class=tax_class) PaymentMethod.objects.create(identifier="bank_xfer", name="Bank Transfer", tax_class=tax_class) PaymentMethod.objects.create(identifier="cash", name="Cash (Pickup Only)", tax_class=tax_class) ShippingMethod.objects.create(identifier="default", name="Post Parcel", tax_class=tax_class) ShippingMethod.objects.create(identifier="pickup", name="Pickup at Helsinki Store", tax_class=tax_class) create_default_order_statuses() get_default_supplier() ProductType.objects.create(identifier="default") SalesUnit.objects.create(identifier="pcs", short_name="pcs", name="pieces") print("Seeded basic shop information") if not User.objects.filter(is_superuser=True).exists(): User.objects.create_superuser( username="******", email="*****@*****.**", password="******", ) print("Superuser created: admin / admin")
def get_frontend_order_state(contact, valid_lines=True): """ Get a dict structure mirroring what the frontend JavaScript would submit. :type contact: Contact|None """ translation.activate("en") shop = get_default_shop() tax = Tax.objects.create(code="test_code", rate=decimal.Decimal("0.20"), name="Default") tax_class = TaxClass.objects.create(identifier="test_tax_class", name="Default") rule = TaxRule.objects.create(tax=tax) rule.tax_classes.add(tax_class) rule.save() product = create_product( sku=printable_gibberish(), supplier=get_default_supplier(), shop=shop ) product.tax_class = tax_class product.save() if valid_lines: lines = [ {"id": "x", "type": "product", "product": {"id": product.id}, "quantity": "32", "baseUnitPrice": 50}, {"id": "y", "type": "other", "sku": "hello", "text": "A greeting", "quantity": 1, "unitPrice": "5.5"}, {"id": "z", "type": "text", "text": "This was an order!", "quantity": 0}, ] else: unshopped_product = create_product(sku=printable_gibberish(), supplier=get_default_supplier()) not_visible_product = create_product( sku=printable_gibberish(), supplier=get_default_supplier(), shop=shop ) not_visible_shop_product = not_visible_product.get_shop_instance(shop) not_visible_shop_product.visible = False not_visible_shop_product.save() lines = [ {"id": "x", "type": "product"}, # no product? {"id": "x", "type": "product", "product": {"id": unshopped_product.id}}, # not in this shop? {"id": "y", "type": "product", "product": {"id": -product.id}}, # invalid product? {"id": "z", "type": "other", "quantity": 1, "unitPrice": "q"}, # what's that price? {"id": "rr", "type": "product", "quantity": 1, "product": {"id": not_visible_product.id}} # not visible ] state = { "customer": {"id": contact.id if contact else None}, "lines": lines, "methods": { "shippingMethod": {"id": get_default_shipping_method().id}, "paymentMethod": {"id": get_default_payment_method().id}, }, "shop": { "selected": { "id": shop.id, "name": shop.name, "currency": shop.currency, "priceIncludeTaxes": shop.prices_include_tax } } } return state
def test_package(): shop = get_default_shop() supplier = get_default_supplier() package_product = create_product("PackageParent", shop=shop, supplier=supplier) assert not package_product.get_package_child_to_quantity_map() children = [create_product("PackageChild-%d" % x, shop=shop, supplier=supplier) for x in range(4)] package_def = {child: 1 + i for (i, child) in enumerate(children)} package_product.make_package(package_def) assert package_product.mode == ProductMode.PACKAGE_PARENT package_product.save() sp = package_product.get_shop_instance(shop) assert not list(sp.get_orderability_errors(supplier=supplier, quantity=1, customer=AnonymousContact())) with pytest.raises(ValueError): # Test re-packaging fails package_product.make_package(package_def) # Check that OrderCreator can deal with packages source = BasketishOrderSource() source.lines.append(SourceLine( type=OrderLineType.PRODUCT, product=package_product, supplier=get_default_supplier(), quantity=10, unit_price=TaxlessPrice(10), )) source.shop = get_default_shop() source.status = get_initial_order_status() creator = OrderCreator(request=None) order = creator.create_order(source) pids_to_quantities = order.get_product_ids_and_quantities() for child, quantity in six.iteritems(package_def): assert pids_to_quantities[child.pk] == 10 * quantity
def create_basket_and_campaign(request, conditions, product_price_value, campaign_discount_value): product = create_product("Some crazy product", request.shop, get_default_supplier(), default_price=product_price_value) basket = get_basket(request) basket.customer = request.customer supplier = get_default_supplier() basket.add_product(supplier=supplier, shop=request.shop, product=product, quantity=1) original_line_count = len(basket.get_final_lines()) assert original_line_count == 1 assert basket.product_count == 1 original_price = basket.total_price campaign = BasketCampaign.objects.create(shop=request.shop, name="test", public_name="test", active=True) BasketDiscountAmount.objects.create( campaign=campaign, discount_amount=campaign_discount_value) for condition in conditions: campaign.conditions.add(condition) assert campaign.is_available() return basket, original_line_count, original_price
def test_cross_sell_plugin_type(): """ Test that template helper returns correct number of cross sells when shop contains multiple relation types """ shop = get_default_shop() supplier = get_default_supplier() product = create_product("test-sku", shop=shop, supplier=supplier, stock_behavior=StockBehavior.UNSTOCKED) context = get_jinja_context(product=product) type_counts = ((ProductCrossSellType.RELATED, 1), (ProductCrossSellType.RECOMMENDED, 2), (ProductCrossSellType.BOUGHT_WITH, 3)) # Create cross sell products and relations in different quantities for type, count in type_counts: _create_cross_sell_products(product, shop, supplier, type, count) assert ProductCrossSell.objects.filter(product1=product, type=type).count() == count # Make sure quantities returned by plugin match for type, count in type_counts: assert len( list( product_helpers.get_product_cross_sells( context, product, type, count))) == count
def get_order_and_source(admin_user): # create original source to tamper with source = BasketishOrderSource(get_default_shop()) source.status = get_initial_order_status() source.billing_address = MutableAddress.objects.create(name="Original Billing") source.shipping_address = MutableAddress.objects.create(name="Original Shipping") source.customer = get_person_contact(admin_user) source.payment_method = get_default_payment_method() source.shipping_method = get_default_shipping_method() source.add_line( type=OrderLineType.PRODUCT, product=get_default_product(), supplier=get_default_supplier(), quantity=1, base_unit_price=source.create_price(10), ) source.add_line( type=OrderLineType.OTHER, quantity=1, base_unit_price=source.create_price(10), require_verification=True, ) assert len(source.get_lines()) == 2 source.creator = admin_user creator = OrderCreator() order = creator.create_order(source) return order, source
def __init__(self, image_dir): self.image_dir = image_dir self.supplier = get_default_supplier() self.sales_unit = SalesUnit.objects.first() self.tax_class = TaxClass.objects.first() self.product_type = ProductType.objects.first() self.shop = Shop.objects.first()
def test_tracking_codes(): product = get_default_product() supplier = get_default_supplier() order = create_order_with_product( product, supplier=supplier, quantity=1, taxless_base_unit_price=10, tax_rate=decimal.Decimal("0.5") ) _add_product_to_order(order, "duck-tape-1", 3, order.shop, supplier) _add_product_to_order(order, "water-1", 2, order.shop, supplier) order.cache_prices() order.check_all_verified() order.save() # Create shipment with tracking code for every product line. product_lines = order.lines.exclude(product_id=None) assert len(product_lines) == 3 for line in product_lines: shipment = order.create_shipment(supplier, {line.product: line.quantity}) if line.quantity != 3: shipment.tracking_code = "123FI" shipment.save() tracking_codes = order.get_tracking_codes() code_count = (len(product_lines)-1) # We skipped that one assert len(tracking_codes) == code_count assert len([tracking_code for tracking_code in tracking_codes if tracking_code == "123FI"]) == code_count
def test_tracking_codes(): product = get_default_product() supplier = get_default_supplier() order = create_order_with_product(product, supplier=supplier, quantity=1, taxless_base_unit_price=10, tax_rate=decimal.Decimal("0.5")) _add_product_to_order(order, "duck-tape-1", 3, order.shop, supplier) _add_product_to_order(order, "water-1", 2, order.shop, supplier) order.cache_prices() order.check_all_verified() order.save() # Create shipment with tracking code for every product line. product_lines = order.lines.exclude(product_id=None) assert len(product_lines) == 3 for line in product_lines: shipment = order.create_shipment(supplier, {line.product: line.quantity}) if line.quantity != 3: shipment.tracking_code = "123FI" shipment.save() tracking_codes = order.get_tracking_codes() code_count = (len(product_lines) - 1) # We skipped that one assert len(tracking_codes) == code_count assert len([ tracking_code for tracking_code in tracking_codes if tracking_code == "123FI" ]) == code_count
def test_best_selling_products_with_multiple_orders(): context = get_jinja_context() supplier = get_default_supplier() shop = get_default_shop() n_products = 2 price = 10 product_1 = create_product("test-sku-1", supplier=supplier, shop=shop) product_2 = create_product("test-sku-2", supplier=supplier, shop=shop) create_order_with_product(product_1, supplier, quantity=1, taxless_base_unit_price=price, shop=shop) create_order_with_product(product_2, supplier, quantity=1, taxless_base_unit_price=price, shop=shop) cache.clear() # Two initial products sold assert product_1 in general.get_best_selling_products(context, n_products=n_products) assert product_2 in general.get_best_selling_products(context, n_products=n_products) product_3 = create_product("test-sku-3", supplier=supplier, shop=shop) create_order_with_product(product_3, supplier, quantity=2, taxless_base_unit_price=price, shop=shop) cache.clear() # Third product sold in greater quantity assert product_3 in general.get_best_selling_products(context, n_products=n_products) create_order_with_product(product_1, supplier, quantity=4, taxless_base_unit_price=price, shop=shop) create_order_with_product(product_2, supplier, quantity=4, taxless_base_unit_price=price, shop=shop) cache.clear() # Third product outsold by first two products assert product_3 not in general.get_best_selling_products(context, n_products=n_products)
def test_sales_ranges_update_after_range_update(): shop = get_default_shop() supplier = get_default_supplier() person = create_random_person() company = create_random_company() create_fully_paid_order(shop, person, supplier, "sku1", 50) create_fully_paid_order(shop, company, supplier, "sku2", 100) assert get_total_sales(shop, person) == 50 assert get_total_sales(shop, company) == 100 sales_range = create_sales_range("gold", shop, 10, 90) assert sales_range.group in person.groups.all() assert sales_range.group not in company.groups.all() sales_range.max_value = None sales_range.save() assert sales_range.group in person.groups.all() assert sales_range.group in company.groups.all() # Make sure customers is actually removed when range changes sales_range.max_value = 60 sales_range.save() assert sales_range.group in person.groups.all() assert sales_range.group not in company.groups.all() # Inactive ranges shouldn't update group members sales_range.min_value = None sales_range.save() assert sales_range.group in person.groups.all() assert sales_range.group not in company.groups.all()
def test_form_populate_initial_data(rf, admin_user): shop = get_default_shop() supplier = get_default_supplier() campaign = BasketCampaign(discount_percentage=0.1, shop=shop) campaign.save() # Test that correct initial value is returned for non-many-to-many field product_amount_initial = 10 product_amount_condition = BasketTotalProductAmountCondition(product_count=product_amount_initial) product_amount_condition.save() campaign.conditions.add(product_amount_condition) products_count_initial = 5 for i in range(products_count_initial): create_product(printable_gibberish(), shop=shop, supplier=supplier, default_price="20") products_initial = Product.objects.all()[:products_count_initial] assert len(products_initial) == products_count_initial # Test that correct initial value is returned for many-to-many field products_in_basket_condition = ProductsInBasketCondition.objects.create() products_in_basket_condition.values = products_initial products_in_basket_condition.save() campaign.conditions.add(products_in_basket_condition) assert len(campaign.conditions.all()) == 2 request=apply_request_middleware(rf.get("/"), user=admin_user) form = BasketCampaignForm(request=request, instance=campaign) assert form.fields["basket_product_condition"].initial == product_amount_initial assert set(form.fields["basket_products_condition"].initial) == set([p.pk for p in products_initial])
def test_get_visible_products_filter(): context = get_jinja_context() shop = get_default_shop() supplier = get_default_supplier() product_1 = create_product( "test-sku-1", supplier=supplier, shop=shop, ) product_2 = create_product( "test-sku-2", supplier=supplier, shop=shop, ) filter_dict = {"id": product_1.id} product_list = general.get_visible_products(context, n_products=2, filter_dict=filter_dict) assert product_1 in product_list assert product_2 not in product_list # Test also with orderable_only False product_list = general.get_visible_products(context, n_products=2, filter_dict=filter_dict, orderable_only=False) assert product_1 in product_list assert product_2 not in product_list
def test_order_source_total_gross_weight(rf, admin_user): source = seed_source(admin_user) supplier = get_default_supplier() products_data = [{ "sku": "sku1234", "net_weight": decimal.Decimal("1"), "gross_weight": decimal.Decimal("43.34257") }, { "sku": "sku4321", "net_weight": decimal.Decimal("11.342569"), "gross_weight": decimal.Decimal("11.34257") }, { "sku": "sku1111", "net_weight": decimal.Decimal("0.00"), "gross_weight": decimal.Decimal("0.00") }] for product_data in products_data: product = create_product(sku=product_data.pop("sku"), shop=source.shop, supplier=supplier, default_price=3.33, **product_data) source.add_line( type=OrderLineType.PRODUCT, product=product, supplier=supplier, quantity=1, base_unit_price=source.create_price(1), ) assert len(source.get_lines()) == len(products_data) assert source.total_gross_weight == sum( [data.get("gross_weight") for data in products_data])
def test_coupon_amount_limit(): coupon = Coupon.objects.create(code="TEST", active=True) get_default_campaign(coupon) contact = create_random_person() shop = get_default_shop() product = create_product("test", shop=shop, supplier=get_default_supplier(), default_price="12") order = create_random_order(customer=contact) for x in range(50): coupon.use(order) assert coupon.usages.count() == 50 coupon.increase_usage_limit_by(5) coupon.save() assert coupon.usage_limit == 55 assert coupon.can_use_code(contact) for x in range(5): coupon.use(order) assert coupon.usages.count() == 55 assert not Coupon.is_usable(coupon.code, order.customer) assert coupon.usages.count() == 55 # no change, limit met
def test_form_populate_initial_data(rf, admin_user): shop = get_default_shop() supplier = get_default_supplier() initial_discount_amount = 20 campaign = BasketCampaign.objects.create(shop=shop) BasketDiscountAmount.objects.create(campaign=campaign, discount_amount=initial_discount_amount) # Test that correct initial value is returned for non-many-to-many field product_amount_initial = 10 product_amount_condition = BasketTotalProductAmountCondition(product_count=product_amount_initial) product_amount_condition.save() campaign.conditions.add(product_amount_condition) products_count_initial = 5 for i in range(products_count_initial): create_product(printable_gibberish(), shop=shop, supplier=supplier, default_price="20") products_initial = Product.objects.all()[:products_count_initial] assert len(products_initial) == products_count_initial # Test that correct initial value is returned for many-to-many field products_in_basket_condition = ProductsInBasketCondition.objects.create() products_in_basket_condition.values = products_initial products_in_basket_condition.save() campaign.conditions.add(products_in_basket_condition) assert len(campaign.conditions.all()) == 2 assert campaign.effects.count() == 1 request=apply_request_middleware(rf.get("/"), user=admin_user) form = BasketCampaignForm(request=request, instance=campaign) assert form.fields["basket_product_condition"].initial == product_amount_initial assert set(form.fields["basket_products_condition"].initial) == set([p.pk for p in products_initial]) assert form.fields["discount_amount_effect"].initial == initial_discount_amount
def test_basket_campaign_module_case1(rf): request, shop, group = initialize_test(rf, False) price = shop.create_price basket = get_basket(request) supplier = get_default_supplier() single_product_price = "50" discount_amount_value = "10" # create basket rule that requires 2 products in basket basket_rule1 = BasketTotalProductAmountCondition.objects.create(value="2") product = create_product(printable_gibberish(), shop=shop, supplier=supplier, default_price=single_product_price) basket.add_product(supplier=supplier, shop=shop, product=product, quantity=1) basket.save() assert basket.product_count == 1 campaign = BasketCampaign.objects.create( shop=shop, public_name="test", name="test", discount_amount_value=discount_amount_value, active=True) campaign.conditions.add(basket_rule1) campaign.save() assert len(basket.get_final_lines()) == 1 # case 1 assert basket.total_price == price(single_product_price) # case 1 basket.add_product(supplier=supplier, shop=shop, product=product, quantity=1) basket.save() assert len(basket.get_final_lines()) == 2 # case 1 assert basket.product_count == 2 assert basket.total_price == (price(single_product_price) * basket.product_count - price(discount_amount_value)) assert OrderLineType.DISCOUNT in [l.type for l in basket.get_final_lines()]
def test_order_creator(admin_user): source = seed_source(admin_user) source.lines.append( SourceLine( type=OrderLineType.PRODUCT, product=get_default_product(), supplier=get_default_supplier(), quantity=1, unit_price=TaxlessPrice(10), )) source.lines.append( SourceLine( type=OrderLineType.OTHER, quantity=1, unit_price=TaxlessPrice(10), require_verification=True, )) creator = OrderCreator(request=None) order = creator.create_order(source) assert get_data_dict(source.billing_address) == get_data_dict( order.billing_address) assert get_data_dict(source.shipping_address) == get_data_dict( order.shipping_address) assert source.customer == order.customer assert source.payment_method == order.payment_method assert source.shipping_method == order.shipping_method assert order.pk
def test_protected_fields(): activate("en") shop = Shop.objects.create( name="testshop", identifier="testshop", status=ShopStatus.ENABLED, public_name="test shop", domain="derp", currency="EUR" ) assert shop.name == "testshop" assert shop.currency == "EUR" shop_form = ShopBaseForm(instance=shop, languages=settings.LANGUAGES) assert not shop_form._get_protected_fields() # No protected fields just yet, right? data = get_form_data(shop_form, prepared=True) shop_form = ShopBaseForm(data=data, instance=shop, languages=settings.LANGUAGES) _test_cleanliness(shop_form) shop_form.save() # Now let's make it protected! create_product(printable_gibberish(), shop=shop, supplier=get_default_supplier()) order = create_random_order(customer=create_random_person(), shop=shop) assert order.shop == shop # And try again... data["currency"] = "XBT" # Bitcoins! shop_form = ShopBaseForm(data=data, instance=shop, languages=settings.LANGUAGES) assert shop_form._get_protected_fields() # So protected! _test_cleanliness(shop_form) shop = shop_form.save() assert shop.currency == "EUR" # But the shop form ignored the change . . .
def test_min_amount_is_not_included(): shop = get_default_shop() supplier = get_default_supplier() person = create_random_person() initial_group_count = person.groups.count() sales_ranges = [ ("silver", 0, 50), ("gold", 50, 100), ("diamond", 100, 1000), ("reverse_diamond", 1000, 100) ] for identifier, min, max in sales_ranges: create_sales_range(identifier, shop, min, max) payment = create_fully_paid_order(shop, person, supplier, "sku1", 50) assert _get_total_sales(shop, person) == 50 update_customers_groups(Payment, payment) assert person.groups.count() == (initial_group_count + 1) assert bool([group for group in person.groups.all() if group.identifier == "gold"]) assert not bool([group for group in person.groups.all() if group.identifier in ["silver", "diamond"]]) payment = create_fully_paid_order(shop, person, supplier, "sku2", 50) assert _get_total_sales(shop, person) == 100 update_customers_groups(Payment, payment) assert person.groups.count() == (initial_group_count + 1) assert bool([group for group in person.groups.all() if group.identifier == "diamond"]) assert not bool([group for group in person.groups.all() if group.identifier == "reverse_diamond"]) assert not bool([group for group in person.groups.all() if group.identifier in ["silver", "gold"]])
def test_sales_between_ranges(): shop = get_default_shop() supplier = get_default_supplier() person = create_random_person() initial_group_count = person.groups.count() sales_ranges = [ ("wood", 15, 0), ("silver", 0, 50), ("diamond", 100, None) ] for identifier, min, max in sales_ranges: create_sales_range(identifier, shop, min, max) payment = create_fully_paid_order(shop, person, supplier, "sku1", 10) assert get_total_sales(shop, person) == 10 update_customers_groups(Payment, payment) assert person.groups.count() == (initial_group_count + 1) assert bool([group for group in person.groups.all() if group.identifier == "silver"]) assert not bool([group for group in person.groups.all() if group.identifier == "wood"]) payment = create_fully_paid_order(shop, person, supplier, "sku2", 50) assert get_total_sales(shop, person) == 60 update_customers_groups(Payment, payment) assert person.groups.count() == initial_group_count assert not bool([group for group in person.groups.all() if group.identifier in ["silver", "gold", "diamond"]]) payment = create_fully_paid_order(shop, person, supplier, "sku3", 200) assert get_total_sales(shop, person) == 260 update_customers_groups(Payment, payment) assert person.groups.count() == (initial_group_count + 1) assert bool([group for group in person.groups.all() if group.identifier == "diamond"])
def test_basket_shipping_error(rf): StoredBasket.objects.all().delete() shop = get_default_shop() supplier = get_default_supplier() shipped_product = create_product( printable_gibberish(), shop=shop, supplier=supplier, default_price=50, shipping_mode=ShippingMode.SHIPPED ) unshipped_product = create_product( printable_gibberish(), shop=shop, supplier=supplier, default_price=50, shipping_mode=ShippingMode.NOT_SHIPPED ) request = rf.get("/") request.session = {} request.shop = shop apply_request_middleware(request) basket = get_basket(request) # With a shipped product but no shipping methods, we oughta get an error basket.add_product(supplier=supplier, shop=shop, product=shipped_product, quantity=1) assert any(ve.code == "no_common_shipping" for ve in basket.get_validation_errors()) basket.clear_all() # But with an unshipped product, we should not basket.add_product(supplier=supplier, shop=shop, product=unshipped_product, quantity=1) assert not any(ve.code == "no_common_shipping" for ve in basket.get_validation_errors())
def test_custom_payment_processor_cash_service(choice_identifier, expected_payment_status): shop = get_default_shop() product = get_default_product() supplier = get_default_supplier() processor = CustomPaymentProcessor.objects.create() payment_method = PaymentMethod.objects.create( shop=shop, payment_processor=processor, choice_identifier=choice_identifier, tax_class=get_default_tax_class()) order = create_order_with_product( product=product, supplier=supplier, quantity=1, taxless_base_unit_price=Decimal('5.55'), shop=shop) order.taxful_total_price = TaxfulPrice(Decimal('5.55'), u'EUR') order.payment_method = payment_method order.save() assert order.payment_status == PaymentStatus.NOT_PAID processor.process_payment_return_request(choice_identifier, order, None) assert order.payment_status == expected_payment_status processor.process_payment_return_request(choice_identifier, order, None) assert order.payment_status == expected_payment_status
def get_order_and_source(admin_user): # create original source to tamper with source = BasketishOrderSource(get_default_shop()) source.status = get_initial_order_status() source.billing_address = MutableAddress.objects.create( name="Original Billing") source.shipping_address = MutableAddress.objects.create( name="Original Shipping") source.customer = get_person_contact(admin_user) source.payment_method = get_default_payment_method() source.shipping_method = get_default_shipping_method() source.add_line( type=OrderLineType.PRODUCT, product=get_default_product(), supplier=get_default_supplier(), quantity=1, base_unit_price=source.create_price(10), ) source.add_line( type=OrderLineType.OTHER, quantity=1, base_unit_price=source.create_price(10), require_verification=True, ) assert len(source.get_lines()) == 2 source.creator = admin_user creator = OrderCreator() order = creator.create_order(source) return order, source
def test_add_and_remove_and_clear(): product = get_default_product() supplier = get_default_supplier() request = get_request_with_basket() basket = request.basket with pytest.raises(ValidationError): basket_commands.handle_add(request, basket, product_id=product.pk, quantity=-3) # Ordering antimatter is not supported # These will get merged into one line... basket_commands.handle_add(request, basket, **{"product_id": product.pk, "quantity": 1, "supplier_id": supplier.pk}) basket_commands.handle_add(request, basket, **{"product_id": product.pk, "quantity": 2}) # ... so there will be 3 products but one line assert basket.product_count == 3 lines = basket.get_lines() assert len(lines) == 1 # ... and deleting that line will clear the basket... basket_commands.handle_del(request, basket, lines[0].line_id) assert basket.product_count == 0 # ... and adding another product will create a new line... basket_commands.handle_add(request, basket, product_id=product.pk, quantity=1) assert basket.product_count == 1 # ... that can be cleared. basket_commands.handle_clear(request, basket) assert basket.product_count == 0
def test_only_cheapest_price_is_selected(rf): request, shop, group = initialize_test(rf, False) price = shop.create_price basket = get_basket(request) supplier = get_default_supplier() # create a basket rule that requires atleast value of 200 rule = BasketTotalAmountCondition.objects.create(value="200") product_price = "200" discount1 = "10" discount2 = "20" # should be selected product = create_product(printable_gibberish(), shop=shop, supplier=supplier, default_price=product_price) basket.add_product(supplier=supplier, shop=shop, product=product, quantity=1) campaign = BasketCampaign.objects.create( shop=shop, public_name="test", name="test", discount_amount_value=discount1, active=True ) campaign.conditions.add(rule) campaign.save() campaign = BasketCampaign.objects.create( shop=shop, public_name="test", name="test", discount_amount_value=discount2, active=True ) campaign.conditions.add(rule) campaign.save() assert len(basket.get_final_lines()) == 2 line_types = [l.type for l in basket.get_final_lines()] assert OrderLineType.DISCOUNT in line_types for line in basket.get_final_lines(): if line.type == OrderLineType.DISCOUNT: assert line.discount_amount == price(discount2)
def test_admin_catalog_campaign_edit_view(rf, admin_user): shop = get_default_shop() view = CatalogCampaignEditView(request=apply_request_middleware(rf.get("/"), user=admin_user)) form_class = view.get_form_class() form_kwargs = view.get_form_kwargs() form = form_class(**form_kwargs) assert not form.is_bound data = get_form_data(form) data.update({ "shop": shop.pk, "name": "test", }) form = form_class(**dict(form_kwargs, data=data)) form.full_clean() assert "You must define discount percentage or amount" in form.errors["__all__"][0] data.update({"discount_amount_value": "20"}) form = form_class(**dict(form_kwargs, data=data)) form.full_clean() # atleast 1 rule is required assert "You must set at least one rule for this campaign" in form.errors["__all__"][0] supplier = get_default_supplier() product = create_product(printable_gibberish(), shop=shop, supplier=supplier, default_price="20") data.update({"product_filter": [1]}) form = form_class(**dict(form_kwargs, data=data)) form.full_clean() assert not form.errors campaign = form.save() assert campaign.filters.count() == 1
def test_min_amount_is_not_included(): shop = get_default_shop() supplier = get_default_supplier() person = create_random_person() initial_group_count = person.groups.count() sales_ranges = [ ("silver", 0, 50), ("gold", 50, 100), ("diamond", 100, 1000), ("reverse_diamond", 1000, 100) ] for identifier, min, max in sales_ranges: create_sales_range(identifier, shop, min, max) payment = create_fully_paid_order(shop, person, supplier, "sku1", 50) assert get_total_sales(shop, person) == 50 update_customers_groups(Payment, payment) assert person.groups.count() == (initial_group_count + 1) assert bool([group for group in person.groups.all() if group.identifier == "gold"]) assert not bool([group for group in person.groups.all() if group.identifier in ["silver", "diamond"]]) payment = create_fully_paid_order(shop, person, supplier, "sku2", 50) assert get_total_sales(shop, person) == 100 update_customers_groups(Payment, payment) assert person.groups.count() == (initial_group_count + 1) assert bool([group for group in person.groups.all() if group.identifier == "diamond"]) assert not bool([group for group in person.groups.all() if group.identifier == "reverse_diamond"]) assert not bool([group for group in person.groups.all() if group.identifier in ["silver", "gold"]])
def test_order_source_parentage(rf, admin_user): source = seed_source(admin_user) product = get_default_product() source.add_line( type=OrderLineType.PRODUCT, product=product, supplier=get_default_supplier(), quantity=1, base_unit_price=source.create_price(10), line_id="parent" ) source.add_line( type=OrderLineType.OTHER, text="Child Line", sku="KIDKIDKID", quantity=1, base_unit_price=source.create_price(5), parent_line_id="parent" ) request = apply_request_middleware(rf.get("/")) creator = OrderCreator(request) order = Order.objects.get(pk=creator.create_order(source).pk) kid_line = order.lines.filter(sku="KIDKIDKID").first() assert kid_line assert kid_line.parent_line.product_id == product.pk
def test_order_creator(rf, admin_user): source = seed_source(admin_user) source.add_line( type=OrderLineType.PRODUCT, product=get_default_product(), supplier=get_default_supplier(), quantity=1, base_unit_price=source.create_price(10), ) source.add_line( type=OrderLineType.OTHER, quantity=1, base_unit_price=source.create_price(10), require_verification=True, ) request = apply_request_middleware(rf.get("/")) creator = OrderCreator(request) order = creator.create_order(source) assert get_data_dict(source.billing_address) == get_data_dict(order.billing_address) assert get_data_dict(source.shipping_address) == get_data_dict(order.shipping_address) assert source.customer == order.customer assert source.payment_method == order.payment_method assert source.shipping_method == order.shipping_method assert order.pk
def test_percentage_campaign(rf): request, shop, group = initialize_test(rf, False) price = shop.create_price basket = get_basket(request) supplier = get_default_supplier() # create a basket rule that requires atleast value of 200 rule = BasketTotalAmountCondition.objects.create(value="200") product_price = "200" discount_percentage = "0.1" expected_discounted_price = price(product_price) - (price(product_price) * Decimal(discount_percentage)) product = create_product(printable_gibberish(), shop=shop, supplier=supplier, default_price=product_price) basket.add_product(supplier=supplier, shop=shop, product=product, quantity=1) campaign = BasketCampaign.objects.create( shop=shop, public_name="test", name="test", discount_percentage=discount_percentage, active=True ) campaign.conditions.add(rule) campaign.save() assert len(basket.get_final_lines()) == 2 assert basket.product_count == 1 assert basket.total_price == expected_discounted_price
def test_order_creation_adds_usage(rf, admin_user): request, shop, group = initialize_test(rf, False) source = seed_source(admin_user) source.add_line( type=OrderLineType.PRODUCT, product=get_default_product(), supplier=get_default_supplier(), quantity=1, base_unit_price=source.create_price(10), ) source.add_line( type=OrderLineType.OTHER, quantity=1, base_unit_price=source.create_price(10), require_verification=True, ) # add coupon coupon = Coupon.objects.create(active=True, code="asdf") BasketCampaign.objects.create(active=True, shop=shop, name="test", public_name="test", discount_percentage="0.1", coupon=coupon) source.add_code(coupon.code) creator = OrderCreator() creator.create_order(source) assert CouponUsage.objects.count() == 1
def test_order_creation_adds_usage(rf, admin_user): request, shop, group = initialize_test(rf, False) source = seed_source(admin_user) source.add_line( type=OrderLineType.PRODUCT, product=get_default_product(), supplier=get_default_supplier(), quantity=1, base_unit_price=source.create_price(10), ) source.add_line( type=OrderLineType.OTHER, quantity=1, base_unit_price=source.create_price(10), require_verification=True ) # add coupon coupon = Coupon.objects.create(active=True, code="asdf") campaign = BasketCampaign.objects.create( active=True, shop=shop, name="test", public_name="test", discount_percentage="0.1", coupon=coupon ) source.add_code(coupon.code) request = apply_request_middleware(rf.get("/")) creator = OrderCreator(request) order = creator.create_order(source) assert CouponUsage.objects.count() == 1
def _get_order_with_coupon(request, initial_status, condition_product_count=1): shop = request.shop basket = get_basket(request) supplier = get_default_supplier() product = create_product(printable_gibberish(), shop=shop, supplier=supplier, default_price="50") basket.add_product(supplier=supplier, shop=shop, product=product, quantity=1) dc = Coupon.objects.create(code="TEST", active=True) campaign = BasketCampaign.objects.create( shop=shop, name="test", public_name="test", coupon=dc, discount_amount_value=shop.create_price("20"), active=True ) rule = BasketTotalProductAmountCondition.objects.create(value=1) campaign.conditions.add(rule) campaign.save() basket.add_code(dc.code) basket.save() basket.status = initial_status creator = OrderCreator(request) order = creator.create_order(basket) assert order.lines.count() == 2 assert OrderLineType.DISCOUNT in [l.type for l in order.lines.all()] return order
def _get_order_with_coupon(request, initial_status, condition_product_count=1): shop = request.shop basket = get_basket(request) supplier = get_default_supplier() product = create_product(printable_gibberish(), shop=shop, supplier=supplier, default_price="50") basket.add_product(supplier=supplier, shop=shop, product=product, quantity=1) dc = Coupon.objects.create(code="TEST", active=True) campaign = BasketCampaign.objects.create(shop=shop, name="test", public_name="test", coupon=dc, active=True) BasketDiscountAmount.objects.create( discount_amount=shop.create_price("20"), campaign=campaign) rule = BasketTotalProductAmountCondition.objects.create(value=1) campaign.conditions.add(rule) campaign.save() basket.add_code(dc.code) basket.save() basket.status = initial_status creator = OrderCreator(request) order = creator.create_order(basket) assert order.lines.count() == 2 assert OrderLineType.DISCOUNT in [l.type for l in order.lines.all()] return order
def _get_frontend_order_state(shop, contact): tax = Tax.objects.create(code="test_code", rate=decimal.Decimal("0.20"), name="Default") tax_class = TaxClass.objects.create(identifier="test_tax_class", name="Default") rule = TaxRule.objects.create(tax=tax) rule.tax_classes.add(tax_class) rule.save() product = create_product( sku=printable_gibberish(), supplier=get_default_supplier(), shop=shop ) product.tax_class = tax_class product.save() lines = [ {"id": "x", "type": "product", "product": {"id": product.id}, "quantity": "32", "baseUnitPrice": 50} ] state = { "customer": {"id": contact.id if contact else None}, "lines": lines, "methods": { "shippingMethod": {"id": get_shipping_method(shop=shop).id}, "paymentMethod": {"id": get_payment_method(shop=shop).id}, }, "shop": { "selected": { "id": shop.id, "name": shop.safe_translation_getter("name"), "currency": shop.currency, "priceIncludeTaxes": shop.prices_include_tax } } } return state
def test_sales_ranges_basic(): shop = get_default_shop() supplier = get_default_supplier() person = create_random_person() initial_group_count = person.groups.count() sales_ranges = [("silver", 0, 50), ("gold", 50, 100), ("diamond", 100, 1000)] for identifier, min, max in sales_ranges: create_sales_level(identifier, shop, min, max) payment = create_fully_paid_order(shop, person, supplier, "sku1", 10) assert _get_total_sales(shop, person) == 10 update_customers_groups(Payment, payment) assert person.groups.count() == (initial_group_count + 1) assert bool([ group for group in person.groups.all() if group.identifier == "silver" ]) payment = create_fully_paid_order(shop, person, supplier, "sku2", 50) assert _get_total_sales(shop, person) == 60 update_customers_groups(Payment, payment) assert person.groups.count() == (initial_group_count + 1) assert bool( [group for group in person.groups.all() if group.identifier == "gold"]) payment = create_fully_paid_order(shop, person, supplier, "sku3", 200) assert _get_total_sales(shop, person) == 260 update_customers_groups(Payment, payment) assert person.groups.count() == (initial_group_count + 1) assert bool([ group for group in person.groups.all() if group.identifier == "diamond" ])
def test_extending_shipment_form_valid_hook(rf, admin_user): shop = get_default_shop() supplier = get_default_supplier() product = create_product(sku="test-sku", shop=shop, supplier=supplier, default_price=3.33) quantity = 1 order = create_order_with_product(product, supplier, quantity=quantity, taxless_base_unit_price=1, shop=shop) extend_form_class = "shoop_tests.admin.test_shipment_creator.ShipmentFormModifierTest" with override_provides(FORM_MODIFIER_PROVIDER_KEY, [extend_form_class]): phone_number = "+358911" data = {"q_%s" % product.pk: 1, "supplier": supplier.pk, "phone": phone_number} request = apply_request_middleware(rf.post("/", data=data), user=admin_user) view = OrderCreateShipmentView.as_view() response = view(request, pk=order.pk) assert response.status_code == 302 # Order should now have shipment, but let's re fetch it first order = Order.objects.get(pk=order.pk) assert order.shipments.count() == 1 shipment = order.shipments.first() assert order.shipping_data.get(shipment.identifier).get("phone") == phone_number assert shipment.supplier_id == supplier.id assert shipment.products.count() == 1 assert shipment.products.first().product_id == product.id
def test_basket_campaign_case2(rf): request, shop, group = initialize_test(rf, False) price = shop.create_price basket = get_basket(request) supplier = get_default_supplier() # create a basket rule that requires at least value of 200 rule = BasketTotalAmountCondition.objects.create(value="200") single_product_price = "50" discount_amount_value = "10" unique_shipping_method = get_shipping_method(shop, price=50) for x in range(3): product = create_product(printable_gibberish(), shop=shop, supplier=supplier, default_price=single_product_price) basket.add_product(supplier=supplier, shop=shop, product=product, quantity=1) assert basket.product_count == 3 campaign = BasketCampaign.objects.create( shop=shop, public_name="test", name="test", discount_amount_value=discount_amount_value, active=True) campaign.conditions.add(rule) campaign.save() assert len(basket.get_final_lines()) == 3 assert basket.total_price == price( single_product_price) * basket.product_count # check that shipping method affects campaign basket.shipping_method = unique_shipping_method basket.save() basket.uncache() assert len(basket.get_final_lines() ) == 4 # Shipping should not affect the rule being triggered line_types = [l.type for l in basket.get_final_lines()] assert OrderLineType.DISCOUNT not in line_types product = create_product(printable_gibberish(), shop=shop, supplier=supplier, default_price=single_product_price) basket.add_product(supplier=supplier, shop=shop, product=product, quantity=1) assert len(basket.get_final_lines()) == 6 # Discount included assert OrderLineType.DISCOUNT in [l.type for l in basket.get_final_lines()]
def test_complex_variation(): request = get_request_with_basket() basket = request.basket shop = get_default_shop() supplier = get_default_supplier() parent = create_product("SuperComplexVarParent", shop=shop, supplier=supplier) color_var = ProductVariationVariable.objects.create(product=parent, identifier="color") size_var = ProductVariationVariable.objects.create(product=parent, identifier="size") ProductVariationVariableValue.objects.create(variable=color_var, identifier="yellow") ProductVariationVariableValue.objects.create(variable=size_var, identifier="small") combinations = list(parent.get_all_available_combinations()) for combo in combinations: child = create_product("xyz-%s" % combo["sku_part"], shop=shop, supplier=supplier) child.link_to_parent(parent, combo["variable_to_value"]) # Elided product should not yield a result yellow_color_value = ProductVariationVariableValue.objects.get(variable=color_var, identifier="yellow") small_size_value = ProductVariationVariableValue.objects.get(variable=size_var, identifier="small") # add to basket yellow + small kwargs = {"var_%d" % color_var.pk: yellow_color_value.pk, "var_%d" % size_var.pk: small_size_value.pk} basket_commands.handle_add_var(request, basket, 1, **kwargs) assert basket.get_product_ids_and_quantities()[child.pk] == 1 with pytest.raises(ValidationError): kwargs = {"var_%d" % color_var.pk: yellow_color_value.pk, "var_%d" % size_var.pk: small_size_value.pk + 1} basket_commands.handle_add_var(request, basket, 1, **kwargs)
def test_default_supplier(): supplier = get_default_supplier() shop_product = get_default_shop_product() product = shop_product.product assert supplier.get_stock_statuses([product.id ])[product.id].logical_count == 0 assert not list( supplier.get_orderability_errors(shop_product, 1, customer=None))
def test_shipment_weights_ship_all(): shop = get_default_shop() supplier = get_default_supplier() order = _get_order(shop, supplier) shipment = order.create_shipment_of_all_products(supplier=supplier) assert shipment.weight == sum( [_get_weight_from_product_data(product_data) for product_data in _get_product_data()] )
def test_product_minimum_order_quantity(admin_user): shop_product = get_default_shop_product() supplier = get_default_supplier() admin_contact = get_person_contact(admin_user) with modify(shop_product, visibility_limit=ProductVisibility.VISIBLE_TO_ALL, orderable=True, minimum_purchase_quantity=10): assert any(ve.code == "purchase_quantity_not_met" for ve in shop_product.get_orderability_errors(supplier=supplier, customer=admin_contact, quantity=1)) assert not any(ve.code == "purchase_quantity_not_met" for ve in shop_product.get_orderability_errors(supplier=supplier, customer=admin_contact, quantity=15))
def test_shipment_weights_separate_shipments(): shop = get_default_shop() supplier = get_default_supplier() order = _get_order(shop, supplier) product_lines = order.lines.exclude(product_id=None) for line in product_lines: shipment = order.create_shipment({line.product: line.quantity}, supplier=supplier) assert shipment.weight == (line.quantity * line.product.gross_weight / 1000)
def test_basket_free_product_coupon(rf): request, shop, group = initialize_test(rf, False) price = shop.create_price basket = get_basket(request) supplier = get_default_supplier() single_product_price = "50" discount_amount_value = "10" # create basket rule that requires 2 products in basket product = create_product(printable_gibberish(), shop=shop, supplier=supplier, default_price=single_product_price) basket.add_product(supplier=supplier, shop=shop, product=product, quantity=1) basket.add_product(supplier=supplier, shop=shop, product=product, quantity=1) basket.save() second_product = create_product(printable_gibberish(), shop=shop, supplier=supplier, default_price=single_product_price) rule = BasketTotalProductAmountCondition.objects.create(value="2") coupon = Coupon.objects.create(code="TEST", active=True) campaign = BasketCampaign.objects.create(active=True, shop=shop, name="test", public_name="test", coupon=coupon) campaign.conditions.add(rule) effect = FreeProductLine.objects.create(campaign=campaign) effect.products.add(second_product) basket.add_code(coupon.code) basket.uncache() final_lines = basket.get_final_lines() assert len(final_lines) == 2 line_types = [l.type for l in final_lines] assert OrderLineType.DISCOUNT not in line_types for line in basket.get_final_lines(): assert line.type == OrderLineType.PRODUCT if line.product != product: assert line.product == second_product
def test_admin_catalog_campaign_edit_view(rf, admin_user): shop = get_default_shop() view = CatalogCampaignEditView( request=apply_request_middleware(rf.get("/"), user=admin_user)) form_class = view.get_form_class() form_kwargs = view.get_form_kwargs() form = form_class(**form_kwargs) assert not form.is_bound data = get_form_data(form) data.update({ "shop": shop.pk, "name": "test", }) form = form_class(**dict(form_kwargs, data=data)) form.full_clean() assert "At least one effect must be defined." in form.errors["__all__"][0] data.update({"discount_amount_effect": "20"}) form = form_class(**dict(form_kwargs, data=data)) form.full_clean() # at least 1 rule is required assert "You must set at least one rule for this campaign" in form.errors[ "__all__"][0] supplier = get_default_supplier() product = create_product(printable_gibberish(), shop=shop, supplier=supplier, default_price="20") data.update({"product_filter": [product.id]}) form = form_class(**dict(form_kwargs, data=data)) form.full_clean() assert not form.errors data.update({ "start_datetime": datetime.datetime.now() + datetime.timedelta(days=1), "end_datetime": datetime.datetime.now() }) form = form_class(**dict(form_kwargs, data=data)) form.full_clean() assert "Campaign end date can't be before start date" in form.errors[ "__all__"][0] data.update({"start_datetime": None}) form = form_class(**dict(form_kwargs, data=data)) form.full_clean() assert not form.errors campaign = form.save() assert campaign.filters.count() == 1
def test_basket(rf, storage): StoredBasket.objects.all().delete() quantities = [3, 12, 44, 23, 65] shop = get_default_shop() get_default_payment_method( ) # Can't create baskets without payment methods supplier = get_default_supplier() products_and_quantities = [] for quantity in quantities: product = create_product(printable_gibberish(), shop=shop, supplier=supplier, default_price=50) products_and_quantities.append((product, quantity)) is_database = ( storage == "shoop.front.basket.storage:DatabaseBasketStorage") with override_settings(SHOOP_BASKET_STORAGE_CLASS_SPEC=storage): for product, q in products_and_quantities: request = rf.get("/") request.session = {} request.shop = shop apply_request_middleware(request) basket = get_basket(request) assert basket == request.basket assert basket.product_count == 0 line = basket.add_product(supplier=supplier, shop=shop, product=product, quantity=q) assert line.quantity == q assert basket.get_lines() assert basket.get_product_ids_and_quantities().get(product.pk) == q assert basket.product_count == q basket.save() delattr(request, "basket") basket = get_basket(request) assert basket.get_product_ids_and_quantities().get(product.pk) == q if is_database: product_ids = set( StoredBasket.objects.last().products.values_list( "id", flat=True)) assert product_ids == set([product.pk]) if is_database: stats = StoredBasket.objects.all().aggregate( n=Sum("product_count"), tfs=Sum("taxful_total_price_value"), tls=Sum("taxless_total_price_value"), ) assert stats["n"] == sum(quantities) if shop.prices_include_tax: assert stats["tfs"] == sum(quantities) * 50 else: assert stats["tls"] == sum(quantities) * 50 basket.finalize()
def test_multiple_campaigns_match_with_coupon(rf): request, shop, group = initialize_test(rf, False) price = shop.create_price basket = get_basket(request) supplier = get_default_supplier() # create a basket rule that requires atleast value of 200 rule = BasketTotalAmountCondition.objects.create(value="200") product_price = "200" discount1 = "10" discount2 = "20" product = create_product(printable_gibberish(), shop=shop, supplier=supplier, default_price=product_price) basket.add_product(supplier=supplier, shop=shop, product=product, quantity=1) campaign = BasketCampaign.objects.create(shop=shop, public_name="test", name="test", active=True) campaign.conditions.add(rule) campaign.save() BasketDiscountAmount.objects.create(discount_amount=discount1, campaign=campaign) dc = Coupon.objects.create(code="TEST", active=True) campaign2 = BasketCampaign.objects.create(shop=shop, public_name="test", name="test", coupon=dc, active=True) BasketDiscountAmount.objects.create(discount_amount=discount2, campaign=campaign2) basket.add_product(supplier=supplier, shop=shop, product=product, quantity=1) resp = handle_add_campaign_code(request, basket, dc.code) assert resp.get("ok") discount_lines_values = [ line.discount_amount for line in basket.get_final_lines() ] assert price(discount1) in discount_lines_values assert price(discount2) in discount_lines_values assert basket.total_price == (price(product_price) * basket.product_count - price(discount1) - price(discount2))
def test_shipment_weights_ship_all(): shop = get_default_shop() supplier = get_default_supplier() order = _get_order(shop, supplier) shipment = order.create_shipment_of_all_products(supplier=supplier) assert shipment.weight == sum([ _get_weight_from_product_data(product_data) for product_data in _get_product_data() ])
def test_partially_shipped_order_status(): shop = get_default_shop() supplier = get_default_supplier() order = _get_order(shop, supplier) assert order.can_edit() first_product_line = order.lines.exclude(product_id=None).first() assert first_product_line.quantity > 1 order.create_shipment({first_product_line.product: 1}, supplier=supplier) assert order.shipping_status == ShippingStatus.PARTIALLY_SHIPPED assert not order.can_edit()
def test_shipment_creation_without_supplier_and_shipment(): shop = get_default_shop() supplier = get_default_supplier() order = _get_order(shop, supplier) product_lines = order.lines.exclude(product_id=None) for line in product_lines: for i in range(0, int(line.quantity)): with pytest.raises(AssertionError): order.create_shipment({line.product: 1}) assert order.shipments.count() == 0