В Django, вы можете добавить метод в наборы запросов? - PullRequest
27 голосов
/ 02 января 2011

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

Ответы [ 6 ]

36 голосов
/ 30 октября 2014

Начиная с 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')

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

15 голосов
/ 01 ноября 2011

Это полное решение, которое, как известно, работает в Django 1.3, предоставлено Заком Смитом и Беном.

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()

Примечание: the get_query_set() метод теперь не поддерживается в Django 1.6 ;* В этом случае следует использовать get_queryset().

5 голосов
/ 02 января 2011

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

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

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

4 голосов
/ 02 января 2011

Вы можете изменить метод 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(), когда требуется только один фильтр;
  • Обратитесь к ответу Маркуса Уайброу для решения, которое будет реализовывать метод как в QuerySet, так и в Manager без дублирования кода.

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

0 голосов
/ 06 августа 2018

Если вам нужны как пользовательские методы Manager, так и пользовательские методы QuerySet, вы можете использовать from_queryset.

class BaseManager(models.Manager):
    def manager_only_method(self):
        return

class CustomQuerySet(models.QuerySet):
    def manager_and_queryset_method(self):
        return

class MyModel(models.Model):
    objects = BaseManager.from_queryset(CustomQuerySet)()

https://docs.djangoproject.com/en/2.1/topics/db/managers/#from-queryset

0 голосов
/ 08 января 2011

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

class TransactionManager(models.Manager):

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

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

...