我正在努力通过GAE提供API,允许用户通过一组实体向前和向后翻页。我查看了section about cursors on the NDB Queries documentation page,其中包含一些示例代码,介绍了如何通过查询结果向后翻页,但它似乎没有按预期工作。我正在使用GAE Development SDK 1.8.8。
以下是该示例的修改版本,可创建5个示例实体,获取并打印第一页,向前进入并打印第二页,然后尝试向后退步并再次打印第一页:
import pprint
from google.appengine.ext import ndb
class Bar(ndb.Model):
foo = ndb.StringProperty()
#ndb.put_multi([Bar(foo="a"), Bar(foo="b"), Bar(foo="c"), Bar(foo="d"), Bar(foo="e")])
# Set up.
q = Bar.query()
q_forward = q.order(Bar.foo)
q_reverse = q.order(-Bar.foo)
# Fetch the first page.
bars1, cursor1, more1 = q_forward.fetch_page(2)
pprint.pprint(bars1)
# Fetch the next (2nd) page.
bars2, cursor2, more2 = q_forward.fetch_page(2, start_cursor=cursor1)
pprint.pprint(bars2)
# Fetch the previous page.
rev_cursor2 = cursor2.reversed()
bars3, cursor3, more3 = q_reverse.fetch_page(2, start_cursor=rev_cursor2)
pprint.pprint(bars3)
(仅供参考,您可以在本地应用引擎的交互式控制台中运行上述内容。)
以上代码打印出以下结果;请注意,结果的第三页只是第二页反转,而不是回到第一页:
[Bar(key=Key('Bar', 4996180836614144), foo=u'a'),
Bar(key=Key('Bar', 6122080743456768), foo=u'b')]
[Bar(key=Key('Bar', 5559130790035456), foo=u'c'),
Bar(key=Key('Bar', 6685030696878080), foo=u'd')]
[Bar(key=Key('Bar', 6685030696878080), foo=u'd'),
Bar(key=Key('Bar', 5559130790035456), foo=u'c')]
我期待看到这样的结果:
[Bar(key=Key('Bar', 4996180836614144), foo=u'a'),
Bar(key=Key('Bar', 6122080743456768), foo=u'b')]
[Bar(key=Key('Bar', 5559130790035456), foo=u'c'),
Bar(key=Key('Bar', 6685030696878080), foo=u'd')]
[Bar(key=Key('Bar', 6685030696878080), foo=u'a'),
Bar(key=Key('Bar', 5559130790035456), foo=u'b')]
如果我将代码段的“获取前一页”部分更改为以下代码片段,我会得到预期的输出,但是我有没有使用前向排序查询和end_cursor代替的缺点文档中描述的机制?
# Fetch the previous page.
bars3, cursor3, more3 = q_forward.fetch_page(2, end_cursor=cursor1)
pprint.pprint(bars3)
答案 0 :(得分:8)
为了让文档中的示例更清晰一点,让我们暂时忘记数据存储区并改为使用列表:
# some_list = [4, 6, 1, 12, 15, 0, 3, 7, 10, 11, 8, 2, 9, 14, 5, 13]
# Set up.
q = Bar.query()
q_forward = q.order(Bar.key)
# This puts the elements of our list into the following order:
# ordered_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
q_reverse = q.order(-Bar.key)
# Now we reversed the order for backwards paging:
# reversed_list = [15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
# Fetch a page going forward.
bars, cursor, more = q_forward.fetch_page(10)
# This fetches the first 10 elements from ordered_list(!)
# and yields the following:
# bars = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# cursor = [... 9, CURSOR-> 10 ...]
# more = True
# Please notice the right-facing cursor.
# Fetch the same page going backward.
rev_cursor = cursor.reversed()
# Now the cursor is facing to the left:
# rev_cursor = [... 9, <-CURSOR 10 ...]
bars1, cursor1, more1 = q_reverse.fetch_page(10, start_cursor=rev_cursor)
# This uses reversed_list(!), starts at rev_cursor and fetches
# the first ten elements to it's left:
# bars1 = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
因此,文档中的示例以两个不同的顺序从两个不同的方向获取相同的页面。这不是你想要达到的目标。
您似乎已经找到了一个很好地覆盖您的用例的解决方案,但让我建议另一个:
只需重复使用cursor1即可返回第2页 如果我们正在讨论前端并且当前页面是第3页,这意味着将cursor3分配给'next'按钮并将cursor1分配给'previous'按钮。
这样你就不得反转查询和光标。
答案 1 :(得分:5)
我冒昧地将Bar
模型更改为Character
模型。该示例看起来更像Pythonic IMO; - )
我写了一个快速单元测试来演示分页,准备复制粘贴:
import unittest
from google.appengine.datastore import datastore_stub_util
from google.appengine.ext import ndb
from google.appengine.ext import testbed
class Character(ndb.Model):
name = ndb.StringProperty()
class PaginationTest(unittest.TestCase):
def setUp(self):
tb = testbed.Testbed()
tb.activate()
self.addCleanup(tb.deactivate)
tb.init_memcache_stub()
policy = datastore_stub_util.PseudoRandomHRConsistencyPolicy(
probability=1)
tb.init_datastore_v3_stub(consistency_policy=policy)
characters = [
Character(id=1, name='Luigi Vercotti'),
Character(id=2, name='Arthur Nudge'),
Character(id=3, name='Harry Bagot'),
Character(id=4, name='Eric Praline'),
Character(id=5, name='Ron Obvious'),
Character(id=6, name='Arthur Wensleydale')]
ndb.put_multi(characters)
query = Character.query().order(Character.key)
# Fetch second page
self.page = query.fetch_page(2, offset=2)
def test_current_page(self):
characters, _cursor, more = self.page
self.assertSequenceEqual(
['Harry Bagot', 'Eric Praline'],
[character.name for character in characters])
self.assertTrue(more)
def test_next_page(self):
_characters, cursor, _more = self.page
query = Character.query().order(Character.key)
characters, cursor, more = query.fetch_page(2, start_cursor=cursor)
self.assertSequenceEqual(
['Ron Obvious', 'Arthur Wensleydale'],
[character.name for character in characters])
self.assertFalse(more)
def test_previous_page(self):
_characters, cursor, _more = self.page
# Reverse the cursor (point it backwards).
cursor = cursor.reversed()
# Also reverse the query order.
query = Character.query().order(-Character.key)
# Fetch with an offset equal to the previous page size.
characters, cursor, more = query.fetch_page(
2, start_cursor=cursor, offset=2)
# Reverse the results (undo the query reverse ordering).
characters.reverse()
self.assertSequenceEqual(
['Luigi Vercotti', 'Arthur Nudge'],
[character.name for character in characters])
self.assertFalse(more)
一些解释:
setUp
方法首先初始化所需的存根。然后将6个示例字符放入id,因此顺序不是随机的。由于有6个字符,我们有3页2个字符。使用有序查询和偏移量2直接获取第二页。注意偏移量,这是示例的关键。
test_current_page
验证是否获取了两个中间字符。为了便于阅读,按名称比较字符。 ; - )
test_next_page
获取下一个(第三个)页面并验证预期字符的名称。到目前为止,一切都很顺利。
现在test_previous_page
很有趣。这样做有两件事,首先光标反转,所以光标现在指向后方而不是向前。 (这提高了可读性,它应该在没有这个的情况下工作,但是偏移量会有所不同,我将把它作为读者的练习。)接下来用反向排序创建一个查询,这是必要的,因为偏移量不能是负数而你想拥有以前的实体。然后使用等于当前页面的页面长度的偏移量获取结果。否则查询将返回相同的结果,但反转(如问题中所示)。现在因为查询是反向排序的,结果都是倒退的。我们只是简单地反转结果列表来解决这个问题。最后但并非最不重要的是,声明了预期的名称。
附注:由于这涉及全局查询,因此概率设置为100%,在生产中(因为最终的一致性),放置和查询后很可能会失败。