def _process_line_quantity_and_price(self, source, sline, sl_kwargs): quantity_val = sline.pop("quantity", None) try: sl_kwargs["quantity"] = parse_decimal_string(quantity_val) except Exception as exc: msg = _( "The quantity '%(quantity)s' (for line %(text)s) is invalid (%(error)s)." ) % { "text": sl_kwargs["text"], "quantity": quantity_val, "error": exc, } self.add_error(ValidationError(msg, code="invalid_quantity")) return False is_product = bool(sline.get("type") == "product") price_val = sline.pop("baseUnitPrice", None) if is_product else sline.pop( "unitPrice", None) try: sl_kwargs["base_unit_price"] = source.create_price( parse_decimal_string(price_val)) except Exception as exc: msg = _( "The price '%(price)s' (for line %(text)s) is invalid (%(error)s)." ) % { "text": sl_kwargs["text"], "price": price_val, "error": exc, } self.add_error(ValidationError(msg, code="invalid_price")) return False discount_val = sline.pop("discountAmount", parse_decimal_string(str("0.00"))) try: sl_kwargs["discount_amount"] = source.create_price( parse_decimal_string(discount_val)) except Exception as exc: msg = _( "The discount '%(discount)s' (for line %(text)s is invalid (%(error)s)." ) % { "discount": discount_val, "text": sl_kwargs["text"], "error": exc, } self.add_error(ValidationError(msg, code="invalid_discount")) return True
def update_quantity(self, line, value, **kwargs): new_quantity = int(parse_decimal_string(value)) # TODO: The quantity could be a non-integral value if new_quantity is None: return False if not (line and line["quantity"] != new_quantity): return False changed = False # Ensure sub-lines also get changed accordingly linked_lines = [line] + list(self.basket.find_lines_by_parent_line_id(line["line_id"])) for linked_line in linked_lines: errors = list(self._get_orderability_errors(linked_line, new_quantity)) if errors: for error in errors: error_texts = ", ".join(six.text_type(sub_error) for sub_error in error) message = u"%s: %s" % (linked_line.get("text") or linked_line.get("name"), error_texts) messages.warning(self.request, message) continue self.basket.update_line(linked_line, quantity=new_quantity) linked_line["quantity"] = new_quantity changed = True return changed
def best_selling(self, request): """ Returns the top 20 (default) best selling products. To change the number of products, set the `limit` query param. """ limit = int(parse_decimal_string(request.query_params.get("limit", 20))) best_selling_products = get_best_selling_product_info( shop_ids=[request.shop.pk]) combined_variation_products = defaultdict(int) for product_id, parent_id, qty in best_selling_products: if parent_id: combined_variation_products[parent_id] += qty else: combined_variation_products[product_id] += qty # take here the top `limit` records, because the filter_queryset below can mess with our work product_ids = [ d[0] for d in sorted(six.iteritems(combined_variation_products), key=lambda i: i[1], reverse=True)[:limit] ] products_qs = Product.objects.filter(id__in=product_ids) products_qs = self.filter_queryset(products_qs).distinct() serializer = ProductSerializer(products_qs, many=True, context={"request": request}) return Response(serializer.data)
def __init__(self, id, value, title, currency, **kwargs): self.currency = currency value = parse_decimal_string(value) value = format_currency(value, currency=self.currency, locale=get_current_babel_locale()) super(DashboardMoneyBlock, self).__init__(id, value, title, **kwargs)
def get_stocks(self, product): stocks = [] supplier_qs = Supplier.objects.filter(shop_products__product=product).distinct() # filtered by supplier supplier_id = int(parse_decimal_string(self.context["request"].query_params.get("supplier", 0))) if supplier_id: supplier_qs = supplier_qs.filter(pk=supplier_id) for supplier in supplier_qs: stock_status = supplier.get_stock_status(product.id) stocks.append({ "id": supplier.id, "name": supplier.name, "type": supplier.type, "logical_count": stock_status.logical_count, "physical_count": stock_status.physical_count, "message": stock_status.message, "error": stock_status.error }) return { "product": product.id, "sku": product.sku, "stocks": stocks }
def _set_numeric_value(self, new_value): if self.attribute.type == AttributeType.BOOLEAN and new_value is None: """ Shuup uses `django.forms.fields.NullBooleanField` in admin. Which can read in the `None` value. Note: This is being handled separately due backwards compatibility. TODO (2.0): Boolean should not be a special case and handling `None` should be same for every "numeric" value. """ self.numeric_value = None self.datetime_value = None self.untranslated_string_value = "" return if isinstance(new_value, datetime.timedelta): value = new_value.total_seconds() if value == int(value): value = int(value) else: value = parse_decimal_string(new_value or 0) if self.attribute.type == AttributeType.INTEGER: value = int(value) if self.attribute.type == AttributeType.BOOLEAN: value = int(bool(value)) self.numeric_value = value self.datetime_value = None self.untranslated_string_value = str(self.numeric_value) return
def get_stocks(self, product): stocks = [] supplier_qs = Supplier.objects.enabled().filter(shop_products__product=product).distinct() # filtered by supplier supplier_id = int(parse_decimal_string(self.context["request"].query_params.get("supplier", 0))) if supplier_id: supplier_qs = supplier_qs.filter(pk=supplier_id) for supplier in supplier_qs: stock_status = supplier.get_stock_status(product.id) stocks.append({ "id": supplier.id, "name": supplier.name, "type": supplier.type, "logical_count": stock_status.logical_count, "physical_count": stock_status.physical_count, "message": stock_status.message, "error": stock_status.error }) return { "product": product.id, "sku": product.sku, "stocks": stocks }
def stock(self, request, pk=None): """ Retrieve or Update the current stocks of the Supplier. You can filter the stocks through `product` and `sku` parameters. """ if request.method == 'POST': return self._adjust_stock(request, pk) supplier = self.get_object() products_qs = Product.objects.all_except_deleted().filter(shop_products__suppliers=supplier) # filter by id product_id = int(parse_decimal_string(request.query_params.get("product", 0))) if product_id: products_qs = products_qs.filter(pk=product_id) # filter by sku product_sku = request.query_params.get("sku") if product_sku: products_qs = products_qs.filter(sku=product_sku) page = self.paginate_queryset(products_qs) context = {'request': request, 'supplier': supplier} serializer = ProductStockSerializer((page or products_qs), many=True, context=context) return Response(serializer.data)
def test_parse_decimal_string_with_dirty_input(): assert parse_decimal_string('1e12') == Decimal(112) assert parse_decimal_string('foo1bar 1x2') == Decimal(112) assert parse_decimal_string('4a bc2def 8g.h5') == Decimal('428.5') assert parse_decimal_string(float('inf')) == Decimal('inf') assert parse_decimal_string(float('-inf')) == Decimal('-inf') assert str(parse_decimal_string(float('nan'))) == str(Decimal('nan')) assert parse_decimal_string('') == Decimal(0) assert parse_decimal_string(' ') == Decimal(0)
def test_parse_decimal_string_with_dirty_input(): assert parse_decimal_string("1e12") == Decimal(112) assert parse_decimal_string("foo1bar 1x2") == Decimal(112) assert parse_decimal_string("4a bc2def 8g.h5") == Decimal("428.5") assert parse_decimal_string(float("inf")) == Decimal("inf") assert parse_decimal_string(float("-inf")) == Decimal("-inf") assert str(parse_decimal_string(float("nan"))) == str(Decimal("nan")) assert parse_decimal_string("") == Decimal(0) assert parse_decimal_string(" ") == Decimal(0)
def newest(self, request): """ Returns the top 20 (default) new products. To change the number of products, set the `limit` query param. """ limit = int(parse_decimal_string(request.query_params.get("limit", 20))) product_qs = self.filter_queryset(self.get_queryset()).order_by("-id").distinct() serializer = ProductSerializer(product_qs[:limit], many=True, context={"request": request}) return Response(serializer.data)
def _initialize_product_line_data(self, product, supplier, shop, quantity=0): if product.variation_children.filter(deleted=False).exists(): raise ValueError("Error! Add a variation parent to the basket is not allowed.") return { "line_id": uuid4().hex, "product": product, "supplier": supplier, "shop": shop, "quantity": parse_decimal_string(quantity), }
def _initialize_product_line_data(product, supplier, shop, quantity=0): if product.variation_children.count(): raise ValueError("Attempting to add variation parent to basket") return { "line_id": uuid.uuid4().int, "product": product, "supplier": supplier, "shop": shop, "quantity": parse_decimal_string(quantity), }
def _initialize_product_line_data(self, product, supplier, shop, quantity=0): if product.variation_children.count(): raise ValueError("Attempting to add variation parent to basket") return { # TODO: FIXME: Make sure line_id's are unique (not random) "line_id": str(random.randint(0, 0x7FFFFFFF)), "product": product, "supplier": supplier, "shop": shop, "quantity": parse_decimal_string(quantity), }
def _import_product(self, sku, data): # noqa product = create_from_datum(Product, sku, data, self.i18n_props, identifier_field="sku") price = parse_decimal_string(data.get("price", "0.00")) if not product: return assert isinstance(product, Product) product.type = self.product_type product.tax_class = self.tax_class product.sales_unit = self.sales_unit product.full_clean() product.save() try: shop_product = product.get_shop_instance(self.shop) except ShopProduct.DoesNotExist: shop_product = ShopProduct.objects.create(product=product, shop=self.shop, default_price_value=price) shop_product.suppliers.add(self.supplier) for limiter_name in ("limit_shipping_methods", "limit_payment_methods"): limiter_val = data.get(limiter_name, ()) m2m_field = getattr(shop_product, limiter_name.replace("limit_", "")) if limiter_val: setattr(shop_product, limiter_name, True) for identifier in limiter_val: m2m_field.add(m2m_field.model.objects.get(identifier=identifier)) else: setattr(shop_product, limiter_name, False) m2m_field.clear() image_name = data.get("image") if image_name and self.include_images: self._attach_image_from_name(product, image_name, shop_product) category_identifier = data.get("category_identifier") if category_identifier: self._attach_category(product, shop_product, category_identifier, as_primary_category=True) additional_category_identifier = data.get("additional_category_identifier") if additional_category_identifier: self._attach_category(product, shop_product, additional_category_identifier) additional_category_identifiers = data.get("additional_category_identifiers") if additional_category_identifiers: for additional_category_identifier in additional_category_identifiers.split(","): self._attach_category(product, shop_product, additional_category_identifier) manufacturer_identifier = data.get("manufacturer_identifier") if manufacturer_identifier: self._attach_manufacturer(product, self.shop, manufacturer_identifier) shop_product.save() product.save()
def newest(self, request): """ Returns the top 20 (default) new products. To change the number of products, set the `limit` query param. """ limit = int(parse_decimal_string(request.query_params.get("limit", 20))) product_qs = self.filter_queryset( self.get_queryset()).order_by("-id").distinct() serializer = ProductSerializer(product_qs[:limit], many=True, context={"request": request}) return Response(serializer.data)
def update_display_quantity(self, line, value, **kwargs): if not line: return False new_display_quantity = parse_decimal_string(value) if new_display_quantity is None: return False basket_line = self.basket.get_basket_line(line['line_id']) if basket_line and basket_line.product: unit = basket_line.shop_product.unit new_quantity = unit.from_display(new_display_quantity) else: new_quantity = new_display_quantity return self._update_quantity(line, new_quantity)
def _process_line_quantity_and_price(self, source, sline, sl_kwargs): quantity_val = sline.pop("quantity", None) try: sl_kwargs["quantity"] = parse_decimal_string(quantity_val) except Exception as exc: msg = _("The quantity '%(quantity)s' (for line %(text)s) is invalid (%(error)s)") % { "text": sl_kwargs["text"], "quantity": quantity_val, "error": exc, } self.add_error(ValidationError(msg, code="invalid_quantity")) return False is_product = bool(sline.get("type") == "product") price_val = sline.pop("baseUnitPrice", None) if is_product else sline.pop("unitPrice", None) try: sl_kwargs["base_unit_price"] = source.create_price(parse_decimal_string(price_val)) except Exception as exc: msg = _("The price '%(price)s' (for line %(text)s) is invalid (%(error)s)") % { "text": sl_kwargs["text"], "price": price_val, "error": exc } self.add_error(ValidationError(msg, code="invalid_price")) return False discount_val = sline.pop("discountAmount", parse_decimal_string(str("0.00"))) try: sl_kwargs["discount_amount"] = source.create_price(parse_decimal_string(discount_val)) except Exception as exc: msg = _("The discount '%(discount)s' (for line %(text)s is invalid (%(error)s)") % { "discount": discount_val, "text": sl_kwargs["text"], "error": exc } self.add_error(ValidationError(msg, code="invalid_discount")) return True
def update_quantity(self, line, value, **kwargs): new_quantity = int(parse_decimal_string( value)) # TODO: The quantity could be a non-integral value if new_quantity is None: return False if not (line and line["quantity"] != new_quantity): return False changed = False # Ensure sub-lines also get changed accordingly linked_lines = [line] + list( self.basket.find_lines_by_parent_line_id(line["line_id"])) orderable_line_ids = [ basket_line.line_id for basket_line in self.basket.get_lines() ] for linked_line in linked_lines: if linked_line["line_id"] not in orderable_line_ids: # Customer can change quantity in non-orderable lines regardless linked_line["quantity"] = new_quantity changed = True else: product = Product.objects.get(pk=linked_line["product_id"]) supplier = Supplier.objects.filter( pk=linked_line.get("supplier_id", 0)).first() # Basket quantities already contain current quantities for orderable lines quantity_delta = new_quantity - line["quantity"] errors = self._get_orderability_errors(product, supplier, quantity_delta) if errors: for error in errors: error_texts = ", ".join( six.text_type(sub_error) for sub_error in error) message = u"%s: %s" % (linked_line.get("text") or linked_line.get("name"), error_texts) messages.warning(self.request, message) continue self.basket.update_line(linked_line, quantity=new_quantity) linked_line["quantity"] = new_quantity changed = True return changed
def _set_numeric_value(self, new_value): if isinstance(new_value, datetime.timedelta): value = new_value.total_seconds() if value == int(value): value = int(value) else: value = parse_decimal_string(new_value or 0) if self.attribute.type == AttributeType.INTEGER: value = int(value) if self.attribute.type == AttributeType.BOOLEAN: value = int(bool(value)) self.numeric_value = value self.datetime_value = None self.untranslated_string_value = str(self.numeric_value) return
def update_quantity(self, line, value, **kwargs): new_quantity = int(parse_decimal_string(value)) # TODO: The quantity could be a non-integral value if new_quantity is None: return False if not (line and line["quantity"] != new_quantity): return False changed = False # Ensure sub-lines also get changed accordingly linked_lines = [line] + list(self.basket.find_lines_by_parent_line_id(line["line_id"])) orderable_line_ids = [basket_line.line_id for basket_line in self.basket.get_lines()] for linked_line in linked_lines: if linked_line["line_id"] not in orderable_line_ids: # Customer can change quantity in non-orderable lines regardless linked_line["quantity"] = new_quantity changed = True else: product = Product.objects.get(pk=linked_line["product_id"]) supplier = Supplier.objects.filter(pk=linked_line.get("supplier_id", 0)).first() # Basket quantities already contain current quantities for orderable lines quantity_delta = new_quantity - line["quantity"] errors = self._get_orderability_errors(product, supplier, quantity_delta) if errors: for error in errors: error_texts = ", ".join(six.text_type(sub_error) for sub_error in error) message = u"%s: %s" % (linked_line.get("text") or linked_line.get("name"), error_texts) messages.warning(self.request, message) continue self.basket.update_line(linked_line, quantity=new_quantity) linked_line["quantity"] = new_quantity changed = True return changed
def best_selling(self, request): """ Returns the top 20 (default) best selling products. To change the number of products, set the `limit` query param. """ limit = int(parse_decimal_string(request.query_params.get("limit", 20))) best_selling_products = get_best_selling_product_info(shop_ids=[request.shop.pk]) combined_variation_products = defaultdict(int) for product_id, parent_id, qty in best_selling_products: if parent_id: combined_variation_products[parent_id] += qty else: combined_variation_products[product_id] += qty # take here the top `limit` records, because the filter_queryset below can mess with our work product_ids = [ d[0] for d in sorted(six.iteritems(combined_variation_products), key=lambda i: i[1], reverse=True)[:limit] ] products_qs = Product.objects.filter(id__in=product_ids) products_qs = self.filter_queryset(products_qs).distinct() serializer = ProductSerializer(products_qs, many=True, context={"request": request}) return Response(serializer.data)
def test_parse_decimal_string_with_unaccepted_input(value): with pytest.raises(decimal.InvalidOperation): parse_decimal_string(value)
def test_parse_decimal_string_with_normal_input(): assert parse_decimal_string("42") == Decimal(42) assert parse_decimal_string("0") == Decimal(0) assert parse_decimal_string(3.5) == Decimal("3.5") assert parse_decimal_string(-5) == Decimal(-5) assert parse_decimal_string("-5") == Decimal(-5)
def update_quantity(self, line, value, **kwargs): new_quantity = parse_decimal_string(value) if new_quantity is None: return False return self._update_quantity(line, new_quantity)
def handle_add(request, basket, product_id, quantity=1, supplier_id=None, **kwargs): # noqa (C901) """ Handle adding a product to the basket. :param product_id: product ID to add (or if `child_product_id` is truey, the parent ID) :param quantity: quantity of products to add :param child_product_id: child product ID to add (if truey) :param supplier_id: The supplier ID for the new line. If None, the first supplier is used. """ product_id = int(product_id) product = get_object_or_404(Product, pk=product_id) shop_product = product.get_shop_instance(shop=request.shop) if not shop_product: raise ValidationError("Product not available in this shop", code="product_not_available_in_shop") if supplier_id: supplier = shop_product.suppliers.filter(pk=supplier_id).first() else: supplier = shop_product.suppliers.first() if not supplier: raise ValidationError("Invalid supplier", code="invalid_supplier") try: quantity = parse_decimal_string(quantity) if not product.sales_unit.allow_fractions: if quantity % 1 != 0: msg = _("The quantity %f is not allowed. " "Please use an integer value.") % quantity raise ValidationError(msg, code="invalid_quantity") quantity = int(quantity) except (ValueError, decimal.InvalidOperation): raise ValidationError(_(u"The quantity %s is not valid.") % quantity, code="invalid_quantity") if quantity <= 0: raise ValidationError(_(u"The quantity %s is not valid.") % quantity, code="invalid_quantity") product_ids_and_quantities = basket.get_product_ids_and_quantities() already_in_basket_qty = product_ids_and_quantities.get(product.id, 0) shop_product.raise_if_not_orderable(supplier=supplier, quantity=(already_in_basket_qty + quantity), customer=basket.customer) # If the product is a package parent, also check child products if product.is_package_parent(): for child_product, child_quantity in six.iteritems( product.get_package_child_to_quantity_map()): already_in_basket_qty = product_ids_and_quantities.get( child_product.id, 0) total_child_quantity = (quantity * child_quantity) sp = child_product.get_shop_instance(shop=request.shop) sp.raise_if_not_orderable(supplier=supplier, quantity=(already_in_basket_qty + total_child_quantity), customer=basket.customer) # TODO: Hook/extension point # if product.form: # return { # "error": u"Form required", # "return": reverse_GET("product-form", kwargs={"pk": product.pk}, GET={"n": quantity}) # } add_product_kwargs = { "product": product, "quantity": quantity, "supplier": supplier, "shop": request.shop, } basket.add_product(**add_product_kwargs) return {'ok': basket.product_count, 'added': quantity}
def test_parse_decimal_string_with_normal_input(): assert parse_decimal_string('42') == Decimal(42) assert parse_decimal_string('0') == Decimal(0) assert parse_decimal_string(3.5) == Decimal('3.5') assert parse_decimal_string(-5) == Decimal(-5) assert parse_decimal_string('-5') == Decimal(-5)
def test_parse_decimal_string_with_float_input(input_val, expected_val): result = parse_decimal_string(input_val) assert result == expected_val
def handle_add( # noqa (C901) request, basket, product_id, quantity=1, unit_type='internal', supplier_id=None, **kwargs): """ Handle adding a product to the basket. :param product_id: product ID to add (or if `child_product_id` is truey, the parent ID) :param quantity: quantity of products to add :param child_product_id: child product ID to add (if truey) :param supplier_id: The supplier ID for the new line. If None, the first supplier is used. """ product_id = int(product_id) product = get_object_or_404(Product, pk=product_id) if product.mode in (ProductMode.SIMPLE_VARIATION_PARENT, ProductMode.VARIABLE_VARIATION_PARENT): raise ValidationError("Invalid product", code="invalid_product") try: shop_product = product.get_shop_instance(shop=request.shop) except ShopProduct.DoesNotExist: raise ValidationError("Product not available in this shop", code="product_not_available_in_shop") if supplier_id: supplier = shop_product.suppliers.enabled().filter(pk=supplier_id).first() else: supplier = shop_product.get_supplier(basket.customer, quantity, basket.shipping_address) if not supplier: raise ValidationError("Invalid supplier", code="invalid_supplier") try: quantity = parse_decimal_string(quantity) if unit_type == 'display': quantity = shop_product.unit.from_display(quantity) if not product.sales_unit.allow_fractions: if quantity % 1 != 0: msg = _( "The quantity %f is not allowed. " "Please use an integer value.") % quantity raise ValidationError(msg, code="invalid_quantity") quantity = int(quantity) except (ValueError, decimal.InvalidOperation): raise ValidationError(_(u"The quantity %s is not valid.") % quantity, code="invalid_quantity") if quantity <= 0: raise ValidationError(_(u"The quantity %s is not valid.") % quantity, code="invalid_quantity") product_ids_and_quantities = basket.get_product_ids_and_quantities() already_in_basket_qty = product_ids_and_quantities.get(product.id, 0) shop_product.raise_if_not_orderable( supplier=supplier, quantity=(already_in_basket_qty + quantity), customer=basket.customer ) # If the product is a package parent, also check child products if product.is_package_parent(): for child_product, child_quantity in six.iteritems(product.get_package_child_to_quantity_map()): already_in_basket_qty = product_ids_and_quantities.get(child_product.id, 0) total_child_quantity = (quantity * child_quantity) try: sp = child_product.get_shop_instance(shop=request.shop) except ShopProduct.DoesNotExist: raise ProductNotOrderableProblem("%s not available in %s" % (child_product, request.shop)) sp.raise_if_not_orderable( supplier=supplier, quantity=(already_in_basket_qty + total_child_quantity), customer=basket.customer ) # TODO: Hook/extension point # if product.form: # return { # "error": u"Form required", # "return": reverse_GET("product-form", kwargs={"pk": product.pk}, GET={"n": quantity}) # } add_product_kwargs = { "product": product, "quantity": quantity, "supplier": supplier, "shop": request.shop, "force_new_line": kwargs.get("force_new_line", False), "extra": kwargs.get("extra"), "parent_line": kwargs.get("parent_line") } line = basket.add_product(**add_product_kwargs) return { 'ok': basket.smart_product_count, 'line_id': line.line_id, 'added': quantity }
def _import_product(self, sku, data): # noqa product = create_from_datum(Product, sku, data, self.i18n_props, identifier_field="sku") price = parse_decimal_string(data.get("price", "0.00")) if not product: return assert isinstance(product, Product) product.type = self.product_type product.tax_class = self.tax_class product.sales_unit = self.sales_unit product.full_clean() product.save() try: shop_product = product.get_shop_instance(self.shop) except ShopProduct.DoesNotExist: shop_product = ShopProduct.objects.create( product=product, shop=self.shop, default_price_value=price) shop_product.suppliers.add(self.supplier) for limiter_name in ("limit_shipping_methods", "limit_payment_methods"): limiter_val = data.get(limiter_name, ()) m2m_field = getattr(shop_product, limiter_name.replace("limit_", "")) if limiter_val: setattr(shop_product, limiter_name, True) for identifier in limiter_val: m2m_field.add( m2m_field.model.objects.get(identifier=identifier)) else: setattr(shop_product, limiter_name, False) m2m_field.clear() image_name = data.get("image") if image_name and self.include_images: self._attach_image_from_name(product, image_name, shop_product) additional_images = (data.get("images") or "") for additional_image_name in additional_images.split(","): if not additional_image_name or image_name == additional_image_name: continue self._attach_image_from_name(product, additional_image_name, shop_product) category_identifier = data.get("category_identifier") if category_identifier: self._attach_category(product, shop_product, category_identifier, as_primary_category=True) additional_category_identifier = data.get( "additional_category_identifier") if additional_category_identifier: self._attach_category(product, shop_product, additional_category_identifier) additional_category_identifiers = data.get( "additional_category_identifiers") if additional_category_identifiers: for additional_category_identifier in additional_category_identifiers.split( ","): self._attach_category(product, shop_product, additional_category_identifier) manufacturer_identifier = data.get("manufacturer_identifier") if manufacturer_identifier: self._attach_manufacturer(product, self.shop, manufacturer_identifier) attributes_data = data.get("attributes", {}) for attribute_identifier, value in attributes_data.items(): attribute = Attribute.objects.filter( identifier=attribute_identifier).first() if attribute: p_attribute, _ = ProductAttribute.objects.get_or_create( attribute=attribute, product=product) p_attribute.value = value p_attribute.save() variation_parent_sku = data.get("variation_parent_sku") variation_variable_values = data.get("variation_variable_value", "").split("-") if variation_parent_sku and variation_variable_values and len( variation_variable_values) == 2: parent_product = Product.objects.filter( sku=variation_parent_sku).first() if parent_product: color = variation_variable_values[0].strip() size = variation_variable_values[1].strip() product.link_to_parent(parent_product, variables={ "size": size, "color": color }) shop_product.save() product.save()
def handle_add(request, basket, product_id, quantity=1, supplier_id=None, **kwargs): # noqa (C901) """ Handle adding a product to the basket. :param product_id: product ID to add (or if `child_product_id` is truey, the parent ID) :param quantity: quantity of products to add :param child_product_id: child product ID to add (if truey) :param supplier_id: The supplier ID for the new line. If None, the first supplier is used. """ product_id = int(product_id) product = get_object_or_404(Product, pk=product_id) shop_product = product.get_shop_instance(shop=request.shop) if not shop_product: raise ValidationError("Product not available in this shop", code="product_not_available_in_shop") if supplier_id: supplier = shop_product.suppliers.filter(pk=supplier_id).first() else: supplier = shop_product.suppliers.first() if not supplier: raise ValidationError("Invalid supplier", code="invalid_supplier") try: quantity = parse_decimal_string(quantity) if not product.sales_unit.allow_fractions: if quantity % 1 != 0: msg = _( "The quantity %f is not allowed. " "Please use an integer value.") % quantity raise ValidationError(msg, code="invalid_quantity") quantity = int(quantity) except (ValueError, decimal.InvalidOperation): raise ValidationError(_(u"The quantity %s is not valid.") % quantity, code="invalid_quantity") if quantity <= 0: raise ValidationError(_(u"The quantity %s is not valid.") % quantity, code="invalid_quantity") product_ids_and_quantities = basket.get_product_ids_and_quantities() already_in_basket_qty = product_ids_and_quantities.get(product.id, 0) shop_product.raise_if_not_orderable( supplier=supplier, quantity=(already_in_basket_qty + quantity), customer=basket.customer ) # If the product is a package parent, also check child products if product.is_package_parent(): for child_product, child_quantity in six.iteritems(product.get_package_child_to_quantity_map()): already_in_basket_qty = product_ids_and_quantities.get(child_product.id, 0) total_child_quantity = (quantity * child_quantity) sp = child_product.get_shop_instance(shop=request.shop) sp.raise_if_not_orderable( supplier=supplier, quantity=(already_in_basket_qty + total_child_quantity), customer=basket.customer ) # TODO: Hook/extension point # if product.form: # return { # "error": u"Form required", # "return": reverse_GET("product-form", kwargs={"pk": product.pk}, GET={"n": quantity}) # } add_product_kwargs = { "product": product, "quantity": quantity, "supplier": supplier, "shop": request.shop, } basket.add_product(**add_product_kwargs) return { 'ok': basket.product_count, 'added': quantity }
def round(self, value): return bankers_round(parse_decimal_string(value), self.decimals)
def handle_add( # noqa (C901) request, basket, product_id, quantity=1, unit_type='internal', supplier_id=None, **kwargs): """ Handle adding a product to the basket. :param product_id: product ID to add (or if `child_product_id` is truey, the parent ID). :param quantity: quantity of products to add. :param child_product_id: child product ID to add (if truey). :param supplier_id: The supplier ID for the new line. If None, the first supplier is used. """ product_id = int(product_id) product = get_object_or_404(Product, pk=product_id) if product.mode in (ProductMode.SIMPLE_VARIATION_PARENT, ProductMode.VARIABLE_VARIATION_PARENT): raise ValidationError("Error! Invalid product.", code="invalid_product") try: shop_product = product.get_shop_instance(shop=request.shop) except ShopProduct.DoesNotExist: raise ValidationError("Error! Product is not available in this shop.", code="product_not_available_in_shop") if supplier_id: supplier = shop_product.suppliers.enabled( shop=shop_product.shop).filter(pk=supplier_id).first() else: supplier = shop_product.get_supplier(basket.customer, quantity, basket.shipping_address) if not supplier: raise ValidationError("Error! Invalid supplier.", code="invalid_supplier") try: quantity = parse_decimal_string(quantity) if unit_type == 'display': quantity = shop_product.unit.from_display(quantity) if not product.sales_unit.allow_fractions: if quantity % 1 != 0: msg = _("Error! The quantity `%f` is not allowed. " "Please use an integer value.") % quantity raise ValidationError(msg, code="invalid_quantity") quantity = int(quantity) except (ValueError, decimal.InvalidOperation): raise ValidationError(_(u"Error! The quantity `%s` is not valid.") % quantity, code="invalid_quantity") if quantity <= 0: raise ValidationError(_(u"Error! The quantity `%s` is not valid, " "should be bigger than zero.") % quantity, code="invalid_quantity") product_ids_and_quantities = basket.get_product_ids_and_quantities() already_in_basket_qty = product_ids_and_quantities.get(product.id, 0) shop_product.raise_if_not_orderable(supplier=supplier, quantity=(already_in_basket_qty + quantity), customer=basket.customer) # If the product is a package parent, also check child products if product.is_package_parent(): for child_product, child_quantity in six.iteritems( product.get_package_child_to_quantity_map()): already_in_basket_qty = product_ids_and_quantities.get( child_product.id, 0) total_child_quantity = (quantity * child_quantity) try: sp = child_product.get_shop_instance(shop=request.shop) except ShopProduct.DoesNotExist: raise ProductNotOrderableProblem( "Error! Product %s is not available in shop %s." % (child_product, request.shop)) sp.raise_if_not_orderable(supplier=supplier, quantity=(already_in_basket_qty + total_child_quantity), customer=basket.customer) # TODO: Hook/extension point # if product.form: # return { # "error": u"Form required", # "return": reverse_GET("product-form", kwargs={"pk": product.pk}, GET={"n": quantity}) # } add_product_kwargs = { "product": product, "quantity": quantity, "supplier": supplier, "shop": request.shop, "force_new_line": kwargs.get("force_new_line", False), "extra": kwargs.get("extra"), "parent_line": kwargs.get("parent_line") } line = basket.add_product(**add_product_kwargs) return { 'ok': basket.smart_product_count, 'line_id': line.line_id, 'added': quantity }
def __init__(self, id, value, title, **kwargs): value = parse_decimal_string(value) if int(value) == value: value = int(value) value = format_number(value, locale=get_current_babel_locale()) super(DashboardNumberBlock, self).__init__(id, value, title, **kwargs)
def post(self, request, **kwargs): """ A temporary basket is created and the product with quantity is added to it. Then the shipping methods can be calculated normally """ shipping_simulator = cached_load("SHIPPING_SIMULATOR_CLASS_SPEC")() shipping_form = shipping_simulator.get_form(data=request.POST) if shipping_form.is_valid(): # create temp basket tmp_basket = cached_load("SHUUP_BASKET_CLASS_SPEC")(request) # sets the shipping addres used to calculate the price and the delivery time tmp_basket.shipping_address = shipping_simulator.get_shipping_address( shipping_form) # from shuup/front/basket/commands.py:handle_add() # fetches the product object product_id = int(request.POST['product_id']) product = Product.objects.get(pk=product_id) shop_product = product.get_shop_instance(shop=request.shop) if not shop_product: logger.error( _("Product ID {0} not available in {1} shop").format( product_id, request.shop)) return HttpResponseBadRequest() if request.POST.get('supplier_id'): supplier = shop_product.suppliers.filter( pk=request.POST['supplier_id']).first() else: supplier = shop_product.suppliers.first() # validate and format the quantity try: quantity = parse_decimal_string(request.POST['quantity']) if not product.sales_unit.allow_fractions: if quantity % 1 != 0: logger.error( _("The quantity %f is not allowed. Please use an integer value." ) % quantity) return HttpResponseBadRequest() quantity = int(quantity) except (ValueError, decimal.InvalidOperation) as e: logger.exception(_("The quantity is not valid: {0}").format(e)) return HttpResponseBadRequest() if quantity <= 0: logger.error(_("The quantity %s is not valid.") % quantity) return HttpResponseBadRequest() # create the dict to use in add_product add_product_kwargs = { "product": product, "quantity": quantity, "supplier": supplier, "shop": request.shop, } tmp_basket.add_product(**add_product_kwargs) methods = get_shipping_methods_from(tmp_basket) return JsonResponse({"methods": methods}) return JsonResponse({})