【问题标题】:Custom properties not saved correctly for Expando models in repeated StructuredProperty在重复的 StructuredProperty 中未正确保存 Expando 模型的自定义属性
【发布时间】:2014-03-04 23:50:25
【问题描述】:

我正在尝试在另一个模型中使用 Expando 模型作为重复的 StructuredProperty。也就是说,我想在我的User 模型中添加不定数量的Accounts。由于 Accounts 可以根据其类型具有不同的属性(Accounts 是对社交网络帐户的引用,例如 Twitter 需要比 Facebook 更多的信息来进行 OAuth 过程),我将我的 Account 模型设计为 @987654328 @。我已在模型定义中添加了所有基本信息,但我计划为特定社交网络添加自定义属性(例如,Twitter 的特定 access_token_secret 属性)。

1/ 你能确认下面的设计(Expando 重复StructuredProperty应该工作吗?

class Account(ndb.Expando):
    account_type = ndb.StringProperty(required=True, choices=['fb', 'tw', 'li'])
    account_id = ndb.StringProperty()
    state = ndb.StringProperty()
    access_token = ndb.StringProperty()

class HUser(User):
    email = ndb.StringProperty(required=True, validator=validate_email)
    created = ndb.DateTimeProperty(auto_now_add=True)
    accounts = ndb.StructuredProperty(Account, repeated=True)

2/ 现在我面临的问题是:当我将 Facebook 帐户添加到我的 HUser 实例时,一切正常;但是,当我将 Twitter 帐户附加到同一个实例并添加一个未在模型中声明的新属性时,问题就出现了,如下所示:

for account in huser.accounts:
    if account.state == "state_we_re_looking_for" and account.account_type == 'tw':
        # we found the appropriate Twitter account reference
        account.access_token_secret = "..." # store the access token secret fetched from Twitter API
        huser.put() # save to the Datastore
        break

这个操作应该将访问令牌秘密保存在我的User 的 Twitter Account 实例中,但实际上它保存在 Facebook Account 实例中(索引为 0 )!

我做错了什么?

谢谢。

【问题讨论】:

    标签: python google-app-engine google-cloud-datastore app-engine-ndb


    【解决方案1】:

    这是 ndb 如何存储StructuredProperty 的一个基本问题。 Datastore 目前没有办法存储这个,所以 ndb 基本上会爆炸你的属性。

    例如,考虑实体:

    HUser(email='test@example.com'.
          accounts=(Account(type='fb',
                            account_id='1',
                            state='1',
                            access_token='1'),
                    Account(type='tw',
                            account_id='2',
                            state='2',
                            access_token='2',
                            access_token_secret='2')))
    

    这实际上将存储在一个如下所示的实体中:

    {
     email : 'test@example.com',
     accounts.type : ['fb', 'tw'],
     accounts.account_id : ['1', '2'],
     accounts.state : ['1', '2'],
     accounts.access_token : ['1', '2'],
     accounts.access_token_secret : ['2']
    }
    

    因为您使用的是ndb.Expando,ndb 不知道它应该用None 填充facebook 帐户的access_token_secret 字段。当 ndb 重新填充您的实体时,它会为它看到的第一个帐户(即 facebook 帐户)填写 access_token_secret

    重组您的数据听起来像是解决此问题的正确方法,但您可能希望让您的 HUser 成为该 Account 的祖先 HUser,以便您使用 strong consistency 查询用户的帐户。

    【讨论】:

    • 哇...我正在考虑选择我自己的答案作为正确的答案,但是您的答案要详细得多,谢谢!至于你的最后一个建议,我会研究它,但我有一个问题:如果我创建 Account 实体并具有对相应 HUser 的祖先引用,我的 Account 模型应该变成什么?我想我可以安全地删除我在回答中谈到的KeyProperty,但是我应该声明任何关于祖先的事情吗?如果没有,那么就意味着祖先不是强制性的,我以后可能会忘记设置一个?
    • 实际上没有任何方法可以声明祖先而不是在创建帐户时设置它。没错,这可能更容易出错(因为您不能将其标记为强制)。但是此时您应该使用如下查询访问用户的所有帐户:Account.query(ancestor=huser.key)。因此,如果您忘记设置祖先,您应该注意到这种新类型的帐户不会出现。更重要的是,这具有很强的一致性,因此您可以保证始终获得一个用户的所有帐户。
    • 另一个想法——你总是可以向你的 HUser 模型添加一个方法来添加一个帐户。至少,Accounts 将始终在HUser 的上下文中创建。类似 github.com/proppy/appengine-todos-flask/commit/… 的东西(抱歉链接到正在进行的代码)。
    • 非常感谢您提供这些详细信息,非常感谢!
    【解决方案2】:

    据我了解,App Engine NDB 似乎不支持包含 Expando 实体本身的 Expando 实体。

    一开始我没有意识到我的HUser 模型继承自Google 的User 类,这正是Expando 模型!

    因此,我什至不知道,我试图将重复的 StructuredPropertyExpando 对象放在另一个 Expando 中,这似乎 不受支持(我没有清楚地找到任何东西但是,写在这个限制上)。

    解决方案是以不同的方式设计数据模型。我将我的Account 对象放在一个单独的实体类型中(这一次,它们是真正的Expando 对象!),我添加了一个KeyProperty 来引用HUser 实体。这涉及到更多的读/写操作,但是代码实际上现在阅读起来要简单得多...

    我会将我自己的问题标记为已回答,除非有人对此处发现的限制有其他有趣的意见。

    【讨论】:

    • 我认为它被提及。 developers.google.com/appengine/docs/python/ndb/… 。如果我是正确的并正确理解您的情况
    • 你指的是黄色段吗?它确实说您不能将重复的StructuredProperty 嵌套在另一个重复的StructuredProperty 中,但没有提及Expando 模型。还是我瞎了? :)
    • 对不起,可能是我的错误参考。将尝试复制它。
    • 感谢查看,但无需转载:看@Patrick Costello的详细解释。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-07-12
    • 2011-03-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-09
    相关资源
    最近更新 更多