Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
suutari-ai committed Mar 18, 2017
1 parent bc07e53 commit 854d611
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 21 deletions.
15 changes: 12 additions & 3 deletions shuup/core/order_creator/_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,7 @@ def __init__(self, shop):
self.zero_price = shop.create_price(0)
self.create_price = self.zero_price.new

self._taxes_calculated = False
self._processed_lines_cache = None
self._object_cache = {}
self.uncache() # Initialize caching variables

def update(self, **values):
for key, value in values.items():
Expand Down Expand Up @@ -462,6 +460,7 @@ def uncache(self):
"""
self._processed_lines_cache = None
self._taxes_calculated = False
self._object_cache = {}

@non_reentrant
def __compute_lines(self):
Expand Down Expand Up @@ -575,6 +574,16 @@ def total_gross_weight(self):
return ((sum(l.product.gross_weight * l.quantity for l in product_lines)) if product_lines else 0)

def _get_object(self, model, pk):
"""
Get model object from database by pk with caching.
Avoids same objects being loaded many times from the database
when constructing SourceLines in the same request.
:type model: type
:type pk: int|Any
:rtype: django.db.models.Model
"""
obj = self._object_cache.get((model, pk))
if not obj:
obj = model.objects.get(pk=pk)
Expand Down
59 changes: 44 additions & 15 deletions shuup/front/basket/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from __future__ import unicode_literals

import random
import warnings
from collections import Counter
from decimal import Decimal

Expand Down Expand Up @@ -93,14 +94,23 @@ def __init__(self, request, basket_name="basket"):
self.ip_address = request.META.get("REMOTE_ADDR")
self.storage = get_storage()
self._data = None
self.dirty = False
self._orderable_lines_cache = None
self._unorderable_lines_cache = None
self._lines_cached = False
self.customer = getattr(request, "customer", None)
self.orderer = getattr(request, "person", None)
self.creator = getattr(request, "user", None)

# Note: Being "dirty" means "not saved". It's independent of
# the caching status (which is cleared with self.uncache()).
# I.e. it's possible to be not saved but cached, or saved but
# not cached.
self.dirty = False
self.uncache() # Set empty values for cache variables

def uncache(self):
super(BaseBasket, self).uncache()
self._orderable_lines_cache = None
self._unorderable_lines_cache = None
self._lines_by_line_id_cache = None

def _load(self):
"""
Get the currently persisted data for this basket.
Expand All @@ -120,6 +130,7 @@ def _load(self):
self.storage.delete(basket=self)
self._data = self.storage.load(basket=self)
self.dirty = False
self.uncache()
return self._data

def save(self):
Expand Down Expand Up @@ -226,17 +237,17 @@ def remove_code(self, code):

def _cache_lines(self):
lines = [BasketLine.from_dict(self, line) for line in self._data_lines]
lines_map = {}
lines_by_line_id = {}
orderable_counter = Counter()
orderable_lines = []
for line in lines:
lines_map[line.line_id] = line
lines_by_line_id[line.line_id] = line
if line.type != OrderLineType.PRODUCT:
orderable_lines.append(line)
else:
product = line.product
quantity = line.quantity + orderable_counter[product.id]
if line.shop_product.is_orderable(line.supplier, self.request.customer, quantity, allow_cache=False):
if line.shop_product.is_orderable(line.supplier, self.customer, quantity, allow_cache=False):
if product.is_package_parent():
quantity_map = product.get_package_child_to_quantity_map()
orderable = True
Expand All @@ -245,7 +256,7 @@ def _cache_lines(self):
in_basket_child_qty = orderable_counter[child_product.id]
total_child_qty = ((quantity * child_quantity) + in_basket_child_qty)
if not sp.is_orderable(
line.supplier, self.request.customer, total_child_qty, allow_cache=False):
line.supplier, self.customer, total_child_qty, allow_cache=False):
orderable = False
break
if orderable:
Expand All @@ -258,20 +269,19 @@ def _cache_lines(self):
orderable_counter[product.id] += line.quantity
self._orderable_lines_cache = orderable_lines
self._unorderable_lines_cache = [line for line in lines if line not in orderable_lines]
self._lines_map = lines_map
self._lines_cached = True
self._lines_by_line_id_cache = lines_by_line_id

@property
def is_empty(self):
return not bool(self.get_lines())

def get_unorderable_lines(self):
if self.dirty or not self._lines_cached:
if self._unorderable_lines_cache is None:
self._cache_lines()
return self._unorderable_lines_cache

def get_lines(self):
if self.dirty or not self._lines_cached:
if self._orderable_lines_cache is None:
self._cache_lines()
return self._orderable_lines_cache

Expand Down Expand Up @@ -406,22 +416,41 @@ def delete_line(self, line_id):
return True
return False

def get_line(self, line_id):
self.get_lines() # Populate self._lines_map
return self._lines_map.get(line_id)
def get_basket_line(self, line_id):
"""
Get basket line by line id.
:rtype: BasketLine
"""
if self._lines_by_line_id_cache is None:
self._cache_lines()
return self._lines_by_line_id_cache.get(line_id)

def find_line_by_line_id(self, line_id):
"""
Find basket data line by line id.
:rtype: dict
"""
for line in self._data_lines:
if six.text_type(line.get("line_id")) == six.text_type(line_id):
return line
return None

def find_lines_by_parent_line_id(self, parent_line_id):
"""
Find basket data lines by parent line id.
:rtype: Iterable[dict]
"""
for line in self._data_lines:
if six.text_type(line.get("parent_line_id")) == six.text_type(parent_line_id):
yield line

def _get_orderable(self):
warnings.warn(
"basket.orderable is deprecated, use basket.is_empty instead",
DeprecationWarning)
return (sum(l.quantity for l in self.get_lines()) > 0)

orderable = property(_get_orderable)
Expand Down
2 changes: 1 addition & 1 deletion shuup/front/basket/update_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def update_display_quantity(self, line, value, **kwargs):
new_display_quantity = parse_decimal_string(value)
if new_display_quantity is None:
return False
basket_line = self.basket.get_line(line['line_id'])
basket_line = self.basket.get_basket_line(line['line_id'])
if basket_line and basket_line.product:
unit = basket_line.shop_product.unit
new_quantity = unit.from_display(new_display_quantity)
Expand Down
8 changes: 6 additions & 2 deletions shuup_tests/front/test_basket.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ def test_basket_orderability_change(rf):
assert len(basket.get_lines()) == 1
assert len(basket.get_unorderable_lines()) == 0
product.soft_delete()
basket.uncache()
assert basket.dirty
assert len(basket.get_lines()) == 0
assert len(basket.get_unorderable_lines()) == 1
Expand Down Expand Up @@ -197,14 +198,17 @@ def test_basket_package_product_orderability_change(rf):
assert len(basket.get_unorderable_lines()) == 0

supplier.adjust_stock(child.id, -1)
assert basket.dirty

# Orderability is already cached, we need to uncache force recheck
basket.uncache()

# After reducing stock to 1, should only be stock for one
assert len(basket.get_lines()) == 1
assert len(basket.get_unorderable_lines()) == 1

supplier.adjust_stock(child.id, -1)
assert basket.dirty

basket.uncache()

# After reducing stock to 0, should be stock for neither
assert len(basket.get_lines()) == 0
Expand Down

0 comments on commit 854d611

Please sign in to comment.