我的应用程序中有以下型号。 Addition模型用于管理Book模型和Collection模型之间的多对多关系,因为我需要在中间模型上包含额外的字段。
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。
感谢。
更新
我应该补充一点,总的来说,由于几个原因,我不是在寻找任何'循环结果并重复删除'的解决方案。
可能会有数十甚至数十万个添加内容(我也会在列出用户添加的所有新增内容的页面上显示此信息),并且响应时间非常重要。
在限制初始结果集时,此解决方案可能会变得更加实用,但它会产生分页问题,这也是必需的。即,如何对整个结果集进行分页,同时仅对该集合的一小部分进行重复数据删除。我愿意接受可以解决这个问题的任何想法。
更新
我还应该提一下,如果同一本书被多个用户添加,我实际上并没有优先选择使用哪种添加,原始或最新添加都可以正常工作。
答案 0 :(得分:0)
以下内容如何 - 它不是一个纯粹的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)
如果涉及的书籍可能超过一千本,您可能希望对初始添加查询设置限制。在排除中传递大量的ID列表并不是一个好主意。使用'values()'非常重要,因为Python可以循环遍历一个基本的dicts列表,比查询集快得多,而且它使用的内存要少得多。
答案 1 :(得分:0)
假设没有大量的新增功能可供展示,这很容易实现:
# 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()
根据您对问题的描述,性能不会成为问题。
答案 2 :(得分:0)
additions = Addition.objects.filter(collection__subscribers=user).values('book').annotate(user=Min('user'), collection=Min('collection')).order_by()
此查询将为您提供包含其用户和集合的唯一书籍列表。书籍,馆藏,用户将是pk
,而不是对象。但我希望你将它们存储在缓存中,这样就不会有问题了。
但是对于你的工作量,我会考虑非规范化。我的查询非常很重,如果您经常添加,则不容易缓存其结果。我的第一种方法是将latest_additions
字段添加到Collection
模型并使用信号进行更新(不添加重复项)。此字段的格式取决于您。
答案 3 :(得分:0)
有时可以放入SQL,尤其是当仅使用ORM的解决方案不具备性能时。在SQL中很容易获得非重复的Addition行ID,然后您可以切换回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()