Подтвердить что ты не робот

В 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()
4b9b3361

Ответ 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.