如何正确使用App Engine Python模型类的自定义__init__?
我正在尝试实现一个延迟删除博客文章的方案。也就是说,不再是烦人的你确定吗?提示,而是给你一个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)