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

Цепочка менеджера Django

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

У меня есть несколько классов абстрактных моделей, которые я использую для обеспечения небольших, специфических функций для других моделей. Две из этих моделей - это 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). Если я отменил порядок, t получить ошибку, но она не отфильтровывает удаленные). Если я изменю GlobalMixin, чтобы привязать GlobalManager к глобальным переменным вместо объектов (поэтому новой командой будет SomeModel.globals.globals()), я получаю экземпляры 1 и 2 (два глобальных символа), в то время как мой предполагаемый результат будет состоять только в том, чтобы получить экземпляр 1 (глобальный, не удаленный).

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

Edit:

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

Ответ 3

Я потратил некоторое время, пытаясь придумать способ создания приятного factory, чтобы сделать это, но у меня много проблем с этим.

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

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

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

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

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

GlobalDeleteManager = create_manager(DeleteManager, globals=globals)

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

Затем вы создаете свою модель mixin:

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

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

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

Сообщите мне, если что-то неясно, и я обновлю ответ.