class DiscussionItemContainer(Persistent, Implicit, Traversable): """ Store DiscussionItem objects. Discussable content that has DiscussionItems associated with it will have an instance of DiscussionItemContainer injected into it to hold the discussion threads. """ implements(IDiscussable, ICallableOpaqueItemEvents) # for the security machinery to allow traversal #__roles__ = None security = ClassSecurityInfo() def __init__(self): self.id = 'talkback' self._container = PersistentMapping() security.declareProtected(View, 'getId') def getId(self): return self.id security.declareProtected(View, 'getReply') def getReply(self, reply_id): """ Return a discussion item, given its ID; raise KeyError if not found. """ return self._container.get(reply_id).__of__(self) # Is this right? security.declareProtected(View, '__bobo_traverse__') def __bobo_traverse__(self, REQUEST, name): """ This will make this container traversable """ target = getattr(self, name, None) if target is not None: return target else: try: return self.getReply(name) except: parent = aq_parent(aq_inner(self)) if parent.getId() == name: return parent else: REQUEST.RESPONSE.notFoundError("%s\n%s" % (name, '')) security.declarePrivate('manage_afterAdd') def manage_afterAdd(self, item, container): """ We have juste been added or moved. Add the contained items to the catalog. """ if aq_base(container) is not aq_base(self): for obj in self.objectValues(): obj.__of__(self).indexObject() security.declarePrivate('manage_afterClone') def manage_afterClone(self, item): """ We have just been cloned. Notify the workflow about the contained items. """ for obj in self.objectValues(): obj.__of__(self).notifyWorkflowCreated() security.declarePrivate('manage_beforeDelete') def manage_beforeDelete(self, item, container): """ Remove the contained items from the catalog. """ if aq_base(container) is not aq_base(self): for obj in self.objectValues(): obj.__of__(self).unindexObject() # # OFS.ObjectManager query interface. # security.declareProtected(AccessContentsInformation, 'objectIds') def objectIds(self, spec=None): """ Return a list of the ids of our DiscussionItems. """ if spec and spec is not DiscussionItem.meta_type: return [] return self._container.keys() security.declareProtected(AccessContentsInformation, 'objectItems') def objectItems(self, spec=None): """ Return a list of (id, subobject) tuples for our DiscussionItems. """ r = [] a = r.append g = self._container.get for id in self.objectIds(spec): a((id, g(id))) return r security.declareProtected(AccessContentsInformation, 'objectValues') def objectValues(self): """ Return a list of our DiscussionItems. """ return self._container.values() # # IDiscussable interface # security.declareProtected(ReplyToItem, 'createReply') def createReply(self, title, text, Creator=None, text_format='structured-text'): """ Create a reply in the proper place """ id = int(DateTime().timeTime()) while self._container.get(str(id), None) is not None: id = id + 1 id = str(id) item = DiscussionItem(id, title=title, description=title) self._container[id] = item item = item.__of__(self) item.setFormat(text_format) item._edit(text) item.addCreator(Creator) item.setReplyTo(self._getDiscussable()) item.indexObject() item.notifyWorkflowCreated() return id security.declareProtected(ManagePortal, 'deleteReply') def deleteReply(self, reply_id): """ Remove a reply from this container """ if reply_id in self._container: reply = self._container.get(reply_id).__of__(self) my_replies = reply.talkback.getReplies() for my_reply in my_replies: my_reply_id = my_reply.getId() self.deleteReply(my_reply_id) if hasattr(reply, 'unindexObject'): reply.unindexObject() del self._container[reply_id] security.declareProtected(View, 'hasReplies') def hasReplies(self, content_obj): """ Test to see if there are any dicussion items """ outer = self._getDiscussable(outer=1) if content_obj == outer: return bool(len(self._container)) else: return bool(len(content_obj.talkback._getReplyResults())) security.declareProtected(View, 'replyCount') def replyCount(self, content_obj): """ How many replies do i have? """ outer = self._getDiscussable(outer=1) if content_obj == outer: return len(self._container) else: replies = content_obj.talkback.getReplies() return self._repcount(replies) security.declarePrivate('_repcount') def _repcount(self, replies): """counts the total number of replies by recursing thru the various levels """ count = 0 for reply in replies: count = count + 1 #if there is at least one reply to this reply replies = reply.talkback.getReplies() if replies: count = count + self._repcount(replies) return count security.declareProtected(View, 'getReplies') def getReplies(self): """ Return a sequence of the IDiscussionResponse objects which are associated with this Discussable """ objects = [] a = objects.append result_ids = self._getReplyResults() for id in result_ids: a(self._container.get(id).__of__(self)) return objects security.declareProtected(View, 'quotedContents') def quotedContents(self): """ Return this object's contents in a form suitable for inclusion as a quote in a response. """ return "" # # Utility methods # security.declarePrivate('_getReplyParent') def _getReplyParent(self, in_reply_to): """ Return the object indicated by the 'in_reply_to', where 'None' represents the "outer" content object. """ outer = self._getDiscussable(outer=1) if not in_reply_to: return outer parent = self._container[in_reply_to].__of__(aq_inner(self)) return parent.__of__(outer) security.declarePrivate('_getDiscussable') def _getDiscussable(self, outer=0): """ """ tb = outer and aq_inner(self) or self return getattr(tb, 'aq_parent', None) security.declarePrivate('_getReplyResults') def _getReplyResults(self): """ Get a list of ids of DiscussionItems which are replies to our Discussable. """ discussable = self._getDiscussable() outer = self._getDiscussable(outer=1) if discussable == outer: in_reply_to = '' else: in_reply_to = discussable.getId() result = [] a = result.append for key, value in self._container.items(): if value.in_reply_to == in_reply_to: a((key, value)) result.sort(lambda a, b: cmp(a[1].creation_date, b[1].creation_date)) return [ x[0] for x in result ]
class UserFolder(BasicUserFolder): """Standard UserFolder object A UserFolder holds User objects which contain information about users including name, password domain, and roles. UserFolders function chiefly to control access by authenticating users and binding them to a collection of roles.""" implements(IStandardUserFolder) meta_type = 'User Folder' id = 'acl_users' title = 'User Folder' def __init__(self): self.data=PersistentMapping() def getUserNames(self): """Return a list of usernames""" names=self.data.keys() names.sort() return names def getUsers(self): """Return a list of user objects""" data=self.data names=data.keys() names.sort() return [data[n] for n in names] def getUser(self, name): """Return the named user object or None""" return self.data.get(name, None) def hasUsers(self): """ This is not a formal API method: it is used only to provide a way for the quickstart page to determine if the default user folder contains any users to provide instructions on how to add a user for newbies. Using getUserNames or getUsers would have posed a denial of service risk.""" return not not len(self.data) def _doAddUser(self, name, password, roles, domains, **kw): """Create a new user""" if password is not None and self.encrypt_passwords \ and not self._isPasswordEncrypted(password): password = self._encryptPassword(password) self.data[name] = User(name, password, roles, domains) def _doChangeUser(self, name, password, roles, domains, **kw): user=self.data[name] if password is not None: if (self.encrypt_passwords and not self._isPasswordEncrypted(password)): password = self._encryptPassword(password) user.__ = password user.roles = roles user.domains = domains def _doDelUsers(self, names): for name in names: del self.data[name]
class MessageCatalog(LanguageManager, ObjectManager, SimpleItem): """Stores messages and their translations... """ meta_type = 'MessageCatalog' implements(IMessageCatalog) security = ClassSecurityInfo() def __init__(self, id, title, sourcelang, languages): self.id = id self.title = title # Language Manager data self._languages = tuple(languages) self._default_language = sourcelang # Here the message translations are stored self._messages = PersistentMapping() # Data for the PO files headers self._po_headers = PersistentMapping() for lang in self._languages: self._po_headers[lang] = empty_po_header ####################################################################### # ITranslationDomain interface # zope.i18n.interfaces.ITranslationDomain ####################################################################### @property def domain(self): """ """ return unicode(self.id) def translate(self, msgid, mapping=None, context=None, target_language=None, default=None): """ """ msgstr = self.gettext(msgid, lang=target_language, default=default) return interpolate(msgstr, mapping) ####################################################################### # Private API ####################################################################### def get_message_key(self, message): if message in self._messages: return message # A message may be stored as unicode or byte string encoding = HTTPRequest.default_encoding if isinstance(message, unicode): message = message.encode(encoding) else: message = unicode(message, encoding) if message in self._messages: return message def get_translations(self, message): message = self.get_message_key(message) return self._messages[message] def get_tabs_message(self, REQUEST): message = REQUEST.get('manage_tabs_message') if message is None: return None return unicode(message, 'utf-8') ####################################################################### # Public API ####################################################################### security.declarePublic('message_exists') def message_exists(self, message): """ """ return self._messages.has_key(message) security.declareProtected('Manage messages', 'message_edit') def message_edit(self, message, language, translation, note): """ """ self._messages[message][language] = translation self._messages[message]['note'] = note security.declareProtected('Manage messages', 'message_del') def message_del(self, message): """ """ del self._messages[message] security.declarePublic('gettext') def gettext(self, message, lang=None, add=1, default=None): """Returns the message translation from the database if available. If add=1, add any unknown message to the database. If a default is provided, use it instead of the message id as a translation for unknown messages. """ if not isinstance(message, (str, unicode)): raise TypeError, 'only strings can be translated.' message = message.strip() if default is None: default = message # Add it if it's not in the dictionary if add and not self._messages.has_key(message) and message: self._messages[message] = PersistentMapping() # Get the string if self._messages.has_key(message): m = self._messages[message] if lang is None: # Builds the list of available languages # should the empty translations be filtered? available_languages = list(self._languages) # Imagine that the default language is 'en'. There is no # translation from 'en' to 'en' in the message catalog # The user has the preferences 'en' and 'nl' in that order # The next two lines make certain 'en' is shown, not 'nl' if not self._default_language in available_languages: available_languages.append(self._default_language) # Get the language! lang = lang_negotiator(available_languages) # Is it None? use the default if lang is None: lang = self._default_language if lang is not None: return m.get(lang) or default return default __call__ = gettext ####################################################################### # Management screens ####################################################################### manage_options = ( {'label': u'Messages', 'action': 'manage_messages', 'help': ('Localizer', 'MC_messages.stx')}, {'label': u'Properties', 'action': 'manage_propertiesForm'}, {'label': u'Import', 'action': 'manage_Import_form', 'help': ('Localizer', 'MC_importExport.stx')}, {'label': u'Export', 'action': 'manage_Export_form', 'help': ('Localizer', 'MC_importExport.stx')}) \ + LanguageManager.manage_options \ + SimpleItem.manage_options ####################################################################### # Management screens -- Messages ####################################################################### security.declareProtected('Manage messages', 'manage_messages') manage_messages = LocalDTMLFile('ui/MC_messages', globals()) security.declarePublic('get_namespace') def get_namespace(self, REQUEST): """For the management interface, allows to filter the messages to show. """ # Check whether there are languages or not languages = self.get_languages_mapping() if not languages: return {} # Input batch_start = REQUEST.get('batch_start', 0) batch_size = REQUEST.get('batch_size', 15) empty = REQUEST.get('empty', 0) regex = REQUEST.get('regex', '') message = REQUEST.get('msg', None) # Build the namespace namespace = {} namespace['batch_size'] = batch_size namespace['empty'] = empty namespace['regex'] = regex # The language lang = REQUEST.get('lang', None) or languages[0]['code'] namespace['language'] = lang # Filter the messages query = regex.strip() try: query = compile(query) except: query = compile('') messages = [] for m, t in self._messages.items(): if query.search(m) and (not empty or not t.get(lang, '').strip()): messages.append(m) messages.sort(filter_sort) # How many messages n = len(messages) namespace['n_messages'] = n # Calculate the start while batch_start >= n: batch_start = batch_start - batch_size if batch_start < 0: batch_start = 0 namespace['batch_start'] = batch_start # Select the batch to show batch_end = batch_start + batch_size messages = messages[batch_start:batch_end] # Batch links namespace['previous'] = get_url(REQUEST.URL, batch_start - batch_size, batch_size, regex, lang, empty) namespace['next'] = get_url(REQUEST.URL, batch_start + batch_size, batch_size, regex, lang, empty) # Get the message message_encoded = None translations = {} if message is None: if messages: message = messages[0] translations = self.get_translations(message) message = to_unicode(message) message_encoded = message_encode(message) else: message_encoded = message message = message_decode(message_encoded) translations = self.get_translations(message) message = to_unicode(message) namespace['message'] = message namespace['message_encoded'] = message_encoded namespace['translations'] = translations namespace['translation'] = translations.get(lang, '') namespace['note'] = translations.get('note', '') # Calculate the current message namespace['messages'] = [] for x in messages: x = to_unicode(x) x_encoded = message_encode(x) url = get_url( REQUEST.URL, batch_start, batch_size, regex, lang, empty, msg=x_encoded) namespace['messages'].append({ 'message': x, 'message_encoded': x_encoded, 'current': x == message, 'url': url}) # The languages for language in languages: code = language['code'] language['name'] = _(language['name'], language=code) language['url'] = get_url(REQUEST.URL, batch_start, batch_size, regex, code, empty, msg=message_encoded) namespace['languages'] = languages return namespace security.declareProtected('Manage messages', 'manage_editMessage') def manage_editMessage(self, message, language, translation, note, REQUEST, RESPONSE): """Modifies a message. """ message_encoded = message message = message_decode(message_encoded) message_key = self.get_message_key(message) self.message_edit(message_key, language, translation, note) url = get_url(REQUEST.URL1 + '/manage_messages', REQUEST['batch_start'], REQUEST['batch_size'], REQUEST['regex'], REQUEST.get('lang', ''), REQUEST.get('empty', 0), msg=message_encoded, manage_tabs_message=_(u'Saved changes.')) RESPONSE.redirect(url) security.declareProtected('Manage messages', 'manage_delMessage') def manage_delMessage(self, message, REQUEST, RESPONSE): """ """ message = message_decode(message) message_key = self.get_message_key(message) self.message_del(message_key) url = get_url(REQUEST.URL1 + '/manage_messages', REQUEST['batch_start'], REQUEST['batch_size'], REQUEST['regex'], REQUEST.get('lang', ''), REQUEST.get('empty', 0), manage_tabs_message=_(u'Saved changes.')) RESPONSE.redirect(url) ####################################################################### # Management screens -- Properties # Management screens -- Import/Export # FTP access ####################################################################### security.declareProtected('View management screens', 'manage_propertiesForm') manage_propertiesForm = LocalDTMLFile('ui/MC_properties', globals()) security.declareProtected('View management screens', 'manage_properties') def manage_properties(self, title, REQUEST=None, RESPONSE=None): """Change the Message Catalog properties. """ self.title = title if RESPONSE is not None: RESPONSE.redirect('manage_propertiesForm') # Properties management screen security.declareProtected('View management screens', 'get_po_header') def get_po_header(self, lang): """ """ # For backwards compatibility if not hasattr(aq_base(self), '_po_headers'): self._po_headers = PersistentMapping() return self._po_headers.get(lang, empty_po_header) security.declareProtected('View management screens', 'update_po_header') def update_po_header(self, lang, last_translator_name=None, last_translator_email=None, language_team=None, charset=None, REQUEST=None, RESPONSE=None): """ """ header = self.get_po_header(lang) if last_translator_name is None: last_translator_name = header['last_translator_name'] if last_translator_email is None: last_translator_email = header['last_translator_email'] if language_team is None: language_team = header['language_team'] if charset is None: charset = header['charset'] header = {'last_translator_name': last_translator_name, 'last_translator_email': last_translator_email, 'language_team': language_team, 'charset': charset} self._po_headers[lang] = header if RESPONSE is not None: RESPONSE.redirect('manage_propertiesForm') security.declareProtected('View management screens', 'manage_Import_form') manage_Import_form = LocalDTMLFile('ui/MC_Import_form', globals()) security.declarePublic('get_charsets') def get_charsets(self): """ """ return charsets[:] security.declarePublic('manage_export') def manage_export(self, x, REQUEST=None, RESPONSE=None): """Exports the content of the message catalog either to a template file (locale.pot) or to an language specific PO file (<x>.po). """ # Get the PO header info header = self.get_po_header(x) last_translator_name = header['last_translator_name'] last_translator_email = header['last_translator_email'] language_team = header['language_team'] charset = header['charset'] # PO file header, empty message. po_revision_date = strftime('%Y-%m-%d %H:%m+%Z', gmtime(time())) pot_creation_date = po_revision_date last_translator = '%s <%s>' % (last_translator_name, last_translator_email) if x == 'locale.pot': language_team = 'LANGUAGE <*****@*****.**>' else: language_team = '%s <%s>' % (x, language_team) r = ['msgid ""', 'msgstr "Project-Id-Version: %s\\n"' % self.title, '"POT-Creation-Date: %s\\n"' % pot_creation_date, '"PO-Revision-Date: %s\\n"' % po_revision_date, '"Last-Translator: %s\\n"' % last_translator, '"Language-Team: %s\\n"' % language_team, '"MIME-Version: 1.0\\n"', '"Content-Type: text/plain; charset=%s\\n"' % charset, '"Content-Transfer-Encoding: 8bit\\n"', '', ''] # Get the messages, and perhaps its translations. d = {} if x == 'locale.pot': filename = x for k in self._messages.keys(): d[k] = "" else: filename = '%s.po' % x for k, v in self._messages.items(): try: d[k] = v[x] except KeyError: d[k] = "" # Generate the file def backslashescape(x): quote_esc = compile(r'"') x = quote_esc.sub('\\"', x) trans = [('\n', '\\n'), ('\r', '\\r'), ('\t', '\\t')] for a, b in trans: x = x.replace(a, b) return x # Generate sorted msgids to simplify diffs dkeys = d.keys() dkeys.sort() for k in dkeys: r.append('msgid "%s"' % backslashescape(k)) v = d[k] r.append('msgstr "%s"' % backslashescape(v)) r.append('') if RESPONSE is not None: RESPONSE.setHeader('Content-type','application/data') RESPONSE.setHeader('Content-Disposition', 'inline;filename=%s' % filename) r2 = [] for x in r: if isinstance(x, unicode): r2.append(x.encode(charset)) else: r2.append(x) return '\n'.join(r2) security.declareProtected('Manage messages', 'po_import') def po_import(self, lang, data): """ """ messages = self._messages # Load the data po = itools.gettext.POFile(string=data) for msgid in po.get_msgids(): # TODO Keep the context if any _context, msgid = msgid if msgid: msgstr = po.get_msgstr(msgid) or '' if not messages.has_key(msgid): messages[msgid] = PersistentMapping() messages[msgid][lang] = msgstr # Set the encoding (the full header should be loaded XXX) self.update_po_header(lang, charset=po.get_encoding()) security.declareProtected('Manage messages', 'manage_import') def manage_import(self, lang, file, REQUEST=None, RESPONSE=None): """ """ # XXX For backwards compatibility only, use "po_import" instead. if isinstance(file, str): content = file else: content = file.read() self.po_import(lang, content) if RESPONSE is not None: RESPONSE.redirect('manage_messages') def objectItems(self, spec=None): """ """ for lang in self._languages: if not hasattr(aq_base(self), lang): self._setObject(lang, POFile(lang)) r = MessageCatalog.inheritedAttribute('objectItems')(self, spec) return r ####################################################################### # TMX support security.declareProtected('View management screens', 'manage_Export_form') manage_Export_form = LocalDTMLFile('ui/MC_Export_form', globals()) security.declareProtected('Manage messages', 'tmx_export') def tmx_export(self, REQUEST, RESPONSE=None): """Exports the content of the message catalog to a TMX file """ src_lang = self._default_language # Get the header info header = self.get_po_header(src_lang) charset = header['charset'] # Init the TMX handler tmx = TMXFile() tmx.header['creationtool'] = u'Localizer' tmx.header['creationtoolversion'] = u'1.x' tmx.header['datatype'] = u'plaintext' tmx.header['segtype'] = u'paragraph' tmx.header['adminlang'] = src_lang tmx.header['srclang'] = src_lang tmx.header['o-encoding'] = u'%s' % charset.lower() # handle messages for msgkey, transunit in self._messages.items(): unit = TMXUnit({}) for lang in transunit.keys(): if lang != 'note': sentence = Sentence({'lang': lang}) sentence.text = transunit[lang] unit.msgstr[lang] = sentence if src_lang not in transunit.keys(): sentence = Sentence({'lang': src_lang}) sentence.text = msgkey unit.msgstr[src_lang] = sentence if transunit.has_key('note'): note = TMXNote(transunit.get('note')) unit.notes.append(note) tmx.messages[msgkey] = unit if RESPONSE is not None: RESPONSE.setHeader('Content-type','application/data') RESPONSE.setHeader('Content-Disposition', 'attachment; filename="%s.tmx"' % self.id) return tmx.to_str() security.declareProtected('Manage messages', 'tmx_import') def tmx_import(self, howmuch, file, REQUEST=None, RESPONSE=None): """Imports a TMX level 1 file. """ try: data = file.read() tmx = TMXFile(string=data) except: return MessageDialog(title = 'Parse error', message = _('impossible to parse the file') , action = 'manage_Import_form',) num_notes = 0 num_trans = 0 if howmuch == 'clear': # Clear the message catalogue prior to import self._messages = {} self._languages = () self._default_language = tmx.get_srclang() for id, msg in tmx.messages.items(): if not self._messages.has_key(id) and howmuch == 'existing': continue msg.msgstr.pop(self._default_language) if not self._messages.has_key(id): self._messages[id] = {} for lang in msg.msgstr.keys(): # normalize the languageTag and extract the core (core, local) = LanguageTag.decode(lang) lang = LanguageTag.encode((core, local)) if lang not in self._languages: self._languages += (lang,) if msg.msgstr[lang].text: self._messages[id][lang] = msg.msgstr[lang].text if core != lang and core != self._default_language: if core not in self._languages: self._languages += (core,) if not msg.msgstr.has_key(core): self._messages[id][core] = msg.msgstr[lang].text if msg.notes: ns = [m.text for m in msg.notes] self._messages[id]['note'] = u' '.join(ns) num_notes += 1 num_trans += 1 if REQUEST is not None: message = _(u'Imported %d messages and %d notes') return MessageDialog( title = _(u'Messages imported'), message = message % (num_trans, num_notes), action = 'manage_messages') ####################################################################### # Backwards compatibility (XXX) ####################################################################### hasmsg = message_exists hasLS = message_exists # CMFLocalizer uses it security.declareProtected('Manage messages', 'xliff_export') def xliff_export(self, dst_lang, export_all=1, REQUEST=None, RESPONSE=None): """Exports the content of the message catalog to an XLIFF file """ from DateTime import DateTime src_lang = self._default_language export_all = int(export_all) # Init the XLIFF handler xliff = XLFFile() # Add the translation units original = '/%s' % self.absolute_url(1) for msgkey, transunit in self._messages.items(): target = transunit.get(dst_lang, '') # If 'export_all' is true export all messages, otherwise export # only untranslated messages if export_all or not target: unit = xliff.add_unit(original, msgkey, None) unit.attributes['id'] = md5text(msgkey) if target: unit.target = target # Add note note = transunit.get('note') if note: unit.notes.append(XLFNote(note)) # build the data-stucture for the File tag file = xliff.files[original] attributes = file.attributes attributes['original'] = original attributes['product-name'] = u'Localizer' attributes['product-version'] = u'1.1.x' attributes['data-type'] = u'plaintext' attributes['source-language'] = src_lang attributes['target-language'] = dst_lang attributes['date'] = DateTime().HTML4() # Serialize xliff = xliff.to_str() # Set response headers RESPONSE.setHeader('Content-Type', 'text/xml; charset=UTF-8') filename = '%s_%s_%s.xlf' % (self.id, src_lang, dst_lang) RESPONSE.setHeader('Content-Disposition', 'attachment; filename="%s"' % filename) # Ok return xliff security.declareProtected('Manage messages', 'xliff_import') def xliff_import(self, howmuch, file, REQUEST=None): """XLIFF is the XML Localization Interchange File Format designed by a group of software providers. It is specified by www.oasis-open.org """ try: data = file.read() xliff = XLFFile(string=data) except: return MessageDialog(title = 'Parse error', message = _('impossible to parse the file') , action = 'manage_Import_form',) num_notes = 0 num_trans = 0 (file_ids, sources, targets) = xliff.get_languages() if howmuch == 'clear': # Clear the message catalogue prior to import self._messages = {} self._languages = () self._default_language = sources[0] # update languages if len(sources) > 1 or sources[0] != self._default_language: return MessageDialog(title = 'Language error', message = _('incompatible language sources') , action = 'manage_Import_form',) for lang in targets: if lang != self._default_language and lang not in self._languages: self._languages += (lang,) # get messages for file in xliff.files: cur_target = file.attributes.get('target-language', '') for msg in file.body.keys(): if not self._messages.has_key(msg) and howmuch == 'existing': pass else: if not self._messages.has_key(msg): self._messages[msg] = {} if cur_target and file.body[msg].target: self._messages[msg][cur_target] = file.body[msg].target num_trans += 1 if file.body[msg].notes: ns = [n.text for n in file.body[msg].notes] comment = ' '.join(ns) self._messages[msg]['note'] = comment num_notes += 1 if REQUEST is not None: return MessageDialog( title = _(u'Messages imported'), message = (_(u'Imported %d messages and %d notes to %s') % \ (num_trans, num_notes, ' '.join(targets))), action = 'manage_messages')
class MessageCatalog(LanguageManager, ObjectManager, SimpleItem): """Stores messages and their translations... """ meta_type = 'MessageCatalog' implements(IMessageCatalog) security = ClassSecurityInfo() POLICY_ADD_FALSE = 0 POLICY_ADD_TRUE = 1 POLICY_ADD_LOG = 2 def __init__(self, id, title, sourcelang, languages): self.id = id self.title = title self.policy = self.POLICY_ADD_TRUE # Language Manager data self._languages = tuple(languages) self._default_language = sourcelang # Here the message translations are stored self._messages = PersistentMapping() # Data for the PO files headers self._po_headers = PersistentMapping() for lang in self._languages: self._po_headers[lang] = empty_po_header ####################################################################### # ITranslationDomain interface # zope.i18n.interfaces.ITranslationDomain ####################################################################### @property def domain(self): """ """ return unicode(self.id) def translate(self, msgid, mapping=None, context=None, target_language=None, default=None): """ """ msgstr = self.gettext(msgid, lang=target_language, default=default) # BBB support str in mapping by converting to unicode for # backward compatibility. if mapping: mapping = dict([to_unicode(k), to_unicode(v)] for k, v in mapping.iteritems()) return interpolate(msgstr, mapping) ####################################################################### # Private API ####################################################################### def get_message_key(self, message): if message in self._messages: return message # A message may be stored as unicode or byte string encoding = HTTPRequest.default_encoding if isinstance(message, unicode): message = message.encode(encoding) else: message = unicode(message, encoding) if message in self._messages: return message def get_translations(self, message): message = self.get_message_key(message) return self._messages[message] def get_tabs_message(self, REQUEST): message = REQUEST.get('manage_tabs_message') if message is None: return None return unicode(message, 'utf-8') ####################################################################### # Public API ####################################################################### security.declarePublic('message_exists') def message_exists(self, message): """ """ # BBB call get_message_key to support both (old) str key and # (new) unicode key. return bool(self.get_message_key(message)) security.declareProtected('Manage messages', 'message_edit') def message_edit(self, message, language, translation, note): """ """ # BBB call get_message_key to support both (old) str key and # (new) unicode key. message = self.get_message_key(message) or message self._messages[message][language] = translation self._messages[message]['note'] = note security.declareProtected('Manage messages', 'message_del') def message_del(self, message): """ """ # BBB call get_message_key to support both (old) str key and # (new) unicode key. message = self.get_message_key(message) or message del self._messages[message] security.declarePublic('gettext') def gettext(self, message, lang=None, add=None, default=None): """Returns the message translation from the database if available. If add=1, add any unknown message to the database. If a default is provided, use it instead of the message id as a translation for unknown messages. """ if not isinstance(message, basestring): raise TypeError, 'only strings can be translated.' if default is None: default = message message = message.strip() # BBB call get_message_key to support both (old) str key and # (new) unicode key. message = self.get_message_key(message) or to_unicode(message) # Add it if it's not in the dictionary if add is None: add = getattr(self, 'policy', self.POLICY_ADD_TRUE) if add != self.POLICY_ADD_FALSE and not self._messages.has_key(message) and message: if add == self.POLICY_ADD_LOG: LOG('New entry added to message catalog %s :' % self.id, INFO, '%s\n%s' % (message, ''.join(format_list(extract_stack()[:-1])))) self._messages[message] = PersistentMapping() # Get the string if self._messages.has_key(message): m = self._messages[message] if lang is None: # Builds the list of available languages # should the empty translations be filtered? available_languages = list(self._languages) # Imagine that the default language is 'en'. There is no # translation from 'en' to 'en' in the message catalog # The user has the preferences 'en' and 'nl' in that order # The next two lines make certain 'en' is shown, not 'nl' if not self._default_language in available_languages: available_languages.append(self._default_language) # Get the language! lang = lang_negotiator(available_languages) # Is it None? use the default if lang is None: lang = self._default_language if lang is not None: return m.get(lang) or default return default __call__ = gettext ####################################################################### # Management screens ####################################################################### manage_options = ( {'label': u'Messages', 'action': 'manage_messages', 'help': ('Localizer', 'MC_messages.stx')}, {'label': u'Properties', 'action': 'manage_propertiesForm'}, {'label': u'Import', 'action': 'manage_Import_form', 'help': ('Localizer', 'MC_importExport.stx')}, {'label': u'Export', 'action': 'manage_Export_form', 'help': ('Localizer', 'MC_importExport.stx')}) \ + LanguageManager.manage_options \ + SimpleItem.manage_options ####################################################################### # Management screens -- Messages ####################################################################### security.declareProtected('Manage messages', 'manage_messages') manage_messages = LocalDTMLFile('ui/MC_messages', globals()) security.declarePublic('get_namespace') def get_namespace(self, REQUEST): """For the management interface, allows to filter the messages to show. """ # Check whether there are languages or not languages = self.get_languages_mapping() if not languages: return {} # Input batch_start = REQUEST.get('batch_start', 0) batch_size = REQUEST.get('batch_size', 15) empty = REQUEST.get('empty', 0) regex = REQUEST.get('regex', '') message = REQUEST.get('msg', None) # Build the namespace namespace = {} namespace['batch_size'] = batch_size namespace['empty'] = empty namespace['regex'] = regex # The language lang = REQUEST.get('lang', None) or languages[0]['code'] namespace['language'] = lang # Filter the messages query = regex.strip() try: query = compile(query) except: query = compile('') messages = [] for m, t in self._messages.items(): if query.search(m) and (not empty or not t.get(lang, '').strip()): messages.append(m) messages.sort(filter_sort) # How many messages n = len(messages) namespace['n_messages'] = n # Calculate the start while batch_start >= n: batch_start = batch_start - batch_size if batch_start < 0: batch_start = 0 namespace['batch_start'] = batch_start # Select the batch to show batch_end = batch_start + batch_size messages = messages[batch_start:batch_end] # Batch links namespace['previous'] = get_url(REQUEST.URL, batch_start - batch_size, batch_size, regex, lang, empty) namespace['next'] = get_url(REQUEST.URL, batch_start + batch_size, batch_size, regex, lang, empty) # Get the message message_encoded = None translations = {} if message is None: if messages: message = messages[0] translations = self.get_translations(message) message = to_unicode(message) message_encoded = message_encode(message) else: message_encoded = message message = message_decode(message_encoded) translations = self.get_translations(message) message = to_unicode(message) namespace['message'] = message namespace['message_encoded'] = message_encoded namespace['translations'] = translations namespace['translation'] = translations.get(lang, '') namespace['note'] = translations.get('note', '') # Calculate the current message namespace['messages'] = [] for x in messages: x = to_unicode(x) x_encoded = message_encode(x) url = get_url( REQUEST.URL, batch_start, batch_size, regex, lang, empty, msg=x_encoded) namespace['messages'].append({ 'message': x, 'message_encoded': x_encoded, 'current': x == message, 'url': url}) # The languages for language in languages: code = language['code'] language['name'] = _(language['name'], language=code) language['url'] = get_url(REQUEST.URL, batch_start, batch_size, regex, code, empty, msg=message_encoded) namespace['languages'] = languages return namespace security.declareProtected('Manage messages', 'manage_editMessage') def manage_editMessage(self, message, language, translation, note, REQUEST, RESPONSE): """Modifies a message. """ message_encoded = message message = message_decode(message_encoded) message_key = self.get_message_key(message) self.message_edit(message_key, language, translation, note) url = get_url(REQUEST.URL1 + '/manage_messages', REQUEST['batch_start'], REQUEST['batch_size'], REQUEST['regex'], REQUEST.get('lang', ''), REQUEST.get('empty', 0), msg=message_encoded, manage_tabs_message=_(u'Saved changes.')) RESPONSE.redirect(url) security.declareProtected('Manage messages', 'manage_delMessage') def manage_delMessage(self, message, REQUEST, RESPONSE): """ """ message = message_decode(message) message_key = self.get_message_key(message) self.message_del(message_key) url = get_url(REQUEST.URL1 + '/manage_messages', REQUEST['batch_start'], REQUEST['batch_size'], REQUEST['regex'], REQUEST.get('lang', ''), REQUEST.get('empty', 0), manage_tabs_message=_(u'Saved changes.')) RESPONSE.redirect(url) ####################################################################### # Management screens -- Properties # Management screens -- Import/Export # FTP access ####################################################################### security.declareProtected('View management screens', 'manage_propertiesForm') manage_propertiesForm = LocalDTMLFile('ui/MC_properties', globals()) security.declareProtected('View management screens', 'manage_properties') def manage_properties(self, title, policy, REQUEST=None, RESPONSE=None): """Change the Message Catalog properties. """ self.title = title self.policy = int(policy) if RESPONSE is not None: RESPONSE.redirect('manage_propertiesForm') # Properties management screen security.declareProtected('View management screens', 'get_po_header') def get_po_header(self, lang): """ """ # For backwards compatibility if not hasattr(aq_base(self), '_po_headers'): self._po_headers = PersistentMapping() return self._po_headers.get(lang, empty_po_header) security.declareProtected('View management screens', 'update_po_header') def update_po_header(self, lang, last_translator_name=None, last_translator_email=None, language_team=None, charset=None, REQUEST=None, RESPONSE=None): """ """ header = self.get_po_header(lang) if last_translator_name is None: last_translator_name = header['last_translator_name'] if last_translator_email is None: last_translator_email = header['last_translator_email'] if language_team is None: language_team = header['language_team'] if charset is None: charset = header['charset'] header = {'last_translator_name': last_translator_name, 'last_translator_email': last_translator_email, 'language_team': language_team, 'charset': charset} self._po_headers[lang] = header if RESPONSE is not None: RESPONSE.redirect('manage_propertiesForm') security.declareProtected('View management screens', 'manage_Import_form') manage_Import_form = LocalDTMLFile('ui/MC_Import_form', globals()) security.declarePublic('get_policies') def get_policies(self): """ """ if not hasattr(self, 'policy'): self.policy = self.POLICY_ADD_TRUE policies = [ [self.POLICY_ADD_FALSE, "Never add new entries automatically"], [self.POLICY_ADD_TRUE, "Add new entries automatically if missing"], [self.POLICY_ADD_LOG, "Add new entries automatically if missing and log the backtrace"], ] return policies security.declarePublic('get_charsets') def get_charsets(self): """ """ return charsets[:] security.declarePublic('manage_export') def manage_export(self, x, REQUEST=None, RESPONSE=None): """Exports the content of the message catalog either to a template file (locale.pot) or to an language specific PO file (<x>.po). """ # Get the PO header info header = self.get_po_header(x) last_translator_name = header['last_translator_name'] last_translator_email = header['last_translator_email'] language_team = header['language_team'] charset = header['charset'] # PO file header, empty message. po_revision_date = strftime('%Y-%m-%d %H:%m+%Z', gmtime(time())) pot_creation_date = po_revision_date last_translator = '%s <%s>' % (last_translator_name, last_translator_email) if x == 'locale.pot': language_team = 'LANGUAGE <*****@*****.**>' else: language_team = '%s <%s>' % (x, language_team) r = ['msgid ""', 'msgstr "Project-Id-Version: %s\\n"' % self.title, '"POT-Creation-Date: %s\\n"' % pot_creation_date, '"PO-Revision-Date: %s\\n"' % po_revision_date, '"Last-Translator: %s\\n"' % last_translator, '"Language-Team: %s\\n"' % language_team, '"MIME-Version: 1.0\\n"', '"Content-Type: text/plain; charset=%s\\n"' % charset, '"Content-Transfer-Encoding: 8bit\\n"', '', ''] # Get the messages, and perhaps its translations. # Convert keys to unicode for proper sorting. d = {} if x == 'locale.pot': filename = x for k in self._messages.keys(): d[to_unicode(k, encoding=charset)] = u"" else: filename = '%s.po' % x for k, v in self._messages.items(): k = to_unicode(k, encoding=charset) d[k] = to_unicode(v.get(x, ""), encoding=charset) # Generate the file def backslashescape(x): x = to_str(x) quote_esc = compile(r'"') x = quote_esc.sub('\\"', x) trans = [('\n', '\\n'), ('\r', '\\r'), ('\t', '\\t')] for a, b in trans: x = x.replace(a, b) return x # Generate sorted msgids to simplify diffs dkeys = d.keys() dkeys.sort() for k in dkeys: r.append('msgid "%s"' % backslashescape(k)) v = d[k] r.append('msgstr "%s"' % backslashescape(v)) r.append('') if RESPONSE is not None: RESPONSE.setHeader('Content-type','application/data') RESPONSE.setHeader('Content-Disposition', 'inline;filename=%s' % filename) return '\n'.join(r) security.declareProtected('Manage messages', 'po_import') def po_import(self, lang, data): """ """ messages = self._messages # Load the data po = polib.pofile(data) encoding = to_str(po.encoding) for entry in po: msgid = to_unicode(entry.msgid, encoding=encoding) if msgid: msgstr = to_unicode(entry.msgstr or '', encoding=encoding) translation_map = messages.get(msgid) if translation_map is None: # convert old non-unicode translations if they exist: translation_map = messages.pop(self.get_message_key(msgid), None) if translation_map is None: translation_map = PersistentMapping() messages[msgid] = translation_map translation_map[lang] = msgstr # Set the encoding (the full header should be loaded XXX) self.update_po_header(lang, charset=encoding) security.declareProtected('Manage messages', 'manage_import') def manage_import(self, lang, file, REQUEST=None, RESPONSE=None): """ """ # XXX For backwards compatibility only, use "po_import" instead. if isinstance(file, str): content = file else: content = file.read() self.po_import(lang, content) if RESPONSE is not None: RESPONSE.redirect('manage_messages') def objectItems(self, spec=None): """ """ for lang in self._languages: if not hasattr(aq_base(self), lang): self._setObject(lang, POFile(lang)) r = MessageCatalog.inheritedAttribute('objectItems')(self, spec) return r ####################################################################### # TMX support security.declareProtected('View management screens', 'manage_Export_form') manage_Export_form = LocalDTMLFile('ui/MC_Export_form', globals()) ####################################################################### # Backwards compatibility (XXX) ####################################################################### hasmsg = message_exists hasLS = message_exists # CMFLocalizer uses it
class HBTreeFolder2Base (Persistent): """Base for BTree-based folders. """ security = ClassSecurityInfo() manage_options=( ({'label':'Contents', 'action':'manage_main',}, ) + Folder.manage_options[1:] ) security.declareProtected(view_management_screens, 'manage_main') manage_main = DTMLFile('contents', globals()) _htree = None # OOBTree: { id -> object } _count = None # A BTrees.Length _v_nextid = 0 # The integer component of the next generated ID title = '' _tree_list = None def __init__(self, id=None): if id is not None: self.id = id self._initBTrees() def _initBTrees(self): self._htree = OOBTree() self._count = Length() self._tree_list = PersistentMapping() def _populateFromFolder(self, source): """Fill this folder with the contents of another folder. """ for name in source.objectIds(): value = source._getOb(name, None) if value is not None: self._setOb(name, aq_base(value)) security.declareProtected(view_management_screens, 'manage_fixCount') def manage_fixCount(self): """Calls self._fixCount() and reports the result as text. """ old, new = self._fixCount() path = '/'.join(self.getPhysicalPath()) if old == new: return "No count mismatch detected in HBTreeFolder2 at %s." % path else: return ("Fixed count mismatch in HBTreeFolder2 at %s. " "Count was %d; corrected to %d" % (path, old, new)) def _fixCount(self): """Checks if the value of self._count disagrees with len(self.objectIds()). If so, corrects self._count. Returns the old and new count values. If old==new, no correction was performed. """ old = self._count() new = len(self.objectIds()) if old != new: self._count.set(new) return old, new security.declareProtected(view_management_screens, 'manage_cleanup') def manage_cleanup(self): """Calls self._cleanup() and reports the result as text. """ v = self._cleanup() path = '/'.join(self.getPhysicalPath()) if v: return "No damage detected in HBTreeFolder2 at %s." % path else: return ("Fixed HBTreeFolder2 at %s. " "See the log for more details." % path) def _cleanup(self): """Cleans up errors in the BTrees. Certain ZODB bugs have caused BTrees to become slightly insane. Fortunately, there is a way to clean up damaged BTrees that always seems to work: make a new BTree containing the items() of the old one. Returns 1 if no damage was detected, or 0 if damage was detected and fixed. """ def hCheck(htree): """ Recursively check the btree """ check(htree) for key in htree.keys(): if not htree.has_key(key): raise AssertionError( "Missing value for key: %s" % repr(key)) else: ob = htree[key] if isinstance(ob, OOBTree): hCheck(ob) return 1 from BTrees.check import check path = '/'.join(self.getPhysicalPath()) try: return hCheck(self._htree) except AssertionError: LOG('HBTreeFolder2', WARNING, 'Detected damage to %s. Fixing now.' % path, error=sys.exc_info()) try: self._htree = OOBTree(self._htree) # XXX hFix needed except: LOG('HBTreeFolder2', ERROR, 'Failed to fix %s.' % path, error=sys.exc_info()) raise else: LOG('HBTreeFolder2', INFO, 'Fixed %s.' % path) return 0 def hashId(self, id): """Return a tuple of ids """ # XXX: why tolerate non-string ids ? id_list = str(id).split(H_SEPARATOR) # We use '-' as the separator by default if len(id_list) > 1: return tuple(id_list) else: return [id,] # try: # We then try int hashing # id_int = int(id) # except ValueError: # return id_list # result = [] # while id_int: # result.append(id_int % MAX_OBJECT_PER_LEVEL) # id_int = id_int / MAX_OBJECT_PER_LEVEL # result.reverse() # return tuple(result) def _getOb(self, id, default=_marker): """ Return the named object from the folder. """ htree = self._htree ob = htree id_list = self.hashId(id) for sub_id in id_list[0:-1]: if default is _marker: ob = ob[sub_id] else: ob = ob.get(sub_id, _marker) if ob is _marker: return default if default is _marker: ob = ob[id] else: ob = ob.get(id, _marker) if ob is _marker: return default return ob.__of__(self) def _setOb(self, id, object): """Store the named object in the folder. """ htree = self._htree id_list = self.hashId(id) for idx in xrange(len(id_list) - 1): sub_id = id_list[idx] if not htree.has_key(sub_id): # Create a new level htree[sub_id] = OOBTree() if isinstance(sub_id, (int, long)): tree_id = 0 for id in id_list[:idx+1]: tree_id = tree_id + id * MAX_OBJECT_PER_LEVEL else: tree_id = H_SEPARATOR.join(id_list[:idx+1]) # Index newly created level self._tree_list[tree_id] = None htree = htree[sub_id] if len(id_list) == 1 and not htree.has_key(None): self._tree_list[None] = None # set object in subtree ob_id = id_list[-1] if htree.has_key(id): raise KeyError('There is already an item named "%s".' % id) htree[id] = object self._count.change(1) def _delOb(self, id): """Remove the named object from the folder. """ htree = self._htree id_list = self.hashId(id) for sub_id in id_list[0:-1]: htree = htree[sub_id] del htree[id] self._count.change(-1) security.declareProtected(view_management_screens, 'getBatchObjectListing') def getBatchObjectListing(self, REQUEST=None): """Return a structure for a page template to show the list of objects. """ if REQUEST is None: REQUEST = {} pref_rows = int(REQUEST.get('dtpref_rows', 20)) b_start = int(REQUEST.get('b_start', 1)) b_count = int(REQUEST.get('b_count', 1000)) b_end = b_start + b_count - 1 url = self.absolute_url() + '/manage_main' count = self.objectCount() if b_end < count: next_url = url + '?b_start=%d' % (b_start + b_count) else: b_end = count next_url = '' if b_start > 1: prev_url = url + '?b_start=%d' % max(b_start - b_count, 1) else: prev_url = '' formatted = [listtext0 % pref_rows] for optID in islice(self.objectIds(), b_start - 1, b_end): optID = escape(optID) formatted.append(listtext1 % (escape(optID, quote=1), optID)) formatted.append(listtext2) return {'b_start': b_start, 'b_end': b_end, 'prev_batch_url': prev_url, 'next_batch_url': next_url, 'formatted_list': ''.join(formatted)} security.declareProtected(view_management_screens, 'manage_object_workspace') def manage_object_workspace(self, ids=(), REQUEST=None): '''Redirects to the workspace of the first object in the list.''' if ids and REQUEST is not None: REQUEST.RESPONSE.redirect( '%s/%s/manage_workspace' % ( self.absolute_url(), quote(ids[0]))) else: return self.manage_main(self, REQUEST) security.declareProtected(access_contents_information, 'tpValues') def tpValues(self): """Ensures the items don't show up in the left pane. """ return () security.declareProtected(access_contents_information, 'objectCount') def objectCount(self): """Returns the number of items in the folder.""" return self._count() security.declareProtected(access_contents_information, 'has_key') def has_key(self, id): """Indicates whether the folder has an item by ID. """ htree = self._htree id_list = self.hashId(id) for sub_id in id_list[0:-1]: if not isinstance(htree, OOBTree): return 0 if not htree.has_key(sub_id): return 0 htree = htree[sub_id] if not htree.has_key(id): return 0 return 1 # Work around for the performance regression introduced in Zope 2.12.23. # Otherwise, we use superclass' __contains__ implementation, which uses # objectIds, which is inefficient in HBTreeFolder2 to lookup a single key. __contains__ = has_key def _htree_iteritems(self, min=None): # BUG: Due to bad design of HBTreeFolder2, buckets other than the root # one must not contain both buckets & leafs. Otherwise, this method # fails. h = self._htree recurse_stack = [] try: for sub_id in min and self.hashId(min) or ('',): if recurse_stack: i.next() if type(h) is not OOBTree: break id += H_SEPARATOR + sub_id if type(h.itervalues().next()) is not OOBTree: sub_id = id else: id = sub_id i = h.iteritems(sub_id) recurse_stack.append(i) h = h[sub_id] except (KeyError, StopIteration): pass while recurse_stack: i = recurse_stack.pop() try: while 1: id, h = i.next() if type(h) is OOBTree: recurse_stack.append(i) i = h.iteritems() else: yield id, h except StopIteration: pass security.declareProtected(access_contents_information, 'treeIds') def treeIds(self, base_id=None): """ Return a list of subtree ids """ tree = self._getTree(base_id=base_id) return [k for k, v in self._htree.items() if isinstance(v, OOBTree)] def _getTree(self, base_id): """ Return the tree wich has the base_id """ htree = self._htree id_list = self.hashId(base_id) for sub_id in id_list: if not isinstance(htree, OOBTree): return None if not htree.has_key(sub_id): raise IndexError, base_id htree = htree[sub_id] return htree def _getTreeIdList(self, htree=None): """ recursively build a list of btree ids """ if htree is None: htree = self._htree btree_list = [] else: btree_list = [] for obj_id in htree.keys(): obj = htree[obj_id] if isinstance(obj, OOBTree): btree_list.extend(["%s-%s"%(obj_id, x) for x in self._getTreeIdList(htree=obj)]) btree_list.append(obj_id) return btree_list security.declareProtected(access_contents_information, 'getTreeIdList') def getTreeIdList(self, htree=None): """ Return list of all tree ids """ if self._tree_list is None or len(self._tree_list.keys()) == 0: tree_list = self._getTreeIdList(htree=htree) self._tree_list = PersistentMapping() for tree in tree_list: self._tree_list[tree] = None return sorted(self._tree_list.keys()) def _checkObjectId(self, ids): """ test id is not in btree id list """ base_id, obj_id = ids if base_id is not None: obj_id = "%s%s%s" %(base_id, H_SEPARATOR, obj_id) return not self._tree_list.has_key(obj_id) security.declareProtected(access_contents_information, 'objectValues') def objectValues(self, base_id=_marker): return HBTreeObjectValues(self, base_id) security.declareProtected(access_contents_information, 'objectIds') def objectIds(self, base_id=_marker): return HBTreeObjectIds(self, base_id) security.declareProtected(access_contents_information, 'objectItems') def objectItems(self, base_id=_marker): # Returns a list of (id, subobject) tuples of the current object. # If 'spec' is specified, returns only objects whose meta_type match # 'spec' return HBTreeObjectItems(self, base_id) # superValues() looks for the _objects attribute, but the implementation # would be inefficient, so superValues() support is disabled. _objects = () security.declareProtected(access_contents_information, 'objectIds_d') def objectIds_d(self, t=None): return dict.fromkeys(self.objectIds(t), 1) def _checkId(self, id, allow_dup=0): if not allow_dup and self.has_key(id): raise BadRequestException, ('The id "%s" is invalid--' 'it is already in use.' % id) def _setObject(self, id, object, roles=None, user=None, set_owner=1): v=self._checkId(id) if v is not None: id=v # If an object by the given id already exists, remove it. if self.has_key(id): self._delObject(id) self._setOb(id, object) object = self._getOb(id) if set_owner: object.manage_fixupOwnershipAfterAdd() # Try to give user the local role "Owner", but only if # no local roles have been set on the object yet. if hasattr(object, '__ac_local_roles__'): if object.__ac_local_roles__ is None: user=getSecurityManager().getUser() if user is not None: userid=user.getId() if userid is not None: object.manage_setLocalRoles(userid, ['Owner']) object.manage_afterAdd(object, self) return id def _delObject(self, id, dp=1): object = self._getOb(id) try: object.manage_beforeDelete(object, self) except BeforeDeleteException, ob: raise except ConflictError: raise
class DuplicatesCriteriaManager(Implicit): """ An object that contains : _criteria {bib_type : [criteria_name]}: contains all possible criteria (meta-data) for each bibliography type duplicates_criteria {bib_type : [criteria] } : contains criteria to be checked for duplication while importing for each bibliography type _nonmeta_criteria = ( 'reference_type', ) : contains criteria not comprised in meta_data _criteria_names = {'publication_authors' : 'authors'} : contains mappings for meta_data that need a special treatement like 'authors' _ignored_criteria = (critrias) : contains the names of same meta_data like 'id' or 'allowDiscussion' to be filtered IMPORTANT : only 'authors','reference_type', 'publication_year', 'title' are fully functional for now """ _nonmeta_criteria = ( 'reference_type', ) _default_duplicates_criteria = ( #FIXME - we may need a default value for these 'authors', 'reference_type', 'publication_year', 'title',) _criteria_names = {'publication_authors' : 'authors'} _ignored_criteria = ('allowDiscussion','id', ) _www = os.path.join(os.path.dirname(__file__), 'www') security = ClassSecurityInfo() manage_options = ( { 'label' : 'Criteria', 'action' : 'manageImportCriteria', }, ) security.declareProtected(ManagePortal, 'manageImportCriteria') manageImportCriteria = PageTemplateFile('manage_criteria', _www) def __init__(self): self._criteria = PersistentMapping() self.duplicates_criteria = PersistentMapping() self.criteriaUpdated = False def allCriteria(self, bib_type=None): # migrate CMFBAT v0.8 duplicates engine, mending a linguistic fault-pas if shasattr(self, '_criterias'): print 'CMFBibliographyAT: performing duplicates engine property update - v0.8 -> v0.9 (allCriteria of %s)' % '/'.join(self.getId()) self._criteria = PersistentMapping() self.initCriteria() try: delattr(self, '_criterias') except: pass # this should have been performed by __init__ but after some product migrations we might want to check it here again if not shasattr(self, '_criteria'): self._criteria = PersistentMapping() if not shasattr(self, 'duplicates_criteria'): self.duplicates_criteria = PersistentMapping() # first call? initialize self._criteria (available duplicates criteria per reference type) if not self._criteria: self.initCriteria() # always init criteria, during development, schema changes, etc. self.initCriteria() if bib_type: try: self._criteria[bib_type] except KeyError: return False return self._criteria[bib_type] else: critKeys = self._criteria.keys() critKeys.sort() return [(key, self._criteria[key]) for key in critKeys] # fixing a linguistic fault-pas allcriterias = allCriteria def sortCriteriaByTitles(self, criteria, i18n_domain='cmfbibliographyat'): """ take a list of criteria tuples (as returned by allCriteria) and sort it according to their i18n names. used for nice display on screen """ # sorting reference types by their i18n titles ref_types_i18n = [ (self.translate(domain='plone', msgid=criterion[0], default=criterion[0]), criterion[0]) for criterion in criteria ] ref_types_i18n.sort() ref_types = [ t[1] for t in ref_types_i18n ] # turning criteria into a dictionary criteria_dict = {} for ref_type in ref_types: criteria_dict[ref_type] = [ t[1] for t in criteria if t[0] == ref_type ][0] # sorting fields for each ref_type, standard fields always preceed non-standard fields standard_fields = ['authors', 'publication_year', 'title', 'reference_type',] for ref_type in ref_types: nonstandard_fields_i18n = [ (self.translate(domain=i18n_domain, msgid='label_%s' % field, default=field), field) for field in criteria_dict[ref_type] if field not in standard_fields ] nonstandard_fields_i18n.sort() nonstandard_fields = [ f[1] for f in nonstandard_fields_i18n ] criteria_dict[ref_type] = standard_fields + nonstandard_fields return [ (t, tuple(criteria_dict[t])) for t in ref_types ] def initCriteria(self): """ initialize the dictionary of import criteria for each bibliography type """ # this is a migration 0.8 -> 0.9 fix: if not shasattr(self, '_criteria'): self._criteria = PersistentMapping() bib_tool = getToolByName(self, 'portal_bibliography') has = self._criteria_names.has_key for bib_type in bib_tool.getBibliographyContentTypes(): bibname = bib_type['name'] self._criteria[bibname] = [criteria for criteria in self._nonmeta_criteria] #adds all meta_data as criteria for each bibliography type for field in bib_type['schema'].fields(): field_name = field.getName() if field_name in self._ignored_criteria: continue if not shasattr(field, 'is_duplicates_criterion'): continue if not field.is_duplicates_criterion: continue if has(field_name): self._criteria[bibname].append(self._criteria_names[field_name]) else : self._criteria[bibname].append(field_name) self._criteria[bibname].sort() self._criteria[bibname] = tuple(self._criteria[bibname]) # fixing a linguistic fault-pas initCriterias = initCriteria def manage_changeCriteria(self, REQUEST): """Changes all criteria for a bibliography type given, called by management screen """ reference_type = REQUEST.get('bibtype') has = REQUEST.has_key if reference_type != 'all': reference_types = [reference_type] else: bib_tool = getToolByName(self, 'portal_bibliography') reference_types = bib_tool.getReferenceTypes() for reference_type in reference_types: criteria = [] for criterion in self._criteria[reference_type]: # the form bibtype_criterion is not useful for now, but it was set up # in case we want to use one submit input for all the bibliography types if has("%s_%s" % (reference_type, criterion)): criteria.append(criterion) try: self.setCriteriaForType(reference_type, criteria) except: return MessageDialog( title ='Warning!', message='Your changes have not been saved', action ='manageImportCriteria') return MessageDialog( title ='Success!', message='Your changes have been saved', action ='manageImportCriteria' ) # fixing a linguistic fault-pas manage_changeCriterias = manage_changeCriteria def setCriteriaForType(self, bib_type, criteria): """update criteria for a bibliography type given""" self.duplicates_criteria[bib_type] = PersistentList(criteria) #FIXME may need to check if any change has been done self.criteriaUpdated = True return True # fixing a linguistic fault-pas setCriteriasForType = setCriteriaForType def setCriteria(self, duplicates_criteria): """update criteria for all bibliography types""" if not duplicates_criteria: duplicates_criteria = {} bib_tool = getToolByName(self, 'portal_bibliography') for key in bib_tool.getReferenceTypes(): duplicates_criteria[key] = [] self.duplicates_criteria = PersistentMapping(duplicates_criteria) self.criteriaUpdated = True # fixing a linguistic fault-pas setCriterias = setCriteria def isCriterionSelected(self, bib_type, criterion): return (criterion in self.getSelectedCriteria(bib_type)) and True or False def isNonMetaCriterion(self, criterion): return criterion in self._nonmeta_criteria # fixing a linguistic fault-pas isCriteriaSelected = isCriterionSelected def getSelectedCriteria(self, bib_type = None): # migrate CMFBAT v0.8 duplicates engine, mending a linguistic fault-pas if shasattr(self, 'imp_criterias'): print 'CMFBibliographyAT: performing duplicates engine property update - v0.8 -> v0.9 (getSelectedCriteria of %s)' % '/'.join(self.getId()) self.duplicates_criteria = PersistentMapping() self.duplicates_criteria = copy.deepcopy(self.imp_criterias) self.criteriaUpdated = self.criteriasUpdated try: delattr(self, 'imp_criterias') except: pass try: delattr(self, 'criteriasUpdated') except: pass # first call? initialize self.duplicates_criteria bib_tool = getToolByName(self, 'portal_bibliography') if not shasattr(self, 'duplicates_criteria') or not self.duplicates_criteria: if self.getId() == bib_tool.getId(): for reference_type in bib_tool.getReferenceTypes(): self.duplicates_criteria[reference_type] = PersistentList(self._default_duplicates_criteria) self.criteriaUpdated = True else: self.duplicates_criteria = bib_tool.getSelectedCriteria() self.criteriaUpdated = True if not shasattr(self, '_criteria') or not self._criteria: self.initCriteria() # make sure, our selected criteria are in sync with available criteria duplicates_criteria = {} for key in self._criteria.keys(): duplicates_criteria[key] = [ criterion for criterion in self._criteria[key] if self.duplicates_criteria.has_key(key) and (criterion in self.duplicates_criteria[key]) ] if bib_type: try: duplicates_criteria[bib_type] except KeyError: return False return duplicates_criteria[bib_type] else: return duplicates_criteria # fixing a linguistic fault-pas getSelectedCriterias = getSelectedCriteria
class UserFolder(BasicUserFolder): """Standard UserFolder object A UserFolder holds User objects which contain information about users including name, password domain, and roles. UserFolders function chiefly to control access by authenticating users and binding them to a collection of roles.""" meta_type = 'User Folder' id = 'acl_users' title = 'User Folder' def __init__(self): self.data = PersistentMapping() def getUserNames(self): """Return a list of usernames""" names = self.data.keys() return sorted(names) def getUsers(self): """Return a list of user objects""" data = self.data names = data.keys() return [data[n] for n in sorted(names)] def getUser(self, name): """Return the named user object or None""" return self.data.get(name, None) def hasUsers(self): """ This is not a formal API method: it is used only to provide a way for the quickstart page to determine if the default user folder contains any users to provide instructions on how to add a user for newbies. Using getUserNames or getUsers would have posed a denial of service risk.""" return not not len(self.data) def _doAddUser(self, name, password, roles, domains, **kw): """Create a new user Note that an existing user of this name is simply overwritten.""" if password is not None and self.encrypt_passwords and \ not self._isPasswordEncrypted(password): password = self._encryptPassword(password) self.data[name] = User(name, password, roles, domains) return self.data[name] def _doChangeUser(self, name, password, roles, domains, **kw): user = self.data[name] if password is not None: if (self.encrypt_passwords and not self._isPasswordEncrypted(password)): password = self._encryptPassword(password) user.__ = password user.roles = roles user.domains = domains def _doDelUsers(self, names): for name in names: del self.data[name]
class HBTreeFolder2Base(Persistent): """Base for BTree-based folders. """ security = ClassSecurityInfo() manage_options = (({ 'label': 'Contents', 'action': 'manage_main', }, ) + Folder.manage_options[1:]) security.declareProtected(view_management_screens, 'manage_main') manage_main = DTMLFile('contents', globals()) _htree = None # OOBTree: { id -> object } _count = None # A BTrees.Length _v_nextid = 0 # The integer component of the next generated ID title = '' _tree_list = None def __init__(self, id=None): if id is not None: self.id = id self._initBTrees() def _initBTrees(self): self._htree = OOBTree() self._count = Length() self._tree_list = PersistentMapping() def _populateFromFolder(self, source): """Fill this folder with the contents of another folder. """ for name in source.objectIds(): value = source._getOb(name, None) if value is not None: self._setOb(name, aq_base(value)) security.declareProtected(view_management_screens, 'manage_fixCount') def manage_fixCount(self): """Calls self._fixCount() and reports the result as text. """ old, new = self._fixCount() path = '/'.join(self.getPhysicalPath()) if old == new: return "No count mismatch detected in HBTreeFolder2 at %s." % path else: return ("Fixed count mismatch in HBTreeFolder2 at %s. " "Count was %d; corrected to %d" % (path, old, new)) def _fixCount(self): """Checks if the value of self._count disagrees with len(self.objectIds()). If so, corrects self._count. Returns the old and new count values. If old==new, no correction was performed. """ old = self._count() new = len(self.objectIds()) if old != new: self._count.set(new) return old, new security.declareProtected(view_management_screens, 'manage_cleanup') def manage_cleanup(self): """Calls self._cleanup() and reports the result as text. """ v = self._cleanup() path = '/'.join(self.getPhysicalPath()) if v: return "No damage detected in HBTreeFolder2 at %s." % path else: return ("Fixed HBTreeFolder2 at %s. " "See the log for more details." % path) def _cleanup(self): """Cleans up errors in the BTrees. Certain ZODB bugs have caused BTrees to become slightly insane. Fortunately, there is a way to clean up damaged BTrees that always seems to work: make a new BTree containing the items() of the old one. Returns 1 if no damage was detected, or 0 if damage was detected and fixed. """ def hCheck(htree): """ Recursively check the btree """ check(htree) for key in htree.keys(): if not htree.has_key(key): raise AssertionError("Missing value for key: %s" % repr(key)) else: ob = htree[key] if isinstance(ob, OOBTree): hCheck(ob) return 1 from BTrees.check import check path = '/'.join(self.getPhysicalPath()) try: return hCheck(self._htree) except AssertionError: LOG('HBTreeFolder2', WARNING, 'Detected damage to %s. Fixing now.' % path, error=sys.exc_info()) try: self._htree = OOBTree(self._htree) # XXX hFix needed except: LOG('HBTreeFolder2', ERROR, 'Failed to fix %s.' % path, error=sys.exc_info()) raise else: LOG('HBTreeFolder2', INFO, 'Fixed %s.' % path) return 0 def hashId(self, id): """Return a tuple of ids """ # XXX: why tolerate non-string ids ? id_list = str(id).split( H_SEPARATOR) # We use '-' as the separator by default if len(id_list) > 1: return tuple(id_list) else: return [ id, ] # try: # We then try int hashing # id_int = int(id) # except ValueError: # return id_list # result = [] # while id_int: # result.append(id_int % MAX_OBJECT_PER_LEVEL) # id_int = id_int / MAX_OBJECT_PER_LEVEL # result.reverse() # return tuple(result) def _getOb(self, id, default=_marker): """ Return the named object from the folder. """ htree = self._htree ob = htree id_list = self.hashId(id) for sub_id in id_list[0:-1]: if default is _marker: ob = ob[sub_id] else: ob = ob.get(sub_id, _marker) if ob is _marker: return default if default is _marker: ob = ob[id] else: ob = ob.get(id, _marker) if ob is _marker: return default return ob.__of__(self) def _setOb(self, id, object): """Store the named object in the folder. """ htree = self._htree id_list = self.hashId(id) for idx in xrange(len(id_list) - 1): sub_id = id_list[idx] if not htree.has_key(sub_id): # Create a new level htree[sub_id] = OOBTree() if isinstance(sub_id, (int, long)): tree_id = 0 for id in id_list[:idx + 1]: tree_id = tree_id + id * MAX_OBJECT_PER_LEVEL else: tree_id = H_SEPARATOR.join(id_list[:idx + 1]) # Index newly created level self._tree_list[tree_id] = None htree = htree[sub_id] if len(id_list) == 1 and not htree.has_key(None): self._tree_list[None] = None # set object in subtree ob_id = id_list[-1] if htree.has_key(id): raise KeyError('There is already an item named "%s".' % id) htree[id] = object self._count.change(1) def _delOb(self, id): """Remove the named object from the folder. """ htree = self._htree id_list = self.hashId(id) for sub_id in id_list[0:-1]: htree = htree[sub_id] del htree[id] self._count.change(-1) security.declareProtected(view_management_screens, 'getBatchObjectListing') def getBatchObjectListing(self, REQUEST=None): """Return a structure for a page template to show the list of objects. """ if REQUEST is None: REQUEST = {} pref_rows = int(REQUEST.get('dtpref_rows', 20)) b_start = int(REQUEST.get('b_start', 1)) b_count = int(REQUEST.get('b_count', 1000)) b_end = b_start + b_count - 1 url = self.absolute_url() + '/manage_main' count = self.objectCount() if b_end < count: next_url = url + '?b_start=%d' % (b_start + b_count) else: b_end = count next_url = '' if b_start > 1: prev_url = url + '?b_start=%d' % max(b_start - b_count, 1) else: prev_url = '' formatted = [listtext0 % pref_rows] for optID in islice(self.objectIds(), b_start - 1, b_end): optID = escape(optID) formatted.append(listtext1 % (escape(optID, quote=1), optID)) formatted.append(listtext2) return { 'b_start': b_start, 'b_end': b_end, 'prev_batch_url': prev_url, 'next_batch_url': next_url, 'formatted_list': ''.join(formatted) } security.declareProtected(view_management_screens, 'manage_object_workspace') def manage_object_workspace(self, ids=(), REQUEST=None): '''Redirects to the workspace of the first object in the list.''' if ids and REQUEST is not None: REQUEST.RESPONSE.redirect('%s/%s/manage_workspace' % (self.absolute_url(), quote(ids[0]))) else: return self.manage_main(self, REQUEST) security.declareProtected(access_contents_information, 'tpValues') def tpValues(self): """Ensures the items don't show up in the left pane. """ return () security.declareProtected(access_contents_information, 'objectCount') def objectCount(self): """Returns the number of items in the folder.""" return self._count() security.declareProtected(access_contents_information, 'has_key') def has_key(self, id): """Indicates whether the folder has an item by ID. """ htree = self._htree id_list = self.hashId(id) for sub_id in id_list[0:-1]: if not isinstance(htree, OOBTree): return 0 if not htree.has_key(sub_id): return 0 htree = htree[sub_id] if not htree.has_key(id): return 0 return 1 # Work around for the performance regression introduced in Zope 2.12.23. # Otherwise, we use superclass' __contains__ implementation, which uses # objectIds, which is inefficient in HBTreeFolder2 to lookup a single key. __contains__ = has_key def _htree_iteritems(self, min=None): # BUG: Due to bad design of HBTreeFolder2, buckets other than the root # one must not contain both buckets & leafs. Otherwise, this method # fails. h = self._htree recurse_stack = [] try: for sub_id in min and self.hashId(min) or ('', ): if recurse_stack: i.next() if type(h) is not OOBTree: break id += H_SEPARATOR + sub_id if type(h.itervalues().next()) is not OOBTree: sub_id = id else: id = sub_id i = h.iteritems(sub_id) recurse_stack.append(i) h = h[sub_id] except (KeyError, StopIteration): pass while recurse_stack: i = recurse_stack.pop() try: while 1: id, h = i.next() if type(h) is OOBTree: recurse_stack.append(i) i = h.iteritems() else: yield id, h except StopIteration: pass security.declareProtected(access_contents_information, 'treeIds') def treeIds(self, base_id=None): """ Return a list of subtree ids """ tree = self._getTree(base_id=base_id) return [k for k, v in self._htree.items() if isinstance(v, OOBTree)] def _getTree(self, base_id): """ Return the tree wich has the base_id """ htree = self._htree id_list = self.hashId(base_id) for sub_id in id_list: if not isinstance(htree, OOBTree): return None if not htree.has_key(sub_id): raise IndexError, base_id htree = htree[sub_id] return htree def _getTreeIdList(self, htree=None): """ recursively build a list of btree ids """ if htree is None: htree = self._htree btree_list = [] else: btree_list = [] for obj_id in htree.keys(): obj = htree[obj_id] if isinstance(obj, OOBTree): btree_list.extend([ "%s-%s" % (obj_id, x) for x in self._getTreeIdList(htree=obj) ]) btree_list.append(obj_id) return btree_list security.declareProtected(access_contents_information, 'getTreeIdList') def getTreeIdList(self, htree=None): """ Return list of all tree ids """ if self._tree_list is None or len(self._tree_list.keys()) == 0: tree_list = self._getTreeIdList(htree=htree) self._tree_list = PersistentMapping() for tree in tree_list: self._tree_list[tree] = None return sorted(self._tree_list.keys()) def _checkObjectId(self, ids): """ test id is not in btree id list """ base_id, obj_id = ids if base_id is not None: obj_id = "%s%s%s" % (base_id, H_SEPARATOR, obj_id) return not self._tree_list.has_key(obj_id) security.declareProtected(access_contents_information, 'objectValues') def objectValues(self, base_id=_marker): return HBTreeObjectValues(self, base_id) security.declareProtected(access_contents_information, 'objectIds') def objectIds(self, base_id=_marker): return HBTreeObjectIds(self, base_id) security.declareProtected(access_contents_information, 'objectItems') def objectItems(self, base_id=_marker): # Returns a list of (id, subobject) tuples of the current object. # If 'spec' is specified, returns only objects whose meta_type match # 'spec' return HBTreeObjectItems(self, base_id) # superValues() looks for the _objects attribute, but the implementation # would be inefficient, so superValues() support is disabled. _objects = () security.declareProtected(access_contents_information, 'objectIds_d') def objectIds_d(self, t=None): return dict.fromkeys(self.objectIds(t), 1) def _checkId(self, id, allow_dup=0): if not allow_dup and self.has_key(id): raise BadRequestException, ('The id "%s" is invalid--' 'it is already in use.' % id) def _setObject(self, id, object, roles=None, user=None, set_owner=1): v = self._checkId(id) if v is not None: id = v # If an object by the given id already exists, remove it. if self.has_key(id): self._delObject(id) self._setOb(id, object) object = self._getOb(id) if set_owner: object.manage_fixupOwnershipAfterAdd() # Try to give user the local role "Owner", but only if # no local roles have been set on the object yet. if hasattr(object, '__ac_local_roles__'): if object.__ac_local_roles__ is None: user = getSecurityManager().getUser() if user is not None: userid = user.getId() if userid is not None: object.manage_setLocalRoles(userid, ['Owner']) object.manage_afterAdd(object, self) return id def _delObject(self, id, dp=1): object = self._getOb(id) try: object.manage_beforeDelete(object, self) except BeforeDeleteException, ob: raise except ConflictError: raise