Пример #1
0
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
Пример #2
0
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)
Пример #3
0
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)
Пример #4
0
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)
Пример #5
0
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)
Пример #6
0
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)
Пример #7
0
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)
Пример #8
0
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)
Пример #9
0
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 []
Пример #10
0
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 []
Пример #11
0
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)
Пример #12
0
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)