Django: получить отличный QuerySet - PullRequest
3 голосов
/ 23 ноября 2011

В моем приложении есть следующие модели. Модель сложения используется для управления отношением «многие ко многим» между моделью «Книга» и моделью «Коллекция», поскольку мне нужно включить дополнительные поля в промежуточную модель.

class Book(models.Model):
    name = models.CharField(max_length=200)
    picture = models.ImageField(upload_to='img', max_length=1000)
    price = models.DecimalField(max_digits=8, decimal_places=2)

class Collection(models.Model):
    user = models.ForeignKey(User)
    name = models.CharField(max_length=100)
    books = models.ManyToManyField(Book, through='Addition')
    subscribers = models.ManyToManyField(User, related_name='collection_subscriptions', blank=True, null=True)

class Addition(models.Model):
    user = models.ForeignKey(User)
    book = models.ForeignKey(Book)
    collection = models.ForeignKey(Collection)
    created = models.DateTimeField(auto_now=False, auto_now_add=True)
    updated = models.DateTimeField(auto_now=True, auto_now_add=True)

В моем приложении пользователи могут добавлять книги в создаваемые ими коллекции (например, художественную литературу, историю и т. Д.). Затем другие пользователи могут следить за теми коллекциями, которые им нравятся.

Когда пользователь входит на сайт, я хотел бы отобразить все книги, которые были недавно добавлены в коллекции, за которыми он следит. В каждой книге я также хотел бы отобразить имя человека, добавившего ее, и название коллекции, в которой она находится.

Я могу получить все дополнения следующим образом ...

additions = Addition.objects.filter(collection__subscribers=user).select_related()

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

Если есть способ получить отдельный список книг в коллекциях, за которыми следит пользователь?

Я использую Django 1.3 + MySQL.

Спасибо.

UPDATE

Я должен добавить, что в целом я не ищу каких-либо «циклических результатов и дедупликации» по нескольким причинам.

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

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

UPDATE

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

Ответы [ 4 ]

0 голосов
/ 04 декабря 2011

Иногда нормально переходить на SQL, особенно когда решение только для ORM неэффективно. Легко получить неповторяющиеся идентификаторы строк дополнения в SQL, а затем вы можете переключиться обратно в ORM для выбора данных. Это два запроса, но они превзойдут все решения, которые я видел до сих пор.

from django.db import connection
from operator import itemgetter
cursor = connection.cursor()

# Select non-duplicate book additions, preferring for most recently updated
query = '''SELECT id, MAX(updated) FROM %s
    GROUP BY book_id''' % Addition._meta.db_table
cursor.execute(query)

# Flatten the results to an id list
addition_ids = map(itemgetter(0), cursor.fetchall())

additions = Addition.objects.filter(
    collection__subscribers=user, id__in=addition_ids).select_related()
0 голосов
/ 29 ноября 2011

Предполагая, что не будет большого количества дополнений для отображения, это может быть легко в трюке:

# duplicated..
additions = Addition.objects.filter(collection__subscribers=user, created__gt=DATE_LAST_LOGIN).select_related()

# remove duplication
added_books = {}
for addition in additions:
    added_books[addition.book] = True
added_books = added_books.keys()

По приведенному вами описанию проблемы производительность не будет проблемой.

0 голосов
/ 01 декабря 2011
additions = Addition.objects.filter(collection__subscribers=user).values('book').annotate(user=Min('user'), collection=Min('collection')).order_by()

Этот запрос даст вам список уникальных книг с их пользователями и коллекциями. Книги, коллекции, пользователи будут pk, а не объекты. Но я надеюсь, что вы сохраните их в кеше, чтобы не было проблем.

Но если бы вы работали, я бы подумал о денормализации. Мой запрос очень тяжелый, и его результаты не легко кэшировать, если вы будете часто добавлять. Мой первый подход заключается в добавлении поля latest_additions к модели Collection и обновлении сигналами (без добавления дубликатов). Формат этого поля зависит от вас.

0 голосов
/ 29 ноября 2011

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

def filter_additions(additions):
    # Use a ValuesQuerySet for performance
    additions_values = additions.values()

    # The following code just eliminates duplicates. You could do 
    # something much more powerful/interesting here if you like,
    # e.g. give preference to additions by a user`s friends 

    book_pk_registry = {}
    excluded_addition_pks = []

    for addition in additions_values:
        addition_pk = addition['id']
        book_pk = addition['book_id']
        if book_pk not in book_pk_registry:
            book_pk_registry[book_pk] = True
        else:
            excluded_addition_pks.append(addition_pk)

    additions = additions.exclude(pk__in=excluded_addition_pks)


additions = Addition.objects.filter(collection__subscribers=user)
additions = filter_additions(additions)

Если может быть задействовано более тысячи книг, вы можете захотеть ограничить первоначальный запрос дополнений. Передача огромных списков идентификаторов в исключении не такая уж хорошая идея. Использование «values ​​()» очень важно, потому что Python может циклически проходить через базовый список команд LOT быстрее, чем набор запросов, и он использует намного меньше памяти.

...