在Django中更改模型名称时,为什么必须声明“ atomic = False”?

时间:2018-08-04 01:15:22

标签: python django sqlite

好的,所以我在official documentation上关注Django 2.0教程 当我意识到我将模型命名为“问题”而不是“问题”时。 (我是Django的新手)

我已经做过

$ python manage.py makemigrations polls
$ python manage.py migrate 

所以我想我可以重复一遍以应用更改。

Django问我是否要重命名,所以我同意。

(venv) H:\PycharmProjects\django_tutorials\mysite>python manage.py makemigrations
Did you rename the polls.Questions model to Question? [y/N] y
Migrations for 'polls':
  polls\migrations\0002_auto_20180804_0935.py
    - Rename model Questions to Question

但是当我尝试迁移时,Django不会运行迁移并向我显示此错误。

(venv) H:\PycharmProjects\django_tutorials\mysite>python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, polls, sessions
Running migrations:
  Applying polls.0001_initial... OK
  Applying polls.0002_auto_20180804_0935...Traceback (most recent call last):
  File "manage.py", line 15, in <module>
    execute_from_command_line(sys.argv)
  File "H:\PycharmProjects\django_tutorials\venv\lib\site-packages\django\core\management\__init__.py", line 381, in execute_from_command_line
    utility.execute()
  File "H:\PycharmProjects\django_tutorials\venv\lib\site-packages\django\core\management\__init__.py", line 375, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "H:\PycharmProjects\django_tutorials\venv\lib\site-packages\django\core\management\base.py", line 316, in run_from_argv
    self.execute(*args, **cmd_options)
  File "H:\PycharmProjects\django_tutorials\venv\lib\site-packages\django\core\management\base.py", line 353, in execute
    output = self.handle(*args, **options)
  File "H:\PycharmProjects\django_tutorials\venv\lib\site-packages\django\core\management\base.py", line 83, in wrapped
    res = handle_func(*args, **kwargs)
  File "H:\PycharmProjects\django_tutorials\venv\lib\site-packages\django\core\management\commands\migrate.py", line 203, in handle
    fake_initial=fake_initial,
  File "H:\PycharmProjects\django_tutorials\venv\lib\site-packages\django\db\migrations\executor.py", line 117, in migrate
    state = self._migrate_all_forwards(state, plan, full_plan, fake=fake, fake_initial=fake_initial)
  File "H:\PycharmProjects\django_tutorials\venv\lib\site-packages\django\db\migrations\executor.py", line 147, in _migrate_all_forwards
    state = self.apply_migration(state, migration, fake=fake, fake_initial=fake_initial)
  File "H:\PycharmProjects\django_tutorials\venv\lib\site-packages\django\db\migrations\executor.py", line 244, in apply_migration
    state = migration.apply(state, schema_editor)
  File "H:\PycharmProjects\django_tutorials\venv\lib\site-packages\django\db\migrations\migration.py", line 124, in apply
    operation.database_forwards(self.app_label, schema_editor, old_state, project_state)
  File "H:\PycharmProjects\django_tutorials\venv\lib\site-packages\django\db\migrations\operations\models.py", line 330, in database_forwards
    new_model._meta.db_table,
  File "H:\PycharmProjects\django_tutorials\venv\lib\site-packages\django\db\backends\sqlite3\schema.py", line 84, in alter_db_table
    ) % old_db_table)
django.db.utils.NotSupportedError: Renaming the 'polls_questions' table while in a transaction is not supported on SQLite because it would break referential integrity. Try adding `atomic = False` to the Migration class.

我用Google搜索,one Stackoverflow question说我应该添加atomic=False,但他从未解释原因。

我还在official documentation上搜索了“非原子迁移”,但仍然看不到为什么需要简单的模型名称更改。 (说实话,很难理解我的意思。)

真的应该这样来更改型号名称吗?我做错了什么吗?

  

非原子迁移

     

在支持DDL事务的数据库上   (SQLite和PostgreSQL),迁移将通过   默认。对于诸如在大型计算机上执行数据迁移的用例   表中,您可能要阻止迁移在   通过将atomic属性设置为False进行交易:

2 个答案:

答案 0 :(得分:0)

TL; DR

您可以在SQLite上set atomic=Falseuse PostgreSQL, MySQL进行原子表重命名。

详细信息

也许您已经知道,此错误在django\db\backends\sqlite3\schema.py", line 84, in alter_db_table处出现

如果您想知道为什么可以看到此代码。 (这将覆盖base/schema.pyhttps://github.com/django/django/blob/master/django/db/backends/sqlite3/schema.py#L77

def alter_db_table(self, model, old_db_table, new_db_table, disable_constraints=True):
    if disable_constraints and self._is_referenced_by_fk_constraint(old_db_table):
        if self.connection.in_atomic_block:
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ # if checked atomic, raise NotSupportedError
            raise NotSupportedError((
                'Renaming the %r table while in a transaction is not '
                'supported on SQLite because it would break referential '
                'integrity. Try adding `atomic = False` to the Migration class.'
            ) % old_db_table)
        self.connection.enable_constraint_checking()
        super().alter_db_table(model, old_db_table, new_db_table)
        self.connection.disable_constraint_checking()
    else:
        super().alter_db_table(model, old_db_table, new_db_table)

然后您可以比较默认的Postgresql或Mysql(它们不会覆盖base/schema.pyhttps://github.com/django/django/blob/master/django/db/backends/base/schema.py#L399

def alter_db_table(self, model, old_db_table, new_db_table):
    """Rename the table a model points to."""
    if (old_db_table == new_db_table or
        (self.connection.features.ignores_table_name_case and
            old_db_table.lower() == new_db_table.lower())):
        return
    self.execute(self.sql_rename_table % {
        "old_table": self.quote_name(old_db_table),
        "new_table": self.quote_name(new_db_table),
    })
    # Rename all references to the old table name.
    for sql in self.deferred_sql:
        if isinstance(sql, Statement):
            sql.rename_table_references(old_db_table, new_db_table)

摘要
Django的Sqlite3不允许原子选项重命名表。
您可以使用Django的Postgresql,Mysql等(允许原子表重命名)。

答案 1 :(得分:0)

简短答案:

是的,这是更改型号名称的方法。您做对了,就像您提到的non-atomic migrations中所述。

详细答案:

来自SQLite Tutorial

SQLite是一个事务数据库,所有更改和查询都可以 原子的,一致的,隔离的和持久的(ACID)。

Atomic表示当您提交事务时,整个 交易是否适用。

默认情况下,SQLite在自动提交模式下运行。这意味着 每个命令,SQLite都会启动,处理和提交事务 自动

现在假设您的数据库中有几个表,您决定重命名其中一个表。 SQLite自动启动事务,并在该事务中尝试重命名表。但是,例如,您的表被其他表引用,因此引发了异常(如您从@EunChong Lee先前提供的代码中可以看到的那样。)

if self.connection.in_atomic_block:
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ # if checked atomic, raise NotSupportedError
        raise NotSupportedError((
            'Renaming the %r table while in a transaction is not '
            'supported on SQLite because it would break referential '
            'integrity. Try adding `atomic = False` to the Migration class.'
        ) % old_db_table)

因此,当您在迁移文件中使用行atomic = False时:

from django.db import migrations

class Migration(migrations.Migration):
    atomic = False

您可以跳过此异常,然后继续重命名表,是的,对我来说看起来并不那么好,但是很好。

幕后发生的事情对我来说也不是很清楚,我想人们应该更多地研究django\db\backends\sqlite3\schema.py中的代码,以更深入地了解如何用atomic=False固定引用以及在什么情况下进行固定。在实施更改时,我们应该更加小心。