В 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()
Ответы
Ответ 1
Вам нужно добавить методы к QuerySet
, которые вы в конечном итоге закончите. Поэтому вам нужно создать и использовать подкласс QuerySet
, который будет определять методы, где бы вы ни хотели эту функциональность.
Я нашел этот учебник, в котором объясняется, как это сделать и почему вы можете захотеть:
http://adam.gomaa.us/blog/2009/feb/16/subclassing-django-querysets/index.html
Ответ 2
По состоянию на django 1.7 добавлена возможность использовать набор запросов в качестве менеджера:
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')
Кроме того, добавлена возможность добавления пользовательских поисков.
Ответ 3
Это полное решение, которое, как известно, работает в 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()
теперь устарел в Django 1.6; get_queryset()
следует использовать вместо этого в этом случае.
Ответ 4
Вы можете изменить метод 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
, но это полностью зависит от вас.
edit. Я, кажется, не обратил внимания на то, что objects
первые ссылки на TransactionManager
и, следовательно, Transaction.objects.reasonably_complex_filter()
невозможны в моей реализации. Это можно устранить тремя способами:
- Реализовать
reasonably_complex_filter
как в Менеджере, так и в QuerySet;
- Используйте
Transaction.objects.all().reasonably_complex_filter()
, когда требуется только один фильтр;
- Обратитесь к Marcus Whybrow для решения, которое будет реализовывать метод как в
QuerySet
, так и в Manager
без дублирования кода.
Это зависит от приложения, которое является наиболее желательным, хотя я бы настоятельно рекомендовал не дублировать код (хотя вы могли бы использовать глобальный метод для преодоления этого). Хотя последний вариант может быть слишком дорогостоящим с точки зрения накладных расходов, если вы только один раз требуете такого рода практики, или если вы намерены использовать только комплексный фильтр в сочетании с другим фильтром.
Ответ 5
На самом деле я перешел к другому методу. Оказалось, мне нужно было только связать вызовы filter
в конце моего пользовательского метода, поэтому я поменял свой метод на использование произвольных аргументов ключевого слова и передал их при вызове filter()
в конце моего достаточно сложного запроса:
class TransactionManager(models.Manager):
def reasonably_complex_filter(self, **kwargs):
return self.get_query_set().filter(...).filter(**kwargs)
Кажется, что я отлично работаю для своих целей и немного проще, чем подклассы QuerySet
.