示例#1
0
    def pre_sql_setup(self):
        """
        Adds the JOINS and SELECTS for fetching multilingual data.
        """
        super(MultilingualSQLCompiler, self).pre_sql_setup()

        if not self.query.include_translation_data:
            return

        opts = self.query.model._meta
        qn = self.quote_name_unless_alias
        qn2 = self.connection.ops.quote_name

        if hasattr(opts, 'translation_model'):
            master_table_name = opts.db_table
            translation_opts = opts.translation_model._meta
            trans_table_name = translation_opts.db_table
            for language_code in get_language_code_list():
                table_alias = get_translation_table_alias(trans_table_name,
                                                          language_code)
                trans_join = ("LEFT JOIN %s AS %s ON ((%s.master_id = %s.%s) AND (%s.language_code = '%s'))"
                           % (qn2(translation_opts.db_table),
                           qn2(table_alias),
                           qn2(table_alias),
                           qn(master_table_name),
                           qn2(opts.pk.column),
                           qn2(table_alias),
                           language_code))
                self.query.extra_join[table_alias] = trans_join
示例#2
0
    def values_list(self, *fields, **kwargs):
        if hasattr(self.model._meta, 'translation_model'):
            extra_select = {}
            trans_opts = self.model._meta.translation_model._meta
            trans_table_name = trans_opts.db_table
            qn2 = self.query.connection.ops.quote_name

            for field_name in fields:
                field_and_lang = trans_opts.translated_fields.get(field_name)
                if field_and_lang:
                    field, language_id = field_and_lang
                    if language_id is None:
                        language_id = getattr(self, '_default_language', None)
                    table_alias = get_translation_table_alias(
                        trans_table_name, language_id)
                    extra_select[field_name] = qn2(table_alias) + '.' + qn2(
                        field.attname)

            # this maps columns to required field_names
            result = self.extra(select=extra_select)
            # and it return MultilingualModelQuerySet instance, so we have to super it
            return super(MultilingualModelQuerySet,
                         result).values_list(*fields, **kwargs)
        else:
            return super(MultilingualModelQuerySet,
                         self).values_list(*fields, **kwargs)
示例#3
0
    def pre_sql_setup(self):
        """Adds the JOINS and SELECTS for fetching multilingual data.
        """
        super(MultilingualQuery, self).pre_sql_setup()

        if not self.include_translation_data:
            return

        opts = self.model._meta
        qn = self.quote_name_unless_alias
        qn2 = self.connection.ops.quote_name
        if hasattr(opts, 'translation_model'):
            master_table_name = opts.db_table
            translation_opts = opts.translation_model._meta
            trans_table_name = translation_opts.db_table
            for language_id in get_language_id_list():
                table_alias = get_translation_table_alias(
                    trans_table_name, language_id)
                trans_join = (
                    'LEFT JOIN %s AS %s ON ((%s.master_id = %s.%s) AND (%s.language_id = %s))'
                    % (qn2(translation_opts.db_table), qn2(table_alias),
                       qn2(table_alias), qn(master_table_name),
                       qn2(self.model._meta.pk.column), qn2(table_alias),
                       language_id))
                self.extra_join[table_alias] = trans_join
示例#4
0
 def __init__(self, model, connection, where=WhereNode):
     self.extra_join = {}
     extra_select = {}
     super(MultilingualQuery, self).__init__(model, connection, where=where)
     opts = self.model._meta
     qn = self.quote_name_unless_alias
     qn2 = self.connection.ops.quote_name
     master_table_name = opts.db_table
     translation_opts = opts.translation_model._meta
     trans_table_name = translation_opts.db_table
     if hasattr(opts, 'translation_model'):
         master_table_name = opts.db_table
         for language_id in get_language_id_list():
             for fname in [f.attname for f in translation_opts.fields]:
                 table_alias = get_translation_table_alias(trans_table_name,
                     language_id)
                 field_alias = get_translated_field_alias(fname,
                     language_id)
                 extra_select[field_alias] = qn2(table_alias) + '.' + qn2(fname)
         self.add_extra(extra_select, None,None,None,None,None)
示例#5
0
 def __init__(self, model, where=WhereNode):
     self.extra_join = {}
     self.include_translation_data = True
     extra_select = {}
     super(MultilingualQuery, self).__init__(model, where=where)
     opts = self.model._meta
     qn = self.get_compiler(DEFAULT_DB_ALIAS).quote_name_unless_alias
     qn2 = self.get_compiler(DEFAULT_DB_ALIAS).connection.ops.quote_name
     master_table_name = opts.db_table
     translation_opts = opts.translation_model._meta
     trans_table_name = translation_opts.db_table
     if hasattr(opts, 'translation_model'):
         master_table_name = opts.db_table
         for language_code in get_language_code_list():
             for fname in [f.attname for f in translation_opts.fields]:
                 table_alias = get_translation_table_alias(trans_table_name,
                     language_code)
                 field_alias = get_translated_field_alias(fname,
                     language_code)
                 extra_select[field_alias] = qn2(table_alias) + '.' + qn2(fname)
         self.add_extra(extra_select, None, None, None, None, None)
         self._trans_extra_select_count = len(self.extra_select)
示例#6
0
 def pre_sql_setup(self):
     """Adds the JOINS and SELECTS for fetching multilingual data.
     """
     super(MultilingualQuery, self).pre_sql_setup()
     opts = self.model._meta
     qn = self.quote_name_unless_alias
     qn2 = self.connection.ops.quote_name
     if hasattr(opts, 'translation_model'):
         master_table_name = opts.db_table
         translation_opts = opts.translation_model._meta
         trans_table_name = translation_opts.db_table
         for language_id in get_language_id_list():
             table_alias = get_translation_table_alias(trans_table_name,
                                                       language_id)
             trans_join = ('LEFT JOIN %s AS %s ON ((%s.master_id = %s.%s) AND (%s.language_id = %s))'
                        % (qn2(translation_opts.db_table),
                        qn2(table_alias),
                        qn2(table_alias),
                        qn(master_table_name),
                        qn2(self.model._meta.pk.column),
                        qn2(table_alias),
                        language_id))
             self.extra_join[table_alias] = trans_join
示例#7
0
 def __init__(self, model, connection, where=WhereNode):
     self.extra_join = {}
     self.include_translation_data = True
     extra_select = {}
     super(MultilingualQuery, self).__init__(model, connection, where=where)
     opts = self.model._meta
     qn = self.quote_name_unless_alias
     qn2 = self.connection.ops.quote_name
     master_table_name = opts.db_table
     translation_opts = opts.translation_model._meta
     trans_table_name = translation_opts.db_table
     if hasattr(opts, 'translation_model'):
         master_table_name = opts.db_table
         for language_id in get_language_id_list():
             for fname in [f.attname for f in translation_opts.fields]:
                 table_alias = get_translation_table_alias(
                     trans_table_name, language_id)
                 field_alias = get_translated_field_alias(
                     fname, language_id)
                 extra_select[field_alias] = qn2(table_alias) + '.' + qn2(
                     fname)
         self.add_extra(extra_select, None, None, None, None, None)
         self._trans_extra_select_count = len(self.extra_select)
示例#8
0
    def values_list(self, *fields, **kwargs):
        if hasattr(self.model._meta, 'translation_model'):
            extra_select = {}
            trans_opts = self.model._meta.translation_model._meta
            trans_table_name = trans_opts.db_table
            qn2 = self.query.connection.ops.quote_name

            for field_name in fields:
                field_and_lang = trans_opts.translated_fields.get(field_name)
                if field_and_lang:
                    field, language_id = field_and_lang
                    if language_id is None:
                        language_id = getattr(self, '_default_language', None)
                    table_alias = get_translation_table_alias(trans_table_name,
                        language_id)
                    extra_select[field_name] = qn2(table_alias) + '.' + qn2(field.attname)
            
            # this maps columns to required field_names
            result = self.extra(select = extra_select)
            # and it return MultilingualModelQuerySet instance, so we have to super it
            return super(MultilingualModelQuerySet, result).values_list(*fields, **kwargs)
        else:
            return super(MultilingualModelQuerySet, self).values_list(*fields, **kwargs) 
示例#9
0
    def _setup_joins_with_translation(self, names, opts, alias,
                                      dupe_multis, allow_many=True,
                                      allow_explicit_fk=False, can_reuse=None,
                                      negate=False, process_extras=True):
        """
        This is based on a full copy of Query.setup_joins because
        currently I see no way to handle it differently.

        TO DO: there might actually be a way, by splitting a single
        multi-name setup_joins call into separate calls.  Check it.

        -- [email protected]

        Compute the necessary table joins for the passage through the fields
        given in 'names'. 'opts' is the Options class for the current model
        (which gives the table we are joining to), 'alias' is the alias for the
        table we are joining to. If dupe_multis is True, any many-to-many or
        many-to-one joins will always create a new alias (necessary for
        disjunctive filters).

        Returns the final field involved in the join, the target database
        column (used for any 'where' constraint), the final 'opts' value and the
        list of tables joined.
        """
        joins = [alias]
        last = [0]
        dupe_set = set()
        exclusions = set()
        extra_filters = []
        for pos, name in enumerate(names):
            try:
                exclusions.add(int_alias)
            except NameError:
                pass
            exclusions.add(alias)
            last.append(len(joins))
            if name == 'pk':
                name = opts.pk.name
            try:
                field, model, direct, m2m = opts.get_field_by_name(name)
            except FieldDoesNotExist:
                for f in opts.fields:
                    if allow_explicit_fk and name == f.attname:
                        # XXX: A hack to allow foo_id to work in values() for
                        # backwards compatibility purposes. If we dropped that
                        # feature, this could be removed.
                        field, model, direct, m2m = opts.get_field_by_name(f.name)
                        break
                else:
                    names = opts.get_all_field_names() + self.aggregate_select.keys()
                    raise FieldError("Cannot resolve keyword %r into field. "
                            "Choices are: %s" % (name, ", ".join(names)))

            if not allow_many and (m2m or not direct):
                for alias in joins:
                    self.unref_alias(alias)
                raise MultiJoin(pos + 1)
            #===================================================================
            # Django Multilingual NG Specific Code START
            #===================================================================
            if hasattr(opts, 'translation_model'):
                translation_opts = opts.translation_model._meta
                if model == opts.translation_model:
                    language_code = translation_opts.translated_fields[name][1]
                    if language_code is None:
                        language_code = get_default_language()
                    #TODO: check alias
                    master_table_name = opts.db_table
                    trans_table_alias = get_translation_table_alias(
                        model._meta.db_table, language_code)
                    new_table = (master_table_name + "__" + trans_table_alias)
                    qn = self.get_compiler(DEFAULT_DB_ALIAS).quote_name_unless_alias
                    qn2 = self.get_compiler(DEFAULT_DB_ALIAS).connection.ops.quote_name
                    trans_join = ("JOIN %s AS %s ON ((%s.master_id = %s.%s) AND (%s.language_code = '%s'))"
                                 % (qn2(model._meta.db_table),
                                 qn2(new_table),
                                 qn2(new_table),
                                 qn(master_table_name),
                                 qn2(model._meta.pk.column),
                                 qn2(new_table),
                                 language_code))
                    self.extra_join[new_table] = trans_join
                    target = field
                    continue
            #===================================================================
            # Django Multilingual NG Specific Code END
            #===================================================================
            elif model:
                # The field lives on a base class of the current model.
                # Skip the chain of proxy to the concrete proxied model
                proxied_model = get_proxied_model(opts)

                for int_model in opts.get_base_chain(model):
                    if int_model is proxied_model:
                        opts = int_model._meta
                    else:
                        lhs_col = opts.parents[int_model].column
                        dedupe = lhs_col in opts.duplicate_targets
                        if dedupe:
                            exclusions.update(self.dupe_avoidance.get(
                                    (id(opts), lhs_col), ()))
                            dupe_set.add((opts, lhs_col))
                        opts = int_model._meta
                        alias = self.join((alias, opts.db_table, lhs_col,
                                opts.pk.column), exclusions=exclusions)
                        joins.append(alias)
                        exclusions.add(alias)
                        for (dupe_opts, dupe_col) in dupe_set:
                            self.update_dupe_avoidance(dupe_opts, dupe_col,
                                    alias)
            cached_data = opts._join_cache.get(name)
            orig_opts = opts
            dupe_col = direct and field.column or field.field.column
            dedupe = dupe_col in opts.duplicate_targets
            if dupe_set or dedupe:
                if dedupe:
                    dupe_set.add((opts, dupe_col))
                exclusions.update(self.dupe_avoidance.get((id(opts), dupe_col),
                        ()))

            if process_extras and hasattr(field, 'extra_filters'):
                extra_filters.extend(field.extra_filters(names, pos, negate))
            if direct:
                if m2m:
                    # Many-to-many field defined on the current model.
                    if cached_data:
                        (table1, from_col1, to_col1, table2, from_col2,
                                to_col2, opts, target) = cached_data
                    else:
                        table1 = field.m2m_db_table()
                        from_col1 = opts.pk.column
                        to_col1 = field.m2m_column_name()
                        opts = field.rel.to._meta
                        table2 = opts.db_table
                        from_col2 = field.m2m_reverse_name()
                        to_col2 = opts.pk.column
                        target = opts.pk
                        orig_opts._join_cache[name] = (table1, from_col1,
                                to_col1, table2, from_col2, to_col2, opts,
                                target)

                    int_alias = self.join((alias, table1, from_col1, to_col1),
                            dupe_multis, exclusions, nullable=True,
                            reuse=can_reuse)
                    if int_alias == table2 and from_col2 == to_col2:
                        joins.append(int_alias)
                        alias = int_alias
                    else:
                        alias = self.join(
                                (int_alias, table2, from_col2, to_col2),
                                dupe_multis, exclusions, nullable=True,
                                reuse=can_reuse)
                        joins.extend([int_alias, alias])
                elif field.rel:
                    # One-to-one or many-to-one field
                    if cached_data:
                        (table, from_col, to_col, opts, target) = cached_data
                    else:
                        opts = field.rel.to._meta
                        target = field.rel.get_related_field()
                        table = opts.db_table
                        from_col = field.column
                        to_col = target.column
                        orig_opts._join_cache[name] = (table, from_col, to_col,
                                opts, target)

                    alias = self.join((alias, table, from_col, to_col),
                            exclusions=exclusions, nullable=field.null)
                    joins.append(alias)
                else:
                    # Non-relation fields.
                    target = field
                    break
            else:
                orig_field = field
                field = field.field
                if m2m:
                    # Many-to-many field defined on the target model.
                    if cached_data:
                        (table1, from_col1, to_col1, table2, from_col2,
                                to_col2, opts, target) = cached_data
                    else:
                        table1 = field.m2m_db_table()
                        from_col1 = opts.pk.column
                        to_col1 = field.m2m_reverse_name()
                        opts = orig_field.opts
                        table2 = opts.db_table
                        from_col2 = field.m2m_column_name()
                        to_col2 = opts.pk.column
                        target = opts.pk
                        orig_opts._join_cache[name] = (table1, from_col1,
                                to_col1, table2, from_col2, to_col2, opts,
                                target)

                    int_alias = self.join((alias, table1, from_col1, to_col1),
                            dupe_multis, exclusions, nullable=True,
                            reuse=can_reuse)
                    alias = self.join((int_alias, table2, from_col2, to_col2),
                            dupe_multis, exclusions, nullable=True,
                            reuse=can_reuse)
                    joins.extend([int_alias, alias])
                else:
                    # One-to-many field (ForeignKey defined on the target model)
                    if cached_data:
                        (table, from_col, to_col, opts, target) = cached_data
                    else:
                        local_field = opts.get_field_by_name(
                                field.rel.field_name)[0]
                        opts = orig_field.opts
                        table = opts.db_table
                        from_col = local_field.column
                        to_col = field.column
                        target = opts.pk
                        orig_opts._join_cache[name] = (table, from_col, to_col,
                                opts, target)

                    alias = self.join((alias, table, from_col, to_col),
                            dupe_multis, exclusions, nullable=True,
                            reuse=can_reuse)
                    joins.append(alias)

            for (dupe_opts, dupe_col) in dupe_set:
                try:
                    self.update_dupe_avoidance(dupe_opts, dupe_col, int_alias)
                except NameError:
                    self.update_dupe_avoidance(dupe_opts, dupe_col, alias)

        if pos != len(names) - 1:
            if pos == len(names) - 2:
                raise FieldError("Join on field %r not permitted. Did you misspell %r for the lookup type?" % (name, names[pos + 1]))
            else:
                raise FieldError("Join on field %r not permitted." % name)

        return field, target, opts, joins, last, extra_filters
示例#10
0
class MultilingualQuery(Query):
    def __init__(self, model, connection, where=WhereNode):
        self.extra_join = {}
        self.include_translation_data = True
        extra_select = {}
        super(MultilingualQuery, self).__init__(model, connection, where=where)
        opts = self.model._meta
        qn = self.quote_name_unless_alias
        qn2 = self.connection.ops.quote_name
        master_table_name = opts.db_table
        translation_opts = opts.translation_model._meta
        trans_table_name = translation_opts.db_table
        if hasattr(opts, 'translation_model'):
            master_table_name = opts.db_table
            for language_id in get_language_id_list():
                for fname in [f.attname for f in translation_opts.fields]:
                    table_alias = get_translation_table_alias(
                        trans_table_name, language_id)
                    field_alias = get_translated_field_alias(
                        fname, language_id)
                    extra_select[field_alias] = qn2(table_alias) + '.' + qn2(
                        fname)
            self.add_extra(extra_select, None, None, None, None, None)
            self._trans_extra_select_count = len(self.extra_select)

    def clone(self, klass=None, **kwargs):
        defaults = {
            'extra_join': self.extra_join,
            'include_translation_data': self.include_translation_data,
        }
        defaults.update(kwargs)
        return super(MultilingualQuery, self).clone(klass=klass, **defaults)

    def pre_sql_setup(self):
        """Adds the JOINS and SELECTS for fetching multilingual data.
        """
        super(MultilingualQuery, self).pre_sql_setup()

        if not self.include_translation_data:
            return

        opts = self.model._meta
        qn = self.quote_name_unless_alias
        qn2 = self.connection.ops.quote_name
        if hasattr(opts, 'translation_model'):
            master_table_name = opts.db_table
            translation_opts = opts.translation_model._meta
            trans_table_name = translation_opts.db_table
            for language_id in get_language_id_list():
                table_alias = get_translation_table_alias(
                    trans_table_name, language_id)
                trans_join = (
                    'LEFT JOIN %s AS %s ON ((%s.master_id = %s.%s) AND (%s.language_id = %s))'
                    % (qn2(translation_opts.db_table), qn2(table_alias),
                       qn2(table_alias), qn(master_table_name),
                       qn2(self.model._meta.pk.column), qn2(table_alias),
                       language_id))
                self.extra_join[table_alias] = trans_join

    def get_from_clause(self):
        """Add the JOINS for related multilingual fields filtering.
        """
        result = super(MultilingualQuery, self).get_from_clause()

        if not self.include_translation_data:
            return result

        from_ = result[0]
        for join in self.extra_join.values():
            from_.append(join)
        return (from_, result[1])

    def add_filter(self,
                   filter_expr,
                   connector=AND,
                   negate=False,
                   trim=False,
                   can_reuse=None,
                   process_extras=True):
        """Copied from add_filter to generate WHERES for translation fields.
        """
        arg, value = filter_expr
        parts = arg.split(LOOKUP_SEP)
        if not parts:
            raise FieldError("Cannot parse keyword query %r" % arg)

        # Work out the lookup type and remove it from 'parts', if necessary.
        if len(parts) == 1 or parts[-1] not in self.query_terms:
            lookup_type = 'exact'
        else:
            lookup_type = parts.pop()

        # Interpret '__exact=None' as the sql 'is NULL'; otherwise, reject all
        # uses of None as a query value.
        if value is None:
            if lookup_type != 'exact':
                raise ValueError("Cannot use None as a query value")
            lookup_type = 'isnull'
            value = True
        elif (value == '' and lookup_type == 'exact'
              and connection.features.interprets_empty_strings_as_nulls):
            lookup_type = 'isnull'
            value = True
        elif callable(value):
            value = value()

        opts = self.get_meta()
        alias = self.get_initial_alias()
        allow_many = trim or not negate

        try:
            field, target, opts, join_list, last, extra_filters = self.setup_joins(
                parts,
                opts,
                alias,
                True,
                allow_many,
                can_reuse=can_reuse,
                negate=negate,
                process_extras=process_extras)
        except MultiJoin, e:
            self.split_exclude(filter_expr, LOOKUP_SEP.join(parts[:e.level]),
                               can_reuse)
            return

        #NOTE: here comes Django Multilingual
        if hasattr(opts, 'translation_model'):
            field_name = parts[-1]
            if field_name == 'pk':
                field_name = opts.pk.name
            translation_opts = opts.translation_model._meta
            if field_name in translation_opts.translated_fields.keys():
                field, model, direct, m2m = opts.get_field_by_name(field_name)
                if model == opts.translation_model:
                    language_id = translation_opts.translated_fields[
                        field_name][1]
                    if language_id is None:
                        language_id = get_default_language()
                    master_table_name = opts.db_table
                    trans_table_alias = get_translation_table_alias(
                        model._meta.db_table, language_id)
                    new_table = (master_table_name + "__" + trans_table_alias)
                    self.where.add(
                        constraint_tuple(new_table, field.column, field,
                                         lookup_type, value), connector)
                    return

        final = len(join_list)
        penultimate = last.pop()
        if penultimate == final:
            penultimate = last.pop()
        if trim and len(join_list) > 1:
            extra = join_list[penultimate:]
            join_list = join_list[:penultimate]
            final = penultimate
            penultimate = last.pop()
            col = self.alias_map[extra[0]][LHS_JOIN_COL]
            for alias in extra:
                self.unref_alias(alias)
        else:
            col = target.column
        alias = join_list[-1]

        while final > 1:
            # An optimization: if the final join is against the same column as
            # we are comparing against, we can go back one step in the join
            # chain and compare against the lhs of the join instead (and then
            # repeat the optimization). The result, potentially, involves less
            # table joins.
            join = self.alias_map[alias]
            if col != join[RHS_JOIN_COL]:
                break
            self.unref_alias(alias)
            alias = join[LHS_ALIAS]
            col = join[LHS_JOIN_COL]
            join_list = join_list[:-1]
            final -= 1
            if final == penultimate:
                penultimate = last.pop()

        if (lookup_type == 'isnull' and value is True and not negate
                and final > 1):
            # If the comparison is against NULL, we need to use a left outer
            # join when connecting to the previous model. We make that
            # adjustment here. We don't do this unless needed as it's less
            # efficient at the database level.
            self.promote_alias(join_list[penultimate])

        if connector == OR:
            # Some joins may need to be promoted when adding a new filter to a
            # disjunction. We walk the list of new joins and where it diverges
            # from any previous joins (ref count is 1 in the table list), we
            # make the new additions (and any existing ones not used in the new
            # join list) an outer join.
            join_it = iter(join_list)
            table_it = iter(self.tables)
            join_it.next(), table_it.next()
            table_promote = False
            join_promote = False
            for join in join_it:
                table = table_it.next()
                if join == table and self.alias_refcount[join] > 1:
                    continue
                join_promote = self.promote_alias(join)
                if table != join:
                    table_promote = self.promote_alias(table)
                break
            self.promote_alias_chain(join_it, join_promote)
            self.promote_alias_chain(table_it, table_promote)

        self.where.add(constraint_tuple(alias, col, field, lookup_type, value),
                       connector)

        if negate:
            self.promote_alias_chain(join_list)
            if lookup_type != 'isnull':
                if final > 1:
                    for alias in join_list:
                        if self.alias_map[alias][JOIN_TYPE] == self.LOUTER:
                            j_col = self.alias_map[alias][RHS_JOIN_COL]
                            entry = self.where_class()
                            entry.add(
                                constraint_tuple(alias, j_col, None, 'isnull',
                                                 True), AND)
                            entry.negate()
                            self.where.add(entry, AND)
                            break
                elif not (lookup_type == 'in' and not value) and field.null:
                    # Leaky abstraction artifact: We have to specifically
                    # exclude the "foo__in=[]" case from this handling, because
                    # it's short-circuited in the Where class.
                    entry = self.where_class()
                    entry.add(
                        constraint_tuple(alias, col, None, 'isnull', True),
                        AND)
                    entry.negate()
                    self.where.add(entry, AND)

        if can_reuse is not None:
            can_reuse.update(join_list)
        if process_extras:
            for filter in extra_filters:
                self.add_filter(filter,
                                negate=negate,
                                can_reuse=can_reuse,
                                process_extras=False)
示例#11
0
    def _setup_joins_with_translation(self,
                                      names,
                                      opts,
                                      alias,
                                      dupe_multis,
                                      allow_many=True,
                                      allow_explicit_fk=False,
                                      can_reuse=None,
                                      negate=False,
                                      process_extras=True):
        """
        This is based on a full copy of Query.setup_joins because
        currently I see no way to handle it differently.

        TO DO: there might actually be a way, by splitting a single
        multi-name setup_joins call into separate calls.  Check it.

        -- [email protected]

        Compute the necessary table joins for the passage through the fields
        given in 'names'. 'opts' is the Options class for the current model
        (which gives the table we are joining to), 'alias' is the alias for the
        table we are joining to. If dupe_multis is True, any many-to-many or
        many-to-one joins will always create a new alias (necessary for
        disjunctive filters).

        Returns the final field involved in the join, the target database
        column (used for any 'where' constraint), the final 'opts' value and the
        list of tables joined.
        """
        joins = [alias]
        last = [0]
        dupe_set = set()
        exclusions = set()
        extra_filters = []
        for pos, name in enumerate(names):
            try:
                exclusions.add(int_alias)
            except NameError:
                pass
            exclusions.add(alias)
            last.append(len(joins))
            if name == 'pk':
                name = opts.pk.name

            try:
                field, model, direct, m2m = opts.get_field_by_name(name)
            except FieldDoesNotExist:
                for f in opts.fields:
                    if allow_explicit_fk and name == f.attname:
                        # XXX: A hack to allow foo_id to work in values() for
                        # backwards compatibility purposes. If we dropped that
                        # feature, this could be removed.
                        field, model, direct, m2m = opts.get_field_by_name(
                            f.name)
                        break
                else:
                    names = opts.get_all_field_names()
                    raise FieldError("Cannot resolve keyword %r into field. "
                                     "Choices are: %s" %
                                     (name, ", ".join(names)))

            if not allow_many and (m2m or not direct):
                for alias in joins:
                    self.unref_alias(alias)
                raise MultiJoin(pos + 1)

            #NOTE: Start Django Multilingual specific code
            if hasattr(opts, 'translation_model'):
                translation_opts = opts.translation_model._meta
                if model == opts.translation_model:
                    language_id = translation_opts.translated_fields[name][1]
                    if language_id is None:
                        language_id = get_default_language()
                    #TODO: check alias
                    master_table_name = opts.db_table
                    trans_table_alias = get_translation_table_alias(
                        model._meta.db_table, language_id)
                    new_table = (master_table_name + "__" + trans_table_alias)
                    qn = self.quote_name_unless_alias
                    qn2 = self.connection.ops.quote_name
                    trans_join = (
                        'LEFT JOIN %s AS %s ON ((%s.master_id = %s.%s) AND (%s.language_id = %s))'
                        % (qn2(model._meta.db_table), qn2(new_table),
                           qn2(new_table), qn(master_table_name),
                           qn2(model._meta.pk.column), qn2(new_table),
                           language_id))
                    self.extra_join[new_table] = trans_join
                    target = field
                    continue
                    #NOTE: End Django Multilingual specific code
            elif model:
                # The field lives on a base class of the current model.
                for int_model in opts.get_base_chain(model):
                    lhs_col = opts.parents[int_model].column
                    dedupe = lhs_col in opts.duplicate_targets
                    if dedupe:
                        exclusions.update(
                            self.dupe_avoidance.get((id(opts), lhs_col), ()))
                        dupe_set.add((opts, lhs_col))
                    opts = int_model._meta
                    alias = self.join(
                        (alias, opts.db_table, lhs_col, opts.pk.column),
                        exclusions=exclusions)
                    joins.append(alias)
                    exclusions.add(alias)
                    for (dupe_opts, dupe_col) in dupe_set:
                        self.update_dupe_avoidance(dupe_opts, dupe_col, alias)
            cached_data = opts._join_cache.get(name)
            orig_opts = opts
            dupe_col = direct and field.column or field.field.column
            dedupe = dupe_col in opts.duplicate_targets
            if dupe_set or dedupe:
                if dedupe:
                    dupe_set.add((opts, dupe_col))
                exclusions.update(
                    self.dupe_avoidance.get((id(opts), dupe_col), ()))

            if process_extras and hasattr(field, 'extra_filters'):
                extra_filters.extend(field.extra_filters(names, pos, negate))
            if direct:
                if m2m:
                    # Many-to-many field defined on the current model.
                    if cached_data:
                        (table1, from_col1, to_col1, table2, from_col2,
                         to_col2, opts, target) = cached_data
                    else:
                        table1 = field.m2m_db_table()
                        from_col1 = opts.pk.column
                        to_col1 = field.m2m_column_name()
                        opts = field.rel.to._meta
                        table2 = opts.db_table
                        from_col2 = field.m2m_reverse_name()
                        to_col2 = opts.pk.column
                        target = opts.pk
                        orig_opts._join_cache[name] = (table1, from_col1,
                                                       to_col1, table2,
                                                       from_col2, to_col2,
                                                       opts, target)

                    int_alias = self.join((alias, table1, from_col1, to_col1),
                                          dupe_multis,
                                          exclusions,
                                          nullable=True,
                                          reuse=can_reuse)
                    if int_alias == table2 and from_col2 == to_col2:
                        joins.append(int_alias)
                        alias = int_alias
                    else:
                        alias = self.join(
                            (int_alias, table2, from_col2, to_col2),
                            dupe_multis,
                            exclusions,
                            nullable=True,
                            reuse=can_reuse)
                        joins.extend([int_alias, alias])
                elif field.rel:
                    # One-to-one or many-to-one field
                    if cached_data:
                        (table, from_col, to_col, opts, target) = cached_data
                    else:
                        opts = field.rel.to._meta
                        target = field.rel.get_related_field()
                        table = opts.db_table
                        from_col = field.column
                        to_col = target.column
                        orig_opts._join_cache[name] = (table, from_col, to_col,
                                                       opts, target)

                    alias = self.join((alias, table, from_col, to_col),
                                      exclusions=exclusions,
                                      nullable=field.null)
                    joins.append(alias)
                else:
                    # Non-relation fields.
                    target = field
                    break
            else:
                orig_field = field
                field = field.field
                if m2m:
                    # Many-to-many field defined on the target model.
                    if cached_data:
                        (table1, from_col1, to_col1, table2, from_col2,
                         to_col2, opts, target) = cached_data
                    else:
                        table1 = field.m2m_db_table()
                        from_col1 = opts.pk.column
                        to_col1 = field.m2m_reverse_name()
                        opts = orig_field.opts
                        table2 = opts.db_table
                        from_col2 = field.m2m_column_name()
                        to_col2 = opts.pk.column
                        target = opts.pk
                        orig_opts._join_cache[name] = (table1, from_col1,
                                                       to_col1, table2,
                                                       from_col2, to_col2,
                                                       opts, target)

                    int_alias = self.join((alias, table1, from_col1, to_col1),
                                          dupe_multis,
                                          exclusions,
                                          nullable=True,
                                          reuse=can_reuse)
                    alias = self.join((int_alias, table2, from_col2, to_col2),
                                      dupe_multis,
                                      exclusions,
                                      nullable=True,
                                      reuse=can_reuse)
                    joins.extend([int_alias, alias])
                else:
                    # One-to-many field (ForeignKey defined on the target model)
                    if cached_data:
                        (table, from_col, to_col, opts, target) = cached_data
                    else:
                        local_field = opts.get_field_by_name(
                            field.rel.field_name)[0]
                        opts = orig_field.opts
                        table = opts.db_table
                        from_col = local_field.column
                        to_col = field.column
                        target = opts.pk
                        orig_opts._join_cache[name] = (table, from_col, to_col,
                                                       opts, target)

                    alias = self.join((alias, table, from_col, to_col),
                                      dupe_multis,
                                      exclusions,
                                      nullable=True,
                                      reuse=can_reuse)
                    joins.append(alias)

            for (dupe_opts, dupe_col) in dupe_set:
                try:
                    self.update_dupe_avoidance(dupe_opts, dupe_col, int_alias)
                except NameError:
                    self.update_dupe_avoidance(dupe_opts, dupe_col, alias)

        if pos != len(names) - 1:
            raise FieldError("Join on field %r not permitted." % name)

        return field, target, opts, joins, last, extra_filters
示例#12
0
    def add_filter(self, filter_expr, connector=AND, negate=False, trim=False,
            can_reuse=None, process_extras=True, force_having=False):
        """
        Copied from add_filter to generate WHERES for translation fields.
        """
        arg, value = filter_expr
        parts = arg.split(LOOKUP_SEP)
        if not parts:
            raise FieldError("Cannot parse keyword query %r" % arg)

        # Work out the lookup type and remove it from the end of 'parts',
        # if necessary.
        lookup_type = 'exact' # Default lookup type
        num_parts = len(parts)
        if (len(parts) > 1 and parts[-1] in self.query_terms
            and arg not in self.aggregates):
            # Traverse the lookup query to distinguish related fields from
            # lookup types.
            lookup_model = self.model
            for counter, field_name in enumerate(parts):
                try:
                    lookup_field = lookup_model._meta.get_field(field_name)
                except FieldDoesNotExist:
                    # Not a field. Bail out.
                    lookup_type = parts.pop()
                    break
                # Unless we're at the end of the list of lookups, let's attempt
                # to continue traversing relations.
                if (counter + 1) < num_parts:
                    try:
                        lookup_model = lookup_field.rel.to
                    except AttributeError:
                        # Not a related field. Bail out.
                        lookup_type = parts.pop()
                        break

        # By default, this is a WHERE clause. If an aggregate is referenced
        # in the value, the filter will be promoted to a HAVING
        having_clause = False

        # Interpret '__exact=None' as the sql 'is NULL'; otherwise, reject all
        # uses of None as a query value.
        if value is None:
            if lookup_type != 'exact':
                raise ValueError("Cannot use None as a query value")
            lookup_type = 'isnull'
            value = True
        elif callable(value):
            value = value()
        elif isinstance(value, ExpressionNode):
            # If value is a query expression, evaluate it
            value = SQLEvaluator(value, self, reuse=can_reuse)
            having_clause = value.contains_aggregate
        # For Oracle '' is equivalent to null. The check needs to be done
        # at this stage because join promotion can't be done at compiler
        # stage. Using DEFAULT_DB_ALIAS isn't nice, but it is the best we
        # can do here. Similar thing is done in is_nullable(), too.
        if (connections[DEFAULT_DB_ALIAS].features.interprets_empty_strings_as_nulls and
                lookup_type == 'exact' and value == ''):
            value = True
            lookup_type = 'isnull'
        for alias, aggregate in self.aggregates.items():
            if alias in (parts[0], LOOKUP_SEP.join(parts)):
                entry = self.where_class()
                entry.add((aggregate, lookup_type, value), AND)
                if negate:
                    entry.negate()
                self.having.add(entry, connector)
                return

        opts = self.get_meta()
        alias = self.get_initial_alias()
        allow_many = trim or not negate

        try:
            field, target, opts, join_list, last, extra_filters = self.setup_joins(
                    parts, opts, alias, True, allow_many, allow_explicit_fk=True,
                    can_reuse=can_reuse, negate=negate,
                    process_extras=process_extras)
        except MultiJoin as e:
            self.split_exclude(filter_expr, LOOKUP_SEP.join(parts[:e.level]),
                    can_reuse)
            return

        table_promote = False
        join_promote = False
            
        #=======================================================================
        # Django Mulitlingual NG Specific Code START
        #=======================================================================
            
        if hasattr(opts, 'translation_model'):
            field_name = parts[-1]
            if field_name == 'pk':
                field_name = opts.pk.name
            translation_opts = opts.translation_model._meta
            if field_name in translation_opts.translated_fields.keys():
                field, model, direct, m2m = opts.get_field_by_name(field_name)
                if model == opts.translation_model:
                    language_code = translation_opts.translated_fields[field_name][1]
                    if language_code is None:
                        language_code = get_default_language()
                    master_table_name = opts.db_table
                    trans_table_alias = get_translation_table_alias(
                        model._meta.db_table, language_code)
                    new_table = (master_table_name + "__" + trans_table_alias)
                    self.where.add(constraint_tuple(new_table, field.column, field, lookup_type, value), connector)
                    return
                    
        #=======================================================================
        # Django Mulitlingual NG Specific Code END
        #=======================================================================

        if (lookup_type == 'isnull' and value is True and not negate and
                len(join_list) > 1):
            # If the comparison is against NULL, we may need to use some left
            # outer joins when creating the join chain. This is only done when
            # needed, as it's less efficient at the database level.
            self.promote_joins(join_list)
            join_promote = True

        # Process the join list to see if we can remove any inner joins from
        # the far end (fewer tables in a query is better).
        nonnull_comparison = (lookup_type == 'isnull' and value is False)
        col, alias, join_list = self.trim_joins(target, join_list, last, trim,
                nonnull_comparison)

        if connector == OR:
            # Some joins may need to be promoted when adding a new filter to a
            # disjunction. We walk the list of new joins and where it diverges
            # from any previous joins (ref count is 1 in the table list), we
            # make the new additions (and any existing ones not used in the new
            # join list) an outer join.
            join_it = iter(join_list)
            table_it = iter(self.tables)
            next(join_it), next(table_it)
            unconditional = False
            for join in join_it:
                table = next(table_it)
                # Once we hit an outer join, all subsequent joins must
                # also be promoted, regardless of whether they have been
                # promoted as a result of this pass through the tables.
                unconditional = (unconditional or
                    self.alias_map[join].join_type == self.LOUTER)
                if join == table and self.alias_refcount[join] > 1:
                    # We have more than one reference to this join table.
                    # This means that we are dealing with two different query
                    # subtrees, so we don't need to do any join promotion.
                    continue
                join_promote = join_promote or self.promote_joins([join], unconditional)
                if table != join:
                    table_promote = self.promote_joins([table])
                # We only get here if we have found a table that exists
                # in the join list, but isn't on the original tables list.
                # This means we've reached the point where we only have
                # new tables, so we can break out of this promotion loop.
                break
            self.promote_joins(join_it, join_promote)
            self.promote_joins(table_it, table_promote or join_promote)

        if having_clause or force_having:
            if (alias, col) not in self.group_by:
                self.group_by.append((alias, col))
            self.having.add((Constraint(alias, col, field), lookup_type, value),
                connector)
        else:
            self.where.add((Constraint(alias, col, field), lookup_type, value),
                connector)

        if negate:
            self.promote_joins(join_list)
            if lookup_type != 'isnull':
                if len(join_list) > 1:
                    for j_alias in join_list:
                        if self.alias_map[j_alias].join_type == self.LOUTER:
                            j_col = self.alias_map[j_alias].rhs_join_col
                            # The join promotion logic should never produce
                            # a LOUTER join for the base join - assert that.
                            assert j_col is not None
                            entry = self.where_class()
                            entry.add(
                                (Constraint(j_alias, j_col, None), 'isnull', True),
                                AND
                            )
                            entry.negate()
                            self.where.add(entry, AND)
                            break
                if self.is_nullable(field):
                    # In SQL NULL = anyvalue returns unknown, and NOT unknown
                    # is still unknown. However, in Python None = anyvalue is False
                    # (and not False is True...), and we want to return this Python's
                    # view of None handling. So we need to specifically exclude the
                    # NULL values, and because we are inside NOT branch they will
                    # be included in the final resultset. We are essentially creating
                    # SQL like this here: NOT (col IS NOT NULL), where the first NOT
                    # is added in upper layers of the code.
                    self.where.add((Constraint(alias, col, None), 'isnull', False), AND)

        if can_reuse is not None:
            can_reuse.update(join_list)
        if process_extras:
            for filter in extra_filters:
                self.add_filter(filter, negate=negate, can_reuse=can_reuse,
                        process_extras=False)