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
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)
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 __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)
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)
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
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 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)
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
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)
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
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)