class WordProperty(bsdWrappers.BaseWrapper): """ Wrapper for modifying word Properties. See wordType.WordType class for interface to manipulate metadata on message. """ __primaryTable__ = bsdWrappers.RegisterTable(WORD_PROPERTIES_TABLE) @classmethod def Create(cls, propertyName, wordTypeID, languageID, propertyDescription=None): """ Create new property object. """ if not propertyName: raise AuthoringValidationError( 'PropertyName (%s) must be a valid string.' % propertyName) dbLanguageID = GetNumericLanguageIDFromLanguageID(languageID) if dbLanguageID == None: raise AuthoringValidationError('Didnt find language (%s).' % languageID) from wordType import WordType if WordType.Get(wordTypeID) == None: raise AuthoringValidationError('Didnt find type (%s).' % wordTypeID) duplicateProps = cls.GetWithFilter(propertyName=propertyName, wordTypeID=wordTypeID, numericLanguageID=dbLanguageID, _getDeleted=True) if duplicateProps and len(duplicateProps): raise AuthoringValidationError( 'Can not insert duplicate properties. propertyName,wordTypeID,languageID : (%s,%s,%s) ' % (propertyName, wordTypeID, languageID)) return bsdWrappers.BaseWrapper._Create( cls, propertyName=propertyName, wordTypeID=wordTypeID, numericLanguageID=dbLanguageID, propertyDescription=propertyDescription) def Copy(self, keyID=None, keyID2=None, keyID3=None, **kw): """ Copy method is not implemented. """ raise NotImplementedError def _DeleteChildren(self): """ Checks for dependent objects. """ wordMeta = WordMetaData.GetWithFilter( wordPropertyID=self.wordPropertyID) if wordMeta and len(wordMeta): raise AuthoringValidationError( 'Property (%s) can not be deleted, because it still has (%s) metadata entry(s).' % (self.wordPropertyID, str(len(wordMeta)))) return True
class MessageText(bsdWrappers.BaseWrapper): """ A wrapper object for message strings, where a message can have multiple strings (one per supported language). """ __primaryTable__ = bsdWrappers.RegisterTable(localizationBSDConst.MESSAGE_TEXTS_TABLE) @classmethod def Create(cls, messageID, languageID = LOCALE_SHORT_ENGLISH, text = '', sourceDataID = None): primaryTable = bsdWrappers.GetTable(MessageText.__primaryTable__) dbLocaleID = GetNumericLanguageIDFromLanguageID(languageID) if dbLocaleID is None: raise RuntimeError('LanguageID %s could not be mapped to a locale ID.' % languageID) import message if not message.Message.Get(messageID): raise RuntimeError('Message ID %d does not exist! Message text not created for language %s' % (messageID, languageID)) statusID = None if languageID != LOCALE_SHORT_ENGLISH: englishText = MessageText.GetMessageTextByMessageID(messageID, LOCALE_SHORT_ENGLISH) if englishText: if sourceDataID is None: sourceDataID = englishText.dataID if MessageText._ValidateChangeToReview(None, sourceDataID, englishText.dataID): statusID = localizationBSDConst.TEXT_STATUS_REVIEW return bsdWrappers.BaseWrapper._Create(cls, messageID, dbLocaleID, text=text, sourceDataID=sourceDataID, statusID=statusID) def SetTextAndSourceDataID(self, text, sourceDataID): """ Set text and sourceDataID field the same time, while setting appropriate flag, like: Review. """ bsdWrappers.BaseWrapper.__setattr__(self, 'sourceDataID', sourceDataID) bsdWrappers.BaseWrapper.__setattr__(self, 'text', text) bsdWrappers.BaseWrapper.__setattr__(self, 'changed', False) englishText = MessageText.GetMessageTextByMessageID(self.messageID, LOCALE_SHORT_ENGLISH) if englishText: self._TrySettingToReview() def MarkTranslationAsCurrent(self): """ Set this translation as translated vs current sourceDataID, by associating this translation to current English row (current in memory!). """ if self.numericLanguageID != GetNumericLanguageIDFromLanguageID(LOCALE_SHORT_ENGLISH): englishText = MessageText.GetMessageTextByMessageID(self.messageID, LOCALE_SHORT_ENGLISH) if englishText: self.sourceDataID = englishText.dataID else: raise AuthoringValidationError("Cannot call SetAsTranslated() method on '%s' text entry with messageID '%s'. The method must be called on translations." % (LOCALE_SHORT_ENGLISH, self.messageID)) def __setattr__(self, key, value): """ Overwritting setattr in order to catch all edits to the row from external code. NOTE: other code in this object needs to be aware of this setattr when changing value of object variables. In other words, local code most likely shouldnt call this setattr. """ if key == localizationBSDConst.COLUMN_TEXT and value != self.text: if self.numericLanguageID != GetNumericLanguageIDFromLanguageID(LOCALE_SHORT_ENGLISH): englishText = MessageText.GetMessageTextByMessageID(self.messageID, LOCALE_SHORT_ENGLISH) if englishText: sourceDataID = englishText.dataID bsdWrappers.BaseWrapper.__setattr__(self, 'sourceDataID', sourceDataID) self._TrySettingToReview() bsdWrappers.BaseWrapper.__setattr__(self, key, value) def _TrySettingToReview(self): """ Perform required validation and if passed, set the status of this text row to Review. """ englishText = MessageText.GetMessageTextByMessageID(self.messageID, LOCALE_SHORT_ENGLISH) if englishText: if MessageText._ValidateChangeToReview(self.statusID, self.sourceDataID, englishText.dataID): self.statusID = localizationBSDConst.TEXT_STATUS_REVIEW @staticmethod def _ValidateChangeToReview(currentStatusID, currentSourceID, englishDataID): """ Validate if ok to change to Review status. Can only change to Review if the current status is not explicitly specified or set to 'Defect'. """ if englishDataID == currentSourceID and (currentStatusID is None or currentStatusID != localizationBSDConst.TEXT_STATUS_DEFECT): return True return False @classmethod def Get(cls, messageID, languageID = LOCALE_SHORT_ENGLISH, _getDeleted = False): dbLocaleID = GetNumericLanguageIDFromLanguageID(languageID) return bsdWrappers._TryGetObjByKey(cls, messageID, dbLocaleID, _getDeleted=_getDeleted) @staticmethod def GetMessageTextsByMessageID(messageID): """ Returns text strings for all languages associated with this messageID. parameters: messageID - requested message ID """ primaryTable = bsdWrappers.GetTable(MessageText.__primaryTable__) return primaryTable.GetRows(_wrapperClass=MessageText, messageID=messageID, _getDeleted=False) @staticmethod def GetMessageTextByMessageID(messageID, languageID): """ Returns text strings for language associated with this messageID. parameters: messageID - requested message ID """ primaryTable = bsdWrappers.GetTable(MessageText.__primaryTable__) dbLocaleID = GetNumericLanguageIDFromLanguageID(languageID) return primaryTable.GetRowByKey(_wrapperClass=MessageText, keyId1=messageID, keyId2=dbLocaleID, _getDeleted=False)
class Message(bsdWrappers.BaseWrapper): """ A wrapper object for messages, where a message is composed of a label identifier, and one or more strings (one string per supported language). This class also handles operations on Metadata. """ __primaryTable__ = bsdWrappers.RegisterTable( localizationBSDConst.MESSAGES_TABLE) _messageTextTable = None _propertyTable = None _bsdSvc = None _APPEND_NEW = '(new)' def GetLabelPath(self, projectID=None): """ Generate path to this label as it'll appear in export/pickle file(s). NOTE: This is primarily used in UI/Content Browser to show the user how the paths would look like. This function doesnt actually care whether this message was tagged with the projectID or not. parameters: projectID - optional parameter. When specified will use Project's working directory when rendering final path string returns: a path string, of the form: u'/UI/Generic/Buttons/Cancel' """ from messageGroup import MessageGroup labelPath = '' if self.label is None else self.label if self.groupID is not None: folderPath = MessageGroup.Get( self.groupID).GetFolderPath(projectID=projectID) if folderPath and labelPath: labelPath = '/'.join((folderPath, labelPath)) elif folderPath: labelPath = folderPath return labelPath def GetOpenedBy(self): """ Aggregates bsd state information across all components of a message (label, texts for each language, and metadata for each language), and returns it in human-readable form. """ openedBy = [self._GetOpenedBy(self)] for messageText in locMessageText.MessageText.GetWithFilter( messageID=self.messageID): openedBy += [self._GetOpenedBy(messageText)] for metadata in locWordMetaData.WordMetaData.GetWithFilter( messageID=self.messageID): openedBy += [self._GetOpenedBy(metadata)] openedBy = '\n'.join([text for text in openedBy if text]) return openedBy def _GetOpenedBy(self, bsdWrapper): """ Get a nicely formatted message describing who is editing any rows that make up this wrapper. Because a message is composed of multiple wrappers, we create a fancier string including the wrapper name. """ openedBy = '' openedByUserIDs = bsdWrapper.GetOpenedByUserIDs() bsdState = bsdWrapper.GetState() if bsdState & dbutil.BSDSTATE_OPENFORADD != 0: openedBy += bsdWrapper.__class__.__name__ + ' added by ' elif bsdState & dbutil.BSDSTATE_OPENFORDELETE != 0: openedBy += bsdWrapper.__class__.__name__ + ' marked for delete by ' elif bsdState & dbutil.BSDSTATE_OPENFOREDIT != 0: openedBy += bsdWrapper.__class__.__name__ + ' opened for edit by ' elif openedByUserIDs: openedBy += bsdWrapper.__class__.__name__ + ' opened in unknown state by ' if not openedBy: return openedBy openedBy += ', '.join((bsdWrapper.cache.Row(const.cacheStaticUsers, userID).userName for userID in openedByUserIDs)) return openedBy def Copy(self, groupID, newLabel=None): """ Copy the current message into groupID with the new Label. All metadata will be copied as well. parameters: groupID - destination group. The message label needs to be unique in the destination group. The group's wordType also needs to be the same as the message wordTypeID. newLabel - if set, the message's label will be changed to this new label pre-req: This method must not be ran within Transaction exceptions: Will throw various exceptions on unsuccessful Copy. returns: New row of the copied message """ Message._ErrorIfInTransaction( 'Message Copy will not run within Transaction.') copyLabel = newLabel or self.label if not Message.CheckIfLabelUnique(copyLabel, groupID): raise AuthoringValidationError('Label (%s) is not unique.' % copyLabel) messageCopy = Message.Create( copyLabel, groupID, text=self.GetTextEntry(LOCALE_SHORT_ENGLISH).text, context=self.context) englishText = messageCopy.GetTextEntry(LOCALE_SHORT_ENGLISH) originalEnglishText = self.GetTextEntry(LOCALE_SHORT_ENGLISH) originalTexts = self.messageTextTable.GetRows(messageID=self.messageID, _getDeleted=False) for aText in originalTexts: if aText.numericLanguageID != localizationBSDConst.LOCALE_ID_ENGLISH: newSourceDataID = englishText.dataID if originalEnglishText.dataID == aText.sourceDataID else None self.messageTextTable.AddRow(messageCopy.messageID, aText.numericLanguageID, sourceDataID=newSourceDataID, text=aText.text, statusID=aText.statusID) locWordMetaData.WordMetaData._CopyAllMetaDataToNewMessage( sourceMessageID=self.messageID, destMessageID=messageCopy.messageID, destinationWordTypeID=messageCopy.wordTypeID) return messageCopy def ResetWordType(self): """ Change the wordTypeID of this message to None. This operation will remove all metadata belonging to this message. NOTE: This code is bsd transaction friendly. """ if self.wordTypeID is not None: with bsd.BsdTransaction(): self._DeleteMetaData() bsdWrappers.BaseWrapper.__setattr__(self, 'wordTypeID', None) def GetAllMetaDataEntries(self, languageID): """ Return all metadata entries for this language returns: list of metadata row objects """ metaDataForLanguage = [] dbLanguageID = GetNumericLanguageIDFromLanguageID(languageID) propertyTable = self.__class__._propertyTable allMetaData = locWordMetaData.WordMetaData.GetWithFilter( messageID=self.messageID) for metaEntry in allMetaData: propertyRow = propertyTable.GetRowByKey(metaEntry.wordPropertyID) if propertyRow and propertyRow.numericLanguageID == dbLanguageID: metaDataForLanguage.append(metaEntry) return metaDataForLanguage def GetMetaDataEntry(self, wordPropertyID): """ Return metadata for the specified property. returns: metadata row object if found, None if property or metadata isnt found """ metaDataRows = locWordMetaData.WordMetaData.GetWithFilter( messageID=self.messageID, wordPropertyID=wordPropertyID) if metaDataRows and len(metaDataRows): return metaDataRows[0] else: return None def GetMetaDataEntryByName(self, propertyName, languageID): """ Return metadata for the specified property / language. returns: metadata row object if found, None if property or metadata isnt found """ propertyRows = self.__class__._propertyTable.GetRows( wordTypeID=self.wordTypeID, propertyName=propertyName, numericLanguageID=GetNumericLanguageIDFromLanguageID(languageID), _getDeleted=False) if propertyRows and len(propertyRows) != 1: return None else: metaDataRows = locWordMetaData.WordMetaData.GetWithFilter( messageID=self.messageID, wordPropertyID=propertyRows[0].wordPropertyID) if metaDataRows and len(metaDataRows): return metaDataRows[0] return None def AddMetaDataEntry(self, wordPropertyID, metaDataValue, transactionBundle=None): """ Add new metadata entry to this message. Will throw an error if the metadata entry already exists. NOTE: This code is bsd transaction friendly. parameters: wordPropertyID - unique identifier for the property metaDataValue - metadata string transactionBundle - cache containing entries of messages to be added within transaction. It is required for validations within transactions. See CreateMessageDataBundle() """ if self.wordTypeID == None: raise AuthoringValidationError( 'Before adding metadata, the wordType needs to be set on this messageID (%s).' % str(self.messageID)) with bsd.BsdTransaction(): locWordMetaData.WordMetaData.TransactionAwareCreate( wordPropertyID, self.messageID, metaDataValue, transactionBundle=transactionBundle) def AddMetaDataEntryByName(self, propertyName, languageID, metaDataValue, transactionBundle=None): """ Add new metadata entry to this message. Will throw an error if the metadata entry already exists. NOTE: This code is bsd transaction friendly. parameters: propertyName - name of the property languageID - id for the language. For example "en-us" metaDataValue - metadata string transactionBundle - cache containing entries of messages to be added within transaction. It is required for validations within transactions. See CreateMessageDataBundle() """ if self.wordTypeID == None: raise AuthoringValidationError( 'Before adding metadata, the wordType needs to be set on this messageID (%s).' % str(self.messageID)) typeRow = locWordType.WordType.Get(self.wordTypeID) if typeRow == None: raise AuthoringValidationError( 'WordTypeID (%s), of this message, does not exist.' % self.wordTypeID) typeName = typeRow.typeName with bsd.BsdTransaction(): locWordMetaData.WordMetaData.TransactionAwareCreateFromPropertyName( typeName, propertyName, languageID, self.messageID, metaDataValue, transactionBundle) def GetTextEntry(self, languageID): """ Returns text row for the language specified returns: text row or None if none found """ return locMessageText.MessageText.Get(self.messageID, languageID) def AddTextEntry(self, languageID, text): """ Add text entry for the language, if one doesnt exist NOTE: This code is bsd transaction friendly. """ textRow = locMessageText.MessageText.Get(self.messageID, languageID) if textRow == None: locMessageText.MessageText.Create(self.messageID, languageID, text=text) else: raise AuthoringValidationError( 'Can not add duplicate text entry. messageID,languageID : (%s, %s)' % (str(self.messageID), languageID)) def GetState(self): """ Aggregates bsd state information across all components of a message (label, texts for each language, and metadata for each language) """ bsdState = super(Message, self).GetState() for messageText in locMessageText.MessageText.GetWithFilter( messageID=self.messageID): bsdState |= messageText.GetState() for metadata in locWordMetaData.WordMetaData.GetWithFilter( messageID=self.messageID): bsdState |= metadata.GetState() return bsdState def GetWordCount(self, languageID='en-us', includeMetadata=True): """ Does a naive word count of the message text in the given language (splitting on spaces). By default, includes the number of words in all metadata entries, where present. """ textEntry = self.GetTextEntry(languageID) if not textEntry: return 0 count = len([ part for part in re.findall('\\w*', textEntry.text or '') if part ]) if includeMetadata: metadataEntries = self.GetAllMetaDataEntries(languageID) for metadata in metadataEntries: if metadata.metaDataValue: count += len([ part for part in re.findall( '\\w*', metadata.metaDataValue or '') if part ]) return count def __init__(self, row): bsdWrappers.BaseWrapper.__init__(self, row) self.__class__.CheckAndSetCache() self.messageTextTable = self.__class__._messageTextTable def __setattr__(self, key, value): if key == localizationBSDConst.COLUMN_LABEL: if not Message.CheckIfLabelUnique(value, self.groupID): raise AuthoringValidationError( 'Label can not be set to non-unique name (%s).' % str(value)) if key == localizationBSDConst.COLUMN_TYPE_ID: if self.wordTypeID is not None: raise AuthoringValidationError( 'Not allowed to edit wordTypeID on the message that may contain metadata. Use ResetWordType().' ) elif locWordType.WordType.Get(value) is None: raise AuthoringValidationError( 'WordTypeID (%s) does not exist.' % str(value)) bsdWrappers.BaseWrapper.__setattr__(self, key, value) def _DeleteChildren(self): with bsd.BsdTransaction('Deleting message: %s' % self.label): self._DeleteText() self._DeleteMetaData() return True def _DeleteText(self): for messageText in locMessageText.MessageText.GetMessageTextsByMessageID( self.messageID): if not messageText.Delete(): raise AuthoringValidationError( 'Message (%s) wrapper was unable to delete text entry.' % self.messageID) def _DeleteMetaData(self): for metaData in locWordMetaData.WordMetaData.GetWithFilter( messageID=self.messageID): if not metaData.Delete(): raise AuthoringValidationError( 'Message (%s) wrapper was unable to metadata entry.' % self.messageID) @classmethod def Get(cls, messageID): return bsdWrappers._TryGetObjByKey(cls, messageID, _getDeleted=False) @classmethod def CheckAndSetCache(cls): """ Reset cache on the class. """ if cls._messageTextTable is None or cls._propertyTable is None: bsdTableSvc = sm.GetService('bsdTable') cls._messageTextTable = bsdTableSvc.GetTable( localizationBSDConst.MESSAGE_TEXTS_TABLE) cls._propertyTable = bsdTableSvc.GetTable( localizationBSDConst.WORD_PROPERTIES_TABLE) cls._bsdSvc = sm.GetService('BSD') @classmethod def Create(cls, label, groupID=None, text='', context=None): """ Creates a new message label, and a blank English string by default. parameters: label - label of the message. Must be unique groupID - destination group. Must match the wordType of this message text - english text context - description for this message entry pre-req: this MUST NOT be called within nested Transactions. This not what this method for. exceptions: throws various exceptions on failures returns: row of the message that we just added """ cls._ErrorIfInTransaction( 'Message Create will not run within Transaction. Use TransactionAwareCreate.' ) with bsd.BsdTransaction('Creating new message: %s' % label) as bsdTransaction: cls._TransactionAwareCreate(label, groupID, LOCALE_SHORT_ENGLISH, text, context, wordTypeID=None, transactionBundle=None) resultList = bsdTransaction.GetTransactionResult() return cls.Get(resultList[0][1].messageID) @classmethod def TransactionAwareCreate(cls, label, groupID=None, text='', context=None, transactionBundle=None): """ This is the transaction-aware portion of the create code. It is split up from Create() on purpose; in order to allow other Wrappers to include these operations as port of their transaction as well. Purpose: Creates a new message label, and a blank English string by default. parameters: label - label of the message. Must be unique groupID - destination group. Must match the wordType of this message text - english text context - description for this message entry wordTypeID - type of the message transactionBundle - cache containing entries of messages to be added within transaction. It is required for validations within transactions. See CreateMessageDataBundle() pre-req: this IS meant to be called within nested Transactions. Returns: reserved actionID dictionary for the message, that will be added when transaction is done. The return is formated as: {"reservedMessageID": INTEGER} Notice: when things in base fail, this may return None """ cls._ErrorIfNotInTransaction( 'Message TransactionAwareCreate will not run within Transaction. Use Create.' ) with bsd.BsdTransaction('Creating new message: %s' % label): actionIDsResult = cls._TransactionAwareCreate( label, groupID, LOCALE_SHORT_ENGLISH, text, context, wordTypeID=None, transactionBundle=transactionBundle) return actionIDsResult @classmethod def _GetGroupRecord(cls, groupID, transactionBundle=None): """ get the group entry, to resolve issue between actionIDs + bundles and live data """ from messageGroup import MessageGroup if transactionBundle and type(groupID) != int: currentGroup = transactionBundle.get( localizationBSDConst.BUNDLE_GROUP, {}).get(groupID, None) else: currentGroup = MessageGroup.Get(groupID) return currentGroup @classmethod def _GetWordTypeID(cls, groupID, transactionBundle=None): """ Resolving two required pieces of data for message creation. """ wordTypeID = None if groupID is not None: parentGroup = cls._GetGroupRecord( groupID, transactionBundle=transactionBundle) if parentGroup: wordTypeID = parentGroup.wordTypeID return wordTypeID @classmethod def _ValidateCreationOfMessage(cls, label, groupID, wordTypeID, transactionBundle=None): """ validate various things before allowing this entry to be created the function is expected to interrupt this execution if things are wrong """ if not cls.CheckIfLabelUnique( label, groupID, transactionBundle=transactionBundle): raise AuthoringValidationError( 'Label (%s) in groupID (%s) is not unique.' % (label, str(groupID))) if groupID != None: parentGroup = cls._GetGroupRecord( groupID, transactionBundle=transactionBundle) if parentGroup: if wordTypeID != parentGroup.wordTypeID: raise AuthoringValidationError( 'Group type doesnt match message type (%s,%s).' % (wordTypeID, parentGroup.wordTypeID)) else: raise AuthoringValidationError( "Parent group (%s) wasn't found." % str(groupID)) if wordTypeID != None: typeRow = locWordType.WordType.Get(wordTypeID) if typeRow == None: raise AuthoringValidationError("Type (%s) wasn't found." % wordTypeID) return True @classmethod def _TransactionAwareCreate(cls, label, groupID, languageID, text, context, wordTypeID=None, transactionBundle=None): """ Worker function for transaction-aware portion of the create code. parameters: label - label of the message. Must be unique groupID - destination group. Must match the wordType of this message languageID - id for text entry text - text entry context - description for this message entry wordTypeID - type of the message transactionBundle - cache containing entries of messages to be added within transaction. It is required for validations within transactions. See CreateMessageDataBundle() Returns: reserved actionID dictionary for the message, that will be added when transaction is done. The return is formated as: {"reservedMessageID": INTEGER} pre-req: always meant to run in transaction """ inheritedWordTypeID = Message._GetWordTypeID(groupID) if wordTypeID is None: wordTypeID = inheritedWordTypeID Message._ValidateCreationOfMessage(label, groupID, wordTypeID, transactionBundle=transactionBundle) dbLocaleID = GetNumericLanguageIDFromLanguageID(languageID) if dbLocaleID is None: raise AuthoringValidationError('Didnt find language (%s).' % languageID) reservedActionID = bsdWrappers.BaseWrapper._Create( cls, label=label, groupID=groupID, context=context, wordTypeID=wordTypeID) if transactionBundle: tupleActionID = (reservedActionID, 'messageID') transactionBundle[localizationBSDConst. BUNDLE_MESSAGE][tupleActionID] = util.KeyVal({ 'label': label, 'groupID': groupID, 'context': context, 'wordTypeID': wordTypeID }) messageTextTable = bsdWrappers.GetTable( locMessageText.MessageText.__primaryTable__) messageTextTable.AddRow((reservedActionID, 'messageID'), dbLocaleID, text=text) if type(reservedActionID) == int: return {'reservedMessageID': reservedActionID} raise AuthoringValidationError( 'Unexpected error. Possibly incorrect use of transactions. Expected actionID but instead got : %s ' % str(reservedActionID)) @classmethod def _ErrorIfInTransaction(cls, errorMessage): cls.CheckAndSetCache() if cls._bsdSvc.TransactionOngoing(): raise AuthoringValidationError(errorMessage) @classmethod def _ErrorIfNotInTransaction(cls, errorMessage): cls.CheckAndSetCache() if not cls._bsdSvc.TransactionOngoing(): raise AuthoringValidationError(errorMessage) @staticmethod def CheckIfLabelUnique(originalLabel, groupID, transactionBundle=None, _appendWord=None): """ Check against data in DB of label is unique parameters: groupID - parent group transactionBundle - see CreateMessageDataBundle() return: True or False """ isUnique, label = Message._CheckLabelUniqueness( originalLabel, groupID, transactionBundle=transactionBundle, returnUnique=False, _appendWord=_appendWord) return isUnique @staticmethod def GetUniqueLabel(originalLabel, groupID, transactionBundle=None, _appendWord=None): """ Generate unique label against data in DB. parameters: groupID - parent group transactionBundle - see CreateMessageDataBundle() return: unique label """ isUnique, label = Message._CheckLabelUniqueness( originalLabel, groupID, transactionBundle=transactionBundle, returnUnique=True, _appendWord=_appendWord) return label @staticmethod def GetMessageByID(messageID): primaryTable = bsdWrappers.GetTable(Message.__primaryTable__) return primaryTable.GetRowByKey(_wrapperClass=Message, keyId1=messageID, _getDeleted=False) @staticmethod def GetMessagesByGroupID(groupID, projectID=None): """ Returns all messages directly beneath the parent group. If ProjectID is specified then return all messages under this group, tagged for this project. """ return Message.GetWithFilter(groupID=groupID) @staticmethod def _CheckLabelUniqueness(originalLabel, groupID, transactionBundle=None, returnUnique=False, _appendWord=None): """ Check or get unique label (if returnUnique is set to True) return: tuple: (if label is unique, unique label string) """ isOriginalLabelUnique = True if originalLabel is None: return (isOriginalLabelUnique, None) primaryTable = bsdWrappers.GetTable(Message.__primaryTable__) newLabel = originalLabel while True: labels = primaryTable.GetRows(label=newLabel, groupID=groupID, _getDeleted=True) atLeastOneMatch = False if transactionBundle: for key, aLabel in transactionBundle[ localizationBSDConst.BUNDLE_MESSAGE].iteritems(): if aLabel.label == newLabel and aLabel.groupID == groupID: atLeastOneMatch = True break if labels and len(labels) or atLeastOneMatch: isOriginalLabelUnique = False if returnUnique: newLabel = newLabel + (Message._APPEND_NEW if not _appendWord else _appendWord) else: break else: break if returnUnique: return (isOriginalLabelUnique, newLabel) else: return (isOriginalLabelUnique, None)
class WordMetaData(bsdWrappers.BaseWrapper): """ Wrapper used for accessing attributes on Metadata. See localizationBSD.Message class for interface to manipulate metadata on message. """ __primaryTable__ = bsdWrappers.RegisterTable( localizationBSDConst.WORD_METADATA_TABLE) _typesTable = None _propertyTable = None _metaDataTable = None _bsdSvc = None def Copy(self, keyID=None, keyID2=None, keyID3=None, **kw): """ Copy method is not valid on metadata. """ raise NotImplementedError def __setattr__(self, key, value): if key == localizationBSDConst.COLUMN_MESSAGEID or key == localizationBSDConst.COLUMN_PROPERTY_ID: raise AuthoringValidationError( 'Not allowed to edit messageID or wordPropertyID on the metadata.' ) bsdWrappers.BaseWrapper.__setattr__(self, key, value) def __init__(self, row): bsdWrappers.BaseWrapper.__init__(self, row) self.__class__.CheckAndSetCache() @classmethod def _GetMessageRecord(cls, messageID, transactionBundle=None): """ Retrieve Message record. Look for message in actionIDs + bundles and live data. """ from message import Message currentMessage = None if transactionBundle and type(messageID) != int: currentMessage = transactionBundle[ localizationBSDConst.BUNDLE_MESSAGE].get(messageID, None) else: currentMessage = Message.Get(messageID) return currentMessage @classmethod def _ValidateCreationOfMetaData(cls, wordPropertyID, messageID, metaDataValue, transactionBundle=None): """ Validate the data before attempting to create it. NOTE: This code is bsd transaction friendly. exceptions: throws various exceptions when checks fail """ cls.CheckAndSetCache() propertyRow = cls._propertyTable.GetRowByKey(keyId1=wordPropertyID, _getDeleted=False) if propertyRow: wordTypeID = propertyRow.wordTypeID else: raise AuthoringValidationError( 'Couldnt find typeID of the propertyID (%s).' % wordPropertyID) currentMessage = cls._GetMessageRecord(messageID, transactionBundle) if currentMessage is None: raise AuthoringValidationError( 'Couldnt find message parent of this metadata. MessageID (%s).' % str(messageID)) if wordTypeID != currentMessage.wordTypeID: raise AuthoringValidationError( "Couldnt verify that property's typeID (%s) matches message's typeID (%s)." % (wordTypeID, currentMessage.wordTypeID)) duplicateMetaData = cls._metaDataTable.GetRows( wordPropertyID=wordPropertyID, messageID=messageID) if duplicateMetaData and len(duplicateMetaData): raise AuthoringValidationError( 'Can not add duplicate metadata. wordPropertyID,messageID : (%s, %s)' % (wordPropertyID, str(messageID))) if cls._bsdSvc.TransactionOngoing() and transactionBundle: for aMetaData in transactionBundle[ localizationBSDConst.BUNDLE_METADATA]: if aMetaData.wordPropertyID == wordPropertyID and aMetaData.messageID == messageID: raise AuthoringValidationError( 'Can not add duplicate metadata. wordPropertyID,messageID : (%s, %s)' % (wordPropertyID, str(messageID))) return True @classmethod def _TransactionAwareCreate(cls, wordPropertyID, messageID, metaDataValue, transactionBundle=None): """ Perform Create. This particular method currently doesnt care if it runs within transaction or not. But it follows a pattern where Create is split into two methods, for now. returns: can return either row or actionID """ cls._ValidateCreationOfMetaData(wordPropertyID, messageID, metaDataValue, transactionBundle) if transactionBundle: transactionBundle[localizationBSDConst.BUNDLE_METADATA].append( util.KeyVal({ 'wordPropertyID': wordPropertyID, 'messageID': messageID, 'metaDataValue': metaDataValue })) result = bsdWrappers.BaseWrapper._Create(cls, wordPropertyID=wordPropertyID, messageID=messageID, metaDataValue=metaDataValue) if type(result) == int: return {'reservedWordMetaDataID': result} else: return result @classmethod def TransactionAwareCreate(cls, wordPropertyID, messageID, metaDataValue, transactionBundle=None): """ Create a metadata record. NOTE: code is currently Transaction friendly (when executed with Message creation) it will also validate data using transactionBundle and live data Returns: reserved actionID dictionary for the message, that will be added when transaction is done. """ return cls._TransactionAwareCreate(wordPropertyID, messageID, metaDataValue, transactionBundle) @classmethod def Create(cls, wordPropertyID, messageID, metaDataValue): """ Create a metadata record. NOTE: code is not Transaction-friendly Returns: row of the message that we just added """ return cls._TransactionAwareCreate(wordPropertyID, messageID, metaDataValue, transactionBundle=None) @classmethod def _TransactionAwareCreateFromPropertyName(cls, typeName, propertyName, languageID, messageID, metaDataValue, transactionBundle=None): """ Same as TransactionAwareCreate. """ cls.CheckAndSetCache() typeAndProperty = cls._GetWordTypeAndPropertyID( cls._typesTable, cls._propertyTable, typeName, propertyName, languageID) if typeAndProperty is None: raise AuthoringValidationError( 'Didnt find matching type and property for typeName,propertyName : (%s,%s).' % (typeName, propertyName)) wordTypeID, wordPropertyID = typeAndProperty return cls._TransactionAwareCreate(wordPropertyID, messageID, metaDataValue, transactionBundle) @classmethod def CreateFromPropertyName(cls, typeName, propertyName, languageID, messageID, metaDataValue): """ Create a metadata record. NOTE: code is not Transaction-friendly Returns: row of the message that we just added """ return cls._TransactionAwareCreateFromPropertyName( typeName, propertyName, languageID, messageID, metaDataValue, transactionBundle=None) @classmethod def TransactionAwareCreateFromPropertyName(cls, typeName, propertyName, languageID, messageID, metaDataValue, transactionBundle=None): """ Create a metadata record. NOTE: code is Transaction-friendly Returns: action ID dictionary """ return cls._TransactionAwareCreateFromPropertyName( typeName, propertyName, languageID, messageID, metaDataValue, transactionBundle) @classmethod def CheckAndSetCache(cls): """ Reset cache on the class. """ if cls._typesTable is None or cls._propertyTable is None or cls._metaDataTable is None: bsdTableSvc = sm.GetService('bsdTable') cls._typesTable = bsdTableSvc.GetTable( localizationBSDConst.WORD_TYPES_TABLE) cls._propertyTable = bsdTableSvc.GetTable( localizationBSDConst.WORD_PROPERTIES_TABLE) cls._metaDataTable = bsdTableSvc.GetTable( localizationBSDConst.WORD_METADATA_TABLE) cls._bsdSvc = sm.GetService('BSD') @classmethod def _CopyAllMetaDataToNewMessage(cls, sourceMessageID, destMessageID, destinationWordTypeID, transactionBundle=None): """ Copy all metadata entries to a newly created message (that may not exist yet) This method is internal for all purposes prereq: sourceMessageID must exist in BSD return: True on success """ cls.CheckAndSetCache() sourceMetaRows = cls._metaDataTable.GetRows(messageID=sourceMessageID, _getDeleted=False) with bsd.BsdTransaction(): for aMetaRow in sourceMetaRows: propertyRow = cls._propertyTable.GetRowByKey( aMetaRow.wordPropertyID) if propertyRow.wordTypeID != destinationWordTypeID: raise AuthoringValidationError( 'Source metadata property typeID doesnt match destination typeID. (%s, %s).' % (propertyRow.wordTypeID, destinationWordTypeID)) cls._TransactionAwareCreate( wordPropertyID=aMetaRow.wordPropertyID, messageID=destMessageID, metaDataValue=aMetaRow.metaDataValue, transactionBundle=transactionBundle) return True @staticmethod def _GetWordTypeAndPropertyID(typesTable, propertiesTable, typeName, propertyName, languageID): """ Retrieve typeID and propertyID based on the parameters passed. Return None if not found. """ wordTypeID = None wordPropertyID = None typeRows = typesTable.GetRows(typeName=typeName, _getDeleted=False) if typeRows and len(typeRows): wordTypeID = typeRows[0].wordTypeID else: return dbLanguageID = GetNumericLanguageIDFromLanguageID(languageID) propertyRows = propertiesTable.GetRows(propertyName=propertyName, wordTypeID=wordTypeID, numericLanguageID=dbLanguageID, _getDeleted=False) if propertyRows and len(propertyRows): wordPropertyID = propertyRows[0].wordPropertyID else: return return (wordTypeID, wordPropertyID)
class MessageGroup(bsdWrappers.BaseWrapper): __primaryTable__ = bsdWrappers.RegisterTable(MESSAGE_GROUPS_TABLE) def __setattr__(self, key, value): if key == 'wordTypeID': if self.wordTypeID: wordType = locWordType.WordType.Get(self.wordTypeID) wordTypeName = wordType.typeName if wordType else 'None' raise AuthoringValidationError( "Cannot change wordTypeID: Group '%s' (groupID %s) may contain metadata for wordType '%s'; call ResetWordType first to delete all metadata in this group and try again." % (self.groupName, self.groupID, wordTypeName)) if locWordType.WordType.Get(value) == None: raise AuthoringValidationError( 'WordTypeID (%s) does not exist.' % value) with bsd.BsdTransaction(): for message in locMessage.Message.GetMessagesByGroupID( self.groupID): message.wordTypeID = value bsdWrappers.BaseWrapper.__setattr__(self, key, value) return if key == 'parentID' and value is not None: if not MessageGroup.Get(value): raise AuthoringValidationError( "Cannot set parentID: '%s' is not a valid groupID." % value) if self._IsSubGroup(value): subGroup = MessageGroup.Get(value) raise AuthoringValidationError( "You cannot assign group '%s' as a child of group '%s' because it would create a circular reference." % (self.groupName, subGroup.groupName)) bsdWrappers.BaseWrapper.__setattr__(self, key, value) @classmethod def Create(cls, parentID=None, groupName='New Folder', isReadOnly=None, wordTypeID=None): if not groupName: raise AuthoringValidationError('You must specify a group name.') messageGroupTable = bsdWrappers.GetTable(MessageGroup.__primaryTable__) if groupName: groupName = MessageGroup.GenerateUniqueName(parentID, groupName) if parentID is not None and MessageGroup.Get(parentID) is None: raise AuthoringValidationError( 'Parent(%s) was not found. Can not create this group. groupName : %s ' % (parentID, groupName)) newGroup = bsdWrappers.BaseWrapper._Create(cls, parentID=parentID, groupName=groupName, isReadOnly=isReadOnly, wordTypeID=wordTypeID) if parentID is not None: projectList = locProject.Project.GetProjectsForGroup(parentID) for aProject in projectList: aProject.AddGroupToProject(newGroup.groupID) if MessageGroup.Get(parentID).important: newGroup.important = MessageGroup.Get(parentID).important return newGroup @classmethod def Get(cls, groupID): return bsdWrappers._TryGetObjByKey(MessageGroup, keyID1=groupID, keyID2=None, keyID3=None, _getDeleted=False) def Copy(self, destGroupID): if destGroupID: destGroup = MessageGroup.Get(destGroupID) if not destGroup: raise AuthoringValidationError('Invalid groupID %s' % destGroupID) if destGroup.groupID == self.groupID: raise AuthoringValidationError( 'You cannot copy a group into itself.') if self._IsSubGroup(destGroup.groupID): raise AuthoringValidationError( "You cannot copy group '%s' into group '%s' because it is a subgroup of '%s'." % (self.groupName, destGroup.groupName, self.groupName)) newGroupName = MessageGroup.GenerateUniqueCopyName( self.groupID, destGroupID) self._Copy(destGroupID, newGroupName) def GetFolderPath(self, projectID=None): pathList = [self.groupName] groupDepth = 0 currentNode = self while currentNode.parentID is not None and groupDepth < 100: currentNode = MessageGroup.Get(currentNode.parentID) if currentNode is not None: pathList = [currentNode.groupName] + pathList groupDepth += 1 pathString = '/'.join(pathList) if projectID is not None: pathString = MessageGroup.TurnIntoRelativePath( pathString, locProject.Project.Get(projectID).workingDirectory) return pathString @staticmethod def TurnIntoRelativePath(absolutePath, workingDirectoryPath): if workingDirectoryPath: workingDirectoryPath = workingDirectoryPath.strip('/') if absolutePath: absolutePath = absolutePath.strip('/') if workingDirectoryPath and absolutePath: rootPathPrefix = '/' workingPathWithSlash = workingDirectoryPath + '/' absolutePath += '/' if absolutePath.startswith(workingPathWithSlash): newPath = absolutePath.replace(workingPathWithSlash, '', 1) else: newPath = rootPathPrefix + absolutePath return newPath.rstrip('/') else: return absolutePath def MarkImportant(self, impValue): self.important = impValue childGroups = MessageGroup.GetMessageGroupsByParentID(self.groupID) for childGroup in childGroups: childGroup.MarkImportant(impValue) def RemoveFromProject(self, projectName): projectRow = locProject.Project.GetByName(projectName) if not projectRow: raise AuthoringValidationError( 'No project (%s) was found. Can not tag with this project name.' % projectName) projectRow.RemoveGroupFromProject(self.groupID) def AddToProject(self, projectID): projectRow = locProject.Project.Get(projectID) if not projectRow: raise AuthoringValidationError( 'No project (%s) was found. Can not tag with this project id.' % projectID) projectRow.AddGroupToProject(self.groupID) def AddToProjectByName(self, projectName): projectRow = locProject.Project.GetByName(projectName) if not projectRow: raise AuthoringValidationError( 'No project (%s) was found. Can not tag with this project name.' % projectName) projectRow.AddGroupToProject(self.groupID) def GetWordCount(self, languageID='en-us', recursive=False, includeMetadata=True, projectID=None): wordCount = sum([ message.GetWordCount(languageID=languageID, includeMetadata=includeMetadata) for message in locMessage.Message.GetMessagesByGroupID(self.groupID) ]) if recursive: childGroups = MessageGroup.GetMessageGroupsByParentID( self.groupID, projectID=projectID) for childGroup in childGroups: wordCount += childGroup.GetWordCount(languageID=languageID, recursive=recursive, projectID=projectID) return wordCount def _Copy(self, destGroupID, groupName): if sm.GetService('BSD').TransactionOngoing(): raise AuthoringValidationError( 'You cannot copy groups from within a transaction.') groupID = MessageGroup.Create(parentID=destGroupID, groupName=groupName, isReadOnly=self.isReadOnly, wordTypeID=self.wordTypeID).groupID with bsd.BsdTransaction( "Copying messages from group '%s' (groupID %s) to %s (groupID %s)" % (self.groupName, self.groupID, groupName, groupID)): for message in locMessage.Message.GetMessagesByGroupID( self.groupID): message.TransactionAwareCopy(groupID) childGroups = MessageGroup.GetMessageGroupsByParentID(self.groupID) for group in childGroups: group._Copy(groupID, group.groupName) def _DeleteChildren(self): with bsd.BsdTransaction(): childGroups = MessageGroup.GetMessageGroupsByParentID(self.groupID) for group in childGroups: if not group.Delete(): return False messages = locMessage.Message.GetMessagesByGroupID(self.groupID) for message in messages: if not message.Delete(): return False for aProject in locProject.Project.GetProjectsForGroup( self.groupID): aProject.RemoveGroupFromProject(self.groupID) return True def ResetWordType(self): with bsd.BsdTransaction(): for message in locMessage.Message.GetMessagesByGroupID( self.groupID): message.ResetWordType() bsdWrappers.BaseWrapper.__setattr__(self, 'wordTypeID', None) def _IsSubGroup(self, groupID): testGroup = MessageGroup.Get(groupID) while testGroup.parentID != None: if testGroup.parentID == self.groupID: return True testGroup = MessageGroup.Get(testGroup.parentID) return False @staticmethod def GetMessageGroupsByParentID(parentID, projectID=None): if projectID: currentProject = locProject.Project.Get(projectID) return currentProject.GetMessageGroupsByParentID(parentID) else: return MessageGroup.GetWithFilter(parentID=parentID) @staticmethod def GetVisibleGroupsByParentID(parentID, projectID=None): if projectID: currentProject = locProject.Project.Get(projectID) return currentProject.GetVisibleGroupsByParentID(parentID) else: return MessageGroup.GetMessageGroupsByParentID(parentID) @staticmethod def GenerateUniqueName(destGroupID, groupName): groupNames = [ group.groupName for group in MessageGroup.GetMessageGroupsByParentID(destGroupID) ] numDuplicates = 2 newLabel = groupName while True: if newLabel not in groupNames: return newLabel newLabel = ''.join((groupName, ' (', str(numDuplicates), ')')) numDuplicates += 1 @staticmethod def GenerateUniqueCopyName(sourceGroupID, destGroupID): sourceGroup = MessageGroup.Get(sourceGroupID) if not sourceGroup: raise AuthoringValidationError('%s is not a valid groupID' % sourceGroupID) if sourceGroup.parentID == destGroupID: return MessageGroup.GenerateUniqueName( destGroupID, sourceGroup.groupName + ' - Copy') return MessageGroup.GenerateUniqueName(destGroupID, sourceGroup.groupName)
class WordMetaData(bsdWrappers.BaseWrapper): __primaryTable__ = bsdWrappers.RegisterTable( localizationBSDConst.WORD_METADATA_TABLE) _typesTable = None _propertyTable = None _metaDataTable = None _bsdSvc = None def Copy(self, keyID=None, keyID2=None, keyID3=None, **kw): raise NotImplementedError def __setattr__(self, key, value): if key == localizationBSDConst.COLUMN_MESSAGEID or key == localizationBSDConst.COLUMN_PROPERTY_ID: raise AuthoringValidationError( 'Not allowed to edit messageID or wordPropertyID on the metadata.' ) bsdWrappers.BaseWrapper.__setattr__(self, key, value) def __init__(self, row): bsdWrappers.BaseWrapper.__init__(self, row) self.__class__.CheckAndSetCache() @classmethod def _GetMessageRecord(cls, messageID, transactionBundle=None): from message import Message currentMessage = None if transactionBundle and type(messageID) != int: currentMessage = transactionBundle[ localizationBSDConst.BUNDLE_MESSAGE].get(messageID, None) else: currentMessage = Message.Get(messageID) return currentMessage @classmethod def _ValidateCreationOfMetaData(cls, wordPropertyID, messageID, metaDataValue, transactionBundle=None): cls.CheckAndSetCache() propertyRow = cls._propertyTable.GetRowByKey(keyId1=wordPropertyID, _getDeleted=False) if propertyRow: wordTypeID = propertyRow.wordTypeID else: raise AuthoringValidationError( 'Couldnt find typeID of the propertyID (%s).' % wordPropertyID) currentMessage = cls._GetMessageRecord(messageID, transactionBundle) if currentMessage is None: raise AuthoringValidationError( 'Couldnt find message parent of this metadata. MessageID (%s).' % str(messageID)) if wordTypeID != currentMessage.wordTypeID: raise AuthoringValidationError( "Couldnt verify that property's typeID (%s) matches message's typeID (%s)." % (wordTypeID, currentMessage.wordTypeID)) duplicateMetaData = cls._metaDataTable.GetRows( wordPropertyID=wordPropertyID, messageID=messageID) if duplicateMetaData and len(duplicateMetaData): raise AuthoringValidationError( 'Can not add duplicate metadata. wordPropertyID,messageID : (%s, %s)' % (wordPropertyID, str(messageID))) if cls._bsdSvc.TransactionOngoing() and transactionBundle: for aMetaData in transactionBundle[ localizationBSDConst.BUNDLE_METADATA]: if aMetaData.wordPropertyID == wordPropertyID and aMetaData.messageID == messageID: raise AuthoringValidationError( 'Can not add duplicate metadata. wordPropertyID,messageID : (%s, %s)' % (wordPropertyID, str(messageID))) return True @classmethod def _TransactionAwareCreate(cls, wordPropertyID, messageID, metaDataValue, transactionBundle=None): cls._ValidateCreationOfMetaData(wordPropertyID, messageID, metaDataValue, transactionBundle) if transactionBundle: transactionBundle[localizationBSDConst.BUNDLE_METADATA].append( util.KeyVal({ 'wordPropertyID': wordPropertyID, 'messageID': messageID, 'metaDataValue': metaDataValue })) result = bsdWrappers.BaseWrapper._Create(cls, wordPropertyID=wordPropertyID, messageID=messageID, metaDataValue=metaDataValue) if type(result) == int: return {'reservedWordMetaDataID': result} else: return result @classmethod def TransactionAwareCreate(cls, wordPropertyID, messageID, metaDataValue, transactionBundle=None): return cls._TransactionAwareCreate(wordPropertyID, messageID, metaDataValue, transactionBundle) @classmethod def Create(cls, wordPropertyID, messageID, metaDataValue): return cls._TransactionAwareCreate(wordPropertyID, messageID, metaDataValue, transactionBundle=None) @classmethod def _TransactionAwareCreateFromPropertyName(cls, typeName, propertyName, languageID, messageID, metaDataValue, transactionBundle=None): cls.CheckAndSetCache() typeAndProperty = cls._GetWordTypeAndPropertyID( cls._typesTable, cls._propertyTable, typeName, propertyName, languageID) if typeAndProperty is None: raise AuthoringValidationError( 'Didnt find matching type and property for typeName,propertyName : (%s,%s).' % (typeName, propertyName)) wordTypeID, wordPropertyID = typeAndProperty return cls._TransactionAwareCreate(wordPropertyID, messageID, metaDataValue, transactionBundle) @classmethod def CreateFromPropertyName(cls, typeName, propertyName, languageID, messageID, metaDataValue): return cls._TransactionAwareCreateFromPropertyName( typeName, propertyName, languageID, messageID, metaDataValue, transactionBundle=None) @classmethod def TransactionAwareCreateFromPropertyName(cls, typeName, propertyName, languageID, messageID, metaDataValue, transactionBundle=None): return cls._TransactionAwareCreateFromPropertyName( typeName, propertyName, languageID, messageID, metaDataValue, transactionBundle) @classmethod def CheckAndSetCache(cls): if cls._typesTable is None or cls._propertyTable is None or cls._metaDataTable is None: bsdTableSvc = sm.GetService('bsdTable') cls._typesTable = bsdTableSvc.GetTable( localizationBSDConst.WORD_TYPES_TABLE) cls._propertyTable = bsdTableSvc.GetTable( localizationBSDConst.WORD_PROPERTIES_TABLE) cls._metaDataTable = bsdTableSvc.GetTable( localizationBSDConst.WORD_METADATA_TABLE) cls._bsdSvc = sm.GetService('BSD') @classmethod def _CopyAllMetaDataToNewMessage(cls, sourceMessageID, destMessageID, destinationWordTypeID, transactionBundle=None): cls.CheckAndSetCache() sourceMetaRows = cls._metaDataTable.GetRows(messageID=sourceMessageID, _getDeleted=False) with bsd.BsdTransaction(): for aMetaRow in sourceMetaRows: propertyRow = cls._propertyTable.GetRowByKey( aMetaRow.wordPropertyID) if propertyRow.wordTypeID != destinationWordTypeID: raise AuthoringValidationError( 'Source metadata property typeID doesnt match destination typeID. (%s, %s).' % (propertyRow.wordTypeID, destinationWordTypeID)) cls._TransactionAwareCreate( wordPropertyID=aMetaRow.wordPropertyID, messageID=destMessageID, metaDataValue=aMetaRow.metaDataValue, transactionBundle=transactionBundle) return True @staticmethod def _GetWordTypeAndPropertyID(typesTable, propertiesTable, typeName, propertyName, languageID): wordTypeID = None wordPropertyID = None typeRows = typesTable.GetRows(typeName=typeName, _getDeleted=False) if typeRows and len(typeRows): wordTypeID = typeRows[0].wordTypeID else: return dbLanguageID = GetNumericLanguageIDFromLanguageID(languageID) propertyRows = propertiesTable.GetRows(propertyName=propertyName, wordTypeID=wordTypeID, numericLanguageID=dbLanguageID, _getDeleted=False) if propertyRows and len(propertyRows): wordPropertyID = propertyRows[0].wordPropertyID else: return return (wordTypeID, wordPropertyID)
class MessageText(bsdWrappers.BaseWrapper): __primaryTable__ = bsdWrappers.RegisterTable( localizationBSDConst.MESSAGE_TEXTS_TABLE) @classmethod def Create(cls, messageID, languageID=LOCALE_SHORT_ENGLISH, text='', sourceDataID=None): primaryTable = bsdWrappers.GetTable(MessageText.__primaryTable__) dbLocaleID = GetNumericLanguageIDFromLanguageID(languageID) if dbLocaleID is None: raise RuntimeError( 'LanguageID %s could not be mapped to a locale ID.' % languageID) import message if not message.Message.Get(messageID): raise RuntimeError( 'Message ID %d does not exist! Message text not created for language %s' % (messageID, languageID)) statusID = None if languageID != LOCALE_SHORT_ENGLISH: englishText = MessageText.GetMessageTextByMessageID( messageID, LOCALE_SHORT_ENGLISH) if englishText: if sourceDataID is None: sourceDataID = englishText.dataID if MessageText._ValidateChangeToReview(None, sourceDataID, englishText.dataID): statusID = localizationBSDConst.TEXT_STATUS_REVIEW return bsdWrappers.BaseWrapper._Create(cls, messageID, dbLocaleID, text=text, sourceDataID=sourceDataID, statusID=statusID) def SetTextAndSourceDataID(self, text, sourceDataID): bsdWrappers.BaseWrapper.__setattr__(self, 'sourceDataID', sourceDataID) bsdWrappers.BaseWrapper.__setattr__(self, 'text', text) bsdWrappers.BaseWrapper.__setattr__(self, 'changed', False) englishText = MessageText.GetMessageTextByMessageID( self.messageID, LOCALE_SHORT_ENGLISH) if englishText: self._TrySettingToReview() def MarkTranslationAsCurrent(self): if self.numericLanguageID != GetNumericLanguageIDFromLanguageID( LOCALE_SHORT_ENGLISH): englishText = MessageText.GetMessageTextByMessageID( self.messageID, LOCALE_SHORT_ENGLISH) if englishText: self.sourceDataID = englishText.dataID else: raise AuthoringValidationError( "Cannot call SetAsTranslated() method on '%s' text entry with messageID '%s'. The method must be called on translations." % (LOCALE_SHORT_ENGLISH, self.messageID)) def __setattr__(self, key, value): if key == localizationBSDConst.COLUMN_TEXT and value != self.text: if self.numericLanguageID != GetNumericLanguageIDFromLanguageID( LOCALE_SHORT_ENGLISH): englishText = MessageText.GetMessageTextByMessageID( self.messageID, LOCALE_SHORT_ENGLISH) if englishText: sourceDataID = englishText.dataID bsdWrappers.BaseWrapper.__setattr__( self, 'sourceDataID', sourceDataID) self._TrySettingToReview() bsdWrappers.BaseWrapper.__setattr__(self, key, value) def _TrySettingToReview(self): englishText = MessageText.GetMessageTextByMessageID( self.messageID, LOCALE_SHORT_ENGLISH) if englishText: if MessageText._ValidateChangeToReview(self.statusID, self.sourceDataID, englishText.dataID): self.statusID = localizationBSDConst.TEXT_STATUS_REVIEW @staticmethod def _ValidateChangeToReview(currentStatusID, currentSourceID, englishDataID): if englishDataID == currentSourceID and ( currentStatusID is None or currentStatusID != localizationBSDConst.TEXT_STATUS_DEFECT): return True return False @classmethod def Get(cls, messageID, languageID=LOCALE_SHORT_ENGLISH, _getDeleted=False): dbLocaleID = GetNumericLanguageIDFromLanguageID(languageID) return bsdWrappers._TryGetObjByKey(cls, messageID, dbLocaleID, _getDeleted=_getDeleted) @staticmethod def GetMessageTextsByMessageID(messageID): primaryTable = bsdWrappers.GetTable(MessageText.__primaryTable__) return primaryTable.GetRows(_wrapperClass=MessageText, messageID=messageID, _getDeleted=False) @staticmethod def GetMessageTextByMessageID(messageID, languageID): primaryTable = bsdWrappers.GetTable(MessageText.__primaryTable__) dbLocaleID = GetNumericLanguageIDFromLanguageID(languageID) return primaryTable.GetRowByKey(_wrapperClass=MessageText, keyId1=messageID, keyId2=dbLocaleID, _getDeleted=False)
class WordType(bsdWrappers.BaseWrapper): """ Wrapper for modifying word Types. This class also handles operations on Properties. """ __primaryTable__ = bsdWrappers.RegisterTable(WORD_TYPES_TABLE) def Copy(self, keyID=None, keyID2=None, keyID3=None, **kw): """ Copy method is not implemented. """ raise NotImplementedError def GetAllProperties(self, languageID): """ Return all properties for this language returns: list of property row objects; empty list if properties werent found """ dbLanguageID = GetNumericLanguageIDFromLanguageID(languageID) if dbLanguageID != None: return locWordProperty.WordProperty.GetWithFilter( wordTypeID=self.wordTypeID, numericLanguageID=dbLanguageID) else: return [] def GetPropertyEntry(self, wordPropertyID): """ Retrieve property on this type, for the given id. returns: row object if found, None if not found """ propertyRow = locWordProperty.WordProperty.Get(wordPropertyID) if propertyRow is not None and propertyRow.wordTypeID == self.wordTypeID: return propertyRow def GetPropertyEntryByName(self, propertyName, languageID): """ Retrieve property on this type, for the given name and language. returns: row object if found, None if not found """ dbLanguageID = GetNumericLanguageIDFromLanguageID(languageID) if dbLanguageID != None: propertyRows = locWordProperty.WordProperty.GetWithFilter( propertyName=propertyName, wordTypeID=self.wordTypeID, numericLanguageID=dbLanguageID) if propertyRows and len(propertyRows): return propertyRows[0] def AddPropertyEntry(self, propertyName, languageID, propertyDescription=None): """ Create a property with given parameters. NOTE: This code is transaction friendly. """ locWordProperty.WordProperty.Create( propertyName, self.wordTypeID, languageID, propertyDescription=propertyDescription) def _DeleteChildren(self): """ Checks for dependent objects. """ wordProperties = locWordProperty.WordProperty.GetWithFilter( wordTypeID=self.wordTypeID) if wordProperties and len(wordProperties): raise AuthoringValidationError( 'Type (%s) can not be deleted, because it still has (%s) property(s).' % (self.wordTypeID, str(len(wordProperties)))) return True @classmethod def Create(cls, typeName, typeDescription=None): """ Create new Type. """ if not typeName: raise AuthoringValidationError( 'Type name (%s) must be valid string.' % typeName) duplicateTypes = cls.GetWithFilter(typeName=typeName, _getDeleted=True) if duplicateTypes and len(duplicateTypes): raise AuthoringValidationError( 'Can not insert duplicate word type (%s).' % typeName) return bsdWrappers.BaseWrapper._Create(cls, typeName=typeName, typeDescription=typeDescription)
class Project(bsdWrappers.BaseWrapper): __primaryTable__ = bsdWrappers.RegisterTable( localizationBSDConst.LOC_PROJECT_TABLE) _projectsToGroups = None def Copy(self, keyID=None, keyID2=None, keyID3=None, **kw): raise NotImplementedError @classmethod def GetByName(cls, projectName): projectRows = Project.GetWithFilter(projectName=projectName) if projectRows and len(projectRows) > 1: raise AuthoringValidationError( "Multiple projects (%s) were found. Duplicate project names aren't allowed to exist." % projectName) if projectRows: return projectRows[0] else: return None def AddLanguageToProject(self, languageID): languageProjectRow, languageRow = self._GetLanguageSetting(languageID) if not languageProjectRow: projectsToLanguages = sm.GetService('bsdTable').GetTable( localizationBSDConst.PROJECT_LANGUAGE_TABLE) projectsToLanguages.AddRow(languageRow.numericLanguageID, self.projectID) def RemoveLanguageFromProject(self, languageID): languageProjectRow, languageRow = self._GetLanguageSetting(languageID) if languageProjectRow: languageProjectRow.Delete() def GetAllLanguages(self): projectsToLanguages = sm.GetService('bsdTable').GetTable( localizationBSDConst.PROJECT_LANGUAGE_TABLE) languagesTable = sm.GetService('bsdTable').GetTable( localizationBSDConst.LANGUAGE_SETTINGS_TABLE) listOfLanguages = [] for aTag in projectsToLanguages.GetRows(projectID=self.projectID, _getDeleted=False): listOfLanguages.append( languagesTable.GetRowByKey(aTag.numericLanguageID, _getDeleted=False).languageID) return listOfLanguages def AddGroupToProject(self, groupID): existingRows = Project._projectsToGroups.GetRows( groupID=groupID, projectID=self.projectID) if existingRows and len(existingRows): return Project._projectsToGroups.AddRow(groupID=groupID, projectID=self.projectID) def RemoveGroupFromProject(self, groupID): from messageGroup import MessageGroup existingRows = Project._projectsToGroups.GetRows( groupID=groupID, projectID=self.projectID) if existingRows: for tag in existingRows: tag.Delete() currentGroup = MessageGroup.Get(groupID) if currentGroup: for aSubgroup in MessageGroup.GetWithFilter( parentID=currentGroup.groupID): self.RemoveGroupFromProject(aSubgroup.groupID) def GetAllGroupIDs(self): allTags = Project._projectsToGroups.GetRows(projectID=self.projectID, _getDeleted=False) if allTags and len(allTags): return [aTag.groupID for aTag in allTags] else: return [] def GetMessageGroupsByParentID(self, groupID): from messageGroup import MessageGroup groupList = [] for subGroup in MessageGroup.GetWithFilter(parentID=groupID): if Project._projectsToGroups.GetRows(groupID=subGroup.groupID, projectID=self.projectID, _getDeleted=False): groupList.append(subGroup) return groupList def GetVisibleGroupsByParentID(self, groupID): from messageGroup import MessageGroup groupList = [] for subGroup in MessageGroup.GetWithFilter(parentID=groupID): if self._IsVisibleGroup(subGroup): groupList.append(subGroup) return groupList def GetAllMessages(self): from message import Message allGroupTags = Project._projectsToGroups.GetRows( projectID=self.projectID, _getDeleted=False) if allGroupTags: allMessages = [] for aGroupTag in allGroupTags: allMessages += Message.GetWithFilter(groupID=aGroupTag.groupID) return allMessages else: return [] def __init__(self, row): bsdWrappers.BaseWrapper.__init__(self, row) self.__class__._CheckAndSetCache() def _GetLanguageSetting(self, languageID): languagesTable = sm.GetService('bsdTable').GetTable( localizationBSDConst.LANGUAGE_SETTINGS_TABLE) languageRows = languagesTable.GetRows(languageID=languageID, _getDeleted=False) if len(languageRows) == 0: raise AuthoringValidationError( 'Can not perform operation since didnt find the unique language setting entry. Instead found (%s) rows. ProjectID = (%s), LanguageID = (%s)' % (len(languageRows), self.projectID, languageID)) languageRow = languageRows[0] projectsToLanguages = sm.GetService('bsdTable').GetTable( localizationBSDConst.PROJECT_LANGUAGE_TABLE) languageProjectRow = projectsToLanguages.GetRowByKey( languageRow.numericLanguageID, self.projectID, _getDeleted=False) return (languageProjectRow, languageRow) @classmethod def _CheckAndSetCache(cls): if cls._projectsToGroups is None: bsdTableSvc = sm.GetService('bsdTable') cls._projectsToGroups = bsdTableSvc.GetTable( localizationBSDConst.PROJECT_GROUP_TABLE) def Delete(self): self._DeleteTags() return bsdWrappers.BaseWrapper.Delete(self) def _DeleteTags(self): batchSize = 1000 rowIndex = 0 projectToGroupsRows = Project._projectsToGroups.GetRows( projectID=self.projectID, _getDeleted=False) while rowIndex < len(projectToGroupsRows): with bsd.BsdTransaction(): for aTag in projectToGroupsRows[rowIndex:rowIndex + batchSize]: rowIndex += 1 aTag.Delete() projectsToLanguages = sm.GetService('bsdTable').GetTable( localizationBSDConst.PROJECT_LANGUAGE_TABLE) for aTag in projectsToLanguages.GetRows(projectID=self.projectID, _getDeleted=False): aTag.Delete() def _IsVisibleGroup(self, groupWrapper): from messageGroup import MessageGroup if Project._projectsToGroups.GetRows(groupID=groupWrapper.groupID, projectID=self.projectID, _getDeleted=False): return True for subGroup in MessageGroup.GetWithFilter( parentID=groupWrapper.groupID): if self._IsVisibleGroup(subGroup): return True return False @classmethod def Create(cls, projectName, projectDescription=None, workingDirectory=None, exportLocation=None, exportFileName=None, exportTypeName=None): if not projectName: raise AuthoringValidationError( 'Project name (%s) must be valid string.' % projectName) duplicateProjects = cls.GetWithFilter(projectName=projectName, _getDeleted=True) if duplicateProjects and len(duplicateProjects): raise AuthoringValidationError( 'Can not insert duplicate project (%s).' % projectName) return bsdWrappers.BaseWrapper._Create( cls, projectName=projectName, projectDescription=projectDescription, workingDirectory=workingDirectory, exportLocation=exportLocation, exportFileName=exportFileName, exportTypeName=exportTypeName) @classmethod def GetProjectsForGroup(cls, groupID): cls._CheckAndSetCache() projectTags = cls._projectsToGroups.GetRows(groupID=groupID, _getDeleted=False) if projectTags and len(projectTags): return [Project.Get(aTag.projectID) for aTag in projectTags] else: return []
class Project(bsdWrappers.BaseWrapper): """ Wrapper for handing creation of Localization Projects. This class also handles relations between Projects, Groups, Messages and allowed language sets for each Project. """ __primaryTable__ = bsdWrappers.RegisterTable( localizationBSDConst.LOC_PROJECT_TABLE) _projectsToGroups = None def Copy(self, keyID=None, keyID2=None, keyID3=None, **kw): """ Copy method is not implemented. """ raise NotImplementedError @classmethod def GetByName(cls, projectName): """ Look up and return project by its name. throws: will throw an exception if it found more than one return: project wrapper; None if not found """ projectRows = Project.GetWithFilter(projectName=projectName) if projectRows and len(projectRows) > 1: raise AuthoringValidationError( "Multiple projects (%s) were found. Duplicate project names aren't allowed to exist." % projectName) if projectRows: return projectRows[0] else: return None def AddLanguageToProject(self, languageID): """ Enable language on this project (for exporters and for exporter to translators). """ languageProjectRow, languageRow = self._GetLanguageSetting(languageID) if not languageProjectRow: projectsToLanguages = sm.GetService('bsdTable').GetTable( localizationBSDConst.PROJECT_LANGUAGE_TABLE) projectsToLanguages.AddRow(languageRow.numericLanguageID, self.projectID) def RemoveLanguageFromProject(self, languageID): """ Disable language on this project (for exporters and for exporter to translators). """ languageProjectRow, languageRow = self._GetLanguageSetting(languageID) if languageProjectRow: languageProjectRow.Delete() def GetAllLanguages(self): """ Get list of all languageIDs for this project """ projectsToLanguages = sm.GetService('bsdTable').GetTable( localizationBSDConst.PROJECT_LANGUAGE_TABLE) languagesTable = sm.GetService('bsdTable').GetTable( localizationBSDConst.LANGUAGE_SETTINGS_TABLE) listOfLanguages = [] for aTag in projectsToLanguages.GetRows(projectID=self.projectID, _getDeleted=False): listOfLanguages.append( languagesTable.GetRowByKey(aTag.numericLanguageID, _getDeleted=False).languageID) return listOfLanguages def AddGroupToProject(self, groupID): """ Tag the given group with this project. """ existingRows = Project._projectsToGroups.GetRows( groupID=groupID, projectID=self.projectID) if existingRows and len(existingRows): return Project._projectsToGroups.AddRow(groupID=groupID, projectID=self.projectID) def RemoveGroupFromProject(self, groupID): """ Remove this project's tag from the group. """ from messageGroup import MessageGroup existingRows = Project._projectsToGroups.GetRows( groupID=groupID, projectID=self.projectID) if existingRows: for tag in existingRows: tag.Delete() currentGroup = MessageGroup.Get(groupID) if currentGroup: for aSubgroup in MessageGroup.GetWithFilter( parentID=currentGroup.groupID): self.RemoveGroupFromProject(aSubgroup.groupID) def GetAllGroupIDs(self): """ Get all groups tagged with this project returns: list of group wrappers; or empty list if nothing is found """ allTags = Project._projectsToGroups.GetRows(projectID=self.projectID, _getDeleted=False) if allTags and len(allTags): return [aTag.groupID for aTag in allTags] else: return [] def GetMessageGroupsByParentID(self, groupID): """ Return all subgroups under this group, tagged for this project. """ from messageGroup import MessageGroup groupList = [] for subGroup in MessageGroup.GetWithFilter(parentID=groupID): if Project._projectsToGroups.GetRows(groupID=subGroup.groupID, projectID=self.projectID, _getDeleted=False): groupList.append(subGroup) return groupList def GetVisibleGroupsByParentID(self, groupID): """ Return all subgroups under this group, tagged for this project, plus those that have at least one child group tagged for this project as well. """ from messageGroup import MessageGroup groupList = [] for subGroup in MessageGroup.GetWithFilter(parentID=groupID): if self._IsVisibleGroup(subGroup): groupList.append(subGroup) return groupList def GetAllMessages(self): """ Get all messages tagged with this project returns: list of message wrappers; or empty list if nothing is found """ from message import Message allGroupTags = Project._projectsToGroups.GetRows( projectID=self.projectID, _getDeleted=False) if allGroupTags: allMessages = [] for aGroupTag in allGroupTags: allMessages += Message.GetWithFilter(groupID=aGroupTag.groupID) return allMessages else: return [] def __init__(self, row): bsdWrappers.BaseWrapper.__init__(self, row) self.__class__._CheckAndSetCache() def _GetLanguageSetting(self, languageID): """ Find language row and language tag for this project. Return what's found in tuple. """ languagesTable = sm.GetService('bsdTable').GetTable( localizationBSDConst.LANGUAGE_SETTINGS_TABLE) languageRows = languagesTable.GetRows(languageID=languageID, _getDeleted=False) if len(languageRows) == 0: raise AuthoringValidationError( 'Can not perform operation since didnt find the unique language setting entry. Instead found (%s) rows. ProjectID = (%s), LanguageID = (%s)' % (len(languageRows), self.projectID, languageID)) languageRow = languageRows[0] projectsToLanguages = sm.GetService('bsdTable').GetTable( localizationBSDConst.PROJECT_LANGUAGE_TABLE) languageProjectRow = projectsToLanguages.GetRowByKey( languageRow.numericLanguageID, self.projectID, _getDeleted=False) return (languageProjectRow, languageRow) @classmethod def _CheckAndSetCache(cls): """ Reset cache on the class. """ if cls._projectsToGroups is None: bsdTableSvc = sm.GetService('bsdTable') cls._projectsToGroups = bsdTableSvc.GetTable( localizationBSDConst.PROJECT_GROUP_TABLE) def Delete(self): """ Delete this project and all of its tags """ self._DeleteTags() return bsdWrappers.BaseWrapper.Delete(self) def _DeleteTags(self): """ Delete children records in batches. Cant do this in a single transaction because the string will become too large for some cases. """ batchSize = 1000 rowIndex = 0 projectToGroupsRows = Project._projectsToGroups.GetRows( projectID=self.projectID, _getDeleted=False) while rowIndex < len(projectToGroupsRows): with bsd.BsdTransaction(): for aTag in projectToGroupsRows[rowIndex:rowIndex + batchSize]: rowIndex += 1 aTag.Delete() projectsToLanguages = sm.GetService('bsdTable').GetTable( localizationBSDConst.PROJECT_LANGUAGE_TABLE) for aTag in projectsToLanguages.GetRows(projectID=self.projectID, _getDeleted=False): aTag.Delete() def _IsVisibleGroup(self, groupWrapper): """ Find if any of the groups that are subgroups/trees of this groupID are tagged for this project. """ from messageGroup import MessageGroup if Project._projectsToGroups.GetRows(groupID=groupWrapper.groupID, projectID=self.projectID, _getDeleted=False): return True for subGroup in MessageGroup.GetWithFilter( parentID=groupWrapper.groupID): if self._IsVisibleGroup(subGroup): return True return False @classmethod def Create(cls, projectName, projectDescription=None, workingDirectory=None, exportLocation=None, exportFileName=None, exportTypeName=None): """ Create new Project. parameters: workingDirectory - path to label working directory in the export file (used where applicable) """ if not projectName: raise AuthoringValidationError( 'Project name (%s) must be valid string.' % projectName) duplicateProjects = cls.GetWithFilter(projectName=projectName, _getDeleted=True) if duplicateProjects and len(duplicateProjects): raise AuthoringValidationError( 'Can not insert duplicate project (%s).' % projectName) return bsdWrappers.BaseWrapper._Create( cls, projectName=projectName, projectDescription=projectDescription, workingDirectory=workingDirectory, exportLocation=exportLocation, exportFileName=exportFileName, exportTypeName=exportTypeName) @classmethod def GetProjectsForGroup(cls, groupID): """ Get all projects tagging this group. returns: list of project wrappers; or empty list if nothing is found """ cls._CheckAndSetCache() projectTags = cls._projectsToGroups.GetRows(groupID=groupID, _getDeleted=False) if projectTags and len(projectTags): return [Project.Get(aTag.projectID) for aTag in projectTags] else: return []
class Message(bsdWrappers.BaseWrapper): __primaryTable__ = bsdWrappers.RegisterTable( localizationBSDConst.MESSAGES_TABLE) _messageTextTable = None _propertyTable = None _bsdSvc = None _APPEND_NEW = '(new)' def GetLabelPath(self, projectID=None): from messageGroup import MessageGroup labelPath = '' if self.label is None else self.label if self.groupID is not None: folderPath = MessageGroup.Get( self.groupID).GetFolderPath(projectID=projectID) if folderPath and labelPath: labelPath = '/'.join((folderPath, labelPath)) elif folderPath: labelPath = folderPath return labelPath def GetOpenedBy(self): openedBy = [self._GetOpenedBy(self)] for messageText in locMessageText.MessageText.GetWithFilter( messageID=self.messageID): openedBy += [self._GetOpenedBy(messageText)] for metadata in locWordMetaData.WordMetaData.GetWithFilter( messageID=self.messageID): openedBy += [self._GetOpenedBy(metadata)] openedBy = '\n'.join([text for text in openedBy if text]) return openedBy def _GetOpenedBy(self, bsdWrapper): openedBy = '' openedByUserIDs = bsdWrapper.GetOpenedByUserIDs() bsdState = bsdWrapper.GetState() if bsdState & dbutil.BSDSTATE_OPENFORADD != 0: openedBy += bsdWrapper.__class__.__name__ + ' added by ' elif bsdState & dbutil.BSDSTATE_OPENFORDELETE != 0: openedBy += bsdWrapper.__class__.__name__ + ' marked for delete by ' elif bsdState & dbutil.BSDSTATE_OPENFOREDIT != 0: openedBy += bsdWrapper.__class__.__name__ + ' opened for edit by ' elif openedByUserIDs: openedBy += bsdWrapper.__class__.__name__ + ' opened in unknown state by ' if not openedBy: return openedBy openedBy += ', '.join((bsdWrapper.cache.Row(const.cacheStaticUsers, userID).userName for userID in openedByUserIDs)) return openedBy def Copy(self, groupID, newLabel=None): Message._ErrorIfInTransaction( 'Message Copy will not run within Transaction.') copyLabel = newLabel or self.label if not Message.CheckIfLabelUnique(copyLabel, groupID): raise AuthoringValidationError('Label (%s) is not unique.' % copyLabel) messageCopy = Message.Create( copyLabel, groupID, text=self.GetTextEntry(LOCALE_SHORT_ENGLISH).text, context=self.context) englishText = messageCopy.GetTextEntry(LOCALE_SHORT_ENGLISH) originalEnglishText = self.GetTextEntry(LOCALE_SHORT_ENGLISH) originalTexts = self.messageTextTable.GetRows(messageID=self.messageID, _getDeleted=False) for aText in originalTexts: if aText.numericLanguageID != localizationBSDConst.LOCALE_ID_ENGLISH: newSourceDataID = englishText.dataID if originalEnglishText.dataID == aText.sourceDataID else None self.messageTextTable.AddRow(messageCopy.messageID, aText.numericLanguageID, sourceDataID=newSourceDataID, text=aText.text, statusID=aText.statusID) locWordMetaData.WordMetaData._CopyAllMetaDataToNewMessage( sourceMessageID=self.messageID, destMessageID=messageCopy.messageID, destinationWordTypeID=messageCopy.wordTypeID) return messageCopy def ResetWordType(self): if self.wordTypeID is not None: with bsd.BsdTransaction(): self._DeleteMetaData() bsdWrappers.BaseWrapper.__setattr__(self, 'wordTypeID', None) def GetAllMetaDataEntries(self, languageID): metaDataForLanguage = [] dbLanguageID = GetNumericLanguageIDFromLanguageID(languageID) propertyTable = self.__class__._propertyTable allMetaData = locWordMetaData.WordMetaData.GetWithFilter( messageID=self.messageID) for metaEntry in allMetaData: propertyRow = propertyTable.GetRowByKey(metaEntry.wordPropertyID) if propertyRow and propertyRow.numericLanguageID == dbLanguageID: metaDataForLanguage.append(metaEntry) return metaDataForLanguage def GetMetaDataEntry(self, wordPropertyID): metaDataRows = locWordMetaData.WordMetaData.GetWithFilter( messageID=self.messageID, wordPropertyID=wordPropertyID) if metaDataRows and len(metaDataRows): return metaDataRows[0] else: return None def GetMetaDataEntryByName(self, propertyName, languageID): propertyRows = self.__class__._propertyTable.GetRows( wordTypeID=self.wordTypeID, propertyName=propertyName, numericLanguageID=GetNumericLanguageIDFromLanguageID(languageID), _getDeleted=False) if propertyRows and len(propertyRows) != 1: return None else: metaDataRows = locWordMetaData.WordMetaData.GetWithFilter( messageID=self.messageID, wordPropertyID=propertyRows[0].wordPropertyID) if metaDataRows and len(metaDataRows): return metaDataRows[0] return None def AddMetaDataEntry(self, wordPropertyID, metaDataValue, transactionBundle=None): if self.wordTypeID == None: raise AuthoringValidationError( 'Before adding metadata, the wordType needs to be set on this messageID (%s).' % str(self.messageID)) with bsd.BsdTransaction(): locWordMetaData.WordMetaData.TransactionAwareCreate( wordPropertyID, self.messageID, metaDataValue, transactionBundle=transactionBundle) def AddMetaDataEntryByName(self, propertyName, languageID, metaDataValue, transactionBundle=None): if self.wordTypeID == None: raise AuthoringValidationError( 'Before adding metadata, the wordType needs to be set on this messageID (%s).' % str(self.messageID)) typeRow = locWordType.WordType.Get(self.wordTypeID) if typeRow == None: raise AuthoringValidationError( 'WordTypeID (%s), of this message, does not exist.' % self.wordTypeID) typeName = typeRow.typeName with bsd.BsdTransaction(): locWordMetaData.WordMetaData.TransactionAwareCreateFromPropertyName( typeName, propertyName, languageID, self.messageID, metaDataValue, transactionBundle) def GetTextEntry(self, languageID): return locMessageText.MessageText.Get(self.messageID, languageID) def AddTextEntry(self, languageID, text): textRow = locMessageText.MessageText.Get(self.messageID, languageID) if textRow == None: locMessageText.MessageText.Create(self.messageID, languageID, text=text) else: raise AuthoringValidationError( 'Can not add duplicate text entry. messageID,languageID : (%s, %s)' % (str(self.messageID), languageID)) def GetState(self): bsdState = super(Message, self).GetState() for messageText in locMessageText.MessageText.GetWithFilter( messageID=self.messageID): bsdState |= messageText.GetState() for metadata in locWordMetaData.WordMetaData.GetWithFilter( messageID=self.messageID): bsdState |= metadata.GetState() return bsdState def GetWordCount(self, languageID='en-us', includeMetadata=True): textEntry = self.GetTextEntry(languageID) if not textEntry: return 0 count = len([ part for part in re.findall('\\w*', textEntry.text or '') if part ]) if includeMetadata: metadataEntries = self.GetAllMetaDataEntries(languageID) for metadata in metadataEntries: if metadata.metaDataValue: count += len([ part for part in re.findall( '\\w*', metadata.metaDataValue or '') if part ]) return count def __init__(self, row): bsdWrappers.BaseWrapper.__init__(self, row) self.__class__.CheckAndSetCache() self.messageTextTable = self.__class__._messageTextTable def __setattr__(self, key, value): if key == localizationBSDConst.COLUMN_LABEL: if not Message.CheckIfLabelUnique(value, self.groupID): raise AuthoringValidationError( 'Label can not be set to non-unique name (%s).' % str(value)) if key == localizationBSDConst.COLUMN_TYPE_ID: if self.wordTypeID is not None: raise AuthoringValidationError( 'Not allowed to edit wordTypeID on the message that may contain metadata. Use ResetWordType().' ) elif locWordType.WordType.Get(value) is None: raise AuthoringValidationError( 'WordTypeID (%s) does not exist.' % str(value)) bsdWrappers.BaseWrapper.__setattr__(self, key, value) def _DeleteChildren(self): with bsd.BsdTransaction('Deleting message: %s' % self.label): self._DeleteText() self._DeleteMetaData() return True def _DeleteText(self): for messageText in locMessageText.MessageText.GetMessageTextsByMessageID( self.messageID): if not messageText.Delete(): raise AuthoringValidationError( 'Message (%s) wrapper was unable to delete text entry.' % self.messageID) def _DeleteMetaData(self): for metaData in locWordMetaData.WordMetaData.GetWithFilter( messageID=self.messageID): if not metaData.Delete(): raise AuthoringValidationError( 'Message (%s) wrapper was unable to metadata entry.' % self.messageID) @classmethod def Get(cls, messageID): return bsdWrappers._TryGetObjByKey(cls, messageID, _getDeleted=False) @classmethod def CheckAndSetCache(cls): if cls._messageTextTable is None or cls._propertyTable is None: bsdTableSvc = sm.GetService('bsdTable') cls._messageTextTable = bsdTableSvc.GetTable( localizationBSDConst.MESSAGE_TEXTS_TABLE) cls._propertyTable = bsdTableSvc.GetTable( localizationBSDConst.WORD_PROPERTIES_TABLE) cls._bsdSvc = sm.GetService('BSD') @classmethod def Create(cls, label, groupID=None, text='', context=None): cls._ErrorIfInTransaction( 'Message Create will not run within Transaction. Use TransactionAwareCreate.' ) with bsd.BsdTransaction('Creating new message: %s' % label) as bsdTransaction: cls._TransactionAwareCreate(label, groupID, LOCALE_SHORT_ENGLISH, text, context, wordTypeID=None, transactionBundle=None) resultList = bsdTransaction.GetTransactionResult() return cls.Get(resultList[0][1].messageID) @classmethod def TransactionAwareCreate(cls, label, groupID=None, text='', context=None, transactionBundle=None): cls._ErrorIfNotInTransaction( 'Message TransactionAwareCreate will not run within Transaction. Use Create.' ) with bsd.BsdTransaction('Creating new message: %s' % label): actionIDsResult = cls._TransactionAwareCreate( label, groupID, LOCALE_SHORT_ENGLISH, text, context, wordTypeID=None, transactionBundle=transactionBundle) return actionIDsResult @classmethod def _GetGroupRecord(cls, groupID, transactionBundle=None): from messageGroup import MessageGroup if transactionBundle and type(groupID) != int: currentGroup = transactionBundle.get( localizationBSDConst.BUNDLE_GROUP, {}).get(groupID, None) else: currentGroup = MessageGroup.Get(groupID) return currentGroup @classmethod def _GetWordTypeID(cls, groupID, transactionBundle=None): wordTypeID = None if groupID is not None: parentGroup = cls._GetGroupRecord( groupID, transactionBundle=transactionBundle) if parentGroup: wordTypeID = parentGroup.wordTypeID return wordTypeID @classmethod def _ValidateCreationOfMessage(cls, label, groupID, wordTypeID, transactionBundle=None): if not cls.CheckIfLabelUnique( label, groupID, transactionBundle=transactionBundle): raise AuthoringValidationError( 'Label (%s) in groupID (%s) is not unique.' % (label, str(groupID))) if groupID != None: parentGroup = cls._GetGroupRecord( groupID, transactionBundle=transactionBundle) if parentGroup: if wordTypeID != parentGroup.wordTypeID: raise AuthoringValidationError( 'Group type doesnt match message type (%s,%s).' % (wordTypeID, parentGroup.wordTypeID)) else: raise AuthoringValidationError( "Parent group (%s) wasn't found." % str(groupID)) if wordTypeID != None: typeRow = locWordType.WordType.Get(wordTypeID) if typeRow == None: raise AuthoringValidationError("Type (%s) wasn't found." % wordTypeID) return True @classmethod def _TransactionAwareCreate(cls, label, groupID, languageID, text, context, wordTypeID=None, transactionBundle=None): inheritedWordTypeID = Message._GetWordTypeID(groupID) if wordTypeID is None: wordTypeID = inheritedWordTypeID Message._ValidateCreationOfMessage(label, groupID, wordTypeID, transactionBundle=transactionBundle) dbLocaleID = GetNumericLanguageIDFromLanguageID(languageID) if dbLocaleID is None: raise AuthoringValidationError('Didnt find language (%s).' % languageID) reservedActionID = bsdWrappers.BaseWrapper._Create( cls, label=label, groupID=groupID, context=context, wordTypeID=wordTypeID) if transactionBundle: tupleActionID = (reservedActionID, 'messageID') transactionBundle[localizationBSDConst. BUNDLE_MESSAGE][tupleActionID] = util.KeyVal({ 'label': label, 'groupID': groupID, 'context': context, 'wordTypeID': wordTypeID }) messageTextTable = bsdWrappers.GetTable( locMessageText.MessageText.__primaryTable__) messageTextTable.AddRow((reservedActionID, 'messageID'), dbLocaleID, text=text) if type(reservedActionID) == int: return {'reservedMessageID': reservedActionID} raise AuthoringValidationError( 'Unexpected error. Possibly incorrect use of transactions. Expected actionID but instead got : %s ' % str(reservedActionID)) @classmethod def _ErrorIfInTransaction(cls, errorMessage): cls.CheckAndSetCache() if cls._bsdSvc.TransactionOngoing(): raise AuthoringValidationError(errorMessage) @classmethod def _ErrorIfNotInTransaction(cls, errorMessage): cls.CheckAndSetCache() if not cls._bsdSvc.TransactionOngoing(): raise AuthoringValidationError(errorMessage) @staticmethod def CheckIfLabelUnique(originalLabel, groupID, transactionBundle=None, _appendWord=None): isUnique, label = Message._CheckLabelUniqueness( originalLabel, groupID, transactionBundle=transactionBundle, returnUnique=False, _appendWord=_appendWord) return isUnique @staticmethod def GetUniqueLabel(originalLabel, groupID, transactionBundle=None, _appendWord=None): isUnique, label = Message._CheckLabelUniqueness( originalLabel, groupID, transactionBundle=transactionBundle, returnUnique=True, _appendWord=_appendWord) return label @staticmethod def GetMessageByID(messageID): primaryTable = bsdWrappers.GetTable(Message.__primaryTable__) return primaryTable.GetRowByKey(_wrapperClass=Message, keyId1=messageID, _getDeleted=False) @staticmethod def GetMessagesByGroupID(groupID, projectID=None): return Message.GetWithFilter(groupID=groupID) @staticmethod def _CheckLabelUniqueness(originalLabel, groupID, transactionBundle=None, returnUnique=False, _appendWord=None): isOriginalLabelUnique = True if originalLabel is None: return (isOriginalLabelUnique, None) primaryTable = bsdWrappers.GetTable(Message.__primaryTable__) newLabel = originalLabel while True: labels = primaryTable.GetRows(label=newLabel, groupID=groupID, _getDeleted=True) atLeastOneMatch = False if transactionBundle: for key, aLabel in transactionBundle[ localizationBSDConst.BUNDLE_MESSAGE].iteritems(): if aLabel.label == newLabel and aLabel.groupID == groupID: atLeastOneMatch = True break if labels and len(labels) or atLeastOneMatch: isOriginalLabelUnique = False if returnUnique: newLabel = newLabel + (Message._APPEND_NEW if not _appendWord else _appendWord) else: break else: break if returnUnique: return (isOriginalLabelUnique, newLabel) else: return (isOriginalLabelUnique, None)
class MessageGroup(bsdWrappers.BaseWrapper): """ A wrapper object for message groups, where a message is a labeled English string or localized string. """ __primaryTable__ = bsdWrappers.RegisterTable(MESSAGE_GROUPS_TABLE) def __setattr__(self, key, value): """ Sets an attribute on the group, with special behavior when changing the word type. Changing the word type will propagate to all messages in the group, removing metadata if messages with a different word type are present. """ if key == 'wordTypeID': if self.wordTypeID: wordType = locWordType.WordType.Get(self.wordTypeID) wordTypeName = wordType.typeName if wordType else 'None' raise AuthoringValidationError( "Cannot change wordTypeID: Group '%s' (groupID %s) may contain metadata for wordType '%s'; call ResetWordType first to delete all metadata in this group and try again." % (self.groupName, self.groupID, wordTypeName)) if locWordType.WordType.Get(value) == None: raise AuthoringValidationError( 'WordTypeID (%s) does not exist.' % value) with bsd.BsdTransaction(): for message in locMessage.Message.GetMessagesByGroupID( self.groupID): message.wordTypeID = value bsdWrappers.BaseWrapper.__setattr__(self, key, value) return if key == 'parentID' and value is not None: if not MessageGroup.Get(value): raise AuthoringValidationError( "Cannot set parentID: '%s' is not a valid groupID." % value) if self._IsSubGroup(value): subGroup = MessageGroup.Get(value) raise AuthoringValidationError( "You cannot assign group '%s' as a child of group '%s' because it would create a circular reference." % (self.groupName, subGroup.groupName)) bsdWrappers.BaseWrapper.__setattr__(self, key, value) @classmethod def Create(cls, parentID=None, groupName='New Folder', isReadOnly=None, wordTypeID=None): """ Create a new group. parameters: wordTypeID - type of the group. Note this parameter doesnt get inherited from parent. """ if not groupName: raise AuthoringValidationError('You must specify a group name.') messageGroupTable = bsdWrappers.GetTable(MessageGroup.__primaryTable__) if groupName: groupName = MessageGroup.GenerateUniqueName(parentID, groupName) if parentID is not None and MessageGroup.Get(parentID) is None: raise AuthoringValidationError( 'Parent(%s) was not found. Can not create this group. groupName : %s ' % (parentID, groupName)) newGroup = bsdWrappers.BaseWrapper._Create(cls, parentID=parentID, groupName=groupName, isReadOnly=isReadOnly, wordTypeID=wordTypeID) if parentID is not None: projectList = locProject.Project.GetProjectsForGroup(parentID) for aProject in projectList: aProject.AddGroupToProject(newGroup.groupID) if MessageGroup.Get(parentID).important: newGroup.important = MessageGroup.Get(parentID).important return newGroup @classmethod def Get(cls, groupID): """ Returns the group wrapper corresponding to the supplied groupID. """ return bsdWrappers._TryGetObjByKey(MessageGroup, keyID1=groupID, keyID2=None, keyID3=None, _getDeleted=False) def Copy(self, destGroupID): """ Copies the current group wrapper to the destination group, as well as all messages and folders it contains. """ if destGroupID: destGroup = MessageGroup.Get(destGroupID) if not destGroup: raise AuthoringValidationError('Invalid groupID %s' % destGroupID) if destGroup.groupID == self.groupID: raise AuthoringValidationError( 'You cannot copy a group into itself.') if self._IsSubGroup(destGroup.groupID): raise AuthoringValidationError( "You cannot copy group '%s' into group '%s' because it is a subgroup of '%s'." % (self.groupName, destGroup.groupName, self.groupName)) newGroupName = MessageGroup.GenerateUniqueCopyName( self.groupID, destGroupID) self._Copy(destGroupID, newGroupName) def GetFolderPath(self, projectID=None): """ Generate path to this group as it'll appear in export/pickle file(s). NOTE: This is primarily used in UI/Content Browser to show the user how the paths would look like. This function doesnt actually care whether this message was tagged with the projectID or not. It is also meant to be used by a similar function on Message: GetLabelPath parameters: projectID - optional parameter. When specified will use Project's working directory when rendering final path string returns: a path string, of the form: u'/UI/Generic/Buttons' """ pathList = [self.groupName] groupDepth = 0 currentNode = self while currentNode.parentID is not None and groupDepth < 100: currentNode = MessageGroup.Get(currentNode.parentID) if currentNode is not None: pathList = [currentNode.groupName] + pathList groupDepth += 1 pathString = '/'.join(pathList) if projectID is not None: pathString = MessageGroup.TurnIntoRelativePath( pathString, locProject.Project.Get(projectID).workingDirectory) return pathString @staticmethod def TurnIntoRelativePath(absolutePath, workingDirectoryPath): """ Function takes label (absolute) directory path, and project (absolute) working directory path then returns relative path version of the first directory path. """ if workingDirectoryPath: workingDirectoryPath = workingDirectoryPath.strip('/') if absolutePath: absolutePath = absolutePath.strip('/') if workingDirectoryPath and absolutePath: rootPathPrefix = '/' workingPathWithSlash = workingDirectoryPath + '/' absolutePath += '/' if absolutePath.startswith(workingPathWithSlash): newPath = absolutePath.replace(workingPathWithSlash, '', 1) else: newPath = rootPathPrefix + absolutePath return newPath.rstrip('/') else: return absolutePath def MarkImportant(self, impValue): """ Sets the "important" bit on the message group to the requested value. If includeSubfolders is true, then this affects all subfolders of the group as well. """ self.important = impValue childGroups = MessageGroup.GetMessageGroupsByParentID(self.groupID) for childGroup in childGroups: childGroup.MarkImportant(impValue) def RemoveFromProject(self, projectName): """ Untag this group from the project """ projectRow = locProject.Project.GetByName(projectName) if not projectRow: raise AuthoringValidationError( 'No project (%s) was found. Can not tag with this project name.' % projectName) projectRow.RemoveGroupFromProject(self.groupID) def AddToProject(self, projectID): """ Tag this group (and subgroups) with the project """ projectRow = locProject.Project.Get(projectID) if not projectRow: raise AuthoringValidationError( 'No project (%s) was found. Can not tag with this project id.' % projectID) projectRow.AddGroupToProject(self.groupID) def AddToProjectByName(self, projectName): """ Tag this group (and subgroups) with the project """ projectRow = locProject.Project.GetByName(projectName) if not projectRow: raise AuthoringValidationError( 'No project (%s) was found. Can not tag with this project name.' % projectName) projectRow.AddGroupToProject(self.groupID) def GetWordCount(self, languageID='en-us', recursive=False, includeMetadata=True, projectID=None): """ Aggregates the word count of all messages in this folder for the specified language. """ wordCount = sum([ message.GetWordCount(languageID=languageID, includeMetadata=includeMetadata) for message in locMessage.Message.GetMessagesByGroupID(self.groupID) ]) if recursive: childGroups = MessageGroup.GetMessageGroupsByParentID( self.groupID, projectID=projectID) for childGroup in childGroups: wordCount += childGroup.GetWordCount(languageID=languageID, recursive=recursive, projectID=projectID) return wordCount def _Copy(self, destGroupID, groupName): """ Helper function for the Copy method. """ if sm.GetService('BSD').TransactionOngoing(): raise AuthoringValidationError( 'You cannot copy groups from within a transaction.') groupID = MessageGroup.Create(parentID=destGroupID, groupName=groupName, isReadOnly=self.isReadOnly, wordTypeID=self.wordTypeID).groupID with bsd.BsdTransaction( "Copying messages from group '%s' (groupID %s) to %s (groupID %s)" % (self.groupName, self.groupID, groupName, groupID)): for message in locMessage.Message.GetMessagesByGroupID( self.groupID): message.TransactionAwareCopy(groupID) childGroups = MessageGroup.GetMessageGroupsByParentID(self.groupID) for group in childGroups: group._Copy(groupID, group.groupName) def _DeleteChildren(self): """ Deletes all items contained beneath this group. """ with bsd.BsdTransaction(): childGroups = MessageGroup.GetMessageGroupsByParentID(self.groupID) for group in childGroups: if not group.Delete(): return False messages = locMessage.Message.GetMessagesByGroupID(self.groupID) for message in messages: if not message.Delete(): return False for aProject in locProject.Project.GetProjectsForGroup( self.groupID): aProject.RemoveGroupFromProject(self.groupID) return True def ResetWordType(self): """ Deletes all existing metadata on all messages in the group, sets their word type to None, and sets the word type of the group to None. """ with bsd.BsdTransaction(): for message in locMessage.Message.GetMessagesByGroupID( self.groupID): message.ResetWordType() bsdWrappers.BaseWrapper.__setattr__(self, 'wordTypeID', None) def _IsSubGroup(self, groupID): """ Returns True if the specified groupID is in a folder beneath the current group. """ testGroup = MessageGroup.Get(groupID) while testGroup.parentID != None: if testGroup.parentID == self.groupID: return True testGroup = MessageGroup.Get(testGroup.parentID) return False @staticmethod def GetMessageGroupsByParentID(parentID, projectID=None): """ Returns all subgroups directly beneath the parent group. If ProjectID is specified then return all subgroups under this group, tagged for this project. """ if projectID: currentProject = locProject.Project.Get(projectID) return currentProject.GetMessageGroupsByParentID(parentID) else: return MessageGroup.GetWithFilter(parentID=parentID) @staticmethod def GetVisibleGroupsByParentID(parentID, projectID=None): """ Returns all subgroups directly beneath the parent group. If ProjectID is specified then return all subgroups under this group, tagged for this project, plus those that have at least one child group tagged for this project as well. NOTE: this method in particular useful for UI that's building the group tree structure. """ if projectID: currentProject = locProject.Project.Get(projectID) return currentProject.GetVisibleGroupsByParentID(parentID) else: return MessageGroup.GetMessageGroupsByParentID(parentID) @staticmethod def GenerateUniqueName(destGroupID, groupName): """ Given a target location and a requested group name, creates an Explorer-style unique name by appending (n) to the end of the group name, where n is the first available number not in use by another group. It is acceptable for groups and messages to have the same name, but two groups with the same parent must have unique names. """ groupNames = [ group.groupName for group in MessageGroup.GetMessageGroupsByParentID(destGroupID) ] numDuplicates = 2 newLabel = groupName while True: if newLabel not in groupNames: return newLabel newLabel = ''.join((groupName, ' (', str(numDuplicates), ')')) numDuplicates += 1 @staticmethod def GenerateUniqueCopyName(sourceGroupID, destGroupID): """ Given source and destination groups, creates an Explorer-style unique name, either by appending (n) to the end of the group name, if the group is being copied to another folder, or by appending "- Copy" to the end of the name (and then potentially adding (n)) if the group is being copied to the same folder. It is acceptable for groups and messages to have the same name, but two groups with the same parent must have unique names. parameters: sourceGroupID - groupID of the group being copied. destGroupID - groupID of the group we are copying the source group to. """ sourceGroup = MessageGroup.Get(sourceGroupID) if not sourceGroup: raise AuthoringValidationError('%s is not a valid groupID' % sourceGroupID) if sourceGroup.parentID == destGroupID: return MessageGroup.GenerateUniqueName( destGroupID, sourceGroup.groupName + ' - Copy') return MessageGroup.GenerateUniqueName(destGroupID, sourceGroup.groupName)