diff --git a/commerce/README.txt b/commerce/README.txt index 9952315..f381c16 100644 --- a/commerce/README.txt +++ b/commerce/README.txt @@ -78,15 +78,31 @@ Customers Carts and Orders ================ - >>> from cybertools.commerce.order import OrderItems - >>> component.provideAdapter(OrderItems) +A cart is just a collection of order items belonging to a certain customer +(or some other kind of party). >>> orderItems = manager.orderItems - >>> orderItems.add(p001, c001, quantity=3) - + >>> orderItems.add(p001, c001, shop=shop1, quantity=3) + + + >>> orderItems.getCart(c001) + [] Orders ------ +The items in a shopping cart may be included in an order. + >>> ord001 = manager.orders.create(u'001', shop=shop1, customer=c001) + + >>> for item in orderItems.getCart(c001): + ... item.setOrder(ord001) + +Now the default cart is empty; we now have to supply the order for +retrieving the order items. + + >>> orderItems.getCart(c001) + [] + >>> orderItems.getCart(c001, ord001) + [] diff --git a/commerce/common.py b/commerce/common.py index 8d857dc..a5a1c78 100644 --- a/commerce/common.py +++ b/commerce/common.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2008 Helmut Merz helmutm@cy55.de +# Copyright (c) 2009 Helmut Merz helmutm@cy55.de # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -22,6 +22,9 @@ Common functionality. $Id$ """ +from zope.app.intid.interfaces import IIntIds +from zope import component + class ContainerAttribute(object): @@ -37,9 +40,11 @@ class ContainerAttribute(object): for k, v in kw.items(): setattr(obj, k, v) self.data[id] = obj + component.getUtility(IIntIds).register(obj) return obj def remove(self, id): + component.getUtility(IIntIds).unregister(self.data[id]) del self.data[id] def get(self, id, default=None): @@ -110,3 +115,16 @@ class BaseObject(object): collection = RelationSet + +# utility functions + +def getUidForObject(obj, intIds=None): + if intIds is None: + intIds = component.getUtility(IIntIds) + return intIds.getId(obj) + +def getObjectForUid(uid, intIds=None): + if intIds is None: + intIds = component.getUtility(IIntIds) + return intIds.getObject(uid) + diff --git a/commerce/customer.py b/commerce/customer.py index cd932b8..00aa0af 100644 --- a/commerce/customer.py +++ b/commerce/customer.py @@ -40,7 +40,7 @@ class Customer(BaseObject): self.orders = self.collection(self, 'customer') -class Address(object): +class Address(BaseObject): implements(IAddress) diff --git a/commerce/interfaces.py b/commerce/interfaces.py index fa6e2dc..8106fb1 100644 --- a/commerce/interfaces.py +++ b/commerce/interfaces.py @@ -335,14 +335,42 @@ class IOrderItem(Interface): default=1, required=True) - order = Attribute(u'The order this order item belongs to.') product = Attribute(u'The product represented by this order item.') + party = Attribute(u'The party (person, customer, session, ...) ' + u'that is ordering the product.') + shop = Attribute(u'The shop from which the product is ordered.') + order = Attribute(u'The order this order item belongs to.') unitPrice = Attribute(u'The basic unit price for one of the product ' u'items ordered.') fullPrice = Attribute(u'The full price for the quantity ordered.') + def setOrder(order): + """ Assign the order given to the order item. + """ + class IOrderItems(Interface): """ A collection of order items. """ + def __getitem__(key): + """ Return the order item identified by the key given. + """ + + def __iter__(): + """ Return an iterator of all order items. + """ + + def query(**criteria): + """ Search for order items. Possible criteria are: + product, party, order, run, timeFrom, timeTo. + """ + + def add(product, party, shop, order='???', run=0, **kw): + """ Create and register an order item; return it. Additional properties + may be specified via keyword arguments. + """ + + def getCart(party, order='???', shop=None, run=0, **kw): + """ Return a collection of order items. + """ diff --git a/commerce/order.py b/commerce/order.py index 269ca5a..4dff4b8 100644 --- a/commerce/order.py +++ b/commerce/order.py @@ -22,16 +22,20 @@ Order and order item classes. $Id$ """ +from zope.app.intid.interfaces import IIntIds +from zope.cachedescriptors.property import Lazy +from zope import component from zope.component import adapts from zope.interface import implements, Interface +from cybertools.commerce.common import getUidForObject, getObjectForUid from cybertools.commerce.common import Relation, BaseObject from cybertools.commerce.interfaces import IOrder, IOrderItem, IOrderItems from cybertools.tracking.btree import Track from cybertools.tracking.interfaces import ITrackingStorage -class Order(object): +class Order(BaseObject): implements(IOrder) @@ -47,11 +51,34 @@ class OrderItem(Track): implements(IOrderItem) + metadata_attributes = Track.metadata_attributes + ('order',) + index_attributes = metadata_attributes + typeName = 'OrderItem' + def __getattr__(self, attr): if attr not in IOrderItem: raise AttributeError(attr) return self.data.get(attr) + def getParent(self): + return IOrderItems(self.__parent__) + + def getObject(self, ref): + if isinstance(ref, int): + return getObjectForUid(ref) + if isinstance(ref, basestring): + if ref.isdigit: + return getObjectForUid(int(ref)) + if ':' in ref: + tp, id = ref.split(':', 1) + return (tp, id) + return ref + + def setOrder(self, order): + parent = self.getParent() + self.order = parent.getUid(order) + parent.context.indexTrack(0, self, 'order') + class OrderItems(object): """ A tracking storage adapter managing order items. @@ -71,14 +98,40 @@ class OrderItems(object): def query(self, **criteria): if 'product' in criteria: - criteria['taskId'] = criteria.pop('product') - if 'person' in criteria: - criteria['userName'] = criteria.pop('person') + criteria['taskId'] = self.getUid(criteria.pop('product')) + if 'party' in criteria: + criteria['userName'] = self.getUid(criteria.pop('party')) + if 'order' in criteria: + criteria['order'] = self.getUid(criteria.pop('order')) if 'run' in criteria: criteria['runId'] = criteria.pop('run') return self.context.query(**criteria) - def add(self, product, person, run=0, **kw): - trackId = self.context.saveUserTrack(product, run, person, kw) + def add(self, product, party, shop, order='???', run=0, **kw): + kw['shop'] = self.getUid(shop) + trackId = self.context.saveUserTrack(self.getUid(product), run, + self.getUid(party), kw) track = self[trackId] + track.order = self.getUid(order) + self.context.indexTrack(0, track, 'order') return track + + def getCart(self, party, order='???', shop=None, run=None, **kw): + if run: + kw['run'] = run + result = self.query(party=party, order=order, **kw) + if shop is None: + return list(result) + return [item for item in result if item.shop == shop] + + # utility methods + + @Lazy + def intIds(self): + return component.getUtility(IIntIds) + + def getUid(self, obj): + if isinstance(obj, BaseObject): + return getUidForObject(obj, self.intIds) + return obj + diff --git a/commerce/shop.py b/commerce/shop.py index 9e4e2fd..3290916 100644 --- a/commerce/shop.py +++ b/commerce/shop.py @@ -24,11 +24,11 @@ $Id$ from zope.interface import implements -from cybertools.commerce.common import RelationSet +from cybertools.commerce.common import RelationSet, BaseObject from cybertools.commerce.interfaces import IShop -class Shop(object): +class Shop(BaseObject): implements(IShop) diff --git a/commerce/tests.py b/commerce/tests.py index 85ce6d1..c5b7f4c 100755 --- a/commerce/tests.py +++ b/commerce/tests.py @@ -8,8 +8,49 @@ $Id$ import unittest, doctest from zope.testing.doctestunit import DocFileSuite + +from zope.app.intid.interfaces import IIntIds +from zope import component +from zope.interface import implements + +from cybertools.commerce.order import OrderItems from cybertools.commerce.product import Product + +class IntIdsStub(object): + """A testing stub (mock utility) for IntIds.""" + implements(IIntIds) + + def __init__(self): + self.objs = [] + + def getObject(self, uid): + return self.objs[uid] + + def register(self, ob): + if ob not in self.objs: + self.objs.append(ob) + return self.objs.index(ob) + + getId = register + queryId = getId + + def unregister(self, ob): + id = self.getId(ob) + self.objs[id] = None + + def __iter__(self): + return iter(xrange(len(self.objs))) + + +def setUp(testCase): + component.provideUtility(IntIdsStub()) + component.provideAdapter(OrderItems) + +def tearDown(testCase): + pass + + class Test(unittest.TestCase): "Basic tests." @@ -21,7 +62,8 @@ def test_suite(): flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS return unittest.TestSuite(( unittest.makeSuite(Test), - DocFileSuite('README.txt', optionflags=flags), + DocFileSuite('README.txt', optionflags=flags, + setUp=setUp, tearDown=tearDown), )) if __name__ == '__main__':