Вопрос

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

У меня есть несколько классов абстрактных моделей, которые я использую для предоставления небольших конкретных функций другим моделям.Две из этих моделей — это 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 (глобальный, неудаляемый).

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

Редактировать:

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

Это было полезно?

Решение

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

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

Другие советы

Вот конкретное решение моей проблемы с использованием специального 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, но любые удаленные экземпляры все равно будут отображаться в администраторе (на случай, если вам понадобится их отменить).

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

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

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с.

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

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.Я не пробовал использовать это, хотя использую некоторые основные принципы динамического расширения классов, чтобы упростить доступ.

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

Еще один вариант, который стоит рассмотреть, — PassThroughManager:

https://django-model-utils.readthedocs.org/en/latest/managers.html#passthroughmanager

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top