2010-03-28 42 views
9

Tôi đang sử dụng Django Paginator ở khắp mọi nơi trên trang web của tôi và thậm chí đã viết một thẻ mẫu đặc biệt, để làm cho nó thuận tiện hơn. Nhưng bây giờ tôi đã đến một trạng thái, nơi mà tôi cần phải thực hiện một truy vấn SQL thô tùy chỉnh phức tạp, mà không cần LIMIT sẽ trả về khoảng 100K bản ghi.Django: Paginator + truy vấn SQL thô

Tôi làm cách nào để sử dụng Django Pagintor với truy vấn tùy chỉnh?

dụ đơn giản của vấn đề của tôi:

mô hình của tôi:

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(); 

Con đường tôi sử dụng pagintation với 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) 

Bằng cách này, Django nhận được rất thông minh, và thêm LIMIT vào truy vấn khi thực thi nó. Nhưng khi tôi sử dụng trình quản lý tùy chỉnh:

all_objects = Person.objects.complicated_list(); 

tất cả dữ liệu được chọn và chỉ sau đó danh sách python được cắt lát, rất chậm. Làm cách nào để tôi làm cho trình quản lý tùy chỉnh của mình hoạt động tương tự như được tích hợp sẵn?

+0

Trong Python, bạn không nên sử dụng khoảng trắng bất cứ khi nào bạn muốn trong lớp Person của bạn. – Gereltod

Trả lời

8

Nhìn vào mã nguồn của Paginator, page() function nói riêng, tôi nghĩ rằng chỉ cần thực hiện slicing ở bên cạnh bạn, và chuyển nó sang mệnh đề LIMIT có liên quan trong truy vấn SQL. Bạn cũng có thể cần phải thêm một số bộ nhớ đệm, nhưng điều này bắt đầu trông giống như QuerySet, vì vậy có thể bạn có thể làm điều gì đó khác:

  • bạn có thể tạo cơ sở dữ liệu VIEW bằng cách sử dụng CREATE VIEW myview AS [truy vấn của bạn];
  • thêm mô hình Django cho XEM rằng, với Meta: managed=False
  • sử dụng mô hình giống như bất kỳ mô hình khác, bao gồm cắt queryset của nó - điều này có nghĩa là nó hoàn toàn phù hợp cho việc sử dụng với Paginator

(Để biết thông tin của bạn - Tôi đã sử dụng cách tiếp cận này trong một thời gian dài ngay bây giờ, ngay cả với các mối quan hệ phức tạp từ nhiều đến nhiều với VIEWs giả mạo các bảng trung gian m2m.)

+0

Wow, thats cool :) Cảm ơn bạn đã trả lời, nhưng tôi guiess tôi đang tìm kiếm một hướng sai. Tôi đã hỏi một câu hỏi khác: http://stackoverflow.com/questions/2532686/django-getting-the-list-of-related-records-for-a-list-of-objects Trình quản lý tùy chỉnh phù hợp không phải là điều tốt nhất ở đây. –

2

Tôi không biết về Django 1.1 nhưng nếu bạn có thể chờ 1.2 (không nên lâu hơn nữa), bạn có thể sử dụng objects.raw() như được mô tả trong this article và trong development documentation.

Nếu không, nếu truy vấn của bạn không quá phức tạp, có thể sử dụng extra clause là đủ.

+0

Cảm ơn bạn đã có một mẹo hữu ích. Nhưng tôi đoán nó không giúp đỡ trong tình huống của tôi –

+0

Bạn vẫn không thể đếm được kết quả truy vấn thô. Có vẻ như bạn thực sự phải làm danh sách (objects.raw()) để nó hoạt động với trình tạo trang. Nhờ http://stackoverflow.com/questions/2317452/django-count-rawqueryset cho thông tin đó. – Josh

1

Đây là một lớp RawPaginator để làm việc với các truy vấn thô. Cần một đối số bổ sung, count, là tổng số truy vấn của bạn. Nó không cắt object_list vì bạn phải phân trang trong truy vấn thô của mình qua 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

Giải pháp rất đẹp. Cảm ơn bạn. –

+0

Khi bạn gọi lớp Paginator mới, giá trị 'đếm' sẽ mất khi đưa ra làm đối số? –

0

Tôi cũng muốn cắm PaginatedRawQuerySet mà tôi đã viết (hãy xem đây là phiên bản alpha). Điều này thêm khả năng cắt vào một queryset thô. Vui lòng tham khảo to this answer - mà tôi đã viết cho một câu hỏi khác với yêu cầu tương tự - để hiểu cách hoạt động (đặc biệt là phần "Một lời cảnh báo" ở cuối).

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