2010-03-28 103 views
9

我在我的网站上到处都是使用Django Paginator,甚至写了一个特殊的模板标签,以使它更方便。但是现在我到了一个状态,在那里我需要制作一个复杂的自定义原始SQL查询,没有LIMIT就会返回大约100K条记录。Django:Paginator +原始SQL查询

如何在自定义查询中使用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(); 

我用pagintation 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(); 

所有数据被选择,才把蟒蛇名单切片,这是非常缓慢的。我如何让自定义管理器的行为类似于内置的?

+0

在Python中,你不应该只在你的Person类中使用空格。 – Gereltod 2017-05-25 04:16:47

回答

8

查看Paginator的源代码,特别是page() function,我认为只需要在您的身边实施slicing,并将其转换为SQL查询中的相关LIMIT子句。您可能还需要添加一些缓存,但开始看起来像查询集,所以也许你可以做别的事情:

  • 您可以使用CREATE VIEW MyView的AS [查询]创建数据库视图。
  • 添加的Django模型的这一观点,与Meta: managed=False
  • 使用,像任何其他的模式,包括切片其查询集模型 - 这意味着它完全适用于分页程序使用

(为了您的信息 - 我我已经使用这种方法很长一段时间了,即使与VIEWs伪造m2m中间表的复杂多对多关系也是如此。)

+0

哇,多数民众赞成酷:)谢谢你的回复,但我guiess我正在寻找一个错误的方向。我问过另一个问题:http://stackoverflow.com/questions/2532686/django-getting-the-list-of-related-records-for-a-list-of-objects Guiess定制管理员不是这里最好的事情。 – 2010-03-28 11:21:32

2

我不知道Django的1.1,但如果你可以等待1.2(这不应该是长了),如this article并在development documentation描述,你可以使objects.raw()使用。

否则,如果您查询不太复杂,也许使用extra clause就足够了。

+0

感谢您提供有用的提示。但我想这对我的情况没有帮助 – 2010-03-28 11:24:42

+0

您仍然无法获得原始查询结果的计数。看起来你实际上必须做list(objects.raw())才能使用paginator。感谢http://stackoverflow.com/questions/2317452/django-count-rawqueryset获取该信息。 – Josh 2012-06-19 00:03:30

1

这是一个RawPaginator类,我重写了Paginator来处理原始查询。它需要一个额外的参数,count,这是您的查询的总数。它不会分割object_list,因为您必须通过OFFSETLIMIT在原始查询中进行分页。

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) 
+0

非常好的解决方案。谢谢。 – 2017-02-25 22:51:40

+0

当你调用新的Paginator类时,当作为参数给出时,'count'会采用什么值? – 2017-10-17 13:43:06

0

我也想插一PaginatedRawQuerySet我写的(请认为这是一个alpha版本)。这将切片功能添加到原始查询集。请参阅to this answer - 我为其他问题编写了一个类似的需求 - 以便了解它是如何工作的(特别是最后的“谨慎之词”一节)。

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