Пример #1
0
class BaseCart(with_metaclass(deferred.ForeignKeyBuilder, models.Model)):
    """
    The fundamental part of a shopping cart.
    """
    customer = deferred.OneToOneField(
        'BaseCustomer',
        verbose_name=_("Customer"),
        related_name='cart',
    )

    created_at = models.DateTimeField(
        _("Created at"),
        auto_now_add=True,
    )

    updated_at = models.DateTimeField(
        _("Updated at"),
        auto_now=True,
    )

    extra = JSONField(verbose_name=_("Arbitrary information for this cart"))

    # our CartManager determines the cart object from the request.
    objects = CartManager()

    class Meta:
        abstract = True
        verbose_name = _("Shopping Cart")
        verbose_name_plural = _("Shopping Carts")

    def __init__(self, *args, **kwargs):
        super(BaseCart, self).__init__(*args, **kwargs)
        # That will hold things like tax totals or total discount
        self.extra_rows = OrderedDict()
        self._cached_cart_items = None
        self._dirty = True

    def save(self, force_update=False, *args, **kwargs):
        if self.pk or force_update is False:
            super(BaseCart, self).save(force_update=force_update,
                                       *args,
                                       **kwargs)
        self._dirty = True

    def update(self, request):
        """
        This should be called after a cart item changed quantity, has been added or removed.

        It will loop on all line items in the cart, and call all the cart modifiers for each item.
        After doing this, it will compute and update the order's total and subtotal fields, along
        with any supplement added along the way by modifiers.

        Note that theses added fields are not stored - we actually want to
        reflect rebate and tax changes on the *cart* items, but we don't want
        that for the order items (since they are legally binding after the
        "purchase" button was pressed)
        """
        if not self._dirty:
            return

        if self._cached_cart_items:
            items = self._cached_cart_items
        else:
            items = CartItemModel.objects.filter_cart_items(self, request)

        # This calls all the pre_process_cart methods and the pre_process_cart_item for each item,
        # before processing the cart. This allows to prepare and collect data on the cart.
        for modifier in cart_modifiers_pool.get_all_modifiers():
            modifier.pre_process_cart(self, request)
            for item in items:
                modifier.pre_process_cart_item(self, item, request)

        self.extra_rows = OrderedDict()  # reset the dictionary
        self.subtotal = 0  # reset the subtotal
        for item in items:
            # item.update iterates over all cart modifiers and invokes method `process_cart_item`
            item.update(request)
            self.subtotal += item.line_total

        # Iterate over the registered modifiers, to process the cart's summary
        for modifier in cart_modifiers_pool.get_all_modifiers():
            for item in items:
                modifier.post_process_cart_item(self, item, request)
            modifier.process_cart(self, request)

        # This calls the post_process_cart method from cart modifiers, if any.
        # It allows for a last bit of processing on the "finished" cart, before
        # it is displayed
        for modifier in reversed(cart_modifiers_pool.get_all_modifiers()):
            modifier.post_process_cart(self, request)

        # Cache updated cart items
        self._cached_cart_items = items
        self._dirty = False

    def empty(self):
        """
        Remove the cart with all its items.
        """
        if self.pk:
            self.items.all().delete()
            self.delete()

    def merge_with(self, other_cart):
        """
        Merge the contents of the other cart into this one, afterwards delete it.
        This is done item by item, so that duplicate items increase the quantity.
        """
        # iterate over the cart and add quantities for items from other cart considered as equal
        for item in self.items.all():
            other_item = item.product.is_in_cart(other_cart, extra=item.extra)
            if other_item:
                item.quantity += other_item.quantity
                item.save()
                other_item.delete()

        # the remaining items from the other cart are merged into this one
        other_cart.items.update(cart=self)
        other_cart.delete()

    def __str__(self):
        return "{}".format(self.pk) if self.pk else '(unsaved)'

    @property
    def num_items(self):
        """
        Returns the number of items in the cart.
        """
        return self.items.filter(quantity__gt=0).count()

    @property
    def total_quantity(self):
        """
        Returns the total quantity of all items in the cart.
        """
        aggr = self.items.aggregate(quantity=models.Sum('quantity'))
        return aggr['quantity'] or 0
        # if we would know, that self.items is already evaluated, then this might be faster:
        # return sum([ci.quantity for ci in self.items.all()])

    @property
    def is_empty(self):
        return self.total_quantity == 0

    def get_caption_data(self):
        return {
            'num_items': self.num_items,
            'total_quantity': self.total_quantity,
            'subtotal': self.subtotal,
            'total': self.total
        }

    @classmethod
    def get_default_caption_data(cls):
        return {
            'num_items': 0,
            'total_quantity': 0,
            'subtotal': Money(),
            'total': Money()
        }
Пример #2
0
RegularUser = create_regular_class('RegularUser')
DeferredBaseUser = create_deferred_base_class('DeferredBaseUser')
DeferredUser = create_deferred_class('DeferredUser', DeferredBaseUser)

RegularCustomer = create_regular_class(
    'RegularCustomer', {
        'user':
        models.OneToOneField(RegularUser, on_delete=models.PROTECT),
        'advertised_by':
        models.ForeignKey(
            'self', null=True, blank=True, on_delete=models.SET_NULL),
    })
DeferredBaseCustomer = create_deferred_base_class(
    'DeferredBaseCustomer', {
        'user':
        deferred.OneToOneField(DeferredBaseUser, on_delete=models.PROTECT),
        'advertised_by':
        deferred.ForeignKey(
            'self', null=True, blank=True, on_delete=models.SET_NULL),
    })
DeferredCustomer = create_deferred_class('DeferredCustomer',
                                         DeferredBaseCustomer)

RegularProduct = create_regular_class('RegularProduct')
DeferredBaseProduct = create_deferred_base_class('DeferredBaseProduct')
DeferredProduct = create_deferred_class('DeferredProduct', DeferredBaseProduct)

# Order is important, it must be declared before DeferredOrder, so that fulfillment tests make sense
DeferredBaseOrderItemBeforeOrder = create_deferred_base_class(
    'DeferredBaseOrderItemBeforeOrder', {
        'order':