def add_ticket(request, quantity=1, redirect_to='satchmo_cart'): formdata = request.POST.copy() details = [] form1 = SelectEventDateForm(request.POST) form1.fields['datetime'].queryset = EventDate.objects.all() if form1.is_valid(): datetime = form1.cleaned_data['datetime'] form2 = SelectPlaceForm(request.POST) form2.fields['seat'].queryset = datetime.event.hallscheme.seats.all() if form2.is_valid(): seat = form2.cleaned_data['seat'] ticket = Ticket.objects.get(seat=seat, datetime=datetime) cart = Cart.objects.from_request(request, create=True) satchmo_cart_details_query.send( cart, product=ticket.product, quantity=quantity, details=details, request=request, form=formdata ) try: added_item = cart.add_item(ticket.product, number_added=1, details=details) added_item.quantity = 1 added_item.save() except CartAddProhibited, cap: return _product_error(request, ticket.product, cap.message) # got to here with no error, now send a signal so that listeners can also operate on this form. satchmo_cart_add_complete.send(cart, cart=cart, cartitem=added_item, product=ticket.product, request=request, form=formdata) satchmo_cart_changed.send(cart, cart=cart, request=request) if request.is_ajax(): data = { 'id': ticket.product.id, 'slug': seat.slug, 'name': ticket.product.translated_name(), 'item_id': added_item.id, 'item_qty': str(round_decimal(quantity, 2)), 'item_price': str(added_item.line_total) or "0.00", 'cart_count': str(round_decimal(cart.numItems, 2)), 'cart_total': str(round_decimal(cart.total, 2)), # Legacy result, for now 'results': _("Success"), } return _json_response(data) else: return redirect(redirect_to) else: return _json_response({'errors': form2.errors, 'results': _("Error")}, True)
def _set_quantity(request, force_delete=False): """Set the quantity for a specific cartitem. Checks to make sure the item is actually in the user's cart. """ cart = Cart.objects.from_request(request, create=False) if isinstance(cart, NullCart): return (False, None, None, _("No cart to update.")) cartplaces = config_value('SHOP', 'CART_PRECISION') if decimal_too_big(request.POST.get('quantity', 0)): return (False,cart,None,_("Bad quantity.")) if force_delete: qty = Decimal('0') else: try: roundfactor = config_value('SHOP', 'CART_ROUNDING') qty = round_decimal(request.POST.get('quantity', 0), places=cartplaces, roundfactor=roundfactor, normalize=True) except RoundedDecimalError as P: return (False, cart, None, _("Bad quantity.")) if qty < Decimal('0'): qty = Decimal('0') try: itemid = int(request.POST.get('cartitem')) except (TypeError, ValueError): return (False, cart, None, _("Bad item number.")) try: cartitem = CartItem.objects.get(pk=itemid, cart=cart) except CartItem.DoesNotExist: return (False, cart, None, _("No such item in your cart.")) if qty == Decimal('0'): cartitem.delete() cartitem = NullCartItem(itemid) else: if config_value('PRODUCT','NO_STOCK_CHECKOUT') == False: stock = cartitem.product.items_in_stock log.debug('checking stock quantity. Have %d, need %d', stock, qty) if stock < qty: return (False, cart, cartitem, _("Unfortunately we only have %(stock)d '%(cartitem_name)s' in stock.") % {'stock': stock, 'cartitem_name': cartitem.product.translated_name()}) cartitem.quantity = round_decimal(qty, places=cartplaces) cartitem.save() satchmo_cart_changed.send(cart, cart=cart, request=request) return (True, cart, cartitem, "")
def _set_quantity(request, force_delete=False): """Set the quantity for a specific cartitem. Checks to make sure the item is actually in the user's cart. """ cart = Cart.objects.from_request(request, create=False) if isinstance(cart, NullCart): return (False, None, None, _("No cart to update.")) cartplaces = config_value('SHOP', 'CART_PRECISION') if decimal_too_big(request.POST.get('quantity', 0)): return (False, cart, None, _("Bad quantity.")) if force_delete: qty = Decimal('0') else: try: roundfactor = config_value('SHOP', 'CART_ROUNDING') qty = round_decimal(request.POST.get('quantity', 0), places=cartplaces, roundfactor=roundfactor, normalize=True) except RoundedDecimalError, P: return (False, cart, None, _("Bad quantity.")) if qty < Decimal('0'): qty = Decimal('0')
def _render_decimal(value, places=2, min_places=2): if value is not None: roundfactor = "0." + "0"*(places-1) + "1" if value < 0: roundfactor = "-" + roundfactor value = round_decimal(val=value, places=places, roundfactor=roundfactor, normalize=True) log.debug('value: %s' % type(value)) parts = ("%f" % value).split('.') n = parts[0] d = "" if len(parts) > 0: d = parts[1] elif min_places: d = "0" * min_places while len(d) < min_places: d = "%s0" % d while len(d) > min_places and d[-1] == '0': d = d[:-1] if len(d) > 0: value = "%s.%s" % (n, d) else: value = n return value
def get_price(request, product_slug): """Get base price for a product, returning the answer encoded as JSON.""" quantity = Decimal('1') try: product = Product.objects.get_by_site(active=True, slug=product_slug) except Product.DoesNotExist: return http.HttpResponseNotFound(json_encode(('', _("not available"))), mimetype="text/javascript") prod_slug = product.slug if request.method == "POST" and request.POST.has_key('quantity'): try: quantity = round_decimal(request.POST['quantity'], places=2, roundfactor=.25) except RoundedDecimalError: quantity = Decimal('1.0') log.warn("Could not parse a decimal from '%s', returning '1.0'", request.POST['quantity']) if 'ConfigurableProduct' in product.get_subtypes(): cp = product.configurableproduct chosen_options = optionids_from_post(cp, request.POST) pvp = cp.get_product_from_options(chosen_options) if not pvp: return http.HttpResponse(json_encode(('', _("not available"))), mimetype="text/javascript") prod_slug = pvp.slug price = moneyfmt(pvp.get_qty_price(quantity)) else: price = moneyfmt(product.get_qty_price(quantity)) if not price: return http.HttpResponse(json_encode(('', _("not available"))), mimetype="text/javascript") return http.HttpResponse(json_encode((prod_slug, price)), mimetype="text/javascript")
def add(request, id=0, redirect_to='satchmo_cart'): """Add an item to the cart.""" log.debug('FORM: %s', request.POST) formdata = request.POST.copy() productslug = None cartplaces = config_value('SHOP', 'CART_PRECISION') roundfactor = config_value('SHOP', 'CART_ROUNDING') if formdata.has_key('productname'): productslug = formdata['productname'] try: product, details = product_from_post(productslug, formdata) if not (product and product.active): log.debug("product %s is not active" % productslug) return bad_or_missing(request, _("That product is not available at the moment.")) else: log.debug("product %s is active" % productslug) except (Product.DoesNotExist, MultiValueDictKeyError): log.debug("Could not find product: %s", productslug) return bad_or_missing(request, _('The product you have requested does not exist.')) # First we validate that the number isn't too big. if decimal_too_big(formdata['quantity']): return _product_error(request, product, _("Please enter a smaller number.")) # Then we validate that we can round it appropriately. try: quantity = round_decimal(formdata['quantity'], places=cartplaces, roundfactor=roundfactor) except RoundedDecimalError, P: return _product_error(request, product, _("Invalid quantity."))
def _render_decimal(value, places=2, min_places=2): if value is not None: roundfactor = "0." + "0" * (places - 1) + "1" if value < 0: roundfactor = "-" + roundfactor value = round_decimal(val=value, places=places, roundfactor=roundfactor, normalize=True) log.debug('value: %s' % type(value)) parts = ("%f" % value).split('.') n = parts[0] d = "" if len(parts) > 0: d = parts[1] elif min_places: d = "0" * min_places while len(d) < min_places: d = "%s0" % d while len(d) > min_places and d[-1] == '0': d = d[:-1] if len(d) > 0: value = "%s.%s" % (n, d) else: value = n return value
def add(request, id=0, redirect_to='satchmo_cart'): """Add an item to the cart.""" log.debug('FORM: %s', request.POST) formdata = request.POST.copy() productslug = None cartplaces = config_value('SHOP', 'CART_PRECISION') roundfactor = config_value('SHOP', 'CART_ROUNDING') if formdata.has_key('productname'): productslug = formdata['productname'] try: product, details = product_from_post(productslug, formdata) if not (product and product.active): log.debug("product %s is not active" % productslug) return bad_or_missing( request, _("That product is not available at the moment.")) else: log.debug("product %s is active" % productslug) except (Product.DoesNotExist, MultiValueDictKeyError): log.debug("Could not find product: %s", productslug) return bad_or_missing( request, _('The product you have requested does not exist.')) # First we validate that the number isn't too big. if decimal_too_big(formdata['quantity']): return _product_error(request, product, _("Please enter a smaller number.")) # Then we validate that we can round it appropriately. try: quantity = round_decimal(formdata['quantity'], places=cartplaces, roundfactor=roundfactor) except RoundedDecimalError: return _product_error(request, product, _("Invalid quantity.")) if quantity <= Decimal('0'): return _product_error(request, product, _("Please enter a positive number.")) cart = Cart.objects.from_request(request, create=True) # send a signal so that listeners can update product details before we add it to the cart. satchmo_cart_details_query.send(cart, product=product, quantity=quantity, details=details, request=request, form=formdata) try: added_item = cart.add_item(product, number_added=quantity, details=details) except CartAddProhibited, cap: return _product_error(request, product, cap.message)
def add(request, id=0, redirect_to='satchmo_cart'): """Add an item to the cart.""" log.debug('FORM: %s', request.POST) formdata = request.POST.copy() productslug = None cartplaces = config_value('SHOP', 'CART_PRECISION') roundfactor = config_value('SHOP', 'CART_ROUNDING') if formdata.has_key('productname'): productslug = formdata['productname'] try: product, details = product_from_post(productslug, formdata) if not (product and product.active): log.debug("product %s is not active" % productslug) return bad_or_missing(request, _("That product is not available at the moment.")) else: log.debug("product %s is active" % productslug) except (Product.DoesNotExist, MultiValueDictKeyError): log.debug("Could not find product: %s", productslug) return bad_or_missing(request, _('The product you have requested does not exist.')) # First we validate that the number isn't too big. if decimal_too_big(formdata['quantity']): return _product_error(request, product, _("Please enter a smaller number.")) # Then we validate that we can round it appropriately. try: quantity = round_decimal(formdata['quantity'], places=cartplaces, roundfactor=roundfactor) except RoundedDecimalError: return _product_error(request, product, _("Invalid quantity.")) if quantity <= Decimal('0'): return _product_error(request, product, _("Please enter a positive number.")) cart = Cart.objects.from_request(request, create=True) # send a signal so that listeners can update product details before we add it to the cart. satchmo_cart_details_query.send( cart, product=product, quantity=quantity, details=details, request=request, form=formdata ) try: added_item = cart.add_item(product, number_added=quantity, details=details) except CartAddProhibited, cap: return _product_error(request, product, cap.message)
def set_quantity_ajax(request, template="shop/json.html"): """Set the quantity for a cart item, returning results formatted for handling by script. """ data = {} if not request.POST: data['results'] = False data['errors'] = _('Internal error: please submit as a POST') else: success, cart, cartitem, errors = _set_quantity(request) data['results'] = success data['errors'] = errors # note we have to convert Decimals to strings, since simplejson doesn't know about Decimals if cart: carttotal = str(round_decimal(cart.total, 2)) cartqty = cart.numItems else: carttotal = "0.00" cartqty = Decimal('0') data['cart_total'] = carttotal data['cart_count'] = cartqty if cartitem: itemid = cartitem.id itemqty = cartitem.quantity price = str(round_decimal(cartitem.line_total, 2)) else: itemid = -1 itemqty = Decimal('0') price = "0.00" data['item_id'] = itemid data['item_qty'] = itemqty data['item_price'] = price encoded = JSONEncoder().encode(data) encoded = mark_safe(encoded) return render_to_response(template, {'json': encoded})
def save(self, cart, request): log.debug('saving') self.full_clean() cartplaces = config_value('SHOP', 'CART_PRECISION') roundfactor = config_value('SHOP', 'CART_ROUNDING') site = Site.objects.get_current() for name, value in self.cleaned_data.items(): opt, key = name.split('__') log.debug('%s=%s', opt, key) quantity = 0 product = None if opt == 'qty': try: quantity = round_decimal(value, places=cartplaces, roundfactor=roundfactor) except RoundedDecimalError: quantity = 0 if not key in self.slugs: log.debug('caught attempt to add product not in the form: %s', key) else: try: product = Product.objects.get(slug=key, site=site) except Product.DoesNotExist: log.debug( 'caught attempt to add an non-existent product, ignoring: %s', key) if product and quantity > Decimal('0'): log.debug('Adding %s=%s to cart from MultipleProductForm', key, value) details = [] formdata = request.POST satchmo_cart_details_query.send(cart, product=product, quantity=quantity, details=details, request=request, form=formdata) added_item = cart.add_item(product, number_added=quantity, details=details) satchmo_cart_add_complete.send(cart, cart=cart, cartitem=added_item, product=product, request=request, form=formdata)
def normalize_decimal(value, args=""): """ PARTIAL UNIT ROUNDING DECIMAL Converts a valid float, integer, or string to a decimal number with a specified number of decimal places, performs "partial unit rounding", and decimal normalization. Usage: val|normalize_decimal val|normalize_decimal:'places=2' val|normalize_decimal:'places=2:roundfactor=.5' val|normalize_decimal:'places=2:roundfactor=.5:normalize=False' val The value to be converted and optionally formated to decimal. places The decimal place precision is defined by integer "places" and must be <= the precision defined in the decimal.Decimal context. roundfactor represents the maximum number of decimal places to display if normalize is False. roundfactor (partial unit rounding factor) If roundfactor is between 0 and 1, roundfactor rounds up (positive roundfactor value) or down (negative roundfactor value) in factional "roundfactor" increments. normalize If normalize is True (any value other than False), then rightmost zeros are truncated. General Filter/Template Usage. normalize_decimal is generally used without parameters in the template. Defaults are: places=2, roundfactor=None, normalize=True If normalize_decimal is not used as a template filter, the value (quantity) will display the full decimal value in the template field. """ if value == '' or value is None: return value args, kwargs = get_filter_args(args, keywords=('places', 'roundfactor', 'normalize'), intargs=('places', ), boolargs=('normalize', ), stripquotes=True) if not 'places' in kwargs: kwargs['places'] = 2 try: return mark_safe(str(round_decimal(val=value, **kwargs))) except RoundedDecimalError as e: log.error("normalize_decimal error val=%s, id-%s, msg=%s", (e.val, e.id, e.msg)) return value
def get_price_detail(request, product_slug): """Get all price details for a product, returning the response encoded as JSON.""" results = {"success": False, "message": _("not available")} price = None if request.method == "POST": reqdata = request.POST else: reqdata = request.GET try: product = Product.objects.get_by_site(active=True, slug=product_slug) found = True prod_slug = product.slug if 'quantity' in reqdata: try: quantity = round_decimal(reqdata['quantity'], places=2, roundfactor=.25) except RoundedDecimalError: quantity = Decimal('1.0') log.warn( "Could not parse a decimal from '%s', returning '1.0'", reqdata['quantity']) else: quantity = Decimal('1.0') if 'ConfigurableProduct' in product.get_subtypes(): cp = product.configurableproduct chosen_options = optionids_from_post(cp, reqdata) product = cp.get_product_from_options(chosen_options) if product: price = product.get_qty_price(quantity) results['slug'] = product.slug results['price'] = float(price) results['success'] = True results['message'] = "" except Product.DoesNotExist: found = False data = json_encode(results) if found: return http.HttpResponse(data, content_type="text/javascript") else: return http.HttpResponseNotFound(data, content_type="text/javascript")
def get_price_detail(request, product_slug): """Get all price details for a product, returning the response encoded as JSON.""" results = { "success" : False, "message" : _("not available") } price = None if request.method=="POST": reqdata = request.POST else: reqdata = request.GET try: product = Product.objects.get_by_site(active=True, slug=product_slug) found = True prod_slug = product.slug if reqdata.has_key('quantity'): try: quantity = round_decimal(reqdata['quantity'], places=2, roundfactor=.25) except RoundedDecimalError: quantity = Decimal('1.0') log.warn("Could not parse a decimal from '%s', returning '1.0'", reqdata['quantity']) else: quantity = Decimal('1.0') if 'ConfigurableProduct' in product.get_subtypes(): cp = product.configurableproduct chosen_options = optionids_from_post(cp, reqdata) product = cp.get_product_from_options(chosen_options) if product: price = product.get_qty_price(quantity) results['slug'] = product.slug results['price'] = float(price) results['success'] = True results['message'] = "" except Product.DoesNotExist: found = False data = json_encode(results) if found: return http.HttpResponse(data, mimetype="text/javascript") else: return http.HttpResponseNotFound(data, mimetype="text/javascript")
def normalize_decimal(value, args=""): """ PARTIAL UNIT ROUNDING DECIMAL Converts a valid float, integer, or string to a decimal number with a specified number of decimal places, performs "partial unit rounding", and decimal normalization. Usage: val|normalize_decimal val|normalize_decimal:'places=2' val|normalize_decimal:'places=2:roundfactor=.5' val|normalize_decimal:'places=2:roundfactor=.5:normalize=False' val The value to be converted and optionally formated to decimal. places The decimal place precision is defined by integer "places" and must be <= the precision defined in the decimal.Decimal context. roundfactor represents the maximum number of decimal places to display if normalize is False. roundfactor (partial unit rounding factor) If roundfactor is between 0 and 1, roundfactor rounds up (positive roundfactor value) or down (negative roundfactor value) in factional "roundfactor" increments. normalize If normalize is True (any value other than False), then rightmost zeros are truncated. General Filter/Template Usage. normalize_decimal is generally used without parameters in the template. Defaults are: places=2, roundfactor=None, normalize=True If normalize_decimal is not used as a template filter, the value (quantity) will display the full decimal value in the template field. """ if value == '' or value is None: return value args, kwargs = get_filter_args(args, keywords=('places','roundfactor', 'normalize'), intargs=('places',), boolargs=('normalize',), stripquotes=True) if not 'places' in kwargs: kwargs['places'] = 2 try: return mark_safe(str(round_decimal(val=value, **kwargs))) except RoundedDecimalError, e: log.error("normalize_decimal error val=%s, id-%s, msg=%s", (e.val, e.id, e.msg)) return value
def save(self, cart, request): log.debug('saving'); self.full_clean() cartplaces = config_value('SHOP', 'CART_PRECISION') roundfactor = config_value('SHOP', 'CART_ROUNDING') for name, value in self.cleaned_data.items(): opt, key = name.split('__') log.debug('%s=%s', opt, key) items = {} quantity = 0 product = None if opt=='qty': try: quantity = round_decimal(value, places=cartplaces, roundfactor=roundfactor) except RoundedDecimalError, P: quantity = 0 if not key in self.slugs: log.debug('caught attempt to add product not in the form: %s', key) else: try: product = Product.objects.get(slug=key) except Product.DoesNotExist: log.debug('caught attempt to add an non-existent product, ignoring: %s', key) if product and quantity > Decimal('0'): log.debug('Adding %s=%s to cart from MultipleProductForm', key, value) details = {} formdata = request.POST satchmo_cart_details_query.send( cart, product=product, quantity=quantity, details=details, request=request, form=formdata ) added_item = cart.add_item(product, number_added=quantity, details=details) satchmo_cart_add_complete.send(cart, cart=cart, cartitem=added_item, product=product, request=request, form=formdata)
def _set_quantity(request, force_delete=False): """Set the quantity for a specific cartitem. Checks to make sure the item is actually in the user's cart. """ cart = Cart.objects.from_request(request, create=False) if isinstance(cart, NullCart): return (False, None, None, _("No cart to update.")) cartplaces = config_value('SHOP', 'CART_PRECISION') if force_delete: qty = Decimal('0') else: try: roundfactor = config_value('SHOP', 'CART_ROUNDING') qty = round_decimal(request.POST.get('quantity', 0), places=cartplaces, roundfactor=roundfactor, normalize=True) except RoundedDecimalError, P: return (False, cart, None, _("Bad quantity.")) if qty < Decimal('0'): qty = Decimal('0')
def get_price(request, product_slug): """Get base price for a product, returning the answer encoded as JSON.""" quantity = Decimal('1') try: product = Product.objects.get_by_site(active=True, slug=product_slug) except Product.DoesNotExist: return http.HttpResponseNotFound(json_encode(('', _("not available"))), content_type="text/javascript") prod_slug = product.slug if request.method == "POST" and 'quantity' in request.POST: try: quantity = round_decimal(request.POST['quantity'], places=2, roundfactor=.25) except RoundedDecimalError: quantity = Decimal('1.0') log.warn("Could not parse a decimal from '%s', returning '1.0'", request.POST['quantity']) if 'ConfigurableProduct' in product.get_subtypes(): cp = product.configurableproduct chosen_options = optionids_from_post(cp, request.POST) pvp = cp.get_product_from_options(chosen_options) if not pvp: return http.HttpResponse(json_encode(('', _("not available"))), content_type="text/javascript") prod_slug = pvp.slug price = moneyfmt(pvp.get_qty_price(quantity)) else: price = moneyfmt(product.get_qty_price(quantity)) if not price: return http.HttpResponse(json_encode(('', _("not available"))), content_type="text/javascript") return http.HttpResponse(json_encode((prod_slug, price)), content_type="text/javascript")
def _render_decimal(value, places=2, min_places=2): # Check to make sure this is a Decimal before we try to round # and format. If it's not, just pass it on. # The admin validation will handle making sure only valid values get # saved. bad_decimal = False try: Decimal(value) except: bad_decimal = True if value is not None and not bad_decimal: roundfactor = "0." + "0" * (places - 1) + "1" if value < 0: roundfactor = "-" + roundfactor value = round_decimal(val=value, places=places, roundfactor=roundfactor, normalize=True) log.debug('value: %s' % type(value)) parts = ("%f" % value).split('.') n = parts[0] d = "" if len(parts) > 0: d = parts[1] elif min_places: d = "0" * min_places while len(d) < min_places: d = "%s0" % d while len(d) > min_places and d[-1] == '0': d = d[:-1] if len(d) > 0: value = "%s.%s" % (n, d) else: value = n return value
def add(request, id=0, redirect_to='satchmo_cart'): """Add an item to the cart.""" log.debug('FORM: %s', request.POST) formdata = request.POST.copy() productslug = None cartplaces = config_value('SHOP', 'CART_PRECISION') roundfactor = config_value('SHOP', 'CART_ROUNDING') if formdata.has_key('productname'): productslug = formdata['productname'] try: product, details = product_from_post(productslug, formdata) if not (product and product.active): log.debug("product %s is not active" % productslug) return bad_or_missing( request, _("That product is not available at the moment.")) else: log.debug("product %s is active" % productslug) except (Product.DoesNotExist, MultiValueDictKeyError): log.debug("Could not find product: %s", productslug) return bad_or_missing( request, _('The product you have requested does not exist.')) # First we validate that the number isn't too big. if decimal_too_big(formdata['quantity']): return _product_error(request, product, _("Please enter a smaller number.")) # Then we validate that we can round it appropriately. try: quantity = round_decimal(formdata['quantity'], places=cartplaces, roundfactor=roundfactor) except RoundedDecimalError, P: return _product_error(request, product, _("Invalid quantity."))
def _render_decimal(value, places=2, min_places=2): # Check to make sure this is a Decimal before we try to round # and format. If it's not, just pass it on. # The admin validation will handle making sure only valid values get # saved. bad_decimal = False try: Decimal(value) except: bad_decimal = True if value is not None and not bad_decimal: roundfactor = "0." + "0"*(places-1) + "1" if value < 0: roundfactor = "-" + roundfactor value = round_decimal(val=value, places=places, roundfactor=roundfactor, normalize=True) log.debug('value: %s' % type(value)) parts = ("%f" % value).split('.') n = parts[0] d = "" if len(parts) > 0: d = parts[1] elif min_places: d = "0" * min_places while len(d) < min_places: d = "%s0" % d while len(d) > min_places and d[-1] == '0': d = d[:-1] if len(d) > 0: value = "%s.%s" % (n, d) else: value = n return value
def changelist_view(self, request, extra_context=None): "The 'change list' admin view for this model." from django.contrib.admin.views.main import ERROR_FLAG opts = self.model._meta app_label = opts.app_label if not self.has_change_permission(request, None): raise PermissionDenied # Check actions to see if any are available on this changelist actions = self.get_actions(request) # Remove action checkboxes if there aren't any actions available. list_display = list(self.list_display) if not actions: try: list_display.remove('action_checkbox') except ValueError: pass ChangeList = self.get_changelist(request) try: cl = ChangeList(request, self.model, list_display, self.list_display_links, self.list_filter, self.date_hierarchy, self.search_fields, self.list_select_related, self.list_per_page, self.list_editable, self) except IncorrectLookupParameters: # Wacky lookup parameters were given, so redirect to the main # changelist page, without parameters, and pass an 'invalid=1' # parameter via the query string. If wacky parameters were given # and the 'invalid=1' parameter was already in the query string, # something is screwed up with the database, so display an error # page. if ERROR_FLAG in request.GET.keys(): return render_to_response('admin/invalid_setup.html', {'title': _('Database error')}) return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1') # If the request was POSTed, this might be a bulk action or a bulk # edit. Try to look up an action or confirmation first, but if this # isn't an action the POST will fall through to the bulk edit check, # below. action_failed = False selected = request.POST.getlist(helpers.ACTION_CHECKBOX_NAME) # Actions with no confirmation if (actions and request.method == 'POST' and 'index' in request.POST and '_save' not in request.POST): if selected: response = self.response_action(request, queryset=cl.get_query_set()) if response: return response else: action_failed = True else: msg = _("Items must be selected in order to perform " "actions on them. No items have been changed.") self.message_user(request, msg) action_failed = True # Actions with confirmation if (actions and request.method == 'POST' and helpers.ACTION_CHECKBOX_NAME in request.POST and 'index' not in request.POST and '_save' not in request.POST): if selected: response = self.response_action(request, queryset=cl.get_query_set()) if response: return response else: action_failed = True # If we're allowing changelist editing, we need to construct a formset # for the changelist given all the fields to be edited. Then we'll # use the formset to validate/process POSTed data. formset = cl.formset = None # Handle POSTed bulk-edit data. if (request.method == "POST" and cl.list_editable and '_save' in request.POST and not action_failed): FormSet = self.get_changelist_formset(request) formset = cl.formset = FormSet(request.POST, request.FILES, queryset=cl.result_list) if formset.is_valid(): changecount = 0 for form in formset.forms: if form.has_changed(): obj = self.save_form(request, form, change=True) self.save_model(request, obj, form, change=True) form.save_m2m() change_msg = self.construct_change_message( request, form, None) self.log_change(request, obj, change_msg) changecount += 1 if changecount: if changecount == 1: name = force_unicode(opts.verbose_name) else: name = force_unicode(opts.verbose_name_plural) msg = ungettext( "%(count)s %(name)s was changed successfully.", "%(count)s %(name)s were changed successfully.", changecount) % { 'count': changecount, 'name': name, 'obj': force_unicode(obj) } self.message_user(request, msg) return HttpResponseRedirect(request.get_full_path()) # Handle GET -- construct a formset for display. elif cl.list_editable: FormSet = self.get_changelist_formset(request) formset = cl.formset = FormSet(queryset=cl.result_list) # Build the list of media to be used by the formset. if formset: media = self.media + formset.media else: media = self.media # Build the action form and populate it with available actions. if actions: action_form = self.action_form(auto_id=None) action_form.fields['action'].choices = self.get_action_choices( request) else: action_form = None selection_note_all = ungettext('%(total_count)s selected', 'All %(total_count)s selected', cl.result_count) # Calculate total orders_total = 0 for order in cl.result_list: orders_total += order.total context = { 'module_name': force_unicode(opts.verbose_name_plural), 'selection_note': _('0 of %(cnt)s selected') % { 'cnt': len(cl.result_list) }, 'selection_note_all': selection_note_all % { 'total_count': cl.result_count }, 'title': cl.title, 'is_popup': cl.is_popup, 'cl': cl, 'media': media, 'has_add_permission': self.has_add_permission(request), 'root_path': self.admin_site.root_path, 'app_label': app_label, 'action_form': action_form, 'actions_on_top': self.actions_on_top, 'actions_on_bottom': self.actions_on_bottom, 'actions_selection_counter': self.actions_selection_counter, 'orders_total': round_decimal(val=orders_total, places=2, roundfactor='0.001', normalize=True) } context.update(extra_context or {}) context_instance = template.RequestContext( request, current_app=self.admin_site.name) return render_to_response('admin/orders/change_list.html', context, context_instance=context_instance)
def testRoundingDecimals(self): """Test Partial Unit Rounding Decimal Conversion behavior""" val = round_decimal(val=3.40, places=5, roundfactor=.5, normalize=True) self.assertEqual(val, Decimal("3.5")) val = round_decimal(val=3.40, places=5, roundfactor=-.5, normalize=True) self.assertEqual(val, Decimal("3")) val = round_decimal(val=0, places=5, roundfactor=-.5, normalize=False) self.assertEqual(val, Decimal("0.00000")) val = round_decimal(0, 5, -.5, False) self.assertEqual(val, Decimal("0.00000")) val = round_decimal(0) self.assertEqual(val, Decimal("0")) val = round_decimal(3.23, 4, -.25) self.assertEqual(val, Decimal("3")) val = round_decimal(-3.23, 4, -.25) self.assertEqual(val, Decimal("-3")) val = round_decimal(-3.23, 4, .25) self.assertEqual(val, Decimal("-3.25")) val = round_decimal(3.23, 4, .25) self.assertEqual(val, Decimal("3.25")) val = round_decimal(3.23, 4, .25, False) self.assertEqual(val, Decimal("3.2500")) val = round_decimal(3.23, 1, .25, False) self.assertEqual(val, Decimal("3.2")) val = round_decimal(2E+1, places=2) self.assertEqual(val, Decimal('20.00'))
def productvariation_details(product, include_tax, user, create=False): """Build the product variation details, for conversion to javascript. Returns variation detail dictionary built like so: details = { "OPTION_KEY" : { "SLUG": "Variation Slug", "PRICE" : {"qty" : "$price", [...]}, "SALE" : {"qty" : "$price", [...]}, "TAXED" : "$taxed price", # omitted if no taxed price requested "QTY" : 1 }, [...] } """ ignore_stock = config_value('PRODUCT', 'NO_STOCK_CHECKOUT') discount = find_best_auto_discount(product) use_discount = discount and discount.percentage > 0 if include_tax: from tax.utils import get_tax_processor taxer = get_tax_processor(user=user) tax_class = product.taxClass details = {'SALE': use_discount} variations = ProductPriceLookup.objects.filter( parentid=product.id).order_by("-price") if variations.count() == 0: if create: log.debug('Creating price lookup for %s', product) ProductPriceLookup.objects.smart_create_for_product(product) variations = ProductPriceLookup.objects.filter( parentid=product.id).order_by("-price") else: log.warning( 'You must run satchmo_rebuild_pricing and add it to a cron-job to run every day, or else the product details will not work for product detail pages.' ) for detl in variations: key = detl.key if details.has_key(key): detail = details[key] qty = detl.quantity else: detail = {} detail['SLUG'] = detl.productslug if not detl.active: qty = round_decimal('-1.0') elif ignore_stock: qty = round_decimal('10000.0') else: qty = round_decimal(detl.items_in_stock) detail['QTY'] = round_decimal(qty) detail['PRICE'] = {} if use_discount: detail['SALE'] = {} if include_tax: detail['TAXED'] = {} if use_discount: detail['TAXED_SALE'] = {} if detl.productimage_set: detail['ADDITIONAL_IMAGES'] = [ u"%s" % prodimg.picture for prodimg in detl.productimage_set.all() ] details[key] = detail qtykey = "%d" % detl.quantity price = detl.dynamic_price detail['PRICE'][qtykey] = moneyfmt(price) if use_discount: detail['SALE'][qtykey] = moneyfmt( calc_discounted_by_percentage(price, discount.percentage)) if include_tax: tax_price = taxer.by_price(tax_class, price) + price detail['TAXED'][qtykey] = moneyfmt(tax_price) if use_discount: detail['TAXED_SALE'][qtykey] = moneyfmt( calc_discounted_by_percentage(tax_price, discount.percentage)) return details
def _round(val, places=2): return str(round_decimal(val=val, places=places, normalize=False))
def testRoundingDecimals(self): """Test Partial Unit Rounding Decimal Conversion behavior""" val = round_decimal(val=3.40, places=5, roundfactor=.5, normalize=True) self.assertEqual(val, Decimal("3.5")) val = round_decimal(val=3.40, places=5, roundfactor=-.5, normalize=True) self.assertEqual(val, Decimal("3")) val = round_decimal(val=0, places=5, roundfactor=-.5, normalize=False) self.assertEqual(val, Decimal("0.00000")) val = round_decimal(0, 5, -.5, False) self.assertEqual(val, Decimal("0.00000")) val = round_decimal(0) self.assertEqual(val, Decimal("0")) val = round_decimal(3.23,4,-.25) self.assertEqual(val, Decimal("3")) val = round_decimal(-3.23,4,-.25) self.assertEqual(val, Decimal("-3")) val = round_decimal(-3.23,4,.25) self.assertEqual(val, Decimal("-3.25")) val = round_decimal(3.23,4,.25) self.assertEqual(val, Decimal("3.25")) val = round_decimal(3.23,4,.25,False) self.assertEqual(val, Decimal("3.2500")) val = round_decimal(3.23,1,.25,False) self.assertEqual(val, Decimal("3.2")) val = round_decimal(2E+1, places=2) self.assertEqual(val, Decimal('20.00'))
def changelist_view(self, request, extra_context=None): "The 'change list' admin view for this model." from django.contrib.admin.views.main import ERROR_FLAG opts = self.model._meta app_label = opts.app_label if not self.has_change_permission(request, None): raise PermissionDenied # Check actions to see if any are available on this changelist actions = self.get_actions(request) # Remove action checkboxes if there aren't any actions available. list_display = list(self.list_display) if not actions: try: list_display.remove("action_checkbox") except ValueError: pass ChangeList = self.get_changelist(request) try: cl = ChangeList( request, self.model, list_display, self.list_display_links, self.list_filter, self.date_hierarchy, self.search_fields, self.list_select_related, self.list_per_page, self.list_editable, self, ) except IncorrectLookupParameters: # Wacky lookup parameters were given, so redirect to the main # changelist page, without parameters, and pass an 'invalid=1' # parameter via the query string. If wacky parameters were given # and the 'invalid=1' parameter was already in the query string, # something is screwed up with the database, so display an error # page. if ERROR_FLAG in request.GET.keys(): return render_to_response("admin/invalid_setup.html", {"title": _("Database error")}) return HttpResponseRedirect(request.path + "?" + ERROR_FLAG + "=1") # If the request was POSTed, this might be a bulk action or a bulk # edit. Try to look up an action or confirmation first, but if this # isn't an action the POST will fall through to the bulk edit check, # below. action_failed = False selected = request.POST.getlist(helpers.ACTION_CHECKBOX_NAME) # Actions with no confirmation if actions and request.method == "POST" and "index" in request.POST and "_save" not in request.POST: if selected: response = self.response_action(request, queryset=cl.get_query_set()) if response: return response else: action_failed = True else: msg = _("Items must be selected in order to perform " "actions on them. No items have been changed.") self.message_user(request, msg) action_failed = True # Actions with confirmation if ( actions and request.method == "POST" and helpers.ACTION_CHECKBOX_NAME in request.POST and "index" not in request.POST and "_save" not in request.POST ): if selected: response = self.response_action(request, queryset=cl.get_query_set()) if response: return response else: action_failed = True # If we're allowing changelist editing, we need to construct a formset # for the changelist given all the fields to be edited. Then we'll # use the formset to validate/process POSTed data. formset = cl.formset = None # Handle POSTed bulk-edit data. if request.method == "POST" and cl.list_editable and "_save" in request.POST and not action_failed: FormSet = self.get_changelist_formset(request) formset = cl.formset = FormSet(request.POST, request.FILES, queryset=cl.result_list) if formset.is_valid(): changecount = 0 for form in formset.forms: if form.has_changed(): obj = self.save_form(request, form, change=True) self.save_model(request, obj, form, change=True) form.save_m2m() change_msg = self.construct_change_message(request, form, None) self.log_change(request, obj, change_msg) changecount += 1 if changecount: if changecount == 1: name = force_unicode(opts.verbose_name) else: name = force_unicode(opts.verbose_name_plural) msg = ungettext( "%(count)s %(name)s was changed successfully.", "%(count)s %(name)s were changed successfully.", changecount, ) % {"count": changecount, "name": name, "obj": force_unicode(obj)} self.message_user(request, msg) return HttpResponseRedirect(request.get_full_path()) # Handle GET -- construct a formset for display. elif cl.list_editable: FormSet = self.get_changelist_formset(request) formset = cl.formset = FormSet(queryset=cl.result_list) # Build the list of media to be used by the formset. if formset: media = self.media + formset.media else: media = self.media # Build the action form and populate it with available actions. if actions: action_form = self.action_form(auto_id=None) action_form.fields["action"].choices = self.get_action_choices(request) else: action_form = None selection_note_all = ungettext("%(total_count)s selected", "All %(total_count)s selected", cl.result_count) # Calculate total orders_total = 0 for order in cl.result_list: orders_total += order.total context = { "module_name": force_unicode(opts.verbose_name_plural), "selection_note": _("0 of %(cnt)s selected") % {"cnt": len(cl.result_list)}, "selection_note_all": selection_note_all % {"total_count": cl.result_count}, "title": cl.title, "is_popup": cl.is_popup, "cl": cl, "media": media, "has_add_permission": self.has_add_permission(request), "root_path": self.admin_site.root_path, "app_label": app_label, "action_form": action_form, "actions_on_top": self.actions_on_top, "actions_on_bottom": self.actions_on_bottom, "actions_selection_counter": self.actions_selection_counter, "orders_total": round_decimal(val=orders_total, places=2, roundfactor="0.001", normalize=True), } context.update(extra_context or {}) context_instance = template.RequestContext(request, current_app=self.admin_site.name) return render_to_response("admin/orders/change_list.html", context, context_instance=context_instance)
def add(request, id=0, redirect_to='satchmo_cart'): """Add an item to the cart.""" log.debug('FORM: %s', request.POST) formdata = request.POST.copy() productslug = None cartplaces = config_value('SHOP', 'CART_PRECISION') roundfactor = config_value('SHOP', 'CART_ROUNDING') if 'productname' in formdata: productslug = formdata['productname'] try: product, details = product_from_post(productslug, formdata) if not (product and product.active): log.debug("product %s is not active" % productslug) return bad_or_missing(request, _("That product is not available at the moment.")) else: log.debug("product %s is active" % productslug) except (Product.DoesNotExist, MultiValueDictKeyError): log.debug("Could not find product: %s", productslug) return bad_or_missing(request, _('The product you have requested does not exist.')) # First we validate that the number isn't too big. if decimal_too_big(formdata['quantity']): return _product_error(request, product, _("Please enter a smaller number.")) # Then we validate that we can round it appropriately. try: quantity = round_decimal(formdata['quantity'], places=cartplaces, roundfactor=roundfactor) except RoundedDecimalError: return _product_error(request, product, _("Invalid quantity.")) if quantity <= Decimal('0'): return _product_error(request, product, _("Please enter a positive number.")) cart = Cart.objects.from_request(request, create=True) # send a signal so that listeners can update product details before we add it to the cart. satchmo_cart_details_query.send( cart, product=product, quantity=quantity, details=details, request=request, form=formdata ) try: added_item = cart.add_item(product, number_added=quantity, details=details) except CartAddProhibited as cap: return _product_error(request, product, cap.message) # got to here with no error, now send a signal so that listeners can also operate on this form. satchmo_cart_add_complete.send(cart, cart=cart, cartitem=added_item, product=product, request=request, form=formdata) satchmo_cart_changed.send(cart, cart=cart, request=request) if request.is_ajax(): data = { 'id': product.id, 'name': product.translated_name(), 'item_id': added_item.id, 'item_qty': str(round_decimal(quantity, 2)), 'item_price': six.text_type(moneyfmt(added_item.line_total)) or "0.00", 'cart_count': str(round_decimal(cart.numItems, 2)), 'cart_total': six.text_type(moneyfmt(cart.total)), # Legacy result, for now 'results': _("Success"), } log.debug('CART AJAX: %s', data) return _json_response(data) else: url = reverse(redirect_to) return HttpResponseRedirect(url)
try: cartitem = CartItem.objects.get(pk=itemid, cart=cart) except CartItem.DoesNotExist: return (False, cart, None, _("No such item in your cart.")) if qty == Decimal('0'): cartitem.delete() cartitem = NullCartItem(itemid) else: if config_value('PRODUCT','NO_STOCK_CHECKOUT') == False: stock = cartitem.product.items_in_stock log.debug('checking stock quantity. Have %d, need %d', stock, qty) if stock < qty: return (False, cart, cartitem, _("Unfortunately we only have %(stock)d '%(cartitem_name)s' in stock.") % {'stock': stock, 'cartitem_name': cartitem.product.translated_name()}) cartitem.quantity = round_decimal(qty, places=cartplaces) cartitem.save() satchmo_cart_changed.send(cart, cart=cart, request=request) return (True, cart, cartitem, "") def display(request, cart=None, error_message='', default_view_tax=None): """Display the items in the cart.""" if default_view_tax is None: default_view_tax = config_value('TAX', 'DEFAULT_VIEW_TAX') if not cart: cart = Cart.objects.from_request(request) if cart.numItems > 0:
def productvariation_details(product, include_tax, user, create=False): """Build the product variation details, for conversion to javascript. Returns variation detail dictionary built like so: details = { "OPTION_KEY" : { "SLUG": "Variation Slug", "PRICE" : {"qty" : "$price", [...]}, "SALE" : {"qty" : "$price", [...]}, "TAXED" : "$taxed price", # omitted if no taxed price requested "QTY" : 1 }, [...] } """ ignore_stock = config_value('PRODUCT','NO_STOCK_CHECKOUT') discount = find_best_auto_discount(product) use_discount = discount and discount.percentage > 0 if include_tax: from tax.utils import get_tax_processor taxer = get_tax_processor(user=user) tax_class = product.taxClass details = {'SALE' : use_discount} variations = ProductPriceLookup.objects.filter(parentid=product.id).order_by("-price") if variations.count() == 0: if create: log.debug('Creating price lookup for %s', product) ProductPriceLookup.objects.smart_create_for_product(product) variations = ProductPriceLookup.objects.filter(parentid=product.id).order_by("-price") else: log.warning('You must run satchmo_rebuild_pricing and add it to a cron-job to run every day, or else the product details will not work for product detail pages.') for detl in variations: key = detl.key if details.has_key(key): detail = details[key] qty = detl.quantity else: detail = {} detail['SLUG'] = detl.productslug if not detl.active: qty = round_decimal('-1.0') elif ignore_stock: qty = round_decimal('10000.0') else: qty = round_decimal(detl.items_in_stock) detail['QTY'] = round_decimal(qty) detail['PRICE'] = {} if use_discount: detail['SALE'] = {} if include_tax: detail['TAXED'] = {} if use_discount: detail['TAXED_SALE'] = {} if detl.productimage_set: detail['ADDITIONAL_IMAGES'] = [u"%s" % prodimg.picture for prodimg in detl.productimage_set.all()] details[key] = detail qtykey = "%d" % detl.quantity price = detl.dynamic_price detail['PRICE'][qtykey] = moneyfmt(price) if use_discount: detail['SALE'][qtykey] = moneyfmt(calc_discounted_by_percentage(price, discount.percentage)) if include_tax: tax_price = taxer.by_price(tax_class, price) + price detail['TAXED'][qtykey] = moneyfmt(tax_price) if use_discount: detail['TAXED_SALE'][qtykey] = moneyfmt(calc_discounted_by_percentage(tax_price, discount.percentage)) return details
def add_ajax(request, id=0, template="shop/json.html"): cartplaces = config_value('SHOP', 'CART_PRECISION') roundfactor = config_value('SHOP', 'CART_ROUNDING') data = {'errors': []} product = None formdata = request.POST.copy() if not formdata.has_key('productname'): data['errors'].append(('product', _('No product requested'))) else: productslug = formdata['productname'] log.debug('CART_AJAX: slug=%s', productslug) try: product, details = product_from_post(productslug, formdata) except Product.DoesNotExist: log.warn("Could not find product: %s", productslug) product = None if not product: data['errors'].append(('product', _('The product you have requested does not exist.'))) else: if not product.active: data['errors'].append(('product', _('That product is not available at the moment.'))) else: data['id'] = product.id data['name'] = product.translated_name() if not formdata.has_key('quantity'): quantity = Decimal('-1') else: quantity = formdata['quantity'] try: quantity = round_decimal(quantity, places=cartplaces, roundfactor=roundfactor) if quantity < Decimal('0'): data['errors'].append(('quantity', _('Choose a quantity.'))) except RoundedDecimalError: data['errors'].append(('quantity', _('Invalid quantity.'))) tempCart = Cart.objects.from_request(request, create=True) if not data['errors']: # send a signal so that listeners can update product details before we add it to the cart. satchmo_cart_details_query.send( tempCart, product=product, quantity=quantity, details=details, request=request, form=formdata ) try: added_item = tempCart.add_item(product, number_added=quantity) request.session['cart'] = tempCart.id data['results'] = _('Success') if added_item: # send a signal so that listeners can also operate on this form and item. satchmo_cart_add_complete.send( tempCart, cartitem=added_item, product=product, request=request, form=formdata ) except CartAddProhibited, cap: data['results'] = _('Error') data['errors'].append(('product', cap.message))
except CartItem.DoesNotExist: return (False, cart, None, _("No such item in your cart.")) if qty == Decimal('0'): cartitem.delete() cartitem = NullCartItem(itemid) else: from satchmo_store.shop.models import Config config = Config.objects.get_current() if config_value('PRODUCT','NO_STOCK_CHECKOUT') == False: stock = cartitem.product.items_in_stock log.debug('checking stock quantity. Have %d, need %d', stock, qty) if stock < qty: return (False, cart, cartitem, _("Not enough items of '%s' in stock.") % cartitem.product.translated_name()) cartitem.quantity = round_decimal(qty, places=cartplaces) cartitem.save() satchmo_cart_changed.send(cart, cart=cart, request=request) return (True, cart, cartitem, "") def display(request, cart=None, error_message='', default_view_tax=NOTSET): """Display the items in the cart.""" if default_view_tax == NOTSET: default_view_tax = config_value('TAX', 'DEFAULT_VIEW_TAX') if not cart: cart = Cart.objects.from_request(request) if cart.numItems > 0:
try: cartitem = CartItem.objects.get(pk=itemid, cart=cart) except CartItem.DoesNotExist: return (False, cart, None, _("No such item in your cart.")) if qty == Decimal('0'): cartitem.delete() cartitem = NullCartItem(itemid) else: if config_value('PRODUCT','NO_STOCK_CHECKOUT') == False: stock = cartitem.product.items_in_stock log.debug('checking stock quantity. Have %d, need %d', stock, qty) if stock < qty: return (False, cart, cartitem, _("Not enough items of '%s' in stock.") % cartitem.product.translated_name()) cartitem.quantity = round_decimal(qty, places=cartplaces) cartitem.save() satchmo_cart_changed.send(cart, cart=cart, request=request) return (True, cart, cartitem, "") def display(request, cart=None, error_message='', default_view_tax=None): """Display the items in the cart.""" if default_view_tax is None: default_view_tax = config_value('TAX', 'DEFAULT_VIEW_TAX') if not cart: cart = Cart.objects.from_request(request) if cart.numItems > 0: