diff --git a/concept.py b/concept.py index feb0433..46dbea4 100644 --- a/concept.py +++ b/concept.py @@ -171,14 +171,16 @@ class Concept(Contained, Persistent): def getChildren(self, predicates=None, sort='default'): return [r.second for r in self.getChildRelations(predicates, sort=sort)] - def getParentRelations (self, predicates=None, parent=None): + def getParentRelations (self, predicates=None, parent=None, sort='default'): predicates = predicates is None and ['*'] or predicates relationships = [ConceptRelation(None, self, p) for p in predicates] - # TODO: sort... - return getRelations(first=parent, second=self, relationships=relationships) + if sort == 'default': + sort = lambda x: (x.order, x.first.title.lower()) + return sorted(getRelations(first=parent, second=self, relationships=relationships), + key=sort) - def getParents(self, predicates=None): - return [r.first for r in self.getParentRelations(predicates)] + def getParents(self, predicates=None, sort='default'): + return [r.first for r in self.getParentRelations(predicates, sort=sort)] def assignChild(self, concept, predicate=None, order=0, relevance=1.0): if predicate is None: diff --git a/i18n/README.txt b/i18n/README.txt index f1d324f..bf1658a 100644 --- a/i18n/README.txt +++ b/i18n/README.txt @@ -108,14 +108,41 @@ languages on the type object. Now we are ready to enter a language-specific title. - >>> from loops.browser.concept import ConceptEditForm + >>> from loops.browser.concept import ConceptEditForm, ConceptView >>> input = {'form.title': 'loops per Zope 3', 'loops.language': 'it', ... 'form.actions.apply': 'Change'} >>> form = ConceptEditForm(topic01, TestRequest(form=input)) >>> form.update() >>> topic01.title - {'it': u'loops per Zope 3'} + {'en': u'loops for Zope 3', 'it': u'loops per Zope 3'} + +If we access an i18n attribute via a view that is i18n-aware we get the +value corresponding to the language preferences that appear in the request. + + >>> input = {'loops.language': 'it'} + >>> view = ConceptView(topic01, TestRequest(form=input)) + >>> view.title + u'loops per Zope 3' + +If there is no entry for the language given we get back the entry for +the default language. + + >>> input = {'loops.language': 'de'} + >>> view = ConceptView(topic01, TestRequest(form=input)) + >>> view.title + u'loops for Zope 3' + +There are also fallbacks - mainly for being able to access the title +attribute in not i18n-aware contexts - that retrieve the value corresponding +to the default language at the time of the attribute creation. + + >>> topic01.title.getDefault() + u'loops for Zope 3' + >>> str(topic01.title) + 'loops for Zope 3' + >>> topic01.title.lower() + u'loops for zope 3' Fin de partie diff --git a/i18n/common.py b/i18n/common.py index 0e336f0..0719224 100644 --- a/i18n/common.py +++ b/i18n/common.py @@ -33,40 +33,65 @@ from cybertools.typology.interfaces import IType from loops.common import adapted, AdapterBase +_not_found = object() + # support for i18n content class I18NValue(PersistentMapping): """ A dictionary to be used for storing values for different languages. """ + default = None + def lower(self): - return str(self).lower() + # this should only be used as a fallback for the title attribute + return self.getDefault().lower() + + def getDefault(self): + if self.default is None: + return self.values()[0] + return self.default def __str__(self): - return self.values()[0] + return str(self.getDefault()) def getI18nValue(obj, attr, langInfo=None): obj = removeSecurityProxy(obj) value = getattr(obj, attr, None) - lang = None if isinstance(value, I18NValue): - lang = langInfo and langInfo.language or value.keys()[0] - value = value.get(lang) - #print '*** getI18nValue', attr, langInfo, lang, getattr(obj, attr, None), value + if langInfo: + result = value.get(langInfo.language, _not_found) + if result is _not_found: + result = value.get(langInfo.defaultLanguage, _not_found) + if result is _not_found: + result = value.getDefault() + return result + else: + return value.getDefault() return value def setI18nValue(obj, attr, value, langInfo=None): obj = removeSecurityProxy(obj) old = getattr(obj, attr, None) if langInfo is None: - setattr(obj, attr, value) - return + if isinstance(old, I18NValue): + raise ValueError('Attribute %s on object %s is an I18NValue (%s) ' + 'and no langInfo given.' % (attr, obj, value)) + else: + setattr(obj, attr, value) + return lang = langInfo.language if isinstance(old, I18NValue): old[lang] = value else: - setattr(obj, attr, I18NValue(((lang, value),))) + i18nValue = I18NValue(((lang, value),)) + defaultLang = langInfo.defaultLanguage + if lang != defaultLang: + # keep existing value + i18nValue[defaultLang] = old + i18nValue.default = i18nValue[defaultLang] + setattr(obj, attr, i18nValue) #print '*** setI18nValue', attr, langInfo, lang, value, getattr(obj, attr, None) diff --git a/xmlrpc/common.py b/xmlrpc/common.py index f2c57bd..6e52161 100644 --- a/xmlrpc/common.py +++ b/xmlrpc/common.py @@ -34,11 +34,13 @@ from zope.security.proxy import removeSecurityProxy from zope.cachedescriptors.property import Lazy from cybertools.typology.interfaces import IType +from loops.common import adapted from loops.concept import Concept +from loops.i18n.browser import I18NView from loops.util import getUidForObject, getObjectForUid, toUnicode -class LoopsMethods(MethodPublisher): +class LoopsMethods(MethodPublisher, I18NView): """ XML-RPC methods for the loops root object. """ @@ -77,40 +79,44 @@ class LoopsMethods(MethodPublisher): tc = self.concepts.getTypeConcept() types = tc.getChildren((self.typePredicate,)) #types = [t for t in types if ITypeConcept(t).typeInterface ... ] - return [objectAsDict(t) for t in types] + return [objectAsDict(t, self.languageInfo) for t in types] def getPredicates(self): pt = self.concepts.getDefaultPredicate().conceptType preds = pt.getChildren((self.concepts.getTypePredicate(),)) - return [objectAsDict(p) for p in preds if p is not self.typePredicate] + return [objectAsDict(p, self.languageInfo) + for p in preds if p is not self.typePredicate] def getChildren(self, id, predicates=[], child=''): obj = getObjectForUid(id) preds = [getObjectForUid(p) for p in predicates] child = child and getObjectForUid(child) or None rels = obj.getChildRelations(preds or None, child) - return formatRelations(rels) + return formatRelations(rels, langInfo=self.languageInfo) def getParents(self, id, predicates=[], parent=''): obj = getObjectForUid(id) preds = [getObjectForUid(p) for p in predicates] parent = parent and getObjectForUid(parent) or None rels = obj.getParentRelations(preds or None, parent) - return formatRelations(rels, useSecond=False) + return formatRelations(rels, useSecond=False, langInfo=self.languageInfo) def getResources(self, id, predicates=[], resource=''): obj = getObjectForUid(id) preds = [getObjectForUid(p) for p in predicates] resource = resource and getObjectForUid(child) or None rels = obj.getResourceRelations(preds or None, resource) - return formatRelations(rels) + return formatRelations(rels, langInfo=self.languageInfo) def getObjectWithChildren(self, obj): - mapping = objectAsDict(obj) - mapping['children'] = formatRelations(obj.getChildRelations()) - mapping['parents'] = formatRelations( - obj.getParentRelations(), useSecond=False) - mapping['resources'] = formatRelations(obj.getResourceRelations()) + mapping = objectAsDict(obj, self.languageInfo) + mapping['children'] = formatRelations(obj.getChildRelations(sort=None), + langInfo=self.languageInfo) + mapping['parents'] = formatRelations(obj.getParentRelations(sort=None), + useSecond=False, + langInfo=self.languageInfo) + mapping['resources'] = formatRelations(obj.getResourceRelations(sort=None), + langInfo=self.languageInfo) return mapping def assignChild(self, objId, predicateId, childId): @@ -134,36 +140,36 @@ class LoopsMethods(MethodPublisher): name = INameChooser(self.concepts).chooseName(name, c) self.concepts[name] = c c.conceptType = type + adapted(c, self.languageInfo).title = title notify(ObjectCreatedEvent(c)) notify(ObjectModifiedEvent(c)) - return objectAsDict(c) + return objectAsDict(c, self.languageInfo) def editConcept(self, objId, attr, value): obj = getObjectForUid(objId) - ti = IType(obj).typeInterface - if ti is not None: - obj = ti(obj) - # TODO: provide conversion if necessary + adapter = adapted(obj, self.languageInfo) + # TODO: provide conversion if necessary - use cybertools.composer.schema value = value.strip() # remove spaces appended by Flash - setattr(obj, attr, toUnicode(value)) + setattr(adapter, attr, toUnicode(value)) notify(ObjectModifiedEvent(obj)) return 'OK' -def objectAsDict(obj): +def objectAsDict(obj, langInfo=None): objType = IType(obj) + adapter = adapted(obj, langInfo) mapping = {'id': getUidForObject(obj), 'name': getName(obj), - 'title': obj.title, 'description': obj.description, + 'title': adapter.title, 'description': adapter.description, 'type': getUidForObject(objType.typeProvider)} ti = objType.typeInterface if ti is not None: - adapter = ti(obj) #for attr in (list(adapter._adapterAttributes) + list(ti)): for attr in list(ti): if attr not in ('__parent__', 'context', 'id', 'name', 'title', 'description', 'type', 'data'): value = getattr(adapter, attr) - # TODO: provide conversion and schema information + # TODO: provide conversion and schema information - + # use cybertools.composer.schema #if value is None or type(value) in (str, unicode): if ITextLine.providedBy(ti[attr]): mapping[attr] = value or u'' @@ -171,7 +177,7 @@ def objectAsDict(obj): # mapping[attr] = ' | '.join(value) return mapping -def formatRelations(rels, useSecond=True): +def formatRelations(rels, useSecond=True, langInfo=None): predIds = {} result = [] for rel in rels: @@ -185,6 +191,6 @@ def formatRelations(rels, useSecond=True): other = rel.second else: other = rel.first - result[predIds[predId]]['objects'].append(objectAsDict(other)) - return result + result[predIds[predId]]['objects'].append(objectAsDict(other, langInfo)) + return sorted(result, key=lambda x: x['title'])