我看不出有什么好方法可以直接做你想做的事情。如果您愿意接受一点反规范化,我建议您使用预保存信号将消息标记为在头部。
#In your model
head = models.BooleanField(default=True)
#As a signal plugin:
def check_head(sender, **kwargs):
message = kwargs['instance']
if hasattr(message,'no_check_head') and message.no_check_head:
return
previous_message = Message.objects.filter(time__lt=message.time).order_by('-time')[0]
if message.source == previous_message.source:
message.head = False
next_message = Message.objects.filter(time__gt=message.time).order_by('time')[0]
if message.source == next_message.source:
next_message.head = False
next_message.no_check_head
next_message.save()
然后你的查询就变得异常简单:
messages = Message.objects.filter(head=True).order_by('time')[0:15]
说实话……信号监听器必须比我写的更复杂一些。我的方法中存在许多丢失同步/丢失更新的问题,解决方案会因您的服务器而异(如果它是单处理的、多线程的,那么 python Lock 对象应该可以解决您的问题,但如果它是多进程的,那么你真的需要实现基于文件或数据库对象的锁定)。此外,您当然还必须编写相应的删除信号侦听器。
显然,此解决方案涉及添加一些数据库命中,但它们是在编辑而不是在视图中,这对您来说可能是值得的。否则,也许考虑一种更粗略的方法:抓取 30 个故事,在视图中循环,剔除那些你不会显示的故事,如果还剩下 15 个,则显示它们,否则重复。绝对是一个可怕的最坏情况,但也许不是可怕的平均情况?
如果您的服务器配置使用多线程的单个进程,Lock 或 RLock 应该可以解决问题。下面是一个使用不可重入锁的可能实现:
import thread
lock = thread.allocate_lock()
def check_head(sender, **kwargs):
# This check must come outside the safe zone
# Otherwise, your code will screech to a hault
message = kwargs['instance']
if hasattr(message,'no_check_head') and message.no_check_head:
return
# define safe zone
lock.acquire()
# see code above
....
lock.release()
同样,相应的删除信号也很关键。
编辑:许多或大多数服务器配置(例如 Apache)将 prefork,这意味着有几个进程正在进行。在这种情况下,上面的代码将毫无用处。请参阅this page,了解如何开始与分叉进程同步。