From c6c4fd56f2d18df1cf07ce80a8f6a82b7339c854 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Tue, 1 Oct 2024 15:44:21 +0200 Subject: [PATCH] cyberapps.commerce: Python3 fixes --- cyberapps/bsm/README.txt | 7 + cyberapps/bsm/__init__.py | 3 + cyberapps/bsm/browser.py | 138 ++++++++++++++++ cyberapps/bsm/configure.zcml | 32 ++++ cyberapps/bsm/data.py | 42 +++++ cyberapps/bsm/interfaces.py | 62 +++++++ cyberapps/bsm/macros.pt | 30 ++++ cyberapps/commerce/README.txt | 187 ++++++++++++++++++++++ cyberapps/commerce/__init__.py | 3 + cyberapps/commerce/browser/__init__.py | 3 + cyberapps/commerce/browser/action.py | 58 +++++++ cyberapps/commerce/browser/base.pt | 22 +++ cyberapps/commerce/browser/base.py | 41 +++++ cyberapps/commerce/browser/configure.zcml | 84 ++++++++++ cyberapps/commerce/browser/customer.py | 37 +++++ cyberapps/commerce/browser/product.pt | 43 +++++ cyberapps/commerce/browser/product.py | 130 +++++++++++++++ cyberapps/commerce/configure.zcml | 95 +++++++++++ cyberapps/commerce/customer.py | 39 +++++ cyberapps/commerce/interfaces.py | 90 +++++++++++ cyberapps/commerce/manager.py | 42 +++++ cyberapps/commerce/order.py | 59 +++++++ cyberapps/commerce/product.py | 140 ++++++++++++++++ cyberapps/commerce/setup.py | 58 +++++++ cyberapps/commerce/shop.py | 31 ++++ cyberapps/commerce/tests.py | 55 +++++++ cyberapps/commerce/util.py | 23 +++ 27 files changed, 1554 insertions(+) create mode 100644 cyberapps/bsm/README.txt create mode 100644 cyberapps/bsm/__init__.py create mode 100644 cyberapps/bsm/browser.py create mode 100644 cyberapps/bsm/configure.zcml create mode 100644 cyberapps/bsm/data.py create mode 100644 cyberapps/bsm/interfaces.py create mode 100644 cyberapps/bsm/macros.pt create mode 100644 cyberapps/commerce/README.txt create mode 100644 cyberapps/commerce/__init__.py create mode 100644 cyberapps/commerce/browser/__init__.py create mode 100644 cyberapps/commerce/browser/action.py create mode 100644 cyberapps/commerce/browser/base.pt create mode 100644 cyberapps/commerce/browser/base.py create mode 100644 cyberapps/commerce/browser/configure.zcml create mode 100644 cyberapps/commerce/browser/customer.py create mode 100644 cyberapps/commerce/browser/product.pt create mode 100644 cyberapps/commerce/browser/product.py create mode 100644 cyberapps/commerce/configure.zcml create mode 100644 cyberapps/commerce/customer.py create mode 100644 cyberapps/commerce/interfaces.py create mode 100644 cyberapps/commerce/manager.py create mode 100644 cyberapps/commerce/order.py create mode 100644 cyberapps/commerce/product.py create mode 100644 cyberapps/commerce/setup.py create mode 100644 cyberapps/commerce/shop.py create mode 100755 cyberapps/commerce/tests.py create mode 100644 cyberapps/commerce/util.py 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


+ + + + + +
+ Fieldname: + + + Value + +

+ +
+ +
+ +
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())