分页取决于 Django 中的项目分组

Posted

技术标签:

【中文标题】分页取决于 Django 中的项目分组【英文标题】:Paging depending on grouping of items in Django 【发布时间】:2010-10-12 22:11:48 【问题描述】:

对于使用 Django/Python 实现的网站,我们有以下要求:

在查看页面上,每个网页显示 15 条消息。当来自同一来源的两个或多个消息在视图上相互跟随时,它们应该被组合在一起。

可能不清楚,但可以通过以下示例:

一个例子是(这次在一个页面上有 5 条消息):

  Message1 Source1
  Message2 Source2
  Message3 Source2
  Message4 Source1
  Message5 Source3
  ...

这应该显示为:

Message1 Source1
Message2 Source2 (click here to 1 more message from Source2)
Message4 Source1
Message5 Source3
Message6 Source2

因此,在每个页面上,页面上都会显示固定数量的项目,其中一些已重新组合。

我们想知道如何创建一个 Django 或 mysql 查询来以最佳且简单的方式查询这些数据。请注意,使用了分页,并且消息按时间排序。

PS:由于SQL的性质,我不认为有一个简单的解决方案,但有时复杂的问题可以很容易解决

【问题讨论】:

【参考方案1】:

我看不出有什么好方法可以直接做你想做的事情。如果您愿意接受一点反规范化,我建议您使用预保存信号将消息标记为在头部。

#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,了解如何开始与分叉进程同步。

【讨论】:

我认为这是您必须采用的一般方法,尽管您可以通过将 last_source 存储在 memcached 中来替换数据库命中;在高并发下仍然存在潜在的竞争条件,但如果它不必是 100% 完美的话...... 正如你所说,不是完美的选择,但我仍然认为是最好的。谢谢 另外...我想我一直喜欢使用信号...但是覆盖保存方法可以做同样的事情。一些文体纯粹主义者会争辩说这是“正确的事情”。【参考方案2】:

我有一个简单但不完美的纯模板解决方案。在模板中,您可以使用regroup 模板标签重新组合记录。重组后,您可以隐藏来自同一来源的连续记录:

% regroup records by source as grouped_records %
% for group in grouped_records %
  % for item in group.list %
    <li% if not forloop.first % style="display:none"% endif %>
        item.message   iterm.source 
       % if forloop.first %
         % ifnotequal group.list|length 1 %
           <a href="#" onclick="...">Show more from the same source...</a>
         % endifnotequal %           
       % endif %
    </li>
  % endfor %
% endfor %

如果不是为了一件事,这将是完美的:分页。如果您的意思是每页显示 15 个项目,并且在一个页面上,前五个来自一个来源,接下来五个来自另一个来源,最后五个来自另一个来源,那么页面上将只有三个可见项目。

【讨论】:

以上是关于分页取决于 Django 中的项目分组的主要内容,如果未能解决你的问题,请参考以下文章

在 django 分页中显示页面项目计数

django项目中的ajax分页和条件查询。

Django框架-CRM项目之分页实现

带有分页和分组的 Django ListView

仅使用新项目(Django)刷新模板中的表格内容

django项目分页