def get_joins(self): """Returns a BaseTable and a list of Join objects that need to be added to a QuerySet object that properly joins this model and the parent. """ # These arguments should match the spec of the Join object. # See https://github.com/django/django/blob/1.8.7/django/db/models/sql/query.py#L896 # noqa join_args = { 'table_name': None, 'parent_alias': None, 'table_alias': None, 'join_type': None, 'join_field': None, 'nullable': self.nullable, } joins = [] related_field = self.parent_model._meta.get_field(self.related_name) # Setup two connections for m2m. if self.relation == 'manytomany': path = related_field.get_path_info() copy1 = join_args.copy() copy1.update({ 'join_field': path[0].join_field, 'parent_alias': self.parent.db_table, 'table_name': self.m2m_db_table, 'join_type': LOUTER, }) copy2 = join_args.copy() copy2.update({ 'join_field': path[1].join_field, 'parent_alias': self.m2m_db_table, 'table_name': self.db_table, 'join_type': LOUTER, }) joins.append(Join(**copy1)) joins.append(Join(**copy2)) else: copy = join_args.copy() copy.update({ 'table_name': self.db_table, 'parent_alias': self.parent.db_table, 'join_field': related_field, 'join_type': LOUTER if self.nullable else INNER, }) joins.append(Join(**copy)) return BaseTable(self.parent.db_table, alias=None), joins
def order_by_translation(qs, fieldname, model=None): """ Order the QuerySet by the translated field, honoring the current and fallback locales. Returns a new QuerySet. The model being sorted needs a get_fallback() classmethod that describes the fallback locale. get_fallback() can return a string or a Field. """ if fieldname.startswith('-'): desc = True fieldname = fieldname[1:] else: desc = False qs = qs.all() model = model or qs.model field = model._meta.get_field(fieldname) # Doing the manual joins is flying under Django's radar, so we need to make # sure the initial alias (the main table) is set up. if not qs.query.table_map: qs.query.get_initial_alias() # Force two new joins against the translation table, without reusing any # aliases. We'll hook up the language fallbacks later. # Passing `reuse=set()` force new joins, and passing `nullable=True` # forces django to make LEFT OUTER JOINs (otherwise django, because we are # building the query manually, does not detect that an inner join would # remove results and happily simplifies the LEFT OUTER JOINs to # INNER JOINs) qs.query = qs.query.clone() qs.query.__class__ = TranslationQuery qs.query.fallback_model = model t1 = qs.query.join(Join(field.remote_field.model._meta.db_table, model._meta.db_table, None, LOUTER, field, True), reuse=set()) t2 = qs.query.join(Join(field.remote_field.model._meta.db_table, model._meta.db_table, None, LOUTER, field, True), reuse=set()) qs.query.translation_aliases = {field: (t1, t2)} f1, f2 = '%s.`localized_string`' % t1, '%s.`localized_string`' % t2 name = 'translated_%s' % field.column ifnull = 'IFNULL(%s, %s)' % (f1, f2) prefix = '-' if desc else '' return qs.extra(select={name: ifnull}, where=['(%s IS NOT NULL OR %s IS NOT NULL)' % (f1, f2)], order_by=[prefix + name])
def join_to_table( table, table2, table_field, table2_field, queryset, alias, extra_restriction_func=lambda where_class, alias, related_alias: None): """ Add a join on `table2` to `queryset` (having table `table`). """ foreign_object = ForeignObject(to=table2, from_fields=[None], to_fields=[None], rel=None) foreign_object.opts = Options(table._meta) foreign_object.opts.model = table foreign_object.get_joining_columns = lambda: ( (table_field, table2_field), ) foreign_object.get_extra_restriction = extra_restriction_func join = Join(table2._meta.db_table, table._meta.db_table, alias, LOUTER, foreign_object, True) queryset.query.join(join) # hook for set alias join.table_alias = alias queryset.query.external_aliases.add(alias) return queryset
def unique_together_left_join(queryset, model, link_field_name, filter_field_name, filter_value, parent_model=None): link_field = copy(model._meta.get_field(link_field_name).rel) filter_field = model._meta.get_field(filter_field_name) def restrictions(where_class, alias, related_alias): cond = where_class() cond.add( filter_field.get_lookup('exact')(filter_field.get_col(alias), filter_value), 'AND') return cond link_field.get_extra_restriction = restrictions if parent_model is not None: parent_alias = parent_model._meta.db_table else: parent_alias = queryset.query.get_initial_alias() return queryset.query.join( Join(model._meta.db_table, parent_alias, None, LOUTER, link_field, True))
def __init__(self, model, trigger_alias, where=TriggerWhereNode): super(TriggerFilterQuery, self).__init__(model, where) self.trigger_alias = trigger_alias try: class JoinField(): def get_joining_columns(self): return None join = Join(None, None, None, None, JoinField(), False) except: join = JoinInfo(None, None, None, None, ((None, None), ), False, None) self.alias_map = {trigger_alias: join}
def _add_language_filter(self): if self._language_filter_tag: # pragma: no cover raise RuntimeError( 'Queryset is already tagged. This is a bug in hvad') self._language_filter_tag = True self.query.language_code = self._language_code if self._language_code == 'all': self._add_select_related() elif self._language_fallbacks: if self._raw_select_related: raise NotImplementedError('Using select_related along with ' 'fallbacks() is not supported') languages = tuple(get_language() if lang is None else lang for lang in (self._language_code, ) + self._language_fallbacks) masteratt = self.model._meta.get_field('master').attname alias = self.query.join( Join(self.model._meta.db_table, self.query.get_initial_alias(), None, LOUTER, BetterTranslationsField(languages, master=masteratt), True)) add_alias_constraints(self, (self.model, alias), id__isnull=True) self.query.add_filter(('%s__isnull' % masteratt, False)) if not self._skip_master_select and getattr(self, '_fields', None) is None: self.query.add_select_related(('master', )) else: language_code = self._language_code or get_language() self.query.add_filter(('language_code', language_code)) self._add_select_related() # if queryset is about to use the model's default ordering, we # override that now with a translated version of the model's ordering if self.query.default_ordering and not self.query.order_by: ordering = self.shared_model._meta.ordering self.query.order_by = self._translate_fieldnames(ordering or []) return self
def add_where(self, queryset, host_table, included_table): alias = None nullable = True join = Join(self.through_model._meta.db_table, included_table, None, LOUTER, self.to_field, nullable) alias, _ = queryset.query.table_alias(join.table_name, create=True) join.table_alias = alias queryset.query.alias_map[alias] = join where = [ '{through_table}."{from_column}" = {host_table}."{host_column}"'. format(through_table=join.table_alias, from_column=self.from_field.field.column, host_table=host_table, host_column=self.from_field.target_field.column) ] queryset.query.add_extra(None, None, where, None, None, None) return queryset
def get(self, request, *args, **kwargs): class JoinCols: @staticmethod def get_joining_columns(): return (('plan_id', 'plan_id'),) @staticmethod def get_extra_restriction(where_class, alias, related_alias): return None cart_items = self.cart_items.filter(option=0) cart_items.query.join(Join( AdvanceDiscount._meta.db_table, # table_name CartItem._meta.db_table, # parent_alias None, # table_alias INNER, # join_type JoinCols(), # join_field False, # nullable )) if cart_items.exists(): # If option == 0 and there is a discount to buy periods # in advance, we will present multiple options to the user. return http.HttpResponseRedirect( reverse('saas_cart_periods', args=(self.organization,))) return super(CartSeatsView, self).get(request, *args, **kwargs)
def join(self, qs=None): ''' Join one queryset together with another using a temporary table. If no queryset is used, it will use the current queryset and join that to itself. `Join` either uses the current queryset and effectively does a self-join to create a new limited queryset OR it uses a querset given by the user. The model of a given queryset needs to contain a valid foreign key to the current queryset to perform a join. A new queryset is then created. ''' to_field = 'id' if qs: fk = [ fk for fk in qs.model._meta.fields if getattr(fk, 'related_model', None) == self.model ] fk = fk[0] if fk else None model_set = '{}_set'.format(self.model.__name__.lower()) key = fk or getattr(qs.model, model_set, None) if not key: raise ValueError('QuerySet is not related to current model') try: fk_column = key.column except AttributeError: fk_column = 'id' to_field = key.field.column qs = qs.only(fk_column) # if we give a qs we need to keep the model qs to not lose anything new_qs = self else: fk_column = 'id' qs = self.only(fk_column) new_qs = self.model.objects.all() TABLE_NAME = 'temp_stuff' query = self.get_quoted_query(qs.query) sql = ''' DROP TABLE IF EXISTS {table_name}; DROP INDEX IF EXISTS {table_name}_id; CREATE TEMPORARY TABLE {table_name} AS {query}; CREATE INDEX {table_name}_{fk_column} ON {table_name} ({fk_column}); '''.format(table_name=TABLE_NAME, fk_column=fk_column, query=str(query)) with connection.cursor() as cursor: cursor.execute(sql) class TempModel(models.Model): temp_key = models.ForeignKey( self.model, on_delete=models.DO_NOTHING, db_column=fk_column, to_field=to_field ) class Meta: managed = False db_table = TABLE_NAME conn = Join( table_name=TempModel._meta.db_table, parent_alias=new_qs.query.get_initial_alias(), table_alias=None, join_type='INNER JOIN', join_field=self.model.tempmodel_set.rel, nullable=False ) new_qs.query.join(conn, reuse=None) return new_qs