LoginSignup
14
14

More than 5 years have passed since last update.

Django 1.7:リリースノート:スキーママイグレーション関連

Posted at

データベース初期化

startproject:

(v17)hdknr@wzy:~/ve/v17/src/sample$ mkdir -p web; django-admin.py startproject app web ;cd web

migrate & createsuperuser :

(v17)hdknr@wzy:~/ve/v17/src/sample/web$ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, contenttypes, auth, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying sessions.0001_initial... OK


(v17)hdknr@wzy:~/ve/v17/src/sample/web$ python manage.py createsuperuser
Username (leave blank to use 'hdknr'): admin
Email address: admin@admin.admin
Password: 
Password (again): 
Superuser created successfully.

syncdbも一応使える:

(v17)hdknr@wzy:~/ve/v17/src/sample/web$ rm db.sqlite3 ; python manage.py syncdb
Operations to perform:
  Apply all migrations: admin, contenttypes, auth, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying sessions.0001_initial... OK

You have installed Django's auth system, and don't have any superusers defined.
Would you like to create one now? (yes/no): yes
Username (leave blank to use 'hdknr'): admin
Email address: admin@admin.admin
Password: 
Password (again): 
Superuser created successfully.

モデルマイグレーション

makemigrations:

(v17)hdknr@wzy:~/ve/v17/src/sample/web$ python manage.py makemigrations 
No changes detected

アプリ追加

(v17)hdknr@wzy:~/ve/v17/src/sample/web$ python manage.py startapp todos
(v17)hdknr@wzy:~/ve/v17/src/sample/web$ tree todos/
todos/
├── __init__.py
├── admin.py
├── migrations
│   └── __init__.py
├── models.py
├── tests.py
└── views.py

1 directory, 6 files

(v17)hdknr@wzy:~/ve/v17/src/sample/web$ echo "INSTALLED_APPS += ('todos',)" >> app/settings.py

モデルクラス無いので追加:

(v17)hdknr@wzy:~/ve/v17/src/sample/web$ python manage.py makemigrations
No changes detected

(v17)hdknr@wzy:~/ve/v17/src/sample/web$ cat todos/models.py

from django.db import models
from django.contrib.auth.models import User


class Todo(models.Model):
    user = models.ForeignKey(User)
    title = models.CharField(max_length=50)
    description = models.TextField()
    due_at = models.DateTimeField(
        null=True, blank=True, default=None)
    created_at = models.DateTimeField(auto_now_add=True, )
    updated_at = models.DateTimeField(auto_now=True, )

makemigrations:

(v17)hdknr@wzy:~/ve/v17/src/sample/web$ python manage.py makemigrations todos

Migrations for 'todos':
  0001_initial.py:
    - Create model Todo

(v17)hdknr@wzy:~/ve/v17/src/sample/web$ tree todos/

todos/
├── __init__.py
├── __init__.pyc
├── admin.py
├── admin.pyc
├── migrations
│   ├── 0001_initial.py
│   ├── __init__.py
│   └── __init__.pyc
├── models.py
├── models.pyc
├── tests.py
└── views.py

1 directory, 11 files

migrate:

(v17)hdknr@wzy:~/ve/v17/src/sample/web$ python manage.py migrate todos
Operations to perform:
  Apply all migrations: todos
Running migrations:
  Applying todos.0001_initial... OK        


(v17)hdknr@wzy:~/ve/v17/src/sample/web$ echo ".schema todos_todo" | python manage.py dbshell

CREATE TABLE "todos_todo" (
    "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, 
    "title" varchar(50) NOT NULL, 
    "description" text NOT NULL, 
    "due_at" datetime NULL, 
    "created_at" datetime NOT NULL, 
    "updated_at" datetime NOT NULL, 
    "user_id" integer NOT NULL REFERENCES "auth_user" ("id"));

CREATE INDEX todos_todo_e8701ad4 ON "todos_todo" ("user_id");

django_migrations

マイグレーションテーブル:

$ echo ".schema django_migrations" | python manage.py dbshell

CREATE TABLE "django_migrations" (
    "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, 
    "app" varchar(255) NOT NULL, 
    "name" varchar(255) NOT NULL, 
    "applied" datetime NOT NULL);

(v17)hdknr@wzy:~/ve/v17/src/sample/web$ echo "select * from django_migrations;" | python manage.py dbshell

1|contenttypes|0001_initial|2014-09-03 02:10:35.695671
2|auth|0001_initial|2014-09-03 02:10:35.744375
3|admin|0001_initial|2014-09-03 02:10:35.788881
4|sessions|0001_initial|2014-09-03 02:10:35.808466
5|todos|0001_initial|2014-09-03 02:10:35.855269
6|todos|0002_todo_closed|2014-09-03 02:11:12.687727

モジュール:

(v17)hdknr@wzy:~/ve/v17/src/sample/web$ tree $VIRTUAL_ENV/lib/python2.7/site-packages/django/db/migrations/ -I "*.pyc"
/home/hdknr/ve/v17/lib/python2.7/site-packages/django/db/migrations/
├── __init__.py
├── autodetector.py
├── executor.py
├── graph.py
├── loader.py
├── migration.py
├── operations
│   ├── __init__.py
│   ├── base.py
│   ├── fields.py
│   ├── models.py
│   └── special.py
├── optimizer.py
├── questioner.py
├── recorder.py
├── state.py
└── writer.py

1 directory, 16 files

shellから:

>>> from django.db.migrations.recorder import *
>>> for i in MigrationRecorder.Migration.objects.values():
...     print i
... 
{'applied': datetime.datetime(2014, 9, 3, 2, 10, 35, 695671, tzinfo=<UTC>), 'app': u'contenttypes', u'id': 1, 'name': u'0001_initial'}
{'applied': datetime.datetime(2014, 9, 3, 2, 10, 35, 744375, tzinfo=<UTC>), 'app': u'auth', u'id': 2, 'name': u'0001_initial'}
{'applied': datetime.datetime(2014, 9, 3, 2, 10, 35, 788881, tzinfo=<UTC>), 'app': u'admin', u'id': 3, 'name': u'0001_initial'}
{'applied': datetime.datetime(2014, 9, 3, 2, 10, 35, 808466, tzinfo=<UTC>), 'app': u'sessions', u'id': 4, 'name': u'0001_initial'}
{'applied': datetime.datetime(2014, 9, 3, 2, 10, 35, 855269, tzinfo=<UTC>), 'app': u'todos', u'id': 5, 'name': u'0001_initial'}
{'applied': datetime.datetime(2014, 9, 3, 2, 11, 12, 687727, tzinfo=<UTC>), 'app': u'todos', u'id': 6, 'name': u'0002_todo_closed'}

pre_migrate/ post_migrate

todos/models.py 修正:

from django.dispatch import receiver
from django.db.models.signals import (
    pre_migrate, post_migrate,
)


def trace(msg, sender, app_config, *args, **kwargs):
    assert sender == app_config
    assert args == ()
    if app_config and app_config.name == "todos":
        print msg 
        for k, v in kwargs.items():
            print "***", k, type(v), v


@receiver(pre_migrate)
def pre_migrate_handler(
    sender=None, app_config=None, *args, **kwargs
):
    trace(
        ">>> pre_migrate signal",
        sender, app_config, *args, **kwargs)


@receiver(post_migrate)
def post_migrate_handler(
    sender=None, app_config=None, *args, **kwargs
):
    trace(
        ">>> post_migrate signal",
        sender, app_config, *args, **kwargs)


class Todo(models.Model):
    closed = models.BooleanField(default=False)
    ....    

makemigrations & migrate:

(v17)hdknr@wzy:~/ve/v17/src/sample/web$ python manage.py makemigrations todos

Migrations for 'todos':
  0002_todo_closed.py:
    - Add field closed to todo

(v17)hdknr@wzy:~/ve/v17/src/sample/web$ python manage.py migrate todos

Operations to perform:
  Apply all migrations: todos
Running migrations:
  Applying todos.0002_todo_closed... OK
>>> post_migrate signal
*** using <type 'str'> default
*** verbosity <type 'int'> 1
*** signal <class 'django.dispatch.dispatcher.Signal'> <django.dispatch.dispatcher.Signal object at 0x25ba110>
*** interactive <type 'bool'> True

Database router の allow_migrateメソッド

allow_syncdb -> allow_migrateの変更。

既存のXOOPSのデータベースを読むようにrouteを設定:

(v17)hdknr@wzy:~/ve/v17/src/sample/web$ more app/database.py

_EXCLUSIVE = lambda obj: \
    {"xoops": "xoops"}.get(obj._meta.app_label, 'default')


class SitesRouter(object):

    def db_for_read(self, model, **hints):
        return _EXCLUSIVE(model)

    def db_for_write(self, model, **hints):
        return _EXCLUSIVE(model)

    def allow_relation(self, obj1, obj2, **hints):
        d1, d2 = _EXCLUSIVE(obj1), _EXCLUSIVE(obj2)

        return None if d1 != d2 else d1

    def allow_migrate(self, db, model):
        ''' allow_syncdb -> allow_migrate '''
        return _EXCLUSIVE(model)

app/settings.py:

INSTALLED_APPS += ('todos', 'xoops',)
#
DATABASES['xoops'] = {
    'ENGINE': 'django.db.backends.mysql',
    'NAME': os.environ['XOOPS_DBNAME'],
    'USER': os.environ['XOOPS_DBUSER'],
    'PASSWORD': os.environ['XOOPS_DBPASSWORD'],
    'TEST_CHARSET': 'utf8',
    'TEST_DATABASE_COLLATION': 'utf8_general_ci',
}
DATABASE_ROUTERS = ['app.database.SitesRouter']

migrateしてみる:

(v17)hdknr@wzy:~/ve/v17/src/sample/web$ python manage.py migrate

Operations to perform:
  Synchronize unmigrated apps: xoops
  Apply all migrations: admin, contenttypes, todos, auth, sessions
Synchronizing apps without migrations:

>>> pre_migrate signal
*** using <type 'str'> default
*** verbosity <type 'int'> 1
*** signal <class 'django.dispatch.dispatcher.Signal'> <django.dispatch.dispatcher.Signal object at 0x27a4190>
*** interactive <type 'bool'> True

  Creating tables...
  Installing custom SQL...
  Installing indexes...
Running migrations:
  No migrations to apply.

>>> post_migrate signal
*** using <type 'str'> default
*** verbosity <type 'int'> 1
*** signal <class 'django.dispatch.dispatcher.Signal'> <django.dispatch.dispatcher.Signal object at 0x27a41d0>
*** interactive <type 'bool'> True

initial_data.json

リリースノート:

initial_data fixtures are no longer loaded for apps with migrations; 
if you want to load initial data for an app, we suggest you do it in a migration.

initial_data.json:

[{"fields": {"description": "task1", "title": "task1", 
  "due_at": null, "created_at": "2014-09-03T03:10:37.059Z", 
  "updated_at": "2014-09-03T03:10:37.059Z", "user": 1, "closed": false}, 
  "model": "todos.todo", "pk": 1}] 

削除:

(v17)hdknr@wzy:~/ve/v17/src/sample/web$ echo "delete from todos_todo;" | python  manage.py dbshell

(v17)hdknr@wzy:~/ve/v17/src/sample/web$ echo "select count(*) from todos_todo;" | python manage.py dbshell
0

todosをmigrate:

(v17)hdknr@wzy:~/ve/v17/src/sample/web$ python manage.py migrate todos
Operations to perform:
  Apply all migrations: todos
Running migrations:
  No migrations to apply.

(v17)hdknr@wzy:~/ve/v17/src/sample/web$ echo "select count(*) from todos_todo;" | python manage.py dbshell
0

全体:

(v17)hdknr@wzy:~/ve/v17/src/sample/web$ python manage.py migrate
Operations to perform:
  Synchronize unmigrated apps: xoops
  Apply all migrations: admin, contenttypes, todos, auth, sessions
Synchronizing apps without migrations:
  Creating tables...
  Installing custom SQL...
  Installing indexes...
Installed 1 object(s) from 1 fixture(s)
Running migrations:
  No migrations to apply.

(v17)hdknr@wzy:~/ve/v17/src/sample/web$ echo "select count(*) from todos_todo;" | python manage.py dbshell
1

todos/fixtures/initial_data.json:

(v17)hdknr@wzy:~/ve/v17/src/sample/web$ python manage.py dumpdata  > todos/fixtures/initial_data.json

migrateで読まない:

(v17)hdknr@wzy:~/ve/v17/src/sample/web$ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, contenttypes, todos, auth, sessions
Running migrations:
  No migrations to apply.

testsだと読まれるが、テーブルが無いのでエラー:

(v17)hdknr@wzy:~/ve/v17/src/sample/web$ python manage.py test todos

....

django.db.utils.OperationalError: 
Problem installing fixture '/home/hdknr/ve/v17/src/sample/web/todos/fixtures/initial_data.json': 
Could not load contenttypes.ContentType(pk=1): no such table: django_content_type

プロジェクトルートにあってもエラー:

(v17)hdknr@wzy:~/ve/v17/src/sample/web$ mv todos/fixtures/initial_data.json  .
(v17)hdknr@wzy:~/ve/v17/src/sample/web$ python manage.py test todos

....

django.db.utils.OperationalError: 
Problem installing fixture '/home/hdknr/ve/v17/src/sample/web/initial_data.json': 
Could not load contenttypes.ContentType(pk=1): no such table: django_content_type

よって, initial_data.json は使わないでloadata で明示的にロードしたほうがいいのだろうか。

TransactionTestCase

TODO:

TransactionTestCaseをつかったことないのないのであとで調べる

リリースノート:

Test rollback behavior is different for apps with migrations; in particular, 
Django will no longer emulate rollbacks on non-transactional databases 
or inside TransactionTestCase unless specifically requested.

Rollback emulation:

Any initial data loaded in migrations will only be available in TestCase tests 
and not in TransactionTestCase tests, 
and additionally only on backends 
where transactions are supported (the most important exception being MyISAM).

Django can reload that data for you on a per-testcase basis 
by setting the serialized_rollback option to True 
in the body of the TestCase or TransactionTestCase, 
but note that this will slow down that test suite by approximately 3x.

Third-party apps or those developing against MyISAM will need to set this; 
in general, however, 
you should be developing your own projects against a transactional database 
and be using TestCase for most tests, and thus not need this setting.

The initial serialization is usually very quick, 
but if you wish to exclude some apps from this process 
(and speed up test runs slightly), 
you may add those apps to TEST_NON_SERIALIZED_APPS.

Apps without migrations are not affected; 
initial_data fixtures are reloaded as usual.

依存

アプリがマイグレーションがあるアプリケーション依存するならば、
かならずマイグレーションを持つようにする事。

It is not advised to have apps 
without migrations depend on 
(have a ForeignKey or ManyToManyField to) apps with migrations. 

Read the dependencies documentation for more.

South

southからのマイグレーション。

  • 簡単 (リファレンス)

    • Southのマイグレーションが最新であることを確認
    • 'south' をINSTALLED_APPSから削除
    • migrations配下の数字で始まるファイルを削除。init.pyは残す。.pycファイルは削除。
    • makemigrations を実行。
    • migrate を実行。
14
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
14