2011-02-03 30 views
30

Tôi đã tạo một mô hình và tôi đang hiển thị biểu mẫu mô hình mặc định/chưa sửa đổi cho nó. Điều này một mình tạo ra 64 truy vấn SQL bởi vì nó có khá ít khóa ngoại, và lần lượt có nhiều khóa ngoại hơn.Django: Buộc chọn liên quan?

Có thể buộc nó luôn luôn (theo mặc định) thực hiện một select_related mỗi khi một trong các mô hình này được trả về?

Trả lời

41

Bạn có thể tạo người quản lý tùy chỉnh và chỉ cần ghi đè get_queryset để ứng dụng được áp dụng ở mọi nơi. Ví dụ:

class MyManager(models.Manager): 
    def get_queryset(self): 
     return super(MyManager, self).get_queryset().select_related('foo', 'bar') 

(Trước Django 1.6, nó là get_query_set).

+0

Làm cách nào để thực hiện điều này khi sử dụng models.QuerySet? –

+0

Tôi nghĩ bạn cần cung cấp thêm thông tin, hoặc một số mã ví dụ, theo ý bạn; nếu bạn có QuerySet, thì bạn có thể gọi trực tiếp 'select_related'. –

+0

Tôi có nghĩa là nếu bạn sử dụng các đối tượng = MyQuerySet.as_manager() –

2

Tạo tùy chỉnh models.Manager và ghi đè tất cả các phương thức (filter, get v.v.) và nối select_related vào mọi truy vấn. Sau đó, đặt trình quản lý này làm thuộc tính objects trên mô hình.

Tôi khuyên bạn chỉ cần thực hiện mã của mình và thêm select_related nếu cần, bởi vì thực hiện select_related trên mọi thứ sẽ gây ra một số vấn đề nghiêm trọng về hiệu suất xuống dòng (và nó sẽ không hoàn toàn rõ ràng nơi nó đến).

+0

Sau đó, tôi phải ghi đè tất cả các trường biểu mẫu trong mẫu biểu mẫu để tôi có thể đặt bộ truy vấn theo cách thủ công ... – mpen

+0

Vâng, bạn luôn có thể thực hiện theo đề xuất đầu tiên của mình. Tôi đã chỉ cảnh báo bạn về các vấn đề về hiệu suất bất lợi có thể xảy ra mà bạn có thể gặp phải. Có nhiều cách để làm điều đó chỉ cho 'ModelForm' mà không ghi đè mọi thứ, nhưng câu trả lời sẽ thực sự phụ thuộc vào chính xác những gì bạn cần làm. Nếu bạn muốn trợ giúp với nó, chỉ cần tạo một câu hỏi khác với nhiều chi tiết hơn. – sdolan

+0

Vâng, tôi có một mô hình Địa chỉ có liên kết đến mã bưu điện, thành phố, tỉnh và quốc gia. Nó sẽ khá nhiều không bao giờ được hiển thị mà không có những lĩnh vực, vì vậy tôi figured tôi cũng có thể bao gồm nó theo mặc định. – mpen

28

Đây cũng là một thủ thuật thú vị:

class DefaultSelectOrPrefetchManager(models.Manager): 
    def __init__(self, *args, **kwargs): 
     self._select_related = kwargs.pop('select_related', None) 
     self._prefetch_related = kwargs.pop('prefetch_related', None) 

     super(DefaultSelectOrPrefetchManager, self).__init__(*args, **kwargs) 

    def get_queryset(self, *args, **kwargs): 
     qs = super(DefaultSelectOrPrefetchManager, self).get_queryset(*args, **kwargs) 

     if self._select_related: 
      qs = qs.select_related(*self._select_related) 
     if self._prefetch_related: 
      qs = qs.prefetch_related(*self._prefetch_related) 

     return qs 


class Sandwich(models.Model): 
    bread = models.ForeignKey(Bread) 
    extras = models.ManyToManyField(Extra) 

    # ... 

    objects = DefaultSelectOrPrefetchManager(select_related=('bread',), prefetch_related=('extras',)) 

Sau đó, bạn có thể tái sử dụng người quản lý dễ dàng giữa các lớp mô hình. Ví dụ như trường hợp sử dụng, điều này sẽ phù hợp nếu bạn có phương thức __unicode__ trên mô hình đã hiển thị chuỗi bao gồm một số thông tin từ mô hình liên quan (hoặc bất kỳ điều gì khác có nghĩa là mô hình liên quan gần như luôn yêu cầu).

... và nếu bạn thực sự muốn nhận được lập dị, đây là phiên bản tổng quát hơn. Nó cho phép bạn gọi bất kỳ chuỗi phương thức nào trên truy vấn mặc định với bất kỳ kết hợp nào của args hoặc kwargs. Có thể có một số lỗi trong mã, nhưng bạn có ý tưởng.

from django.db import models 


class MethodCalls(object): 
    """ 
    A mock object which logs chained method calls. 
    """ 
    def __init__(self): 
     self._calls = [] 

    def __getattr__(self, name): 
     c = Call(self, name) 
     self._calls.append(c) 
     return c 

    def __iter__(self): 
     for c in self._calls: 
      yield tuple(c) 


class Call(object): 
    """ 
    Used by `MethodCalls` objects internally to represent chained method calls. 
    """ 
    def __init__(self, calls_obj, method_name): 
     self._calls = calls_obj 
     self.method_name = method_name 

    def __call__(self, *method_args, **method_kwargs): 
     self.method_args = method_args 
     self.method_kwargs = method_kwargs 

     return self._calls 

    def __iter__(self): 
     yield self.method_name 
     yield self.method_args 
     yield self.method_kwargs 


class DefaultQuerysetMethodCallsManager(models.Manager): 
    """ 
    A model manager class which allows specification of a sequence of 
    method calls to be applied by default to base querysets. 
    `DefaultQuerysetMethodCallsManager` instances expose a property 
    `default_queryset_method_calls` to which chained method calls can be 
    applied to indicate which methods should be called on base querysets. 
    """ 
    def __init__(self, *args, **kwargs): 
     self.default_queryset_method_calls = MethodCalls() 

     super(DefaultQuerysetMethodCallsManager, self).__init__(*args, **kwargs) 

    def get_queryset(self, *args, **kwargs): 
     qs = super(DefaultQuerysetMethodCallsManager, self).get_queryset(*args, **kwargs) 

     for method_name, method_args, method_kwargs in self.default_queryset_method_calls: 
      qs = getattr(qs, method_name)(*method_args, **method_kwargs) 

     return qs 


class Sandwich(models.Model): 
    bread = models.ForeignKey(Bread) 
    extras = models.ManyToManyField(Extra) 

    # Other field definitions... 

    objects = DefaultQuerysetMethodCallsManager() 
    objects.default_queryset_method_calls.filter(
     bread__type='wheat', 
    ).select_related(
     'bread', 
    ).prefetch_related(
     'extras', 
    ) 

Đối tượng python-mock lấy cảm hứng MethodCalls là một nỗ lực làm cho API tự nhiên hơn. Một số có thể thấy rằng một chút bối rối. Nếu có, bạn có thể chỉ ra mã đó cho số __init__ arg hoặc kwarg mà chỉ chấp nhận một bộ thông tin cuộc gọi phương thức.

+0

Điều này thực sự đã cứu ngày của tôi - thay vì viết tấn 'ModelForm's, ghi đè 'ModelChoiceField's cho đến khi bò về nhà. (Espacially nếu 'MyModel .__ unicode __()' sử dụng những gì cần phải được 'select_related' –

+0

Câu trả lời tuyệt vời Điều này giải quyết phần lớn các vấn đề liên quan đến n + 1 của tôi – Eldamir

+1

Lưu ý:' get_query_set' không được chấp nhận trong Django 1.6] (https : //docs.djangoproject.com/en/1.10/releases/1.6/#get-query-set-and-similar-methods-renamed-to-get-queryset) Cần thay thế bằng 'get_queryset' ngay bây giờ. – keithb

Các vấn đề liên quan