Джанго менеджер цепочки - PullRequest
29 голосов
/ 01 мая 2009

Мне было интересно, можно ли (и если да, то каким образом) связать вместе несколько менеджеров, чтобы создать набор запросов, на который влияют оба отдельных менеджера. Я объясню конкретный пример, над которым я работаю:

У меня есть несколько классов абстрактных моделей, которые я использую для предоставления небольшой специфической функциональности другим моделям. Две из этих моделей - DeleteMixin и GlobalMixin.

DeleteMixin определяется так:

class DeleteMixin(models.Model):
    deleted = models.BooleanField(default=False)
    objects = DeleteManager()

    class Meta:
        abstract = True

    def delete(self):
        self.deleted = True
        self.save()

По сути, он обеспечивает псевдо-удаление (флаг удаления) вместо фактического удаления объекта.

GlobalMixin определяется так:

class GlobalMixin(models.Model):
    is_global = models.BooleanField(default=True)

    objects = GlobalManager()

    class Meta:
        abstract = True

Это позволяет любому объекту быть определенным как глобальный объект или частный объект (такой как публичный / частный пост в блоге).

У обоих из них есть свои собственные менеджеры, которые влияют на возвращаемый набор запросов. Мой DeleteManager фильтрует набор запросов, чтобы возвращать только те результаты, для которых флаг удаленного доступа имеет значение False, а GlobalManager фильтрует набор запросов, чтобы возвращать только результаты, помеченные как глобальные. Вот декларация для обоих:

class DeleteManager(models.Manager):
    def get_query_set(self):
        return super(DeleteManager, self).get_query_set().filter(deleted=False)

class GlobalManager(models.Manager):
    def globals(self):
        return self.get_query_set().filter(is_global=1)

Желаемая функциональность заключается в том, чтобы модель расширяла обе эти абстрактные модели и позволяла возвращать только результаты, которые не были удалены и являются глобальными. Я выполнил тестовый пример для модели с 4 экземплярами: один был глобальным и не удаленным, один был глобальным и удаленным, один был неглобальным и не удаленным, а другой был неглобальным и удаленным. Если я пытаюсь получить наборы результатов следующим образом: SomeModel.objects.all (), я получаю экземпляры 1 и 3 (два не удаленных - отлично!). Если я пытаюсь SomeModel.objects.globals (), я получаю сообщение об ошибке, что DeleteManager не имеет глобальных (это предполагает, что мое объявление модели таково: SomeModel (DeleteMixin, GlobalMixin). Если я изменяю порядок, я не ' Ошибка не появляется, но она не отфильтровывает удаленные). Если я изменю GlobalMixin, чтобы присоединить GlobalManager к глобальным переменным вместо объектов (таким образом, новая команда будет SomeModel.globals.globals ()), я получу экземпляры 1 и 2 (два глобальных), в то время как мой предполагаемый результат будет состоять только в получении экземпляра 1 (глобальный, не удаленный).

Я не был уверен, сталкивался ли кто-нибудь с какой-либо ситуацией, подобной этой, и достиг ли результата. Буду очень признателен за способ заставить его работать в моем нынешнем мышлении или переделку, обеспечивающую функциональность, которая мне нужна. Я знаю, что этот пост был немного скучным. Если понадобится еще какое-нибудь объяснение, я был бы рад предоставить его.

Edit:

Ниже я опубликовал окончательное решение, которое использовал для решения этой конкретной проблемы. Он основан на ссылке на пользовательский QuerySetManager Саймона.

Ответы [ 4 ]

21 голосов
/ 02 мая 2009

Смотрите этот фрагмент на Djangosnippets: http://djangosnippets.org/snippets/734/

Вместо того, чтобы помещать свои пользовательские методы в менеджер, вы создаете подкласс самого запроса. Это очень просто и отлично работает. Единственная проблема, с которой я столкнулся, связана с наследованием модели, вы всегда должны определять менеджера в подклассах модели (просто: «objects = QuerySetManager ()» в подклассе), даже если они будут наследовать набор запросов. Это будет иметь больше смысла, когда вы используете QuerySetManager.

8 голосов
/ 02 мая 2009

Вот конкретное решение моей проблемы с использованием пользовательского QuerySetManager от Саймона, с которым Скотт связался.

from django.db import models
from django.contrib import admin
from django.db.models.query import QuerySet
from django.core.exceptions import FieldError

class MixinManager(models.Manager):    
    def get_query_set(self):
        try:
            return self.model.MixinQuerySet(self.model).filter(deleted=False)
        except FieldError:
            return self.model.MixinQuerySet(self.model)

class BaseMixin(models.Model):
    admin = models.Manager()
    objects = MixinManager()

    class MixinQuerySet(QuerySet):

        def globals(self):
            try:
                return self.filter(is_global=True)
            except FieldError:
                return self.all()

    class Meta:
        abstract = True

class DeleteMixin(BaseMixin):
    deleted = models.BooleanField(default=False)

    class Meta:
        abstract = True

    def delete(self):
        self.deleted = True
        self.save()

class GlobalMixin(BaseMixin):
    is_global = models.BooleanField(default=True)

    class Meta:
        abstract = True

Любой миксин в будущем, который хочет добавить дополнительную функциональность к набору запросов, просто должен расширить BaseMixin (или иметь его где-нибудь в своей иерархии). Каждый раз, когда я пытаюсь отфильтровать установленный запрос, я помещаю его в try-catch на тот случай, если это поле на самом деле не существует (то есть не расширяет этот миксин). Глобальный фильтр вызывается с помощью globals (), тогда как фильтр удаления вызывается автоматически (если что-то удаляется, я никогда не хочу, чтобы это отображалось). Использование этой системы допускает следующие типы команд:

TemporaryModel.objects.all() # If extending DeleteMixin, no deleted instances are returned
TemporaryModel.objects.all().globals() # Filter out the private instances (non-global)
TemporaryModel.objects.filter(...) # Ditto about excluding deleteds

Следует отметить, что фильтр удаления не повлияет на интерфейсы администратора, потому что диспетчер по умолчанию объявляется первым (делая его по умолчанию). Я не помню, когда они сменили администратора на использование Model._default_manager вместо Model.objects, но все удаленные экземпляры все равно будут отображаться в администраторе (в случае, если вам необходимо удалить их).

2 голосов
/ 02 октября 2012

Стоит рассмотреть еще один вариант - PassThroughManager:

https://django -model-utils.readthedocs.org / ен / последний / managers.html # passthroughmanager

2 голосов
/ 02 мая 2009

Я потратил некоторое время, пытаясь придумать способ построить хорошую фабрику для этого, но у меня много проблем с этим.

Лучшее, что я могу вам предложить, - это связать ваше наследство. Он не очень общий, поэтому я не уверен, насколько он полезен, но все, что вам нужно сделать, это:

class GlobalMixin(DeleteMixin):
    is_global = models.BooleanField(default=True)

    objects = GlobalManager()

    class Meta:
        abstract = True

class GlobalManager(DeleteManager):
    def globals(self):
        return self.get_query_set().filter(is_global=1)

Если вы хотите что-то более общее, лучшее, что я могу придумать, - это определить базу Mixin и Manager, которая переопределяет get_query_set() (я предполагаю, что вы хотите сделать это только один раз; все становится довольно в противном случае сложно), а затем передайте список полей, которые вы хотите добавить, через Mixin s.

Это будет выглядеть примерно так (совсем не проверено):

class DeleteMixin(models.Model):
    deleted = models.BooleanField(default=False)

    class Meta:
        abstract = True

def create_mixin(base_mixin, **kwargs):
    class wrapper(base_mixin):
        class Meta:
            abstract = True
    for k in kwargs.keys():
        setattr(wrapper, k, kwargs[k])
    return wrapper

class DeleteManager(models.Manager):
    def get_query_set(self):
        return super(DeleteManager, self).get_query_set().filter(deleted=False)

def create_manager(base_manager, **kwargs):
    class wrapper(base_manager):
        pass
    for k in kwargs.keys():
        setattr(wrapper, k, kwargs[k])
    return wrapper

Хорошо, так это безобразно, но что это тебе дает? По сути, это то же решение, но гораздо более динамичное и немного более СУХОЕ, хотя и более сложное для чтения.

Сначала вы динамически создаете своего менеджера:

def globals(inst):
    return inst.get_query_set().filter(is_global=1)

GlobalDeleteManager = create_manager(DeleteManager, globals=globals)

Это создает нового менеджера, который является подклассом DeleteManager и имеет метод с именем globals.

Далее вы создаете свою модель миксина:

GlobalDeleteMixin = create_mixin(DeleteMixin,
                                 is_global=models.BooleanField(default=False),
                                 objects = GlobalDeleteManager())

Как я уже сказал, это ужасно. Но это означает, что вам не нужно переопределять globals(). Если вы хотите, чтобы у другого типа менеджера был globals(), просто позвоните create_manager снова с другой базой. И вы можете добавить столько новых методов, сколько захотите. То же самое для менеджера, вы просто продолжаете добавлять новые функции, которые будут возвращать разные наборы запросов.

Итак, это действительно практично? Возможно, нет. Этот ответ - скорее упражнение в (ab) с использованием гибкости Python. Я не пробовал использовать это, хотя я использую некоторые базовые принципы динамически расширяемых классов, чтобы облегчить доступ к ним.

Дайте мне знать, если что-то неясно, и я обновлю ответ.

...