«частные» модели, наборы запросов по умолчанию и методы цепочки - PullRequest
3 голосов
/ 17 июня 2011

У меня есть логический флаг private на моей модели и пользовательский менеджер, который перезаписывает метод get_query_set с фильтром, удаляя private = True:

class myManager(models.Manager):
    def get_query_set(self):
        qs = super(myManager, self).get_query_set()
        qs = qs.filter(private=False)
        return qs

class myModel(models.Model):
    private = models.BooleanField(default=False)
    owner = models.ForeignKey('Profile', related_name="owned")
    #...etc...

    objects = myManager()

Я хочу исключить набор запросов по умолчаниючастные модели по умолчанию используются в качестве меры безопасности, предотвращая случайное использование модели, показывающей частные модели.

Иногда, однако, я хочу показать частные модели, поэтому в диспетчере у меня есть следующее:

def for_user(self, user):
    if user and not user.is_authenticated():
        return self.get_query_set()
    qs = super(myManager, self).get_query_set()
    qs = qs.filter(Q(owner=user, private=True) | Q(private=False))
    return qs

Это отлично работает, за исключением того, что я не могу зацепить фильтр.Это становится проблемой, когда у меня есть fk, указывающий на myModel, и использую otherModel.mymodel_set.otherModel.mymodel_set.for_user (user) не будет работать, потому что mymodel_set возвращает объект QuerySet, а не менеджера.

Теперь начинается настоящая проблема, так как я не вижу способа заставить метод for_user () работатьв подклассе QuerySet, потому что я не могу получить доступ к полному нефильтрованному набору запросов (в основном перезаписывая get_query_set) из подкласса QuerySet, как я могу в менеджере (используя super () для получения базового набора запросов.)

Каков наилучший способ обойти это?

Я не привязан к какому-либо конкретному интерфейсу, но мне бы хотелось, чтобы он был как можно более djangoy / DRY.Очевидно, я мог бы сбросить защиту и просто вызвать метод для фильтрации частных задач при каждом вызове, но я действительно не хочу этого делать.

Обновление

Ответ Манджи ниже очень близок, однако он терпит неудачу, когда требуемый набор запросов не является подмножеством набора запросов по умолчанию.Я предполагаю, что реальный вопрос здесь состоит в том, как я могу удалить определенный фильтр из цепочечного запроса?

Ответы [ 3 ]

2 голосов
/ 17 июня 2011

Определите пользовательский QuerySet (содержащий ваши пользовательские методы фильтра):

class MyQuerySet(models.query.QuerySet):

    def public(self):
        return self.filter(private=False)

    def for_user(self, user):
        if user and not user.is_authenticated():
            return self.public()
        return self.filter(Q(owner=user, private=True) | Q(private=False))

Определите пользовательский менеджер, который будет использовать MyQuerySet (MyQuerySet пользовательские фильтры будут доступны, как если бы они были определены вменеджер [путем переопределения __getattr__]):

# A Custom Manager accepting custom QuerySet
class MyManager(models.Manager):

    use_for_related_fields = True

    def __init__(self, qs_class=models.query.QuerySet):
        self.queryset_class = qs_class
        super(QuerySetManager, self).__init__()

    def get_query_set(self):
        return self.queryset_class(self.model).public()

    def __getattr__(self, attr, *args):
        try:
            return getattr(self.__class__, attr, *args)
        except AttributeError:
            return getattr(self.get_query_set(), attr, *args) 

Тогда в модели:

class MyModel(models.Model):
    private = models.BooleanField(default=False)
    owner = models.ForeignKey('Profile', related_name="owned")
    #...etc...

    objects = myManager(MyQuerySet)

Теперь вы можете:

¤ только по умолчанию общедоступные модели:

    MyModel.objects.filter(..

¤ доступ for_user модели:

    MyModel.objects.for_user(user1).filter(..

Из-за ( use_for_related_fields = True ) этот же менеджер будет использоваться для связанных менеджеров.Таким образом, вы также можете:

¤ доступ только по умолчанию public модели от связанных менеджеров:

    otherModel.mymodel_set.filter(..

¤ доступ for_user от связанных менеджеров:

    otherModel.mymodel_set.for_user(user).filter(..

Дополнительная информация: Создание подклассов Django QuerySets & Пользовательские менеджеры с цепочечными фильтрами (фрагмент django)

0 голосов
/ 24 июня 2011

Если вам нужно «сбросить» QuerySet, вы можете получить доступ к модели набора запросов и снова вызвать оригинальный менеджер (для полного сброса). Однако это, вероятно, не очень полезно для вас, если только вы не отслеживали предыдущие операторы filter / exclude и т. Д. И не можете снова воспроизвести их в наборе запросов сброса. С небольшим планированием, которое на самом деле не будет слишком сложным, но может оказаться немного грубой силой.

В целом ответ Манджи - верный путь.

Таким образом, изменяя ответ Манджи, вы должны заменить существующий "model"."private" = False на ("model"."owner_id" = 2 AND "model"."private" = True ) OR "model"."private" = False ). Для этого вам нужно пройти через объект where объекта query набора запросов, чтобы найти соответствующий бит для удаления. Объект запроса имеет объект WhereNode, представляющий дерево предложения where, причем каждый узел имеет несколько дочерних элементов. Вам нужно вызвать as_sql на узле, чтобы выяснить, нужен ли он вам:


from django.db import connection
qn = connection.ops.quote_name
q = myModel.objects.all()
print q.query.where.children[0].as_sql(qn, connection)

Что должно дать вам что-то вроде:


('"model"."private" = ?', [False])

Однако попытка сделать это, вероятно, требует гораздо больших усилий, чем это того стоит, и она углубляется в кусочки Django, которые, вероятно, не стабильны по API.

Моя рекомендация: использовать двух менеджеров . Один, который может получить доступ ко всему (аварийный штрих сортировки), другой с примененной фильтрацией по умолчанию. Менеджер по умолчанию - первый, поэтому вам нужно поиграться с порядком в зависимости от того, что вам нужно сделать. Затем реструктурируйте свой код, чтобы узнать, какой из них использовать, чтобы у вас не было проблемы с наличием там дополнительного предложения private = False.

0 голосов
/ 17 июня 2011

Чтобы использовать цепочку, вы должны переопределить get_query_set в вашем менеджере и поместить for_user в свой пользовательский QuerySet.

Мне не нравится это решение, но оно работает.

class CustomQuerySet(models.query.QuerySet):
    def for_user(self):
        return super(CustomQuerySet, self).filter(*args, **kwargs).filter(private=False)

class CustomManager(models.Manager):
    def get_query_set(self):
        return CustomQuerySet(self.model, using=self._db)
...