def test_09_selective_discount(self): """Test applying discounts with product restriction""" p1 = self.create_product() p2 = self.create_product() p2.name = 'Discountable' p2.save() d = Discount( type=Discount.PERCENTAGE_VOUCHER, name='Some discount', code='asdf', value=Decimal('30'), is_active=True, ) d.config = {'name_filter': {'name': 'Discountable'}} d.save() order = self.create_order() order.modify_item(p1, 3) order.modify_item(p2, 2) d.add_to(order) order.recalculate_total() # Test that only one order item has its discount applied Product = plata.product_model() self.assertEqual(Product.objects.all().count(), 2) self.assertEqual(order.items.count(), 2) self.assertEqual( 1, len([ item for item in order.items.all() if item._line_item_discount ]))
def _eligible_products(self, order, items): """ Return a list of products which are eligible for discounting using the discount configuration. """ product_model = plata.product_model() products = product_model._default_manager.filter( id__in=[item.product_id for item in items]) orderitems = order.items.model._default_manager.filter( id__in=[item.id for item in items]) for key, parameters in self.config.items(): parameters = dict((str(k), v) for k, v in parameters.items()) cfg = dict(self.CONFIG_OPTIONS)[key] if 'product_query' in cfg: products = products.filter( cfg['product_query'](**parameters)) if 'orderitem_query' in cfg: orderitems = orderitems.filter( cfg['orderitem_query'](**parameters)) return products.filter(id__in=orderitems.values('product_id'))
def test_09_selective_discount(self): """Test applying discounts with product restriction""" p1 = self.create_product() p2 = self.create_product() p2.name = 'Discountable' p2.save() d = Discount( type=Discount.PERCENTAGE_VOUCHER, name='Some discount', code='asdf', value=Decimal('30'), is_active=True, ) d.config = {'name_filter': {'name': 'Discountable'}} d.save() order = self.create_order() order.modify_item(p1, 3) order.modify_item(p2, 2) d.add_to(order) order.recalculate_total() # Test that only one order item has its discount applied Product = plata.product_model() self.assertEqual(Product.objects.all().count(), 2) self.assertEqual(order.items.count(), 2) self.assertEqual(1, len([item for item in order.items.all() if item._line_item_discount]))
def items_in_stock(self, product, update=False, exclude_order=None): """ Determine the items in stock for the given product variation, optionally updating the ``items_in_stock`` field in the database. If ``exclude_order`` is given, ``update`` is always switched off and transactions from the given order aren't taken into account. """ queryset = self.stock().filter(product=product) if exclude_order: update = False queryset = queryset.filter(Q(order__isnull=True) | ~Q(order=exclude_order)) count = queryset.aggregate(items=Sum('change')).get('items') or 0 product_model = plata.product_model() if isinstance(product, product_model): product.items_in_stock = count if update: product_model._default_manager.filter(id=getattr(product, 'pk', product)).update( items_in_stock=count) return count
def _eligible_products(self, order, items): """ Return a list of products which are eligible for discounting using the discount configuration. """ product_model = plata.product_model() products = product_model._default_manager.filter( id__in=[item.product_id for item in items]) orderitems = order.items.model._default_manager.filter( id__in=[item.id for item in items]) for key, parameters in self.config.items(): parameters = dict((str(k), v) for k, v in parameters.items()) cfg = dict(self.CONFIG_OPTIONS)[key] if "product_query" in cfg: products = products.filter(cfg["product_query"](**parameters)) if "orderitem_query" in cfg: orderitems = orderitems.filter( cfg["orderitem_query"](**parameters)) return products.filter(id__in=orderitems.values("product_id"))
def plata_categories_and_featured(): categories = Category.objects.public() for category in categories: category.featured = plata.product_model().objects.featured().filter( categories__id=category.id) return { 'categories': categories }
def open_new_period(self, name=None): """ Create a new period and create initial transactions for all product variations with their current ``items_in_stock`` value """ period = Period.objects.create(name=name or ugettext('New period')) for p in plata.product_model()._default_manager.all(): p.stock_transactions.create( period=period, type=StockTransaction.INITIAL, change=p.items_in_stock, notes=ugettext('New period'), )
def items_in_stock(self, product, update=False, exclude_order=None, include_reservations=False): """ Determine the items in stock for the given product variation, optionally updating the ``items_in_stock`` field in the database. If ``exclude_order`` is given, ``update`` is always switched off and transactions from the given order aren't taken into account. If ``include_reservations`` is ``True``, ``update`` is always switched off. """ queryset = self.filter(period=Period.objects.current(), product=product) if exclude_order: update = False queryset = queryset.filter( Q(order__isnull=True) | ~Q(order=exclude_order)) if include_reservations: update = False queryset = queryset.exclude( type=self.model.PAYMENT_PROCESS_RESERVATION, created__lt=timezone.now() - timedelta(seconds=15 * 60), ) else: queryset = queryset.exclude( type=self.model.PAYMENT_PROCESS_RESERVATION) count = queryset.aggregate(items=Sum("change")).get("items") or 0 product_model = plata.product_model() if isinstance(product, product_model): product.items_in_stock = count if update: product_model._default_manager.filter( id=getattr(product, "pk", product)).update( items_in_stock=count) return count
def items_in_stock(self, product, update=False, exclude_order=None, include_reservations=False): """ Determine the items in stock for the given product variation, optionally updating the ``items_in_stock`` field in the database. If ``exclude_order`` is given, ``update`` is always switched off and transactions from the given order aren't taken into account. If ``include_reservations`` is ``True``, ``update`` is always switched off. """ queryset = self.filter( period=Period.objects.current(), product=product) if exclude_order: update = False queryset = queryset.filter( Q(order__isnull=True) | ~Q(order=exclude_order)) if include_reservations: update = False queryset = queryset.exclude( type=self.model.PAYMENT_PROCESS_RESERVATION, created__lt=timezone.now() - timedelta(seconds=15*60)) else: queryset = queryset.exclude( type=self.model.PAYMENT_PROCESS_RESERVATION) count = queryset.aggregate(items=Sum('change')).get('items') or 0 product_model = plata.product_model() if isinstance(product, product_model): product.items_in_stock = count if update: product_model._default_manager.filter( id=getattr(product, 'pk', product) ).update(items_in_stock=count) return count
def product_xls(): """ Create a list of all product variations, including stock and aggregated stock transactions (by type) """ from plata.product.stock.models import Period StockTransaction = plata.stock_model() xls = XLSDocument() xls.add_sheet(capfirst(_('products'))) _transactions = StockTransaction.objects.filter( period=Period.objects.current(), ).order_by().values('product', 'type').annotate(Sum('change')) transactions = defaultdict(dict) for t in _transactions: transactions[t['product']][t['type']] = t['change__sum'] titles = [ capfirst(_('product')), _('SKU'), capfirst(_('stock')), ] titles.extend( unicode(name) for key, name in StockTransaction.TYPE_CHOICES) data = [] for product in plata.product_model().objects.all().select_related(): row = [ product, getattr(product, 'sku', ''), getattr(product, 'items_in_stock', -1), ] row.extend( transactions[product.id].get(key, '') for key, name in StockTransaction.TYPE_CHOICES) data.append(row) xls.table(titles, data) return xls
def product_xls(): """ Create a list of all product variations, including stock and aggregated stock transactions (by type) """ from plata.product.stock.models import Period StockTransaction = plata.stock_model() xls = XLSXDocument() xls.add_sheet(capfirst(_("products"))) _transactions = ( StockTransaction.objects.filter(period=Period.objects.current()) .order_by() .values("product", "type") .annotate(Sum("change")) ) transactions = defaultdict(dict) for t in _transactions: transactions[t["product"]][t["type"]] = t["change__sum"] titles = [capfirst(_("product")), _("SKU"), capfirst(_("stock"))] titles.extend("%s" % row[1] for row in StockTransaction.TYPE_CHOICES) data = [] for product in plata.product_model().objects.all().select_related(): row = [ product, getattr(product, "sku", ""), getattr(product, "items_in_stock", -1), ] row.extend( transactions[product.id].get(key, "") for key, name in StockTransaction.TYPE_CHOICES ) data.append(row) xls.table(titles, data) return xls
def product_xls(): """ Create a list of all product variations, including stock and aggregated stock transactions (by type) """ from plata.product.stock.models import Period StockTransaction = plata.stock_model() xls = XLSDocument() xls.add_sheet(capfirst(_('products'))) _transactions = StockTransaction.objects.filter( period=Period.objects.current(), ).order_by().values( 'product', 'type').annotate(Sum('change')) transactions = defaultdict(dict) for t in _transactions: transactions[t['product']][t['type']] = t['change__sum'] titles = [ capfirst(_('product')), _('SKU'), capfirst(_('stock')), ] titles.extend('%s' % row[1] for row in StockTransaction.TYPE_CHOICES) data = [] for product in plata.product_model().objects.all().select_related(): row = [ product, getattr(product, 'sku', ''), getattr(product, 'items_in_stock', -1), ] row.extend(transactions[product.id].get(key, '') for key, name in StockTransaction.TYPE_CHOICES) data.append(row) xls.table(titles, data) return xls
class SingleProductTeaserContent(models.Model): product = models.ForeignKey(plata.product_model()) feincms_item_editor_inline = ProductInline class Meta: abstract = True verbose_name = _('Product teaser') verbose_name_plural = _('Product teasers') @property def media(self): media = forms.Media() media.add_js(('plata/cart.js', )) return media def __unicode__(self): return unicode(self.product) def render(self, **kwargs): request = kwargs.get('request') # need request for csrf token return render_to_string('content/product/single_teaser.html', {'product': self.product}, context_instance=RequestContext(request))
def ready(self): if plata.settings.PLATA_STOCK_TRACKING: product_model = plata.product_model() try: product_model._meta.get_field('items_in_stock') except FieldDoesNotExist: raise ImproperlyConfigured( 'Product model %r must have a field named' ' `items_in_stock`' % (product_model, )) from plata.product.stock.models import ( StockTransaction, update_items_in_stock, validate_order_stock_available, ) from plata.shop.models import Order signals.post_delete.connect(update_items_in_stock, sender=StockTransaction) signals.post_save.connect(update_items_in_stock, sender=StockTransaction) Order.register_validator(validate_order_stock_available, Order.VALIDATE_CART)
def create_product(self, stock=0): global PRODUCTION_CREATION_COUNTER PRODUCTION_CREATION_COUNTER += 1 tax_class, tax_class_germany, tax_class_something = self.create_tax_classes() Product = plata.product_model() product = Product.objects.create( name='Test Product %s' % PRODUCTION_CREATION_COUNTER, ) if stock: product.stock_transactions.create( type=StockTransaction.PURCHASE, change=stock, ) # An old price in CHF which should not influence the rest of the tests product.prices.create( currency='CHF', tax_class=tax_class, _unit_price=Decimal('99.90'), tax_included=True, ) product.prices.create( currency='CHF', tax_class=tax_class, _unit_price=Decimal('199.90'), tax_included=True, #valid_from=date(2000, 1, 1), #valid_until=date(2001, 1, 1), ) product.prices.create( currency='CHF', tax_class=tax_class, _unit_price=Decimal('299.90'), tax_included=True, #valid_from=date(2000, 1, 1), ) product.prices.create( currency='CHF', tax_class=tax_class, _unit_price=Decimal('299.90'), tax_included=True, #valid_from=date(2000, 7, 1), #is_sale=True, ) product.prices.create( currency='CHF', tax_class=tax_class, _unit_price=Decimal('79.90'), tax_included=True, #is_sale=True, ) product.prices.create( currency='EUR', tax_class=tax_class_germany, _unit_price=Decimal('49.90'), tax_included=True, ) product.prices.create( currency='CAD', tax_class=tax_class_something, _unit_price=Decimal('65.00'), tax_included=False, ) """ # A few prices which are not yet (or no more) active product.prices.create( currency='CHF', tax_class=tax_class, _unit_price=Decimal('110.00'), tax_included=True, #is_active=False, ) product.prices.create( currency='CHF', tax_class=tax_class, _unit_price=Decimal('120.00'), tax_included=True, is_active=True, valid_from=date(2100, 1, 1), ) product.prices.create( currency='CHF', tax_class=tax_class, _unit_price=Decimal('130.00'), tax_included=True, is_active=True, valid_from=date(2000, 1, 1), valid_until=date(2001, 1, 1), ) """ return product
from django.contrib.auth.models import User from django.core import mail from django.core.exceptions import ValidationError from django.utils import timezone import plata from plata.contact.models import Contact from plata.discount.models import Discount from plata.product.stock.models import Period, StockTransaction from plata.shop.models import Order, OrderPayment from .base import PlataTest, get_request Product = plata.product_model() class ViewTest(PlataTest): def setUp(self): self.ORIG_TEMPLATE_DIRS = settings.TEMPLATE_DIRS settings.TEMPLATE_DIRS = (os.path.join(os.path.dirname(__file__), 'templates'),) def tearDown(self): settings.TEMPLATE_DIRS = self.ORIG_TEMPLATE_DIRS def test_01_cart_empty(self): """Test cart is empty redirects work properly""" self.assertContains(self.client.get('/cart/'), 'Cart is empty') self.assertRedirects(self.client.get('/checkout/'), '/cart/') self.assertRedirects(self.client.get('/discounts/'), '/cart/')
def create_product(self, stock=0): global PRODUCTION_CREATION_COUNTER PRODUCTION_CREATION_COUNTER += 1 tax_class, tax_class_germany, tax_class_something =\ self.create_tax_classes() Product = plata.product_model() product = Product.objects.create(name='Test Product %s' % PRODUCTION_CREATION_COUNTER, ) if stock: product.stock_transactions.create( type=StockTransaction.PURCHASE, change=stock, ) # An old price in CHF which should not influence the rest of the tests product.prices.create( currency='CHF', tax_class=tax_class, _unit_price=Decimal('99.90'), tax_included=True, ) product.prices.create( currency='CHF', tax_class=tax_class, _unit_price=Decimal('199.90'), tax_included=True, # valid_from=date(2000, 1, 1), # valid_until=date(2001, 1, 1), ) product.prices.create( currency='CHF', tax_class=tax_class, _unit_price=Decimal('299.90'), tax_included=True, # valid_from=date(2000, 1, 1), ) product.prices.create( currency='CHF', tax_class=tax_class, _unit_price=Decimal('299.90'), tax_included=True, # valid_from=date(2000, 7, 1), # is_sale=True, ) product.prices.create( currency='CHF', tax_class=tax_class, _unit_price=Decimal('79.90'), tax_included=True, # is_sale=True, ) product.prices.create( currency='EUR', tax_class=tax_class_germany, _unit_price=Decimal('49.90'), tax_included=True, ) product.prices.create( currency='CAD', tax_class=tax_class_something, _unit_price=Decimal('65.00'), tax_included=False, ) """ # A few prices which are not yet (or no more) active product.prices.create( currency='CHF', tax_class=tax_class, _unit_price=Decimal('110.00'), tax_included=True, #is_active=False, ) product.prices.create( currency='CHF', tax_class=tax_class, _unit_price=Decimal('120.00'), tax_included=True, is_active=True, valid_from=date(2100, 1, 1), ) product.prices.create( currency='CHF', tax_class=tax_class, _unit_price=Decimal('130.00'), tax_included=True, is_active=True, valid_from=date(2000, 1, 1), valid_until=date(2001, 1, 1), ) """ return product
def product_xls(): """ Create a list of all product variations, including stock and aggregated stock transactions (by type) """ workbook = xlwt.Workbook() s = workbook.add_sheet(capfirst(_('products'))) style = Style() row = 0 s.write(row, 0, capfirst(_('products')), style=style.title) row += 1 s.write(row, 0, _('Report of %s') % (date.today().strftime('%Y-%m-%d')), style=style.normal) row += 2 s.write(row, 0, capfirst(_('product')), style=style.bold) s.write(row, 1, _('SKU'), style=style.bold) s.write(row, 2, capfirst(_('stock')), style=style.bold) col = 10 for type_id, type_name in StockTransaction.TYPE_CHOICES: s.write(row, col, unicode(type_name), style=style.bold) col += 1 row += 2 s.col(0).width = 10000 s.col(1).width = 3000 s.col(2).width = 2000 s.col(3).width = 300 s.col(4).width = 300 s.col(5).width = 300 s.col(6).width = 300 s.col(7).width = 300 s.col(8).width = 300 s.col(9).width = 300 _transactions = StockTransaction.objects.filter( period=Period.objects.current()).values('product', 'type').annotate(Sum('change')) transactions = {} for t in _transactions: transactions.setdefault(t['product'], {})[t['type']] = t['change__sum'] for product in plata.product_model().objects.all().select_related(): s.write(row, 0, unicode(product)) s.write(row, 1, product.sku) s.write(row, 2, product.items_in_stock) col = 10 for type_id, type_name in StockTransaction.TYPE_CHOICES: if product.id in transactions: s.write(row, col, transactions[product.id].get(type_id, '')) col += 1 row += 1 return workbook
def plata_bestsellers(): return { 'bestsellers': plata.product_model().objects.bestsellers() }