Аннотировать наследование нескольких таблиц в Django - PullRequest
5 голосов
/ 15 мая 2010

У меня есть базовая модель LoggedEvent и ряд моделей подклассов, таких как:

class LoggedEvent(models.Model):
    user = models.ForeignKey(User, blank=True, null=True)
    timestamp = models.DateTimeField(auto_now_add=True)

class AuthEvent(LoggedEvent):
    good = models.BooleanField()
    username = models.CharField(max_length=12)

class LDAPSearchEvent(LoggedEvent):
    type = models.CharField(max_length=12)
    query = models.CharField(max_length=24)

class PRISearchEvent(LoggedEvent):
    type = models.CharField(max_length=12)
    query = models.CharField(max_length=24)

Пользователи генерируют эти события при выполнении соответствующих действий. Я пытаюсь сгенерировать отчет об использовании количества каждого типа событий, вызванных каждым пользователем в прошлом месяце. Я борюсь с ORM Джанго и, когда я рядом, я сталкиваюсь с проблемой. Вот код запроса:

def usage(request):
    # Calculate date range
    today = datetime.date.today()
    month_start = datetime.date(year=today.year, month=today.month - 1, day=1)
    month_end = datetime.date(year=today.year, month=today.month, day=1) - datetime.timedelta(days=1)

    # Search for how many LDAP events were generated per user, last month
    baseusage = User.objects.filter(loggedevent__timestamp__gte=month_start, loggedevent__timestamp__lte=month_end)
    ldapusage = baseusage.exclude(loggedevent__ldapsearchevent__id__lt=1).annotate(count=Count('loggedevent__pk'))
    authusage = baseusage.exclude(loggedevent__authevent__id__lt=1).annotate(count=Count('loggedevent__pk'))

    return render_to_response('usage.html', {
        'ldapusage' : ldapusage,
        'authusage' : authusage,
    }, context_instance=RequestContext(request))

И ldapusage, и authusage представляют собой список пользователей, каждый из которых помечен атрибутом .count, который должен представлять, сколько конкретных событий сгенерировал пользователь. Однако в обоих списках атрибуты .count имеют одинаковое значение. На самом деле аннотированное количество считается равным количеству событий, сгенерированных пользователем, независимо от их типа. Так что казалось бы, что мой конкретный

authusage = baseusage.exclude(loggedevent__authevent__id__lt=1)

не исключает подкласс. Я пробовал id__lt = 1, id__isnull = True и другие. Halp.

1 Ответ

4 голосов
/ 24 августа 2010

Ключ к наследованию модели Django заключается в том, что он помнит, что с неабстрактным базовым классом все действительно является экземпляром базового класса, который может иметь некоторые дополнительные данные, привязанные сбоку из отдельной таблицы. , Это означает, что когда вы выполняете поиск по базовой таблице, вы возвращаете экземпляры базового класса, и нет никакого способа определить, к какому подклассу это относится, без повторных запросов к базе данных в таблицах подклассов, чтобы увидеть, содержат ли они запись с соответствующим ключом ( «У меня есть событие. Есть ли у него запись в AuthEvent? Нет. А как насчет события LDAP?…»). Среди прочего это означает, что вы не можете легко фильтровать их в обычных запросах к базовому классу, не выполняя объединение для каждой таблицы подклассов.

У вас есть пара вариантов: один из них - просто выполнить запросы к подклассу и подсчитать результаты (ldap_event_count = LDAPEvent.objects.filter(user=foo).count(),…), которых может быть достаточно для одного отчета. Я обычно рекомендую добавить поле типа контента в базовый класс, чтобы вы могли эффективно определить, к какому конкретному подклассу относится экземпляр, не выполняя другой запрос:

content_type = models.ForeignKey("contenttypes.ContentType")

Это позволяет сделать два основных улучшения: наиболее распространенным является то, что вы можете иметь дело со многими событиями в общем, без необходимости делать что-то вроде нажатия на специфичные для подкласса средства доступа (например, event.authevent или event.ldapevent) и обработку DoesNotExist. В этом случае было бы также просто переписать ваш запрос, так как вы могли бы просто сделать что-то вроде Event.objects.aggregate(Count("content_type")), чтобы получить значения отчета, что особенно удобно, если ваша логика усложняется («Событие является Auth или LDAP и…») ,

...