2012-05-03 31 views
18

Mỗi khi tôi sử dụng Quản trị viên để liệt kê các mục nhập của một mô hình, Quản trị viên đếm các hàng trong bảng. Tệ hơn nữa, có vẻ như đang làm như vậy ngay cả khi bạn lọc truy vấn của mình.Ngăn chặn quản trị viên django chạy SELECT COUNT (*) trên biểu mẫu danh sách

Ví dụ nếu tôi muốn chỉ hiển thị các mô hình có id là 123, 456, 789 tôi có thể làm:

/admin/myapp/mymodel/?id__in=123,456,789 

Nhưng các truy vấn chạy (trong số những người khác) là:

SELECT COUNT(*) FROM `myapp_mymodel` WHERE `myapp_mymodel`.`id` IN (123, 456, 789) # okay 
SELECT COUNT(*) FROM `myapp_mymodel` # why??? 

Mà là giết chết mysql + innodb. Có vẻ như vấn đề được thừa nhận một phần là in this ticket, nhưng vấn đề của tôi có vẻ cụ thể hơn vì nó đếm tất cả các hàng ngay cả khi nó không được yêu cầu.

Có cách nào để vô hiệu hóa các hàng toàn cầu đó không?

Lưu ý: Tôi đang sử dụng django 1.2.7.

+1

Khi bạn lọc nó sẽ hiển thị ví dụ "21 kết quả (tổng số 3011)", do đó, 'count (*)' là bắt buộc để hiển thị tổng số. Để tắt nó đi, tôi nghĩ rằng bạn sẽ phải làm rất nhiều hack ứng dụng quản trị django. – Alasdair

+0

Bây giờ tôi thấy, cảm ơn. Tôi đoán chúng tôi sẽ phải chờ đợi cho vé đó đi qua, sau đó. – Nova

+0

'Lưu ý: Tôi đang sử dụng django 1.2.7.' - xem xét nâng cấp. –

Trả lời

18

OK, tôi nghĩ tôi đã tìm được giải pháp.Như Peter gợi ý, phương pháp tốt nhất là làm việc trên count tài sản và nó có thể được thực hiện bằng cách ghi đè nó với bộ truy vấn tùy chỉnh (như đã thấy trong this post) chuyên tính với một tương đương xấp xỉ:

from django.db import connections, models 
from django.db.models.query import QuerySet 

class ApproxCountQuerySet(QuerySet): 
    """Counting all rows is very expensive on large Innodb tables. This 
    is a replacement for QuerySet that returns an approximation if count() 
    is called with no additional constraints. In all other cases it should 
    behave exactly as QuerySet. 

    Only works with MySQL. Behaves normally for all other engines. 
    """ 

    def count(self): 
     # Code from django/db/models/query.py 

     if self._result_cache is not None and not self._iter: 
      return len(self._result_cache) 

     is_mysql = 'mysql' in connections[self.db].client.executable_name.lower() 

     query = self.query 
     if (is_mysql and not query.where and 
       query.high_mark is None and 
       query.low_mark == 0 and 
       not query.select and 
       not query.group_by and 
       not query.having and 
       not query.distinct): 
      # If query has no constraints, we would be simply doing 
      # "SELECT COUNT(*) FROM foo". Monkey patch so the we 
      # get an approximation instead. 
      cursor = connections[self.db].cursor() 
      cursor.execute("SHOW TABLE STATUS LIKE %s", 
        (self.model._meta.db_table,)) 
      return cursor.fetchall()[0][4] 
     else: 
      return self.query.get_count(using=self.db) 

Sau đó, trong quản trị viên:

class MyAdmin(admin.ModelAdmin): 

    def queryset(self, request): 
     qs = super(MyAdmin, self).queryset(request) 
     return qs._clone(klass=ApproxCountQuerySet) 

Chức năng gần đúng có thể gây rối tung lên trang số 100000, nhưng nó đủ tốt cho trường hợp của tôi.

+2

Cảm ơn. Với một chút chỉnh sửa, điều này cũng làm việc cho tôi trong PostgreSQL, mặc dù tôi đã phải sử dụng truy vấn 'SELECT reltuples :: int FROM pg_class WHERE oid = '% s' :: regclass;' – Cerin

+4

Không còn hoạt động trong 1.9 – LS55321

+0

@ LS55321 Bất kỳ ý tưởng làm thế nào chúng ta có thể sử dụng ApproxCountQuerySet? – Jickson

3

Nếu đây là vấn đề nghiêm trọng, bạn có thể phải thực hiện hành động Drastic ™.

Nhìn mã để cài đặt 1.3.1, tôi thấy rằng mã quản trị đang sử dụng trình chuyển trang được trả về . Lớp paginator mặc định có vẻ như ở django/core/paginator.py. Lớp đó có giá trị riêng được gọi là _count được đặt trong Paginator._get_count() (dòng 120 trong bản sao của tôi). Điều này lần lượt được sử dụng để thiết lập thuộc tính của lớp Paginator được gọi là count. Tôi nghĩ rằng _get_count() là mục tiêu của bạn. Bây giờ sân khấu được thiết lập.

Bạn có một vài lựa chọn:

  1. Trực tiếp sửa đổi mã nguồn. Tôi làm không phải đề xuất điều này, nhưng vì bạn dường như bị kẹt ở mức 1.2.7, bạn có thể thấy rằng đó là cách tốt nhất. Hãy nhớ ghi lại thay đổi này! Người bảo trì trong tương lai (kể cả có thể là chính bạn) sẽ cảm ơn bạn vì những người đứng đầu.

  2. Monkeypatch lớp học. Điều này là tốt hơn so với sửa đổi trực tiếp bởi vì a) nếu bạn không thích thay đổi bạn chỉ cần bình luận ra các monkeypatch, và b) nó có nhiều khả năng làm việc với các phiên bản tương lai của Django. Tôi có một monkeypatch sẽ trở lại hơn 4 năm vì họ vẫn chưa sửa lỗi trong biến mẫu _resolve_lookup() mã không nhận dạng được các cuộc gọi ở cấp độ đánh giá cao nhất, chỉ ở các cấp thấp hơn. Mặc dù các bản vá (mà kết thúc tốt đẹp phương pháp của một lớp) đã được viết với 0,97 trước, nó vẫn hoạt động ở 1.3.1.

tôi đã không dành thời gian để tìm ra chính xác những gì thay đổi bạn sẽ phải làm cho vấn đề của bạn, nhưng nó có thể là dọc theo dòng của việc thêm một thành viên _approx_count đến các lớp học phù hợp class META và sau đó kiểm tra để xem nếu attr đó tồn tại. Nếu có và là None thì bạn hãy thực hiện sql.count() và đặt nó. Bạn cũng có thể cần phải đặt lại nó nếu bạn đang ở trên (hoặc gần) trang cuối cùng của danh sách. Liên hệ với tôi nếu bạn cần thêm một chút trợ giúp về vấn đề này; Email của tớ nằm trong phần thông tin cá nhân nhé.

7

Tôi thấy câu trả lời của Nova rất hữu ích, nhưng tôi sử dụng postgres. Tôi sửa đổi nó một chút để làm việc cho postgres với một số thay đổi nhỏ để xử lý không gian tên bảng, và hơi khác nhau "phát hiện postgres" logic.

Đây là phiên bản pg.

class ApproxCountPgQuerySet(models.query.QuerySet): 
    """approximate unconstrained count(*) with reltuples from pg_class""" 

    def count(self): 
     if self._result_cache is not None and not self._iter: 
      return len(self._result_cache) 

     if hasattr(connections[self.db].client.connection, 'pg_version'): 
      query = self.query 
      if (not query.where and query.high_mark is None and query.low_mark == 0 and 
       not query.select and not query.group_by and not query.having and not query.distinct): 
       # If query has no constraints, we would be simply doing 
       # "SELECT COUNT(*) FROM foo". Monkey patch so the we get an approximation instead. 
       parts = [p.strip('"') for p in self.model._meta.db_table.split('.')] 
       cursor = connections[self.db].cursor() 
       if len(parts) == 1: 
        cursor.execute("select reltuples::bigint FROM pg_class WHERE relname = %s", parts) 
       else: 
        cursor.execute("select reltuples::bigint FROM pg_class c JOIN pg_namespace n on (c.relnamespace = n.oid) WHERE n.nspname = %s AND c.relname = %s", parts) 
      return cursor.fetchall()[0][0] 
     return self.query.get_count(using=self.db) 
3

Giải pháp của Nova (ApproxCountQuerySet) hoạt động tuyệt vời, tuy nhiên trong phiên bản mới hơn của phương pháp queryset Django đã thay thế bằng get_queryset, vì vậy nó bây giờ sẽ là:

class MyAdmin(admin.ModelAdmin): 

    def get_queryset(self, request): 
     qs = super(MyAdmin, self).get_queryset(request) 
     return qs._clone(klass=ApproxCountQuerySet) 
Các vấn đề liên quan