def get_by_model(self, queryset_or_model, tags): """ Create a ``QuerySet`` containing instances of the specified model associated with a given tag or list of tags. """ tags = get_tag_list(tags) tag_count = len(tags) if tag_count == 0: # No existing tags were given queryset, model = get_queryset_and_model(queryset_or_model) return model._default_manager.none() elif tag_count == 1: # Optimisation for single tag - fall through to the simpler # query below. tag = tags[0] else: return self.get_intersection_by_model(queryset_or_model, tags) queryset, model = get_queryset_and_model(queryset_or_model) content_type = ContentType.objects.get_for_model(model) opts = self.model._meta tagged_item_table = qn(opts.db_table) return queryset.extra( tables=[opts.db_table], where=[ '%s.content_type_id = %%s' % tagged_item_table, '%s.tag_id = %%s' % tagged_item_table, '%s.%s = %s.object_id' % (qn(model._meta.db_table), qn(model._meta.pk.column), tagged_item_table) ], params=[content_type.pk, tag.pk], )
def get_related(self, obj, queryset_or_model, num=None): """ Retrieve a list of instances of the specified model which share tags with the model instance ``obj``, ordered by the number of shared tags in descending order. If ``num`` is given, a maximum of ``num`` instances will be returned. """ queryset, model = get_queryset_and_model(queryset_or_model) model_table = qn(model._meta.db_table) content_type = ContentType.objects.get_for_model(obj) related_content_type = ContentType.objects.get_for_model(model) query = """ SELECT %(model_pk)s, COUNT(related_tagged_item.object_id) AS %(count)s FROM %(model)s, %(tagged_item)s, %(tag)s, %(tagged_item)s related_tagged_item WHERE %(tagged_item)s.object_id = %%s AND %(tagged_item)s.content_type_id = %(content_type_id)s AND %(tag)s.id = %(tagged_item)s.tag_id AND related_tagged_item.content_type_id = %(related_content_type_id)s AND related_tagged_item.tag_id = %(tagged_item)s.tag_id AND %(model_pk)s = related_tagged_item.object_id""" if content_type.pk == related_content_type.pk: # Exclude the given instance itself if determining related # instances for the same model. query += """ AND related_tagged_item.object_id != %(tagged_item)s.object_id""" query += """ GROUP BY %(model_pk)s ORDER BY %(count)s DESC %(limit_offset)s""" query = query % { 'model_pk': '%s.%s' % (model_table, qn(model._meta.pk.column)), 'count': qn('count'), 'model': model_table, 'tagged_item': qn(self.model._meta.db_table), 'tag': qn(self.model._meta.get_field('tag').rel.to._meta.db_table), 'content_type_id': content_type.pk, 'related_content_type_id': related_content_type.pk, # Hardcoding this for now just to get tests working again - this # should now be handled by the query object. 'limit_offset': num is not None and 'LIMIT %s' or '', } cursor = connection.cursor() params = [obj.pk] if num is not None: params.append(num) cursor.execute(query, params) object_ids = [row[0] for row in cursor.fetchall()] if len(object_ids) > 0: # Use in_bulk here instead of an id__in lookup, because id__in would # clobber the ordering. object_dict = queryset.in_bulk(object_ids) return [object_dict[object_id] for object_id in object_ids \ if object_id in object_dict] else: return []
def get_intersection_by_model(self, queryset_or_model, tags): """ Create a ``QuerySet`` containing instances of the specified model associated with *all* of the given list of tags. """ tags = get_tag_list(tags) tag_count = len(tags) queryset, model = get_queryset_and_model(queryset_or_model) if not tag_count: return model._default_manager.none() model_table = qn(model._meta.db_table) # This query selects the ids of all objects which have all the # given tags. query = """ SELECT %(model_pk)s FROM %(model)s, %(tagged_item)s WHERE %(tagged_item)s.content_type_id = %(content_type_id)s AND %(tagged_item)s.tag_id IN (%(tag_id_placeholders)s) AND %(model_pk)s = %(tagged_item)s.object_id GROUP BY %(model_pk)s HAVING COUNT(%(model_pk)s) = %(tag_count)s""" % { 'model_pk': '%s.%s' % (model_table, qn(model._meta.pk.column)), 'model': model_table, 'tagged_item': qn(self.model._meta.db_table), 'content_type_id': ContentType.objects.get_for_model(model).pk, 'tag_id_placeholders': ','.join(['%s'] * tag_count), 'tag_count': tag_count, } cursor = connection.cursor() cursor.execute(query, [tag.pk for tag in tags]) object_ids = [row[0] for row in cursor.fetchall()] if len(object_ids) > 0: return queryset.filter(pk__in=object_ids) else: return model._default_manager.none()