Skip to content

Commit bea9a8d

Browse files
committed
Merge branch 'main' into feature/lazy_basket_creation
Conflicts: src/viur/shop/modules/cart.py src/viur/shop/skeletons/cart.py
2 parents 2600a5b + 24f671f commit bea9a8d

File tree

13 files changed

+94
-73
lines changed

13 files changed

+94
-73
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<div align="center">
2+
<img src="https://github.com/viur-framework/viur-artwork/raw/main/icons/icon-shop.svg" height="196" alt="A hexagonal logo of Shop" title="Shop logo"/>
23
<h1>viur-shop (WIP)</h1>
34
<a href="https://pypi.org/project/viur-shop/">
45
<img alt="Badge showing current PyPI version" title="PyPI" src="https://img.shields.io/pypi/v/viur-shop">

pyproject.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ build-backend = "setuptools.build_meta"
99
name = "viur-shop"
1010
dynamic = ["version"]
1111
dependencies = [
12-
"viur-toolkit>=0.1.0.dev4"
12+
"viur-toolkit>=0.2.0",
13+
"viur-core>=3.7.0"
1314
]
14-
requires-python = ">=3.10"
15+
requires-python = ">=3.11"
1516
authors = [
1617
{ name = "Sven Eberth", email = "se@mausbrand.de" },
1718
]
@@ -23,11 +24,10 @@ readme = "README.md"
2324
license = { file = "LICENSE" }
2425
keywords = ["viur", "plugin", "backend", "shop"]
2526
classifiers = [
26-
"Development Status :: 1 - Planning",
27+
"Development Status :: 2 - Pre-Alpha",
2728
"Intended Audience :: Developers",
2829
"License :: OSI Approved :: MIT License",
2930
"Operating System :: OS Independent",
30-
"Programming Language :: Python :: 3.10",
3131
"Programming Language :: Python :: 3.11",
3232
"Programming Language :: Python :: 3.12",
3333
"Topic :: Software Development :: Libraries :: Python Modules",

src/viur/shop/modules/address.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ def _disable_old_default(self, skel: SkeletonInstance) -> None:
8282
for other_skel in query.fetch(100):
8383
if skel["key"] != other_skel["key"]:
8484
other_skel["is_default"] = False
85-
other_skel.toDB()
85+
other_skel.write()
8686

8787

8888
Address.json = True

src/viur/shop/modules/cart.py

Lines changed: 54 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@
55
from viur.core.bones import BaseBone
66
from viur.core.prototypes import Tree
77
from viur.core.prototypes.tree import SkelType
8+
from viur.core.session import Session
89
from viur.core.skeleton import Skeleton, SkeletonInstance
910
from viur.shop.modules.abstract import ShopModuleAbstract
1011
from viur.shop.types import *
1112
from viur.shop.types.exceptions import InvalidStateError
12-
from ..globals import SENTINEL, SHOP_LOGGER
13+
from ..globals import SENTINEL, SHOP_INSTANCE, SHOP_LOGGER
1314
from ..services import EVENT_SERVICE, Event
1415
from ..skeletons.cart import CartItemSkel, CartNodeSkel
1516
from ..skeletons.order import OrderSkel
@@ -40,9 +41,9 @@ def baseSkel(
4041
if sub_skel is None:
4142
return cls() # noqa
4243
if isinstance(sub_skel, list):
43-
return cls.subSkel(*sub_skel)
44+
return cls.subskel(*sub_skel)
4445
else:
45-
return cls.subSkel(sub_skel)
46+
return cls.subskel(sub_skel)
4647

4748
def canView(self, skelType: SkelType, skel: SkeletonInstance) -> bool:
4849
if super().canView(skelType, skel):
@@ -63,7 +64,7 @@ def current_session_cart_key(self) -> db.Key | None:
6364
def get_current_session_cart_key(self, *, create_if_missing: bool = False) -> db.Key | None:
6465
if user := current.user.get():
6566
user_skel = conf.main_app.vi.user.viewSkel()
66-
user_skel.fromDB(user["key"])
67+
user_skel.read(user["key"])
6768
if user_skel["basket"]:
6869
self.session["session_cart_key"] = user_skel["basket"]["dest"]["key"]
6970
current.session.get().markChanged()
@@ -74,7 +75,7 @@ def get_current_session_cart_key(self, *, create_if_missing: bool = False) -> db
7475
@property
7576
def current_session_cart(self) -> SkeletonInstance_T[CartNodeSkel]: # TODO: Caching
7677
skel = self.viewSkel("node")
77-
if not skel.fromDB(self.get_current_session_cart_key(create_if_missing=True)):
78+
if not skel.read(self.get_current_session_cart_key(create_if_missing=True)):
7879
logger.critical(f"Invalid session_cart_key {self.current_session_cart_key} ?! Not in DB!")
7980
self.detach_session_cart()
8081
return self.current_session_cart
@@ -87,8 +88,8 @@ def _ensure_current_session_cart(self) -> db.Key:
8788
root_node["is_root_node"] = True
8889
root_node["name"] = root_node.name.getDefaultValue(root_node)
8990
root_node["cart_type"] = CartType.BASKET
90-
key = root_node.toDB()
91-
self.session["session_cart_key"] = key
91+
root_node.write()
92+
self.session["session_cart_key"] = root_node["key"]
9293
current.session.get().markChanged()
9394
# Store basket at the user skel, it will be shared over multiple sessions / devices
9495
if user := current.user.get():
@@ -106,9 +107,9 @@ def detach_session_cart(self) -> db.Key:
106107
@staticmethod
107108
def _set_basket_txn(user_key: db.Key, basket_key: db.Key | None) -> SkeletonInstance:
108109
user_skel = conf.main_app.vi.user.editSkel()
109-
user_skel.fromDB(user_key)
110+
user_skel.read(user_key)
110111
user_skel.setBoneValue("basket", basket_key)
111-
user_skel.toDB()
112+
user_skel.write()
112113
return user_skel
113114

114115
def get_available_root_nodes(self, *args, **kwargs) -> list[dict[t.Literal["name", "key"], str]]:
@@ -160,7 +161,7 @@ def is_valid_node(
160161
"""
161162
# TODO: return (okay_status, reason, skel) tuple/Dataclass?
162163
skel = self.viewSkel("node")
163-
if not skel.fromDB(node_key):
164+
if not skel.read(node_key):
164165
logger.debug(f"fail reason: 404")
165166
return False
166167
logger.debug(f'{skel=}')
@@ -224,7 +225,7 @@ def cart_get(
224225
if not isinstance(cart_key, db.Key):
225226
raise TypeError(f"cart_key must be an instance of db.Key")
226227
skel = self.viewSkel(skel_type)
227-
if not skel.fromDB(cart_key):
228+
if not skel.read(cart_key):
228229
logger.debug(f"Cart {cart_key} does not exist")
229230
return None
230231
if not self.canView(skel_type, skel):
@@ -278,13 +279,13 @@ def add_or_update_article(
278279
res = skel.setBoneValue("article", article_key)
279280
skel["parententry"] = parent_cart_key
280281
parent_skel = self.viewSkel("node")
281-
assert parent_skel.fromDB(parent_cart_key)
282+
assert parent_skel.read(parent_cart_key)
282283
if parent_skel["is_root_node"]:
283284
skel["parentrepo"] = parent_skel["key"]
284285
else:
285286
skel["parentrepo"] = parent_skel["parentrepo"]
286287
article_skel: SkeletonInstance = self.shop.article_skel()
287-
if not article_skel.fromDB(article_key):
288+
if not article_skel.read(article_key):
288289
raise errors.NotFound(f"Article with key {article_key=} does not exist!")
289290
if not article_skel["shop_listed"]:
290291
# logger.debug(f"not listed: {article_skel=}")
@@ -337,7 +338,7 @@ def add_or_update_article(
337338
descr_appendix=f'Quantity of free article cannot be greater than 1! (reached {skel["quantity"]})'
338339
)
339340
skel = self.additional_add_or_update_article(skel, **kwargs)
340-
key = skel.toDB()
341+
skel.write()
341342
EVENT_SERVICE.call(Event.ARTICLE_CHANGED, skel=skel, deleted=False)
342343
self.clear_children_cache()
343344
# TODO: Validate quantity with hook (stock availability)
@@ -346,7 +347,7 @@ def add_or_update_article(
346347
skel=parent_skel
347348
)
348349
EVENT_SERVICE.call(Event.CART_CHANGED, skel=parent_skel, deleted=False)
349-
parent_skel.toDB()
350+
parent_skel.write()
350351

351352
return skel
352353

@@ -370,7 +371,7 @@ def move_article(
370371
parent_skel = self.viewSkel("node")
371372
if not self.is_valid_node(new_parent_cart_key):
372373
raise e.InvalidArgumentException("parent_cart_key", parent_cart_key)
373-
if not parent_skel.fromDB(new_parent_cart_key):
374+
if not parent_skel.read(new_parent_cart_key):
374375
raise e.InvalidArgumentException(
375376
"new_parent_cart_key", new_parent_cart_key,
376377
f"Target cart node does not exist"
@@ -381,7 +382,7 @@ def move_article(
381382
f"Target cart node is inside a different repo"
382383
)
383384
skel["parententry"] = new_parent_cart_key
384-
skel.toDB()
385+
skel.write()
385386
EVENT_SERVICE.call(Event.ARTICLE_CHANGED, skel=skel, deleted=False)
386387
return skel
387388

@@ -415,7 +416,7 @@ def cart_add(
415416
discount_key=discount_key,
416417
)
417418
skel = self.additional_cart_add(skel, **kwargs)
418-
skel.toDB()
419+
skel.write()
419420
EVENT_SERVICE.call(Event.CART_CHANGED, skel=skel, deleted=False)
420421
self.onAdded("node", skel)
421422
return skel
@@ -445,7 +446,7 @@ def cart_update(
445446
# TODO: must be inside a own root node ...
446447
# if not self.canEdit(skel):
447448
# raise errors.Forbidden
448-
assert skel.fromDB(cart_key)
449+
assert skel.read(cart_key)
449450
skel = self._cart_set_values(
450451
skel=skel,
451452
parent_cart_key=parent_cart_key,
@@ -456,7 +457,7 @@ def cart_update(
456457
discount_key=discount_key,
457458
)
458459
self.additional_cart_update(skel, **kwargs)
459-
skel.toDB()
460+
skel.write()
460461
EVENT_SERVICE.call(Event.CART_CHANGED, skel=skel, deleted=False)
461462
return skel
462463

@@ -482,7 +483,7 @@ def _cart_set_values(
482483
if not self.is_valid_node(parent_cart_key):
483484
raise e.InvalidArgumentException("parent_cart_key", parent_cart_key)
484485
parent_skel = self.viewSkel("node")
485-
assert parent_skel.fromDB(parent_cart_key)
486+
assert parent_skel.read(parent_cart_key)
486487
if parent_skel["is_root_node"]:
487488
skel["parentrepo"] = parent_skel["key"]
488489
else:
@@ -531,10 +532,12 @@ def cart_remove(
531532
self,
532533
cart_key: db.Key,
533534
) -> None:
534-
self.deleteRecursive(cart_key)
535535
skel = self.editSkel("node")
536-
if not skel.fromDB(cart_key):
536+
if not skel.read(cart_key):
537537
raise errors.NotFound
538+
# This delete could fail if the cart is used by an order
539+
skel.delete()
540+
self.deleteRecursive(cart_key)
538541
if skel["parententry"] is None or skel["is_root_node"]:
539542
logger.info(f"{skel['key']} was a root node!")
540543
# raise NotImplementedError("Cannot delete root node")
@@ -543,7 +546,6 @@ def cart_remove(
543546
self.detach_session_cart()
544547
# del self.session["session_cart_key"]
545548
# current.session.get().markChanged()
546-
skel.delete()
547549
EVENT_SERVICE.call(Event.CART_CHANGED, skel=skel, deleted=True)
548550

549551
# --- Hooks ---------------------------------------------------------------
@@ -629,14 +631,14 @@ def freeze_cart(
629631
# ensure each article still exists and shop_listed is True
630632
return NotImplemented
631633
cart_skel = self.editSkel("node")
632-
assert cart_skel.fromDB(cart_key)
634+
assert cart_skel.read(cart_key)
633635
"""
634636
skel["shop_vat_rate_value"] = self.shop.vat_rate.get_vat_rate_for_country(
635637
country=order_skel["billing_address"]["dest"]["country"],
636638
category=article_skel["shop_vat_rate_category"],
637639
)
638640
"""
639-
cart_skel.toDB()
641+
cart_skel.write()
640642

641643
# -------------------------------------------------------------------------
642644

@@ -646,13 +648,13 @@ def get_discount_for_leaf(
646648
) -> list[SkeletonInstance]:
647649
if isinstance(leaf_key_or_skel, db.Key):
648650
skel = self.viewSkel("leaf")
649-
skel.fromDB(leaf_key_or_skel)
651+
skel.read(leaf_key_or_skel)
650652
else:
651653
skel = leaf_key_or_skel
652654
discounts = []
653655
while (pk := skel["parententry"]):
654656
skel = self.viewSkel("node", sub_skel="discount")
655-
if not skel.fromDB(pk):
657+
if not skel.read(pk):
656658
raise InvalidStateError(f"{pk=} doesn't exist!")
657659
if discount := skel["discount"]:
658660
discounts.append(discount["dest"])
@@ -665,10 +667,32 @@ def add_new_parent(self, leaf_skel, **kwargs):
665667
for key, value in kwargs.items():
666668
new_parent_skel[key] = value # TODO: use .setBoneValue?
667669
self.onAdd("node", new_parent_skel)
668-
new_parent_skel.toDB()
670+
new_parent_skel.write()
669671
self.onAdded("node", new_parent_skel)
670672
EVENT_SERVICE.call(Event.CART_CHANGED, skel=new_parent_skel, deleted=False)
671673
leaf_skel["parententry"] = new_parent_skel["key"]
672-
leaf_skel.toDB()
674+
leaf_skel.write()
673675
EVENT_SERVICE.call(Event.ARTICLE_CHANGED, skel=leaf_skel, deleted=False)
674676
return new_parent_skel
677+
678+
679+
try:
680+
Session.on_delete
681+
except AttributeError: # backward compatibility for viur-core
682+
from viur.core.version import __version__
683+
684+
logger.warning(f"viur-core {__version__} has no Session.on_delete")
685+
Session.on_delete = lambda *_, **__: None
686+
687+
688+
@Session.on_delete
689+
def delete_guest_cart(session: db.Entity) -> None:
690+
"""Delete carts from guest sessions to avoid orphaned carts"""
691+
if session["user"] != Session.GUEST_USER:
692+
return
693+
try:
694+
cart = session["data"]["shop"]["cart"]["session_cart_key"]
695+
except (KeyError, TypeError):
696+
return
697+
SHOP_INSTANCE.get().cart.cart_remove(cart)
698+
logger.debug(f"Deleted {cart=} and children after deleting {session=}")

src/viur/shop/modules/discount.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def search(
5151

5252
skel = self.viewSkel()
5353
if discount_key is not None:
54-
if not skel.fromDB(discount_key):
54+
if not skel.read(discount_key):
5555
raise errors.NotFound
5656
return [skel]
5757
elif code is not None:
@@ -154,7 +154,7 @@ def apply(
154154
for leaf_skel in leaf_skels:
155155
# Assign discount on new parent node for the leaf where the article is
156156
parent_skel = self.shop.cart.viewSkel("node")
157-
assert parent_skel.fromDB(leaf_skel["parententry"])
157+
assert parent_skel.read(leaf_skel["parententry"])
158158
if parent_skel["discount"] and parent_skel["discount"]["dest"]["key"] == discount_skel["key"]:
159159
logger.info("Parent has already this discount key")
160160
continue
@@ -191,7 +191,7 @@ def can_apply(
191191
cart = None
192192
else:
193193
cart = self.shop.cart.viewSkel("node")
194-
if not cart.fromDB(cart_key):
194+
if not cart.read(cart_key):
195195
raise errors.NotFound
196196

197197
if not as_automatically and skel["activate_automatically"]:
@@ -226,7 +226,7 @@ def remove(
226226

227227
discount_skel = self.viewSkel()
228228

229-
if not discount_skel.fromDB(discount_key):
229+
if not discount_skel.read(discount_key):
230230
raise errors.NotFound
231231
try:
232232
# Todo what we do when we have more than more condition

src/viur/shop/modules/discount_condition.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ def on_change(self, skel, event: str):
100100
# logger.debug(pprint.pformat(skel, width=120))
101101
skel_old = self.viewSkel()
102102
if skel["key"] is not None: # not on add
103-
skel_old.fromDB(skel["key"])
103+
skel_old.read(skel["key"])
104104
current.request_data.get()[f'shop_skel_{skel["key"]}'] = skel_old
105105

106106
def on_changed(self, skel, event: str):
@@ -119,7 +119,7 @@ def generate_subcodes(self, parent_key: db.Key, prefix: str, amount: int):
119119
"""Generate subcodes for a parent individual code."""
120120
chunk_amount = amount
121121
while chunk_amount > 0:
122-
skel = self.addSkel() # .subSkel("individual")
122+
skel = self.addSkel() # .subskel("individual")
123123
skel["is_subcode"] = True
124124
skel["quantity_volume"] = 1
125125
skel.setBoneValue("parent_code", parent_key)
@@ -131,7 +131,7 @@ def generate_subcodes(self, parent_key: db.Key, prefix: str, amount: int):
131131

132132
try:
133133
self.onAdd(skel)
134-
skel.toDB()
134+
skel.write()
135135
self.onAdded(skel)
136136
break
137137
except ValueError as e:
@@ -164,7 +164,7 @@ def get_skel(cls, key: db.Key, expires: int = 3600) -> SkeletonInstance_T["Disco
164164
def _get_skel(key: db.Key, ttl_hash: int | None = None) -> SkeletonInstance_T["DiscountConditionSkel"] | None:
165165
# logger.debug(f"_get_skel({key=}, {ttl_hash=})")
166166
skel = SHOP_INSTANCE.get().discount_condition.viewSkel()
167-
if not skel.fromDB(key):
167+
if not skel.read(key):
168168
return None
169169
return skel # type: ignore
170170

@@ -175,7 +175,7 @@ def get_by_code(self, code: str = None) -> t.Iterator[SkeletonInstance]:
175175
for cond_skel in query.fetch(100):
176176
if cond_skel["is_subcode"]:
177177
parent_cond_skel = self.viewSkel()
178-
assert parent_cond_skel.fromDB(cond_skel["parent_code"]["dest"]["key"])
178+
assert parent_cond_skel.read(cond_skel["parent_code"]["dest"]["key"])
179179
yield parent_cond_skel
180180
# yield cond_skel["parent_code"]["dest"]
181181
else:
@@ -202,7 +202,7 @@ def mark_discount_used(order_skel, payment):
202202

203203
for discount in discounts:
204204
d_skel = self.shop.discount.viewSkel()
205-
d_skel.fromDB(discount)
205+
d_skel.read(discount)
206206
for condition in d_skel["condition"]:
207207
# TODO: Increase only "active" conditions in case of OR operator
208208
# cond_skel = toolkit.get_full_skel_from_ref_skel(condition["dest"])

0 commit comments

Comments
 (0)