我在我的网站上到处都使用Django Paginator,甚至还写了一个特殊的模板标签,以使其更方便。但现在我进入了一个状态,我需要创建一个复杂的自定义原始SQL查询,没有LIMIT
将返回大约100K记录。
如何在自定义查询中使用Django Pagintor?
我的问题的简化示例:
我的模特:
class PersonManager(models.Manager):
def complicated_list(self):
from django.db import connection
#Real query is much more complex
cursor.execute("""SELECT * FROM `myapp_person`""");
result_list = []
for row in cursor.fetchall():
result_list.append(row[0]);
return result_list
class Person(models.Model):
name = models.CharField(max_length=255);
surname = models.CharField(max_length=255);
age = models.IntegerField();
objects = PersonManager();
我在Django ORM中使用pagintation的方式:
all_objects = Person.objects.all();
paginator = Paginator(all_objects, 10);
try:
page = int(request.GET.get('page', '1'))
except ValueError:
page = 1
try:
persons = paginator.page(page)
except (EmptyPage, InvalidPage):
persons = paginator.page(paginator.num_pages)
这样,Django变得非常聪明,并在执行时向查询添加LIMIT
。但是当我使用自定义管理器时:
all_objects = Person.objects.complicated_list();
选择所有数据,然后切片python列表,这非常慢。如何使我的自定义管理器的行为类似于内置管理器?
答案 0 :(得分:9)
查看Paginator的源代码,特别是page() function,我认为只需要实现slicing,并将其转换为SQL查询中的相关LIMIT子句。您可能还需要添加一些缓存,但这看起来像QuerySet,所以也许您可以做其他事情:
(为了您的信息 - 我已经使用这种方法很长一段时间了,即使是与VIEW伪造m2m中间表的复杂的多对多关系。)
答案 1 :(得分:2)
我不知道Django 1.1但是如果你可以等到1.2(不应该那么长)你可以按照this article和development documentation中的描述使用objects.raw()
{3}}
否则,如果查询不是太复杂,可能使用extra
clause就足够了。
答案 2 :(得分:2)
这是我创建的RawPaginator
类,它会覆盖Paginator
以处理原始查询。它需要一个额外的参数count
,这是您的查询的总计数。它不会对object_list
进行分片,因为您必须通过OFFSET
和LIMIT
在原始查询中进行分页。
from django.core.paginator import Paginator
class RawPaginator(Paginator):
def __init__(self, object_list, per_page, count, **kwargs):
super().__init__(object_list, per_page, **kwargs)
self.raw_count = count
def _get_count(self):
return self.raw_count
count = property(_get_count)
def page(self, number):
number = self.validate_number(number)
return self._get_page(self.object_list, number, self)
答案 3 :(得分:1)
我还想插一个我写的PaginatedRawQuerySet
(请将其视为alpha版本)。这会将切片功能添加到原始查询集。请参考to this answer - 我为另一个有类似要求的问题写的 - 以了解它是如何工作的(特别是"谨慎和#34;最后一节)。
from django.db import models
from django.db.models import sql
from django.db.models.query import RawQuerySet
class PaginatedRawQuerySet(RawQuerySet):
def __init__(self, raw_query, **kwargs):
super(PaginatedRawQuerySet, self).__init__(raw_query, **kwargs)
self.original_raw_query = raw_query
self._result_cache = None
def __getitem__(self, k):
"""
Retrieves an item or slice from the set of results.
"""
if not isinstance(k, (slice, int,)):
raise TypeError
assert ((not isinstance(k, slice) and (k >= 0)) or
(isinstance(k, slice) and (k.start is None or k.start >= 0) and
(k.stop is None or k.stop >= 0))), \
"Negative indexing is not supported."
if self._result_cache is not None:
return self._result_cache[k]
if isinstance(k, slice):
qs = self._clone()
if k.start is not None:
start = int(k.start)
else:
start = None
if k.stop is not None:
stop = int(k.stop)
else:
stop = None
qs.set_limits(start, stop)
return qs
qs = self._clone()
qs.set_limits(k, k + 1)
return list(qs)[0]
def __iter__(self):
self._fetch_all()
return iter(self._result_cache)
def count(self):
if self._result_cache is not None:
return len(self._result_cache)
return self.model.objects.count()
def set_limits(self, start, stop):
limit_offset = ''
new_params = tuple()
if start is None:
start = 0
elif start > 0:
new_params += (start,)
limit_offset = ' OFFSET %s'
if stop is not None:
new_params = (stop - start,) + new_params
limit_offset = 'LIMIT %s' + limit_offset
self.params = self.params + new_params
self.raw_query = self.original_raw_query + limit_offset
self.query = sql.RawQuery(sql=self.raw_query, using=self.db, params=self.params)
def _fetch_all(self):
if self._result_cache is None:
self._result_cache = list(super().__iter__())
def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, self.model.__name__)
def __len__(self):
self._fetch_all()
return len(self._result_cache)
def _clone(self):
clone = self.__class__(raw_query=self.raw_query, model=self.model, using=self._db, hints=self._hints,
query=self.query, params=self.params, translations=self.translations)
return clone