diff --git a/cyberapps/bsm/README.txt b/cyberapps/bsm/README.txt
new file mode 100644
index 0000000..42d1f4c
--- /dev/null
+++ b/cyberapps/bsm/README.txt
@@ -0,0 +1,7 @@
+========================
+Berlin School Management
+========================
+
+School Information
+==================
+
diff --git a/cyberapps/bsm/__init__.py b/cyberapps/bsm/__init__.py
new file mode 100644
index 0000000..38314f3
--- /dev/null
+++ b/cyberapps/bsm/__init__.py
@@ -0,0 +1,3 @@
+"""
+$Id$
+"""
diff --git a/cyberapps/bsm/browser.py b/cyberapps/bsm/browser.py
new file mode 100644
index 0000000..03679d8
--- /dev/null
+++ b/cyberapps/bsm/browser.py
@@ -0,0 +1,138 @@
+#
+# Copyright (c) 2007 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
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+
+"""
+View classes for the BSM (Berlin School Management) project.
+
+$Id$
+"""
+
+from zope import interface, component
+from zope.app.pagetemplate import ViewPageTemplateFile
+from zope.cachedescriptors.property import Lazy
+
+from cybertools.composer.schema import Schema
+from cybertools.composer.schema import Field
+from cybertools.reporter.browser.report import DetailView, ListingView
+from cybertools.reporter.resultset import ResultSet, Cell
+from loops.browser.concept import ConceptView
+from loops.browser.node import NodeView
+from loops.common import adapted
+from loops.browser.common import conceptMacrosTemplate
+from loops import util
+
+
+bsmTemplate = ViewPageTemplateFile('macros.pt')
+
+
+class TitleCell(Cell):
+
+ @property
+ def url(self):
+ view = self.row.resultSet.view
+ if view is None:
+ return u''
+ return ('%s/.target%s' %
+ (view.url, util.getUidForObject(self.row.context.context)))
+
+
+class UrlCell(Cell):
+
+ limit = None
+
+ @property
+ def url(self):
+ value = self.value
+ if not value:
+ return ''
+ if self.field.name == 'email':
+ return 'mailto:' + value
+ if value.startswith('http'):
+ return value
+ return 'http://' + value
+
+ @property
+ def text(self):
+ text = super(UrlCell, self).text
+ if self.limit and len(text) > self.limit:
+ text = text[:self.limit-1] + '...'
+ return text
+
+
+class UrlCellWithLimit(UrlCell):
+
+ limit = 20
+
+
+class SchoolDetails(DetailView):
+
+ conceptMacros = conceptMacrosTemplate
+
+ @property
+ def macro(self):
+ return bsmTemplate.macros['detail']
+
+ @Lazy
+ def nodeView(self):
+ return NodeView(self.context, self.request)
+
+ def resources(self):
+ for obj in self.context.getResources():
+ yield ConceptView(obj, self.request)
+
+ @Lazy
+ def resultSet(self):
+ result = ResultSet([adapted(self.context)])
+ result.schema = Schema(
+ Field(u'title', u'Name'),
+ Field(u'address', u'Anschrift'),
+ Field(u'headMaster', u'Rektor'),
+ Field(u'telephone', u'Telefon'),
+ Field(u'telefax', u'Telefax'),
+ Field(u'email', u'E-Mail', renderFactory=UrlCell),
+ Field(u'website', u'Web', renderFactory=UrlCell),
+ )
+ result.view = self.nodeView
+ return result
+
+
+class SchoolListing(ListingView):
+
+ @property
+ def children(self):
+ for obj in self.nodeView.virtualTargetObject.getChildren():
+ yield adapted(obj)
+
+ @Lazy
+ def nodeView(self):
+ return NodeView(self.context, self.request)
+
+ @Lazy
+ def resultSet(self):
+ result = ResultSet(self.children)
+ result.schema = Schema(
+ Field(u'title', u'Name', renderFactory=TitleCell),
+ Field(u'address', u'Anschrift'),
+ Field(u'headMaster', u'Rektor'),
+ Field(u'telephone', u'Telefon'),
+ Field(u'telefax', u'Telefax'),
+ Field(u'email', u'E-Mail', renderFactory=UrlCellWithLimit),
+ Field(u'website', u'Web', renderFactory=UrlCellWithLimit),
+ )
+ result.view = self.nodeView
+ return result
diff --git a/cyberapps/bsm/configure.zcml b/cyberapps/bsm/configure.zcml
new file mode 100644
index 0000000..bc09002
--- /dev/null
+++ b/cyberapps/bsm/configure.zcml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cyberapps/bsm/data.py b/cyberapps/bsm/data.py
new file mode 100644
index 0000000..ff29839
--- /dev/null
+++ b/cyberapps/bsm/data.py
@@ -0,0 +1,42 @@
+#
+# Copyright (c) 2007 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
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+
+"""
+Classes for BSM School Informations.
+
+$Id$
+"""
+
+from zope.component import adapts
+from zope.interface import implements
+
+from cyberapps.bsm.interfaces import ISchoolInfo
+from loops.common import AdapterBase
+from loops.interfaces import IConcept
+from loops.type import TypeInterfaceSourceList
+
+
+TypeInterfaceSourceList.typeInterfaces += (ISchoolInfo,)
+
+
+class SchoolInfoAdapter(AdapterBase):
+
+ implements(ISchoolInfo)
+
+ _contextAttributes = list(ISchoolInfo) + list(IConcept)
+
diff --git a/cyberapps/bsm/interfaces.py b/cyberapps/bsm/interfaces.py
new file mode 100644
index 0000000..5ade8d5
--- /dev/null
+++ b/cyberapps/bsm/interfaces.py
@@ -0,0 +1,62 @@
+#
+# Copyright (c) 2007 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
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+
+"""
+Interfaces for BSM (Berlin School Managment).
+
+$Id$
+"""
+
+from zope.interface import Interface, Attribute
+from zope import schema
+
+from loops.util import _
+
+
+class ISchoolInfo(Interface):
+ """ Information about schools taking part in the Berlin School Management
+ (BSM) project.
+
+ The name of the school is in the ``title`` attribute.
+ """
+
+ address = schema.TextLine(
+ title=_(u'Address'),
+ description=_(u'Postal address of the school'),
+ required=False,)
+ headMaster = schema.TextLine(
+ title=_(u'Headmaster'),
+ description=_(u'Name of the head master of the school'),
+ required=False,)
+ telephone = schema.TextLine(
+ title=_(u'Telephone'),
+ description=_(u'Telephone number of the headmaster'),
+ required=False,)
+ telefax = schema.TextLine(
+ title=_(u'Telefax'),
+ description=_(u'Telefax number of the headmaster'),
+ required=False,)
+ email = schema.TextLine(
+ title=_(u'Email'),
+ description=_(u'Email address of the headmaster'),
+ required=False,)
+ website = schema.TextLine(
+ title=_(u'Website'),
+ description=_(u'URL of the website of the school'),
+ required=False,)
+
diff --git a/cyberapps/bsm/macros.pt b/cyberapps/bsm/macros.pt
new file mode 100644
index 0000000..e3d2452
--- /dev/null
+++ b/cyberapps/bsm/macros.pt
@@ -0,0 +1,30 @@
+
+
+
+
+
+ Something
+
+
+
+
+
+
+
diff --git a/cyberapps/commerce/README.txt b/cyberapps/commerce/README.txt
new file mode 100644
index 0000000..a50e8b0
--- /dev/null
+++ b/cyberapps/commerce/README.txt
@@ -0,0 +1,187 @@
+====================
+eCommerce with loops
+====================
+
+Note: This package depends on loops.
+
+Let's do some basic set up
+
+ >>> from zope.app.testing.setup import placefulSetUp, placefulTearDown
+ >>> site = placefulSetUp(True)
+
+ >>> from zope import component, interface
+
+and setup a simple loops site with a concept manager and some concepts
+(with all the type machinery, what in real life is done via standard
+ZCML setup):
+
+ >>> from cyberapps.commerce.tests import TestSite
+ >>> t = TestSite(site)
+ >>> concepts, resources, views = t.setup()
+
+We also collect here some basic imports we'll need later.
+
+ >>> from loops.concept import Concept
+ >>> from loops.common import adapted
+ >>> from loops.setup import addAndConfigureObject
+
+We also use an adapter to the concept manager for accessing the commerce
+objects.
+
+ >>> from cyberapps.commerce.manager import Manager
+ >>> manager = Manager(concepts)
+
+
+Shops and Products
+==================
+
+Let's start with two shops:
+
+ >>> shop1 = manager.shops.create('shop1', title='PC up Ltd')
+ >>> shop2 = manager.shops.create('shop2', title='Video up Ltd')
+
+ >>> len(list(manager.shops))
+ 2
+
+ >>> shop1.title
+ 'PC up Ltd'
+
+Now we create a few products ...
+
+ >>> p001 = manager.products.create('001', title='Silent Case')
+ >>> p002 = manager.products.create('002', title='Portable Projector')
+ >>> p003 = manager.products.create('003', title='HD Flatscreen Monitor')
+ >>> p004 = manager.products.create('004', title='Giga Mainboard')
+
+ >>> p001.title
+ 'Silent Case'
+ >>> p001.fullDescription
+
+... and add them to the shops.
+
+ >>> shop1.products.add(p001)
+ >>> shop1.products.add(p003)
+ >>> shop1.products.add(p004)
+ >>> shop2.products.add(p002)
+ >>> shop2.products.add(p003)
+
+We can now list the products in a shop.
+
+ >>> sorted((p.productId, p.title) for p in shop1.products)
+ [('001', 'Silent Case'), ('003', 'HD Flatscreen Monitor'),
+ ('004', 'Giga Mainboard')]
+
+ >>> sorted((s.name, s.title) for s in p003.shops)
+ [('shop1', 'PC up Ltd'), ('shop2', 'Video up Ltd')]
+
+Categories
+----------
+
+ >>> cat001 = manager.categories.create('001', title='Cases')
+ >>> p001.categories.add(cat001)
+
+
+Customers
+=========
+
+Now let's add a few customers.
+
+ >>> c001 = manager.customers.create('001', title='Your Local Computer Store')
+ >>> c002 = manager.customers.create('002', title='Speedy Gonzales')
+ >>> c003 = manager.customers.create('003', title='TeeVee')
+ >>> c004 = manager.customers.create('004', title='MacVideo')
+
+These are stored in a separate ConceptManager object.
+
+ >>> customers = concepts.getLoopsRoot()['customers']
+ >>> len(customers)
+ 4
+
+In the testing scenario we have to index all the customer objects.
+
+ >>> from zope.app.catalog.interfaces import ICatalog
+ >>> catalog = component.getUtility(ICatalog)
+ >>> from loops import util
+ >>> for r in customers.values():
+ ... catalog.index_doc(int(util.getUidForObject(r)), r)
+
+Now the customers can be accessed via the standard concept manager adapter.
+
+ >>> manager.customers.get('004')
+
+
+ >>> shop1.customers.add(c001)
+ >>> shop1.customers.add(c002)
+ >>> shop1.customers.add(c004)
+ >>> shop2.customers.add(c002)
+ >>> shop2.customers.add(c003)
+ >>> shop2.customers.add(c004)
+
+ >>> sorted((c.customerId, c.title) for c in shop1.customers)
+ [('001', 'Your Local Computer Store'), ('002', 'Speedy Gonzales'),
+ ('004', 'MacVideo')]
+
+ >>> sorted((s.name, s.title) for s in c002.shops)
+ [('shop1', 'PC up Ltd'), ('shop2', 'Video up Ltd')]
+
+
+Carts and Orders
+================
+
+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, shop=shop1, quantity=3)
+
+
+ >>> orderItems.getCart(c001)
+ []
+
+Orders
+------
+
+The items in a shopping cart may be included in an order.
+
+ >>> ord001 = manager.orders.create('001', shop=shop1, customer=c001)
+
+ >>> for item in orderItems.getCart(c001):
+ ... item.setOrder(ord001)
+
+Now the default cart is empty; we have to supply the order for
+retrieving the order items. But now we can omit the customer from the query.
+
+ >>> orderItems.getCart(c001)
+ []
+ >>> orderItems.getCart(c001, ord001)
+ []
+ >>> orderItems.getCart(order=ord001)
+ []
+
+
+Administrative Views and Forms
+==============================
+
+ >>> from zope.publisher.browser import TestRequest
+
+Listings
+--------
+
+ >>> from cyberapps.commerce.browser.base import SimpleListing
+
+Forms
+-----
+
+ >>> from loops.view import Node
+ >>> from cyberapps.commerce.browser.product import CreateProductPage
+
+ >>> home = addAndConfigureObject(views, Node, 'home', target=cat001.context)
+
+ >>> form = CreateProductPage(home, TestRequest())
+
+
+Fin de partie
+=============
+
+ >>> placefulTearDown()
diff --git a/cyberapps/commerce/__init__.py b/cyberapps/commerce/__init__.py
new file mode 100644
index 0000000..38314f3
--- /dev/null
+++ b/cyberapps/commerce/__init__.py
@@ -0,0 +1,3 @@
+"""
+$Id$
+"""
diff --git a/cyberapps/commerce/browser/__init__.py b/cyberapps/commerce/browser/__init__.py
new file mode 100644
index 0000000..38314f3
--- /dev/null
+++ b/cyberapps/commerce/browser/__init__.py
@@ -0,0 +1,3 @@
+"""
+$Id$
+"""
diff --git a/cyberapps/commerce/browser/action.py b/cyberapps/commerce/browser/action.py
new file mode 100644
index 0000000..3eaf930
--- /dev/null
+++ b/cyberapps/commerce/browser/action.py
@@ -0,0 +1,58 @@
+#
+# 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
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+
+"""
+Action definitions for loops-based eCommerce applications.
+
+$Id$
+"""
+
+from cyberapps.commerce.util import _
+from cybertools.browser.action import actions
+from loops.browser.action import DialogAction, TargetAction
+
+
+actions.register('create_product', 'portlet', TargetAction,
+ title=_(u'Create Product...'),
+ description=_(u'Create a new product.'),
+ viewName='create_product_page.html',
+)
+
+actions.register('edit_product', 'portlet', TargetAction,
+ title=_(u'Edit Product...'),
+ description=_(u'Modify product data.'),
+ viewName='edit_product_page.html',
+)
+
+actions.register('create_category', 'portlet', TargetAction,
+ title=_(u'Create Catgory...'),
+ description=_(u'Create a new category.'),
+ viewName='create_category_page.html',
+)
+
+actions.register('edit_category', 'portlet', TargetAction,
+ title=_(u'Edit Category...'),
+ description=_(u'Modify category data.'),
+ viewName='edit_category_page.html',
+)
+
+actions.register('edit_customer', 'portlet', TargetAction,
+ title=_(u'Edit Customer...'),
+ description=_(u'Modify customer data.'),
+ viewName='edit_concept_page.html',
+)
diff --git a/cyberapps/commerce/browser/base.pt b/cyberapps/commerce/browser/base.pt
new file mode 100644
index 0000000..48a2dc8
--- /dev/null
+++ b/cyberapps/commerce/browser/base.pt
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+ -
+ Something
+
+ -
+ Description
+
+
+
+
+
diff --git a/cyberapps/commerce/browser/base.py b/cyberapps/commerce/browser/base.py
new file mode 100644
index 0000000..6c67d82
--- /dev/null
+++ b/cyberapps/commerce/browser/base.py
@@ -0,0 +1,41 @@
+#
+# 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
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+
+"""
+Common base/common view classes for loops-based eCommerce applications.
+
+$Id$
+"""
+
+from zope.app.pagetemplate import ViewPageTemplateFile
+from zope.cachedescriptors.property import Lazy
+from zope import component
+
+from loops.browser.concept import ConceptView
+from loops.common import adapted
+from loops import util
+
+
+base_macros = ViewPageTemplateFile('base.pt')
+
+
+class SimpleListing(ConceptView):
+
+ @Lazy
+ def macro(self):
+ return base_macros.macros['simple_listing']
diff --git a/cyberapps/commerce/browser/configure.zcml b/cyberapps/commerce/browser/configure.zcml
new file mode 100644
index 0000000..2c06868
--- /dev/null
+++ b/cyberapps/commerce/browser/configure.zcml
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cyberapps/commerce/browser/customer.py b/cyberapps/commerce/browser/customer.py
new file mode 100644
index 0000000..81e5e3a
--- /dev/null
+++ b/cyberapps/commerce/browser/customer.py
@@ -0,0 +1,37 @@
+#
+# 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
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+
+"""
+View classes for customers and related objects.
+
+$Id$
+"""
+
+from zope.app.pagetemplate import ViewPageTemplateFile
+from zope.cachedescriptors.property import Lazy
+
+from loops.browser.concept import ConceptView
+from loops.common import adapted
+from loops import util
+
+
+# customers
+
+class CustomerView(ConceptView):
+
+ pass
diff --git a/cyberapps/commerce/browser/product.pt b/cyberapps/commerce/browser/product.pt
new file mode 100644
index 0000000..898df47
--- /dev/null
+++ b/cyberapps/commerce/browser/product.pt
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+ Subcategories
+
+
+ -
+ Category
+
+ -
+ Description
+
+
+
+
+ Products
+
+
+ -
+ Product
+
+ -
+ Description
+
+
+
+
+
+
+
+
+
diff --git a/cyberapps/commerce/browser/product.py b/cyberapps/commerce/browser/product.py
new file mode 100644
index 0000000..c342dbf
--- /dev/null
+++ b/cyberapps/commerce/browser/product.py
@@ -0,0 +1,130 @@
+#
+# 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
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+
+"""
+View classes for loops-based eCommerce applications.
+
+$Id$
+"""
+
+from zope.app.pagetemplate import ViewPageTemplateFile
+from zope.cachedescriptors.property import Lazy
+from zope import component
+
+from cybertools.browser.action import actions
+from cybertools.composer.schema import Schema
+from cybertools.composer.schema import Field
+from loops.browser.action import DialogAction, TargetAction
+from loops.browser.concept import ConceptView
+from loops.browser.form import EditConceptPage, CreateConceptPage
+from loops.browser.form import EditConcept, CreateConcept
+from loops.browser.node import NodeView
+from loops.common import adapted
+from loops import util
+
+
+product_macros = ViewPageTemplateFile('product.pt')
+
+
+# products
+
+class ProductView(ConceptView):
+
+ pass
+
+
+class EditProductPage(EditConceptPage):
+
+ showAssignments = False
+
+ def setupController(self):
+ super(EditProductPage, self).setupController()
+ self.registerDojoFormAllGrid()
+
+
+class CreateProductPage(CreateConceptPage):
+
+ showAssignments = False
+ typeToken = '.loops/concepts/product'
+ fixedType = True
+ form_action = 'create_product'
+
+ def setupController(self):
+ super(CreateProductPage, self).setupController()
+ self.registerDojoFormAllGrid()
+
+
+class CreateProduct(CreateConcept):
+
+ def getNameFromData(self):
+ id = self.request.form.get('productId')
+ if id:
+ return 'p' + id
+
+
+# categories
+
+class CategoryView(ConceptView):
+
+ @Lazy
+ def macro(self):
+ return product_macros.macros['category']
+
+ @Lazy
+ def subcategories(self):
+ for c in self.adapted.subcategories:
+ view = ConceptView(c.context, self.request)
+ yield view
+
+ @Lazy
+ def products(self):
+ for c in self.adapted.products:
+ view = ProductView(c.context, self.request)
+ yield view
+
+
+class EditCategoryPage(EditConceptPage):
+
+ #showAssignments = False
+
+ def setupController(self):
+ super(EditCategoryPage, self).setupController()
+ self.registerDojoFormAllGrid()
+
+
+class CreateCategoryPage(CreateConceptPage):
+
+ #showAssignments = False
+ typeToken = '.loops/concepts/category'
+ fixedType = True
+ form_action = 'create_category'
+
+ def setupController(self):
+ super(CreateCategoryPage, self).setupController()
+ self.registerDojoFormAllGrid()
+
+
+class CreateCategory(CreateConcept):
+
+ def getNameFromData(self):
+ return super(CreateCategory, self).getNameFromData()
+
+ id = self.request.form.get('productId')
+ if id:
+ return 'p' + id
+
diff --git a/cyberapps/commerce/configure.zcml b/cyberapps/commerce/configure.zcml
new file mode 100644
index 0000000..56b3226
--- /dev/null
+++ b/cyberapps/commerce/configure.zcml
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cyberapps/commerce/customer.py b/cyberapps/commerce/customer.py
new file mode 100644
index 0000000..a0e202e
--- /dev/null
+++ b/cyberapps/commerce/customer.py
@@ -0,0 +1,39 @@
+# cyberapps.commerce.customer
+
+""" Customer adapter and related classes.
+"""
+
+from zope.interface import implementer
+
+from cyberapps.commerce.interfaces import ICustomer, IAddress
+from cybertools.commerce.customer import Customer as BaseCustomer
+from cybertools.commerce.customer import Address as BaseAddress
+from loops.common import AdapterBase
+from loops.common import ChildRelationSetProperty, ParentRelationSetProperty
+from loops.interfaces import IConceptSchema
+from loops.type import TypeInterfaceSourceList
+
+
+TypeInterfaceSourceList.typeInterfaces += (ICustomer, IAddress)
+
+
+@implementer(ICustomer)
+class Customer(AdapterBase, BaseCustomer):
+
+ _adapterAttributes = ('context', '__parent__', 'shops')
+ _contextAttributes = list(ICustomer)
+ _noexportAttributes = ('shops', 'orders')
+
+ shops = ParentRelationSetProperty('shop.customer', noSecurityCheck=True)
+ orders = ChildRelationSetProperty('customer.order')
+
+ @property
+ def identifier(self):
+ return self.customerId
+
+
+@implementer(IAddress)
+class Address(AdapterBase, BaseAddress):
+
+ _contextAttributes = list(IAddress)
+
diff --git a/cyberapps/commerce/interfaces.py b/cyberapps/commerce/interfaces.py
new file mode 100644
index 0000000..19c92cb
--- /dev/null
+++ b/cyberapps/commerce/interfaces.py
@@ -0,0 +1,90 @@
+#
+# 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
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+
+"""
+Specific interfaces.
+
+$Id$
+"""
+
+from cyberapps.commerce.util import _
+from cybertools.commerce.interfaces import IShop, IProduct, ICategory, IManufacturer
+from cybertools.commerce.interfaces import ICustomer, IAddress
+from cybertools.commerce.interfaces import IOrder, IOrderItem
+from loops.interfaces import ILoopsAdapter
+from loops.schema.base import Relation, RelationSet
+
+
+class IShop(ILoopsAdapter, IShop):
+
+ pass
+
+
+class ICategory(ILoopsAdapter, ICategory):
+
+ accessorySubcategories = RelationSet(
+ title=_(u'Accessory Subcategories'),
+ description=_(u'A collection of categories that contain products '
+ u'that may be used as accessories for this category.'),
+ target_types=('category',),
+ required=False)
+ selectedProducts = RelationSet(
+ title=_(u'Selected Products'),
+ description=_(u'Selected products for this category.'),
+ target_types=('product',),
+ required=False)
+
+
+class IProduct(ILoopsAdapter, IProduct):
+
+ shops = RelationSet(
+ title=_(u'Shops'),
+ description=_(u'The shops providing this product..'),
+ target_types=('shop',),
+ required=False)
+ categories = RelationSet(
+ title=_(u'Categories'),
+ description=_(u'The product categories this product belongs to.'),
+ target_types=('category',),
+ required=False)
+ manufacturer = Relation(
+ title=_(u'Manufacturer'),
+ description=_(u'The manufacturer providing this product.'),
+ target_types=('manufacturer',),
+ required=False)
+ recommendedAccessories = RelationSet(
+ title=_(u'Recommended Accessories'),
+ description=_(u'Accessories for this product.'),
+ target_types=('product',),
+ required=False)
+
+
+class ICustomer(ILoopsAdapter, ICustomer):
+
+ pass
+
+
+class IAddress(ILoopsAdapter, IAddress):
+
+ pass
+
+
+class IOrder(ILoopsAdapter, IOrder):
+
+ pass
+
diff --git a/cyberapps/commerce/manager.py b/cyberapps/commerce/manager.py
new file mode 100644
index 0000000..dcf84d1
--- /dev/null
+++ b/cyberapps/commerce/manager.py
@@ -0,0 +1,42 @@
+# cyberapps.commerce.manager
+
+""" The commerce manager (container, registry, ...).
+"""
+
+from zope.cachedescriptors.property import Lazy
+from zope.component import adapts
+from zope.interface import implementer
+
+from cybertools.commerce.interfaces import IManager, IOrderItems
+from loops.common import TypeInstancesProperty
+from loops.concept import Concept
+from loops.interfaces import IConceptManager
+from loops.setup import addAndConfigureObject
+
+
+@implementer(IManager)
+class Manager(object):
+
+ adapts(IConceptManager)
+
+ langInfo = None
+
+ shops = TypeInstancesProperty('shop')
+ products = TypeInstancesProperty('product', 'productId', 'p')
+ categories = TypeInstancesProperty('category', 'name', 'cat')
+ customers = TypeInstancesProperty('customer', 'customerId', 'c',
+ container='customers')
+ orders = TypeInstancesProperty('order', 'orderId', '',
+ container='orders')
+
+ def __init__(self, context):
+ self.context = context
+
+ @Lazy
+ def records(self):
+ return self.context.getLoopsRoot().getRecordManager()
+
+ @Lazy
+ def orderItems(self):
+ return IOrderItems(self.records['orderitems'])
+
diff --git a/cyberapps/commerce/order.py b/cyberapps/commerce/order.py
new file mode 100644
index 0000000..d50de6d
--- /dev/null
+++ b/cyberapps/commerce/order.py
@@ -0,0 +1,59 @@
+# cyberapps.commerce.order
+
+""" Order and order item classes.
+"""
+
+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 implementer, Interface
+
+from cyberapps.commerce.interfaces import IOrder
+from cybertools.commerce.order import Order as BaseOrder
+from cybertools.commerce.order import OrderItem as BaseOrderItem
+from cybertools.commerce.order import OrderItems as BaseOrderItems
+from loops.common import AdapterBase, ParentRelation
+from loops.interfaces import ILoopsObject
+from loops import util
+from loops.type import TypeInterfaceSourceList
+
+
+TypeInterfaceSourceList.typeInterfaces += (IOrder,)
+
+
+@implementer(IOrder)
+class Order(AdapterBase, BaseOrder):
+
+ _adapterAttributes = ('context', '__parent__', 'customer')
+ _contextAttributes = list(IOrder)
+
+ #shop = ParentRelation('shop.order') # implemented as context attribute
+ customer = ParentRelation('customer.order')
+
+
+class OrderItem(BaseOrderItem):
+
+ def getObject(self, ref):
+ if isinstance(ref, int):
+ return util.getObjectForUid(ref)
+ if isinstance(ref, basestring):
+ if ref.isdigit:
+ return util.getObjectForUid(ref)
+ if ':' in ref:
+ tp, id = ref.split(':', 1)
+ return (tp, id)
+ return ref
+
+
+class OrderItems(BaseOrderItems):
+
+ # utility methods
+
+ def getUid(self, obj):
+ if ILoopsObject.providedBy(obj):
+ return util.getUidForObject(obj, self.intIds)
+ elif isinstance(obj, AdapterBase):
+ return util.getUidForObject(obj.context, self.intIds)
+ return obj
+
diff --git a/cyberapps/commerce/product.py b/cyberapps/commerce/product.py
new file mode 100644
index 0000000..c80660d
--- /dev/null
+++ b/cyberapps/commerce/product.py
@@ -0,0 +1,140 @@
+# cyberapps.commerce.product
+
+""" Product adapter.
+"""
+
+from zope.app.intid.interfaces import IIntIds
+from zope import component
+from zope.interface import implementer
+
+from cyberapps.commerce.interfaces import IProduct, ICategory
+from cybertools.commerce.interfaces import IManufacturer, ISupplier
+from cybertools.commerce.product import Product as BaseProduct, Category as BaseCategory
+from cybertools.commerce.product import Manufacturer, Supplier
+from cybertools.util.cache import cache
+from loops.common import adapted, AdapterBase, baseObject, ParentRelation
+from loops.common import ParentRelationSetProperty, ChildRelationSetProperty
+from loops.interfaces import IConceptSchema
+from loops.type import TypeInterfaceSourceList
+from loops import util
+
+
+TypeInterfaceSourceList.typeInterfaces += (
+ IProduct, ICategory, IManufacturer, ISupplier)
+
+
+@implementer(IProduct)
+class Product(AdapterBase, BaseProduct):
+
+ _adapterAttributes = ('context', '__parent__',
+ 'shops', 'manufacturer', 'suppliers', 'categories',
+ 'recommendedAccessories')
+ _contextAttributes = list(IProduct)
+ _noexportAttributes = ('shops', 'categories', 'manufacturer', 'suppliers',
+ 'recommendedAccessories')
+
+ shops = ParentRelationSetProperty('shop.product')
+ categories = ParentRelationSetProperty('category.product')
+ manufacturer = ParentRelation('manufacturer.product')
+ suppliers = ParentRelationSetProperty('supplier.product')
+ recommendedAccessories = ChildRelationSetProperty('product.accessory')
+
+ @property
+ def identifier(self):
+ return self.productId
+
+
+@implementer(ICategory)
+class Category(AdapterBase, BaseCategory):
+
+ _adapterAttributes = ('context', '__parent__',
+ 'shops', 'subcategories', 'products',
+ 'accessorySubcategories', 'selectedProducts')
+ _contextAttributes = list(ICategory)
+ _noexportAttributes = ('shops', 'products', 'parentCategories',
+ 'subcategories', 'accessorySubcategories',
+ 'selectedProducts')
+
+ shops = ParentRelationSetProperty('shop.category')
+ products = ChildRelationSetProperty('category.product')
+ parentCategories = ParentRelationSetProperty('standard', ICategory)
+ subcategories = ChildRelationSetProperty('standard')
+ accessorySubcategories = ChildRelationSetProperty('category.accessory')
+ selectedProducts = ChildRelationSetProperty('category.selected')
+
+ def getLongTitle(self):
+ parent = u''
+ defaultPredicate = self.getLoopsRoot().getConceptManager().getDefaultPredicate()
+ pr = self.context.getParentRelations([defaultPredicate])
+ for r in pr:
+ if r.relevance >= 0.8:
+ parent = r.first.title
+ if parent:
+ parent = u' - ' + parent
+ return self.title + parent
+
+ def getIsActiveCacheId(self, shop=None, *args, **kw):
+ shopId = shop is None and 'None' or shop.uid
+ filter = kw.get('filter') or ''
+ return 'commerce.category.isActive.%s.%s.%s' % (self.uid, shopId, filter)
+
+ @cache(getIsActiveCacheId, lifetime=93600)
+ def isActive(self, shop=None, filter=None):
+ depth = 0
+ for cat in self.subcategories:
+ if ICategory.providedBy(cat):
+ depth = max(depth, int(cat.isActive(shop, filter=filter)))
+ if depth:
+ return depth + 1
+ for prod in self.products:
+ if prod.isActive(shop, filter=filter):
+ return True
+ return False
+
+ def getAccessoryParents(self):
+ pred = self.getLoopsRoot().getConceptManager()['category.accessory']
+ for c in self.context.getParents([pred]):
+ yield adapted(c)
+
+ def getAllProductsCacheId(self, shop=None, *args, **kw):
+ shopId = shop is None and 'None' or shop.uid
+ filter = kw.get('filter') or ''
+ return 'commerce.category.allProducts.%s.%s.%s' % (self.uid, shopId, filter)
+
+ @cache(getAllProductsCacheId, lifetime=93600)
+ def getAllProductsUids(self, shop=None, filter=None):
+ intids = component.getUtility(IIntIds)
+ result = [util.getUidForObject(baseObject(p), intids)
+ for p in self.products
+ if p.isActive(shop, filter)]
+ for c in self.subcategories:
+ result.extend(c.getAllProductsUids())
+ return result
+
+
+class Manufacturer(AdapterBase, Manufacturer):
+
+ _contextAttributes = list(IManufacturer)
+ _noexportAttributes = ('products',)
+
+ products = ChildRelationSetProperty('manufacturer.product')
+
+ def getIsActiveCacheId(self, shop, *args, **kw):
+ shopId = shop is None and 'None' or shop.uid
+ filter = kw.get('filter') or ''
+ return 'commerce.manufacturer.isActive.%s.%s.%s' % (self.uid, shopId, filter)
+
+ @cache(getIsActiveCacheId, lifetime=72000)
+ def isActive(self, shop=None, filter=None):
+ for prod in self.products:
+ if prod.isActive(shop, filter=filter):
+ return True
+
+
+class Supplier(AdapterBase, Supplier):
+
+ _contextAttributes = list(ISupplier)
+ _noexportAttributes = ('products',)
+
+ products = ChildRelationSetProperty('supplier.product')
+
diff --git a/cyberapps/commerce/setup.py b/cyberapps/commerce/setup.py
new file mode 100644
index 0000000..cd9a6bf
--- /dev/null
+++ b/cyberapps/commerce/setup.py
@@ -0,0 +1,58 @@
+# cyberapps.commerce.setup
+
+""" Automatic setup of a loops site for the commerce package.
+"""
+
+from zope.component import adapts
+
+from cyberapps.commerce.order import OrderItem
+from cybertools.commerce.interfaces import IShop, IProduct, ICategory
+from cybertools.commerce.interfaces import ICustomer, IOrder
+from cybertools.tracking.btree import TrackingStorage
+from loops.concept import Concept, ConceptManager
+from loops.interfaces import ITypeConcept
+from loops.setup import SetupManager as BaseSetupManager
+
+
+class SetupManager(BaseSetupManager):
+
+ def setup(self):
+ concepts = self.context.getConceptManager()
+ type = concepts.getTypeConcept()
+ predicate = concepts.getPredicateType()
+ customers = self.addObject(self.context, ConceptManager, 'customers')
+ orders = self.addObject(self.context, ConceptManager, 'orders')
+ # type concepts:
+ tShop = self.addAndConfigureObject(concepts, Concept, 'shop',
+ title=u'Shop', conceptType=type, typeInterface=IShop)
+ tProduct = self.addAndConfigureObject(concepts, Concept, 'product',
+ title=u'Product', conceptType=type, typeInterface=IProduct)
+ tCategory = self.addAndConfigureObject(concepts, Concept, 'category',
+ title=u'Category', conceptType=type, typeInterface=ICategory)
+ tCustomer = self.addAndConfigureObject(concepts, Concept, 'customer',
+ title=u'Customer', conceptType=type, typeInterface=ICustomer)
+ tOrder = self.addAndConfigureObject(concepts, Concept, 'order',
+ title=u'Order', conceptType=type, typeInterface=IOrder)
+ # predicates:
+ category_product = self.addObject(concepts, Concept, 'category.product',
+ title=u'category <- product', conceptType=predicate)
+ category_accessory = self.addObject(concepts, Concept, 'category.accessory',
+ title=u'category <- accessory subcategory', conceptType=predicate)
+ #category_selected = self.addObject(concepts, Concept, 'category.selected',
+ # title=u'category <- selected product', conceptType=predicate)
+ product_accessory = self.addObject(concepts, Concept, 'product.accessory',
+ title=u'product <- accessory', conceptType=predicate)
+ shop_product = self.addObject(concepts, Concept, 'shop.product',
+ title=u'shop <- product', conceptType=predicate)
+ shop_customer = self.addObject(concepts, Concept, 'shop.customer',
+ title=u'shop <- customer', conceptType=predicate)
+ #shop_order = self.addObject(concepts, Concept, 'shop.order',
+ # title=u'shop <- order', conceptType=predicate)
+ manufacturer_product = self.addObject(concepts, Concept, 'manufacturer.product',
+ title=u'manufacturer <- product', conceptType=predicate)
+ customer_order = self.addObject(concepts, Concept, 'customer.order',
+ title=u'customer <- order', conceptType=predicate)
+ # records:
+ records = self.context.getRecordManager()
+ if 'orderitems' not in records:
+ records['orderitems'] = TrackingStorage(trackFactory=OrderItem)
diff --git a/cyberapps/commerce/shop.py b/cyberapps/commerce/shop.py
new file mode 100644
index 0000000..4adb35f
--- /dev/null
+++ b/cyberapps/commerce/shop.py
@@ -0,0 +1,31 @@
+# cyberapps.commerce.shop
+
+""" Shop adapter.
+"""
+
+from zope.interface import implementer
+from zope.traversing.api import getName
+
+from cyberapps.commerce.interfaces import IShop
+from cybertools.commerce.shop import Shop as BaseShop
+from loops.common import AdapterBase, ChildRelationSetProperty
+from loops.type import TypeInterfaceSourceList
+
+
+TypeInterfaceSourceList.typeInterfaces += (IShop,)
+
+
+@implementer(IShop)
+class Shop(AdapterBase, BaseShop):
+
+ _adapterAttributes = ('context', '__parent__',
+ 'products', 'categories', 'suppliers', 'customers')
+ _contextAttributes = list(IShop)
+ _noexportAttributes = ('products', 'customers')
+
+ products = ChildRelationSetProperty('shop.product')
+ customers = ChildRelationSetProperty('shop.customer')
+
+ @property
+ def name(self):
+ return getName(self.context)
diff --git a/cyberapps/commerce/tests.py b/cyberapps/commerce/tests.py
new file mode 100755
index 0000000..1111281
--- /dev/null
+++ b/cyberapps/commerce/tests.py
@@ -0,0 +1,55 @@
+# $Id$
+
+import unittest, doctest
+from zope.testing.doctestunit import DocFileSuite
+from zope import component
+from zope.interface.verify import verifyClass
+
+from cyberapps.commerce.customer import Customer, Address
+from cyberapps.commerce.interfaces import IShop, IProduct, ICategory
+from cyberapps.commerce.interfaces import ICustomer, IAddress, IOrder
+from cyberapps.commerce.order import Order, OrderItems
+from cyberapps.commerce.product import Product, Category
+from cyberapps.commerce.shop import Shop
+from cyberapps.commerce.setup import SetupManager
+from cybertools.commerce.interfaces import IProduct
+from loops.interfaces import ILoops
+from loops.setup import ISetupManager
+from loops.tests.setup import TestSite as BaseTestSite
+
+
+class TestSite(BaseTestSite):
+
+ def __init__(self, site):
+ self.site = site
+
+ def setup(self):
+ component.provideAdapter(SetupManager, (ILoops,), ISetupManager,
+ name='cyberapps.commerce')
+ component.provideAdapter(Shop, provides=IShop)
+ component.provideAdapter(Product, provides=IProduct)
+ component.provideAdapter(Category, provides=ICategory)
+ component.provideAdapter(Customer, provides=ICustomer)
+ component.provideAdapter(Address, provides=IAddress)
+ component.provideAdapter(Order, provides=IOrder)
+ component.provideAdapter(OrderItems)
+ concepts, resources, views = self.baseSetup()
+ return concepts, resources, views
+
+
+class Test(unittest.TestCase):
+ "Basic tests for the cyberapps.commerce package."
+
+ def testSomething(self):
+ pass
+
+
+def test_suite():
+ flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
+ return unittest.TestSuite((
+ unittest.makeSuite(Test),
+ DocFileSuite('README.txt', optionflags=flags),
+ ))
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='test_suite')
diff --git a/cyberapps/commerce/util.py b/cyberapps/commerce/util.py
new file mode 100644
index 0000000..165866b
--- /dev/null
+++ b/cyberapps/commerce/util.py
@@ -0,0 +1,23 @@
+# cyberapps.commerce.util
+
+""" Utility functions.
+"""
+
+from email.mime.text import MIMEText
+from zope import component
+from zope.i18nmessageid import MessageFactory
+from zope.sendmail.interfaces import IMailDelivery
+
+
+_ = MessageFactory('cyberapps.commerce')
+
+
+def sendEMail(subject, message, sender, recipients):
+ if isinstance(message, unicode):
+ message = message.encode('UTF-8')
+ msg = MIMEText(message, 'plain', 'utf-8')
+ msg['Subject'] = subject
+ msg['From'] = sender
+ msg['To'] = ', '.join(recipients)
+ mailhost = component.getUtility(IMailDelivery, 'Mail')
+ mailhost.send(sender, recipients, msg.as_string())