Сложный обратный запрос в Джанго - PullRequest
3 голосов
/ 15 октября 2011

В двух словах: мои модели B -> A <- C </strong>, я хочу отфильтровать Bs , где существует хотя бы один C ,удовлетворяющих некоторым произвольным условиям и относящимся к тем же A , что и B .Также приветствуется помощь по некоторым усложняющим факторам (см. Ниже).

Подробности:

Я пытаюсь создать универсальную модель для ограничения доступа пользователей к строкам в других моделях.Вот (упрощенный) пример:

class CanRead(models.Model):
    user = models.ForeignKey(User)
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    content_object = generic.GenericForeignKey('content_type', 'object_id')

class Direct(models.Model):
    ...

class Indirect(models.Model):
    direct = models.ForeignKey(Direct)
    ...

class Indirect2(models.Model):
    indirect = models.ForeignKey(Indirect)
    ...

Невозможно связать CanRead с каждой строкой в ​​каждой модели (слишком дорого в пространстве), поэтому ожидается, что только некоторые модели будут иметьэта ассоциация (как Direct выше).В этом случае вот как я могу увидеть, доступен ли пользователю Direct или нет:

Direct.objects.filter(Q(canread__user=current_user), rest_of_query)

(К сожалению, этот запрос не будет работать - в 1.2.5по крайней мере - из-за общего fk, любая помощь с этим была бы признательна, но есть обходные пути, реальная проблема заключается в том, что будет дальше)

Доступность других будет продиктована их отношениями с другими моделями.Таким образом, Косвенный будет доступен пользователю, если прямой доступен, и Косвенный2 будет, если косвенный__direct и т. Д.

Моя проблема в том, как я могу сделать этот запрос?Я испытываю желание написать что-то вроде:

Indirect.objects.filter(Q(canread__content_object=F('direct'), canread__user=current_user), rest_of_query)

Indirect2.objects.filter(Q(canread__content_object=F('indirect__direct'), canread__user=current_user), rest_of_query)

, но это не работает (Django ожидает связь между CanRead и Косвенным - что несуществует - для обратного запроса на работу).Если бы я писал это прямо на SQL, я бы сделал что-то вроде:

SELECT *
  FROM indirect i
    JOIN direct d ON i.direct = d.id
    JOIN canread c ON c.object_id = d.id
  WHERE
    c.content_type = <<content type for Direct>> AND
    c.user = <<current user>> AND
    <<rest_of_query>>

, но я не могу перевести этот запрос в Django.Является ли это возможным?Если нет, то каков был бы наименее навязчивый способ сделать это (используя как можно меньше необработанного SQL)?

Спасибо за ваше время!

Примечание. Упомянутый обходной путь будет не использоватьgeneric fk ... :( Я могу отказаться от CanRead модели и иметь множество CanReadDirect , CanReadDirect2 , CanReadDirect3 и т. д. Этонезначительные хлопоты, но не слишком мешают моему проекту.

Ответы [ 2 ]

3 голосов
/ 15 октября 2011

Для простого случая, который вы дали, решение простое:

B.objects.filter(a__c__isnull=False)

Для фактического запроса вот моя попытка:

Indirect.objects.filter(direct__id__in=
    zip(*CanRead.objects.filter(
           content_type=ContentType.objects.get_for_model(Direct)
        ).values_list('id'))[0])

Но этот способ очень медленный: вы извлекаете идентификаторы из одного набора запросов, а затем делаете запрос с помощью

where id in (1, 2, 3, ... 10000)

Что очень медленно. У нас была похожая проблема с объединениями общих внешних ключей в нашем проекте, и мы решили прибегнуть к необработанным запросам в менеджере моделей.

class DirectManager(Manager):
    def can_edit(self, user):
        return self.raw(...)

Я бы также порекомендовал проверить фреймворк для каждой строки в Django 1.3.

1 голос
/ 23 октября 2011

модели контроля доступа не так просты ... используйте хорошо известную модель контроля доступа, такую ​​как: DAC / MAC или RBAC, также существует проект под названием django-rbac.

...