2011-01-02 1 views
24

В Django, если у меня есть класс модели, например.В Django вы можете добавить метод к запросам?

from django.db import models 

class Transaction(models.Model): 
    ... 

затем, если я хочу добавить методы к модели, хранить, например. достаточно сложные фильтры, я могу добавить пользовательский диспетчер моделей, например.

class TransactionManager(models.Manager): 

    def reasonably_complex_filter(self): 
     return self.get_query_set().filter(...) 


class Transaction(models.Model): 
    objects = TransactionManager() 

И тогда я могу сделать:

>>> Transaction.objects.reasonably_complex_filter() 

Есть ли способ, я могу добавить пользовательский метод, который может быть прикован к концу запроса набора из модели?

т.е. добавить пользовательский метод таким образом, что я могу это сделать:

>>> Transaction.objects.filter(...).reasonably_complex_filter() 

ответ

5

Вам нужно добавить методы к QuerySet, которые вы в конечном итоге в конечном итоге с. Поэтому вам нужно создать и использовать подкласс QuerySet, который будет определять методы, где бы вы ни хотели эту функциональность.

Я нашел этот учебник, который объясняет, как это сделать, и причины, по которым вы можете захотеть:

http://adam.gomaa.us/blog/2009/feb/16/subclassing-django-querysets/index.html

+0

Право, получено. Знаете ли вы, что метод, предложенный в этом блоге, работает надежно? Комментарии предполагают, что подход '__getattr__' имеет некоторые проблемы. –

+0

Я не ставил этот код на практике сам, но я уже давно видел '__getattr__', который был использован в проектах Django раньше аналогичным образом. –

+1

Статья довольно старая, но дело в том, что вам нужно прикрепить свои методы к объекту QuerySet. –

3

Вы можете изменить метод get_query_set() вернуть пользовательский QuerySet, добавляя методы, которые вы требуете. В вашем случае, вы будете использовать:

class TransactionManager(models.Manager): 
    def get_query_set(self): 
     return TransactionQuerySet(self.model) 

class TransactionQuerySet(models.query.QuerySet): 
    def reasonably_complex_filter(self): 
     return self.filter(...) 

Я видел примеры подклассов TransactionQuerySet в Transaction модели, или в соответствующем Manager, но это полностью зависит от вас.

редактировать: Я, кажется, упустили из виду тот факт, что objects первые ссылки на TransactionManager и поэтому Transaction.objects.reasonably_complex_filter() не представляется возможным в моей реализации. Это можно устранить тремя способами:

  • Внесите reasonably_complex_filter как в Менеджере, так и в QuerySet;
  • Используйте Transaction.objects.all().reasonably_complex_filter(), если это единственный фильтр;
  • Обратитесь к ответу Маркуса Виббро для решения, которое будет реализовывать метод как в QuerySet, так и в Manager без дублирования кода.

Это зависит от того, какая опция является наиболее желательной, хотя я бы настоятельно рекомендовал не дублировать код (хотя вы могли бы использовать глобальный метод для преодоления этого). Хотя последний вариант может быть слишком дорогостоящим с точки зрения накладных расходов, если вы только требуете такого рода практики один раз, или если вы только собираетесь использовать сложный фильтр в сочетании с другим фильтром.

0

Я на самом деле закончил с другим методом.Оказалось, что я нужен только для цепи filter вызовов на конце моего пользовательского метода, поэтому я с поправками мой метод, чтобы принимать произвольные именованные аргументы, и передать их через к filter() вызова на конце моего довольно сложного запроса:

class TransactionManager(models.Manager): 

    def reasonably_complex_filter(self, **kwargs): 
     return self.get_query_set().filter(...).filter(**kwargs) 

Кажется, что я отлично работаю для своих целей и немного проще, чем подклассы QuerySet.

14

Это полное решение, которое, как известно, работает в Django 1.3, любезно предоставлено Zach Smith и Ben.

class Entry(models.Model): 
    objects = EntryManager() # don't forget this 

    is_public = models.BooleanField() 
    owner = models.ForeignKey(User) 


class EntryManager(models.Manager): 
    '''Use this class to define methods just on Entry.objects.''' 
    def get_query_set(self): 
     return EntryQuerySet(self.model) 

    def __getattr__(self, name, *args): 
     if name.startswith("_"): 
      raise AttributeError 
     return getattr(self.get_query_set(), name, *args) 

    def get_stats(self): 
     '''A sample custom Manager method.''' 
     return { 'public_count': self.get_query_set().public().count() } 


class EntryQuerySet(models.query.QuerySet): 
    '''Use this class to define methods on queryset itself.''' 
    def public(self): 
     return self.filter(is_public=True) 

    def by(self, owner): 
     return self.filter(owner=owner) 


stats = Entry.objects.get_stats()  
my_entries = Entry.objects.by(request.user).public() 

Примечание:get_query_set() метод is now deprecated in Django 1.6; В этом случае следует использовать get_queryset().

29

В Джанго 1.7, была добавлена ​​способность to use a query set as a manager:

class PersonQuerySet(models.QuerySet): 
    def authors(self): 
     return self.filter(role='A') 

    def editors(self): 
     return self.filter(role='E') 

class Person(models.Model): 
    first_name = models.CharField(max_length=50) 
    last_name = models.CharField(max_length=50) 
    role = models.CharField(max_length=1, choices=(('A', _('Author')), 
                ('E', _('Editor')))) 
    people = PersonQuerySet.as_manager() 

В результате следующее:

Person.people.authors(last_name='Dahl') 

Кроме того, также была добавлена ​​возможность добавлять custom lookups.