在syncdb期间阻止代码运行

1 投票
2 回答
520 浏览
提问于 2025-04-15 11:58

我有一些代码,导致syncdb出错(因为它在表格创建之前就试图访问模型)。

有没有办法让这些代码在syncdb时不执行?类似于:

if not syncdb:
    run_some_code()

谢谢 :)

补充说明: 顺便说一下,我考虑过使用post_init信号……对于访问数据库的代码,这样做好吗?

更多信息

这是应要求提供的更多信息 :)

我遇到过几次这种情况,比如……我在修改django-cron时,发现有必要确保在加载django时没有现有的任务(因为它会搜索所有已安装的应用程序中的任务,并在加载时自动添加它们)。

所以我在__init__.py文件的顶部添加了以下代码:

import sqlite3

try:
        # Delete all the old jobs from the database so they don't interfere with this instance of django
        oldJobs = models.Job.objects.all()
        for oldJob in oldJobs:
                oldJob.delete()
except sqlite3.OperationalError:
        # When you do syncdb for the first time, the table isn't 
        # there yet and throws a nasty error... until now
        pass

显而易见,这样做不好。它和sqlite绑定在一起,我相信还有更好的地方可以放这段代码(这只是我碰到这个问题的方式),但它确实有效。

如你所见,出现的错误是操作错误(在sqlite中),堆栈跟踪显示类似“找不到表django_cron_job”的信息。

解决方案

最终的目标是在任何页面加载之前运行一些代码

这可以通过在urls.py文件中执行来实现,因为它必须在页面可以被服务之前被导入(显然)。

这样我就能去掉那个丑陋的try/except块了 :) 感谢上帝(还有S. Lott)

2 个回答

2

在模块级别尝试访问模型(在它们被创建之前)几乎只能存在于模块的顶层;这需要在导入模块时执行的代码,正如你的例子所示。这就是你猜测的,syncdb 失败的原因。它试图导入模块,但导入模块的过程会导致应用程序级别的代码执行;可以说是一种“副作用”。

在 Python 中,避免模块导入导致副作用的愿望非常强烈,因此 if __name__ == '__main__': 这种约定在可执行的 Python 脚本中变得非常普遍。当仅仅加载一个代码库就开始执行应用程序时,麻烦就来了 :-)

对于 Django 应用来说,这不仅仅是个麻烦。想象一下,每次导入模块时都会执行 oldJob.delete() 的后果。虽然在使用 Django 开发服务器时,它似乎只执行了一次,但在生产环境中,它会被执行得相当频繁。例如,如果你使用 Apache,Apache 会经常启动几个子进程来处理请求。随着长时间运行的服务器进程,你的 Django 应用会在每次为你的 web 服务器分叉一个处理程序时被重新启动,这意味着模块会被导入,delete() 会被多次调用,且往往是不可预测的。不幸的是,信号也帮不了忙,因为信号可能在每次初始化 Apache 进程时都会被触发。

顺便说一下,不仅仅是 web 服务器可能会导致你的代码意外执行。如果你使用像 epydoc 这样的工具,它们会导入你的代码来生成 API 文档。这反过来会导致你的应用逻辑开始执行,这显然是运行文档解析器时不希望出现的副作用。

因此,像这样的清理代码最好通过 cron 任务来处理,定期检查过期的任务并清理数据库。这个自定义脚本也可以手动运行,或者由任何进程运行(例如在部署期间,或者作为单元测试的 setUp() 函数的一部分,以确保测试运行干净)。无论你怎么做,重要的是,这样的代码应该始终被 明确 执行,而不是因为打开源文件而 隐式 执行。

希望这能帮到你。我知道这没有提供判断 syncdb 是否正在运行的方法,但如果你在设计 Django 应用时考虑到生产部署,syncdb 的问题就会神奇地消失。

4

编辑:顺便说一下,我考虑过使用 post_init 信号……对于访问数据库的代码,这样做好吗?

绝对不行。

如果你在数据库表还没创建之前就有代码在访问模型,那你就有大麻烦了。你可能在做一些非常错误的事情。

通常情况下,你大约只需要运行一次 syncdb。这样数据库就创建好了,你的网页应用就可以使用这个数据库。

有时候,你可能会做一些设计上的修改,这时需要删除并重新创建数据库。然后你的网页应用会长时间使用这个数据库。

一般来说,你不需要在 __init__.py 模块中写代码。几乎不应该在 __init__.py 模块里有执行实际工作的代码。这种情况非常少见,而且不适合 Django。

我不明白你为什么要去搞 __init__.py,而 Django Cron 说你应该在 urls.py 中安排你的调度。


编辑

清除记录是一回事。

__init__.py 和 Django-cron 的 base.py 中乱搞显然是完全错误的做法。如果事情这么复杂,那你就是在做错事。

我无法判断你想做什么,但这应该是很简单的。

你的 urls.py 只能在 syncdb 之后,并且在所有的 ORM 内容都配置并正确绑定之后运行。

例如,你的 urls.py 可以删除一些行,然后再向表中添加一些行。到这个时候,所有的 syncdb 问题都解决了。

为什么你的逻辑不放在 urls.py 中呢?

撰写回答