Exemplo n.º 1
0
def test_removing_translations(session, session_cls, first, second):
    translator = Translator(Translation, session_cls(), 'language')
    translator.bind(session)
    instance = Model(name=first)
    session.add(instance)
    session.commit()

    expected_count = 1 if is_translatable_value(first) else 0
    assert translator.session.query(Translation).count() == expected_count

    instance.name = second
    session.commit()

    expected_count = 1 if is_translatable_value(second) else 0
    assert translator.session.query(Translation).count() == expected_count
Exemplo n.º 2
0
def collect_translatables(manager, obj):
    """ Return translatables from ``obj``.

    Mutates ``obj`` to replace translations with placeholders.

    Expects translator.save_translation or translator.delete_translations
    to be called for each collected translatable.
    """
    if isinstance(obj, PersistableType):
        # object is a type; nothing to do
        return []

    translatables = []

    descriptor = manager.type_registry.get_descriptor(type(obj))
    message_id = get_message_id(manager, obj)

    for attr_name in iter_translatables(descriptor):
        attr = getattr(obj, attr_name)
        if is_translatable_value(attr):
            setattr(obj, attr_name, PLACEHOLDER)
        context = get_context(manager, obj, attr_name)
        translatable = TaalTranslatableString(
            context, message_id, attr)
        translatables.append(translatable)

    return translatables
Exemplo n.º 3
0
def after_commit(session):
    """ Save any pending translations for this session """
    for transaction, target, column, value in flush_log.pop(session, []):
        translator = get_translator(session)

        translatable = make_from_obj(target, column.name, value)
        if is_translatable_value(value):
            translator.save_translation(translatable, commit=True)
        else:
            # a non-translatable value in the commit log indicates a deletion
            translator.delete_translations(translatable)

        old_value = getattr(target, column.name)
        if is_translatable_value(old_value):
            # we may now have a primary key
            old_value.message_id = translatable.message_id
            # value is now saved. No need to keep around
            old_value.pending_value = None
Exemplo n.º 4
0
    def to_python(value):

        if not is_translatable_value(value):
            return value

        if value == PLACEHOLDER:
            # Before translation, return a placeholder that's more likely to
            # generate an error than a normal string.
            return PlaceholderValue

        raise RuntimeError(
            "Unexpected value found in placeholder column: '{}'".format(value))
Exemplo n.º 5
0
def set_(target, value, oldvalue, initiator):
    """ Wrap any value in ``TranslatableString``, except None and the empty
    string
    """
    if not is_translatable_value(value):
        return value

    if isinstance(value, TaalTranslatableString):
        return TaalTranslatableString(
            value.context, value.message_id, value.pending_value)

    translatable = make_from_obj(target, initiator.key, value)
    return translatable
Exemplo n.º 6
0
def refresh(target, args, attrs):
    mapper = inspect(target.__class__)
    if attrs is None:
        attrs = mapper.columns.keys()

    for column_name in attrs:
        if column_name not in mapper.columns:
            continue
        column = mapper.columns[column_name]
        if isinstance(column.type, TranslatableString):
            value = getattr(target, column.name)
            if is_translatable_value(value):
                translatable = make_from_obj(target, column.name, value)
                setattr(target, column.name, translatable)
    return target
Exemplo n.º 7
0
    def process_result_value(self, value, dialect):

        if not is_translatable_value(value):
            return value

        if value == PLACEHOLDER:

            # can't prevent this from being returned to the user
            # in the case of a direct query for Model.field
            # Return something that's more likely to error early
            # than a string
            return PlaceholderValue

        raise RuntimeError(
            "Unexpected value found in placeholder column: '{}'".format(value))
Exemplo n.º 8
0
    def serialize(self, obj, for_db=False):
        if for_db or type(obj) is PersistableType:
            return super(Manager, self).serialize(obj)

        message_id = get_message_id(self, obj)
        data = super(Manager, self).serialize(obj)
        descriptor = self.type_registry.get_descriptor(type(obj))
        for attr_name, attr_type in descriptor.attributes.items():
            if isinstance(attr_type, TranslatableString):
                value = data[attr_name]
                if is_translatable_value(value):
                    context = get_context(self, obj, attr_name)
                    data[attr_name] = TaalTranslatableString(
                        context, message_id)
        return data
Exemplo n.º 9
0
    def save(self, obj):
        translatables = collect_translatables(self, obj)
        result = super(Manager, self).save(obj)

        if translatables:
            translator = get_translator(self)
            for translatable in translatables:
                if is_translatable_value(translatable.pending_value):
                    translator.save_translation(translatable)
                else:
                    # delete all translations (in every language) if the
                    # value is None or the empty string
                    translator.delete_translations(translatable)

        return result
Exemplo n.º 10
0
def load(target, context):
    """ Wrap columns when loading data from the db """
    mapper = inspect(target.__class__)
    for column in mapper.columns:
        if isinstance(column.type, TranslatableString):
            value = getattr(target, column.name)
            if not is_translatable_value(value):
                continue
            elif value is PlaceholderValue:
                translatable = make_from_obj(target, column.name, value)
                setattr(target, column.name, translatable)
            elif isinstance(value, TaalTranslatableString):
                continue  # during session.merge
            else:
                raise TypeError("Unexpected column value '{}'".format(
                    value))
Exemplo n.º 11
0
def add_to_flush_log(session, target, delete=False):
    cls = target.__class__
    for column, attr_name in translatable_models.get(cls, {}).items():
        history = get_history(target, attr_name)
        if not delete and not history.has_changes():
            # for non-delete actions, we're only interested in changed columns
            continue

        if delete:
            value = None  # will trigger deletion of translations
        else:
            value = getattr(target, attr_name)
        if is_translatable_value(value):
            pending_translatables.add(value)
            value = value.pending_value
        flush_log.setdefault(session, []).append(
            (session.transaction, target, column, value))
Exemplo n.º 12
0
    def process_bind_param(self, value, dialect):
        if not is_translatable_value(value):
            return value

        if not isinstance(value, TaalTranslatableString):
            # this should only happen if someone is trying to query
            # TODO: verify this
            raise RuntimeError("Cannot filter on translated fields")

        if value in pending_translatables:
            pending_translatables.remove(value)
        else:
            raise RuntimeError(
                "Cannot save directly to translated fields. "
                "Use ``save_translation`` instead "
                "Value was '{}'".format(value))

        return PLACEHOLDER
Exemplo n.º 13
0
def add_to_flush_log(session, target, delete=False):
    mapper = inspect(target.__class__)
    for column in mapper.columns:
        history = get_history(target, column.name)
        if not delete and not history.has_changes():
            # for non-delete actions, we're only interested in changed columns
            continue

        if isinstance(column.type, TranslatableString):
            if delete:
                value = None  # will trigger deletion of translations
            else:
                value = getattr(target, column.name)
            if is_translatable_value(value):
                pending_translatables.add(value)
                value = value.pending_value
            flush_log.setdefault(session, []).append(
                (session.transaction, target, column, value))
Exemplo n.º 14
0
    def change_instance_type(self, obj, type_id, updated_values=None):

        if updated_values is None:
            updated_values = {}

        updated_values = updated_values.copy()

        old_descriptor = self.type_registry.get_descriptor(type(obj))
        new_descriptor = self.type_registry.get_descriptor_by_id(type_id)

        old_message_id = get_message_id(self, obj)
        old_translatables = {}

        # collect any translatable fields on the original object
        # also, replace any values with placeholders for the super() call

        for attr_name in iter_translatables(old_descriptor):
            attr = getattr(obj, attr_name)
            if is_translatable_value(attr):
                setattr(obj, attr_name, PLACEHOLDER)
            context = get_context(self, obj, attr_name)
            translatable = TaalTranslatableString(
                context, old_message_id, attr)
            old_translatables[attr_name] = translatable

        new_translatables = {}

        # collect any translatable fields from the new type
        # also, replace any values in updated_values with placeholders
        # for the super() call

        # note that we can't collect the context/message_id until after
        # we call super(), since they may be about to change
        # (context will definitely change, and message_id might, if we add or
        # remove unique attributes)

        for attr_name in iter_translatables(new_descriptor):
            attr = updated_values.get(attr_name)
            if is_translatable_value(attr):
                updated_values[attr_name] = PLACEHOLDER
            translatable = TaalTranslatableString(
                None, None, attr)
            new_translatables[attr_name] = translatable

        new_obj = super(Manager, self).change_instance_type(
            obj, type_id, updated_values)

        # we are now able to fill in context/message_id for the new object

        new_message_id = get_message_id(self, new_obj)
        for attr_name, translatable in new_translatables.items():
            translatable.message_id = new_message_id
            translatable.context = get_context(self, new_obj, attr_name)

        to_delete = set(old_translatables) - set(new_translatables)
        to_rename = set(old_translatables) & set(new_translatables)
        to_add = set(new_translatables) - set(old_translatables)

        translator = get_translator(self)

        for key in to_delete:
            translatable = old_translatables[key]
            translator.delete_translations(translatable)

        for key in to_rename:
            old_translatable = old_translatables[key]
            new_translatable = new_translatables[key]
            translator.move_translations(old_translatable, new_translatable)
            if new_translatable.pending_value is not None:
                # updated_values contained a key for a field already existing
                # on the old type. save the updated translation
                translator.save_translation(new_translatable)

        for key in to_add:
            translatable = new_translatables[key]
            if translatable.pending_value is not None:
                translator.save_translation(translatable)

        return new_obj