Skip to content

Commit c77575d

Browse files
authored
feat: Create basket lazy (#101)
Instead of creating a basket always this PR changes the behavior to a lazy creation. The basket will from now on only created on `article_add` or if `basket_view` oder `get_current_session_cart_key` has been explicit called with `create_if_missing=True`.
1 parent 24f671f commit c77575d

File tree

4 files changed

+66
-18
lines changed

4 files changed

+66
-18
lines changed

src/viur/shop/modules/api.py

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import typing as t # noqa
22

33
from google.protobuf.message import DecodeError
4-
from viur.core import current, db, errors, exposed, force_post
5-
from viur.core.render.json.default import DefaultRender as JsonRenderer
64

75
import viur.shop.types.exceptions as e
6+
from viur.core import current, db, errors, exposed, force_post
7+
from viur.core.render.json.default import DefaultRender as JsonRenderer
88
from viur.shop.modules.abstract import ShopModuleAbstract
99
from viur.shop.skeletons import ShippingSkel
1010
from viur.shop.types import *
@@ -48,12 +48,22 @@ def article_add(
4848
article_key: str | db.Key,
4949
quantity: int = 1,
5050
quantity_mode: QuantityMode = QuantityMode.REPLACE,
51-
parent_cart_key: str | db.Key,
51+
parent_cart_key: str | db.Key | t.Literal["BASKET"] = SENTINEL,
5252
**kwargs,
5353
):
54-
"""Add an article to the cart"""
54+
"""Add an article to the cart
55+
56+
:param article_key: Key of the article to add.
57+
:param quantity: Quantity of the article to add.
58+
:param quantity_mode: Behavior of the quantity: absolute or relative valuation
59+
:param parent_cart_key: Key of the (sub) cart (node) to which
60+
this leaf will be added as a child.
61+
Use "BASKET" as key to use the basket of the current session.
62+
"""
5563
article_key = self._normalize_external_key(
5664
article_key, "article_key")
65+
if parent_cart_key == "BASKET":
66+
parent_cart_key = self.shop.cart.get_current_session_cart_key(create_if_missing=True)
5767
parent_cart_key = self._normalize_external_key(
5868
parent_cart_key, "parent_cart_key")
5969
assert isinstance(quantity_mode, QuantityMode)
@@ -73,12 +83,23 @@ def article_update(
7383
article_key: str | db.Key,
7484
quantity: int,
7585
quantity_mode: QuantityMode = QuantityMode.REPLACE,
76-
parent_cart_key: str | db.Key,
86+
parent_cart_key: str | db.Key | t.Literal["BASKET"] = SENTINEL,
7787
**kwargs,
7888
):
79-
"""Update an existing article in the cart"""
89+
"""Update an existing article in the cart
90+
91+
:param article_key: Key of the article to update.
92+
Note: This is not the key of the leaf skel!
93+
:param quantity: Quantity of the article to update.
94+
:param quantity_mode: Behavior of the quantity: absolute or relative valuation
95+
:param parent_cart_key: Optional. Key of the (sub) cart (node) to which
96+
this leaf will be moved to as a child.
97+
Use "BASKET" as key to use the basket of the current session.
98+
"""
8099
article_key = self._normalize_external_key(
81100
article_key, "article_key")
101+
if parent_cart_key == "BASKET":
102+
parent_cart_key = self.shop.cart.get_current_session_cart_key(create_if_missing=True)
82103
parent_cart_key = self._normalize_external_key(
83104
parent_cart_key, "parent_cart_key")
84105
assert isinstance(quantity_mode, QuantityMode)
@@ -260,19 +281,33 @@ def cart_clear(
260281
def basket_list(
261282
self,
262283
):
263-
"""List the children of the basket (the cart stored in the session)"""
284+
"""List the children of the basket (the cart stored in the session)
285+
286+
:raises errors.PreconditionFailed: If no basket created yet for this session
287+
"""
288+
if self.shop.cart.current_session_cart_key is None:
289+
raise errors.PreconditionFailed("No basket created yet for this session") # TODO(discuss): explicit?
290+
return [] # TODO(discuss): implicit?
264291
return self.cart_list(cart_key=self.shop.cart.current_session_cart_key)
265292

266293
@exposed
267294
def basket_view(
268295
self,
296+
*,
297+
create_if_missing: bool = False,
269298
):
270299
"""View the basket (the cart stored in the session) itself
271300
301+
:param create_if_missing: Create the basket if not already created for this session
302+
:raises errors.PreconditionFailed: If no basket created yet for this session (and it should not be created)
303+
272304
See also :meth:`basket_view` to view any cart.
273305
"""
306+
cart_key = self.shop.cart.get_current_session_cart_key(create_if_missing=create_if_missing)
307+
if cart_key is None:
308+
raise errors.PreconditionFailed("No basket created yet for this session")
274309
return JsonResponse(self.shop.cart.cart_get(
275-
cart_key=self.shop.cart.current_session_cart_key, skel_type="node",
310+
cart_key=cart_key, skel_type="node",
276311
))
277312

278313
@exposed
@@ -506,6 +541,8 @@ def _normalize_external_key(
506541
return None
507542
elif not external_key:
508543
raise InvalidKeyException(external_key, parameter_name)
544+
if isinstance(external_key, db.Key):
545+
return external_key
509546
try:
510547
return db.Key.from_legacy_urlsafe(external_key)
511548
except (ValueError, DecodeError): # yes, the exception really comes from protobuf...

src/viur/shop/modules/cart.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,32 +58,35 @@ def canView(self, skelType: SkelType, skel: SkeletonInstance) -> bool:
5858
# --- Session -------------------------------------------------------------
5959

6060
@property
61-
def current_session_cart_key(self):
61+
def current_session_cart_key(self) -> db.Key | None:
62+
return self.get_current_session_cart_key(create_if_missing=False)
63+
64+
def get_current_session_cart_key(self, *, create_if_missing: bool = False) -> db.Key | None:
6265
if user := current.user.get():
6366
user_skel = conf.main_app.vi.user.viewSkel()
6467
user_skel.read(user["key"])
6568
if user_skel["basket"]:
6669
self.session["session_cart_key"] = user_skel["basket"]["dest"]["key"]
6770
current.session.get().markChanged()
68-
self._ensure_current_session_cart()
71+
if create_if_missing:
72+
self._ensure_current_session_cart()
6973
return self.session.get("session_cart_key")
7074

7175
@property
72-
def current_session_cart(self): # TODO: Caching
76+
def current_session_cart(self) -> SkeletonInstance_T[CartNodeSkel]: # TODO: Caching
7377
skel = self.viewSkel("node")
74-
if not skel.read(self.current_session_cart_key):
78+
if not skel.read(self.get_current_session_cart_key(create_if_missing=True)):
7579
logger.critical(f"Invalid session_cart_key {self.current_session_cart_key} ?! Not in DB!")
7680
self.detach_session_cart()
7781
return self.current_session_cart
7882
raise InvalidStateError(f"Invalid session_cart_key {self.current_session_cart_key} ?! Not in DB!")
79-
return skel
83+
return skel # type: ignore
8084

81-
def _ensure_current_session_cart(self):
85+
def _ensure_current_session_cart(self) -> db.Key:
8286
if not self.session.get("session_cart_key"):
8387
root_node = self.addSkel("node")
84-
user = current.user.get() and current.user.get()["name"] or "__guest__"
8588
root_node["is_root_node"] = True
86-
root_node["name"] = f"Session Cart of {user} created at {utils.utcNow()}"
89+
root_node["name"] = root_node.name.getDefaultValue(root_node)
8790
root_node["cart_type"] = CartType.BASKET
8891
root_node.write()
8992
self.session["session_cart_key"] = root_node["key"]
@@ -110,7 +113,9 @@ def _set_basket_txn(user_key: db.Key, basket_key: db.Key | None) -> SkeletonInst
110113
return user_skel
111114

112115
def get_available_root_nodes(self, *args, **kwargs) -> list[dict[t.Literal["name", "key"], str]]:
113-
root_nodes = [self.current_session_cart]
116+
root_nodes = []
117+
if self.current_session_cart_key is not None:
118+
root_nodes.append(self.current_session_cart)
114119

115120
if user := current.user.get():
116121
for wishlist in user["wishlist"]:

src/viur/shop/modules/discount.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ def apply(
7979
if not bool(code) ^ bool(discount_key):
8080
raise ValueError(f"Need code xor discount_code")
8181
cart_key = self.shop.cart.current_session_cart_key # TODO: parameter?
82+
if cart_key is None:
83+
raise errors.PreconditionFailed("No basket created yet for this session")
8284

8385
skels = self.search(code, discount_key)
8486
# logger.debug(f"{skels = }")

src/viur/shop/skeletons/cart.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import typing as t # noqa
33

44
from viur import toolkit
5-
from viur.core import db
5+
from viur.core import current, db, utils
66
from viur.core.bones import *
77
from viur.core.prototypes.tree import TreeSkel
88
from viur.core.skeleton import SkeletonInstance
@@ -195,6 +195,10 @@ class CartNodeSkel(TreeSkel): # STATE: Complete (as in model)
195195
)
196196

197197
name = StringBone(
198+
defaultValue=lambda skel, bone: (
199+
f"Session Cart of {current.user.get() and current.user.get()["name"] or "__guest__"}"
200+
f" created at {utils.utcNow()}"
201+
)
198202
)
199203

200204
cart_type = SelectBone(

0 commit comments

Comments
 (0)