Beispiel #1
0
    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
            ]))
Beispiel #2
0
    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'))
Beispiel #3
0
    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]))
Beispiel #4
0
    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
Beispiel #5
0
    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"))
Beispiel #6
0
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
    }
Beispiel #7
0
    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'),
            )
Beispiel #8
0
    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'),
                )
Beispiel #9
0
    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
Beispiel #10
0
    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
Beispiel #11
0
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
Beispiel #12
0
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
Beispiel #13
0
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
Beispiel #14
0
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))
Beispiel #15
0
    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)
Beispiel #16
0
    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
Beispiel #17
0
  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/')
Beispiel #18
0
    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
Beispiel #19
0
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
Beispiel #20
0
def plata_bestsellers():
    return {
        'bestsellers': plata.product_model().objects.bestsellers()
    }