def process(self, order=None): """ Calculate the tax and return it """ if order: self.order = order else: order = self.order percent = config_value("TAX", "PERCENT") sub_total = Decimal("0.00") for item in order.orderitem_set.filter(product__taxable=True): sub_total += item.sub_total itemtax = sub_total * (percent / 100) taxrates = {"%i%%" % percent: itemtax} if config_value("TAX", "TAX_SHIPPING"): shipping = order.shipping_sub_total sub_total += shipping ship_tax = shipping * (percent / 100) taxrates["Shipping"] = ship_tax tax = sub_total * (percent / 100) return tax, taxrates
def _get_shipping_choices(request, paymentmodule, cart, contact, default_view_tax=False): """Iterate through legal shipping modules, building the list for display to the user. Returns the shipping choices list, along with a dictionary of shipping choices, useful for building javascript that operates on shipping choices. """ shipping_options = [] shipping_dict = {} if not cart.is_shippable: methods = [shipping_method_by_key("NoShipping")] else: methods = shipping_methods() valid_methods = [] for method in methods: method.calculate(cart, contact) if method.valid(): valid_methods.append((method, method.cost())) # sort methods by cost valid_methods.sort(key=lambda method_cost: int(method_cost[1])) for method, shipcost in valid_methods: template = lookup_template(paymentmodule, "shipping_options.html") t = loader.get_template(template) shipping_tax = None taxed_shipping_price = None if config_value("TAX", "TAX_SHIPPING"): shipping_tax = config_value("TAX", "TAX_CLASS") taxer = _get_taxprocessor(request) total = shipcost + taxer.by_price(shipping_tax, shipcost) currency_code = currency_for_request(request) taxed_shipping_price = money_format(total, currency_code) data = { "amount": shipcost, "description": method.description(), "method": method.method(), "expected_delivery": method.expectedDelivery(), "default_view_tax": default_view_tax, "shipping_tax": shipping_tax, "taxed_shipping_price": taxed_shipping_price, } if hasattr(method, "shipping_discount"): data["discount"] = method.shipping_discount() shipping_options.append((method.id, t.render(data))) shipping_dict[method.id] = shipcost return shipping_options, shipping_dict
def _get_shipping_choices( request, paymentmodule, cart, contact, default_view_tax=False ): """Iterate through legal shipping modules, building the list for display to the user. Returns the shipping choices list, along with a dictionary of shipping choices, useful for building javascript that operates on shipping choices. """ shipping_options = [] shipping_dict = {} if not cart.is_shippable: methods = [shipping_method_by_key("NoShipping")] else: methods = shipping_methods() valid_methods = [] for method in methods: method.calculate(cart, contact) if method.valid(): valid_methods.append((method, method.cost())) # sort methods by cost valid_methods.sort(key=lambda method_cost: int(method_cost[1])) for method, shipcost in valid_methods: template = lookup_template(paymentmodule, "shipping_options.html") t = loader.get_template(template) shipping_tax = None taxed_shipping_price = None if config_value("TAX", "TAX_SHIPPING"): shipping_tax = config_value("TAX", "TAX_CLASS") taxer = _get_taxprocessor(request) total = shipcost + taxer.by_price(shipping_tax, shipcost) currency_code = currency_for_request(request) taxed_shipping_price = money_format(total, currency_code) data = { "amount": shipcost, "description": method.description(), "method": method.method(), "expected_delivery": method.expectedDelivery(), "default_view_tax": default_view_tax, "shipping_tax": shipping_tax, "taxed_shipping_price": taxed_shipping_price, } if hasattr(method, "shipping_discount"): data["discount"] = method.shipping_discount() shipping_options.append((method.id, t.render(data))) shipping_dict[method.id] = shipcost return shipping_options, shipping_dict
def shipping(self, subtotal=None): if subtotal is None and self.order: subtotal = self.order.shipping_sub_total if subtotal: subtotal = self.order.shipping_sub_total if config_value("TAX", "TAX_SHIPPING"): percent = config_value("TAX", "PERCENT") t = subtotal * (percent / 100) else: t = Decimal("0.00") else: t = Decimal("0.00") return t
def make_urlpatterns(): patterns = [] for key in config_value("PAYMENT", "MODULES"): try: cfg = config_get(key, "MODULE") except SettingNotSet: log.warning("Could not find module %s, skipping", key) continue module_name = cfg.editor_value url_module = "%s.urls" % module_name namespace = module_name.split(".")[-1] patterns.append( path(config_value(key, "URL_BASE"), [url_module, module_name, namespace])) return patterns
def make_urlpatterns(): patterns = [] for key in config_value("PAYMENT", "MODULES"): try: cfg = config_get(key, "MODULE") except SettingNotSet: log.warning("Could not find module %s, skipping", key) continue module_name = cfg.editor_value url_module = "%s.urls" % module_name namespace = module_name.split(".")[-1] patterns.append( path(config_value(key, "URL_BASE"), [url_module, module_name, namespace]) ) return patterns
def testBalanceMethods(self): order = TestOrderFactory() order.recalculate_total(save=False) price = order.total subtotal = order.sub_total self.assertEqual(subtotal, Decimal("25.00")) self.assertEqual(price, Decimal("35.00")) self.assertEqual(order.balance, price) paytype = config_value("PAYMENT", "MODULES")[0] pmt = OrderPayment(order=order, payment=paytype, amount=Decimal("5.00")) pmt.save() self.assertEqual(order.balance, Decimal("30.00")) self.assertEqual(order.balance_paid, Decimal("5.00")) self.assertTrue(order.is_partially_paid) pmt = OrderPayment(order=order, payment=paytype, amount=Decimal("30.00")) pmt.save() self.assertEqual(order.balance, Decimal("0.00")) self.assertFalse(order.is_partially_paid) self.assertTrue(order.paid_in_full)
def payment_live(settings): try: if config_value("PAYMENT", "LIVE"): return settings["LIVE"].value except SettingNotSet: pass return False
def google_adwords_signup(context): """ Output signup info in the format that Google adwords needs. """ request = context["request"] try: request = context["request"] except KeyError: logger.warning( "Template satchmo.show.templatetags.google.google_adwords_sale couldn't get the request from the context. Are you missing the request context_processor?" ) return "" secure = request.is_secure() try: language_code = request.LANGUAGE_CODE except AttributeError: language_code = settings.LANGUAGE_CODE return { "GOOGLE_ADWORDS_ID": config_value("GOOGLE", "ADWORDS_ID"), "Store": settings.SITE_NAME, "value": 1, "label": "signup", "secure": secure, "language_code": language_code, }
def google_adwords_sale(context): """ Output our receipt in the format that Google Adwords needs. """ order = context["order"] try: request = context["request"] except KeyError: logging.warning( "Template satchmo.show.templatetags.google.google_adwords_sale couldn't get the request from the context. Are you missing the request context_processor?" ) return "" secure = request.is_secure() try: language_code = request.LANGUAGE_CODE except KeyError: language_code = settings.LANGUAGE_CODE return { "GOOGLE_ADWORDS_ID": config_value("GOOGLE", "ADWORDS_ID"), "Store": settings.SITE_NAME, "value": order.total, "label": "purchase", "secure": secure, "language_code": language_code, }
def cartitem_total(parser, token): """Returns the line total for the cartitem, possibly with tax added. If currency evaluates true, then return the total formatted through money_format. Example:: {% cartitem_total cartitem [show_tax] [currency] %} """ tokens = token.contents.split() if len(tokens) < 2: raise template.TemplateSyntaxError( "'%s' tag requires a cartitem argument" % tokens[0] ) cartitem = tokens[1] if len(tokens) > 2: show_tax = tokens[2] else: show_tax = config_value("TAX", "DEFAULT_VIEW_TAX") if len(tokens) > 3: show_currency = tokens[3] else: show_currency = "True" return CartitemTotalNode(cartitem, show_currency, show_tax)
def __init__(self, request, payment_module): self.request = request self.paymentModule = payment_module processor_module = payment_module.MODULE.load_module("processor") self.processor = processor_module.PaymentProcessor(self.paymentModule) self.viewTax = config_value("TAX", "DEFAULT_VIEW_TAX") self.order = None self.cart = None self.extra_context = {} # To override the form_handler, set this # otherwise it will use the built-in `_onForm` self.onForm = self._onForm # To override the success method, set this # othewise it will use the built-in `_onSuccess` self.onSuccess = self._onSuccess # False on any "can not continue" error self.valid = False # The value to be returned from the view # an HttpResponse or a HttpRedirect self.response = None self.processorMessage = "" self.processorReasonCode = "" self.processorResults = None self.templates = { "CONFIRM": "checkout/confirm.html", "EMPTY_CART": "checkout/empty_cart", "404": "shop_404.html", }
def edit_subtypes(product): output = "<ul>" subtypes = product.get_subtypes() for key in config_value("PRODUCT", "PRODUCT_TYPES"): app, subtype = key.split("::") is_config = "ConfigurableProduct" in subtypes if subtype in subtypes: output += ( '<li><a href="/admin/%s/%s/%s/">' % (app, subtype.lower(), product.pk) + _("Edit %(subtype)s") % {"subtype": subtype} + "</a></li>" ) if is_config or subtype == "ProductVariation": output += ( '<li><a href="/product/admin/%s/variations/">Variation Manager</a></li>' % (product.slug) ) else: if not (is_config and subtype == "ProductVariation"): output += ( ' <li><a href="/admin/%s/%s/add/?product=%s">' % (app, subtype.lower(), product.pk) + _("Add %(subtype)s") % {"subtype": subtype} + "</a></li>" ) output += "</ul>" return output
def cartitem_total(parser, token): """Returns the line total for the cartitem, possibly with tax added. If currency evaluates true, then return the total formatted through money_format. Example:: {% cartitem_total cartitem [show_tax] [currency] %} """ tokens = token.contents.split() if len(tokens) < 2: raise template.TemplateSyntaxError( "'%s' tag requires a cartitem argument" % tokens[0]) cartitem = tokens[1] if len(tokens) > 2: show_tax = tokens[2] else: show_tax = config_value("TAX", "DEFAULT_VIEW_TAX") if len(tokens) > 3: show_currency = tokens[3] else: show_currency = "True" return CartitemTotalNode(cartitem, show_currency, show_tax)
def displayDoc(request, id, doc): # Create the HttpResponse object with the appropriate PDF headers for an invoice or a packing slip order = get_object_or_404(Order, pk=id) shopDetails = Config.objects.get_current() filename_prefix = shopDetails.site.domain if doc == "invoice": filename = "%s-invoice.pdf" % filename_prefix template = "invoice.rml" elif doc == "packingslip": filename = "%s-packingslip.pdf" % filename_prefix template = "packing-slip.rml" elif doc == "shippinglabel": filename = "%s-shippinglabel.pdf" % filename_prefix template = "shipping-label.rml" else: return HttpResponseRedirect("/admin") response = HttpResponse(mimetype="application/pdf") response["Content-Disposition"] = "attachment; filename=%s" % filename icon_uri = config_value("SHOP", "LOGO_URI") t = loader.get_template(os.path.join("pdf", template)) c = Context( { "filename": filename, "iconURI": icon_uri, "shopDetails": shopDetails, "order": order, } ) pdf = trml2pdf.parseString(smart_str(t.render(c))) response.write(pdf) return response
def displayDoc(request, id, doc): # Create the HttpResponse object with the appropriate PDF headers # for an invoice or a packing slip order = get_object_or_404(Order, pk=id) shopDetails = Config.objects.get_current() filename_prefix = Site.objects.get_current().domain if doc == "invoice": filename = "%s-invoice.pdf" % filename_prefix template = "invoice.rml" elif doc == "packingslip": filename = "%s-packingslip.pdf" % filename_prefix template = "packing-slip.rml" elif doc == "shippinglabel": filename = "%s-shippinglabel.pdf" % filename_prefix template = "shipping-label.rml" else: return HttpResponseRedirect("/admin") response = HttpResponse(mimetype="application/pdf") response["Content-Disposition"] = "attachment; filename=%s" % filename icon_uri = config_value("SHOP", "LOGO_URI") t = loader.get_template(os.path.join("pdf", template)) c = Context( { "filename": filename, "iconURI": icon_uri, "shopDetails": shopDetails, "order": order, } ) pdf = trml2pdf.parseString(smart_str(t.render(c))) response.write(pdf) return response
def display_bestsellers(request, count=0, template="product/best_sellers.html"): """Display a list of the products which have sold the most""" if count == 0: count = config_value("SHOP", "NUM_PAGINATED") ctx = {"products": bestsellers(count)} return render(request, template, ctx)
def edit_subtypes(product): output = "<ul>" subtypes = product.get_subtypes() for key in config_value("PRODUCT", "PRODUCT_TYPES"): app, subtype = key.split("::") is_config = "ConfigurableProduct" in subtypes if subtype in subtypes: output += ('<li><a href="/admin/%s/%s/%s/">' % (app, subtype.lower(), product.pk) + _("Edit %(subtype)s") % { "subtype": subtype } + "</a></li>") if is_config or subtype == "ProductVariation": output += ( '<li><a href="/product/admin/%s/variations/">Variation Manager</a></li>' % (product.slug)) else: if not (is_config and subtype == "ProductVariation"): output += (' <li><a href="/admin/%s/%s/add/?product=%s">' % (app, subtype.lower(), product.pk) + _("Add %(subtype)s") % { "subtype": subtype } + "</a></li>") output += "</ul>" return output
def order_tracking(request, order_id): order = None try: contact = Contact.objects.from_request(request, create=False) try: order = Order.objects.get(id__exact=order_id, contact=contact) except Order.DoesNotExist: pass except Contact.DoesNotExist: contact = None if order is None: return bad_or_missing( request, _("The order you have requested doesn't exist, or you don't have access to it." ), ) ctx = { "default_view_tax": config_value("TAX", "DEFAULT_VIEW_TAX"), "contact": contact, "order": order, } return render(request, "shop/order_tracking.html", ctx)
def handle(self, *args, **options): for fulfilment_house in config_value("FULFILMENT", "MODULES"): if options["verbosity"] >= 2: self.stdout.write( "Fulfilling with {house}".format(house=fulfilment_house) ) logger.debug("Fulfilling with {house}".format(house=fulfilment_house)) api_module = "{fulfilment_house}.api".format( fulfilment_house=fulfilment_house ) module = importlib.import_module(api_module) for order in Order.objects.unfulfilled().order_by("time_stamp"): if module.send_order(order): if options["verbosity"] >= 2: self.stdout.write('Order fulfilment processed "%s"' % order) logger.info('Order fulfilment processed "%s"' % order) else: if options["verbosity"] >= 2: self.stdout.write( 'Order fulfilment not processed, Something went wrong. "%s"' % order ) logger.warning( 'Order fulfilment not processed, Something went wrong. "%s"' % order )
def order_tracking(request, order_id): order = None try: contact = Contact.objects.from_request(request, create=False) try: order = Order.objects.get(id__exact=order_id, contact=contact) except Order.DoesNotExist: pass except Contact.DoesNotExist: contact = None if order is None: return bad_or_missing( request, _( "The order you have requested doesn't exist, or you don't have access to it." ), ) ctx = { "default_view_tax": config_value("TAX", "DEFAULT_VIEW_TAX"), "contact": contact, "order": order, } return render(request, "shop/order_tracking.html", ctx)
def home(request, template="base_index.html"): # Display the category, its child categories, and its products. if request.method == "GET": currpage = request.GET.get("page", 1) else: currpage = 1 featured = display_featured() count = config_value("SHOP", "NUM_PAGINATED") paginator = Paginator(featured, count) is_paged = False page = None try: paginator.validate_number(currpage) except EmptyPage: return bad_or_missing(request, _("Invalid page number")) is_paged = paginator.num_pages > 1 page = paginator.page(currpage) ctx = { "all_products_list": page.object_list, "is_paginated": is_paged, "page_obj": page, "paginator": paginator, } return render(request, template, ctx)
def show_tracker(secure=False): """ Output the google tracker code. """ return { "GOOGLE_CODE": config_value("GOOGLE", "ANALYTICS_CODE"), "secure": secure }
def shipping_methods(): methods = [] modules = config_value("SHIPPING", "MODULES") logger.debug("Getting shipping methods: %s", modules) for m in modules: module = load_module(m) methods.extend(module.get_methods()) return methods
def credit_choices(settings=None, include_module_if_no_choices=False): choices = [] keys = [] for module in config_value("PAYMENT", "MODULES"): vals = config_choice_values(module, "CREDITCHOICES") for val in vals: key, label = val if key not in keys: keys.append(key) pair = (key, ugettext(label)) choices.append(pair) if include_module_if_no_choices and not vals: key = config_value(module, "KEY") label = config_value(module, "LABEL") pair = (key, ugettext(label)) choices.append(pair) return choices
def send_owner_order_notice(new_order, template="email/order/placed_notice.txt"): """Send an order confirmation mail to the owner. """ from satchmo.shop.models import Config if config_value("PAYMENT", "ORDER_EMAIL_OWNER"): shop_config = Config.objects.get_current() shop_email = shop_config.store_email t = loader.get_template(template) c = {"order": new_order, "shop_config": shop_config} subject = _("New order on {shop_name}").format( shop_name=shop_config.store_name) eddresses = [shop_email] more = config_value("PAYMENT", "ORDER_EMAIL_EXTRA") if more: more = [m.strip() for m in more.split(",")] for m in more: if m not in eddresses: eddresses.append(m) eddresses = [e for e in eddresses if e] if not eddresses: log.warn("No shop owner email specified, skipping owner_email") return try: body = t.render(c) message = EmailMessage(subject, body, shop_email, eddresses) message.send() except (SocketError, SMTPRecipientsRefused) as e: if settings.DEBUG: log.warn( "Ignoring email error, since you are running in DEBUG mode. Email was:\nTo:%s\nSubject: %s\n---\n%s", ",".join(eddresses), subject, body, ) else: log.fatal("Error sending mail: %s" % e) raise IOError( "Could not send email, please check to make sure your email settings are correct, and that you are not being blocked by your ISP." )
def display_featured(limit=None, random=None): """ Used by the index generic view to choose how the featured products are displayed. Items can be displayed randomly or all in order """ if random: random_display = random else: random_display = config_value("SHOP", "RANDOM_FEATURED") if limit: num_to_display = limit else: num_to_display = config_value("SHOP", "NUM_DISPLAY") q = Product.objects.featured_by_site().filter(items_in_stock__gt=0) if not random_display: return q[:num_to_display] else: return q.order_by("?")[:num_to_display]
def display_featured(limit=None, random=None): """ Used by the index generic view to choose how the featured products are displayed. Items can be displayed randomly or all in order """ if random: random_display = random else: random_display = config_value("SHOP", "RANDOM_FEATURED") if limit: num_to_display = limit else: num_to_display = config_value("SHOP", "NUM_DISPLAY") q = Product.objects.featured().filter(items_in_stock__gt=0) if not random_display: return q[:num_to_display] else: return q.order_by("?")[:num_to_display]
def testSmallPayment(self): order = TestOrderFactory() order.recalculate_total(save=False) paytype = config_value("PAYMENT", "MODULES")[0] pmt = OrderPayment(order=order, payment=paytype, amount=Decimal("0.000001")) pmt.save() self.assertTrue(order.is_partially_paid)
def cost(self): """ Complex calculations can be done here as long as the return value is a dollar figure """ assert self._calculated for cartitem in self.cart.cartitem_set.all(): if cartitem.product.is_shippable: return config_value("SHIPPING", "FLAT_RATE") return Decimal("0.00")
def test_product(self): # Test for an easily missed reversion. When you lookup a productvariation product then # you should get the page of the parent configurableproduct, but with the options for # that variation already selected response = self.client.get(prefix + "/product/neat-book-soft/") self.assertContains(response, 'option value="soft" selected="selected"') self.assertContains( response, smart_str("%s5.00" % config_value("CURRENCY", "CURRENCY")) )
def send_owner_order_notice(new_order, template="email/order/placed_notice.txt"): """Send an order confirmation mail to the owner. """ from satchmo.shop.models import Config if config_value("PAYMENT", "ORDER_EMAIL_OWNER"): shop_config = Config.objects.get_current() shop_email = shop_config.store_email t = loader.get_template(template) c = {"order": new_order, "shop_config": shop_config} subject = _("New order on {shop_name}").format(shop_name=shop_config.store_name) eddresses = [shop_email] more = config_value("PAYMENT", "ORDER_EMAIL_EXTRA") if more: more = [m.strip() for m in more.split(",")] for m in more: if m not in eddresses: eddresses.append(m) eddresses = [e for e in eddresses if e] if not eddresses: log.warn("No shop owner email specified, skipping owner_email") return try: body = t.render(c) message = EmailMessage(subject, body, shop_email, eddresses) message.send() except (SocketError, SMTPRecipientsRefused) as e: if settings.DEBUG: log.warn( "Ignoring email error, since you are running in DEBUG mode. Email was:\nTo:%s\nSubject: %s\n---\n%s", ",".join(eddresses), subject, body, ) else: log.fatal("Error sending mail: %s" % e) raise IOError( "Could not send email, please check to make sure your email settings are correct, and that you are not being blocked by your ISP." )
def test_product(self): # Test for an easily missed reversion. When you lookup a productvariation product then # you should get the page of the parent configurableproduct, but with the options for # that variation already selected response = self.client.get(prefix + "/product/neat-book-soft/") self.assertContains(response, 'option value="soft" selected="selected"') self.assertContains( response, smart_str("%s5.00" % config_value("CURRENCY", "CURRENCY")))
def cost(self): """ Complex calculations can be done here as long as the return value is a dollar figure """ fee = Decimal("0.00") rate = config_value("SHIPPING", "PER_RATE") for cartitem in self.cart.cartitem_set.all(): if cartitem.product.is_shippable: fee += rate * cartitem.quantity return fee
def labelled_payment_choices(): active_payment_modules = config_choice_values("PAYMENT", "MODULES", translate=True) choices = [] for module, module_name in active_payment_modules: label = config_value(module, "LABEL", default=module_name) choices.append((module, label)) signals.payment_choices.send(None, choices=choices) return choices
def confirm_info(request): "Create form to send to WorldPay" # Check that items are in stock cart = Cart.objects.from_request(request) if cart.not_enough_stock(): return HttpResponseRedirect(reverse("satchmo_cart")) try: order = Order.objects.from_request(request) except Order.DoesNotExist: order = None if not (order and order.validate(request)): context = {"message": _("Your order is no longer valid.")} return render(request, "shop_404.html", context) template = lookup_template(payment_module, "checkout/worldpay/confirm.html") live = payment_module.LIVE.value currency = order.currency.iso_4217_code inst_id = payment_module.INSTID.value default_view_tax = config_value("TAX", "DEFAULT_VIEW_TAX") if live: post_url = payment_module.CONNECTION.value else: post_url = payment_module.CONNECTION_TEST.value if payment_module.MD5.value > "": # Doing the MD5 Signature dance # Generating secret "secret;amount;currency;cartId" balance = trunc_decimal(order.balance, 2) signature = "%s:%s:%s:%s" % ( payment_module.MD5.value, balance, currency, order.id, ) MD5 = md5(signature).hexdigest() else: MD5 = False ctx = { "order": order, "inst_id": inst_id, "currency": currency, "post_url": post_url, "default_view_tax": default_view_tax, "PAYMENT_LIVE": live, "MD5": MD5, "session": request.session.session_key, } return render(request, template, ctx)
def testSimpleRequires(self): v = config_value("req1", "bool2") self.assertTrue(v) keys = [cfg.key for cfg in self.g1] self.assertEqual(keys, ["c2", "c3", "bool1", "bool2"]) c = config_get("req1", "bool1") c.update(True) keys = [cfg.key for cfg in self.g1] self.assertEqual(keys, ["c1", "c2", "c3", "bool1", "bool2"])
def testRequiresSingleValue(self): v = config_value("SHOP", "valchoices2") self.assertEqual(v, "a") keys = [cfg.key for cfg in self.g2] self.assertEqual(keys, ["c1"]) c = config_get("SHOP", "valchoices2") c.update("b") keys = [cfg.key for cfg in self.g2] self.assertEqual(keys, ["c2"])
def convert_to_currency(value, currency_code, ignore_buffer=False): """Convert a Decimal value using the current exchange rate for the supplied currency_code""" if value is None or value == Decimal("0.00"): return Decimal("0.00") currency = Currency.objects.get_primary() if currency_code is not None and currency_code != currency.iso_4217_code: try: currency = Currency.objects.accepted().get(iso_4217_code=currency_code) except Currency.DoesNotExist: pass try: exchange_rate = currency.exchange_rates.latest().rate except ExchangeRate.DoesNotExist: exchange_rate = Decimal("1.00") # Add a small buffer if value and ignore_buffer is False: buffer = config_value("CURRENCY", "BUFFER") value = value + buffer # Multiply by the exchange rate value = value * exchange_rate # Quantize value using bankers rounding value = value.quantize(Decimal(".01"), rounding=ROUND_HALF_EVEN) # Round up to the nearest half unit of currency if config_value("CURRENCY", "ROUND_UP"): if value % 1 > Decimal("0.5"): value = Decimal(math.ceil(value)) else: value = Decimal(math.floor(value)) + Decimal("0.5") # Take away 1 minor unit of currency if config_value("CURRENCY", "PSYCHOLOGICAL_PRICING") and value == math.ceil(value): value = value - Decimal("0.01") return value
def shipping(self, subtotal=None): if subtotal is None and self.order: subtotal = self.order.shipping_sub_total if subtotal: rate = None if config_value("TAX", "TAX_SHIPPING"): try: tc = TaxClass.objects.get(title=config_value("TAX", "TAX_CLASS")) rate = self.get_rate(taxclass=tc) except: log.error("'Shipping' TaxClass doesn't exist.") if rate: t = rate * subtotal else: t = Decimal("0.00") else: t = Decimal("0.00") return t
def test_downloadable_zero_shipping(self): subtypes = config_value("PRODUCT", "PRODUCT_TYPES") if "product::DownloadableProduct" in subtypes: subtype2 = DownloadableProduct.objects.create(product=self.product1) self.assertEqual( self.product1.get_subtypes(), ("ConfigurableProduct", "DownloadableProduct"), ) self.assertFalse(subtype2.is_shippable) self.assertFalse(self.product1.is_shippable) self.assertFalse(self.cart1.is_shippable) self.assertEqual(flat(self.cart1, None).cost(), Decimal("0.00")) self.assertEqual(per(self.cart1, None).cost(), Decimal("0.00"))