如何正确使用App Engine Python模型类的自定义__init__?

10 投票
1 回答
4966 浏览
提问于 2025-04-16 01:30

我正在尝试实现一个延迟删除博客文章的方案。也就是说,不再是烦人的你确定吗?提示,而是给你一个2分钟的时间来取消删除。

我想用一个数据库模型类(DeleteQueueItem)来跟踪哪些内容会在什么时候被删除,因为我发现没有办法从队列中删除任务,并且我怀疑可以查询到队列里的内容。

创建一个DeleteQueueItem实体时,应该自动设置一个delete_when属性,并把任务添加到队列中。我使用博客文章的相对路径作为它们的key_name,也想在这里使用这个key_name。这让我需要一个自定义的init方法:

class DeleteQueueItem(db.Model):
    """Model to keep track of items that will be deleted via task queue."""

    # URL path to the blog post is handled as key_name
    delete_when = db.DateTimeProperty()

    def __init__(self, **kwargs):
        delay = 120  # Seconds
        t = datetime.timedelta(seconds=delay)
        deadline = datetime.datetime.now() - t
        key_name = kwargs.get('key_name')

        db.Model.__init__(self, **kwargs)
        self.delete_when = deadline

        taskqueue.add(url='/admin/task/delete_page', 
                      countdown=delay,
                      params={'path': key_name})

这似乎可以正常工作,直到我尝试删除这个实体:

fetched_item = models.DeleteQueueItem.get_by_key_name(path)

这时出现了错误:

TypeError: __init__() takes exactly 1 non-keyword argument (2 given)

我到底哪里做错了?

1 个回答

15

一般来说,你不应该尝试去重写模型类中的init方法。虽然这样做是可能的,但正确的构造函数行为相当复杂,而且在不同版本之间可能会有所变化,这样可能会导致你的代码出错(虽然我们尽量避免这种情况!)。其中一个原因是,构造函数不仅要被你自己的代码用来创建新的模型,还要被框架用来从数据存储中恢复模型。

一个更好的方法是使用工厂方法,也就是你调用这个方法来代替构造函数。

另外,你可能希望在写入实体的同时添加任务,而不是在创建时添加。如果不这样做,你可能会遇到竞争条件:任务可能会在你把新实体存储到数据存储之前就执行了!

这里有一个建议的重构方式:

class DeleteQueueItem(db.Model):
    """Model to keep track of items that will be deleted via task queue."""

    # URL path to the blog post is handled as key_name
    delete_when = db.DateTimeProperty()

    @classmethod
    def new(cls, key_name):
        delay = 120  # Seconds
        t = datetime.timedelta(seconds=delay)
        deadline = datetime.datetime.now() - t

        return cls(key_name=key_name, delete_when=deadline)

    def put(self, **kwargs):
      def _tx():
        taskqueue.add(url='/admin/task/delete_page', 
                      countdown=delay,
                      params={'path': key_name},
                      transactional=True)
        return super(DeleteQueueItem, self).put(**kwargs)
      if not self.is_saved():
        return db.run_in_transaction(_tx)
      else:
        return super(DeleteQueueItem, self).put(**kwargs)

撰写回答