检测应用引擎数据存储模型属性变化

0 投票
4 回答
900 浏览
提问于 2025-04-18 17:21

如何在每次数据存储实体属性变化时触发方法调用?

我查找过的一种方法是修改 db.Model.put 方法。这意味着我要重写 put 方法。虽然这样可以让我对每次 put() 操作做出反应,但我不太清楚如何检测地址属性是否发生了变化,因为在 .put() 的开始,self.address 已经被设置好了。

详细说明:

我有用户,每个用户都有一个实际的地址。

class User(db.Model):
    ...
    address = db.StringProperty() # for example "2 Macquarie Street, Sydney"
    ...

我想验证输入的地址是否正确。为此,我有一个比较复杂的地址检查函数(它会联系一个远程的 API)和一个布尔字段。

class User(db.Model):
    ...
    address = db.StringProperty()
    address_is_valid = db.BooleanProperty(default=False)

    def address_has_changed(self):
        self.address_is_valid = False
        Task(
            url = "/check_address", # this would later set .address_is_valid 
            params = {
                'user' : self.key()
            }
        ).add()
    ...

但是,我该如何让 address_has_changed 方法在每次地址变化时都能触发,而不需要到处显式调用它呢?

# It should work when changing an address
some_user = User.all().get()
some_user.address = "Santa Claus Main Post Office, FI-96930 Arctic Circle"
some_user.put()

# It should also work when multiple models are changed
...
db.put([some_user, another_user, yet_another_user])

# It should even work when creating a user
sherlock = User(address='221 B Baker St, London, England')
sherlock.put() # this should trigger address_has_changed

4 个回答

1

你提到的Nick的文章和ndb钩子并不能解决跟踪实体明确变化的问题,它们只是让解决这个问题变得更简单。

通常情况下,你会在预存储钩子(pre put hook)里面调用你的address_is_changed方法,而不是在代码的各个地方都调用put()。

我有一些代码,利用这些钩子策略来记录每次记录的变化。

不过,你的代码实际上并没有检测到地址的变化。

你可以考虑切换到ndb,然后使用post_get hook(把你想要检查的原始属性值存起来,比如在会话或请求对象中),接着用pre_put_hook来检查当前属性和原始值,看看是否需要采取任何行动,然后再调用你的address_has_changed方法。你也可以使用这个策略在db上(按照Nick的文章),但那样你就得自己做更多的工作。

1

使用Python的属性功能。这样一来,每当地址真的发生变化时,就可以很方便地调用address_has_changed这个方法。

3

那什么是钩子呢?

NDB提供了一种轻量级的钩子机制。通过定义一个钩子,应用程序可以在某些操作之前或之后运行一些代码;比如,一个模型在每次执行get()之前可能会运行某个函数。

from google.appengine.ext import ndb

class Friend(ndb.Model):
  name = ndb.StringProperty()

  def _pre_put_hook(self):
    # inform someone they have new friend

  @classmethod
  def _post_delete_hook(cls, key, future):
    # inform someone they have lost a friend

f = Friend()
f.name = 'Carole King'
f.put() # _pre_put_hook is called
fut = f.key.delete_async() # _post_delete_hook not yet called
fut.get_result() # _post_delete_hook is called

你可以加入一些额外的逻辑,这样就可以检查地址的原始版本和新版本,如果它们不同,就执行比较复杂的操作,否则就直接保存。

2

好吧,这个回答可能来得有点晚,差不多两年了,但无论如何还是要说一下。你可以在 __init()__ 方法里创建一些类的局部变量,用来存储旧的值。在你调用 put 方法的时候,可以把这些旧变量里的值进行比较。

class User(db.Model):

    def __init__(self, *args, **kwargs):
        super(User, self).__init__(*args, **kwargs)
        self._old_address = self.address

    address = db.StringProperty() 

    def put(self, **kwargs):
        if self._old_address != self.address:
             # ...
             # do your thing
             # ...

        super(User, self).put(**kwargs)

撰写回答