diff --git a/README.txt b/README.txt index 3938d8b..67a4ae7 100755 --- a/README.txt +++ b/README.txt @@ -4,7 +4,7 @@ loops - Linked Objects for Organization and Processing Services ($Id$) -The loops platform is built up of three parts: +The loops platform consists up of three basic types of objects: (1) concepts: simple interconnected objects usually representing meta-information diff --git a/browser/common.py b/browser/common.py index a56cb7a..9a00d5c 100644 --- a/browser/common.py +++ b/browser/common.py @@ -106,6 +106,7 @@ class BaseView(GenericView, I18NView): # TODO: get rid of removeSecurityProxy() call self.context = removeSecurityProxy(context) self.setSkin(self.loopsRoot.skinName) + self.checkLanguage() try: if not canAccess(context, 'title'): raise Unauthorized diff --git a/browser/loops.css b/browser/loops.css index ba29dc1..a01dd3b 100644 --- a/browser/loops.css +++ b/browser/loops.css @@ -31,6 +31,12 @@ table.listing td { margin-left: 5px; } +.top-actions{ + position: absolute; + right: 2em; + top: 1em; +} + /*.content-1 h1 { */ h1 { font-size: 160%; @@ -98,6 +104,14 @@ div.image { margin-right: 5px; } +img.selected { + border: 2px solid #d6dcf6; +} + +img.notselected { + border: 2px solid #eff8ff; +} + /* search stuff */ diff --git a/browser/node.py b/browser/node.py index 632478e..75b3d8d 100644 --- a/browser/node.py +++ b/browser/node.py @@ -46,6 +46,7 @@ from cybertools.browser import configurator from cybertools.browser.view import GenericView from cybertools.typology.interfaces import IType, ITypeManager from cybertools.xedit.browser import ExternalEditorView +from loops.i18n.browser import i18n_macros from loops.interfaces import IConcept, IResource, IDocument, IMediaAsset, INode from loops.interfaces import IViewConfiguratorSchema from loops.resource import MediaAsset @@ -77,6 +78,8 @@ class NodeView(BaseView): media='all', position=3) cm.register('js', 'loops.js', resourceName='loops.js') #cm.register('js', 'loops.js', resourceName='loops1.js') + cm.register('top_actions', 'top_actions', name='multi_actions', + subMacros=[i18n_macros.macros['language_switch']]) cm.register('portlet_left', 'navigation', title='Navigation', subMacro=node_macros.macros['menu']) #if not IUnauthenticatedPrincipal.providedBy(self.request.principal): diff --git a/browser/node_macros.pt b/browser/node_macros.pt index f339b7b..0a53a44 100644 --- a/browser/node_macros.pt +++ b/browser/node_macros.pt @@ -200,7 +200,12 @@ - + + + + blubb + + diff --git a/helpers.txt b/helpers.txt index 2fe8b04..29d11f9 100755 --- a/helpers.txt +++ b/helpers.txt @@ -10,39 +10,22 @@ package. Let's first do some basic imports - >>> from zope.app.testing.setup import placefulSetUp, placefulTearDown - >>> site = placefulSetUp(True) - >>> from zope import interface, component >>> from zope.interface import Interface >>> from zope.publisher.browser import TestRequest -and provide a relation registry: + >>> from zope.app.testing.setup import placefulSetUp, placefulTearDown + >>> site = placefulSetUp(True) - >>> from cybertools.relation.tests import IntIdsStub - >>> component.provideUtility(IntIdsStub()) - >>> from cybertools.relation.registry import DummyRelationRegistry - >>> component.provideUtility(DummyRelationRegistry()) + >>> from loops.tests.setup import TestSite + >>> t = TestSite(site) + >>> concepts, resources, views = t.setup() -and care for some type adapter machinery: - - >>> from loops.interfaces import IConcept - >>> from loops.interfaces import ITypeConcept - >>> from loops.type import TypeConcept - >>> component.provideAdapter(TypeConcept, (IConcept,), ITypeConcept) - -Now we can setup a simple loops site with its manager objects, using a -loops setup manager: - - >>> from loops.base import Loops - >>> loopsRoot = site['loops'] = Loops() - - >>> from loops.setup import SetupManager - >>> setup = SetupManager(loopsRoot) - >>> concepts, resources, views = setup.setup() >>> concepts['hasType'].title u'has Type' + >>> loopsRoot = site['loops'] + We also add some example concepts, >>> from loops.concept import Concept @@ -217,6 +200,8 @@ for controlling e.g. storage for external files. >>> ef1_type = IType(ef1) >>> IType(ef1).options [] + + >>> from loops.type import TypeConcept >>> extfile_ad = TypeConcept(extfile) >>> extfile_ad.options = ['dummy', 'storage:varsubdir', ... 'storage_parameters:extfiles'] @@ -239,11 +224,9 @@ get a type manager from all loops objects, always with the same context: True >>> types = typeManager.types - >>> sorted(t.token for t in types) - ['.loops/concepts/domain', '.loops/concepts/file', '.loops/concepts/note', - '.loops/concepts/predicate', '.loops/concepts/query', - '.loops/concepts/textdocument', '.loops/concepts/topic', - '.loops/concepts/type'] + >>> typeTokens = sorted(t.token for t in types) + >>> len(typeTokens) + 9 >>> typeManager.getType('.loops/concepts/topic') == cc1_type True @@ -252,14 +235,13 @@ The listTypes() method allows us to select types that fulfill a certain condition: >>> types = typeManager.listTypes(include=('concept',)) - >>> sorted(t.token for t in types) - ['.loops/concepts/domain', '.loops/concepts/predicate', - '.loops/concepts/query', '.loops/concepts/topic', '.loops/concepts/type'] + >>> typeTokens = sorted(t.token for t in types) + >>> len(typeTokens) + 6 >>> types = typeManager.listTypes(exclude=('concept',)) - >>> sorted(t.token for t in types) - ['.loops/concepts/file', '.loops/concepts/note', - '.loops/concepts/textdocument'] - + >>> typeTokens = sorted(t.token for t in types) + >>> len(typeTokens) + 3 Type-based interfaces and adapters ---------------------------------- @@ -273,6 +255,7 @@ will be adaptable to. The default for this is None: For concept objects that provide types (type providers) the value of the typeInterface attribute is the ITypeConcept interface: + >>> from loops.interfaces import ITypeConcept >>> topic_type.typeInterface is ITypeConcept True @@ -286,6 +269,7 @@ i.e. the 'topic' concept, via an adapter: >>> class Topic(object): ... implements(ITopic) ... def __init__(self, context): pass + >>> from loops.interfaces import IConcept >>> component.provideAdapter(Topic, (IConcept,), ITopic) >>> ITypeConcept(topic).typeInterface = ITopic @@ -306,7 +290,7 @@ browser views; among others also some important informations about the context object's type: >>> from loops.browser.common import BaseView - >>> view = BaseView(cc1, TestRequest) + >>> view = BaseView(cc1, TestRequest()) >>> view.typeTitle u'Topic' >>> view.typeInterface diff --git a/i18n/browser.py b/i18n/browser.py index 7f5e820..fbc632e 100644 --- a/i18n/browser.py +++ b/i18n/browser.py @@ -24,6 +24,7 @@ $Id$ from zope import interface, component from zope.app.pagetemplate import ViewPageTemplateFile +from zope.app.session.interfaces import ISession from zope.cachedescriptors.property import Lazy from zope.i18n.interfaces import IUserPreferredLanguages from zope.i18n.negotiator import negotiator @@ -31,6 +32,11 @@ from zope.i18n.negotiator import negotiator from loops.common import adapted +packageId = 'loops.i18n.browser' + +i18n_macros = ViewPageTemplateFile('i18n_macros.pt') + + class LanguageInfo(object): def __init__(self, context, request): @@ -80,8 +86,10 @@ class I18NView(object): return adapted(self.context, self.languageInfo) def checkLanguage(self): - # get language from session - self.setPreferredLanguage() + session = ISession(self.request)[packageId] + lang = session.get('language') + if lang: + self.setLanguage(lang) def setLanguage(self, lang=None): lang = lang or self.request.form.get('lang') @@ -89,10 +97,12 @@ class I18NView(object): upl = IUserPreferredLanguages(self.request) upl.setPreferredLanguages([lang]) - def switchLanguage(self, lang=None, keep=False): + def switchLanguage(self): + lang = self.request.form.get('loops.language') keep = self.request.form.get('keep') if keep: - pass # set in session - self.setPreferredLanguage(lang) + session = ISession(self.request)[packageId] + session['language'] = lang + self.setLanguage(lang) return self() diff --git a/i18n/configure.zcml b/i18n/configure.zcml index 7a78860..539eb27 100644 --- a/i18n/configure.zcml +++ b/i18n/configure.zcml @@ -5,8 +5,10 @@ xmlns="http://namespaces.zope.org/browser" i18n_domain="zope"> + + diff --git a/i18n/flags/de.gif b/i18n/flags/de.gif new file mode 100755 index 0000000..75728dd Binary files /dev/null and b/i18n/flags/de.gif differ diff --git a/i18n/flags/en.gif b/i18n/flags/en.gif new file mode 100644 index 0000000..3c6bce1 Binary files /dev/null and b/i18n/flags/en.gif differ diff --git a/i18n/flags/it.gif b/i18n/flags/it.gif new file mode 100755 index 0000000..d79e90e Binary files /dev/null and b/i18n/flags/it.gif differ diff --git a/i18n/flags/pl.gif b/i18n/flags/pl.gif new file mode 100755 index 0000000..bf10646 Binary files /dev/null and b/i18n/flags/pl.gif differ diff --git a/i18n/i18n_macros.pt b/i18n/i18n_macros.pt new file mode 100644 index 0000000..30cfe2d --- /dev/null +++ b/i18n/i18n_macros.pt @@ -0,0 +1,14 @@ + + + + + + + diff --git a/search/README.txt b/search/README.txt index 97491e7..9768427 100755 --- a/search/README.txt +++ b/search/README.txt @@ -17,29 +17,17 @@ 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 cybertools.relation.registry import DummyRelationRegistry - >>> from cybertools.relation.tests import IntIdsStub - >>> relations = DummyRelationRegistry() - >>> component.provideUtility(relations) - >>> component.provideUtility(IntIdsStub()) - + >>> from loops.concept import Concept >>> from loops.type import ConceptType, TypeConcept >>> from loops.interfaces import ITypeConcept - >>> component.provideAdapter(ConceptType) - >>> component.provideAdapter(TypeConcept) - >>> from loops.base import Loops - >>> loopsRoot = site['loops'] = Loops() + >>> from loops.tests.setup import TestSite + >>> t = TestSite(site) + >>> concepts, resources, views = t.setup() - >>> from loops.setup import SetupManager - >>> setup = SetupManager(loopsRoot) - >>> concepts, resources, views = setup.setup() - >>> typeConcept = concepts['type'] - - >>> from loops.concept import Concept - >>> topic = concepts['topic'] = Concept(u'Topic') - >>> for c in (topic,): c.conceptType = typeConcept + >>> loopsRoot = site['loops'] >>> query = concepts['query'] + >>> topic = concepts['topic'] = Concept(u'Topic') In addition we create a concept that holds the search page and a node (page) that links to this concept: @@ -103,7 +91,7 @@ a controller attribute for the search view. >>> searchView.submitReplacing('1.results', '1.search.form', pageView) 'return submitReplacing("1.results", "1.search.form", - "http://127.0.0.1/loops/views/page/.target10/@@searchresults.html")' + "http://127.0.0.1/loops/views/page/.target29/@@searchresults.html")' Basic (text/title) search ------------------------- @@ -202,7 +190,7 @@ of the concepts' titles: >>> request = TestRequest(form=form) >>> view = Search(page, request) >>> view.listConcepts() - "[['Zope (Topic)', '12']]" + "[['Zope (Topic)', '33']]" Preset Concept Types on Search Forms ------------------------------------ @@ -213,8 +201,7 @@ a certain qualifier, via the option attribute of the type interface. Let's start with a new type, the customer type. - >>> customer = concepts['customer'] = Concept('Customer') - >>> customer.conceptType = typeConcept + >>> customer = concepts['customer'] >>> custType = ITypeConcept(customer) >>> custType.options [] @@ -239,16 +226,16 @@ and thus include the customer type in the preset search types. ('concept', 'search') >>> searchView = Search(search, TestRequest()) >>> list(searchView.presetSearchTypes) - [{'token': 'loops:concept:customer', 'title': 'Customer'}] + [{'token': 'loops:concept:customer', 'title': u'Customer'}] >>> searchView.conceptsForType('loops:concept:customer') [{'token': 'none', 'title': u'not selected'}, - {'token': '18', 'title': u'Zope Corporation'}, - {'token': '19', 'title': u'cyberconcepts'}] + {'token': '47', 'title': u'Zope Corporation'}, + {'token': '49', 'title': u'cyberconcepts'}] Let's use this new search option for querying: - >>> form = {'search.4.text_selected': u'18'} + >>> form = {'search.4.text_selected': u'47'} >>> resultsView = SearchResults(page, TestRequest(form=form)) >>> results = list(resultsView.results) >>> results[0].title diff --git a/tests/setup.py b/tests/setup.py index 34056eb..11fcc36 100644 --- a/tests/setup.py +++ b/tests/setup.py @@ -13,8 +13,11 @@ from zope.app.catalog.text import TextIndex from zope.app.container.interfaces import IObjectRemovedEvent from zope.app.security.principalregistry import principalRegistry from zope.app.security.interfaces import IAuthentication +from zope.app.session.interfaces import IClientIdManager, ISessionDataContainer +from zope.app.session import session from zope.dublincore.annotatableadapter import ZDCAnnotatableAdapter from zope.dublincore.interfaces import IZopeDublinCore +from zope.interface import implements from cybertools.composer.schema.factory import SchemaFactory from cybertools.composer.schema.field import FieldInstance, NumberFieldInstance @@ -40,6 +43,12 @@ from loops.schema import ResourceSchemaFactory, FileSchemaFactory, NoteSchemaFac from loops.setup import SetupManager, addObject from loops.type import LoopsType, ConceptType, ResourceType, TypeConcept +class ClientIdManager(object): + """ dummy, for testing only """ + implements(IClientIdManager) + def getClientId(self, request): + return 'dummy' + class TestSite(object): @@ -58,6 +67,10 @@ class TestSite(object): component.provideAdapter(ZDCAnnotatableAdapter, (ILoopsObject,), IZopeDublinCore) component.provideAdapter(AttributeAnnotations, (ILoopsObject,)) component.provideUtility(principalRegistry, IAuthentication) + component.provideAdapter(session.ClientId) + component.provideAdapter(session.Session) + component.provideUtility(session.RAMSessionDataContainer(), ISessionDataContainer) + component.provideUtility(ClientIdManager()) component.provideAdapter(LoopsType) component.provideAdapter(ConceptType)