代码之家  ›  专栏  ›  技术社区  ›  Silver Light

Django:分页器+原始SQL查询

  •  11
  • Silver Light  · 技术社区  · 14 年前

    我在我的网站上到处使用django paginator,甚至写了一个特殊的模板标签,以使它更方便。但现在我进入了一种状态,在这种状态下,我需要进行一个复杂的自定义原始SQL查询,如果没有 LIMIT 将返回大约10万条记录。

    如何在自定义查询中使用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的分页方式:

    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)
    

    这样,姜戈变得非常聪明,并补充道 极限 在执行查询时。但是当我使用自定义管理器时:

    all_objects = Person.objects.complicated_list();
    

    所有数据都被选中,然后只有python列表被切片,这非常慢。如何使自定义管理器的行为类似于内置管理器?

    4 回复  |  直到 7 年前
        1
  •  9
  •   Tomasz Zieliński    14 年前

    看看paginator的源代码, page() function 特别是,我认为这只是执行的问题 slicing 并将其转换为SQL查询中的相关限制子句。您可能还需要添加一些缓存,但这开始看起来像queryset,所以您可以做其他事情:

    • 您可以使用create view myview as[您的查询]创建数据库视图;
    • 为该视图添加Django模型,使用 Meta: managed=False
    • 像其他模型一样使用该模型,包括切片其查询集-这意味着它非常适合与分页器一起使用。

    (供您参考-我已经使用这种方法很长时间了,即使与伪造M2M中间表的视图有复杂的多对多关系。)

        2
  •  2
  •   Felix Kling    14 年前

    我不知道django 1.1,但是如果你能等1.2(不应该再长了),你可以利用 objects.raw() 如上所述 this article 而在 development documentation .

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

        3
  •  2
  •   Jonathan Potter    8 年前

    这里是一个 RawPaginator 类I进行了重写 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)
    
        4
  •  1
  •   Community CDub    7 年前

    我还想插上 PaginatedRawQuerySet 这是我写的(请把它看作是阿尔法版本)。这将向原始查询集添加切片功能。请参阅 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