2009-04-30 3 views
29

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

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

ответ

21

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

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

+0

Это, кажется, сделали свое дело. Я должен был больше сосредоточиться на наборе запросов и меньше на менеджере. Я отправил свое возможное решение в качестве ответа, хотя я выбрал ваш, как лучший. – Adam

2

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

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

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) 

Если вы хотите что-то более общее, лучшее, что я могу придумать является определение base 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 

Ok, так что это некрасиво, но то, что оно получает вас? По сути, это одно и то же решение, но гораздо более динамичное и немного более СУЩЕСТВУЮЩЕЕ, хотя и более сложное для чтения.

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

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

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

+0

Цепочка наследования, безусловно, будет работать, но, к сожалению, это победит цель моих миксинов. Есть экземпляры (я не уверен, когда), где я бы хотел, чтобы что-то просто было GlobalMixin или просто было DeleteMixin (или оба они). Я определенно должен заглянуть в ваше другое предложение. Я согласен, что это немного уродливо, но, возможно, его можно очистить, чтобы обеспечить аналогичную функциональность в более чистом пакете. – Adam

+0

Вы можете сделать их с помощью этого метода, просто перейдя в models.Model как базовый mixin и models.Manager как базовый менеджер, но это стоит того, если у вас будет много перестановок микшинов и менеджеров. – tghw

8

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

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 

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

+1

Чтобы администратор использовал тот менеджер, которого вы хотите, посмотрите здесь: http://stackoverflow.com/questions/1545067/django-specify-which-model-manager-django-admin-should-use –