Django: 分页器 + 原生 SQL 查询
我在网站上到处使用Django的分页功能,还写了一个特别的模板标签,让它更方便使用。但现在我遇到了一个问题,我需要做一个复杂的自定义SQL查询,如果不加LIMIT
的话,会返回大约10万条记录。
我该如何在自定义查询中使用Django的分页功能?
我的问题简化一下就是:
我的模型:
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进行分页的方式:
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的列表切片,这样非常慢。我该如何让我的自定义管理器的行为像内置的那样?
4 个回答
6
这里有一个我制作的 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)
15
看了Paginator的源代码,特别是page()函数,我觉得你只需要在自己这边实现一下切片,然后把它转化为SQL查询中的LIMIT语句就可以了。你可能还需要加一些缓存,但这就有点像QuerySet了,所以也许你可以尝试其他方法:
- 你可以用CREATE VIEW myview AS [你的查询]来创建一个数据库视图;
- 为这个视图添加一个Django模型,并设置Meta: managed=False;
- 像使用其他模型一样使用这个模型,包括对它的查询集进行切片——这意味着它完全可以和Paginator一起使用。
(顺便说一下,我已经用这种方法很久了,甚至在处理复杂的多对多关系时,也用视图来伪装中间表。)